Figure 3.8:
Concentration versus time for the ancillary model predictive controller with sample time \Delta =12 (left) and \Delta =8 (right).
Code for Figure 3.8
Text of the GNU GPL.
main.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72 | % Robust control of an exothermic reaction.
% Load parameters for problem.
nompars = cstrparam();
nompars.tfinal = 600;
nompars.colloc = 10;
nompars.plantSteps = 20;
% Disturbance.
nompars.A = 0.002;
nompars.w = 0.4;
% Different sample times.
Deltas = [8, 12];
pars = struct();
for i = 1:length(Deltas)
f = sprintf('d%d', Deltas(i));
pars.(f) = nompars;
pars.(f).Delta = Deltas(i);
end
pars.d12.colloc = 24; % Need more points for longer sample time.
fields = fieldnames(pars);
% Prediction Horizon is same.
predHorizon = 360;
for i = 1:length(fields)
f = fields{i};
pars.(f).N = round(predHorizon/pars.(f).Delta);
end
% Run nominal (central path) MPC using sample time of 12. Then resample.
reftraj = nominalmpc(pars.d12);
% Run tube-based MPC multiple times.
results = struct();
for i = 1:length(fields)
f = fields{i};
pars.(f).reftraj = resample_nominal(reftraj, pars.(f).Delta);
results.(f) = tubempc(pars.(f));
end
% Make plot.
figure();
Nrows = 3;
Ncols = length(fields);
ylabels = {'concentration', 'temperature', 'coolant flow'};
ylims = {[0, 1], [0.3, 0.8], [-0.1, 1.4]};
for j = 1:Ncols
f = fields{j};
for i = 1:Nrows
subplot(Nrows, Ncols, (i - 1)*Ncols + j);
hold('on');
if i == 3
stairs(results.(f).t, results.(f).u([1:end,end]), '-k');
stairs(reftraj.t, reftraj.u([1:end,end]), '--b');
else
plot(results.(f).t, results.(f).x(i,:), '-k', ...
reftraj.t, reftraj.x(i,:), '--b');
end
xlim([0, 500]);
ylim(ylims{i});
if i == 1
title(sprintf('Delta = %s', f(2:end)));
elseif i == Nrows
xlabel('Time');
end
if j == 1
ylabel(ylabels{i});
end
end
end
|
nominalmpc.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108 | function reference_trajectory = nominalmpc(pars)
% reference_trajectory = nominalmpc(pars)
%
% Runs nominal NMPC for CSTR example in Chapter 3 to generate the reference
% trajectory for tube-based MPC.
%
% Note: In this script, "p" denotes the disturbance ("w" in MPC textbook).
mpc = import_mpctools();
% Read parameters.
Nu = pars.Nu;
Np = pars.Np;
Delta = pars.Delta;
tfinal = pars.tfinal;
N = pars.N;
uprev = pars.uprev;
usp = pars.usp;
% Add time state.
Nx = pars.Nx + 1;
xub = [pars.xub; Inf];
xlb = [pars.xlb; -Inf];
x0 = [pars.x0; 0];
ze = [pars.ze; tfinal];
% Tightened constraints for nominal problem.
uub = pars.uub_nominal;
ulb = pars.ulb_nominal;
% Make functions.
ode_model = @(x, u) pars.ode(x, u, 0, pars);
ode_plant = @(x, u, p) pars.ode(x, u, p, pars);
ode_casadi = mpc.getCasadiFunc(ode_model, [Nx, Nu], ...
{'x', 'u'}, {'cstr_model'});
cstrsim = mpc.getCasadiIntegrator(ode_plant, Delta, [Nx, Nu, Np], ...
{'x', 'u', 'p'}, {'cstr_plant'});
stagecost_casadi = @(x, u, xsp, usp) stagecost(x, u, xsp, usp, pars);
termcost_casadi = @(x, xsp) termcost(x, xsp, pars);
l = mpc.getCasadiFunc(stagecost_casadi, [Nx, Nu, Nx, Nu], {'x', 'u', 'xsp', 'usp'}, {'l'});
Vf = mpc.getCasadiFunc(termcost_casadi, [Nx, Nx], {'x', 'xsp'}, {'Vf'});
% Make NMPC controller.
Ncontrol = struct('x', Nx, 'u', Nu, 't', N);
values.xsp = repmat(ze, 1, N + 1);
values.usp = repmat(usp, 1, N);
guess.x = [linspace(x0(1), ze(1), N + 1); linspace(x0(2), ze(2), N + 1); ...
linspace(x0(3), x0(3) + N*Delta, N + 1)];
guess.u = linspace(uprev, usp, N);
Ncontrol.c = pars.colloc;
% Avoid using terminal constraint (use high terminal penalty instead so
% that ipopt can solve the problem).
solverNMPC = mpc.nmpc('f', ode_casadi, 'N', Ncontrol, 'Delta', Delta, ...
'verbosity', 0, 'l', l, 'Vf', Vf, ...
'x0', x0, 'guess', guess, 'par', values, ...
'lb', struct('x', xlb*ones(1, N + 1), 'u', ulb*ones(Nu, N)), ...
'ub', struct('x', xub*ones(1, N + 1), 'u', uub*ones(Nu, N)));
% Closed-loop simulation.
Nsim = tfinal/Delta + N;
x = NaN(Nx, Nsim + 1);
x(:, 1) = x0;
u = NaN(Nu, Nsim);
for i = 1:Nsim
% Set initial condition and solve.
solverNMPC.fixvar('x', 1, x(:, i));
solverNMPC.solve();
% Print status.
fprintf('%d: %s\n', i, solverNMPC.status);
if ~isequal(solverNMPC.status, 'Solve_Succeeded')
warning('Failed at time %d!', i);
break
end%if
% Inject control and simulate plant.
u(:,i) = solverNMPC.var.u(:,1);
x(:,i + 1) = full(cstrsim(x(:,i), u(:,i), 0));
solverNMPC.saveguess();
end%for
% Save data.
t = Delta*(0:Nsim)';
reference_trajectory = struct();
reference_trajectory.x = x;
reference_trajectory.u = u;
reference_trajectory.t = t;
end%function
function l = stagecost(x, u, xsp, usp, pars)
% Quadratic stage cost.
Q = pars.Q;
R = pars.R;
xerr = x(1:pars.Nx) - xsp(1:pars.Nx);
uerr = u - usp;
l = Q*(xerr'*xerr) + R*(uerr'*uerr);
end%function
function Vf = termcost(x, xsp, pars)
% Quadratic terminal penalty.
Qf = pars.Qf;
xerr = x(1:pars.Nx) - xsp(1:pars.Nx);
Vf = Qf*(xerr'*xerr);
end%function
|
tubempc.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119 | function result = tubempc(pars)
% result = tubempc(pars)
%
% Runs tube-based NMPC for CSTR example in Chapter 3.
%
% Note: In this script, "p" denotes the disturbance ("w" in MPC textbook).
mpc = import_mpctools();
% Read parameters.
Nu = pars.Nu;
Np = pars.Np;
Delta = pars.Delta;
uub = pars.uub;
ulb = pars.ulb;
tfinal = pars.tfinal;
N = pars.N;
plantSteps = pars.plantSteps;
% Add time state.
Nx = pars.Nx + 1;
xub = [pars.xub; Inf];
xlb = [pars.xlb; -Inf];
x0 = [pars.x0; 0];
% Get reference trajectories.
xsp = pars.reftraj.x;
usp = pars.reftraj.u;
% Make functions.
ode_model = @(x, u) pars.ode(x, u, 0, pars);
ode_plant = @(x, u, p) pars.ode(x, u, p, pars);
ode_casadi = mpc.getCasadiFunc(ode_model, [Nx, Nu], ...
{'x', 'u'}, {'cstr_model'});
cstrsim = mpc.getCasadiIntegrator(ode_plant, Delta/plantSteps, [Nx, Nu, Np], ...
{'x', 'u', 'p'}, {'cstr_plant'});
stagecost_casadi = @(x, u, xsp, usp) stagecost(x, u, xsp, usp, pars);
termcost_casadi = @(x, xsp) termcost(x, xsp, pars);
l = mpc.getCasadiFunc(stagecost_casadi, [Nx, Nu, Nx, Nu], {'x', 'u', 'xsp', 'usp'}, {'l'});
Vf = mpc.getCasadiFunc(termcost_casadi, [Nx, Nx], {'x', 'xsp'}, {'Vf'});
% Make NMPC controller.
Ncontrol = struct('x', Nx, 'u', Nu, 't', N);
values.xsp = xsp(:, 1:(N + 1));
values.usp = usp(:, 1:N);
guess.x = xsp(:, 1:(N+1));
guess.u = usp(:, 1:N);
Ncontrol.c = pars.colloc;
% Avoid using terminal constraint (use high terminal penalty instead so
% that ipopt can solve the problem).
solverNMPC = mpc.nmpc('f', ode_casadi, 'N', Ncontrol, 'Delta', Delta, ...
'verbosity', 0, 'l', l, 'Vf', Vf, ...
'x0', x0, 'guess', guess, 'par', values, ...
'lb', struct('x', xlb*ones(1, N + 1), 'u', ulb*ones(Nu, N)), ...
'ub', struct('x', xub*ones(1, N + 1), 'u', uub*ones(Nu, N)));
% Closed-loop simulation.
Nsim = tfinal/Delta;
x = NaN(Nx, Nsim + 1);
x(:, 1) = x0;
xplant = NaN(Nx, (plantSteps*Nsim + 1));
xplant(:, 1) = x0;
xplantnow = NaN(Nx, plantSteps + 1);
xplantnow(:, 1) = x0;
u = NaN(Nu, Nsim);
for i = 1:Nsim
% Set initial condition and solve.
solverNMPC.fixvar('x', 1, x(:, i));
solverNMPC.par.xsp = xsp(:, i:(i + N));
solverNMPC.par.usp = usp(:, i:(i + N - 1));
solverNMPC.solve();
% Print status.
fprintf('%d: %s\n', i, solverNMPC.status);
if ~isequal(solverNMPC.status, 'Solve_Succeeded')
warning('Failed at time %d!', i);
break
end
% Inject control and simulate plant using finer step size.
u(:,i) = solverNMPC.var.u(:,1);
for j = 1:plantSteps
xplantnow(:, j+1) = full(cstrsim(xplantnow(:, j), u(:,i), 1));
end
xplant(:, 1 + (1:plantSteps) + (i-1)*plantSteps) = xplantnow(:, 2:end);
xplantnow(:, 1) = xplantnow(:, end);
x(:,i + 1) = xplantnow(:, end);
solverNMPC.saveguess();
end
% Save data.
result = struct();
result.pars = rmfield(pars, 'ode');
result.x = x;
result.u = u;
result.t = (0:Delta:tfinal)';
result.xplant = xplant;
result.tplant = (0:(Delta/plantSteps):tfinal)';
end%function
function l = stagecost(x, u, xsp, usp, pars)
% Quadratic stage cost.
Q = pars.Q;
R = pars.R;
xerr = x(1:pars.Nx) - xsp(1:pars.Nx);
uerr = u - usp;
l = Q*(xerr'*xerr) + R*(uerr'*uerr);
end%function
function Vf = termcost(x, xsp, pars)
% Quadratic terminal penalty.
Qf = pars.Qf;
xerr = x(1:pars.Nx) - xsp(1:pars.Nx);
Vf = Qf*(xerr'*xerr);
end%function
|
cstrparam.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64 | function pars = cstrparam()
% Loads default parameters for robust MPC CSTR problem.
% Model Parameters.
pars = struct();
pars.theta = 20;
pars.k = 300;
pars.M = 5;
pars.xf = 0.3947;
pars.xc = 0.3816;
pars.alpha = 0.117;
% Simulation.
pars.tfinal = 300;
pars.N = 40;
pars.Q = 1/2;
pars.R = 1/2;
pars.Qf = 1e5;
pars.Nx = 2;
pars.Nu = 1;
pars.Np = 1;
pars.Delta = 3;
pars.xub = [2; 2];
pars.xlb = [0; 0];
pars.uub = 2;
pars.ulb = 0;
pars.colloc = 5;
pars.plantSteps = 2;
% Initial Conditions and Setpoints.
pars.x0 = [0.9831; 0.3918];
pars.ze = [0.2632; 0.6519];
pars.uprev = 0.02;
pars.usp = 455/600;
pars.uub_nominal = 2;
pars.ulb_nominal = 0.02;
pars.A = 0;
pars.w = 1;
pars.ode = @cstrode;
% Set the random number generator seed.
rand('state', 0);
end%function
function dxdt = cstrode(x, u, p, pars)
% Nonlinear ODE model for reactor.
c = x(1);
T = x(2);
t = x(3); % Augmented time state for time-varying model.
rate = pars.k*c*exp(-pars.M/T);
w = pars.A*sin(t*pars.w)*p;
dcdt = (1 - c)/(pars.theta) - rate;
dTdt = (pars.xf - T)/(pars.theta) + rate ...
- pars.alpha*u*(T - pars.xc) + w;
dtdt = 1;
dxdt = [dcdt; dTdt; dtdt];
end%function
|
resample_nominal.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 | function reftraj_resampled = resample_nominal(reftraj, Delta)
% Resample the reference (central path) trajectory.
t = reftraj.t;
u = reftraj.u;
x = reftraj.x;
t0 = t(1);
tf = t(end);
tnew = (t0:Delta:tf)';
% Interpolate to get new reference trajectories.
unew = interp1(t(1:end-1), u', tnew, 'spline', 'extrap');
xnew = interp1(t, x', tnew, 'spline', 'extrap');
reftraj_resampled = struct();
reftraj_resampled.t = tnew;
reftraj_resampled.u = unew';
reftraj_resampled.x = xnew';
|