function newsim
% 10.450 Spring 2006
% MIT ChE
% function newsim simulates a process under feedback control

% INSTRUCTIONS:
% This is not an application program with good user I/O.  Rather it is a
% template that you adapt to your needs.  Hence, you must modify this file:
% (1) In the section below, assign values to parameters: set the duration
%     and resolution of the simulation, set the sampling rate, and specify
%     how much history is stored for plotting.  In addition, set controller
%     parameters, sensor limits, valve capacity, and initial conditions.
% (2) Describe the process in subfunction "process_derivative"
% (3) Describe the disturbance variable over the course of simulation 
%     in subfunction "disturbance".
% (4) Describe the set point over the course of the simulation in
%     subfunction "setpoint".

% VARIABLE AND PARAMETER NAMES have been prepared for you.
% physical variables important to the process:
% y: controlled variable
% xd: disturbance variable
% xm: manipulated variable
% y1: another output variable
% (The variable y1 is included to illustrate how this template may be
% expanded to more complex processes.  Further variables may be added in
% a simular manner, as well as further controllers placed on these.)

% scaled variables important to the feedback loop:
% ys: measured value of y 
% ysp: set point value
% xco: output of controller

% -- Each of these variables has a value set at the start of simulation, 
% signified by the appendage _init

% -- Each of these variables has an array of its history, signified by
% the appendage _hist

% parameters important to the feedback loop:
% ymin: lower value of y that sensor can read
% ymax: upper value of y that sensor can read
% Kv: gain of the valve
% Kc: gain of the controller
% TI: integral time of the controller
% TD: derivative time of the controller

% parameters concerning passage of time and attentiveness of the controller:
% time_now: starts at zero and proceeds by steps of time_step
% time_step: the time increment at which the process is calculated
% time_end: the time at which the simulation ends
% history_points: the number of points to hold in the history arrays
% steps_between_samples: number of time steps that pass before controller
% looks computes the error ysp - ys and adjusts its output xco.

% USER INPUT SECTION ++++++++++++++++++++++++++++++++++++++++++++++
% Describe what you are simulating: what are the disturbance, manipulated,
% and controlled variables?  What does the process do?
%
%
%
% Specify labels to be used in the plots (around 15 characters max)
t_label = 'time (s)';
y_label = 'label';
y1_label = '(unused)';
xm_label = 'label';
xd_label = 'label';

% Specify parameters that control the simulation itself:
% Time step for computation of process behavior; disturbance and manipulated
% variables are sampled at this interval, and the response variable is 
% computed from one time to the next.  The units are arbitrary, 
% but should be kept consistent with those of other time specifications, 
% such as controller and process parameters.
% Set time_step according to the time scales of the process and
% disturbances: fast changes need small time steps.
time_step = 1;    % arbitrary time units

% Time to end simulation.  Set long enough to see all the interesting
% transients.
time_end = 100;  % arbitrary time units

% Number of time steps to keep in history for plotting.
history_points = 100;  % dimensionless integer

% The controller samples and reacts when this number of time steps has 
% elapsed.  The minumum steps_between_samples is 1; large integers make 
% coarse control.
steps_between_samples = 2;   % dimensionless integer

% Choose whether to print results to screen: 'ys' or 'no'.  Perhaps useful
% for debugging.
is_print = 'no';

% CONTROLLER TUNING
% Controller dimensionless gain (set to 0 to disable controller)
Kc = 0; % (+ or -, as appropriate) 

% Controller integral time (set to ~10000 to disable I-mode)
TI = 10000;  % arbitrary time units

% Controller derivative time (set to 0 to disable D mode)
TD = 0;  % arbitrary time units

% SENSITIVITY OF THE SENSOR
% Sensor range (sensor produces 0-100% scaled input for controller 
% according to these limits)
% Process-specific lower limit
ymin = 0; % units of response variable
% Process-specific upper limit
ymax = 10; % units of response variable

% SIZE OF VALVE
% valve capacity (translates 0-100% controller output into physical 
% manipulated variable)
% NOTE: air-to-close valve has negative gain.
%       air-to-open valve has positive gain.
Kv = 0.002; % m3 s-1 %out-1   

% The simulation begins with the controlled variable y at setpoint value, 
% so that initial error is zero. However, if the specified initial 
% manipulated and disturbance variables are inconsistent with this value, 
% y will vary with time, from the start.
% Here, specify the initial controller output.  It is the controller bias,
% and sets the initial value of the manipulated variable, as well.
xco_init = 50; % controller output is a scaled (0 - 100%) quantity

% As needed, set the initial value of the noncontrolled output, too.
y1_init = 0; % extra variable for more complicated processes
% END OF USER INPUT ------------------------------------------------

% Calculate other initial conditions by using the subfunctions.
time_now = 0;

% Initial xd is taken from the subfunction that describes disturbance.
xd_init = disturbance(time_now); 

% Initial xm is determined by the initial controller output.
xm_init = valve(Kv, xco_init);

% Initial ysp is taken from subfunction that describes set point.
ysp_init = setpoint(time_now); % set point is a scaled (0 - 100%) quantity

% The scaled sensor reading is equal to the scaled set point.
ys_init = ysp_init; 

% y (physical variable) is calculated from ys (scaled variable)
y_init = ys_init*(ymax-ymin)/100. + ymin; % process variable 

% Create arrays for history of the variables; fill them from
% the initial conditions.
sample_time = steps_between_samples * time_step;
time_hist = linspace (-time_step*(history_points-1), 0, history_points);
y_hist = linspace (y_init, y_init, history_points); % physical variable
y1_hist = linspace (y1_init, y1_init, history_points); % extra variable 
ys_hist = linspace (ys_init, ys_init, history_points); % scaled sensor output
ysp_hist = linspace (ysp_init, ysp_init, history_points); % scaled setpt
xco_hist = linspace (xco_init, xco_init, history_points); % scaled controller output
xd_hist = linspace (xd_init, xd_init, history_points); % physical variable
xm_hist = linspace (xm_init, xm_init, history_points); % physical variable

% Define a set of scalars to represent present-time conditions.
% Set these variables to initial conditions before entering the simulation.
y = y_init;
y1 = y1_init;
ys = ys_init;
ysp = ysp_init;
xco = xco_init;
xm = xm_init;
xd = xd_init;

if is_print == 'ys'
    disp '    time    disturb     manip     setpt    control    other'
    disp ([time_now, xd, xm, ysp, y, y1])
end % if

sample_counter = steps_between_samples; % arrange to sample at first time step
is_exit = 'no'; % prepare to stay within controller loop

% Point of view: in this "while loop" we calculate variables around the 
% feedback loop at a newly-updated time_now.  That is, we calculate 
%  y, ys, xco, etc. at this new time.  We obtain previous
% values, as needed, from the history arrays.  The most recent 
% historical value can be found in the last array element, e.g.,
% y_hist(history_points) is the value of y at time_now - time_step.
% The history arrays are updated at the end of the loop.  We are then
% ready to advance the time step for another swing through the loop.
while is_exit == 'no'
    % Advance the time.
    time_now = time_now + time_step;
    
    % Compute the process at this new time.
    % (This step is here because this is a simulated process.)
    [y y1] = process (time_now, time_step, history_points, ...
                      y_hist, y1_hist, xm_hist, xd_hist);
    % NOTE: function "process" returns values for both y and y1.
    
    % Read the sensor; ys returns as scaled (0-100%) variable.
    ys = sensor (y, ymin, ymax);
    
    % Check the set point; ysp is scaled (0-100%) variable
    ysp = setpoint (time_now);
    
    % If it's time, invoke the controller algorithm
    if sample_counter == steps_between_samples
        xco = controller (Kc,TI,TD,history_points,xco_init,... 
            sample_time,steps_between_samples,ys,ysp,...
            xco_hist,ys_hist,ysp_hist);
        sample_counter = 1; % reset the sampling counter
    else
        xco = xco_hist(history_points);   % keep value from previous time step
        sample_counter = sample_counter + 1; % advance the sampling counter
    end
    
    % Output controller signal to valve.
    xm = valve (Kv, xco);
    
    % Update the disturbance.
    % (this step is here because this is a simulated process)
    xd = disturbance (time_now);
    
    % place the present results into the history arrays
    time_hist = time_hist + time_step; % add to every element in array
    y_hist(1:history_points-1)=y_hist(2:history_points); % shift all elements back by one
    y_hist(history_points) = y; % update with most recent value
    y1_hist(1:history_points-1)=y1_hist(2:history_points); % shift all elements back by one
    y1_hist(history_points) = y1; % update with most recent value
    ys_hist(1:history_points-1)=ys_hist(2:history_points); % shift all elements back by one
    ys_hist(history_points) = ys; % update with most recent value
    ysp_hist(1:history_points-1)=ysp_hist(2:history_points); % shift all elements back by one
    ysp_hist(history_points) = ysp; % update with most recent value
    xco_hist(1:history_points-1)=xco_hist(2:history_points); % shift all elements back by one
    xco_hist(history_points) = xco; % update with most recent value
    xm_hist(1:history_points-1)=xm_hist(2:history_points); % shift all elements back by one
    xm_hist(history_points) = xm; % update with most recent value
    xd_hist(1:history_points-1)=xd_hist(2:history_points); % shift all elements back by one
    xd_hist(history_points) = xd; % update with most recent value

    % print current results to screen
    if is_print == 'ys'
        disp '    time    disturb     manip     setpt    control'
        disp ([time_now, xd, xm, ysp, y, y1])
    end % if
    
    if time_now > time_end
        is_exit = 'ys'
    end % if
end % while

% PLOTTING
% Two plots will be made: one in scaled variables, as might be displayed in
% the control room, and a second in physical variables.

% Scaled variables:
y_scale = (y_hist - ymin)/(ymax - ymin) * 100.; % scale the controlled variable

scrsz = get(0,'ScreenSize');
figure('Name','Scaled Variables',...
       'Position',[1 0.3*scrsz(4) 0.45*scrsz(3) 0.45*scrsz(4)] )
subplot (2,1,1);
plot(time_hist,ysp_hist,'c',time_hist,y_scale,'b');
ylabel ('controlled variable');
grid on;

subplot (2,1,2);
plot(time_hist,xco_hist);
ylabel ('controller output');
xlabel (t_label);
grid on;

% Physical variables:
ysp_scale = ysp_hist*(ymax-ymin)/100. + ymin; % render set pt in phys units

figure('Name','Physical Variables',...
       'Position',[0.5*scrsz(3) 0.3*scrsz(4) 0.45*scrsz(3) 0.45*scrsz(4)] )
subplot (4,1,1);
plot(time_hist,ysp_scale,'c',time_hist,y_hist,'b');
ylabel (y_label);
grid on;

subplot (4,1,2);
plot(time_hist,y1_hist);
ylabel (y1_label);
grid on;

subplot (4,1,3);
plot(time_hist,xm_hist);
ylabel (xm_label);
grid on;

subplot (4,1,4);
plot(time_hist,xd_hist);
ylabel (xd_label);
xlabel (t_label);
grid on;

% FINISH - wait for the user to view plots
input('ENTER to conclude');
close all

end % function newsim


function [y y1] = process(...
                      time_now,...
                      time_step,...
                      history_points,...
                      y_hist,...
                      y1_hist,...
                      xm_hist,...
                      xd_hist  )
% Computes y over the past time_step to time_now.
% This function is generic for integrating 2 ODEs; all process description
% is in the subfunction process_derivative.
y_initial = y_hist(history_points);
y1_initial = y1_hist(history_points);
initial_values = [y_initial, y1_initial];
tspan = [time_now - time_step, time_now];
% The MATLAB function ode45 integrates a derivative vector from initial
% values given in init_vals over the time interval tspan.
[t,ycalc] = ode45(@process_derivative,tspan,initial_values,[],...
                  history_points,y_hist,y1_hist,xd_hist,xm_hist);
y = ycalc(size(ycalc,1),1); % get final value on interval
y1 = ycalc(size(ycalc,1),2); % get final value on interval
end % function process


function dydt = process_derivative(...
                      t,...
                      ycalc,...
                      history_points,...
                      y_hist,...
                      y1_hist,...
                      xd_hist,...
                      xm_hist )
% Defines the process in physical variables.  The purpose of this
% subfunction is to calculate the derivatives of the two response
% variables y and y1, where the former is controlled.  The two derivatives
% are stored in the output vector dydt.  The derivatives will generally be
% functions of time t, input variables xd and xm, and output variables 
% y and y1.
% USER INPUT required here ++++++++++++++++++++++++++++++++++++++++
dydt = zeros(2,1); % Increase the row dimension of this array to add variables

% The input variables are assumed to be constant over interval tspan.
dummy_input = xd_hist(history_points); % most recent disturbance variable

% The most recent output variables.
dummy_output = y_hist(history_points); % most recent response variable

% Define parameters.

% Define the derivatives.
dydt(1) = 0; % 
dydt(2) = 0; % define as needed
end % function process_derivative


function xd = disturbance(time_now)
% Describe the disturbance input over time.
% USER INPUT required here ++++++++++++++++++++++++++++++++++++++++
if time_now < 10
    xd = 0.1; % m3 s-1
elseif time_now < 70
        xd = 0.14;
else 
    xd = 0.1; % cm3 s-1
end %if 
end % function disturbance


function ysp = setpoint(time_now)
% The set point is a scaled variable (0-100%).
% USER INPUT required here ++++++++++++++++++++++++++++++++++++++++
if time_now < 40
    ysp = 40;
else
    ysp = 60;
end % if
end % function setpoint


function xco = controller(...
                    Kc,...
                    TI,...
                    TD,...
                    history_points,...
                    xco_init,...
                    sample_time,...
                    steps_between_samples,...
                    ys,...
                    ysp,...
                    xco_hist,...
                    ys_hist,...
                    ysp_hist )
% Computes controller output for input measurement and set point.
% PID or other algorithms can be programmed, as desired.

xco = 0; % controller output will depend on error and parameters
end % function controller


function ys = sensor(y, ymin, ymax)
% The sensor reads the controlled variable and scales it.
ys = (y - ymin)/(ymax - ymin)*100.;
end % function sensor


function xm = valve(Kv, xco)
% The manipulated variable is calculated from the controller output.
    xm = Kv*xco;
end % function valve

