% **************************************************************************
% CASCADE-FEEDFORWARD CONTROL SIMULATOR 
% COPYRIGHT (C) 1994, T.E. MARLIN
%
% FILENAME : CFFSIM.M
% WRITTEN BY : F.BOUDREAU
% VERSION : 2.0
% LAST UPDATED : NOVEMBER 1994
% DESCRIPTION OF FILE :
%    1. INITIALIZE VECTORS.
%    2. CALCULATE CONSTANTS FOR TRANSFER FUNCTIONS IN DISCRETE FORM.
%       NOTE THAT TRANSFER FUNCTIONS ARE DISCRETIZED BY FIRST
%       MULTIPLYING BY A ZERO-ORDER HOLD, AND THEN EXPRESSING IN
%       Z-TRANFORMS.  DEAD TIMES ARE ROUND OFF TO THE NEAREST INTEGER.
%    3. GENERATE MEASUREMENT NOISE.
%    4. PRECALCULATE DISTURBANCES AND FEEDFORWARD CONTROL ACTIONS.
%    5. EXECUTE SIMULATION. THERE ARE TWO SIMULATION LOOPS; ONE FOR
%       PRC2 MODE AND THE OTHER FOR ALL OTHER CASES (SEE COMMENTS IN
%       'SIMULATION LOOPS' SECTION).
%    5. CALCULATE PERFORMANCE INDICES.
%    6. ADD OFFSETS TO VARIABLES THAT WILL BE PLOTTED (THE SIMULATION
%       IS IN DEVIATION VARIABLES).
% ALL FILES IN THIS PACKAGE :
%    CFF     .M  - MAIN MENU AND ENTRY POINT
%    CFFDAT  .M  - EXTERNAL DATA FILE
%    CFFGRAPH.M  - PLOTS SIMULATION RESULTS
%    CFFM1   .M  - MENU FOR PROCESS T.F. EDITING
%    CFFM2   .M  - MENU FOR PID TUNING
%    CFFM3   .M  - MENU FOR DISTURBANCE T.F. EDITING
%    CFFMFF  .M  - FEEDFORWARD CONTROLLER MENU
%    CFFMSIM .M  - SIMULATION OPTIONS MENU
%    CFFNAME .M  - MENU FOR VARIABLES NAMES, INITIAL VALUES, BOUNDS
%    CFFSIM  .M  - EXECUTES SIMULATION
% **************************************************************************


% FLAG THAT INDICATES AT LEAST ONE SIMULATION HAS BEEN RUN:
EXECUTION = 1;

% IF CONTROLLER MODE IS SET TO PRC=2 AND CASCADE IS OFF, RESET TO PRC=0:
if CASCADE == 0 & PRC == 2
   PRC = 0;
end

% INDICATES TO THE USER THAT THE SIMULATION IS RUNNING
% (ON THE GRAPH SCREEN):
%clg;
%(0.35,0.5,'SIMULATION IN PROCESS','sc');

disp('    SIMULATION IN PROGRESS')

% TIME VECTOR PADDED WITH ZEROS AT THE BEGINNING:
t = [tstart.*ones(1,nsup) tstart:tstep:tend];

% INDEX VECTOR FOR SIMULATION LOOP, ALSO TO BE USED LATER FOR PLOTTING:
% ind = nsup+1:ntot;            %  old statement
  ind = nsup+1:max(size(t));    %  ensures that ntot and t have same
                                %  size regardless of of roundoff

% INITIALIZE VARIABLES
% --------------------
E1 = zeros(ntot,1);      % INPUT TO PRIMARY PID CONTROLLER
C1 = zeros(ntot,1);      % OUTPUT OF PRIMARY PID CONTROLLER
SP1 = zeros(ntot,1);     % SET POINT TO THE PRIMARY CONTROLLER
SP2 = zeros(ntot,1);     % SET-POINT TO THE SECONDARY CONTROLLER
E2 = zeros(ntot,1);      % INPUT TO THE SECONDARY CONTROLLER
C2 = zeros(ntot,1);      % OUTPUT OF THE SECONDARY CONTROLLER
X2 = zeros(ntot+1,1);    % OUTPUT THE INNER PROCESS G2, NOISE-FREE
Y2 = zeros(ntot,1);      % X2 + INNER DISTURBANCE GD2(S)*D2(S)
Y2m = zeros(ntot,1);     % Y2 + MEASUREMENT NOISE
X1 = zeros(ntot+1,1);    % OUTPUT OF OUTER PROCESS G1, NOISE-FREE
Y1 = zeros(ntot,1);      % X1 + OUTER DISTURBANCE GD1(S)*D1(S)
Y1m = zeros(ntot,1);     % Y1 + MEASUREMENT NOISE
Cff = zeros(ntot,1);     % OUTPUT OF FEEDFORWARD CONTROLLER
D1 = zeros(ntot,1);      % DISTURBANCE IN PRIMARY LOOP (MEASURED FOR FF)
D1m = zeros(ntot,1);     % MEASURED DISTURBANCE D1
D2 = zeros(ntot,1);      % DISTURBANCE IN SECONDARY LOOP 
N1 = zeros(ntot,1);      % GD1(S)*D1(S)
N2 = zeros(ntot,1);      % GD2(S)*D2(S)
NY1 = zeros(ntot,1);     % MEASUREMENT NOISE ON Y1
NY2 = zeros(ntot,1);     % MEASUREMENT NOISE ON Y2
ND1 = zeros(ntot,1);     % MEASUREMENT NOISE ON D1

% CALCULATE CONSTANTS FOR PROCESSES IN DISCRETE FORM
% --------------------------------------------------
% E.G. COEFFICIENTS FOR PRIMARY PROCESS
% Y1(k)+a1(i)Y1(k-1)+a2(i)Y1(k-2)=b1(i)C1(k-1)+b2(i)C1(k-2)
for i = 1:2
   if T1(i) == 0.0;                  % PERFORM THIS CHECK SO THAT 
      temp1(i) = 0.0;                % EXP(-INFINITY) IS GIVEN THE
   else                              % VALUE ZERO.
      temp1(i) = exp(-tstep/T1(i));
   end
   if T2(i) == 0.0;
      temp2(i) = 0.0;
   else
      temp2(i) = exp(-tstep/T2(i));
   end
   if T1(i) == T2(i)        %note: T1=T2 also imply temp1=temp2
      if T1(i) == 0         % CASE 1: T1=T2=0
         a1(i) = 0;
         a2(i) = 0;
         b1(i) = K(i)*(TA(i)/tstep+1);
         b2(i) = -K(i)*TA(i)/tstep;
      else                  % CASE 2: T1=T2 but not zero
         temp = (TA(i)-T1(i))/T1(i)^2*tstep;
         a1(i) = -2*temp1(i);
         a2(i) = temp1(i)^2;
         b1(i) = K(i)*(1+(temp-1)*temp1(i));
         b2(i) = K(i)*temp1(i)*(temp1(i)-1-temp);
      end
   else                     % CASE 3: T1 not equal to T2
      a1(i) = -temp1(i)-temp2(i);  
      a2(i) = temp1(i)*temp2(i);  
      b1(i) = K(i)*(1+(TA(i)-T1(i))/(T1(i)-T2(i))*temp1(i)...
                    +(T2(i)-TA(i))/(T1(i)-T2(i))*temp2(i));
      b2(i) = K(i)*(a2(i)+(TA(i)-T1(i))/(T1(i)-T2(i))*temp2(i)...
                    +(T2(i)-TA(i))/(T1(i)-T2(i))*temp1(i));      
   end
end

% CALCULATE CONSTANTS FOR DISTURBANCE PROCESSES IN DISCRETE FORM
% --------------------------------------------------------------
% E.G. COEFFICIENTS FOR PRIMARY DISTURBANCE: 
% N1(k)+a1d(i)N1(k-1)+a2d(i)N1(k-2)=b1d(i)D1(k-1)+b2d(i)D1(k-2)
for i=1:2
   if T1d(i)==0.0;                   % PERFORM THIS CHECK SO THAT
      temp1(i) = 0.0;                % EXP(-INFINITY) IS GIVEN THE
   else                              % VALUE ZERO.
      temp1(i) = exp(-tstep/T1d(i));
   end
   if T2d(i)==0.0;
      temp2(i) = 0.0;
   else
      temp2(i) = exp(-tstep/T2d(i));
   end
   if T1d(i) == T2d(i)
      if T1d(i) == 0
         a1d(i) = 0;
         a2d(i) = 0;
         b1d(i) = Kd(i)*(TAd(i)/tstep+1);
         b2d(i) = -Kd(i)*TAd(i)/tstep;
      else
         temp = (TAd(i)-T1d(i))/T1d(i)^2*tstep;
         a1d(i) = -2*temp1(i);
         a2d(i) = temp1(i)^2;
         b1d(i) = Kd(i)*(1+(temp-1)*temp1(i));
         b2d(i) = Kd(i)*temp1(i)*(temp1(i)-1-temp);
      end
   else
      a1d(i) = -temp1(i)-temp2(i);
      a2d(i) = temp1(i)*temp2(i);
      b1d(i) = Kd(i)*(1+(TAd(i)-T1d(i))/(T1d(i)-T2d(i))*temp1(i)...
                      +(T2d(i)-TAd(i))/(T1d(i)-T2d(i))*temp2(i));
      b2d(i) = Kd(i)*(a2d(i)+(TAd(i)-T1d(i))/(T1d(i)-T2d(i))*temp2(i)...
                      +(T2d(i)-TAd(i))/(T1d(i)-T2d(i))*temp1(i));
   end
end
% if temp(1)=0 b1d = Kd*(1+(T2d-TAd)/(-T2d)*temp2)

% CALCULATE COEFFICIENTS FOR CONTROLLERS IN DISCRETE FORM
% -------------------------------------------------------
% E.G. FOR PRIMARY PID:
% C1(k)-C1(k-1) = KC(1)*(b1c(1)E1(k)-E1(k-1)-TD(1)/tstep(...))
% FOR FEEDFORWARD:
% Cff(k)+a1ff*Cff(k-1)=b1ff*D1m(k-Dffd)+b2ff*D1m(k-1-Dffd)
%
% PID's:
for i=1:2          % IF INTEGRAL TIME CONST = 0, TURN OFF INTEGRAL MODE
  if TI(i) == 0
     b1c(i) = 1;
  else
     b1c(i) = 1+tstep/TI(i);
  end
end
% Feedforward:

   a1ff =      (Tlg/tstep    ) / (Tlg/tstep + 1);
   b1ff =  Kff*(Tld/tstep + 1) / (Tlg/tstep + 1);
   b2ff = -Kff*(Tld/tstep    ) / (Tlg/tstep + 1);


% GENERATE WHITE NOISE FOR THE MEASUREMENT ERRORS
% -----------------------------------------------
%   "randn" evaluates normally distributed random variable
if NOISE == 1
   % FOR NOISE ON PRIMARY OUTPUT MEASUREMENT:
   randn('seed',SeedY1);
   A1 = randn(ntot,1).*sqrt(SaY1);
   % FOR NOISE ON SECONDARY OUTPUT MEASUREMENT:
   randn('seed',SeedY2);
   A2 = randn(ntot,1).*sqrt(SaY2);
   % FOR NOISE ON FEEDFORWARD DISTURBANCE MEASUREMENT:
   randn('seed',SeedD1);
   AD1 = randn(ntot,1).*sqrt(SaD1);
else
   A1 = zeros(ntot,1);
   A2 = zeros(ntot,1);
   AD1 = zeros(ntot,1);
end

% CALCULATE DISTURBANCES AND FF CONTROL ACTIONS BEFORE MAIN SIMULATION LOOP
% -------------------------------------------------------------------------
Dts = Dtstep./100.*(tend-tstart);   % REAL TIME TO INTRODUCE DISTURBANCES
SP1ts = SPtstep/100*(tend-tstart);  % REAL TIME TO INTRODUCE S-P STEPS
for k = ind
   % INTRODUCE STEPS IN DISTURBANCES AT APPROPRIATE TIME:
   if (t(k) >= Dts(1) & DISTURBANCE == 1)
      D1(k) = Ddelta(1);
   end
   if (t(k) >= Dts(2) & DISTURBANCE == 1)
      D2(k) = Ddelta(2);
   end
   % OUTER LOOP DISTURBANCE:
   N1(k) = - a1d(1)*N1(k-1) - a2d(1)*N1(k-2) + ...
         b1d(1)*D1(k-1-DDd(1)) + b2d(1)*D1(k-2-DDd(1));
   % INNER LOOP DISTURBANCE:
   N2(k) = - a1d(2)*N2(k-1) - a2d(2)*N2(k-2) + ...
         b1d(2)*D2(k-1-DDd(2)) + b2d(2)*D2(k-2-DDd(2));
   % MEASUREMENT ERROR ON THE OUTER LOOP OUTPUT Y1:
   NY1(k) = dY1(1)*NY1(k-1) + dY1(2)*NY1(k-2) + A1(k);
   % MEASUREMENT ERROR ON THE INNER LOOP OUTPUT Y2:
   NY2(k) = dY2(1)*NY2(k-1) + dY2(2)*NY2(k-2) + A2(k);
   % MEASUREMENT ERROR ON THE OUTER LOOP DISTURBANCE:
   ND1(k) = dD1(1)*ND1(k-1) + dD1(2)*ND1(k-2) + AD1(k);
   % CALCULATE FF CONTROL ACTION:
   % * NOTE THAT FF IS AUTOMATICALLY TURNED OFF FOR PRC MODES
   if PRC == 0
      D1m(k) = D1(k)+ND1(k);
      Cff(k) = a1ff*Cff(k-1)+b1ff*D1m(k-Dffd)+b2ff*D1m(k-1-Dffd);      
   else
      D1m(k) = D1(k) + ND1(k);
      Cff(k) = 0;                      % no FF when PRC
   end
end

% SIMULATION LOOPS
% ----------------
% Note: There are 2 simulation loops, one for PRC = 2 and
% one for all other cases; some code is thus duplicated but
% simulation should run faster in PRC2 mode. Also this avoids a
% couple more cases to handle in the other (main) simulation loop.

% PROCESS REACTION CURVE INDEX:
% PRC = 0 : NORMAL SIMULATION, NO PRC (CASCADE OR SINGLE-LOOP)
% PRC = 1 : PRC FOR PRIMARY CONTROLLER (CASCADE OR SINGLE-LOOP)
% PRC = 2 : PRC FOR SECONDARY CONTROLLER, ALLOWED ONLY IN CASCADE MODE

if PRC == 2   % CALCULATE PRC FOR INNER LOOP
   for k = ind                  
      if t(k) >= SP1ts
         C2(k) = SP_PRC(2);
      end
      % INNER PROCESS, NOISE FREE PROCESS OUTPUT:
      X2(k+1) = - a1(2)*X2(k) - a2(2)*X2(k-1) + ...
                b1(2)*C2(k-DD(2)) + b2(2)*C2(k-1-DD(2)); 
      % ADD DISTURBANCE TO OUTPUT X2:
      Y2(k) = X2(k) + N2(k);
      % ADD MEASUREMENT NOISE:
      Y2m(k) = Y2(k) + NY2(k);
      X1(k+1) = -a1(1)*X1(k) - a2(1)*X1(k-1) + ...
                b1(1)*Y2(k-DD(1)) + b2(1)*Y2(k-1-DD(1));
      Y1(k) = X1(k) + N1(k);
      Y1m(k) = Y1(k) + NY1(k);
   end % for
else   % MODE PRC1 OR NORMAL SIMULATION (WITH CASCADE ON OR OFF)
   for k = ind         % loop in time indices
      if t(k) >= SP1ts
         SP1(k) = SPdelta;
      end
      % ADD DISTURBANCE TO OUTER PROCESS OUTPUT:
      Y1(k) = X1(k) + N1(k);
      % ADD MEASUREMENT NOISE:
      Y1m(k) = Y1(k) + NY1(k);
      if PRC == 0
         % OUTER CONTROLLER INPUT:
         E1(k) = SP1(k) - Y1m(k);
         % OUTER CONTROLLER OUTPUT:
          C1(k) = C1(k-1)+KC(1)*(b1c(1)*E1(k)-E1(k-1)-...
                TD(1)/tstep*(Y1m(k)-2*Y1m(k-1)+Y1m(k-2)));
      else
         if t(k) >= SP1ts
            C1(k) = SP_PRC(1);
         end
      end

%     windup on primary controller output
      
      % SET POINT OF INNER CONTROLLER:
      
      SP2(k) = C1(k)+Cff(k);     % recall that Cff=0 if in PRC mode

%     clamp on secondary set point after FF added
      if CASCADE == 1                % CHECK BOUNDS IF CASCADE
          SP2(k) = min (SP2(k), maxC(1)-Yinit(2)) ;
          SP2(k) = max (SP2(k), minC(1)-Yinit(2)) ;
          C1(k)  = SP2(k) - Cff(k);   % prevent primary reset windup
         
      end
%          
         
        % ADD DISTURBANCE TO OUTPUT X2:
         Y2(k) = X2(k) + N2(k);
         % ADD MEASUREMENT NOISE:
         Y2m(k) = Y2(k) + NY2(k);
         % INNER CONTROLLER INPUT:
         E2(k) = SP2(k) - Y2m(k)*CASCADE;
         % multiplying by flag CASCADE=0 will cancel inner controller
         % INNER CONTROLLER OUTPUT:
         if CASCADE == 1
             C2(k) = C2(k-1)+KC(2)*(b1c(2)*E2(k)-E2(k-1)-...
                   TD(2)/tstep*(Y2m(k)-2*Y2m(k-1)+Y2m(k-2)));
         else
            C2(k) = E2(k);
         end
%        windup check
         if CASCADE ==1
%            for cascade control, the primary controller could
%            windup when the final element reaches a limit
%            this calculation resets the primary output/sec SP
%            to the value that would bring the sec output just to its
%            limit - by back calculating from the PID result with C2(k)=limit
            if C2(k) <= minC(2)-MVinit
               %SP2(k) = Y2m(k); 
               holdd = (TD(2)/tstep)*(Y2m(k)-2*Y2m(k-1)+Y2m(k-2));
               E2(k)= (((minC(2)-MVinit)-C2(k-1))/KC(2) + E2(k-1)+holdd)/b1c(2);
               SP2(k) = E2(k) + Y2m(k);
               C1(k)  = SP2(k) ;  %- Cff(k);
            end
            if C2(k) >= maxC(2)-MVinit
               holdd = (TD(2)/tstep)*(Y2m(k)-2*Y2m(k-1)+Y2m(k-2));
               E2(k)= (((maxC(2)-MVinit)-C2(k-1))/KC(2) + E2(k-1)+holdd)/b1c(2);
               SP2(k) = E2(k) + Y2m(k);
               C1(k)  = SP2(k) ;  %- Cff(k);
            end   
         end
         C2(k) = min (C2(k), maxC(2)-MVinit) ;
         C2(k) = max (C2(k), minC(2)-MVinit) ; 
         

         if CASCADE ~= 1
             C1(k) = C2(k) - Cff(k);  %don't let SL windup
         end

        % INNER PROCESS, NOISE FREE PROCESS OUTPUT:
         X2(k+1) = - a1(2)*X2(k) - a2(2)*X2(k-1) + ...
                   b1(2)*C2(k-DD(2)) + b2(2)*C2(k-1-DD(2));  
      % OUTER PROCESS, NOISE-FREE PROCESS OUTPUT:
      X1(k+1) = -a1(1)*X1(k) - a2(1)*X1(k-1) + ...
                b1(1)*Y2(k-DD(1)) + b2(1)*Y2(k-1-DD(1));
   end % for
end % if

% CALCULATE PERFORMANCE INDICES
% -----------------------------
deltaC1 = C1(nsup+1:ntot) - C1(nsup:ntot-1);
deltaC2 = C2(nsup+1:ntot) - C2(nsup:ntot-1);
err1 = Y1m(ind) - SP1(ind);
err2 = Y2m(ind) - SP2(ind);
IAE1=sum(abs(err1))*tstep;     % calculates IAE for Y1m
IAE2=sum(abs(err2))*tstep;     % calculates IAE for Y2m
ISE1=sum(err1.^2)*tstep;       % calculates ISE for Y1m
ISE2=sum(err2.^2)*tstep;       % calculates ISE for Y2m
                        % MV performance, ---1 for SL ; ---2 for cascade
SAM1=(sum(abs(deltaC1))/tstep)*tstep;     % calculates sum of absolute deltaC1
SAM2=(sum(abs(deltaC2))/tstep)*tstep;     % calculates sum of absolute deltaC2=deltaMV
SSM1=(sum(deltaC2.^2)/(tstep^2))*tstep;   % calculates sum of squared deltaC1
SSM2=(sum(deltaC2.^2)/(tstep^2))*tstep;   % calculates sum of squared deltaC2=deltaMV

% ADD OFFSETS TO Y's AND C's
% --------------------------
% So that values have the right magnitudes on the plots
Y1m = Y1m + Yinit(1);
Y2m = Y2m + Yinit(2); 
SP1 = SP1 + Yinit(1);
SP2 = SP2 + Yinit(2);
if CASCADE == 1         % CASCADE STRUCTURE, ADD PROPER I.C.
    C1 = C1 + Yinit(2);
    C2 = C2 + MVinit;
else                    % Single loop structure, add proper I.C.
    C2 = C2 + MVinit;   %
end

CASCADEsave = CASCADE;  % Save current value for the graph routine;

%clf;
%close all
%text(0.32,0.50,'THE SIMULATION IS COMPLETE','sc');
disp (' ')
disp ('    THE SIMULATION IS COMPLETE '  )
pause(2);       % PAUSES FOR 1 SECOND
%close all
