├── assets └── images │ ├── default_scenario.jpg │ └── three_tank_system.jpg ├── .gitignore ├── src ├── @Sim3Tanks │ ├── clearModel.m │ ├── getValveSignals.m │ ├── getFaultOffsets.m │ ├── getFlowVariables.m │ ├── getStateVariables.m │ ├── getFaultMagnitudes.m │ ├── getSensorMeasurements.m │ ├── displayModel.m │ ├── clearVariables.m │ ├── setDefaultModel.m │ ├── plotFaultOffsets.m │ ├── plotFaultMagnitudes.m │ ├── getDefaultLinearModel.m │ ├── plotLevels.m │ ├── plotValves.m │ ├── plotFlows.m │ ├── simulateModel.m │ └── Sim3Tanks.m ├── satSignal.m ├── sysDynamicModel.m ├── getRGBtriplet.m ├── sysMeasurements.m ├── checkOperationMode.m ├── defaultPhysicalParam.m ├── defaultOperationMode.m ├── checkEnabledValves.m ├── checkEnabledNoises.m ├── checkPhysicalParam.m ├── sysFlowRates.m ├── createSim3Tanks.m ├── checkEnabledFaults.m └── getMessage.m ├── addons ├── truthTable.m ├── faultStepwise.m ├── stepSignal.m └── faultDriftwise.m ├── tests ├── pid_flow_control │ ├── pidFlowControl.m │ └── pidFlowControl_test.m └── pid_level_control │ ├── pidLevelControl.m │ └── pidLevelControl_test.m ├── LICENSE ├── Sim3Tanks_test.m └── README.md /assets/images/default_scenario.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/e-controls/Sim3Tanks/HEAD/assets/images/default_scenario.jpg -------------------------------------------------------------------------------- /assets/images/three_tank_system.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/e-controls/Sim3Tanks/HEAD/assets/images/three_tank_system.jpg -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Windows default autosave extension 2 | *.asv 3 | 4 | # OSX / *nix default autosave extension 5 | *.m~ 6 | 7 | # Compiled MEX binaries (all platforms) 8 | *.mex* 9 | 10 | # Packaged app and toolbox files 11 | *.mlappinstall 12 | *.mltbx 13 | 14 | # Generated helpsearch folders 15 | helpsearch*/ 16 | 17 | # Simulink code generation folders 18 | slprj/ 19 | sccprj/ 20 | 21 | # Matlab code generation folders 22 | codegen/ 23 | 24 | # Simulink autosave extension 25 | *.autosave 26 | 27 | # Simulink cache files 28 | *.slxc 29 | 30 | # Octave session info 31 | octave-workspace 32 | -------------------------------------------------------------------------------- /src/@Sim3Tanks/clearModel.m: -------------------------------------------------------------------------------- 1 | function clearModel(varargin) 2 | % clearModel is a Sim3Tanks method. This method does not have an input 3 | % argument. It clears all variables and restores a Sim3Tanks object. 4 | % 5 | % Example: 6 | % tts.clearModel(); 7 | 8 | % https://github.com/e-controls/Sim3Tanks 9 | 10 | %========================================================================== 11 | 12 | if(nargin()>1) 13 | error(getMessage('ERR001')); 14 | else 15 | objSim3Tanks = varargin{1}; 16 | end 17 | 18 | %========================================================================== 19 | 20 | objSim3Tanks.prepareModel(); 21 | 22 | objSim3Tanks.clearVariables(); 23 | 24 | end 25 | -------------------------------------------------------------------------------- /addons/truthTable.m: -------------------------------------------------------------------------------- 1 | function [T] = truthTable(N) 2 | % T = truthTable(N) returns the truth table for N variables. 3 | 4 | % Written by Arllem Farias, 2024. 5 | % https://github.com/e-controls/Sim3Tanks 6 | 7 | if(nargin() ~= 1) 8 | error('This function must have just one input argument.'); 9 | elseif(N~=floor(N) || N<=0) 10 | error('The input argument must be a positive integer.'); 11 | end 12 | 13 | T = zeros(2^N,N); 14 | 15 | cont = 0; 16 | for j = 1 : N 17 | jump = 2^(N-j); 18 | for i = 1 : 2^N 19 | cont = cont + 1; 20 | if(cont > jump) 21 | T(i,j) = 1; 22 | if(cont == 2*jump) 23 | cont = 0; 24 | end 25 | end 26 | end 27 | end 28 | 29 | T = logical(T); 30 | 31 | end -------------------------------------------------------------------------------- /src/satSignal.m: -------------------------------------------------------------------------------- 1 | function [y] = satSignal(varargin) 2 | % satSignal is a Sim3Tanks function. This function saturates a variable x 3 | % into the bounds [xmin,xmax]. 4 | % 5 | % y = satSignal(x,[xmin,xmax]), where x is the variable (scalar or vector), 6 | % and [xmin,xmax] are the bounds. 7 | 8 | % https://github.com/e-controls/Sim3Tanks 9 | 10 | %========================================================================== 11 | 12 | if(nargin()<2) 13 | error(getMessage('ERR001')); 14 | elseif(nargin()>2) 15 | error(getMessage('ERR002')); 16 | else 17 | x = varargin{1}; 18 | xrange = varargin{2}; 19 | end 20 | 21 | if ~(isnumeric(x) && isnumeric(xrange)) 22 | error(getMessage('ERR003')); 23 | end 24 | 25 | minValue = xrange(:,1); 26 | maxValue = xrange(:,2); 27 | 28 | y = min(maxValue,max(minValue,x)); 29 | 30 | end -------------------------------------------------------------------------------- /src/@Sim3Tanks/getValveSignals.m: -------------------------------------------------------------------------------- 1 | function [varargout] = getValveSignals(varargin) 2 | % getValveSignals is a Sim3Tanks method. This method does not have an input 3 | % argument and returns a data table with the values of the valve signals. 4 | % 5 | % Example: 6 | % tts.getValveSignals(); 7 | 8 | % https://github.com/e-controls/Sim3Tanks 9 | 10 | %========================================================================== 11 | 12 | if(nargin()>1) 13 | error(getMessage('ERR002')); 14 | else 15 | objSim3Tanks = varargin{1}; 16 | end 17 | 18 | %========================================================================== 19 | 20 | LIST_OF_VALVES = Sim3Tanks.LIST_OF_VALVES; 21 | 22 | v = objSim3Tanks.getInternalValveSignals(); 23 | t = objSim3Tanks.getInternalSimulationTime(); 24 | 25 | if(~isempty(v)) 26 | v = array2timetable(v,'RowTimes',seconds(t),'VariableNames',LIST_OF_VALVES); 27 | end 28 | 29 | varargout{1} = v; 30 | 31 | end -------------------------------------------------------------------------------- /src/@Sim3Tanks/getFaultOffsets.m: -------------------------------------------------------------------------------- 1 | function [varargout] = getFaultOffsets(varargin) 2 | % getFaultOffsets is a Sim3Tanks method. This method does not have an input 3 | % argument and returns a data table with the values of the fault offsets. 4 | % 5 | % Example: 6 | % tts.getFaultOffsets(); 7 | 8 | % https://github.com/e-controls/Sim3Tanks 9 | 10 | %========================================================================== 11 | 12 | if(nargin()>1) 13 | error(getMessage('ERR002')); 14 | else 15 | objSim3Tanks = varargin{1}; 16 | end 17 | 18 | %========================================================================== 19 | 20 | LIST_OF_FAULTS = Sim3Tanks.LIST_OF_FAULTS(11:end); 21 | 22 | f = objSim3Tanks.getInternalFaultOffsets(); 23 | t = objSim3Tanks.getInternalSimulationTime(); 24 | 25 | if(~isempty(f)) 26 | f = array2timetable(f,'RowTimes',seconds(t),'VariableNames',LIST_OF_FAULTS); 27 | end 28 | 29 | varargout{1} = f; 30 | 31 | end -------------------------------------------------------------------------------- /src/@Sim3Tanks/getFlowVariables.m: -------------------------------------------------------------------------------- 1 | function [varargout] = getFlowVariables(varargin) 2 | % getFlowVariables is a Sim3Tanks method. This method does not have an 3 | % input argument and returns a data table with the values of the flow 4 | % variables. 5 | % 6 | % Example: 7 | % tts.getFlowVariables(); 8 | 9 | % https://github.com/e-controls/Sim3Tanks 10 | 11 | %========================================================================== 12 | 13 | if(nargin()>1) 14 | error(getMessage('ERR002')); 15 | else 16 | objSim3Tanks = varargin{1}; 17 | end 18 | 19 | %========================================================================== 20 | 21 | LIST_OF_FLOWS = Sim3Tanks.LIST_OF_FLOWS; 22 | 23 | q = objSim3Tanks.getInternalFlowVariables(); 24 | t = objSim3Tanks.getInternalSimulationTime(); 25 | 26 | if(~isempty(q)) 27 | q = array2timetable(q,'RowTimes',seconds(t),'VariableNames',LIST_OF_FLOWS); 28 | end 29 | 30 | varargout{1} = q; 31 | 32 | end -------------------------------------------------------------------------------- /tests/pid_flow_control/pidFlowControl.m: -------------------------------------------------------------------------------- 1 | function [uk] = pidFlowControl(ek_past,uk_past,Ts) 2 | 3 | % Error signals at k, k-1, and k-2 4 | e_k = ek_past(3); 5 | e_k_1 = ek_past(2); 6 | e_k_2 = ek_past(1); 7 | 8 | % Past control signals at k-2 9 | u1_k_2 = uk_past(1,1); 10 | u2_k_2 = uk_past(2,1); 11 | 12 | % Saturation info 13 | u_min = 0; 14 | u_max = 1; 15 | 16 | % Controller gains 17 | Kp = 0.05; 18 | Ki = 0.01; 19 | Kd = 0.001; 20 | 21 | Ti = Kp/Ki; 22 | Td = Kd/Kp; 23 | 24 | % Digital PID controllers (Approximate by Tustin) 25 | a0 = Kp*(1 + Ts/(2*Ti) + 2*Td/Ts); 26 | a1 = Kp*(Ts/Ti - 4*Td/Ts); 27 | a2 = Kp*(-1 + Ts/(2*Ti) + 2*Td/Ts); 28 | 29 | % PID to valve Kp1 30 | u1 = u1_k_2 + a0*e_k + a1*e_k_1 + a2*e_k_2; 31 | u1 = min(u_max,max(u_min,u1)); % Saturation 32 | 33 | % PID to valve Kp2 34 | u2 = u2_k_2 + a0*e_k + a1*e_k_1 + a2*e_k_2; 35 | u2 = min(u_max,max(u_min,u2)); % Saturation 36 | 37 | % Control signal 38 | uk = [u1 u2]'; 39 | 40 | end -------------------------------------------------------------------------------- /src/@Sim3Tanks/getStateVariables.m: -------------------------------------------------------------------------------- 1 | function [varargout] = getStateVariables(varargin) 2 | % getStateVariables is a Sim3Tanks method. This method does not have an 3 | % input argument and returns a data table with the values of the state 4 | % variables. 5 | % 6 | % Example: 7 | % tts.getStateVariables(); 8 | 9 | % https://github.com/e-controls/Sim3Tanks 10 | 11 | %========================================================================== 12 | 13 | if(nargin()>1) 14 | error(getMessage('ERR002')); 15 | else 16 | objSim3Tanks = varargin{1}; 17 | end 18 | 19 | %========================================================================== 20 | 21 | LIST_OF_STATES = Sim3Tanks.LIST_OF_STATES; 22 | 23 | x = objSim3Tanks.getInternalStateVariables(); 24 | t = objSim3Tanks.getInternalSimulationTime(); 25 | 26 | if(~isempty(x)) 27 | x = array2timetable(x,'RowTimes',seconds(t),'VariableNames',LIST_OF_STATES); 28 | end 29 | 30 | varargout{1} = x; 31 | 32 | end -------------------------------------------------------------------------------- /src/@Sim3Tanks/getFaultMagnitudes.m: -------------------------------------------------------------------------------- 1 | function [varargout] = getFaultMagnitudes(varargin) 2 | % getFaultMagnitudes is a Sim3Tanks method. This method does not have an 3 | % input argument and returns a data table with the values of the fault 4 | % magnitudes. 5 | % 6 | % Example: 7 | % tts.getFaultMagnitudes(); 8 | 9 | % https://github.com/e-controls/Sim3Tanks 10 | 11 | %========================================================================== 12 | 13 | if(nargin()>1) 14 | error(getMessage('ERR002')); 15 | else 16 | objSim3Tanks = varargin{1}; 17 | end 18 | 19 | %========================================================================== 20 | 21 | LIST_OF_FAULTS = Sim3Tanks.LIST_OF_FAULTS; 22 | 23 | f = objSim3Tanks.getInternalFaultMagnitudes(); 24 | t = objSim3Tanks.getInternalSimulationTime(); 25 | 26 | if(~isempty(f)) 27 | f = array2timetable(f,'RowTimes',seconds(t),'VariableNames',LIST_OF_FAULTS); 28 | end 29 | 30 | varargout{1} = f; 31 | 32 | end -------------------------------------------------------------------------------- /src/@Sim3Tanks/getSensorMeasurements.m: -------------------------------------------------------------------------------- 1 | function [varargout] = getSensorMeasurements(varargin) 2 | % getSensorMeasurements is a Sim3Tanks method. This method does not have an 3 | % input argument and returns a data table with the values of the measured 4 | % variables. 5 | % 6 | % Example: 7 | % tts.getSensorMeasurements(); 8 | 9 | % https://github.com/e-controls/Sim3Tanks 10 | 11 | %========================================================================== 12 | 13 | if(nargin()>1) 14 | error(getMessage('ERR002')); 15 | else 16 | objSim3Tanks = varargin{1}; 17 | end 18 | 19 | %========================================================================== 20 | 21 | LIST_OF_STATES = Sim3Tanks.LIST_OF_STATES; 22 | LIST_OF_FLOWS = Sim3Tanks.LIST_OF_FLOWS; 23 | 24 | y = objSim3Tanks.getInternalSensorMeasurements(); 25 | t = objSim3Tanks.getInternalSimulationTime(); 26 | 27 | if(~isempty(y)) 28 | y = array2timetable(y,'RowTimes',seconds(t),'VariableNames',[LIST_OF_STATES;LIST_OF_FLOWS]); 29 | end 30 | 31 | varargout{1} = y; 32 | 33 | end -------------------------------------------------------------------------------- /tests/pid_level_control/pidLevelControl.m: -------------------------------------------------------------------------------- 1 | function [uk] = pidLevelControl(ek_past,uk_past,Ts) 2 | 3 | % Error signals at k, k-1, and k-2 4 | e1_k = ek_past(1,3); 5 | e1_k_1 = ek_past(1,2); 6 | e1_k_2 = ek_past(1,1); 7 | 8 | e2_k = ek_past(2,3); 9 | e2_k_1 = ek_past(2,2); 10 | e2_k_2 = ek_past(2,1); 11 | 12 | % Past control signals at k-2 13 | u1_k_2 = uk_past(1,1); 14 | u2_k_2 = uk_past(2,1); 15 | 16 | % Saturation info 17 | u_min = 0; 18 | u_max = 120; 19 | 20 | % Controller gains 21 | Kp = 5; 22 | Ki = 10; 23 | Kd = 0.05; 24 | 25 | Ti = Kp/Ki; 26 | Td = Kd/Kp; 27 | 28 | % Digital PID controllers (Approximate by Tustin) 29 | a0 = Kp*(1 + Ts/(2*Ti) + 2*Td/Ts); 30 | a1 = Kp*(Ts/Ti - 4*Td/Ts); 31 | a2 = Kp*(-1 + Ts/(2*Ti) + 2*Td/Ts); 32 | 33 | % PID to valve Kp1 34 | u1 = u1_k_2 + a0*e1_k + a1*e1_k_1 + a2*e1_k_2; 35 | u1 = min(u_max,max(u_min,u1)); % Saturation 36 | 37 | % PID to valve Kp2 38 | u2 = u2_k_2 + a0*e2_k + a1*e2_k_1 + a2*e2_k_2; 39 | u2 = min(u_max,max(u_min,u2)); % Saturation 40 | 41 | % Control signal 42 | uk = [u1 u2]'; 43 | 44 | end -------------------------------------------------------------------------------- /src/sysDynamicModel.m: -------------------------------------------------------------------------------- 1 | function dxdt = sysDynamicModel(A,q,w) 2 | % sysDynamicModel is a Sim3Tanks function. This function describes the 3 | % dynamic model of the three-tank system. 4 | % 5 | % Example: 6 | % dxdt = sysDynamicModel(A,q,w) 7 | % A : cross-sectional area of the tanks 8 | % q : flow rate vector 9 | % w : process noise vector 10 | 11 | % https://github.com/e-controls/Sim3Tanks 12 | 13 | %========================================================================== 14 | 15 | if(nargin()<3) 16 | error(getMessage('ERR001')); 17 | elseif(nargin()>3) 18 | error(getMessage('ERR002')); 19 | end 20 | 21 | %========================================================================== 22 | 23 | Q1in = q(1); 24 | Q2in = q(2); 25 | Q3in = q(3); 26 | Qa = q(4); 27 | Qb = q(5); 28 | Q13 = q(6); 29 | Q23 = q(7); 30 | Q1 = q(8); 31 | Q2 = q(9); 32 | Q3 = q(10); 33 | 34 | dx1dt = (1/A)*(Q1in - Qa - Q13 - Q1) + w(1); 35 | dx2dt = (1/A)*(Q2in - Qb - Q23 - Q2) + w(2); 36 | dx3dt = (1/A)*(Q3in + Q13 + Q23 + Qa + Qb - Q3) + w(3); 37 | 38 | dxdt = [dx1dt ; dx2dt ; dx3dt]; 39 | 40 | end -------------------------------------------------------------------------------- /src/getRGBtriplet.m: -------------------------------------------------------------------------------- 1 | function [varargout] = getRGBtriplet(varargin) 2 | % getRGBtriplet is a Sim3Tanks function. This function returns the RGB 3 | % triplet associated with a tag. 4 | 5 | % https://github.com/e-controls/Sim3Tanks 6 | 7 | %========================================================================== 8 | 9 | if(nargin()<1) 10 | error(getMessage('ERR001')); 11 | elseif(nargin()>1) 12 | error(getMessage('ERR002')); 13 | end 14 | 15 | %========================================================================== 16 | 17 | % RGB Triplet 18 | LIST_OF_COLORS = {... 19 | 'normal', [102 205 170]/255;... 20 | 'fault' , [255 127 080]/255;... 21 | 'closed', [119 136 153]/255;... 22 | 'open' , [205 201 201]/255;... 23 | 'step' , [205 201 201]/255;... 24 | 'drift' , [238 233 233]/255;... 25 | 'blue' , [179 199 255]/255;... 26 | 'white' , [255 255 255]/255;... 27 | }; 28 | 29 | option = find(strcmpi(varargin{1},LIST_OF_COLORS),1); 30 | 31 | if(isempty(option)) 32 | error(getMessage('ERR003')); 33 | end 34 | 35 | varargout{1} = LIST_OF_COLORS{option,2}; 36 | 37 | end -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 e-Controls UFAM 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/sysMeasurements.m: -------------------------------------------------------------------------------- 1 | function y = sysMeasurements(x,q,faultMag,offset,mNoise) 2 | % sysMeasurements is a Sim3Tanks function. This function describes the 3 | % level and flow measurements of the three-tank system through sensors. 4 | % 5 | % Example: 6 | % y = sysMeasurements(x,q,faultMag,offset,mNoise); 7 | % x : state vector 8 | % q : flow rate vector 9 | % faultMag : fault magnitude vector 10 | % offset : fault offset vector 11 | % mNoise : measurement noise vector 12 | 13 | % https://github.com/e-controls/Sim3Tanks 14 | 15 | %========================================================================== 16 | 17 | if(nargin()<5) 18 | error(getMessage('ERR001')); 19 | elseif(nargin()>5) 20 | error(getMessage('ERR002')); 21 | end 22 | 23 | %========================================================================== 24 | 25 | N = numel(Sim3Tanks.LIST_OF_VALVES); 26 | 27 | y = [x , q]; % [h1,h2,h3,Q1in,Q2in,Q3in,Qa,Qb,Q13,Q23,Q1,Q2,Q3] 28 | 29 | for i = 1 : numel(y) 30 | j = N + i; 31 | if(faultMag(j) ~= 1) 32 | y(i) = (1-faultMag(j))*y(i) + offset(j) + mNoise(i); 33 | else 34 | y(i) = 0; % Could it be a NaN? 35 | end 36 | end -------------------------------------------------------------------------------- /addons/faultStepwise.m: -------------------------------------------------------------------------------- 1 | function [varargout] = faultStepwise(MAGNITUDE,FAULT_TIME,TIME_VECTOR) 2 | % [SIGNAL,TIME_VECTOR] = faultStepwise(MAGNITUDE,FAULT_TIME,TIME_VECTOR) 3 | % generates a stepwise fault signal, where MAGNITUDE specifies the maximum 4 | % fault magnitude within the range FAULT_TIME, which is a two-position 5 | % vector containing the start and end time of the fault, [START,END]. The 6 | % output SIGNAL is the corresponding signal vector, and TIME_VECTOR is a 7 | % vector of time points. 8 | % 9 | % If TIME_VECTOR is omitted, a default time vector is declared with the 10 | % following settings: [0 : 0.1 : max(FAULT_TIME)+10] 11 | % 12 | % Example: 13 | % [signal,t] = faultStepwise(0.8,[10 40]); 14 | % plot(t,signal); 15 | % grid on; 16 | % xlabel('time'); 17 | % ylabel('magnitude'); 18 | % title('Stepwise Fault Signal'); 19 | 20 | % Written by Arllem Farias, 2024. 21 | % https://github.com/e-controls/Sim3Tanks 22 | 23 | if(MAGNITUDE<0 || MAGNITUDE>1) 24 | error('MAGNITUDE must be within the range [0,1].'); 25 | elseif(numel(FAULT_TIME)~=2) 26 | error('FAULT_TIME must be a two-position vector.'); 27 | end 28 | 29 | % If the TIME_VECTOR argument is omitted. 30 | if(nargin() < 3) 31 | duration = max(FAULT_TIME) + 10; 32 | TIME_VECTOR = 0 : 0.1 : duration; 33 | end 34 | 35 | SIGNAL = zeros(size(TIME_VECTOR)); 36 | 37 | SIGNAL(TIME_VECTOR>=FAULT_TIME(1) & TIME_VECTOR<=FAULT_TIME(2)) = MAGNITUDE; 38 | 39 | varargout{1} = SIGNAL; 40 | varargout{2} = TIME_VECTOR; 41 | 42 | end -------------------------------------------------------------------------------- /src/@Sim3Tanks/displayModel.m: -------------------------------------------------------------------------------- 1 | function displayModel(varargin) 2 | % displayModel is a Sim3Tanks method. This method does not have an input 3 | % argument. It displays the model settings of a Sim3Tanks object on the 4 | % command line. 5 | % 6 | % Example: 7 | % tts.displayModel(); 8 | 9 | % https://github.com/e-controls/Sim3Tanks 10 | 11 | %========================================================================== 12 | 13 | if(nargin()==1) 14 | structVar = varargin{1}.Model; 15 | elseif(nargin()==2 && isa(varargin{2},'struct')) 16 | structVar = varargin{2}; 17 | else 18 | error(getMessage('ERR002')); 19 | end 20 | 21 | %========================================================================== 22 | 23 | fields = fieldnames(structVar); 24 | isThereSubfield = false; 25 | PREFIX1 = '+-- '; 26 | PREFIX2 = 'O-- '; 27 | 28 | for i = 1 : numel(fields) 29 | currentField = fields{i}; 30 | fieldValue = structVar.(currentField); 31 | fieldType = class(fieldValue); 32 | 33 | if(isstruct(fieldValue)) 34 | isThereSubfield = true; 35 | fprintf('\n%s%s (Struct)\n', PREFIX1, currentField); 36 | displayModel(varargin{1},fieldValue); 37 | 38 | elseif(isThereSubfield) 39 | fprintf('\n%s%s: %s (%s)\n', PREFIX1, ... 40 | currentField, mat2str(fieldValue), fieldType); 41 | else 42 | fprintf('\t%s%s: %s (%s)\n', PREFIX2, ... 43 | currentField, mat2str(fieldValue), fieldType); 44 | end 45 | 46 | end 47 | -------------------------------------------------------------------------------- /src/checkOperationMode.m: -------------------------------------------------------------------------------- 1 | function [varargout] = checkOperationMode(varargin) 2 | % checkOperationMode is a Sim3Tanks function. This function checks the 3 | % operation mode of a Sim3Tanks object and returns an array with 0s and 4 | % 1s, where 0 corresponds to Closed and 1 corresponds to Open. A second 5 | % array with the IDs of the valves is also returned. 6 | % 7 | % Example: 8 | % [opMode,valveID] = checkOperationMode(objSim3Tanks); 9 | 10 | % https://github.com/e-controls/Sim3Tanks 11 | 12 | %========================================================================== 13 | 14 | if(nargin()<1) 15 | error(getMessage('ERR001')); 16 | elseif(nargin()>1) 17 | error(getMessage('ERR002')); 18 | end 19 | 20 | if(isa(varargin{1},'Sim3Tanks')) 21 | objSim3Tanks = varargin{1}; 22 | else 23 | error(getMessage('ERR004')); 24 | end 25 | 26 | %========================================================================== 27 | 28 | LIST_OF_FIELDS = Sim3Tanks.LIST_OF_FIELDS; 29 | LIST_OF_VALVES = Sim3Tanks.LIST_OF_VALVES; 30 | 31 | %========================================================================== 32 | 33 | opMode = zeros(size(LIST_OF_VALVES)); 34 | valveID = cell(size(LIST_OF_VALVES)); 35 | 36 | for i = 1 : numel(LIST_OF_VALVES) 37 | valve = objSim3Tanks.Model.(LIST_OF_FIELDS{2}).(LIST_OF_VALVES{i}).OperationMode; 38 | if(strcmpi(valve,'Open')) 39 | opMode(i) = 1; 40 | elseif(strcmpi(valve,'Closed')) 41 | opMode(i) = 0; 42 | else 43 | error(getMessage('ERR009')); 44 | end 45 | valveID{i} = LIST_OF_VALVES{i}; 46 | end 47 | 48 | varargout{1} = opMode; 49 | varargout{2} = valveID; 50 | 51 | end -------------------------------------------------------------------------------- /addons/stepSignal.m: -------------------------------------------------------------------------------- 1 | function [varargout] = stepSignal(VALUES,TIMES,TIME_VECTOR) 2 | % [SIGNAL,TIME_VECTOR] = stepSignal(VALUES,TIMES,TIME_VECTOR) generates a 3 | % step signal, where VALUES specifies the amplitude of the SIGNAL at the 4 | % corresponding TIMES position. The SIGNAL remains at zero until the first 5 | % time in TIMES, after that it changes to the next value at the subsequent 6 | % time, and so on. The output SIGNAL is the corresponding signal values, 7 | % and TIME_VECTOR is a vector of time points. 8 | % 9 | % If TIME_VECTOR is omitted, a default time vector is declared with the 10 | % following settings: [0 : 0.1 : max(FAULT_TIME)+10] 11 | % 12 | % Example: 13 | % VALUES = [30, 50, 40]; 14 | % TIMES = [10, 30, 50]; 15 | % [signal,t] = stepSignal(VALUES,TIMES); 16 | % plot(t,signal); 17 | % grid on; 18 | % xlabel('time'); 19 | % ylabel('magnitude'); 20 | % title('Setpoint Signal'); 21 | 22 | % Written by Arllem Farias, 2024. 23 | % https://github.com/e-controls/Sim3Tanks 24 | 25 | if(numel(VALUES) ~= numel(TIMES)) 26 | error('The VALUES and TIMES vectors must have the same size.'); 27 | end 28 | 29 | % If the TIME_VECTOR argument is omitted. 30 | if(nargin() < 3) 31 | duration = max(TIMES) + 10; 32 | TIME_VECTOR = 0 : 0.1 : duration; 33 | end 34 | 35 | SIGNAL = zeros(size(TIME_VECTOR)); 36 | 37 | for k = 1 : numel(TIMES) 38 | if(k == 1) 39 | SIGNAL(TIME_VECTOR >= TIMES(k)) = VALUES(k); 40 | else 41 | SIGNAL(TIME_VECTOR >= TIMES(k-1) & TIME_VECTOR < TIMES(k)) = VALUES(k-1); 42 | SIGNAL(TIME_VECTOR >= TIMES(k)) = VALUES(k); 43 | end 44 | end 45 | 46 | varargout{1} = SIGNAL; 47 | varargout{2} = TIME_VECTOR; 48 | 49 | end -------------------------------------------------------------------------------- /src/@Sim3Tanks/clearVariables.m: -------------------------------------------------------------------------------- 1 | function clearVariables(varargin) 2 | % clearVariables is a Sim3Tanks method. This method clears all state and 3 | % flow variables, valve and fault signals, and the data measured by the 4 | % sensors of a Sim3Tanks object. 5 | % 6 | % Example: 7 | % tts.clearVariables(option); 8 | % 9 | % To clear a specific variable, enter one of the following options as an 10 | % input argument: 11 | % 'states' : to clear only the estate variables. 12 | % 'flows' : to clear only the flow variables. 13 | % 'sensors' : to clear only the measurement data. 14 | % 'valves' : to clear only the valve signals. 15 | % 'faults' : to clear only the fault singals 16 | % 17 | % If there is no input argument, all these variables will be cleared. 18 | 19 | % https://github.com/e-controls/Sim3Tanks 20 | 21 | %========================================================================== 22 | 23 | if(nargin()>2) 24 | error(getMessage('ERR002')); 25 | else 26 | objSim3Tanks = varargin{1}; 27 | end 28 | 29 | %========================================================================== 30 | 31 | if(nargin()==1) 32 | objSim3Tanks.setInternalStateVariables([]); 33 | objSim3Tanks.setInternalFlowVariables([]); 34 | objSim3Tanks.setInternalSensorMeasurements([]); 35 | objSim3Tanks.setInternalValveSignals([]); 36 | objSim3Tanks.setInternalFaultMagnitudes([]); 37 | objSim3Tanks.setInternalFaultOffsets([]); 38 | objSim3Tanks.resetInternalSimulationTime(); 39 | else 40 | switch lower(varargin{2}) 41 | case 'states' 42 | objSim3Tanks.setInternalStateVariables([]); 43 | case 'flows' 44 | objSim3Tanks.setInternalFlowVariables([]); 45 | case 'sensors' 46 | objSim3Tanks.setInternalSensorMeasurements([]); 47 | case 'valves' 48 | objSim3Tanks.setInternalValveSignals([]); 49 | case 'faults' 50 | objSim3Tanks.setInternalFaultMagnitudes([]); 51 | objSim3Tanks.setInternalFaultOffsets([]); 52 | otherwise 53 | error(getMessage('ERR003')); 54 | end 55 | end 56 | -------------------------------------------------------------------------------- /src/defaultPhysicalParam.m: -------------------------------------------------------------------------------- 1 | function [varargout] = defaultPhysicalParam(varargin) 2 | % defaultPhysicalParam is a Sim3Tanks function. This function returns the 3 | % default physical parameters of the three-tank system. 4 | % 5 | % d = defaultPhysicalParam(p) returns the default value of a specific 6 | % parameter, the input argument can be: 7 | % p = 'tankRadius' 8 | % p = 'tankHeight' 9 | % p = 'pipeRadius' 10 | % p = 'transPipeHeight' 11 | % p = 'correctionTerm' 12 | % p = 'gravityConstant' 13 | % p = 'pumpMinFlow' 14 | % p = 'pumpMaxFlow' 15 | % 16 | % If the input argument is omitted, all parameters are grouped and returned 17 | % in a single struct. 18 | 19 | % https://github.com/e-controls/Sim3Tanks 20 | 21 | %========================================================================== 22 | 23 | if(nargin()>1) 24 | error(getMessage('ERR002')); 25 | end 26 | 27 | %========================================================================== 28 | 29 | param{1} = 5; % Radius of the tanks (cm) 30 | param{2} = 50; % Height of the tanks (cm) 31 | param{3} = 0.6; % Radius of the pipes (cm) 32 | param{4} = 30; % Height of the transmission pipes (cm) 33 | param{5} = 1; % Flow correction term (-) 34 | param{6} = 981; % Gravity constant (cm/s^2) 35 | param{7} = 0; % Minimum flow value of the pumps (cm^3/s) 36 | param{8} = 120; % Maximum flow value of the pumps (cm^3/s) 37 | 38 | LIST_OF_PARAM = Sim3Tanks.LIST_OF_PARAM; 39 | 40 | if(numel(param) ~= numel(LIST_OF_PARAM)) 41 | error(getMessage('ERR006')); 42 | end 43 | 44 | %========================================================================== 45 | 46 | if(nargin()==0) 47 | 48 | for i = 1 : numel(LIST_OF_PARAM) 49 | default.(LIST_OF_PARAM{i}) = param{i}; 50 | end 51 | 52 | varargout{1} = default; 53 | 54 | elseif(nargin()==1) 55 | 56 | varargout{1} = []; 57 | for i = 1 : numel(LIST_OF_PARAM) 58 | if(strcmpi(varargin{1},LIST_OF_PARAM{i})) 59 | varargout{1} = param{i}; 60 | break; 61 | end 62 | end 63 | 64 | if(isempty(varargout{1})) 65 | error(getMessage('ERR003')); 66 | end 67 | 68 | else 69 | error(getMessage('ERR000')); 70 | end 71 | -------------------------------------------------------------------------------- /addons/faultDriftwise.m: -------------------------------------------------------------------------------- 1 | function [varargout] = faultDriftwise(MAGNITUDE,FAULT_TIME,TIME_VECTOR) 2 | % [SIGNAL,TIME_VECTOR] = faultDriftwise(MAGNITUDE,FAULT_TIME,TIME_VECTOR) 3 | % generates a driftwise fault signal, where MAGNITUDE specifies the maximum 4 | % fault magnitude within the range FAULT_TIME, which is a four-position 5 | % vector containing each transition time of the fault, [t1,t2,t3,t4]. The 6 | % output SIGNAL is the corresponding signal vector, and TIME_VECTOR is a 7 | % vector of time points. 8 | % 9 | % If TIME_VECTOR is omitted, a default time vector is declared with the 10 | % following settings: [0 : 0.1 : max(FAULT_TIME)+10] 11 | % 12 | % Example: 13 | % [signal,t] = faultDriftwise(0.8,[20 40 80 120]); 14 | % plot(t,signal); 15 | % grid on; 16 | % xlabel('time'); 17 | % ylabel('magnitude'); 18 | % title('Driftwise Fault Signal'); 19 | 20 | % Written by Arllem Farias, 2024. 21 | % https://github.com/e-controls/Sim3Tanks 22 | 23 | if(MAGNITUDE<0 || MAGNITUDE>1) 24 | error('MAGNITUDE must be within the range [0,1].'); 25 | elseif(numel(FAULT_TIME)~=4) 26 | error('FAULT_TIME must be a four-position vector.'); 27 | end 28 | 29 | % If the TIME_VECTOR argument is omitted. 30 | if(nargin() < 3) 31 | duration = max(FAULT_TIME) + 10; 32 | TIME_VECTOR = 0 : 0.1 : duration; 33 | end 34 | 35 | % First part of the signal 36 | SIGNAL = zeros(size(TIME_VECTOR)); 37 | 38 | SIGNAL(TIME_VECTOR>=FAULT_TIME(1) & TIME_VECTOR<=FAULT_TIME(2)) = 1; 39 | NUMBER_OF_SAMPLES = sum(SIGNAL); 40 | [~,START_INDEX] = max(SIGNAL); 41 | 42 | for j = START_INDEX : START_INDEX+NUMBER_OF_SAMPLES-1 43 | SIGNAL(j) = SIGNAL(j-1) + MAGNITUDE/NUMBER_OF_SAMPLES; 44 | SIGNAL(j) = min(1,SIGNAL(j)); 45 | end 46 | 47 | % Second part of the signal 48 | SIGNAL(TIME_VECTOR>FAULT_TIME(2) & TIME_VECTOR<=FAULT_TIME(3)) = MAGNITUDE; 49 | 50 | % Third part of the signal 51 | SIGNAL_AUX = zeros(size(TIME_VECTOR)); 52 | 53 | SIGNAL_AUX(TIME_VECTOR>FAULT_TIME(3) & TIME_VECTOR<=FAULT_TIME(4)) = 1; 54 | NUMBER_OF_SAMPLES = sum(SIGNAL_AUX); 55 | [~,START_INDEX] = max(SIGNAL_AUX); 56 | 57 | for j = START_INDEX : START_INDEX+NUMBER_OF_SAMPLES-1 58 | SIGNAL(j) = SIGNAL(j-1) - MAGNITUDE/NUMBER_OF_SAMPLES; 59 | SIGNAL(j) = max(0,SIGNAL(j)); 60 | end 61 | 62 | varargout{1} = SIGNAL; 63 | varargout{2} = TIME_VECTOR; 64 | 65 | end -------------------------------------------------------------------------------- /src/defaultOperationMode.m: -------------------------------------------------------------------------------- 1 | function [varargout] = defaultOperationMode(varargin) 2 | % defaultOperationMode is a Sim3Tanks function. This function returns the 3 | % default operation mode of the system valves. 4 | % 5 | % d = defaultOperationMode(v) returns the default state of a specific 6 | % valve, the input argument can be: 7 | % v = 'Kp1' 8 | % v = 'Kp2' 9 | % v = 'Kp3' 10 | % v = 'Ka' 11 | % v = 'Kb' 12 | % v = 'K13' 13 | % v = 'K23' 14 | % v = 'K1' 15 | % v = 'K2' 16 | % v = 'K3' 17 | % 18 | % If the input argument is omitted, all valve states are grouped and 19 | % returned into a single struct. 20 | 21 | % https://github.com/e-controls/Sim3Tanks 22 | 23 | %========================================================================== 24 | 25 | if(nargin()>1) 26 | error(getMessage('ERR002')); 27 | end 28 | 29 | %========================================================================== 30 | 31 | valve{1} = 'Open'; % Default state of the valve Kp1 32 | valve{2} = 'Open'; % Default state of the valve Kp2 33 | valve{3} = 'Closed'; % Default state of the valve Kp3 34 | valve{4} = 'Closed'; % Default state of the valve Ka 35 | valve{5} = 'Closed'; % Default state of the valve Kb 36 | valve{6} = 'Open'; % Default state of the valve K13 37 | valve{7} = 'Open'; % Default state of the valve K23 38 | valve{8} = 'Closed'; % Default state of the valve K1 39 | valve{9} = 'Closed'; % Default state of the valve K2 40 | valve{10} = 'Open'; % Default state of the valve K3 41 | 42 | LIST_OF_VALVES = Sim3Tanks.LIST_OF_VALVES; 43 | 44 | if(numel(valve) ~= numel(LIST_OF_VALVES)) 45 | error(getMessage('ERR006')); 46 | end 47 | 48 | %========================================================================== 49 | 50 | if(nargin()==0) 51 | 52 | for i = 1 : numel(LIST_OF_VALVES) 53 | default.(LIST_OF_VALVES{i}) = valve{i}; 54 | end 55 | varargout{1} = default; 56 | 57 | elseif(nargin()==1) 58 | 59 | varargout{1} = []; 60 | for i = 1 : numel(LIST_OF_VALVES) 61 | if(strcmpi(varargin{1},LIST_OF_VALVES{i})) 62 | varargout{1} = valve{i}; 63 | break; 64 | end 65 | end 66 | 67 | if(isempty(varargout{1})) 68 | error(getMessage('ERR003')); 69 | end 70 | 71 | else 72 | error(getMessage('ERR000')); 73 | end -------------------------------------------------------------------------------- /src/checkEnabledValves.m: -------------------------------------------------------------------------------- 1 | function [varargout] = checkEnabledValves(varargin) 2 | % checkEnabledValves is a Sim3Tanks function. This function checks the 3 | % valves that have the EnableControl field set to true in a Sim3Tanks 4 | % object and returns an array with the IDs of the enabled valves. A second 5 | % array with the OpeningRate field values is also returned. If the 6 | % EnableControl field is set to false, then the returned value will be 0 7 | % or 1, depending on the operation mode configured in the model (Closed or 8 | % Open, respectively). 9 | % 10 | % Example: 11 | % [valveID,openingRate] = checkEnabledValves(objSim3Tanks); 12 | 13 | % https://github.com/e-controls/Sim3Tanks 14 | 15 | %========================================================================== 16 | 17 | if(nargin()<1) 18 | error(getMessage('ERR001')); 19 | elseif(nargin()>1) 20 | error(getMessage('ERR002')); 21 | end 22 | 23 | if(isa(varargin{1},'Sim3Tanks')) 24 | objSim3Tanks = varargin{1}; 25 | else 26 | error(getMessage('ERR004')); 27 | end 28 | 29 | %========================================================================== 30 | 31 | LIST_OF_FIELDS = Sim3Tanks.LIST_OF_FIELDS; 32 | LIST_OF_VALVES = Sim3Tanks.LIST_OF_VALVES; 33 | 34 | %========================================================================== 35 | 36 | opMode = checkOperationMode(objSim3Tanks); 37 | 38 | valveID = cell(size(LIST_OF_VALVES)); 39 | openingRate = zeros(size(LIST_OF_VALVES)); 40 | 41 | for i = 1 : numel(LIST_OF_VALVES) 42 | 43 | valve = objSim3Tanks.Model.(LIST_OF_FIELDS{2}).(LIST_OF_VALVES{i}); 44 | 45 | if(islogical(valve.EnableControl) && valve.EnableControl) 46 | 47 | if(isempty(valve.OpeningRate)) 48 | warning(getMessage('WARN003')); 49 | openingRate(i) = opMode(i); 50 | elseif(valve.OpeningRate<0 || valve.OpeningRate>1) 51 | warning(getMessage('WARN004')); 52 | openingRate(i) = satSignal(valve.OpeningRate,[0 1]); 53 | else 54 | openingRate(i) = valve.OpeningRate; 55 | end 56 | 57 | valveID{i} = LIST_OF_VALVES{i}; 58 | 59 | elseif(islogical(valve.EnableControl)) 60 | openingRate(i) = opMode(i); 61 | valveID{i} = []; 62 | else 63 | error(getMessage('ERR010')); 64 | end 65 | 66 | end 67 | 68 | varargout{1} = valveID; 69 | varargout{2} = openingRate; 70 | 71 | end -------------------------------------------------------------------------------- /src/checkEnabledNoises.m: -------------------------------------------------------------------------------- 1 | function [varargout] = checkEnabledNoises(varargin) 2 | % checkEnabledNoises is a Sim3Tanks function. This function checks if the 3 | % process and measurement noises have their EnableSignal field set to true 4 | % in a Sim3Tanks object and returns an array with the Magnitude field 5 | % values. If the EnableSignal field is set to false, then the returned 6 | % value will be an array of 0s with appropriate dimensions. 7 | % 8 | % Example: 9 | % [pNoise,mNoise] = checkEnabledNoises(objSim3Tanks); 10 | 11 | % https://github.com/e-controls/Sim3Tanks 12 | 13 | %========================================================================== 14 | 15 | if(nargin()<1) 16 | error(getMessage('ERR001')); 17 | elseif(nargin()>1) 18 | error(getMessage('ERR002')); 19 | end 20 | 21 | if(isa(varargin{1},'Sim3Tanks')) 22 | objSim3Tanks = varargin{1}; 23 | else 24 | error(getMessage('ERR004')); 25 | end 26 | 27 | %========================================================================== 28 | 29 | LIST_OF_FIELDS = Sim3Tanks.LIST_OF_FIELDS; 30 | Nx = numel(Sim3Tanks.LIST_OF_STATES); 31 | Nq = numel(Sim3Tanks.LIST_OF_FLOWS); 32 | 33 | %========================================================================== 34 | 35 | pNoise = objSim3Tanks.Model.(LIST_OF_FIELDS{4}); 36 | 37 | if(islogical(pNoise.EnableSignal) && pNoise.EnableSignal) 38 | 39 | if(isempty(pNoise.Magnitude)) 40 | warning(getMessage('WARN005')); 41 | pNoiseMag = zeros(1,Nx); 42 | elseif(numel(pNoise.Magnitude)~=Nx) 43 | error(getMessage('ERR006')); 44 | else 45 | pNoiseMag = pNoise.Magnitude; 46 | end 47 | 48 | elseif(islogical(pNoise.EnableSignal)) 49 | pNoiseMag = zeros(1,Nx); 50 | else 51 | error(getMessage('ERR011')); 52 | end 53 | 54 | %========================================================================== 55 | 56 | mNoise = objSim3Tanks.Model.(LIST_OF_FIELDS{5}); 57 | 58 | if(islogical(mNoise.EnableSignal) && mNoise.EnableSignal) 59 | 60 | if(isempty(mNoise.Magnitude)) 61 | warning(getMessage('WARN006')); 62 | mNoiseMag = zeros(1,Nx+Nq); 63 | elseif(numel(mNoise.Magnitude)~=Nx+Nq) 64 | error(getMessage('ERR006')); 65 | else 66 | mNoiseMag = mNoise.Magnitude; 67 | end 68 | 69 | elseif(islogical(mNoise.EnableSignal)) 70 | mNoiseMag = zeros(1,Nx+Nq); 71 | else 72 | error(getMessage('ERR011')); 73 | end 74 | 75 | varargout{1} = pNoiseMag; 76 | varargout{2} = mNoiseMag; 77 | 78 | end -------------------------------------------------------------------------------- /src/@Sim3Tanks/setDefaultModel.m: -------------------------------------------------------------------------------- 1 | function setDefaultModel(varargin) 2 | % setDefaultModel is a Sim3Tanks method. This method does not have an input 3 | % argument and configures a Sim3Tanks object to the default model. 4 | % 5 | % Example: 6 | % tts.setDefaultModel(); 7 | 8 | % https://github.com/e-controls/Sim3Tanks 9 | 10 | %========================================================================== 11 | 12 | if(nargin()>1) 13 | error(getMessage('ERR002')); 14 | else 15 | objSim3Tanks = varargin{1}; 16 | end 17 | 18 | %========================================================================== 19 | 20 | LIST_OF_FIELDS = Sim3Tanks.LIST_OF_FIELDS; 21 | LIST_OF_VALVES = Sim3Tanks.LIST_OF_VALVES; 22 | LIST_OF_FAULTS = Sim3Tanks.LIST_OF_FAULTS; 23 | LIST_OF_PARAM = Sim3Tanks.LIST_OF_PARAM; 24 | Nx = numel(Sim3Tanks.LIST_OF_STATES); 25 | Nq = numel(Sim3Tanks.LIST_OF_FLOWS); 26 | 27 | %========================================================================== 28 | 29 | D = defaultPhysicalParam(); 30 | for i = 1 : numel(LIST_OF_PARAM) 31 | objSim3Tanks.Model.(LIST_OF_FIELDS{1}).(LIST_OF_PARAM{i}) = D.(LIST_OF_PARAM{i}); 32 | end 33 | 34 | D = defaultOperationMode(); 35 | for i = 1 : numel(LIST_OF_VALVES) 36 | objSim3Tanks.Model.(LIST_OF_FIELDS{2}).(LIST_OF_VALVES{i}).OperationMode = D.(LIST_OF_VALVES{i}); 37 | end 38 | 39 | K = checkOperationMode(objSim3Tanks); 40 | for i = 1 : numel(LIST_OF_VALVES) 41 | objSim3Tanks.Model.(LIST_OF_FIELDS{2}).(LIST_OF_VALVES{i}).EnableControl = false; 42 | objSim3Tanks.Model.(LIST_OF_FIELDS{2}).(LIST_OF_VALVES{i}).OpeningRate = K(i); 43 | end 44 | objSim3Tanks.Model.(LIST_OF_FIELDS{2}).(LIST_OF_VALVES{1}).EnableControl = true; 45 | objSim3Tanks.Model.(LIST_OF_FIELDS{2}).(LIST_OF_VALVES{2}).EnableControl = true; 46 | 47 | for i = 1 : numel(LIST_OF_FAULTS) 48 | objSim3Tanks.Model.(LIST_OF_FIELDS{3}).(LIST_OF_FAULTS{i}).EnableSignal = false; 49 | objSim3Tanks.Model.(LIST_OF_FIELDS{3}).(LIST_OF_FAULTS{i}).Magnitude = 0; 50 | end 51 | 52 | for i = 11 : numel(LIST_OF_FAULTS) 53 | objSim3Tanks.Model.(LIST_OF_FIELDS{3}).(LIST_OF_FAULTS{i}).Offset = 0; 54 | end 55 | 56 | objSim3Tanks.Model.(LIST_OF_FIELDS{4}).EnableSignal = false; 57 | objSim3Tanks.Model.(LIST_OF_FIELDS{4}).Magnitude = zeros(1,Nx); 58 | 59 | objSim3Tanks.Model.(LIST_OF_FIELDS{5}).EnableSignal = false; 60 | objSim3Tanks.Model.(LIST_OF_FIELDS{5}).Magnitude = zeros(1,Nx+Nq); 61 | 62 | objSim3Tanks.Model.(LIST_OF_FIELDS{6}) = [40 25 20]; % [h1 h2 h3] 63 | 64 | objSim3Tanks.clearVariables(); 65 | 66 | end -------------------------------------------------------------------------------- /src/checkPhysicalParam.m: -------------------------------------------------------------------------------- 1 | function [varargout] = checkPhysicalParam(varargin) 2 | % checkPhysicalParam is a Sim3Tanks function. This function checks if the 3 | % values in the PhysicalParam field are consistent (i.e., if they are real 4 | % and finite) and returns a struct with the respective values. A second 5 | % argument with the field names is also returned. 6 | % 7 | % Example: 8 | % [Param,ID] = checkPhysicalParam(objSim3Tanks); 9 | 10 | % https://github.com/e-controls/Sim3Tanks 11 | 12 | %========================================================================== 13 | 14 | if(nargin()<1) 15 | error(getMessage('ERR001')); 16 | elseif(nargin()>1) 17 | error(getMessage('ERR002')); 18 | end 19 | 20 | if(isa(varargin{1},'Sim3Tanks')) 21 | objSim3Tanks = varargin{1}; 22 | else 23 | error(getMessage('ERR004')); 24 | end 25 | 26 | %========================================================================== 27 | 28 | LIST_OF_FIELDS = Sim3Tanks.LIST_OF_FIELDS; 29 | LIST_OF_PARAM = Sim3Tanks.LIST_OF_PARAM; 30 | 31 | %========================================================================== 32 | 33 | Param = struct(); 34 | 35 | for i = 1 : numel(LIST_OF_PARAM) 36 | value = objSim3Tanks.Model.(LIST_OF_FIELDS{1}).(LIST_OF_PARAM{i}); 37 | if(isempty(value)) 38 | error(getMessage('ERR008')); 39 | elseif(isnumeric(value) && isfinite(value)) 40 | Param.(LIST_OF_PARAM{i}) = value; 41 | else 42 | error(getMessage('ERR008')); 43 | end 44 | end 45 | 46 | if(~(Param.(LIST_OF_PARAM{1})>0)) 47 | % TankRadius 48 | error(getMessage('ERR012')); 49 | 50 | elseif(~(Param.(LIST_OF_PARAM{2})>0)) 51 | % TankHeight 52 | error(getMessage('ERR013')); 53 | 54 | elseif(~(Param.(LIST_OF_PARAM{3})>0 && Param.(LIST_OF_PARAM{3})0 && Param.(LIST_OF_PARAM{4})0)) 63 | % CorrectionTerm 64 | error(getMessage('ERR016')); 65 | 66 | elseif(~(Param.(LIST_OF_PARAM{6})>0)) 67 | % GravityConstant 68 | error(getMessage('ERR017')); 69 | 70 | elseif(~(Param.(LIST_OF_PARAM{7})>=0)) 71 | % PumpMinFlow 72 | error(getMessage('ERR018')); 73 | 74 | elseif(~(Param.(LIST_OF_PARAM{8})>=Param.(LIST_OF_PARAM{7}))) 75 | % PumpMaxFlow 76 | error(getMessage('ERR019')); 77 | 78 | else 79 | varargout{1} = Param; 80 | varargout{2} = fieldnames(Param); 81 | end 82 | 83 | end -------------------------------------------------------------------------------- /src/sysFlowRates.m: -------------------------------------------------------------------------------- 1 | function Qx = sysFlowRates(x,K,h0,Beta) 2 | % sysFlowRates is a Sim3Tanks function. This function describes the flow 3 | % equations of the three-tank system. 4 | % 5 | % Example: 6 | % Qx = sysFlowRates(x,K,h0,Beta) 7 | % x : state vector 8 | % K : valve vector 9 | % h0 : transmission pipe height 10 | % Beta : constant value 11 | 12 | % https://github.com/e-controls/Sim3Tanks 13 | 14 | %========================================================================== 15 | 16 | if(nargin()<4) 17 | error(getMessage('ERR001')); 18 | elseif(nargin()>4) 19 | error(getMessage('ERR002')); 20 | end 21 | 22 | %========================================================================== 23 | 24 | h1 = x(1); 25 | h2 = x(2); 26 | h3 = x(3); 27 | 28 | if(h1<=h0 && h2<=h0 && h3<=h0) 29 | % fprintf('CASE 1 : h1<=h0, h2<=h0, h3<=h0\n'); 30 | Qa = K(4)*0; 31 | Qb = K(5)*0; 32 | 33 | elseif(h1<=h0 && h2<=h0 && h3>h0) 34 | % fprintf('CASE 2 : h1<=h0, h2<=h0, h3>h0\n'); 35 | Qa = K(4)*Beta*sign(h0-h3)*sqrt(abs(h0-h3)); 36 | Qb = K(5)*Beta*sign(h0-h3)*sqrt(abs(h0-h3)); 37 | 38 | elseif(h1<=h0 && h2>h0 && h3<=h0) 39 | % fprintf('CASE 3 : h1<=h0, h2>h0, h3<=h0\n'); 40 | Qa = K(4)*0; 41 | Qb = K(5)*Beta*sign(h2-h3)*sqrt(abs(h2-h3)); 42 | 43 | elseif(h1<=h0 && h2>h0 && h3>h0) 44 | % fprintf('CASE 4 : h1<=h0, h2>h0, h3>h0\n'); 45 | Qa = K(4)*Beta*sign(h0-h3)*sqrt(abs(h0-h3)); 46 | Qb = K(5)*Beta*sign(h2-h3)*sqrt(abs(h2-h3)); 47 | 48 | elseif(h1>h0 && h2<=h0 && h3<=h0) 49 | % fprintf('CASE 5 : h1>h0, h2<=h0, h3<=h0\n'); 50 | Qa = K(4)*Beta*sign(h1-h0)*sqrt(abs(h1-h0)); 51 | Qb = K(5)*0; 52 | 53 | elseif(h1>h0 && h2<=h0 && h3>h0) 54 | % fprintf('CASE 6 : h1>h0, h2<=h0, h3>h0\n'); 55 | Qa = K(4)*Beta*sign(h1-h3)*sqrt(abs(h1-h3)); 56 | Qb = K(5)*Beta*sign(h0-h3)*sqrt(abs(h0-h3)); 57 | 58 | elseif(h1>h0 && h2>h0 && h3<=h0) 59 | % fprintf('CASE 7 : h1>h0, h2>h0, h3<=h0\n'); 60 | Qa = K(4)*Beta*sign(h1-h0)*sqrt(abs(h1-h0)); 61 | Qb = K(5)*Beta*sign(h2-h0)*sqrt(abs(h2-h0)); 62 | 63 | elseif(h1>h0 && h2>h0 && h3>h0) 64 | % fprintf('CASE 8 : h1>h0, h2>h0, h3>h0\n'); 65 | Qa = K(4)*Beta*sign(h1-h3)*sqrt(abs(h1-h3)); 66 | Qb = K(5)*Beta*sign(h2-h3)*sqrt(abs(h2-h3)); 67 | 68 | else 69 | error(getMessage('ERR000')); 70 | end 71 | 72 | Q13 = K(6)*Beta*sign(h1-h3)*sqrt(abs(h1-h3)); 73 | Q23 = K(7)*Beta*sign(h2-h3)*sqrt(abs(h2-h3)); 74 | Q1 = K(8)*Beta*sqrt(abs(h1)); 75 | Q2 = K(9)*Beta*sqrt(abs(h2)); 76 | Q3 = K(10)*Beta*sqrt(abs(h3)); 77 | 78 | Qx = [Qa,Qb,Q13,Q23,Q1,Q2,Q3]; 79 | 80 | end -------------------------------------------------------------------------------- /src/createSim3Tanks.m: -------------------------------------------------------------------------------- 1 | function [varargout] = createSim3Tanks(varargin) 2 | % createSim3Tanks is a Sim3Tanks function. This function does not have an 3 | % input argument and returns a Sim3Tanks object. 4 | % 5 | % tts = createSim3Tanks() returns an object with the following attributes 6 | % and methods: 7 | % Model: [attribute] 8 | % simulateModel: [method] 9 | % displayModel: [method] 10 | % clearModel: [method] 11 | % clearVariables: [method] 12 | % setDefaultModel: [method] 13 | % getDefaultLinearModel: [method] 14 | % getStateVariables: [method] 15 | % getFlowVariables: [method] 16 | % getSensorMeasurements: [method] 17 | % getValveSignals: [method] 18 | % getFaultMagnitudes: [method] 19 | % getFaultOffsets: [method] 20 | % plotLevels: [method] 21 | % plotFlows: [method] 22 | % plotValves: [method] 23 | % plotFaultMagnitudes: [method] 24 | % plotFaultOffsets: [method] 25 | % 26 | % By default, all numeric fields are empty, the operation mode of all 27 | % valves is set to 'Closed', and control is disabled. In addition, all 28 | % fault signals are disabled, including the process and measurement noises. 29 | % It is up to the user to configure the scenario as desired. 30 | % 31 | % To set the default scenario, call: tts.setDefaultModel(); 32 | % 33 | % To see the Model attribute and their values, call: tts.displayModel(); 34 | 35 | % https://github.com/e-controls/Sim3Tanks 36 | 37 | %========================================================================== 38 | 39 | if(nargin()~=0) 40 | error(getMessage('ERR002')); 41 | end 42 | 43 | %========================================================================== 44 | 45 | % Should we create a constructor using KEY-VALUE pairs??? 46 | % Ex.: createSim3Tanks('TankRadius',0.5,'Kp1',{open,enabled,openingRate},...) 47 | 48 | persistent globalCounter; 49 | 50 | if(isempty(globalCounter)) 51 | globalCounter = 1; 52 | else 53 | globalCounter = globalCounter + 1; 54 | end 55 | 56 | tts = Sim3Tanks(); % Calling constructor method 57 | 58 | tts.About.Name = 'Sim3Tanks'; 59 | tts.About.Version = '2.0.2'; 60 | tts.About.Description = 'A Benchmark Model Simulator for Process Control and Monitoring'; 61 | tts.About.License = 'MIT'; 62 | tts.About.Link = ['' ... 63 | 'https://github.com/e-controls/Sim3Tanks']; 64 | tts.About.CurrentPath = pwd(); 65 | tts.About.MatlabVer = version(); 66 | tts.About.SystemArch = computer(); 67 | tts.About.ObjectID = globalCounter; 68 | 69 | varargout{1} = tts; 70 | 71 | end 72 | -------------------------------------------------------------------------------- /src/checkEnabledFaults.m: -------------------------------------------------------------------------------- 1 | function [varargout] = checkEnabledFaults(varargin) 2 | % checkEnabledFaults is a Sim3Tanks function. This function checks the 3 | % faults that have EnableSignal field set to true in a Sim3Tanks object and 4 | % returns an array with the IDs of the enabled faults. Two more arrays with 5 | % the values ​​of the Magnitude and Offset fields are also returned. If the 6 | % EnableSignal field is set to false, then the returned values will be 0. 7 | % 8 | % Example: 9 | % [faultID,faultMag,offset] = checkEnabledFaults(objSim3Tanks); 10 | 11 | % https://github.com/e-controls/Sim3Tanks 12 | 13 | %========================================================================== 14 | 15 | if(nargin()<1) 16 | error(getMessage('ERR001')); 17 | elseif(nargin()>1) 18 | error(getMessage('ERR002')); 19 | end 20 | 21 | if(isa(varargin{1},'Sim3Tanks')) 22 | objSim3Tanks = varargin{1}; 23 | else 24 | error(getMessage('ERR004')); 25 | end 26 | 27 | %========================================================================== 28 | 29 | LIST_OF_FIELDS = Sim3Tanks.LIST_OF_FIELDS; 30 | LIST_OF_FAULTS = Sim3Tanks.LIST_OF_FAULTS; 31 | 32 | %========================================================================== 33 | 34 | faultID = cell(size(LIST_OF_FAULTS)); 35 | faultMag = zeros(size(LIST_OF_FAULTS)); 36 | offset = zeros(size(LIST_OF_FAULTS)); 37 | 38 | for i = 1 : numel(LIST_OF_FAULTS) 39 | 40 | fault = objSim3Tanks.Model.(LIST_OF_FIELDS{3}).(LIST_OF_FAULTS{i}); 41 | 42 | if(islogical(fault.EnableSignal) && fault.EnableSignal) 43 | 44 | % Check magnitude values 45 | if(isempty(fault.Magnitude)) 46 | warning(getMessage('WARN001')); 47 | faultMag(i) = 0; 48 | elseif(fault.Magnitude<0 || fault.Magnitude>1) 49 | warning(getMessage('WARN002')); 50 | faultMag(i) = satSignal(fault.Magnitude,[0 1]); 51 | else 52 | faultMag(i) = fault.Magnitude; 53 | end 54 | 55 | % Check offset values 56 | if(isfield(fault,'Offset')) 57 | if(isempty(fault.Offset)) 58 | warning(getMessage('WARN007')); 59 | offset(i) = 0; 60 | elseif(~isfinite(fault.Offset)) 61 | error(getMessage('ERR022')); 62 | else 63 | offset(i) = fault.Offset; 64 | end 65 | end 66 | 67 | faultID{i} = LIST_OF_FAULTS{i}; 68 | 69 | elseif(islogical(fault.EnableSignal)) 70 | faultID{i} = []; 71 | faultMag(i) = 0; 72 | offset(i) = 0; 73 | else 74 | error(getMessage('ERR011')); 75 | end 76 | 77 | end 78 | 79 | varargout{1} = faultID; 80 | varargout{2} = faultMag; 81 | varargout{3} = offset; 82 | 83 | end -------------------------------------------------------------------------------- /src/@Sim3Tanks/plotFaultOffsets.m: -------------------------------------------------------------------------------- 1 | function plotFaultOffsets(varargin) 2 | % plotFaultOffsets is a Sim3Tanks method. This method plots the behavior 3 | % of the fault offsets over time. 4 | % 5 | % Example: 6 | % tts.plotFaultOffsets(); 7 | % 8 | % To plot a specific fault offset, use one of the following options as 9 | % an input argument: 'f11', 'f12', 'f13', ..., 'f21', 'f22', or 'f23'. 10 | % 11 | % If there is no input argument, all fault offsets will be plotted on the 12 | % same figure. 13 | 14 | % https://github.com/e-controls/Sim3Tanks 15 | 16 | %========================================================================== 17 | 18 | if(nargin()>2) 19 | error(getMessage('ERR002')); 20 | else 21 | objSim3Tanks = varargin{1}; 22 | end 23 | 24 | time = objSim3Tanks.getInternalSimulationTime(); 25 | F = objSim3Tanks.getInternalFaultOffsets(time); 26 | 27 | if(isempty(F)) 28 | warning(getMessage('WARN008')); 29 | return; 30 | else 31 | time = F.Time; % casting to seconds 32 | end 33 | 34 | FAULT_IDs = Sim3Tanks.LIST_OF_FAULTS(11:end); 35 | 36 | tagSim3Tanks = 'Sim3Tanks'; 37 | 38 | %========================================================================== 39 | 40 | if(nargin()==1) 41 | 42 | plotAllFaults(tagSim3Tanks,FAULT_IDs,F,time); 43 | 44 | elseif(nargin()==2) 45 | 46 | option = find(strcmpi(varargin{2},FAULT_IDs)); 47 | 48 | if(~isempty(option)) 49 | ID = FAULT_IDs{option}; 50 | else 51 | error(getMessage('ERR003')); 52 | end 53 | 54 | plotOneFault(tagSim3Tanks,ID,F,time); 55 | 56 | end 57 | 58 | end 59 | 60 | %========================================================================== 61 | 62 | function plotAllFaults(tagSim3Tanks,FAULT_IDs,F,time) 63 | 64 | FIG_ID = [tagSim3Tanks,'_figOffset_fault_all']; 65 | 66 | try 67 | delete(findobj('Tag',FIG_ID)); 68 | catch 69 | end 70 | 71 | N0 = numel(time); 72 | N1 = round(N0/4); 73 | MarkIdx = 01:N1:N0; 74 | 75 | figure('Tag',FIG_ID,... 76 | 'Numbertitle','off',... 77 | 'Name',[tagSim3Tanks,': fault offsets']); 78 | 79 | for i = 1 : numel(FAULT_IDs) 80 | 81 | subplot(3,5,i); hold on; box on; 82 | 83 | p = plot(time,F.(FAULT_IDs{i}),'r-o','MarkerIndices',MarkIdx); 84 | legend(p,FAULT_IDs{i},'Location','Best'); 85 | 86 | xlim([time(1) time(N0)]); 87 | 88 | end 89 | 90 | end 91 | 92 | %========================================================================== 93 | 94 | function plotOneFault(tagSim3Tanks,FAULT_ID,F,time) 95 | 96 | FIG_ID = [tagSim3Tanks,'_figOffset_fault_',FAULT_ID]; 97 | 98 | try 99 | delete(findobj('Tag',FIG_ID)); 100 | catch 101 | end 102 | 103 | N0 = numel(time); 104 | N1 = round(N0/4); 105 | MarkIdx = 1:N1:N0; 106 | 107 | figure('Tag',FIG_ID,... 108 | 'Numbertitle','off',... 109 | 'Name',[tagSim3Tanks,': ',FAULT_ID,' offset']); hold on; box on; 110 | 111 | p = plot(time,F.(FAULT_ID),'r-o','MarkerIndices',MarkIdx); 112 | legend(p,FAULT_ID,'Location','Best'); 113 | 114 | xlabel('Time'); 115 | ylabel('Offset'); 116 | 117 | xlim([time(1) time(N0)]); 118 | 119 | end -------------------------------------------------------------------------------- /src/@Sim3Tanks/plotFaultMagnitudes.m: -------------------------------------------------------------------------------- 1 | function plotFaultMagnitudes(varargin) 2 | % plotFaultMagnitudes is a Sim3Tanks method. This method plots the behavior 3 | % of the fault magnitudes over time. 4 | % 5 | % Example: 6 | % tts.plotFaultMagnitudes(); 7 | % 8 | % To plot a specific fault magnitude, use one of the following options as 9 | % an input argument: 'f1', 'f2', 'f3', ..., 'f21', 'f22', or 'f23'. 10 | % 11 | % If there is no input argument, all fault magnitudes will be plotted on 12 | % the same figure. 13 | 14 | % https://github.com/e-controls/Sim3Tanks 15 | 16 | %========================================================================== 17 | 18 | if(nargin()>2) 19 | error(getMessage('ERR002')); 20 | else 21 | objSim3Tanks = varargin{1}; 22 | end 23 | 24 | time = objSim3Tanks.getInternalSimulationTime(); 25 | F = objSim3Tanks.getInternalFaultMagnitudes(time); 26 | 27 | if(isempty(F)) 28 | warning(getMessage('WARN008')); 29 | return; 30 | else 31 | time = F.Time; % casting to seconds 32 | end 33 | 34 | FAULT_IDs = Sim3Tanks.LIST_OF_FAULTS; 35 | 36 | tagSim3Tanks = 'Sim3Tanks'; 37 | 38 | %========================================================================== 39 | 40 | if(nargin()==1) 41 | 42 | plotAllFaults(tagSim3Tanks,FAULT_IDs,F,time); 43 | 44 | elseif(nargin()==2) 45 | 46 | option = find(strcmpi(varargin{2},FAULT_IDs)); 47 | 48 | if(~isempty(option)) 49 | ID = FAULT_IDs{option}; 50 | else 51 | error(getMessage('ERR003')); 52 | end 53 | 54 | plotOneFault(tagSim3Tanks,ID,F,time); 55 | 56 | end 57 | 58 | end 59 | 60 | %========================================================================== 61 | 62 | function plotAllFaults(tagSim3Tanks,FAULT_IDs,F,time) 63 | 64 | FIG_ID = [tagSim3Tanks,'_figMag_fault_all']; 65 | 66 | try 67 | delete(findobj('Tag',FIG_ID)); 68 | catch 69 | end 70 | 71 | N0 = numel(time); 72 | N1 = round(N0/4); 73 | MarkIdx = 01:N1:N0; 74 | 75 | figure('Tag',FIG_ID,... 76 | 'Numbertitle','off',... 77 | 'Name',[tagSim3Tanks,': fault magnitudes']); 78 | 79 | for i = 1 : numel(FAULT_IDs) 80 | 81 | subplot(4,6,i); hold on; box on; 82 | 83 | p = plot(time,F.(FAULT_IDs{i}),'r-o','MarkerIndices',MarkIdx); 84 | legend(p,FAULT_IDs{i},'Location','Best'); 85 | 86 | xlim([time(1) time(N0)]); 87 | ylim([0 1]); 88 | 89 | end 90 | 91 | end 92 | 93 | %========================================================================== 94 | 95 | function plotOneFault(tagSim3Tanks,FAULT_ID,F,time) 96 | 97 | FIG_ID = [tagSim3Tanks,'_figMag_fault_',FAULT_ID]; 98 | 99 | try 100 | delete(findobj('Tag',FIG_ID)); 101 | catch 102 | end 103 | 104 | N0 = numel(time); 105 | N1 = round(N0/4); 106 | MarkIdx = 1:N1:N0; 107 | 108 | figure('Tag',FIG_ID,... 109 | 'Numbertitle','off',... 110 | 'Name',[tagSim3Tanks,': ',FAULT_ID,' magnitude']); hold on; box on; 111 | 112 | p = plot(time,F.(FAULT_ID),'r-o','MarkerIndices',MarkIdx); 113 | legend(p,FAULT_ID,'Location','Best'); 114 | 115 | xlabel('Time'); 116 | ylabel('Magnitude'); 117 | 118 | xlim([time(1) time(N0)]); 119 | ylim([0 1]); 120 | 121 | end -------------------------------------------------------------------------------- /src/@Sim3Tanks/getDefaultLinearModel.m: -------------------------------------------------------------------------------- 1 | function [varargout] = getDefaultLinearModel(varargin) 2 | % getDefaultLinearModel is a Sim3Tanks method. This method returns a linear 3 | % model of the default scenario. 4 | % 5 | % Example: 6 | % [SYS,OP] = tts.getDefaultLinearModel(x1op,METHOD,TSPAN); 7 | % 8 | % >> x1op > 0 (mandatory): define the operating point of T1. 9 | % 10 | % >> METHOD (optional): define the discretization method. The following 11 | % options are valid: 12 | % - 'zoh'; 13 | % - 'foh'; 14 | % - 'impulse'; 15 | % - 'tustin'; 16 | % - 'matched'; 17 | % - 'euler'; 18 | % 19 | % >> TSPAN (optional): define the sampling time of the discretization 20 | % method. If METHOD is passed as input, then TSPAN becomes mandatory. 21 | % 22 | % A continuous model is returned if METHOD and TSPAN are omitted. 23 | 24 | % https://github.com/e-controls/Sim3Tanks 25 | 26 | %========================================================================== 27 | 28 | if(nargin()>4) 29 | error(getMessage('ERR002')); 30 | elseif(mod(nargin(),2) ~= 0) 31 | error(getMessage('ERR021')); 32 | else 33 | objSim3Tanks = varargin{1}; 34 | end 35 | 36 | %========================================================================== 37 | 38 | [Param,ID] = checkPhysicalParam(objSim3Tanks); 39 | 40 | Rtank = Param.(ID{1}); 41 | Hmax = Param.(ID{2}); 42 | Rpipe = Param.(ID{3}); 43 | % h0 = Param.(ID{4}); 44 | mu = Param.(ID{5}); 45 | g = Param.(ID{6}); 46 | % Qmin = Param.(ID{7}); 47 | Qmax = Param.(ID{8}); 48 | 49 | Sc = pi()*(Rtank^2); % Cross-sectional area of the tanks (cm^2) 50 | S = pi()*(Rpipe^2); % Cross-sectional area of the pipes (cm^2) 51 | Beta = mu*S*sqrt(2*g); % Constant value 52 | 53 | %========================================================================== 54 | 55 | x1op = varargin{2}; 56 | 57 | if ~(isnumeric(x1op) && isfinite(x1op) && isscalar(x1op) && x1op>0 && x1op0) 118 | error(getMessage('ERR003')); 119 | end 120 | 121 | if(strcmpi(METHOD,options.euler)) 122 | nx = size(A,2); % x = [h1 h2 h3]' 123 | SYS = ss(eye(nx)+TS*A,TS*B,C,D,TS); 124 | 125 | else 126 | % c2d function from MATLAB 127 | SYS = c2d(SYS,TS,METHOD); 128 | end 129 | end 130 | 131 | varargout{1} = SYS; 132 | varargout{2} = OP; 133 | 134 | end -------------------------------------------------------------------------------- /src/@Sim3Tanks/plotLevels.m: -------------------------------------------------------------------------------- 1 | function plotLevels(varargin) 2 | % plotLevels is a Sim3Tanks method. This method plots the behavior of the 3 | % levels over time. 4 | % 5 | % Example: 6 | % tts.plotLevels(); 7 | % 8 | % To plot a specific level, use one of the following options as an input 9 | % argument: 10 | % 'h1' : to plot the level of Tank 1 11 | % 'h2' : to plot the level of Tank 2 12 | % 'h3' : to plot the level of Tank 3 13 | % 14 | % If there is no input argument, all levels will be plotted on the same 15 | % figure. 16 | 17 | % https://github.com/e-controls/Sim3Tanks 18 | 19 | %========================================================================== 20 | 21 | if(nargin()>2) 22 | error(getMessage('ERR002')); 23 | else 24 | objSim3Tanks = varargin{1}; 25 | end 26 | 27 | time = objSim3Tanks.getInternalSimulationTime(); 28 | X = objSim3Tanks.getInternalStateVariables(time); 29 | 30 | if(isempty(X)) 31 | warning(getMessage('WARN008')); 32 | return; 33 | else % If I have a non-empty X, then I also have a non-empty Y. 34 | Y = objSim3Tanks.getInternalSensorMeasurements(time); 35 | time = Y.Time; % casting to seconds 36 | end 37 | 38 | STATE_IDs = Sim3Tanks.LIST_OF_STATES; 39 | 40 | tagSim3Tanks = 'Sim3Tanks'; 41 | 42 | %========================================================================== 43 | 44 | if(nargin()==1) 45 | 46 | plotAllLevels(tagSim3Tanks,STATE_IDs,X,Y,time); 47 | 48 | elseif(nargin()==2) 49 | 50 | option = find(strcmpi(varargin{2},STATE_IDs)); 51 | 52 | if(~isempty(option)) 53 | ID = STATE_IDs{option}; 54 | else 55 | error(getMessage('ERR003')); 56 | end 57 | 58 | plotOneLevel(tagSim3Tanks,ID,X,Y,time); 59 | 60 | end 61 | 62 | end 63 | 64 | %========================================================================== 65 | 66 | function plotAllLevels(tagSim3Tanks,STATE_IDs,X,Y,time) 67 | 68 | FIG_ID = [tagSim3Tanks,'_fig_level_all']; 69 | 70 | try 71 | delete(findobj('Tag',FIG_ID)); 72 | catch 73 | end 74 | 75 | N0 = numel(time); 76 | N1 = round(N0/4); 77 | N2 = round(N1/2); 78 | N3 = round((N1+N2)/2); 79 | 80 | MarkIdx1 = 01:N1:N0; 81 | MarkIdx2 = N2:N1:N0; 82 | MarkIdx3 = N3:N1:N0; 83 | 84 | figure('Tag',FIG_ID,... 85 | 'Numbertitle','off',... 86 | 'Name',[tagSim3Tanks,': levels']); 87 | 88 | % Real value 89 | subplot(2,1,1); hold on; box on; 90 | p1 = plot(time,X.(STATE_IDs{1}),'-o','MarkerIndices',MarkIdx1); 91 | p2 = plot(time,X.(STATE_IDs{2}),'-s','MarkerIndices',MarkIdx2); 92 | p3 = plot(time,X.(STATE_IDs{3}),'-^','MarkerIndices',MarkIdx3); 93 | legend([p1 p2 p3],STATE_IDs{1},STATE_IDs{2},STATE_IDs{3},... 94 | 'Location','Northeast'); 95 | xlabel('Time'); 96 | ylabel('Real values'); 97 | xlim([time(1) time(N0)]); 98 | 99 | % Measured value 100 | subplot(2,1,2); hold on; box on; 101 | p1 = plot(time,Y.(STATE_IDs{1}),'-o','MarkerIndices',MarkIdx1); 102 | p2 = plot(time,Y.(STATE_IDs{2}),'-s','MarkerIndices',MarkIdx2); 103 | p3 = plot(time,Y.(STATE_IDs{3}),'-^','MarkerIndices',MarkIdx3); 104 | legend([p1 p2 p3],STATE_IDs{1},STATE_IDs{2},STATE_IDs{3},... 105 | 'Location','Northeast'); 106 | xlabel('Time'); 107 | ylabel('Measured values'); 108 | xlim([time(1) time(N0)]); 109 | 110 | end 111 | 112 | %========================================================================== 113 | 114 | function plotOneLevel(tagSim3Tanks,STATE_ID,X,Y,time) 115 | 116 | FIG_ID = [tagSim3Tanks,'_fig_level_',STATE_ID]; 117 | 118 | try 119 | delete(findobj('Tag',FIG_ID)); 120 | catch 121 | end 122 | 123 | realValue = X.(STATE_ID); 124 | measuredValue = Y.(STATE_ID); 125 | 126 | N0 = numel(time); 127 | N1 = round(N0/4); 128 | MarkIdx = 1:N1:N0; 129 | 130 | figure('Tag',FIG_ID,... 131 | 'Numbertitle','off',... 132 | 'Name',[tagSim3Tanks,': ',STATE_ID]); 133 | 134 | % Real value 135 | subplot(2,1,1); hold on; box on; 136 | plot(time,realValue,'b-o','MarkerIndices',MarkIdx); 137 | legend(STATE_ID,'Location','Northeast'); 138 | xlabel('Time'); 139 | ylabel('Real value'); 140 | xlim([time(1) time(N0)]); 141 | 142 | % Measured value 143 | subplot(2,1,2); hold on; box on; 144 | plot(time,measuredValue,'r-o','MarkerIndices',MarkIdx); 145 | legend(STATE_ID,'Location','Northeast'); 146 | xlabel('Time'); 147 | ylabel('Measured value'); 148 | xlim([time(1) time(N0)]); 149 | 150 | end -------------------------------------------------------------------------------- /src/@Sim3Tanks/plotValves.m: -------------------------------------------------------------------------------- 1 | function plotValves(varargin) 2 | % plotValves is a Sim3Tanks method. This method plots the behavior of the 3 | % valves over time. 4 | % 5 | % Example: 6 | % tts.plotValves(); 7 | % 8 | % To plot a specific valve behavior, use one of the following options as 9 | % an input argument: 'Kp1', 'Kp2', 'Kp3', 'Ka', 'Kb', 'K13', 'K23', 'K1', 10 | % 'K2', or 'K3'. 11 | % 12 | % If there is no input argument, all valves will be plotted on the same 13 | % figure. 14 | 15 | % https://github.com/e-controls/Sim3Tanks 16 | 17 | %========================================================================== 18 | 19 | if(nargin()>2) 20 | error(getMessage('ERR002')); 21 | else 22 | objSim3Tanks = varargin{1}; 23 | end 24 | 25 | time = objSim3Tanks.getInternalSimulationTime(); 26 | K = objSim3Tanks.getInternalValveSignals(time); 27 | 28 | if(isempty(K)) 29 | warning(getMessage('WARN008')); 30 | return; 31 | else 32 | time = K.Time; % casting to seconds 33 | end 34 | 35 | [opMode,VALVE_IDs] = checkOperationMode(objSim3Tanks); 36 | 37 | tagSim3Tanks = 'Sim3Tanks'; 38 | 39 | %========================================================================== 40 | 41 | if(nargin()==1) 42 | 43 | plotAllValves(tagSim3Tanks,VALVE_IDs,opMode,K,time); 44 | 45 | elseif(nargin()==2) 46 | 47 | option = find(strcmpi(varargin{2},VALVE_IDs)); 48 | 49 | if(~isempty(option)) 50 | ID = VALVE_IDs{option}; 51 | opMode = opMode(option); 52 | else 53 | error(getMessage('ERR003')); 54 | end 55 | 56 | plotOneValve(tagSim3Tanks,ID,K,opMode,time); 57 | 58 | end 59 | 60 | end 61 | 62 | %========================================================================== 63 | 64 | function plotAllValves(tagSim3Tanks,VALVE_IDs,opMode,K,time) 65 | 66 | FIG_ID = [tagSim3Tanks,'_fig_valve_all']; 67 | 68 | try 69 | delete(findobj('Tag',FIG_ID)); 70 | catch 71 | end 72 | 73 | normal = getRGBtriplet('normal'); 74 | fault = getRGBtriplet('fault'); 75 | 76 | N0 = numel(time); 77 | N1 = round(N0/4); 78 | MarkIdx = 01:N1:N0; 79 | 80 | figure('Tag',FIG_ID,... 81 | 'Numbertitle','off',... 82 | 'Name',[tagSim3Tanks,': valves']); 83 | 84 | for i = 1 : numel(opMode) 85 | 86 | subplot(4,3,i); hold on; box on; 87 | 88 | flag1 = 'Closed'; 89 | flag2 = 'Open'; 90 | if(opMode(i) ~= 0) 91 | flag1 = 'Open'; 92 | flag2 = 'Closed'; 93 | end 94 | 95 | dK = repmat(opMode(i),size(time)); 96 | 97 | plot(time,dK,'--','Color',normal); 98 | plot(time,1-dK,'--','Color',fault); 99 | 100 | p = plot(time,K.(VALVE_IDs{i}),'k-o','MarkerIndices',MarkIdx); 101 | legend(p,VALVE_IDs{i},'Location','Best'); 102 | 103 | xlim([time(1) time(N0)]); 104 | ylim([-0.1 1.1]); 105 | 106 | text(time(1),opMode(i),... 107 | flag1,... 108 | 'FontSize',8,... 109 | 'HorizontalAlignment','Left',... 110 | 'BackgroundColor',normal); 111 | text(time(1),1-opMode(i),... 112 | flag2,... 113 | 'FontSize',8,... 114 | 'HorizontalAlignment','Left',... 115 | 'BackgroundColor',fault); 116 | 117 | end 118 | 119 | end 120 | 121 | %========================================================================== 122 | 123 | function plotOneValve(tagSim3Tanks,VALVE_ID,K,opMode,time) 124 | 125 | FIG_ID = [tagSim3Tanks,'_fig_valve_',VALVE_ID]; 126 | 127 | try 128 | delete(findobj('Tag',FIG_ID)); 129 | catch 130 | end 131 | 132 | normal = getRGBtriplet('normal'); 133 | fault = getRGBtriplet('fault'); 134 | 135 | N0 = numel(time); 136 | N1 = round(N0/4); 137 | MarkIdx = 1:N1:N0; 138 | 139 | figure('Tag',FIG_ID,... 140 | 'Numbertitle','off',... 141 | 'Name',[tagSim3Tanks,': ',VALVE_ID]); hold on; box on; 142 | 143 | flag1 = 'Closed'; 144 | flag2 = 'Open'; 145 | if(opMode ~= 0) 146 | flag1 = 'Open'; 147 | flag2 = 'Closed'; 148 | end 149 | 150 | dK = repmat(opMode,size(time)); 151 | 152 | plot(time,dK,'--','Color',normal); 153 | plot(time,1-dK,'--','Color',fault); 154 | 155 | p = plot(time,K.(VALVE_ID),'k-o','MarkerIndices',MarkIdx); 156 | legend(p,VALVE_ID,'Location','Best'); 157 | 158 | xlabel('Time'); 159 | ylabel('Opening Rate'); 160 | 161 | xlim([time(1) time(N0)]); 162 | ylim([-0.1 1.1]); 163 | 164 | text(time(1),opMode,flag1,... 165 | 'FontSize',8,... 166 | 'HorizontalAlignment','Left',... 167 | 'BackgroundColor',normal); 168 | text(time(1),1-opMode,flag2,... 169 | 'FontSize',8,... 170 | 'HorizontalAlignment','Left',... 171 | 'BackgroundColor',fault); 172 | 173 | end -------------------------------------------------------------------------------- /src/@Sim3Tanks/plotFlows.m: -------------------------------------------------------------------------------- 1 | function plotFlows(varargin) 2 | % plotFlows is a Sim3Tanks method. This method plots the behavior of the 3 | % flows over time. 4 | % 5 | % Example: 6 | % tts.plotFlows(); 7 | % 8 | % To plot a specific flow, use one of the following options as an input 9 | % argument: 10 | % 'Q1in' : to plot the inflow of Tank 1 11 | % 'Q2in' : to plot the inflow of Tank 2 12 | % 'Q3in' : to plot the inflow of Tank 3 13 | % 'Qa' : to plot the transmission flow from Tank 1 to 3 14 | % 'Qb' : to plot the transmission flow from Tank 2 to 3 15 | % 'Q13' : to plot the connection flow from Tank 1 to 3 16 | % 'Q23' : to plot the connection flow from Tank 2 to 3 17 | % 'Q1' : to plot the outflow of Tank 1 18 | % 'Q2' : to plot the outflow of Tank 2 19 | % 'Q3' : to plot the outflow of Tank 3 20 | % 21 | % If there is no input argument, all flows will be plotted on the same 22 | % figure. 23 | 24 | % https://github.com/e-controls/Sim3Tanks 25 | 26 | %========================================================================== 27 | 28 | if(nargin()>2) 29 | error(getMessage('ERR002')); 30 | else 31 | objSim3Tanks = varargin{1}; 32 | end 33 | 34 | time = objSim3Tanks.getInternalSimulationTime(); 35 | Q = objSim3Tanks.getInternalFlowVariables(time); 36 | 37 | if(isempty(Q)) 38 | warning(getMessage('WARN008')); 39 | return; 40 | else % If I have a non-empty Q, then I also have a non-empty Y. 41 | Y = objSim3Tanks.getInternalSensorMeasurements(time); 42 | time = Y.Time; % casting to seconds 43 | end 44 | 45 | FLOW_IDs = Sim3Tanks.LIST_OF_FLOWS; 46 | 47 | tagSim3Tanks = 'Sim3Tanks'; 48 | 49 | %========================================================================== 50 | 51 | if(nargin()==1) 52 | 53 | plotAllFlows(tagSim3Tanks,FLOW_IDs,Q,Y,time); 54 | 55 | elseif(nargin()==2) 56 | 57 | option = find(strcmpi(varargin{2},FLOW_IDs)); 58 | 59 | if(~isempty(option)) 60 | ID = FLOW_IDs{option}; 61 | else 62 | error(getMessage('ERR003')); 63 | end 64 | 65 | plotOneFlow(tagSim3Tanks,ID,Q,Y,time); 66 | 67 | end 68 | 69 | end 70 | 71 | %========================================================================== 72 | 73 | function plotAllFlows(tagSim3Tanks,FLOW_IDs,Q,Y,time) 74 | 75 | FIG_ID = [tagSim3Tanks,'_fig_flow_all']; 76 | 77 | try 78 | delete(findobj('Tag',FIG_ID)); 79 | catch 80 | end 81 | 82 | N = numel(time); 83 | 84 | figure('Tag',FIG_ID,... 85 | 'Numbertitle','off',... 86 | 'Name',[tagSim3Tanks,': flows']); 87 | 88 | % Real value 89 | subplot(2,1,1); hold on; box on; 90 | p1 = plot(time,Q.(FLOW_IDs{1})); 91 | p2 = plot(time,Q.(FLOW_IDs{2})); 92 | p3 = plot(time,Q.(FLOW_IDs{3})); 93 | p4 = plot(time,Q.(FLOW_IDs{4})); 94 | p5 = plot(time,Q.(FLOW_IDs{5})); 95 | p6 = plot(time,Q.(FLOW_IDs{6})); 96 | p7 = plot(time,Q.(FLOW_IDs{7})); 97 | p8 = plot(time,Q.(FLOW_IDs{8})); 98 | p9 = plot(time,Q.(FLOW_IDs{9})); 99 | p10 = plot(time,Q.(FLOW_IDs{10})); 100 | legend([p1 p2 p3 p4 p5 p6 p7 p8 p9 p10],... 101 | FLOW_IDs{1},FLOW_IDs{2},FLOW_IDs{3},FLOW_IDs{4},FLOW_IDs{5},... 102 | FLOW_IDs{6},FLOW_IDs{7},FLOW_IDs{8},FLOW_IDs{9},FLOW_IDs{10},... 103 | 'Location','NorthEastOutside'); 104 | xlabel('Time'); 105 | ylabel('Real values'); 106 | xlim([time(1) time(N)]); 107 | 108 | % Measured value 109 | subplot(2,1,2); hold on; box on; 110 | plot(time,Y.(FLOW_IDs{1})); 111 | plot(time,Y.(FLOW_IDs{2})); 112 | plot(time,Y.(FLOW_IDs{3})); 113 | plot(time,Y.(FLOW_IDs{4})); 114 | plot(time,Y.(FLOW_IDs{5})); 115 | plot(time,Y.(FLOW_IDs{6})); 116 | plot(time,Y.(FLOW_IDs{7})); 117 | plot(time,Y.(FLOW_IDs{8})); 118 | plot(time,Y.(FLOW_IDs{9})); 119 | plot(time,Y.(FLOW_IDs{10})); 120 | xlabel('Time'); 121 | ylabel('Measured values'); 122 | xlim([time(1) time(N)]); 123 | 124 | end 125 | 126 | %========================================================================== 127 | 128 | function plotOneFlow(tagSim3Tanks,FLOW_ID,Q,Y,time) 129 | 130 | FIG_ID = [tagSim3Tanks,'_fig_flow_',FLOW_ID]; 131 | 132 | try 133 | delete(findobj('Tag',FIG_ID)); 134 | catch 135 | end 136 | 137 | realValue = Q.(FLOW_ID); 138 | measuredValue = Y.(FLOW_ID); 139 | 140 | N0 = numel(time); 141 | N1 = round(N0/4); 142 | MarkIdx = 1:N1:N0; 143 | 144 | figure('Tag',FIG_ID,... 145 | 'Numbertitle','off',... 146 | 'Name',[tagSim3Tanks,': ',FLOW_ID]); 147 | 148 | % Real value 149 | subplot(2,1,1); hold on; box on; 150 | plot(time,realValue,'b-o','MarkerIndices',MarkIdx); 151 | legend(FLOW_ID,'Location','Northeast'); 152 | xlabel('Time'); 153 | ylabel('Real value'); 154 | xlim([time(1) time(N0)]); 155 | 156 | % Measured value 157 | subplot(2,1,2); hold on; box on; 158 | plot(time,measuredValue,'r-o','MarkerIndices',MarkIdx); 159 | legend(FLOW_ID,'Location','Northeast'); 160 | xlabel('Time'); 161 | ylabel('Measured value'); 162 | xlim([time(1) time(N0)]); 163 | 164 | end -------------------------------------------------------------------------------- /Sim3Tanks_test.m: -------------------------------------------------------------------------------- 1 | clc; clear; close all; 2 | 3 | % Add internal dependency paths 4 | addpath(genpath('src')); 5 | 6 | % Set time vector. 7 | tstart = 0; % Start time 8 | tstop = 300; % Stop time 9 | Ts = 0.1; % Sample time 10 | time = tstart : Ts : tstop; 11 | 12 | N = numel(time); % Number of samples 13 | 14 | % Create a Sim3Tanks object. 15 | tts = createSim3Tanks(); 16 | 17 | % Set the object to the default model. 18 | tts.setDefaultModel(); 19 | 20 | % Or, to change any Model parameter, just access its respective field and 21 | % change its value. 22 | tts.Model.PhysicalParam.TankRadius = 5; 23 | tts.Model.PhysicalParam.TankHeight = 50; 24 | tts.Model.PhysicalParam.PipeRadius = 0.6; 25 | tts.Model.PhysicalParam.TransPipeHeight = 30; 26 | tts.Model.PhysicalParam.CorrectionTerm = 1; 27 | tts.Model.PhysicalParam.GravityConstant = 981; 28 | tts.Model.PhysicalParam.PumpMinFlow = 0; 29 | tts.Model.PhysicalParam.PumpMaxFlow = 120; 30 | tts.Model.InitialCondition = [40 25 20]; 31 | 32 | % To enable control of the valves Kp1 and Kp2 set this flags to true. 33 | tts.Model.ValveSettings.Kp1.EnableControl = false; 34 | tts.Model.ValveSettings.Kp2.EnableControl = false; 35 | 36 | % A disturbance in tank 3, but in the default model the Kp3.OperationMode 37 | % field is 'Closed' and the Kp3.EnableControl field is false, therefore the 38 | % state of the valve Kp3 is maintained closed and, consequently, the value 39 | % of the flow Qp3 does not have effect on the system. To change this the 40 | % Kp3.EnableControl field must be set to true. 41 | tts.Model.ValveSettings.Kp3.OperationMode = 'Closed'; 42 | tts.Model.ValveSettings.Kp3.EnableControl = false; 43 | 44 | % Generate a disturbance signal. 45 | v = floor(N/3):2*floor(N/3); 46 | d = zeros(size(time)); 47 | d(v) = 0.5+random('norm',0,0.1,size(v)); 48 | 49 | % Generate the pump flow. 50 | Qp1 = 80*ones(size(time)); 51 | Qp2 = 80*ones(size(time)); 52 | Qp3 = 80*ones(size(time)); 53 | 54 | % Fault signal generation. Here only fault f1 is enabled, the other faults 55 | % have their EnabledSignal field set to false (by default), therefore they 56 | % do not affect the system. 57 | tts.Model.FaultSettings.f1.EnableSignal = true; 58 | f1 = 1./(1+exp(-(1/time(floor(N/4)))*(-2*floor(N/4):2*floor(N/4)))); % sigmoid fault signal 59 | 60 | tts.Model.FaultSettings.f2.EnableSignal = false; 61 | f2 = 1./(1+exp(-(1/time(floor(N/4)))*(-3*floor(N/4):1*floor(N/4)))); % sigmoid fault signal 62 | 63 | for k = 2 : N % k=1 conrresponds to initial condition 64 | 65 | fprintf([getMessage('tag'),'Running simulation (%d/%d)\n'],k,N); 66 | 67 | % Simulating a control signal for the valves Kp1 and Kp2. 68 | u = rand(2,1); 69 | 70 | % There is no effect because the Kp1 and Kp2 EnableControl field is 71 | % false, to change this it must be set to true outside of this loop. 72 | tts.Model.ValveSettings.Kp1.OpeningRate = u(1); 73 | tts.Model.ValveSettings.Kp2.OpeningRate = u(2); 74 | 75 | % A disturbance in tank 3 over time. 76 | tts.Model.ValveSettings.Kp3.OpeningRate = d(k); 77 | 78 | % Taking the fault magnitude over time. 79 | tts.Model.FaultSettings.f1.Magnitude = f1(k); 80 | 81 | % There is no effect because the f2.EnableSignal field is false, to 82 | % change this it must be set to true outside of this loop. 83 | tts.Model.FaultSettings.f2.Magnitude = f2(k); 84 | 85 | % Process noise. 86 | tts.Model.ProcessNoise.EnableSignal = false; % It is not enable 87 | tts.Model.ProcessNoise.Magnitude = random('norm',0,0.1,[1 3]); 88 | 89 | % Measurement noise. 90 | tts.Model.MeasurementNoise.EnableSignal = true; % It is enable 91 | yx = random('norm',0,0.2,[1 03]); % Level sensors 92 | yq = random('norm',0,0.6,[1 10]); % Flow sensors 93 | tts.Model.MeasurementNoise.Magnitude = [yx,yq]; 94 | 95 | % Simulating the system. 96 | % If the pair ('Qp1',VALUE1) is omitted, then the declared value in the 97 | % field tts.Model.PhysicalParam.PumpMaxFlow is used as default. The 98 | % same is valid for the pairs ('Qp2',VALUE2) and ('Qp3',VALUE3). For 99 | % the pair ('Tspan',VALUE), the default value is 0.1. 100 | [y,x,q] = tts.simulateModel('Qp1',Qp1(k),'Qp2',Qp2(k),'Qp3',Qp3(k),'Tspan',Ts,'allSteps',true); 101 | 102 | end 103 | 104 | fprintf([getMessage('tag'),'The simulation is done!\n']); 105 | 106 | % Preparing variables 107 | 108 | X = tts.getStateVariables(); 109 | Q = tts.getFlowVariables(); 110 | Y = tts.getSensorMeasurements(); 111 | K = tts.getValveSignals(); 112 | F = tts.getFaultMagnitudes(); 113 | O = tts.getFaultOffsets(); 114 | 115 | tts.displayModel(); 116 | 117 | %% Plots 118 | 119 | fprintf([getMessage('tag'),'Plotting Graphs...\n']); 120 | 121 | tts.plotLevels(); 122 | %tts.plotLevels('h1'); 123 | 124 | tts.plotFlows(); 125 | %tts.plotFlows('Q3'); 126 | 127 | tts.plotValves(); 128 | %tts.plotValves('Kp1'); 129 | 130 | tts.plotFaultMagnitudes(); 131 | %tts.plotFaultMagnitudes('f1'); 132 | 133 | tts.plotFaultOffsets(); 134 | %tts.plotFaultOffsets('f11'); 135 | -------------------------------------------------------------------------------- /src/getMessage.m: -------------------------------------------------------------------------------- 1 | function [varargout] = getMessage(varargin) 2 | % getMessage is a Sim3Tanks function. This function returns the message 3 | % associated with a code. 4 | 5 | % https://github.com/e-controls/Sim3Tanks 6 | 7 | %========================================================================== 8 | 9 | if(nargin()<1) 10 | error(getMessage('ERR001')); 11 | elseif(nargin()>1) 12 | error(getMessage('ERR002')); 13 | else 14 | messageCode = upper(varargin{1}); 15 | end 16 | 17 | %========================================================================== 18 | 19 | LIST_OF_FIELDS = Sim3Tanks.LIST_OF_FIELDS; 20 | LIST_OF_PARAM = Sim3Tanks.LIST_OF_PARAM; 21 | 22 | tagSim3Tanks = '#Sim3Tanks::'; 23 | 24 | if ~ischar(messageCode) 25 | error([tagSim3Tanks,'ERR999:','The input argument must be a char type, not a %s.'],class(messageCode)); 26 | end 27 | 28 | switch messageCode 29 | case 'TAG' 30 | msg = []; 31 | messageCode = []; 32 | 33 | case 'ERR000' 34 | msg = 'Unknown error.'; 35 | 36 | case 'ERR001' 37 | msg = 'Not enough input arguments.'; 38 | 39 | case 'ERR002' 40 | msg = 'Too many input arguments.'; 41 | 42 | case 'ERR003' 43 | msg = 'Invalid input parameter.'; 44 | 45 | case 'ERR004' 46 | msg = 'The input argument must be a Sim3Tanks object.'; 47 | 48 | case 'ERR005' 49 | msg = 'The input argument must be a row vector.'; 50 | 51 | case 'ERR006' 52 | msg = 'The dimensions are not consistent.'; 53 | 54 | case 'ERR007' 55 | msg = 'The state variabels must be finite.'; 56 | 57 | case 'ERR008' % PhysicalParam 58 | msg = ['The subfields of <',LIST_OF_FIELDS{1},'> must have real and finite values.']; 59 | 60 | case 'ERR009' 61 | msg = 'The field must be set to ''Open'' or ''Closed''.'; 62 | 63 | case 'ERR010' 64 | msg = 'The field must be set to a logical value (true or false).'; 65 | 66 | case 'ERR011' 67 | msg = 'The field must be set to a logical value (true or false).'; 68 | 69 | case 'ERR012' % TankRadius 70 | msg = ['The field <',LIST_OF_PARAM{1},'> must be greater than 0.']; 71 | 72 | case 'ERR013' % TankHeight 73 | msg = ['The field <',LIST_OF_PARAM{2},'> must be greater than 0.']; 74 | 75 | case 'ERR014' % PipeRadius % TankRadius 76 | msg = ['The field <',LIST_OF_PARAM{3},'> must be greater than 0 and less than <',LIST_OF_PARAM{1},'>.']; 77 | 78 | case 'ERR015' % TransPipeHeight % TankHeight 79 | msg = ['The field <',LIST_OF_PARAM{4},'> must be greater than 0 and less than <',LIST_OF_PARAM{2},'>.']; 80 | 81 | case 'ERR016' % CorrectionTerm 82 | msg = ['The field <',LIST_OF_PARAM{5},'> must be greater than 0.']; 83 | 84 | case 'ERR017' % GravityConstant 85 | msg = ['The field <',LIST_OF_PARAM{6},'> must be greater than 0.']; 86 | 87 | case 'ERR018' % PumpMinFlow 88 | msg = ['The field <',LIST_OF_PARAM{7},'> must be greater than or equal to 0.']; 89 | 90 | case 'ERR019' % PumpMaxFlow % PumpMinFlow 91 | msg = ['The field <',LIST_OF_PARAM{8},'> must be greater than or equal to <',LIST_OF_PARAM{7},'>.']; 92 | 93 | case 'ERR020' 94 | msg = 'Invalid number of input arguments (must be even).'; 95 | 96 | case 'ERR021' 97 | msg = 'Invalid number of input arguments (must be odd).'; 98 | 99 | case 'ERR022' 100 | msg = 'The sensor fault offset value must be finite.'; 101 | 102 | case 'ERR023' 103 | msg = 'The input argument must be a row or a column vector of numeric type.'; 104 | 105 | case 'ERR024' 106 | msg = 'The initial condition must be a three-position row vector.'; 107 | 108 | % 109 | % 110 | % 111 | 112 | case 'WARN001' 113 | msg = 'The Magnitude of an enabled fault is set to empty, so zero is assumed as the default value.'; 114 | 115 | case 'WARN002' 116 | msg = 'The Magnitude of an enabled fault is out of bounds [0,1], so its value is saturated.'; 117 | 118 | case 'WARN003' 119 | msg = 'The OpeningRate of an enabled valve is set to empty, so OperationMode is assumed as the default value.'; 120 | 121 | case 'WARN004' 122 | msg = 'The OpeningRate of an enabled valve is out of bounds [0,1], so its value is saturated.'; 123 | 124 | case 'WARN005' 125 | msg = 'The ProcessNoise Magnitude is set to empty, so a row vector of zeros is assumed as the default value.'; 126 | 127 | case 'WARN006' 128 | msg = 'The MeasurmentNoise Magnitude is set to empty, so a row vector of zeros is assumed as the default value.'; 129 | 130 | case 'WARN007' 131 | msg = 'The Offset of a sensor fault is set to empty, so zero is assumed as the default value.'; 132 | 133 | case 'WARN008' 134 | msg = 'There is no value to plot (the variable is empty).'; 135 | 136 | otherwise 137 | msg = ['Code ',messageCode,' not found!']; 138 | messageCode = []; 139 | 140 | end 141 | 142 | varargout{1} = [tagSim3Tanks,messageCode,': ',msg]; 143 | 144 | end -------------------------------------------------------------------------------- /src/@Sim3Tanks/simulateModel.m: -------------------------------------------------------------------------------- 1 | function [y,x,q] = simulateModel(varargin) 2 | % simulateModel is a Sim3Tanks method. This method simulates the dynamic 3 | % behavior of the three-tank system defined by the user. 4 | % 5 | % The input parameter must follow the pair ('NAME',VALUE), where NAME must 6 | % be Qp1, Qp2, Qp3, or Tspan, and VALUE must be a numeric type. 7 | % 8 | % If the pair ('Qp1',VALUE1) is omitted, then the declared value in the 9 | % field objSim3Tanks.Model.PhysicalParam.PumpMaxFlow is used as default. 10 | % The same is valid for the pairs ('Qp2',VALUE2) and ('Qp3',VALUE3). For 11 | % the pair ('Tspan',VALUE), the default value is 0.1. 12 | % 13 | % NOTE: It is highly recommended to use simulation time increment as Tspan. 14 | % 15 | % The pair ('allSteps',true) enables the return of all intermediate steps 16 | % of each simulation call. By default, its value is false, and only the 17 | % final step is returned. 18 | % 19 | % The output arguments are the following vectors: 20 | % y = [h1,h2,h3,Q1in,Q2in,Q3in,Qa,Qb,Q13,Q23,Q1,Q2,Q3] : measurement vector 21 | % x = [h1,h2,h3] : state vector 22 | % q = [Q1in,Q2in,Q3in,Qa,Qb,Q13,Q23,Q1,Q2,Q3] : flow vector 23 | % 24 | % Examples of how to call the method: 25 | % 26 | % tts = createSim3Tanks(); % create an object. 27 | % tts.setDefaultModel(); % configure the object to the default model. 28 | % 29 | % [y,x,q] = tts.simulateModel(); % default values are used. 30 | % 31 | % [y,x,q] = tts.simulateModel('Qp1',100); % only the value of Qp1 is 32 | % % updated to 100. 33 | % 34 | % [y,x,q] = tts.simulateModel('Qp2',110,'Tspan',0.2); % only the values 35 | % % of Qp2 and Tspan are updated to 110 and 0.2, respectively. 36 | % 37 | % [y,x,q] = tts.simulateModel('allSteps',true); % the variables y, x, 38 | % % and q will have more than one line. 39 | % 40 | % See also createSim3Tanks, setDefaultModel, getSensorMeasurements, 41 | % getStateVariables, getFlowVariables, getFaultSignals, 42 | % getValveSignals. 43 | 44 | % https://github.com/e-controls/Sim3Tanks 45 | 46 | %========================================================================== 47 | 48 | if(mod(nargin(),2) == 0) 49 | error(getMessage('ERR020')); 50 | else 51 | objSim3Tanks = varargin{1}; 52 | end 53 | 54 | %========================================================================== 55 | 56 | [Param,ID] = checkPhysicalParam(objSim3Tanks); 57 | 58 | Rtank = Param.(ID{1}); 59 | Hmax = Param.(ID{2}); 60 | Rpipe = Param.(ID{3}); 61 | h0 = Param.(ID{4}); 62 | mu = Param.(ID{5}); 63 | g = Param.(ID{6}); 64 | Qmin = Param.(ID{7}); 65 | Qmax = Param.(ID{8}); 66 | 67 | Sc = pi()*(Rtank^2); % Cross-sectional area of the tanks (cm^2) 68 | S = pi()*(Rpipe^2); % Cross-sectional area of the pipes (cm^2) 69 | Beta = mu*S*sqrt(2*g); % Constant value 70 | 71 | %========================================================================== 72 | 73 | % Default options 74 | options.QP1 = Qmax; 75 | options.QP2 = Qmax; 76 | options.QP3 = Qmax; 77 | options.TSPAN = 0.1; 78 | options.ALLSTEPS = false; 79 | 80 | % Check input options 81 | for i = 2 : 2 : nargin() 82 | name = upper(varargin{i}); 83 | if(isfield(options,name)) 84 | options.(name) = varargin{i+1}; 85 | else 86 | error(getMessage('ERR003')); 87 | end 88 | end 89 | 90 | % New options 91 | Qp1 = options.QP1; 92 | Qp2 = options.QP2; 93 | Qp3 = options.QP3; 94 | Tspan = options.TSPAN; 95 | allSteps = options.ALLSTEPS; 96 | 97 | %========================================================================== 98 | 99 | opMode = checkOperationMode(objSim3Tanks); 100 | [valveID,openingRate] = checkEnabledValves(objSim3Tanks); 101 | [faultID,faultMag,offset] = checkEnabledFaults(objSim3Tanks); 102 | 103 | K = zeros(size(opMode)); 104 | 105 | % ID = Sim3Tanks.LIST_OF_VALVES; 106 | for i = 1 : numel(opMode) 107 | OP = opMode(i); 108 | EC = isempty(valveID{i}); 109 | EF = isempty(faultID{i}); 110 | 111 | % State machine to select the valve behavior 112 | if(~OP && EC && EF || OP && EC && EF) 113 | % fprintf('STATE 1 : %s = OP\n',ID{i}); 114 | K(i) = opMode(i); 115 | 116 | elseif(~OP && EC && ~EF) 117 | % fprintf('STATE 2 : %s = f\n',ID{i}); 118 | K(i) = faultMag(i); 119 | 120 | elseif(~OP && ~EC && EF || OP && ~EC && EF) 121 | % fprintf('STATE 3 : %s = K\n',ID{i}); 122 | K(i) = openingRate(i); 123 | 124 | elseif(~OP && ~EC && ~EF || OP && ~EC && ~EF) 125 | % fprintf('STATE 4 : %s = K*(1-f)\n',ID{i}); 126 | K(i) = openingRate(i)*(1-faultMag(i)); 127 | 128 | elseif(OP && EC && ~EF) 129 | % fprintf('STATE 5 : %s = 1-f\n',ID{i}); 130 | K(i) = 1-faultMag(i); 131 | 132 | else % Invalid state 133 | error(getMessage('ERR000')); 134 | end 135 | end 136 | 137 | %========================================================================== 138 | 139 | Nx = numel(Sim3Tanks.LIST_OF_STATES); 140 | Nq = numel(Sim3Tanks.LIST_OF_FLOWS); 141 | 142 | x = objSim3Tanks.getInternalStateVariables(); 143 | 144 | if(isempty(x)) 145 | 146 | x = objSim3Tanks.Model.InitialCondition; 147 | 148 | if(isempty(x)||~isrow(x)||numel(x)~=numel(Sim3Tanks.LIST_OF_STATES)) 149 | error(getMessage('ERR024')); 150 | end 151 | 152 | q = [... 153 | K(1)*satSignal(Qp1,[Qmin Qmax]),... 154 | K(2)*satSignal(Qp2,[Qmin Qmax]),... 155 | K(3)*satSignal(Qp3,[Qmin Qmax]),... 156 | sysFlowRates(x,K,h0,Beta)]; 157 | 158 | [~,mNoise] = checkEnabledNoises(objSim3Tanks); 159 | 160 | y = sysMeasurements(x,q,faultMag,offset,mNoise); 161 | 162 | objSim3Tanks.setInternalStateVariables(x); 163 | objSim3Tanks.setInternalFlowVariables(q); 164 | objSim3Tanks.setInternalSensorMeasurements(y); 165 | objSim3Tanks.setInternalValveSignals(opMode'); 166 | objSim3Tanks.setInternalFaultMagnitudes(faultMag'); 167 | objSim3Tanks.setInternalFaultOffsets(offset(11:end)'); 168 | objSim3Tanks.resetInternalSimulationTime(); 169 | 170 | else 171 | x = x(end,:); 172 | end 173 | 174 | %========================================================================== 175 | 176 | i = 1; 177 | 178 | while(1) 179 | 180 | % Flow rate vector 181 | Qx = [... 182 | K(1)*satSignal(Qp1,[Qmin Qmax]),... 183 | K(2)*satSignal(Qp2,[Qmin Qmax]),... 184 | K(3)*satSignal(Qp3,[Qmin Qmax]),... 185 | sysFlowRates(x(i,:),K,h0,Beta)]; 186 | 187 | [pNoise,mNoise] = checkEnabledNoises(objSim3Tanks); 188 | 189 | if(i==1) % Levels --> x = [h1,h2,h3] 190 | 191 | % Solver Configuration 192 | options = odeset('MaxStep',Tspan,'RelTol',1e-6); 193 | [t,x] = ode45(@(t,x)sysDynamicModel(Sc,Qx,pNoise),[0 Tspan],x,options); 194 | 195 | if(isfinite(x)) 196 | if(allSteps) 197 | x = satSignal(x(2:end,:),[0 Hmax]); 198 | t = t(2:end,:); 199 | else 200 | x = satSignal(x(end,:),[0 Hmax]); 201 | t = t(end); 202 | end 203 | else 204 | error(getMessage('ERR007')); 205 | end 206 | 207 | numberOfStates = size(x,1); 208 | y = zeros(numberOfStates,Nx+Nq); 209 | q = zeros(numberOfStates,Nq); 210 | 211 | t0 = objSim3Tanks.getInternalSimulationTime(end); 212 | 213 | end 214 | 215 | % Flows --> q = [Q1in,Q2in,Q3in,Qa,Qb,Q13,Q23,Q1,Q2,Q3] 216 | q(i,:) = Qx; 217 | 218 | % Measurements --> y = [h1,h2,h3,Q1in,Q2in,Q3in,Qa,Qb,Q13,Q23,Q1,Q2,Q3] 219 | y(i,:) = sysMeasurements(x(i,:),q(i,:),faultMag,offset,mNoise); 220 | 221 | objSim3Tanks.pushInternalStateVariables(x(i,:)); 222 | objSim3Tanks.pushInternalFlowVariables(q(i,:)); 223 | objSim3Tanks.pushInternalSensorMeasurements(y(i,:)); 224 | objSim3Tanks.pushInternalValveSignals(K'); 225 | objSim3Tanks.pushInternalFaultMagnitudes(faultMag'); 226 | objSim3Tanks.pushInternalFaultOffsets(offset(11:end)'); 227 | objSim3Tanks.incrementInternalSimulationTime(t0+t(i)); 228 | 229 | if(i>=numberOfStates) 230 | break; 231 | else 232 | i = i + 1; 233 | end 234 | 235 | end -------------------------------------------------------------------------------- /tests/pid_flow_control/pidFlowControl_test.m: -------------------------------------------------------------------------------- 1 | % This script simulates Sim3Tanks in a closed-loop with a digital PID 2 | % controller. The controlled variable is the outflow Q3, and the control 3 | % signals are the valves Kp1 and Kp2. 4 | 5 | clc; clear; close all; 6 | 7 | % Add internal dependency paths 8 | addpath(genpath('..\..\src')); 9 | addpath(genpath('..\..\addons')); 10 | 11 | % Set time vector 12 | tstart = 0; % Start time 13 | tstop = 120; % Stop time 14 | Ts = 0.1; % Sample time 15 | time = tstart : Ts : tstop; 16 | 17 | N = numel(time); % Number of samples 18 | 19 | % Create a Sim3Tanks object 20 | tts = createSim3Tanks(); 21 | 22 | %% System parameters 23 | tts.Model.PhysicalParam.TankRadius = 5; 24 | tts.Model.PhysicalParam.TankHeight = 50; 25 | tts.Model.PhysicalParam.PipeRadius = 0.6; 26 | tts.Model.PhysicalParam.TransPipeHeight = 30; 27 | tts.Model.PhysicalParam.CorrectionTerm = 1; 28 | tts.Model.PhysicalParam.GravityConstant = 981; 29 | tts.Model.PhysicalParam.PumpMinFlow = 0; 30 | tts.Model.PhysicalParam.PumpMaxFlow = 120; 31 | 32 | %% Valve settings 33 | tts.Model.ValveSettings.Kp1.OperationMode = 'Open'; 34 | tts.Model.ValveSettings.Kp1.EnableControl = true; 35 | tts.Model.ValveSettings.Kp2.OperationMode = 'Open'; 36 | tts.Model.ValveSettings.Kp2.EnableControl = true; 37 | tts.Model.ValveSettings.K13.OperationMode = 'Open'; 38 | tts.Model.ValveSettings.K13.EnableControl = false; 39 | tts.Model.ValveSettings.K23.OperationMode = 'Open'; 40 | tts.Model.ValveSettings.K23.EnableControl = false; 41 | tts.Model.ValveSettings.K3.OperationMode = 'Open'; 42 | tts.Model.ValveSettings.K3.EnableControl = false; 43 | 44 | %% Fault settings 45 | tts.Model.FaultSettings.f1.EnableSignal = true; % Loss Of Effectiveness in Kp1 46 | tts.Model.FaultSettings.f2.EnableSignal = true; % Loss Of Effectiveness in Kp1 47 | tts.Model.FaultSettings.f6.EnableSignal = true; % Clogging Q13 48 | tts.Model.FaultSettings.f9.EnableSignal = true; % Leakage Q2 49 | tts.Model.FaultSettings.f10.EnableSignal = true; % Clogging Q3 50 | tts.Model.FaultSettings.f11.EnableSignal = true; % Sensor h1 51 | tts.Model.FaultSettings.f12.EnableSignal = true; % Sensor h2 52 | tts.Model.FaultSettings.f19.EnableSignal = true; % Sensor Q13 53 | tts.Model.FaultSettings.f20.EnableSignal = true; % Sensor Q23 54 | tts.Model.FaultSettings.f23.EnableSignal = true; % Sensor Q3 55 | 56 | %% Setting simulation scenario 57 | 58 | % Setpoint for flow Q3 (cm^3/s) 59 | sp = stepSignal([140 180 80],[0 20 60],time); 60 | 61 | % Fault signals 62 | f1 = zeros(size(time)); 63 | f2 = zeros(size(time)); 64 | f6 = zeros(size(time)); 65 | f9 = zeros(size(time)); 66 | f10 = zeros(size(time)); 67 | f11 = zeros(size(time)); 68 | f12 = zeros(size(time)); 69 | f19 = zeros(size(time)); 70 | f20 = zeros(size(time)); 71 | f23 = zeros(size(time)); 72 | 73 | % Offset for sensor faults 74 | tts.Model.FaultSettings.f11.Offset = 0; 75 | tts.Model.FaultSettings.f12.Offset = 0; 76 | tts.Model.FaultSettings.f19.Offset = 0; 77 | tts.Model.FaultSettings.f20.Offset = 0; 78 | tts.Model.FaultSettings.f23.Offset = 0; 79 | 80 | % Initial condition 81 | tts.Model.InitialCondition = [10 10 8]; 82 | uk(:,1) = [0;0]; % First control signal 83 | 84 | %% System simulation 85 | 86 | fprintf([getMessage('tag'),'Starting simulation...\n']); 87 | 88 | for k = 2 : N % k=1 conrresponds to initial condition 89 | 90 | fprintf([getMessage('tag'),'Simulating NONLINEAR plant (%d/%d)\n'],k,N); 91 | 92 | % +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 93 | % #BEGIN: Continuous Nonlinear Plant ++++++++++++++++++++++++++++++++++ 94 | % +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 95 | 96 | % D/A converter: uk->u(t) 97 | u = uk(:,k-1); 98 | 99 | % Control signal for the valves Kp1 and Kp2. 100 | tts.Model.ValveSettings.Kp1.OpeningRate = u(1); 101 | tts.Model.ValveSettings.Kp2.OpeningRate = u(2); 102 | 103 | % Taking the fault magnitude over time. 104 | tts.Model.FaultSettings.f1.Magnitude = f1(k); 105 | tts.Model.FaultSettings.f2.Magnitude = f2(k); 106 | tts.Model.FaultSettings.f6.Magnitude = f6(k); 107 | tts.Model.FaultSettings.f9.Magnitude = f9(k); 108 | tts.Model.FaultSettings.f10.Magnitude = f10(k); 109 | tts.Model.FaultSettings.f11.Magnitude = f11(k); 110 | tts.Model.FaultSettings.f12.Magnitude = f12(k); 111 | tts.Model.FaultSettings.f19.Magnitude = f19(k); 112 | tts.Model.FaultSettings.f20.Magnitude = f20(k); 113 | tts.Model.FaultSettings.f23.Magnitude = f23(k); 114 | 115 | % Process noise. 116 | tts.Model.ProcessNoise.EnableSignal = false; 117 | tts.Model.ProcessNoise.Magnitude = random('norm',0,0.1,[1 3]); 118 | 119 | % Measurement noise. 120 | tts.Model.MeasurementNoise.EnableSignal = false; 121 | yx = random('norm',0,0.2,[1 03]); % Level Sensors 122 | yq = random('norm',0,0.6,[1 10]); % Flow Sensors 123 | tts.Model.MeasurementNoise.Magnitude = [yx,yq]; 124 | 125 | % Nonlinear dynamic model. 126 | [yt,xt,qt] = tts.simulateModel('Tspan',Ts); 127 | 128 | % Measured output vector for this case: y = [h1, h2, Q13, Q23, Q3]' 129 | y = [yt(1),yt(2),yt(9),yt(10),yt(13)]'; 130 | 131 | % A/D converter: y(t)->yk 132 | yk(:,k) = y; %#ok<*SAGROW> 133 | 134 | % --------------------------------------------------------------------- 135 | % #END: Continuous Nonlinear Plant ------------------------------------ 136 | % --------------------------------------------------------------------- 137 | 138 | 139 | % +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 140 | % #BEGIN: Digital PID control for flow Q3 +++++++++++++++++++++++++++++ 141 | % +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 142 | if(k>2) 143 | ek_past = sp(:,k-2:k) - yk(end,k-2:k); % [e(k-2) e(k-1) e(k)] 144 | uk_past = uk(:,k-2:k-1); % [u(k-2) u(k-1)] 145 | uk(:,k) = pidFlowControl(ek_past,uk_past,Ts); 146 | else 147 | uk(:,k) = zeros(2,1); 148 | end 149 | % --------------------------------------------------------------------- 150 | % #END: Digital PID control for flow Q3 ------------------------------- 151 | % --------------------------------------------------------------------- 152 | 153 | end 154 | 155 | fprintf([getMessage('tag'),'The simulation is done!\n']); 156 | 157 | % Preparing variables 158 | 159 | X = tts.getStateVariables(); 160 | Q = tts.getFlowVariables(); 161 | Y = tts.getSensorMeasurements(); 162 | K = tts.getValveSignals(); 163 | F = tts.getFaultMagnitudes(); 164 | O = tts.getFaultOffsets(); 165 | 166 | %% Plots 167 | 168 | fprintf([getMessage('tag'),'Plotting Graphs...\n']); 169 | 170 | MarkIdx = 1:round(N/20):N; 171 | 172 | % Valve signals (Actuators) 173 | figure(); 174 | subplot(2,1,1); hold on; grid on; 175 | plot(K.Time,K.Kp1,'-*r','MarkerIndices',MarkIdx); 176 | legend('$K_{P_1}(k)$',... 177 | 'Interpreter','latex',... 178 | 'Orientation','horizontal',... 179 | 'Location','best',... 180 | 'FontSize',12); 181 | xlabel('time (s)','Interpreter','latex'); 182 | ylabel('opening rate','Interpreter','latex'); 183 | 184 | subplot(2,1,2); hold on; grid on; 185 | plot(K.Time,K.Kp2,'-*r','MarkerIndices',MarkIdx); 186 | legend('$K_{P_2}(k)$',... 187 | 'Interpreter','latex',... 188 | 'Orientation','horizontal',... 189 | 'Location','best',... 190 | 'FontSize',12); 191 | xlabel('time (s)','Interpreter','latex'); 192 | ylabel('opening rate','Interpreter','latex'); 193 | 194 | % Levels 195 | figure(); 196 | subplot(3,1,1); hold on; grid on; 197 | p1 = plot(Y.Time,Y.h1,'.m'); 198 | p2 = plot(X.Time,X.h1,'-*r','MarkerIndices',MarkIdx); 199 | legend([p1 p2],'$\tilde{x}_{1}$','$x_{1}$',... 200 | 'Interpreter','latex',... 201 | 'Orientation','horizontal',... 202 | 'Location','best',... 203 | 'FontSize',12) 204 | xlabel('time (s)','Interpreter','latex'); 205 | ylabel('level (cm)','Interpreter','latex'); 206 | 207 | subplot(3,1,2); hold on; grid on; 208 | p1 = plot(Y.Time,Y.h2,'.m'); 209 | p2 = plot(X.Time,X.h2,'-*r','MarkerIndices',MarkIdx); 210 | legend([p1 p2],'$\tilde{x}_{2}$','$x_{2}$',... 211 | 'Interpreter','latex',... 212 | 'Orientation','horizontal',... 213 | 'Location','best',... 214 | 'FontSize',12); 215 | xlabel('time (s)','Interpreter','latex'); 216 | ylabel('level (cm)','Interpreter','latex'); 217 | 218 | subplot(3,1,3); hold on; grid on; 219 | p1 = plot(X.Time,X.h3,'-*r','MarkerIndices',MarkIdx); 220 | legend(p1,'$x_{3}$',... 221 | 'Interpreter','latex',... 222 | 'Orientation','horizontal',... 223 | 'Location','best',... 224 | 'FontSize',12) 225 | xlabel('time (s)','Interpreter','latex'); 226 | ylabel('level (cm)','Interpreter','latex'); 227 | 228 | % Flows 229 | figure(); 230 | subplot(3,1,1); hold on; grid on; 231 | p1 = plot(Y.Time,Y.Q13,'.m'); 232 | p2 = plot(Q.Time,Q.Q13,'-*r','MarkerIndices',MarkIdx); 233 | legend([p1 p2],'$\tilde{Q}_{13}$','$Q_{13}$',... 234 | 'Interpreter','latex',... 235 | 'Orientation','horizontal',... 236 | 'Location','best',... 237 | 'FontSize',12) 238 | xlabel('time (s)','Interpreter','latex'); 239 | ylabel('flow ($\mathrm{cm^3/s}$)','Interpreter','latex'); 240 | 241 | subplot(3,1,2); hold on; grid on; 242 | p1 = plot(Y.Time,Y.Q23,'.m'); 243 | p2 = plot(Q.Time,Q.Q23,'-*r','MarkerIndices',MarkIdx); 244 | legend([p1 p2],'$\tilde{Q}_{23}$','$Q_{23}$',... 245 | 'Interpreter','latex',... 246 | 'Orientation','horizontal',... 247 | 'Location','best',... 248 | 'FontSize',12) 249 | xlabel('time (s)','Interpreter','latex'); 250 | ylabel('flow ($\mathrm{cm^3/s}$)','Interpreter','latex'); 251 | 252 | subplot(3,1,3); hold on; grid on; 253 | p0 = plot(Q.Time,sp(1,:),'--ok','MarkerIndices',MarkIdx); 254 | p1 = plot(Y.Time,Y.Q3,'.m'); 255 | p2 = plot(Q.Time,Q.Q3,'-*r','MarkerIndices',MarkIdx); 256 | legend([p0 p1 p2],'$Q_\mathrm{3ref}$','$\tilde{Q}_{3}$','$Q_{3}$',... 257 | 'Interpreter','latex',... 258 | 'Orientation','horizontal',... 259 | 'Location','best',... 260 | 'FontSize',12) 261 | xlabel('time (s)','Interpreter','latex'); 262 | ylabel('flow ($\mathrm{cm^3/s}$)','Interpreter','latex'); 263 | -------------------------------------------------------------------------------- /tests/pid_level_control/pidLevelControl_test.m: -------------------------------------------------------------------------------- 1 | % This script simulates Sim3Tanks in a closed-loop with a digital PID 2 | % controller. The controlled variables are the levels h1 and h2, and the 3 | % control signals are the flows Qp1 and Qp2 provided by pumps P1 and P2, 4 | % respectively. 5 | 6 | clc; clear; close all; 7 | 8 | % Add internal dependency paths 9 | addpath(genpath('..\..\src')); 10 | addpath(genpath('..\..\addons')); 11 | 12 | % Set time vector 13 | tstart = 0; % Start time 14 | tstop = 120; % Stop time 15 | Ts = 0.1; % Sample time 16 | time = tstart : Ts : tstop; 17 | 18 | N = numel(time); % Number of samples 19 | 20 | % Create a Sim3Tanks object 21 | tts = createSim3Tanks(); 22 | 23 | %% System parameters 24 | tts.Model.PhysicalParam.TankRadius = 5; 25 | tts.Model.PhysicalParam.TankHeight = 50; 26 | tts.Model.PhysicalParam.PipeRadius = 0.6; 27 | tts.Model.PhysicalParam.TransPipeHeight = 30; 28 | tts.Model.PhysicalParam.CorrectionTerm = 1; 29 | tts.Model.PhysicalParam.GravityConstant = 981; 30 | tts.Model.PhysicalParam.PumpMinFlow = 0; 31 | tts.Model.PhysicalParam.PumpMaxFlow = 120; 32 | 33 | %% Valve settings 34 | tts.Model.ValveSettings.Kp1.OperationMode = 'Open'; 35 | tts.Model.ValveSettings.Kp1.EnableControl = false; 36 | tts.Model.ValveSettings.Kp2.OperationMode = 'Open'; 37 | tts.Model.ValveSettings.Kp2.EnableControl = false; 38 | tts.Model.ValveSettings.K13.OperationMode = 'Open'; 39 | tts.Model.ValveSettings.K13.EnableControl = false; 40 | tts.Model.ValveSettings.K23.OperationMode = 'Open'; 41 | tts.Model.ValveSettings.K23.EnableControl = false; 42 | tts.Model.ValveSettings.K3.OperationMode = 'Open'; 43 | tts.Model.ValveSettings.K3.EnableControl = false; 44 | 45 | %% Fault settings 46 | tts.Model.FaultSettings.f1.EnableSignal = true; % Loss Of Effectiveness in Kp1 47 | tts.Model.FaultSettings.f2.EnableSignal = true; % Loss Of Effectiveness in Kp1 48 | tts.Model.FaultSettings.f6.EnableSignal = true; % Clogging Q13 49 | tts.Model.FaultSettings.f9.EnableSignal = true; % Leakage Q2 50 | tts.Model.FaultSettings.f10.EnableSignal = true; % Clogging Q3 51 | tts.Model.FaultSettings.f11.EnableSignal = true; % Sensor h1 52 | tts.Model.FaultSettings.f12.EnableSignal = true; % Sensor h2 53 | tts.Model.FaultSettings.f19.EnableSignal = true; % Sensor Q13 54 | tts.Model.FaultSettings.f20.EnableSignal = true; % Sensor Q23 55 | tts.Model.FaultSettings.f23.EnableSignal = true; % Sensor Q3 56 | 57 | %% Setting simulation scenario 58 | 59 | % Setpoint for level h1 and h2 (cm) 60 | sp_h1 = stepSignal([10 14],[0 20],time); 61 | sp_h2 = stepSignal([10 16],[0 60],time); 62 | 63 | sp = [sp_h1;sp_h2]; 64 | 65 | % Fault signals 66 | f1 = zeros(size(time)); 67 | f2 = zeros(size(time)); 68 | f6 = zeros(size(time)); 69 | f9 = zeros(size(time)); 70 | f10 = zeros(size(time)); 71 | f11 = zeros(size(time)); 72 | f12 = zeros(size(time)); 73 | f19 = zeros(size(time)); 74 | f20 = zeros(size(time)); 75 | f23 = zeros(size(time)); 76 | 77 | % Offset for sensor faults 78 | tts.Model.FaultSettings.f11.Offset = 0; 79 | tts.Model.FaultSettings.f12.Offset = 0; 80 | tts.Model.FaultSettings.f19.Offset = 0; 81 | tts.Model.FaultSettings.f20.Offset = 0; 82 | tts.Model.FaultSettings.f23.Offset = 0; 83 | 84 | % Initial condition 85 | tts.Model.InitialCondition = [10 10 8]; 86 | uk(:,1) = [0;0]; % First control signal 87 | 88 | %% System simulation 89 | 90 | fprintf([getMessage('tag'),'Starting simulation...\n']); 91 | 92 | for k = 2 : N % k=1 conrresponds to initial condition 93 | 94 | fprintf([getMessage('tag'),'Simulating NONLINEAR plant (%d/%d)\n'],k,N); 95 | 96 | % +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 97 | % #BEGIN: Continuous Nonlinear Plant ++++++++++++++++++++++++++++++++++ 98 | % +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 99 | 100 | % D/A converter: uk->u(t) 101 | u = uk(:,k-1); 102 | 103 | % Control signal for the valves Kp1 and Kp2. 104 | % tts.Model.ValveSettings.Kp1.OpeningRate = 1; 105 | % tts.Model.ValveSettings.Kp2.OpeningRate = 1; 106 | 107 | % Taking the fault magnitude over time. 108 | tts.Model.FaultSettings.f1.Magnitude = f1(k); 109 | tts.Model.FaultSettings.f2.Magnitude = f2(k); 110 | tts.Model.FaultSettings.f6.Magnitude = f6(k); 111 | tts.Model.FaultSettings.f9.Magnitude = f9(k); 112 | tts.Model.FaultSettings.f10.Magnitude = f10(k); 113 | tts.Model.FaultSettings.f11.Magnitude = f11(k); 114 | tts.Model.FaultSettings.f12.Magnitude = f12(k); 115 | tts.Model.FaultSettings.f19.Magnitude = f19(k); 116 | tts.Model.FaultSettings.f20.Magnitude = f20(k); 117 | tts.Model.FaultSettings.f23.Magnitude = f23(k); 118 | 119 | % Process noise. 120 | tts.Model.ProcessNoise.EnableSignal = false; 121 | tts.Model.ProcessNoise.Magnitude = random('norm',0,0.1,[1 3]); 122 | 123 | % Measurement noise. 124 | tts.Model.MeasurementNoise.EnableSignal = false; 125 | yx = random('norm',0,0.2,[1 03]); % Level Sensors 126 | yq = random('norm',0,0.6,[1 10]); % Flow Sensors 127 | tts.Model.MeasurementNoise.Magnitude = [yx,yq]; 128 | 129 | % Nonlinear dynamic model. 130 | [yt,xt,qt] = tts.simulateModel('Qp1',u(1),'Qp2',u(2),'Tspan',Ts); 131 | 132 | % Measured output vector for this case: y = [h1, h2, Q13, Q23, Q3]' 133 | y = [yt(1),yt(2),yt(9),yt(10),yt(13)]'; 134 | 135 | % A/D converter: y(t)->yk 136 | yk(:,k) = y; %#ok<*SAGROW> 137 | 138 | % --------------------------------------------------------------------- 139 | % #END: Continuous Nonlinear Plant ------------------------------------ 140 | % --------------------------------------------------------------------- 141 | 142 | 143 | % +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 144 | % #BEGIN: Digital PID control for levels h1 and h2 ++++++++++++++++++++ 145 | % +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 146 | if(k>2) 147 | ek_past = sp(:,k-2:k) - yk(1:2,k-2:k); % [e(k-2) e(k-1) e(k)] 148 | uk_past = uk(:,k-2:k-1); % [u(k-2) u(k-1)] 149 | uk(:,k) = pidLevelControl(ek_past,uk_past,Ts); 150 | else 151 | uk(:,k) = zeros(2,1); 152 | end 153 | % --------------------------------------------------------------------- 154 | % #END: Digital PID control for levels h1 and h2 ---------------------- 155 | % --------------------------------------------------------------------- 156 | 157 | end 158 | 159 | fprintf([getMessage('tag'),'The simulation is done!\n']); 160 | 161 | % Preparing variables 162 | 163 | X = tts.getStateVariables(); 164 | Q = tts.getFlowVariables(); 165 | Y = tts.getSensorMeasurements(); 166 | K = tts.getValveSignals(); 167 | F = tts.getFaultMagnitudes(); 168 | O = tts.getFaultOffsets(); 169 | 170 | %% Plots 171 | 172 | fprintf([getMessage('tag'),'Plotting Graphs...\n']); 173 | 174 | MarkIdx = 1:round(N/20):N; 175 | 176 | % Pump signals (Actuators) 177 | figure(); 178 | subplot(2,1,1); hold on; grid on; 179 | plot(Q.Time,Q.Q1in,'-*r','MarkerIndices',MarkIdx); 180 | legend('$Q_{1in}$',... 181 | 'Interpreter','latex',... 182 | 'Orientation','horizontal',... 183 | 'Location','best',... 184 | 'FontSize',12); 185 | xlabel('time (s)','Interpreter','latex'); 186 | ylabel('inflow ($\mathrm{cm^3/s}$)','Interpreter','latex'); 187 | 188 | subplot(2,1,2); hold on; grid on; 189 | plot(Q.Time,Q.Q2in,'-*r','MarkerIndices',MarkIdx); 190 | legend('$Q_{2in}$',... 191 | 'Interpreter','latex',... 192 | 'Orientation','horizontal',... 193 | 'Location','best',... 194 | 'FontSize',12); 195 | xlabel('time (s)','Interpreter','latex'); 196 | ylabel('inflow ($\mathrm{cm^3/s}$)','Interpreter','latex'); 197 | 198 | % Levels 199 | figure(); 200 | subplot(3,1,1); hold on; grid on; 201 | p0 = plot(X.Time,sp(1,:),'--ok','MarkerIndices',MarkIdx); 202 | p1 = plot(Y.Time,Y.h1,'.m'); 203 | p2 = plot(X.Time,X.h1,'-*r','MarkerIndices',MarkIdx); 204 | legend([p0 p1 p2],'$x_\mathrm{1ref}$','$\tilde{x}_{1}$','$x_{1}$',... 205 | 'Interpreter','latex',... 206 | 'Orientation','horizontal',... 207 | 'Location','best',... 208 | 'FontSize',12) 209 | xlabel('time (s)','Interpreter','latex'); 210 | ylabel('level (cm)','Interpreter','latex'); 211 | 212 | subplot(3,1,2); hold on; grid on; 213 | p0 = plot(X.Time,sp(2,:),'--ok','MarkerIndices',MarkIdx); 214 | p1 = plot(Y.Time,Y.h2,'.m'); 215 | p2 = plot(X.Time,X.h2,'-*r','MarkerIndices',MarkIdx); 216 | legend([p0 p1 p2],'$x_\mathrm{2ref}$','$\tilde{x}_{2}$','$x_{2}$',... 217 | 'Interpreter','latex',... 218 | 'Orientation','horizontal',... 219 | 'Location','best',... 220 | 'FontSize',12); 221 | xlabel('time (s)','Interpreter','latex'); 222 | ylabel('level (cm)','Interpreter','latex'); 223 | 224 | subplot(3,1,3); hold on; grid on; 225 | p1 = plot(X.Time,X.h3,'-*r','MarkerIndices',MarkIdx); 226 | legend(p1,'$x_{3}$',... 227 | 'Interpreter','latex',... 228 | 'Orientation','horizontal',... 229 | 'Location','best',... 230 | 'FontSize',12) 231 | xlabel('time (s)','Interpreter','latex'); 232 | ylabel('level (cm)','Interpreter','latex'); 233 | 234 | % Flows 235 | figure(); 236 | subplot(3,1,1); hold on; grid on; 237 | p1 = plot(Y.Time,Y.Q13,'.m'); 238 | p2 = plot(Q.Time,Q.Q13,'-*r','MarkerIndices',MarkIdx); 239 | legend([p1 p2],'$\tilde{Q}_{13}$','$Q_{13}$',... 240 | 'Interpreter','latex',... 241 | 'Orientation','horizontal',... 242 | 'Location','best',... 243 | 'FontSize',12) 244 | xlabel('time (s)','Interpreter','latex'); 245 | ylabel('flow ($\mathrm{cm^3/s}$)','Interpreter','latex'); 246 | 247 | subplot(3,1,2); hold on; grid on; 248 | p1 = plot(Y.Time,Y.Q23,'.m'); 249 | p2 = plot(Q.Time,Q.Q23,'-*r','MarkerIndices',MarkIdx); 250 | legend([p1 p2],'$\tilde{Q}_{23}$','$Q_{23}$',... 251 | 'Interpreter','latex',... 252 | 'Orientation','horizontal',... 253 | 'Location','best',... 254 | 'FontSize',12) 255 | xlabel('time (s)','Interpreter','latex'); 256 | ylabel('flow ($\mathrm{cm^3/s}$)','Interpreter','latex'); 257 | 258 | subplot(3,1,3); hold on; grid on; 259 | p1 = plot(Y.Time,Y.Q3,'.m'); 260 | p2 = plot(Q.Time,Q.Q3,'-*r','MarkerIndices',MarkIdx); 261 | legend([p1 p2],'$\tilde{Q}_{3}$','$Q_{3}$',... 262 | 'Interpreter','latex',... 263 | 'Orientation','horizontal',... 264 | 'Location','best',... 265 | 'FontSize',12) 266 | xlabel('time (s)','Interpreter','latex'); 267 | ylabel('flow ($\mathrm{cm^3/s}$)','Interpreter','latex'); 268 | -------------------------------------------------------------------------------- /src/@Sim3Tanks/Sim3Tanks.m: -------------------------------------------------------------------------------- 1 | classdef Sim3Tanks < handle 2 | % Sim3Tanks is the class used to define the system configurations. 3 | 4 | % https://github.com/e-controls/Sim3Tanks 5 | 6 | %====================================================================== 7 | 8 | properties (Access = public, Hidden = true, Constant = true) 9 | 10 | % The sequential order of the cell elements must be maintained! 11 | LIST_OF_FIELDS = ... 12 | {'PhysicalParam';'ValveSettings';'FaultSettings';... 13 | 'ProcessNoise';'MeasurementNoise';'InitialCondition'}; 14 | LIST_OF_PARAM = ... 15 | {'TankRadius';'TankHeight';'PipeRadius';'TransPipeHeight';... 16 | 'CorrectionTerm';'GravityConstant';'PumpMinFlow';'PumpMaxFlow'}; 17 | LIST_OF_VALVES = ... 18 | {'Kp1';'Kp2';'Kp3';'Ka';'Kb';'K13';'K23';'K1';'K2';'K3'}; 19 | LIST_OF_FAULTS = ... 20 | {'f1';'f2';'f3';'f4';'f5';'f6';'f7';'f8';'f9';'f10';'f11';'f12';... 21 | 'f13';'f14';'f15';'f16';'f17';'f18';'f19';'f20';'f21';'f22';'f23'}; 22 | LIST_OF_STATES = ... 23 | {'h1';'h2';'h3'}; 24 | LIST_OF_FLOWS = ... 25 | {'Q1in';'Q2in';'Q3in';'Qa';'Qb';'Q13';'Q23';'Q1';'Q2';'Q3'}; 26 | 27 | end 28 | 29 | %====================================================================== 30 | 31 | properties (Access = private, Hidden = true) 32 | SimulationTime {mustBeFinite,mustBeNonnegative} = []; 33 | StateVariables {mustBeFinite,mustBeNonnegative} = []; 34 | FlowVariables {mustBeFinite} = []; 35 | SensorMeasurements {mustBeFinite} = []; 36 | ValveSignals {mustBeGreaterThanOrEqual(ValveSignals,0),mustBeLessThanOrEqual(ValveSignals,1)} = []; 37 | FaultMagnitudes {mustBeGreaterThanOrEqual(FaultMagnitudes,0),mustBeLessThanOrEqual(FaultMagnitudes,1)} = []; 38 | FaultOffsets {mustBeFinite} = []; 39 | end 40 | 41 | %====================================================================== 42 | 43 | properties (Access = public, Hidden = true) 44 | 45 | About = []; 46 | 47 | end 48 | 49 | properties (Access = public, Hidden = false) 50 | 51 | Model = []; 52 | 53 | end 54 | 55 | %====================================================================== 56 | 57 | methods % Class Constructor 58 | 59 | function obj = Sim3Tanks(varargin) 60 | % Check input arguments 61 | if(nargin()~=0) 62 | error(getMessage('ERR002')); 63 | end 64 | obj.prepareModel(); 65 | end 66 | 67 | end 68 | 69 | %====================================================================== 70 | 71 | methods (Access = private, Hidden = true) % Prepare method 72 | 73 | function prepareModel(this) 74 | 75 | % PhysicalParam 76 | for i = 1 : numel(this.LIST_OF_PARAM) 77 | this.Model.(this.LIST_OF_FIELDS{1}).(this.LIST_OF_PARAM{i}) = []; 78 | end 79 | 80 | % ValveSettings 81 | for i = 1 : numel(this.LIST_OF_VALVES) 82 | this.Model.(this.LIST_OF_FIELDS{2}).(this.LIST_OF_VALVES{i}).OperationMode = 'Closed'; 83 | this.Model.(this.LIST_OF_FIELDS{2}).(this.LIST_OF_VALVES{i}).EnableControl = false; 84 | this.Model.(this.LIST_OF_FIELDS{2}).(this.LIST_OF_VALVES{i}).OpeningRate = []; 85 | end 86 | 87 | % FaultSettings (Magnitudes) 88 | for i = 1 : numel(this.LIST_OF_FAULTS) 89 | this.Model.(this.LIST_OF_FIELDS{3}).(this.LIST_OF_FAULTS{i}).EnableSignal = false; 90 | this.Model.(this.LIST_OF_FIELDS{3}).(this.LIST_OF_FAULTS{i}).Magnitude = []; 91 | end 92 | 93 | % FaultSettings (Offsets) 94 | for i = 11 : numel(this.LIST_OF_FAULTS) 95 | this.Model.(this.LIST_OF_FIELDS{3}).(this.LIST_OF_FAULTS{i}).Offset = []; 96 | end 97 | 98 | % ProcessNoise 99 | this.Model.(this.LIST_OF_FIELDS{4}).EnableSignal = false; 100 | this.Model.(this.LIST_OF_FIELDS{4}).Magnitude = []; 101 | 102 | % MeasurementNoise 103 | this.Model.(this.LIST_OF_FIELDS{5}).EnableSignal = false; 104 | this.Model.(this.LIST_OF_FIELDS{5}).Magnitude = []; 105 | 106 | % InitialCondition 107 | this.Model.(this.LIST_OF_FIELDS{6}) = []; 108 | 109 | % Initializing simulation time 110 | this.SimulationTime = 0; 111 | 112 | end 113 | 114 | end 115 | 116 | %====================================================================== 117 | 118 | methods (Access = private, Hidden = true) % Setter methods 119 | 120 | function setInternalStateVariables(this,x) 121 | N = numel(this.LIST_OF_STATES); 122 | if(nargin()==1) 123 | error(getMessage('ERR001')); 124 | elseif(~isrow(x) && ~isempty(x)) 125 | error(getMessage('ERR005')); 126 | elseif(numel(x)~=N && ~isempty(x)) 127 | error([getMessage('ERR006'),' The system has ',num2str(N),' state variables.']); 128 | end 129 | this.StateVariables = x; 130 | end 131 | 132 | function setInternalFlowVariables(this,q) 133 | N = numel(this.LIST_OF_FLOWS); 134 | if(nargin()==1) 135 | error(getMessage('ERR001')); 136 | elseif(~isrow(q) && ~isempty(q)) 137 | error(getMessage('ERR005')); 138 | elseif(numel(q)~=N && ~isempty(q)) 139 | error([getMessage('ERR006'),' The system has ',num2str(N),' flow variables.']); 140 | end 141 | this.FlowVariables = q; 142 | end 143 | 144 | function setInternalSensorMeasurements(this,y) 145 | N = numel(this.LIST_OF_STATES) + numel(this.LIST_OF_FLOWS); 146 | if(nargin()==1) 147 | error(getMessage('ERR001')); 148 | elseif(~isrow(y) && ~isempty(y)) 149 | error(getMessage('ERR005')); 150 | elseif(numel(y)~=N && ~isempty(y)) 151 | error([getMessage('ERR006'),' The system has ',num2str(N),' measured variables.']); 152 | end 153 | this.SensorMeasurements = y; 154 | end 155 | 156 | function setInternalValveSignals(this,v) 157 | N = numel(this.LIST_OF_VALVES); 158 | if(nargin()==1) 159 | error(getMessage('ERR001')); 160 | elseif(~isrow(v) && ~isempty(v)) 161 | error(getMessage('ERR005')); 162 | elseif(numel(v)~=N && ~isempty(v)) 163 | error([getMessage('ERR006'),' The system has ',num2str(N),' valves.']); 164 | end 165 | this.ValveSignals = v; 166 | end 167 | 168 | function setInternalFaultMagnitudes(this,f) 169 | N = numel(this.LIST_OF_FAULTS); 170 | if(nargin()==1) 171 | error(getMessage('ERR001')); 172 | elseif(~isrow(f) && ~isempty(f)) 173 | error(getMessage('ERR005')); 174 | elseif(numel(f)~=N && ~isempty(f)) 175 | error([getMessage('ERR006'),' The system has ',num2str(N),' fault magnitudes.']); 176 | end 177 | this.FaultMagnitudes = f; 178 | end 179 | 180 | function setInternalFaultOffsets(this,f) 181 | N = numel(this.LIST_OF_FAULTS(11:end)); 182 | if(nargin()==1) 183 | error(getMessage('ERR001')); 184 | elseif(~isrow(f) && ~isempty(f)) 185 | error(getMessage('ERR005')); 186 | elseif(numel(f)~=N && ~isempty(f)) 187 | error([getMessage('ERR006'),' The system has ',num2str(N),' fault offsets.']); 188 | end 189 | this.FaultOffsets = f; 190 | end 191 | 192 | end 193 | 194 | %====================================================================== 195 | 196 | methods (Access = private, Hidden = true) % Pusher methods 197 | 198 | function pushInternalStateVariables(this,x) 199 | N = numel(this.LIST_OF_STATES); 200 | if(nargin()==1) 201 | error(getMessage('ERR001')); 202 | elseif(~isrow(x) && ~isempty(x)) 203 | error(getMessage('ERR005')); 204 | elseif(numel(x)~=N && ~isempty(x)) 205 | error([getMessage('ERR006'),' The system has ',num2str(N),' state variables.']); 206 | end 207 | this.StateVariables = [this.StateVariables;x]; 208 | end 209 | 210 | function pushInternalFlowVariables(this,q) 211 | N = numel(this.LIST_OF_FLOWS); 212 | if(nargin()==1) 213 | error(getMessage('ERR001')); 214 | elseif(~isrow(q) && ~isempty(q)) 215 | error(getMessage('ERR005')); 216 | elseif(numel(q)~=N && ~isempty(q)) 217 | error([getMessage('ERR006'),' The system has ',num2str(N),' flow variables.']); 218 | end 219 | this.FlowVariables = [this.FlowVariables;q]; 220 | end 221 | 222 | function pushInternalSensorMeasurements(this,y) 223 | N = numel(this.LIST_OF_STATES) + numel(this.LIST_OF_FLOWS); 224 | if(nargin()==1) 225 | error(getMessage('ERR001')); 226 | elseif(~isrow(y) && ~isempty(y)) 227 | error(getMessage('ERR005')); 228 | elseif(numel(y)~=N && ~isempty(y)) 229 | error([getMessage('ERR006'),' The system has ',num2str(N),' measured variables.']); 230 | end 231 | this.SensorMeasurements = [this.SensorMeasurements;y]; 232 | end 233 | 234 | function pushInternalValveSignals(this,v) 235 | N =numel(this.LIST_OF_VALVES); 236 | if(nargin()==1) 237 | error(getMessage('ERR001')); 238 | elseif(~isrow(v) && ~isempty(v)) 239 | error(getMessage('ERR005')); 240 | elseif(numel(v)~=N && ~isempty(v)) 241 | error([getMessage('ERR006'),' The system has ',num2str(N),' valves.']); 242 | end 243 | this.ValveSignals = [this.ValveSignals;v]; 244 | end 245 | 246 | function pushInternalFaultMagnitudes(this,f) 247 | N = numel(this.LIST_OF_FAULTS); 248 | if(nargin()==1) 249 | error(getMessage('ERR001')); 250 | elseif(~isrow(f) && ~isempty(f)) 251 | error(getMessage('ERR005')); 252 | elseif(numel(f)~=N && ~isempty(f)) 253 | error([getMessage('ERR006'),' The system has ',num2str(N),' fault magnitudes.']); 254 | end 255 | this.FaultMagnitudes = [this.FaultMagnitudes;f]; 256 | end 257 | 258 | function pushInternalFaultOffsets(this,f) 259 | N = numel(this.LIST_OF_FAULTS(11:end)); 260 | if(nargin()==1) 261 | error(getMessage('ERR001')); 262 | elseif(~isrow(f) && ~isempty(f)) 263 | error(getMessage('ERR005')); 264 | elseif(numel(f)~=N && ~isempty(f)) 265 | error([getMessage('ERR006'),' The system has ',num2str(N),' fault offsets.']); 266 | end 267 | this.FaultOffsets = [this.FaultOffsets;f]; 268 | end 269 | 270 | end 271 | 272 | %====================================================================== 273 | 274 | methods (Access = private, Hidden = true) % Getter methods 275 | 276 | function x = getInternalStateVariables(varargin) 277 | this = varargin{1}; 278 | x = this.StateVariables; 279 | if(nargin()==2 && ~isempty(x)) 280 | t = varargin{2}; 281 | varNames = this.LIST_OF_STATES; 282 | x = array2timetable(x,'RowTimes',seconds(t),'VariableNames',varNames); 283 | elseif(nargin()>2) 284 | error(getMessage('ERR002')); 285 | end 286 | end 287 | 288 | function q = getInternalFlowVariables(varargin) 289 | this = varargin{1}; 290 | q = this.FlowVariables; 291 | if(nargin()==2 && ~isempty(q)) 292 | t = varargin{2}; 293 | varNames = this.LIST_OF_FLOWS; 294 | q = array2timetable(q,'RowTimes',seconds(t),'VariableNames',varNames); 295 | elseif(nargin()>2) 296 | error(getMessage('ERR002')); 297 | end 298 | end 299 | 300 | function y = getInternalSensorMeasurements(varargin) 301 | this = varargin{1}; 302 | y = this.SensorMeasurements; 303 | if(nargin()==2 && ~isempty(y)) 304 | t = varargin{2}; 305 | varNames = [this.LIST_OF_STATES;this.LIST_OF_FLOWS]; 306 | y = array2timetable(y,'RowTimes',seconds(t),'VariableNames',varNames); 307 | elseif(nargin()>2) 308 | error(getMessage('ERR002')); 309 | end 310 | end 311 | 312 | function v = getInternalValveSignals(varargin) 313 | this = varargin{1}; 314 | v = this.ValveSignals; 315 | if(nargin()==2 && ~isempty(v)) 316 | t = varargin{2}; 317 | varNames = this.LIST_OF_VALVES; 318 | v = array2timetable(v,'RowTimes',seconds(t),'VariableNames',varNames); 319 | elseif(nargin()>2) 320 | error(getMessage('ERR002')); 321 | end 322 | end 323 | 324 | function f = getInternalFaultMagnitudes(varargin) 325 | this = varargin{1}; 326 | f = this.FaultMagnitudes; 327 | if(nargin()==2 && ~isempty(f)) 328 | t = varargin{2}; 329 | varNames = this.LIST_OF_FAULTS; 330 | f = array2timetable(f,'RowTimes',seconds(t),'VariableNames',varNames); 331 | elseif(nargin()>2) 332 | error(getMessage('ERR002')); 333 | end 334 | end 335 | 336 | function f = getInternalFaultOffsets(varargin) 337 | this = varargin{1}; 338 | f = this.FaultOffsets; 339 | if(nargin()==2 && ~isempty(f)) 340 | t = varargin{2}; 341 | varNames = this.LIST_OF_FAULTS(11:end); 342 | f = array2timetable(f,'RowTimes',seconds(t),'VariableNames',varNames); 343 | elseif(nargin()>2) 344 | error(getMessage('ERR002')); 345 | end 346 | end 347 | 348 | end 349 | 350 | %====================================================================== 351 | 352 | methods (Access = private, Hidden = true) % Time methods 353 | 354 | function resetInternalSimulationTime(varargin) 355 | if(nargin()>1) 356 | error(getMessage('ERR002')); 357 | end 358 | this = varargin{1}; 359 | this.SimulationTime = 0; 360 | end 361 | 362 | function incrementInternalSimulationTime(varargin) 363 | if(nargin()<2) 364 | error(getMessage('ERR001')); 365 | end 366 | this = varargin{1}; 367 | time = varargin{2}; 368 | this.SimulationTime = [this.SimulationTime;time]; 369 | end 370 | 371 | function t = getInternalSimulationTime(varargin) 372 | this = varargin{1}; 373 | t = this.SimulationTime; 374 | if(nargin()==2) 375 | t = t(varargin{2}); 376 | elseif(nargin()>2) 377 | error(getMessage('ERR002')); 378 | end 379 | end 380 | 381 | end 382 | 383 | end -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Status](https://img.shields.io/badge/STATUS-Updating...-blue) 2 | ![License](https://img.shields.io/badge/License-_MIT-green) 3 | ![Language](https://img.shields.io/badge/Language-_MATLAB-green) 4 | > [!NOTE] 5 | > This README is still being updated... 6 | 7 | # Sim3Tanks: A Benchmark Model Simulator for Process Control and Monitoring 8 | Sim3Tanks is suitable for studying and developing process control, fault detection and diagnosis, and fault-tolerant control strategies for nonlinear multivariable systems. This new release was developed in the MATLAB environment, version 9.11.0 (2021b), and can be used via command-line script <`file.m`>. Future releases will include versions for using the simulator via Simulink block diagram <`file.slx`> and graphical user interface <`file.fig`>. 9 | 10 | The original paper that publishes the simulator can be accessed at and its repository at . 11 | 12 | ## 1. Table of Contents 13 | 14 | - [Sim3Tanks Plant Description](#2-sim3tanks-plant-description) 15 | - [Fault Description and Symbols](#3-fault-description-and-symbols) 16 | - [How to Create and Configure a Sim3Tanks Object](#4-how-to-create-and-configure-a-sim3tanks-object) 17 | - [Contributions](#5-contributions) 18 | - [Citation](#6-citation) 19 | - [License](#7-license) 20 | 21 | 22 | ## 2. Sim3Tanks Plant Description 23 | Sim3Tanks simulates the dynamic behavior of the following plant: 24 | 25 |

26 | 27 |
28 | Figure 1: Sim3Tanks plant. 29 |

30 | 31 | - The plant consists of three cylindrical tanks interconnected by four pipes, allowing fluid exchange between the lateral tanks (T1 and T2) and the central tank (T3) in both directions. The dashed arrows indicate the reference direction of each flow. Negative values indicate that the flow is opposite to this reference. 32 | 33 | - The upper pipes and valves that connect the lateral tanks to the central tank are located at the same height h0 and are called transmission pipes and valves. The lower pipes and valves are aligned with the base of the tanks and are called connection pipes and valves. At the bottom of each tank are the output pipes and valves. 34 | 35 | - The pumps P1, P2, and P3 share the same minimum and maximum flow rate constraints, but they work independently and can provide different flow rates QP1, QP2, and QP3 to the tanks T1, T2, and T3, respectively. 36 | 37 | - The tanks are identical and have the same radius and maximum height. Similarly, all pipes are also identical and have the same radius value. The system has three level sensors (one per tank) and ten flow sensors (one per valve), and any valve can be configured as an actuator. 38 | 39 | - When a valve is enabled to be controlled, it becomes an actuator and, consequently, Sim3Tanks simulates an actuator fault (loss of effectiveness). Otherwise, a plant fault is simulated, which could be a clogging if the valve operation mode is defined as 'Open', or leakage if its operation mode is set to 'Closed'. 40 | 41 | ## 3. Fault Description and Symbols 42 | Sim3Tanks allows the user to define different scenarios for case studies with as many actuators and sensors as necessary, in addition to simulating different kinds of perturbations, faults, and noises. The following table briefly describes the faults modeled in Sim3Tanks. 43 | 44 | | Symbol | Description | 45 | | --- | --- | 46 | | `f1` | Blocking/Clogging in pump 1, if `ValveSettings.Kp1.OperationMode = 'Open'`. | 47 | | | Disturbance in tank 1, if `ValveSettings.Kp1.OperationMode = 'Closed'`. | 48 | | `f2` | Blocking/Clogging in pump 2, if `ValveSettings.Kp2.OperationMode = 'Open'`. | 49 | | | Disturbance in tank 2, if `ValveSettings.Kp2.OperationMode = 'Closed'`. | 50 | | `f3` | Blocking/Clogging in pump 3, if `ValveSettings.Kp3.OperationMode = 'Open'`. | 51 | | | Disturbance in tank 3, if `ValveSettings.Kp3.OperationMode = 'Closed'`. | 52 | | `f4` | Clogging in the transmission pipe from tank 1 to tank 3, if `ValveSettings.Ka.OperationMode = 'Open'`. | 53 | | | Leakage through the transmission pipe from tank 1 to tank 3, if `ValveSettings.Ka.OperationMode = 'Closed'`. | 54 | | `f5` | Clogging in the transmission pipe from tank 2 to tank 3, if `ValveSettings.Kb.OperationMode = 'Open'`. | 55 | | | Leakage through the transmission pipe from tank 2 to tank 3, if `ValveSettings.Kb.OperationMode = 'Closed'`. | 56 | | `f6` | Clogging in the connection pipe from tank 1 to tank 3, if `ValveSettings.K13.OperationMode = 'Open'`. | 57 | | | Leakage through the connection pipe from tank 1 to tank 3, if `ValveSettings.K13.OperationMode = 'Closed'`. | 58 | | `f7` | Clogging in the connection pipe from tank 2 to tank 3, if `ValveSettings.K23.OperationMode = 'Open'`. | 59 | | | Leakage through the connection pipe from tank 2 to tank 3, if `ValveSettings.K23.OperationMode = 'Closed'`. | 60 | | `f8` | Clogging in output pipe of the tank 1, if `ValveSettings.K1.OperationMode = 'Open'`. | 61 | | | Leakage in tank 1, if `ValveSettings.K1.OperationMode = 'Closed'`. | 62 | | `f9` | Clogging in output pipe of the tank 2, if `ValveSettings.K2.OperationMode = 'Open'`. | 63 | | | Leakage in tank 2, if `ValveSettings.K2.OperationMode = 'Closed'`. | 64 | | `f10` | Clogging in output pipe of the tank 3, if `ValveSettings.K3.OperationMode = 'Open'`. | 65 | | | Leakage in tank 3, if `ValveSettings.K3.OperationMode = 'Closed'`. | 66 | | `f11` | Fault in level sensor `h1`. | 67 | | `f12` | Fault in level sensor `h2`. | 68 | | `f13` | Fault in level sensor `h3`. | 69 | | `f14` | Fault in flow sensor `Q1in`. | 70 | | `f15` | Fault in flow sensor `Q2in`. | 71 | | `f16` | Fault in flow sensor `Q3in`. | 72 | | `f17` | Fault in flow sensor `Qa`. | 73 | | `f18` | Fault in flow sensor `Qb`. | 74 | | `f19` | Fault in flow sensor `Q13`. | 75 | | `f20` | Fault in flow sensor `Q23`. | 76 | | `f21` | Fault in flow sensor `Q1`. | 77 | | `f22` | Fault in flow sensor `Q2`. | 78 | | `f23` | Fault in flow sensor `Q3`. | 79 | 80 | ## 4. How to Create and Configure a Sim3Tanks Object 81 | A Sim3Tanks object is created using the `createSim3Tanks()` function. 82 | 83 | ```sh 84 | tts = createSim3Tanks(); 85 | ``` 86 | 87 | This function does not have an input argument and returns an object with the following fields: 88 | 89 | - [**Model**](#41-the-model-attribute) : [attribute] 90 | - [**simulateModel**](#42-the-simulatemodel-method) : [method] 91 | - [**displayFields**](#43-the-displayfields-method) : [method] 92 | - [**clearModel**](#44-the-clearmodel-method) : [method] 93 | - [**clearVariables**](#45-the-clearvariables-method) : [method] 94 | - [**setDefaultModel**](#46-the-setdefaultmodel-method) : [method] 95 | - [**getDefaultLinearModel**](#47-the-getdefaultlinearmodel-method) : [method] 96 | - [**getStateVariables**](#48-the-getstatevariables-method) : [method] 97 | - [**getFlowVariables**](#49-the-getflowvariables-method) : [method] 98 | - [**getSensorMeasurements**](#410-the-getsensormeasurements-method) : [method] 99 | - [**getValveSignals**](#411-the-getvalvesignals-method) : [method] 100 | - [**getFaultSignals**](#412-the-getfaultsignals-method) : [method] 101 | 102 | --- 103 | ### 4.1. The `Model` attribute 104 | This attribute is used to define the system configurations. It is divided into the following subfields: 105 | 106 | - **PhysicalParam**: used to define the system's physical structure. 107 | - **TankRadius**: must be real and greater than 0. 108 | ``` 109 | tts.Model.PhysicalParam.TankRadius = 5; 110 | ``` 111 | - **TankHeight**: must be real and greater than 0. 112 | ``` 113 | tts.Model.PhysicalParam.TankHeight = 50; 114 | ``` 115 | - **PipeRadius**: must be real, greater than 0, and less than TankRadius. 116 | ``` 117 | tts.Model.PhysicalParam.PipeRadius = 0.6; 118 | ``` 119 | - **TransPipeHeight**: must be real, greater than 0, and less than TankHeight. 120 | ``` 121 | tts.Model.PhysicalParam.TransPipeHeight = 30; 122 | ``` 123 | - **CorrectionTerm**: must be real and greater than 0. 124 | ``` 125 | tts.Model.PhysicalParam.CorrectionTerm = 1; 126 | ``` 127 | - **GravityConstant**: must be real and greater than 0. 128 | ``` 129 | tts.Model.PhysicalParam.GravityConstant = 981; 130 | ``` 131 | - **PumpMinFlow**: must be real and greater than or equal to 0. 132 | ``` 133 | tts.Model.PhysicalParam.PumpMinFlow = 0; 134 | ``` 135 | - **PumpMaxFlow**: must be real and greater than PumpMinFlow. 136 | ``` 137 | tts.Model.PhysicalParam.PumpMaxFlow = 120; 138 | ``` 139 | 140 | - **ValveSettings**: used to define the system valve settings. It is divided into ten subfields, one per valve (Kp1, Kp2, Kp3, Ka, Kb, K13, K23, K1, K2, K3), and each one has the following settings: 141 | - **OperationMode**: must be set to ‘Open’ or ‘Closed’. 142 | ``` 143 | tts.Model.ValveSettings.Kp1.OperationMode = 'Open'; 144 | ``` 145 | - **EnableControl**: must be set to a logical value (true or false). 146 | ``` 147 | tts.Model.ValveSettings.Kp1.EnableControl = true; 148 | ``` 149 | - **OpeningRate**: must be real and belong to the range [0,1]. 150 | ``` 151 | tts.Model.ValveSettings.Kp1.OpeningRate = 0.9; 152 | ``` 153 | 154 | - **FaultSettings**: used to define the system fault settings. It is divided into twenty-three subfields, one per fault (f1, f2, …, f23), and each one has the following settings: 155 | - **EnableSignal**: must be set to a logical value (true or false). 156 | ``` 157 | tts.Model.FaultSettings.f1.EnableSignal = true; 158 | ``` 159 | - **Magnitude**: must be real and belong to the range [0,1]. 160 | ``` 161 | tts.Model.FaultSettings.f1.Magnitude = 0.3; 162 | ``` 163 | 164 | - **ProcessNoise**: used to set the system process noise. 165 | - **EnableSignal**: must be set to a logical value (true or false). 166 | ``` 167 | tts.Model.ProcessNoise.EnableSignal = true; 168 | ``` 169 | - **Magnitude**: must be a row or a column vector with three real and finite elements ([h1 h2 h3]). 170 | ``` 171 | tts.Model.ProcessNoise.Magnitude = [-0.05 0.06 -0.02]; 172 | ``` 173 | 174 | - **MeasurementNoise**: used to set the system process noise. 175 | - **EnableSignal**: must be set to a logical value (true or false). 176 | ``` 177 | tts.Model.MeasurementNoise.EnableSignal = true; 178 | ``` 179 | - **Magnitude**: must be a row or a column vector with thirteen real and finite elements ([h1, h2, h3, Q1in, Q2in, Q3in, Qa, Qb, Q13, Q23, Q1, Q2, Q3]). 180 | ``` 181 | tts.Model.MeasurementNoise.Magnitude = [0.26 -0.12 -0.18 0.39 -0.66 -0.96 0.81 0.71 -0.42 0.68 0.44 0.61 -0.45]; 182 | ``` 183 | 184 | - **InitialCondition**: used to define the system's initial condition and must be filled with a row vector of three elements. 185 | ``` 186 | tts.Model.InitialCondition = [40 25 20]; 187 | ``` 188 | --- 189 | ### 4.2. The `simulateModel()` method 190 | This method simulates the dynamic behavior of the three-tank system. 191 | ``` 192 | [y,x,q] = tts.simulateModel('Qp1',VALUE1,'Qp2',VALUE2,'Qp3',VALUE3,'Tspan',VALUE4); 193 | ``` 194 | > [!IMPORTANT] 195 | > Sim3Tanks uses the ode45 solver to solve the system differential equations numerically, and it is highly recommended to use simulation time increment as Tspan. 196 | 197 | --- 198 | ### 4.3. The `displayFields()` method 199 | This method does not have an input argument. It displays all fields and subfields of an object on the command line. 200 | ``` 201 | tts.displayFields(); 202 | ``` 203 | 204 | --- 205 | ### 4.4. The `clearModel()` method 206 | This method does not have an input argument. It clears all variables and restores an object's settings. 207 | ``` 208 | tts.clearModel(); 209 | ``` 210 | 211 | --- 212 | ### 4.5. The `clearVariables()` method 213 | This method does not have an input argument. It clears all state variables, valve, fault, flow signals, and sensor measurement data. 214 | ``` 215 | tts.clearVariables(); 216 | ``` 217 | 218 | --- 219 | ### 4.6. The `setDefaultModel()` method 220 | This method does not have an input argument. It configures a Sim3Tanks object to the default model. 221 | ``` 222 | tts.setDefaultModel(); 223 | ``` 224 | 225 | 226 | --- 227 | ### 4.7. The `getDefaultLinearModel()` method 228 | This method returns a linear model of the default scenario. 229 | ``` 230 | [SYS,OP] = tts.getDefaultLinearModel(x1op,METHOD,TSPAN); 231 | ``` 232 | 233 | Input arguments: 234 | - `x1op>0` (mandatory): define the operating point of T1. The remaining variables are found as follows: 235 | > x2op = x1op; 236 | > 237 | > x3op = (4/5)*x1op; 238 | > 239 | > u1op = (Beta/Qp)*sqrt(x1op/5); 240 | > 241 | > u2op = u1op; 242 | > 243 | > Q13op = Beta*sign(x1op - x3op)*sqrt(abs(x1op - x3op)); 244 | > 245 | > Q23op = Beta*sign(x2op - x3op)*sqrt(abs(x2op - x3op)); 246 | > 247 | > Q3op = Beta*sqrt(x3op); 248 | > 249 | > x_op = [x1op; x2op; x3op]; 250 | > 251 | > u_op = [u1op; u2op]; 252 | > 253 | > y_op = [x1op; x2op; Q13op; Q23op; Q3op]; 254 | 255 | - `METHOD`(optional): define the discretization method. The following options are valid: 256 | - 'zoh'; 257 | - 'foh'; 258 | - 'impulse'; 259 | - 'tustin'; 260 | - 'matched'; 261 | - 'euler'; 262 | 263 | - `TSPAN`(optional): define the sampling time of the discretization method. If `METHOD` is passed as input, then `TSPAN` becomes mandatory. 264 | 265 | > [!NOTE] 266 | > A continuous model is returned if `METHOD` and `TSPAN` are omitted. 267 | 268 | --- 269 | ### 4.8. The `getStateVariables()` method 270 | This method does not have an input argument. It returns a data table with the values of the state variables. 271 | ``` 272 | tts.getStateVariables(); 273 | ``` 274 | 275 | --- 276 | ### 4.9. The `getFlowVariables()` method 277 | This method does not have an input argument. It returns a data table with the values of the flow variables. 278 | ``` 279 | tts.getFlowVariables(); 280 | ``` 281 | 282 | --- 283 | ### 4.10. The `getSensorMeasurements()` method 284 | This method does not have an input argument. It returns a data table with the values of the measured variables. 285 | ``` 286 | tts.getSensorMeasurements(); 287 | ``` 288 | 289 | --- 290 | ### 4.11. The `getValveSignals()` method 291 | This method does not have an input argument. It returns a data table with the values of the valve signals. 292 | ``` 293 | tts.getValveSignals(); 294 | ``` 295 | 296 | --- 297 | ### 4.12. The `getFaultSignals()` method 298 | This method does not have an input argument. It returns a data table with the values of the fault signals. 299 | ``` 300 | tts.getFaultSignals(); 301 | ``` 302 | 303 | ## 5. Contributions 304 | Contributions are welcome! Please follow these steps: 305 | 306 | 1. Fork this repository. 307 | 2. Create a branch with your feature: 308 | ``` 309 | git checkout -b my-feature 310 | ``` 311 | 3. Commit your changes: 312 | ``` 313 | git commit -m 'feat: add my feature to do something' 314 | ``` 315 | 4. Push to the branch: 316 | ``` 317 | git push origin my-feature 318 | ``` 319 | 320 | 5. Open a Pull Request. 321 | 322 | ## 6. Citation 323 | If this project has helped you in your research, please consider citing: 324 | 325 | ``` 326 | @article{farias2018sim3tanks, 327 | title={Sim3Tanks: A benchmark model simulator for process control and monitoring}, 328 | author={Farias, Arllem O. and Queiroz, Gabriel Alisson C. and Bessa, Iury V. and Medeiros, Renan Landau P. and Cordeiro, Lucas C. and Palhares, Reinaldo M.}, 329 | journal={IEEE Access}, 330 | volume={6}, 331 | pages={62234--62254}, 332 | year={2018}, 333 | publisher={IEEE}, 334 | doi={10.1109/ACCESS.2018.2874752} 335 | } 336 | ``` 337 | 338 | ## 7. License 339 | This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for more details. 340 | --------------------------------------------------------------------------------