Figure 3.7:
Comparison of standard and tube-based MPC with an aggressive model predictive controller.
Code for Figure 3.7
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 | % Robust control of an exothermic reaction.
% Load parameters for problem.
pars = cstrparam();
pars.Q = 1/2;
pars.R = 5;
% Run nominal MPC to generate reference trajectory for tube-based MPC.
pars.reftraj = nominalmpc(pars);
% Create disturbances for the scenarios.
Nsamples = 10; % Note that the figure in the text uses 100 samples.
pars.A_rand = 0.001*rand(Nsamples, 1);
pars.w_rand = rand(Nsamples, 1);
% Different cost function for ancillary.
tubepars = pars;
tubepars.Q = 50;
tubepars.R = 1/20;
% Run both standard and tube-based MPC 100 times to generate figure.
data = run_standardvstube(pars, tubepars, Nsamples);
|
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
|
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
|
run_standardvstube.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 | function data = run_standardvstube(standard_pars, tube_pars, Nsamples)
% Runs standard and tube MPC for problem and make resulting plot of
% trajectories.
standardMPCdata = struct();
tubeMPCdata = struct();
for i = 1:Nsamples
fprintf('*** Sample %d of %d.\n', i, Nsamples);
% Disturbance for this realization.
standard_pars.A = standard_pars.A_rand(i);
standard_pars.w = standard_pars.w_rand(i);
tube_pars.A = tube_pars.A_rand(i);
tube_pars.w = tube_pars.w_rand(i);
% Run standard MPC.
result = standardmpc(standard_pars);
standardMPCdata.x1(:,i) = (result.x(1,:))';
standardMPCdata.x2(:,i) = (result.x(2,:))';
standardMPCdata.x1plant(:,i) = (result.xplant(1,:))';
standardMPCdata.x2plant(:,i) = (result.xplant(2,:))';
standardMPCdata.u(:,i) = result.u';
standardMPCdata.pars = result.pars;
% Run tube-based MPC.
result = tubempc(tube_pars);
tubeMPCdata.x1(:,i) = (result.x(1,:))';
tubeMPCdata.x2(:,i) = (result.x(2,:))';
tubeMPCdata.x1plant(:,i) = (result.xplant(1,:))';
tubeMPCdata.x2plant(:,i) = (result.xplant(2,:))';
tubeMPCdata.u(:,i) = result.u';
tubeMPCdata.pars = result.pars;
end
% Make plot.
standardMPCdata.t = result.t;
standardMPCdata.tplant = result.tplant;
tubeMPCdata.t = result.t;
tubeMPCdata.tplant = result.tplant;
figure();
subplot(3,2,1)
plot(standardMPCdata.tplant, standardMPCdata.x1plant, 'k-')
ylabel('concentration')
title('standard MPC')
subplot(3,2,3)
plot(standardMPCdata.tplant, standardMPCdata.x2plant, 'k-')
ylabel('temperature')
subplot(3,2,5)
plot(standardMPCdata.t(1:end-1), standardMPCdata.u, 'k-')
ylabel('coolant flow')
xlabel('time')
subplot(3,2,2)
plot(standardMPCdata.tplant, tubeMPCdata.x1plant, 'k-')
ylabel('concentration')
title('tube-based MPC')
subplot(3,2,4)
plot(standardMPCdata.tplant, tubeMPCdata.x2plant, 'k-')
ylabel('temperature')
subplot(3,2,6)
plot(standardMPCdata.t(1:end-1), tubeMPCdata.u, 'k-')
ylabel('coolant flow')
xlabel('time')
% Output Results.
data = struct();
data.standard = standardMPCdata;
data.tube = tubeMPCdata;
end%function
|
standardmpc.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 | function result = standardmpc(pars)
% result = standardmpc(pars)
%
% Runs standard 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;
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];
% 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 = 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;
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.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 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%for
% 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
|
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
|