%The function runSimulation.m runds a fed-batch fermentation simulation
%using the ODE sover ode15s
%It uses the model FedBatchModel_EH.m, stores additional calculated outputs
%over time and returns the time vector t, solution matrix y, updated
%parameter updatedParams and extra outputs

function [t, y, updatedParams, extraOutputs] = runSimulation(params)

    %ODE solver options with tight relative and absolute tolerances and an
    %termination function TerminationFunction.m
    options = odeset('RelTol', 1e-8, 'AbsTol', 1e-8, 'Events', @TerminationFunction);

    %Preallocate for storing extra outputs
    extraOutputs.my_s = []; %growth rate
    extraOutputs.Y_x_s = []; %conversion of substrate to biomass
    extraOutputs.F = []; %Substrate feeding rate
    extraOutputs.times = []; %Time points corresponding to extra outputs
    extraOutputs.w = []; %Specific productivity calculated in FedBatchModel_EH.m

    %function to update params and store outputs
    function dydt = odeWrapper(t, y)
        [dydt, params, outputs] = FedBatchModel_EH(t, y, params); %Call FedBatchModel_EH.m

        %Set negative values of w to NaN because of possible numerical
        %instability
        if outputs.w <= 0
            outputs.w = NaN;
        end

        %Set negative values of my_s to NaN because of possible numerical
        %instability
        if outputs.my_s <= 0
            outputs.my_s = NaN;
        end

        % Set negative values of Y_x_s to NaN because of possible numerical
        %instability
        if outputs.Y_x_s <= 0
            outputs.Y_x_s = NaN;
        end

        %Store time and outputs in extraOutputs
        extraOutputs.times = [extraOutputs.times; t];
        extraOutputs.my_s = [extraOutputs.my_s; outputs.my_s];
        extraOutputs.Y_x_s = [extraOutputs.Y_x_s; outputs.Y_x_s];
        extraOutputs.F = [extraOutputs.F; outputs.F];
        extraOutputs.w = [extraOutputs.w; outputs.w];
    end

    %Run the ode solver over 50 hours with 2000 time steps
    [t, y] = ode15s(@(t, y) odeWrapper(t, y), linspace(0, 50*3600, 2000), [params.X_0, params.S_0, params.V_0, params.v_0, params.P_0, params.A_0]', options);
    
    %Return the updated parameter structure
    updatedParams = params;
end