Skip to content

Commit b58cf8e

Browse files
authored
Merge pull request #378 from JuliaControl/print_optim_dim
added: pretty-print optimization problem dimensions for MPC and MHE
2 parents 4b02782 + 7b9d014 commit b58cf8e

10 files changed

Lines changed: 155 additions & 85 deletions

File tree

Project.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
name = "ModelPredictiveControl"
22
uuid = "61f9bdb8-6ae4-484a-811f-bbf86720c31c"
3-
version = "2.4.3"
3+
version = "2.4.4"
44
authors = ["Francis Gagnon"]
55

66
[deps]

src/controller/construct.jl

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -248,17 +248,20 @@ julia> mpc = setconstraint!(mpc, umin=[0], umax=[100], Δumin=[-10], Δumax=[+10
248248
LinMPC controller with a sample time Ts = 4.0 s:
249249
├ estimator: SteadyKalmanFilter
250250
├ model: LinModel
251-
├ optimizer: OSQP
251+
├ optimizer: OSQP
252252
├ transcription: SingleShooting
253253
└ dimensions:
254-
├ 10 prediction steps Hp
255-
├ 2 control steps Hc
256-
├ 1 slack variable ϵ (control constraints)
257-
├ 1 manipulated inputs u (0 integrating states)
258-
├ 2 estimated states x̂
259-
├ 1 measured outputs ym (1 integrating states)
260-
├ 0 unmeasured outputs yu
261-
└ 0 measured disturbances d
254+
│ ├ 10 prediction steps Hp
255+
│ ├ 2 control steps Hc
256+
│ ├ 1 manipulated inputs u (0 integrating states)
257+
│ ├ 2 estimated states x̂
258+
│ ├ 1 measured outputs ym (1 integrating states)
259+
│ ├ 0 unmeasured outputs yu
260+
│ └ 0 measured disturbances d
261+
└ optimization:
262+
├ 3 decision variables Z̃ (1 slack variable)
263+
├ 25 linear inequality constraints A (0 custom)
264+
└ 0 linear equality constraints Aeq
262265
```
263266
264267
# Extended Help

src/controller/explicitmpc.jl

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,8 @@ ExplicitMPC controller with a sample time Ts = 4.0 s:
122122
├ estimator: SteadyKalmanFilter
123123
├ model: LinModel
124124
└ dimensions:
125+
├ 30 prediction steps Hp
126+
├ 1 control steps Hc
125127
├ 1 manipulated inputs u (0 integrating states)
126128
├ 4 estimated states x̂
127129
├ 2 measured outputs ym (2 integrating states)
@@ -188,6 +190,8 @@ function Base.show(io::IO, mpc::ExplicitMPC)
188190
println(io, "├ estimator: $(nameof(typeof(mpc.estim)))")
189191
println(io, "├ model: $(nameof(typeof(model)))")
190192
println(io, "└ dimensions:")
193+
println(io, "$(lpad(Hp, n)) prediction steps Hp")
194+
println(io, "$(lpad(Hc, n)) control steps Hc")
191195
print_estim_dim(io, mpc.estim, n)
192196
end
193197

src/controller/linmpc.jl

Lines changed: 35 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -180,17 +180,20 @@ julia> mpc = LinMPC(model, Mwt=[0, 1], Nwt=[0.5], Hp=30, Hc=1)
180180
LinMPC controller with a sample time Ts = 4.0 s:
181181
├ estimator: SteadyKalmanFilter
182182
├ model: LinModel
183-
├ optimizer: OSQP
183+
├ optimizer: OSQP
184184
├ transcription: SingleShooting
185185
└ dimensions:
186-
├ 30 prediction steps Hp
187-
├ 1 control steps Hc
188-
├ 1 slack variable ϵ (control constraints)
189-
├ 1 manipulated inputs u (0 integrating states)
190-
├ 4 estimated states x̂
191-
├ 2 measured outputs ym (2 integrating states)
192-
├ 0 unmeasured outputs yu
193-
└ 0 measured disturbances d
186+
│ ├ 30 prediction steps Hp
187+
│ ├ 1 control steps Hc
188+
│ ├ 1 manipulated inputs u (0 integrating states)
189+
│ ├ 4 estimated states x̂
190+
│ ├ 2 measured outputs ym (2 integrating states)
191+
│ ├ 0 unmeasured outputs yu
192+
│ └ 0 measured disturbances d
193+
└ optimization:
194+
├ 2 decision variables Z̃ (1 slack variable)
195+
├ 1 linear inequality constraints A (0 custom)
196+
└ 0 linear equality constraints Aeq
194197
```
195198
196199
# Extended Help
@@ -267,17 +270,20 @@ julia> mpc = LinMPC(estim, Mwt=[0, 1], Nwt=[0.5], Hp=30, Hc=1)
267270
LinMPC controller with a sample time Ts = 4.0 s:
268271
├ estimator: KalmanFilter
269272
├ model: LinModel
270-
├ optimizer: OSQP
273+
├ optimizer: OSQP
271274
├ transcription: SingleShooting
272275
└ dimensions:
273-
├ 30 prediction steps Hp
274-
├ 1 control steps Hc
275-
├ 1 slack variable ϵ (control constraints)
276-
├ 1 manipulated inputs u (0 integrating states)
277-
├ 3 estimated states x̂
278-
├ 1 measured outputs ym (1 integrating states)
279-
├ 1 unmeasured outputs yu
280-
└ 0 measured disturbances d
276+
│ ├ 30 prediction steps Hp
277+
│ ├ 1 control steps Hc
278+
│ ├ 1 manipulated inputs u (0 integrating states)
279+
│ ├ 3 estimated states x̂
280+
│ ├ 1 measured outputs ym (1 integrating states)
281+
│ ├ 1 unmeasured outputs yu
282+
│ └ 0 measured disturbances d
283+
└ optimization:
284+
├ 2 decision variables Z̃ (1 slack variable)
285+
├ 1 linear inequality constraints A (0 custom)
286+
└ 0 linear equality constraints Aeq
281287
```
282288
"""
283289
function LinMPC(
@@ -331,4 +337,15 @@ function init_optimization!(mpc::LinMPC, model::LinModel, optim::JuMP.GenericMod
331337
@constraint(optim, linconstrainteq, Aeq*Z̃var .== beq)
332338
set_objective_hessian!(mpc, model, Z̃var)
333339
return nothing
340+
end
341+
342+
"Print the decision variable and linear constraint dimensions for `LinMPC`."
343+
function print_optim_dim(io::IO, mpc::LinMPC)
344+
nZ̃, nϵ = length(mpc.Z̃), mpc.
345+
nA, nW, nAeq = sum(mpc.con.i_b) , mpc.con.nw*(mpc.Hp + 1), size(mpc.con.Aeq, 1)
346+
m = maximum(ndigits.((nZ̃, nA, nAeq))) + 1
347+
println(io, " └ optimization:")
348+
println(io, "$(lpad(nZ̃, m)) decision variables Z̃ ($nϵ slack variable)")
349+
println(io, "$(lpad(nA, m)) linear inequality constraints A ($nW custom)")
350+
print(io, "$(lpad(nAeq, m)) linear equality constraints Aeq")
334351
end

src/controller/nonlinmpc.jl

Lines changed: 40 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -253,14 +253,19 @@ NonLinMPC controller with a sample time Ts = 10.0 s:
253253
├ jacobian: AutoSparse (AutoForwardDiff, TracerSparsityDetector, GreedyColoringAlgorithm)
254254
├ hessian: nothing
255255
└ dimensions:
256-
├ 20 prediction steps Hp
257-
├ 10 control steps Hc
258-
├ 1 slack variable ϵ (control constraints)
259-
├ 1 manipulated inputs u (0 integrating states)
260-
├ 2 estimated states x̂
261-
├ 1 measured outputs ym (1 integrating states)
262-
├ 0 unmeasured outputs yu
263-
└ 0 measured disturbances d
256+
│ ├ 20 prediction steps Hp
257+
│ ├ 10 control steps Hc
258+
│ ├ 1 manipulated inputs u (0 integrating states)
259+
│ ├ 2 estimated states x̂
260+
│ ├ 1 measured outputs ym (1 integrating states)
261+
│ ├ 0 unmeasured outputs yu
262+
│ └ 0 measured disturbances d
263+
└ optimization:
264+
├ 51 decision variables Z̃ (1 slack variable)
265+
├ 1 linear inequality constraints A (0 custom)
266+
├ 20 linear equality constraints Aeq
267+
├ 0 nonlinear inequality constraints g (0 custom)
268+
└ 20 nonlinear equality constraints geq
264269
```
265270
266271
# Extended Help
@@ -396,14 +401,19 @@ NonLinMPC controller with a sample time Ts = 10.0 s:
396401
├ jacobian: AutoForwardDiff
397402
├ hessian: nothing
398403
└ dimensions:
399-
├ 20 prediction steps Hp
400-
├ 2 control steps Hc
401-
├ 1 slack variable ϵ (control constraints)
402-
├ 1 manipulated inputs u (0 integrating states)
403-
├ 2 estimated states x̂
404-
├ 1 measured outputs ym (1 integrating states)
405-
├ 0 unmeasured outputs yu
406-
└ 0 measured disturbances d
404+
│ ├ 20 prediction steps Hp
405+
│ ├ 2 control steps Hc
406+
│ ├ 1 manipulated inputs u (0 integrating states)
407+
│ ├ 2 estimated states x̂
408+
│ ├ 1 measured outputs ym (1 integrating states)
409+
│ ├ 0 unmeasured outputs yu
410+
│ └ 0 measured disturbances d
411+
└ optimization:
412+
├ 3 decision variables Z̃ (1 slack variable)
413+
├ 1 linear inequality constraints A (0 custom)
414+
├ 0 linear equality constraints Aeq
415+
├ 0 nonlinear inequality constraints g (0 custom)
416+
└ 0 nonlinear equality constraints geq
407417
```
408418
"""
409419
function NonLinMPC(
@@ -1161,3 +1171,17 @@ function print_backends(io::IO, mpc::NonLinMPC)
11611171
println(io, "├ jacobian: $(backend_str(mpc.jacobian))")
11621172
println(io, "├ hessian: $(backend_str(mpc.hessian))")
11631173
end
1174+
1175+
"Print the decision variable, linear and nonlinear constraint dimensions for `NonLinMPC`."
1176+
function print_optim_dim(io::IO, mpc::NonLinMPC)
1177+
nZ̃, nϵ = length(mpc.Z̃), mpc.
1178+
nA, nW, nAeq = sum(mpc.con.i_b) , mpc.con.nw*(mpc.Hp + 1), size(mpc.con.Aeq, 1)
1179+
ng, nc, neq = sum(mpc.con.i_g), mpc.con.nc, mpc.con.neq
1180+
m = maximum(ndigits.((nZ̃, nA, nAeq, ng, neq))) + 1
1181+
println(io, " └ optimization:")
1182+
println(io, "$(lpad(nZ̃, m)) decision variables Z̃ ($nϵ slack variable)")
1183+
println(io, "$(lpad(nA, m)) linear inequality constraints A ($nW custom)")
1184+
println(io, "$(lpad(nAeq, m)) linear equality constraints Aeq")
1185+
println(io, "$(lpad(ng, m)) nonlinear inequality constraints g ($nc custom)")
1186+
print(io, "$(lpad(neq, m)) nonlinear equality constraints geq")
1187+
end

src/estimator/internal_model.jl

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -358,13 +358,14 @@ function evaloutput(estim::InternalModel, d)
358358
end
359359

360360
"Print InternalModel information without i/o integrators."
361-
function print_estim_dim(io::IO, estim::InternalModel, n)
361+
function print_estim_dim(io::IO, estim::InternalModel, n; firstchars=" ")
362362
nu, nd = estim.model.nu, estim.model.nd
363363
nx̂, nym, nyu = estim.nx̂, estim.nym, estim.nyu
364-
println(io, "$(lpad(nu, n)) manipulated inputs u")
365-
println(io, "$(lpad(nx̂, n)) estimated states x̂")
366-
println(io, "$(lpad(nym, n)) measured outputs ym")
367-
println(io, "$(lpad(nyu, n)) unmeasured outputs yu")
368-
print(io, "$(lpad(nd, n)) measured disturbances d")
364+
f = firstchars
365+
println(io, "$f$(lpad(nu, n)) manipulated inputs u")
366+
println(io, "$f$(lpad(nx̂, n)) estimated states x̂")
367+
println(io, "$f$(lpad(nym, n)) measured outputs ym")
368+
println(io, "$f$(lpad(nyu, n)) unmeasured outputs yu")
369+
print(io, "$f$(lpad(nd, n)) measured disturbances d")
369370
end
370371

src/estimator/mhe.jl

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -22,16 +22,26 @@ end
2222
print_backends(::IO, ::MovingHorizonEstimator, ::LinModel) = nothing
2323

2424
"Print the overall dimensions of the MHE `estim` with left padding `n`."
25-
function print_estim_dim(io::IO, estim::MovingHorizonEstimator, n)
25+
function print_estim_dim(io::IO, estim::MovingHorizonEstimator, n; firstchars=nothing)
2626
nu, nd = estim.model.nu, estim.model.nd
2727
nx̂, nym, nyu = estim.nx̂, estim.nym, estim.nyu
2828
He, nε = estim.He, estim.
2929
niu, niym = sum(estim.nint_u), sum(estim.nint_ym)
30-
println(io, "$(lpad(He, n)) estimation steps He")
31-
println(io, "$(lpad(nε, n)) slack variable ε (estimation constraints)")
32-
println(io, "$(lpad(nu, n)) manipulated inputs u ($niu integrating states)")
33-
println(io, "$(lpad(nx̂, n)) estimated states x̂")
34-
println(io, "$(lpad(nym, n)) measured outputs ym ($niym integrating states)")
35-
println(io, "$(lpad(nyu, n)) unmeasured outputs yu")
36-
print(io, "$(lpad(nd, n)) measured disturbances d")
30+
println(io, " │ ├$(lpad(He, n)) estimation steps He")
31+
println(io, " │ ├$(lpad(nu, n)) manipulated inputs u ($niu integrating states)")
32+
println(io, " │ ├$(lpad(nx̂, n)) estimated states x̂")
33+
println(io, " │ ├$(lpad(nym, n)) measured outputs ym ($niym integrating states)")
34+
println(io, " │ ├$(lpad(nyu, n)) unmeasured outputs yu")
35+
print(io, " │ └$(lpad(nd, n)) measured disturbances d")
36+
if isnothing(firstchars) # the user prints the MHE object itself, not a controller:
37+
nZ̃, nε = length(estim.Z̃), estim.
38+
nA = sum(estim.con.i_b)
39+
ng, nc = sum(estim.con.i_g), estim.con.nc
40+
m = maximum(ndigits.((nZ̃, nA, ng))) + 1
41+
println(io)
42+
println(io, " └ optimization:")
43+
println(io, "$(lpad(nZ̃, m)) decision variables Z̃ ($nε slack variable)")
44+
println(io, "$(lpad(nA, m)) linear inequality constraints A")
45+
print(io, "$(lpad(ng, m)) nonlinear inequality constraints g ($nc custom)")
46+
end
3747
end

src/estimator/mhe/construct.jl

Lines changed: 22 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -332,13 +332,16 @@ MovingHorizonEstimator estimator with a sample time Ts = 10.0 s:
332332
├ arrival covariance: UnscentedKalmanFilter
333333
├ direct: true
334334
└ dimensions:
335-
├ 5 estimation steps He
336-
├ 0 slack variable ε (estimation constraints)
337-
├ 1 manipulated inputs u (0 integrating states)
338-
├ 2 estimated states x̂
339-
├ 1 measured outputs ym (1 integrating states)
340-
├ 0 unmeasured outputs yu
341-
└ 0 measured disturbances d
335+
│ ├ 5 estimation steps He
336+
│ ├ 1 manipulated inputs u (0 integrating states)
337+
│ ├ 2 estimated states x̂
338+
│ ├ 1 measured outputs ym (1 integrating states)
339+
│ ├ 0 unmeasured outputs yu
340+
│ └ 0 measured disturbances d
341+
└ optimization:
342+
├ 12 decision variables Z̃ (0 slack variable)
343+
├ 0 linear inequality constraints A
344+
└ 0 nonlinear inequality constraints g (0 custom)
342345
```
343346
344347
# Extended Help
@@ -772,17 +775,20 @@ julia> estim = MovingHorizonEstimator(LinModel(ss(0.5,1,1,0,1)), He=3);
772775
julia> estim = setconstraint!(estim, x̂min=[-50, -50], x̂max=[50, 50])
773776
MovingHorizonEstimator estimator with a sample time Ts = 1.0 s:
774777
├ model: LinModel
775-
├ optimizer: OSQP
776-
├ arrival covariance: KalmanFilter
778+
├ optimizer: OSQP
779+
├ arrival covariance: KalmanFilter
777780
├ direct: true
778781
└ dimensions:
779-
├ 3 estimation steps He
780-
├ 0 slack variable ε (estimation constraints)
781-
├ 1 manipulated inputs u (0 integrating states)
782-
├ 2 estimated states x̂
783-
├ 1 measured outputs ym (1 integrating states)
784-
├ 0 unmeasured outputs yu
785-
└ 0 measured disturbances d
782+
│ ├ 3 estimation steps He
783+
│ ├ 1 manipulated inputs u (0 integrating states)
784+
│ ├ 2 estimated states x̂
785+
│ ├ 1 measured outputs ym (1 integrating states)
786+
│ ├ 0 unmeasured outputs yu
787+
│ └ 0 measured disturbances d
788+
└ optimization:
789+
├ 8 decision variables Z̃ (0 slack variable)
790+
├ 16 linear inequality constraints A
791+
└ 0 nonlinear inequality constraints g (0 custom)
786792
```
787793
788794
# Extended Help

src/predictive_control.jl

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ include("controller/nonlinmpc.jl")
2828

2929
function Base.show(io::IO, mpc::PredictiveController)
3030
estim, model = mpc.estim, mpc.estim.model
31-
Hp, Hc, nϵ = mpc.Hp, mpc.Hc, mpc.
31+
Hp, Hc = mpc.Hp, mpc.Hc
3232
nu, nd = model.nu, model.nd
3333
nx̂, nym, nyu = estim.nx̂, estim.nym, estim.nyu
3434
other_dims = get_other_dims(estim)
@@ -40,15 +40,19 @@ function Base.show(io::IO, mpc::PredictiveController)
4040
println(io, "├ transcription: $(nameof(typeof(mpc.transcription)))")
4141
print_backends(io, mpc)
4242
println(io, "└ dimensions:")
43-
println(io, "$(lpad(Hp, n)) prediction steps Hp")
44-
println(io, "$(lpad(Hc, n)) control steps Hc")
45-
println(io, "$(lpad(nϵ, n)) slack variable ϵ (control constraints)")
46-
print_estim_dim(io, mpc.estim, n)
43+
println(io, " │ ├$(lpad(Hp, n)) prediction steps Hp")
44+
println(io, " │ ├$(lpad(Hc, n)) control steps Hc")
45+
print_estim_dim(io, mpc.estim, n, firstchars="")
46+
println(io) # add a linebreak since `print_estim_dim` ends with a `print` (w/o ln)
47+
print_optim_dim(io, mpc)
4748
end
4849

4950
"No differentiation backends to print for a `PredictiveController` by default."
5051
print_backends(::IO, ::PredictiveController) = nothing
5152

53+
"No dimensions related to the optimization problem by default."
54+
print_optim_dim(io::IO, ::PredictiveController) = println(io, " └ optimization: nothing")
55+
5256
"Functor allowing callable `PredictiveController` object as an alias for `moveinput!`."
5357
function (mpc::PredictiveController)(
5458
ry::AbstractVector = mpc.estim.model.yop,

src/state_estim.jl

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -50,13 +50,14 @@ function print_details(io::IO, estim::StateEstimator)
5050
end
5151

5252
"Print the overall dimensions of the state estimator `estim` with left padding `n`."
53-
function print_estim_dim(io::IO, estim::StateEstimator, n)
53+
function print_estim_dim(io::IO, estim::StateEstimator, n; firstchars=" ")
5454
nu, nd = estim.model.nu, estim.model.nd
5555
nx̂, nym, nyu = estim.nx̂, estim.nym, estim.nyu
5656
niu, niym = sum(estim.nint_u), sum(estim.nint_ym)
57-
println(io, "$(lpad(nu, n)) manipulated inputs u ($niu integrating states)")
58-
println(io, "$(lpad(nx̂, n)) estimated states x̂")
59-
println(io, "$(lpad(nym, n)) measured outputs ym ($niym integrating states)")
60-
println(io, "$(lpad(nyu, n)) unmeasured outputs yu")
61-
print(io, "$(lpad(nd, n)) measured disturbances d")
57+
f = firstchars
58+
println(io, "$f$(lpad(nu, n)) manipulated inputs u ($niu integrating states)")
59+
println(io, "$f$(lpad(nx̂, n)) estimated states x̂")
60+
println(io, "$f$(lpad(nym, n)) measured outputs ym ($niym integrating states)")
61+
println(io, "$f$(lpad(nyu, n)) unmeasured outputs yu")
62+
print(io, "$f$(lpad(nd, n)) measured disturbances d")
6263
end

0 commit comments

Comments
 (0)