Skip to content

Commit bd928b4

Browse files
authored
Merge pull request #31 from JuliaControl/new_jump_nlp_syntax
New jump nlp syntax, merge into main?
2 parents 58fd28a + 383f165 commit bd928b4

8 files changed

+82
-66
lines changed

Project.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
name = "ModelPredictiveControl"
22
uuid = "61f9bdb8-6ae4-484a-811f-bbf86720c31c"
33
authors = ["Francis Gagnon"]
4-
version = "0.21.3"
4+
version = "0.22.0"
55

66
[deps]
77
ControlSystemsBase = "aaaaaaaa-a6ca-5380-bf3e-84a91bcd477e"

src/ModelPredictiveControl.jl

+1-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import ControlSystemsBase: iscontinuous, isdiscrete, sminreal, minreal, c2d, d2c
1616

1717
import JuMP
1818
import JuMP: MOIU, MOI, GenericModel, Model, optimizer_with_attributes, register
19-
import JuMP: @variable, @constraint, @objective, @NLconstraint, @NLobjective
19+
import JuMP: @variable, @operator, @constraint, @objective
2020

2121
import PreallocationTools: DiffCache, get_tmp
2222

src/controller/construct.jl

+2-2
Original file line numberDiff line numberDiff line change
@@ -256,7 +256,7 @@ function setconstraint!(
256256
JuMP.delete(optim, optim[:linconstraint])
257257
JuMP.unregister(optim, :linconstraint)
258258
@constraint(optim, linconstraint, A*ΔŨvar .≤ b)
259-
setnonlincon!(mpc, model)
259+
setnonlincon!(mpc, model, optim)
260260
else
261261
i_b, i_g = init_matconstraint_mpc(model,
262262
i_Umin, i_Umax, i_ΔŨmin, i_ΔŨmax,
@@ -320,7 +320,7 @@ function init_matconstraint_mpc(::SimModel{NT},
320320
end
321321

322322
"By default, there is no nonlinear constraint, thus do nothing."
323-
setnonlincon!(::PredictiveController, ::SimModel) = nothing
323+
setnonlincon!(::PredictiveController, ::SimModel, ::JuMP.GenericModel) = nothing
324324

325325
"""
326326
default_Hp(model::LinModel)

src/controller/nonlinmpc.jl

+23-21
Original file line numberDiff line numberDiff line change
@@ -313,28 +313,28 @@ function init_optimization!(mpc::NonLinMPC, model::SimModel, optim)
313313
end
314314
end
315315
Jfunc, gfunc = get_optim_functions(mpc, mpc.optim)
316-
register(optim, :Jfunc, nΔŨ, Jfunc, autodiff=true)
317-
@NLobjective(optim, Min, Jfunc(ΔŨvar...))
316+
@operator(optim, J, nΔŨ, Jfunc)
317+
@objective(optim, Min, J(ΔŨvar...))
318318
ny, nx̂, Hp = model.ny, mpc.estim.nx̂, mpc.Hp
319319
if length(con.i_g) 0
320320
for i in eachindex(con.Y0min)
321-
sym = Symbol("g_Y0min_$i")
322-
register(optim, sym, nΔŨ, gfunc[i], autodiff=true)
321+
name = Symbol("g_Y0min_$i")
322+
optim[name] = JuMP.add_nonlinear_operator(optim, nΔŨ, gfunc[i]; name)
323323
end
324324
i_end_Ymin = 1Hp*ny
325325
for i in eachindex(con.Y0max)
326-
sym = Symbol("g_Y0max_$i")
327-
register(optim, sym, nΔŨ, gfunc[i_end_Ymin+i], autodiff=true)
326+
name = Symbol("g_Y0max_$i")
327+
optim[name] = JuMP.add_nonlinear_operator(optim, nΔŨ, gfunc[i_end_Ymin+i]; name)
328328
end
329329
i_end_Ymax = 2Hp*ny
330330
for i in eachindex(con.x̂0min)
331-
sym = Symbol("g_x̂0min_$i")
332-
register(optim, sym, nΔŨ, gfunc[i_end_Ymax+i], autodiff=true)
331+
name = Symbol("g_x̂0min_$i")
332+
optim[name] = JuMP.add_nonlinear_operator(optim, nΔŨ, gfunc[i_end_Ymax+i]; name)
333333
end
334334
i_end_x̂min = 2Hp*ny + nx̂
335335
for i in eachindex(con.x̂0max)
336-
sym = Symbol("g_x̂0max_$i")
337-
register(optim, sym, nΔŨ, gfunc[i_end_x̂min+i], autodiff=true)
336+
name = Symbol("g_x̂0max_$i")
337+
optim[name] = JuMP.add_nonlinear_operator(optim, nΔŨ, gfunc[i_end_x̂min+i]; name)
338338
end
339339
end
340340
return nothing
@@ -397,26 +397,28 @@ function get_optim_functions(mpc::NonLinMPC, ::JuMP.GenericModel{JNT}) where JNT
397397
end
398398

399399
"Set the nonlinear constraints on the output predictions `Ŷ` and terminal states `x̂end`."
400-
function setnonlincon!(mpc::NonLinMPC, ::NonLinModel)
401-
optim = mpc.optim
400+
function setnonlincon!(
401+
mpc::NonLinMPC, ::NonLinModel, optim::JuMP.GenericModel{JNT}
402+
) where JNT<:Real
402403
ΔŨvar = optim[:ΔŨvar]
403404
con = mpc.con
404-
map(con -> JuMP.delete(optim, con), JuMP.all_nonlinear_constraints(optim))
405+
nonlin_constraints = JuMP.all_constraints(optim, JuMP.NonlinearExpr, MOI.LessThan{JNT})
406+
map(con_ref -> JuMP.delete(optim, con_ref), nonlin_constraints)
405407
for i in findall(.!isinf.(con.Y0min))
406-
f_sym = Symbol("g_Y0min_$(i)")
407-
JuMP.add_nonlinear_constraint(optim, :($(f_sym)($(ΔŨvar...)) <= 0))
408+
gfunc_i = optim[Symbol("g_Y0min_$(i)")]
409+
@constraint(optim, gfunc_i(ΔŨvar...) <= 0)
408410
end
409411
for i in findall(.!isinf.(con.Y0max))
410-
f_sym = Symbol("g_Y0max_$(i)")
411-
JuMP.add_nonlinear_constraint(optim, :($(f_sym)($(ΔŨvar...)) <= 0))
412+
gfunc_i = optim[Symbol("g_Y0max_$(i)")]
413+
@constraint(optim, gfunc_i(ΔŨvar...) <= 0)
412414
end
413415
for i in findall(.!isinf.(con.x̂0min))
414-
f_sym = Symbol("g_x̂0min_$(i)")
415-
JuMP.add_nonlinear_constraint(optim, :($(f_sym)($(ΔŨvar...)) <= 0))
416+
gfunc_i = optim[Symbol("g_x̂0min_$(i)")]
417+
@constraint(optim, gfunc_i(ΔŨvar...) <= 0)
416418
end
417419
for i in findall(.!isinf.(con.x̂0max))
418-
f_sym = Symbol("g_x̂0max_$(i)")
419-
JuMP.add_nonlinear_constraint(optim, :($(f_sym)($(ΔŨvar...)) <= 0))
420+
gfunc_i = optim[Symbol("g_x̂0max_$(i)")]
421+
@constraint(optim, gfunc_i(ΔŨvar...) <= 0)
420422
end
421423
return nothing
422424
end

src/estimator/mhe/construct.jl

+33-22
Original file line numberDiff line numberDiff line change
@@ -536,7 +536,7 @@ function setconstraint!(
536536
JuMP.delete(optim, optim[:linconstraint])
537537
JuMP.unregister(optim, :linconstraint)
538538
@constraint(optim, linconstraint, A*Z̃var .≤ b)
539-
setnonlincon!(estim, model)
539+
setnonlincon!(estim, model, optim)
540540
else
541541
i_b, i_g = init_matconstraint_mhe(model,
542542
i_x̃min, i_x̃max, i_X̂min, i_X̂max, i_Ŵmin, i_Ŵmax, i_V̂min, i_V̂max
@@ -598,28 +598,31 @@ function init_matconstraint_mhe(::SimModel{NT},
598598
end
599599

600600
"By default, no nonlinear constraints in the MHE, thus return nothing."
601-
setnonlincon!(::MovingHorizonEstimator, ::SimModel) = nothing
601+
setnonlincon!(::MovingHorizonEstimator, ::SimModel, ::JuMP.GenericModel) = nothing
602602

603603
"Set the nonlinear constraints on the output predictions `Ŷ` and terminal states `x̂end`."
604-
function setnonlincon!(estim::MovingHorizonEstimator, ::NonLinModel)
604+
function setnonlincon!(
605+
estim::MovingHorizonEstimator, ::NonLinModel, optim::JuMP.GenericModel{JNT}
606+
) where JNT<:Real
605607
optim, con = estim.optim, estim.con
606608
Z̃var = optim[:Z̃var]
607-
map(con -> JuMP.delete(optim, con), JuMP.all_nonlinear_constraints(optim))
609+
nonlin_constraints = JuMP.all_constraints(optim, JuMP.NonlinearExpr, MOI.LessThan{JNT})
610+
map(con_ref -> JuMP.delete(optim, con_ref), nonlin_constraints)
608611
for i in findall(.!isinf.(con.X̂0min))
609-
f_sym = Symbol("g_X̂0min_$(i)")
610-
JuMP.add_nonlinear_constraint(optim, :($(f_sym)($(Z̃var...)) <= 0))
612+
gfunc_i = optim[Symbol("g_X̂0min_$(i)")]
613+
@constraint(optim, gfunc_i(Z̃var...) <= 0)
611614
end
612615
for i in findall(.!isinf.(con.X̂0max))
613-
f_sym = Symbol("g_X̂0max_$(i)")
614-
JuMP.add_nonlinear_constraint(optim, :($(f_sym)($(Z̃var...)) <= 0))
616+
gfunc_i = optim[Symbol("g_X̂0max_$(i)")]
617+
@constraint(optim, gfunc_i(Z̃var...) <= 0)
615618
end
616619
for i in findall(.!isinf.(con.V̂min))
617-
f_sym = Symbol("g_V̂min_$(i)")
618-
JuMP.add_nonlinear_constraint(optim, :($(f_sym)($(Z̃var...)) <= 0))
620+
gfunc_i = optim[Symbol("g_V̂min_$(i)")]
621+
JuMP.@constraint(optim, gfunc_i(Z̃var...) <= 0)
619622
end
620623
for i in findall(.!isinf.(con.V̂max))
621-
f_sym = Symbol("g_V̂max_$(i)")
622-
JuMP.add_nonlinear_constraint(optim, :($(f_sym)($(Z̃var...)) <= 0))
624+
gfunc_i = optim[Symbol("g_V̂max_$(i)")]
625+
JuMP.@constraint(optim, gfunc_i(Z̃var...) <= 0)
623626
end
624627
return nothing
625628
end
@@ -1073,28 +1076,36 @@ function init_optimization!(
10731076
end
10741077
end
10751078
Jfunc, gfunc = get_optim_functions(estim, optim)
1076-
register(optim, :Jfunc, nZ̃, Jfunc, autodiff=true)
1077-
@NLobjective(optim, Min, Jfunc(Z̃var...))
1079+
@operator(optim, J, nZ̃, Jfunc)
1080+
@objective(optim, Min, J(Z̃var...))
10781081
nV̂, nX̂ = estim.He*estim.nym, estim.He*estim.nx̂
10791082
if length(con.i_g) 0
10801083
for i in eachindex(con.X̂0min)
1081-
sym = Symbol("g_X̂0min_$i")
1082-
register(optim, sym, nZ̃, gfunc[i], autodiff=true)
1084+
name = Symbol("g_X̂0min_$i")
1085+
optim[name] = JuMP.add_nonlinear_operator(
1086+
optim, nZ̃, gfunc[i]; name
1087+
)
10831088
end
10841089
i_end_X̂min = nX̂
10851090
for i in eachindex(con.X̂0max)
1086-
sym = Symbol("g_X̂0max_$i")
1087-
register(optim, sym, nZ̃, gfunc[i_end_X̂min+i], autodiff=true)
1091+
name = Symbol("g_X̂0max_$i")
1092+
optim[name] = JuMP.add_nonlinear_operator(
1093+
optim, nZ̃, gfunc[i_end_X̂min + i]; name
1094+
)
10881095
end
10891096
i_end_X̂max = 2*nX̂
10901097
for i in eachindex(con.V̂min)
1091-
sym = Symbol("g_V̂min_$i")
1092-
register(optim, sym, nZ̃, gfunc[i_end_X̂max+i], autodiff=true)
1098+
name = Symbol("g_V̂min_$i")
1099+
optim[name] = JuMP.add_nonlinear_operator(
1100+
optim, nZ̃, gfunc[i_end_X̂max + i]; name
1101+
)
10931102
end
10941103
i_end_V̂min = 2*nX̂ + nV̂
10951104
for i in eachindex(con.V̂max)
1096-
sym = Symbol("g_V̂max_$i")
1097-
register(optim, sym, nZ̃, gfunc[i_end_V̂min+i], autodiff=true)
1105+
name = Symbol("g_V̂max_$i")
1106+
optim[name] = JuMP.add_nonlinear_operator(
1107+
optim, nZ̃, gfunc[i_end_V̂min + i]; name
1108+
)
10981109
end
10991110
end
11001111
return nothing

src/estimator/mhe/execute.jl

+4-1
Original file line numberDiff line numberDiff line change
@@ -564,4 +564,7 @@ function setmodel_estimator!(
564564
end
565565

566566
"Called by plots recipes for the estimated states constraints."
567-
getX̂con(estim::MovingHorizonEstimator, _ ) = estim.con.X̂0min+estim.X̂op, estim.con.X̂0max+estim.X̂op
567+
getX̂con(estim::MovingHorizonEstimator, _ ) = estim.con.X̂0min+estim.X̂op, estim.con.X̂0max+estim.X̂op
568+
569+
"No nonlinear constraints if `model` is a [`LinModel`](@ref), return `g` unchanged."
570+
con_nonlinprog!(g, ::MovingHorizonEstimator, ::LinModel, _ , _ , _ ) = g

test/test_predictive_control.jl

+9-6
Original file line numberDiff line numberDiff line change
@@ -439,8 +439,10 @@ end
439439
nmpc7 = NonLinMPC(nonlinmodel, Hp=15, Ewt=1e-3, JE=(UE,ŶE,D̂E) -> UE.*ŶE.*D̂E)
440440
@test nmpc7.E == 1e-3
441441
@test nmpc7.JE([1,2],[3,4],[4,6]) == [12, 48]
442-
nmpc8 = NonLinMPC(nonlinmodel, Hp=15, optim=JuMP.Model(OSQP.MathOptInterfaceOSQP.Optimizer))
443-
@test solver_name(nmpc8.optim) == "OSQP"
442+
optim = JuMP.Model(optimizer_with_attributes(Ipopt.Optimizer, "nlp_scaling_max_gradient"=>1.0))
443+
nmpc8 = NonLinMPC(nonlinmodel, Hp=15, optim=optim)
444+
@test solver_name(nmpc8.optim) == "Ipopt"
445+
@test get_attribute(nmpc8.optim, "nlp_scaling_max_gradient") == 1.0
444446
im = InternalModel(nonlinmodel)
445447
nmpc9 = NonLinMPC(im, Hp=15)
446448
@test isa(nmpc9.estim, InternalModel)
@@ -504,11 +506,12 @@ end
504506
nmpc4 = NonLinMPC(nonlinmodel, Hp=15, Mwt=[0], Nwt=[0], Lwt=[1])
505507
u = moveinput!(nmpc4, [0], d, R̂u=fill(12, nmpc4.Hp))
506508
@test u [12] atol=5e-2
507-
nmpc5 = setconstraint!(NonLinMPC(nonlinmodel, Hp=15, Cwt=Inf), ymax=[1])
508-
g_Ymax_end = nmpc5.optim.nlp_model.operators.registered_multivariate_operators[end].f
509-
@test g_Ymax_end((1.0, 1.0)) 0.0 # test gfunc_i(i,::NTuple{N, Float64})
509+
nmpc5 = setconstraint!(NonLinMPC(nonlinmodel, Hp=15, Cwt=Inf), ymin=[1])
510+
g_Y0min_end = nmpc5.optim[:g_Y0min_15].func
511+
# test gfunc_i(i,::NTuple{N, Float64}):
512+
@test g_Y0min_end(20.0, 10.0) 0.0
510513
# test gfunc_i(i,::NTuple{N, ForwardDiff.Dual}) :
511-
@test ForwardDiff.gradient(g_Ymax_end, [1.0, 1.0]) [0.0, 0.0]
514+
@test ForwardDiff.gradient(vec->g_Y0min_end(vec...), [20.0, 10.0]) [-5, -5] atol=1e-3
512515
linmodel3 = LinModel{Float32}(0.5*ones(1,1), ones(1,1), ones(1,1), zeros(1,0), zeros(1,0), 1.0)
513516
nmpc6 = NonLinMPC(linmodel3, Hp=10)
514517
@test moveinput!(nmpc6, [0]) [0.0]

test/test_state_estim.jl

+9-12
Original file line numberDiff line numberDiff line change
@@ -666,14 +666,12 @@ end
666666
@test mhe9. I(6)
667667
@test mhe9. I(2)
668668

669-
optim = Model(Ipopt.Optimizer)
669+
optim = JuMP.Model(optimizer_with_attributes(Ipopt.Optimizer, "nlp_scaling_max_gradient"=>1.0))
670670
covestim = ExtendedKalmanFilter(nonlinmodel, 1:2, 0, [1, 1], I_6, I_6, I_2)
671671
mhe10 = MovingHorizonEstimator(
672672
nonlinmodel, 5, 1:2, 0, [1, 1], I_6, I_6, I_2, Inf, optim, covestim
673673
)
674-
675-
mhe11 = MovingHorizonEstimator(nonlinmodel, He=5, optim=Model(OSQP.Optimizer))
676-
@test solver_name(mhe11.optim) == "OSQP"
674+
@test solver_name(mhe10.optim) == "Ipopt"
677675

678676
mhe12 = MovingHorizonEstimator(nonlinmodel, He=5, Cwt=1e3)
679677
@test size(mhe12.Ẽ, 2) == 6*mhe12.nx̂ + 1
@@ -735,15 +733,14 @@ end
735733
= updatestate!(mhe3, [0], [0])
736734
@test [0, 0] atol=1e-3
737735
@test isa(x̂, Vector{Float32})
736+
737+
mhe4 = setconstraint!(MovingHorizonEstimator(nonlinmodel, He=1, nint_ym=0), v̂max=[50,50])
738+
g_V̂max_end = mhe4.optim[:g_V̂max_2].func
739+
# test gfunc_i(i,::NTuple{N, Float64})
740+
@test g_V̂max_end(0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0) 0.0
741+
# test gfunc_i(i,::NTuple{N, ForwardDiff.Dual}) :
742+
@test ForwardDiff.gradient(vec->g_V̂max_end(vec...), zeros(8)) zeros(8)
738743

739-
mhe4 = setconstraint!(MovingHorizonEstimator(nonlinmodel, He=1, nint_ym=0), x̂max=[50,50,50,50])
740-
g_X̂max_end = mhe4.optim.nlp_model.operators.registered_multivariate_operators[end].f
741-
# test gfunc_i(i,::NTuple{N, Float64}):
742-
@test g_X̂max_end(
743-
(1.0, 1.0, 1.0, 1.0, 0.0, 0.0, 0.0, 0.0)) 0.0
744-
# test gfunc_i(i,::NTuple{N, ForwardDiff.Dual}):
745-
@test ForwardDiff.gradient(
746-
g_X̂max_end, [1.0, 1.0, 1.0, 1.0, 0.0, 0.0, 0.0, 0.0]) [0, 0, 0, 0, 0, 0, 0, 0]
747744
= diagm([1/4, 1/4, 1/4, 1/4].^2)
748745
= diagm([1, 1].^2)
749746
optim = Model(Ipopt.Optimizer)

0 commit comments

Comments
 (0)