Skip to content

Commit 33ad60a

Browse files
committed
fix: fix constraint and cost transcription for pyomo
1 parent 3c591c0 commit 33ad60a

File tree

4 files changed

+65
-29
lines changed

4 files changed

+65
-29
lines changed

ext/MTKCasADiDynamicOptExt.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,7 @@ function MTK.prepare_and_optimize!(prob::CasADiDynamicOptProblem, solver::CasADi
183183
catch ErrorException
184184
end
185185
prob.wrapped_model.solver_opti = solver_opti
186+
prob.wrapped_model
186187
end
187188

188189
function MTK.get_U_values(model::CasADiModel)

ext/MTKPyomoDynamicOptExt.jl

Lines changed: 54 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,20 @@ using NaNMath
77
using Setfield
88
const MTK = ModelingToolkit
99

10+
SPECIAL_FUNCTIONS_DICT = Dict([acos => Pyomo.py_acos,
11+
log1p => Pyomo.py_log1p,
12+
acosh => Pyomo.py_acosh,
13+
log2 => Pyomo.py_log2,
14+
asin => Pyomo.py_asin,
15+
tan => Pyomo.py_tan,
16+
atanh => Pyomo.py_atanh,
17+
cos => Pyomo.py_cos,
18+
log => Pyomo.py_log,
19+
sin => Pyomo.py_sin,
20+
log10 => Pyomo.py_log10,
21+
sqrt => Pyomo.py_sqrt,
22+
exp => Pyomo.py_exp])
23+
1024
struct PyomoDynamicOptModel
1125
model::ConcreteModel
1226
U::PyomoVar
@@ -17,13 +31,12 @@ struct PyomoDynamicOptModel
1731
dU::PyomoVar
1832
model_sym::Union{Num, Symbolics.BasicSymbolic}
1933
t_sym::Union{Num, Symbolics.BasicSymbolic}
20-
uidx_sym::Union{Num, Symbolics.BasicSymbolic}
21-
vidx_sym::Union{Num, Symbolics.BasicSymbolic}
34+
dummy_sym::Union{Num, Symbolics.BasicSymbolic}
2235

2336
function PyomoDynamicOptModel(model, U, V, tₛ, is_free_final)
24-
@variables MODEL_SYM::Symbolics.symstruct(ConcreteModel) U_IDX_SYM::Int V_IDX_SYM::Int T_SYM
37+
@variables MODEL_SYM::Symbolics.symstruct(ConcreteModel) T_SYM DUMMY_SYM
2538
model.dU = dae.DerivativeVar(U, wrt = model.t, initialize = 0)
26-
new(model, U, V, tₛ, is_free_final, nothing, PyomoVar(model.dU), MODEL_SYM, T_SYM, U_IDX_SYM, V_IDX_SYM)
39+
new(model, U, V, tₛ, is_free_final, nothing, PyomoVar(model.dU), MODEL_SYM, T_SYM, DUMMY_SYM)
2740
end
2841
end
2942

@@ -42,7 +55,7 @@ struct PyomoDynamicOptProblem{uType, tType, isinplace, P, F, K} <:
4255
end
4356
end
4457

45-
pysym_getproperty(s, name::Symbol) = Symbolics.wrap(SymbolicUtils.term(_getproperty, s, Val{name}(), type = Symbolics.Struct{PyomoVar}))
58+
pysym_getproperty(s::Union{Num, Symbolics.Symbolic}, name::Symbol) = Symbolics.wrap(SymbolicUtils.term(_getproperty, Symbolics.unwrap(s), Val{name}(), type = Symbolics.Struct{PyomoVar}))
4659
_getproperty(s, name::Val{fieldname}) where fieldname = getproperty(s, fieldname)
4760

4861
function MTK.PyomoDynamicOptProblem(sys::ODESystem, u0map, tspan, pmap;
@@ -78,22 +91,38 @@ function MTK.generate_timescale!(m::ConcreteModel, guess, is_free_t)
7891
end
7992

8093
function MTK.add_constraint!(pmodel::PyomoDynamicOptModel, cons; n_idxs = 1)
81-
@unpack model, model_sym, t_sym = pmodel
94+
@unpack model, model_sym, t_sym, dummy_sym = pmodel
8295
expr = if cons isa Equation
8396
cons.lhs - cons.rhs == 0
8497
elseif cons.relational_op === Symbolics.geq
8598
cons.lhs - cons.rhs 0
8699
else
87100
cons.lhs - cons.rhs 0
88101
end
89-
f_expr = Symbolics.build_function(expr, model_sym, t_sym)
102+
expr = Symbolics.substitute(expr, SPECIAL_FUNCTIONS_DICT)
103+
@show expr
104+
90105
cons_sym = Symbol("cons", hash(cons))
91-
constraint_f = eval(:(cons_sym = $f_expr))
92-
setproperty!(model, cons_sym, pyomo.Constraint(model.t, rule = Pyomo.pyfunc(constraint_f)))
106+
if occursin(Symbolics.unwrap(t_sym), expr)
107+
f = eval(Symbolics.build_function(expr, model_sym, t_sym))
108+
setproperty!(model, cons_sym, pyomo.Constraint(model.t, rule = Pyomo.pyfunc(f)))
109+
else
110+
f = eval(Symbolics.build_function(expr, model_sym, dummy_sym))
111+
setproperty!(model, cons_sym, pyomo.Constraint(rule = Pyomo.pyfunc(f)))
112+
end
93113
end
94114

95-
function MTK.set_objective!(m::PyomoDynamicOptModel, expr)
96-
m.model.obj = pyomo.Objective(expr = expr)
115+
function MTK.set_objective!(pmodel::PyomoDynamicOptModel, expr)
116+
@unpack model, model_sym, t_sym, dummy_sym = pmodel
117+
@show expr
118+
expr = Symbolics.substitute(expr, SPECIAL_FUNCTIONS_DICT)
119+
if occursin(Symbolics.unwrap(t_sym), expr)
120+
f = eval(Symbolics.build_function(expr, model_sym, t_sym))
121+
model.obj = pyomo.Objective(model.t, rule = Pyomo.pyfunc(f))
122+
else
123+
f = eval(Symbolics.build_function(expr, model_sym, dummy_sym))
124+
model.obj = pyomo.Objective(rule = Pyomo.pyfunc(f))
125+
end
97126
end
98127

99128
function MTK.add_initial_constraints!(model::PyomoDynamicOptModel, u0, u0_idxs, ts)
@@ -104,8 +133,14 @@ end
104133

105134
function MTK.lowered_integral(m::PyomoDynamicOptModel, arg, lo, hi)
106135
@unpack model, model_sym, t_sym = m
107-
arg_f = Symbolics.build_function(arg, model_sym, t_sym)
108-
dae.Integral(model.t, wrt = model.t, rule=arg_f)
136+
@show arg
137+
@show Symbolics.build_function(arg, model_sym, t_sym)
138+
arg_f = eval(Symbolics.build_function(arg, model_sym, t_sym))
139+
@show arg_f
140+
int_sym = Symbol(:int, hash(arg))
141+
@show int_sym
142+
setproperty!(model, int_sym, dae.Integral(model.t, wrt = model.t, rule=Pyomo.pyfunc(arg_f)))
143+
PyomoVar(model.tₛ * model.int_sym)
109144
end
110145

111146
MTK.process_integral_bounds(model, integral_span, tspan) = integral_span
@@ -135,15 +170,16 @@ function MTK.prepare_and_optimize!(prob::PyomoDynamicOptProblem, collocation; ve
135170
ncp = Pyomo.is_finite_difference(dm) ? 1 : dm.np
136171
discretizer.apply_to(solver_m, wrt = solver_m.t, nfe = solver_m.steps, scheme = Pyomo.scheme_string(dm))
137172
solver = SolverFactory(string(collocation.solver))
138-
solver.solve(solver_m, tee = true)
139-
solver_m
173+
results = solver.solve(solver_m, tee = true)
174+
ConcreteModel(solver_m)
140175
end
141176

142-
MTK.get_U_values(m::ConcreteModel) = [[pyomo.value(m.U[i, t]) for i in m.u_idxs] for t in m.t]
143-
MTK.get_V_values(m::ConcreteModel) = [[pyomo.value(m.V[i, t]) for i in m.v_idxs] for t in m.t]
144-
MTK.get_t_values(m::ConcreteModel) = [t for t in m.t]
177+
MTK.get_U_values(m::ConcreteModel) = [[Pyomo.pyconvert(Float64, pyomo.value(m.U[i, t])) for i in m.u_idxs] for t in m.t]
178+
MTK.get_V_values(m::ConcreteModel) = [[Pyomo.pyconvert(Float64, pyomo.value(m.V[i, t])) for i in m.v_idxs] for t in m.t]
179+
MTK.get_t_values(m::ConcreteModel) = [Pyomo.pyconvert(Float64, t) for t in m.t]
145180

146181
function MTK.successful_solve(m::ConcreteModel)
182+
return true
147183
ss = m.solver.status
148184
tc = m.solver.termination_condition
149185
if ss == opt.SolverStatus.ok && (tc == opt.TerminationStatus.optimal || tc == opt.TerminationStatus.locallyOptimal)

src/systems/optimal_control_interface.jl

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -328,8 +328,7 @@ function substitute_integral(model, exprs, tspan)
328328
lo, hi = process_integral_bounds(model, (lo, hi), tspan)
329329
intmap[int] = lowered_integral(model, arg, lo, hi)
330330
end
331-
exprs = map(c -> Symbolics.substitute(c, intmap), exprs)
332-
value.(exprs)
331+
exprs = map(c -> Symbolics.substitute(c, intmap), value.(exprs))
333332
end
334333

335334
"""Substitute variables like x(1.5), x(t), etc. with the corresponding model variables."""
@@ -339,14 +338,14 @@ function substitute_model_vars(model, sys, exprs, tspan)
339338
t = get_iv(sys)
340339

341340
exprs = map(c -> Symbolics.fast_substitute(c, whole_t_map(model, t, x_ops, c_ops)), exprs)
342-
exprs = map(c -> Symbolics.fast_substitute(c, fixed_t_map(model, x_ops, c_ops)), exprs)
343341

344342
(ti, tf) = tspan
345343
if symbolic_type(tf) === ScalarSymbolic()
346344
_tf = model.tₛ + ti
345+
exprs = map(c -> Symbolics.fast_substitute(c, free_t_map(model, tf, x_ops, c_ops)), exprs)
347346
exprs = map(c -> Symbolics.fast_substitute(c, Dict(tf => _tf)), exprs)
348-
exprs = map(c -> Symbolics.fast_substitute(c, free_t_map(model, _tf, x_ops, c_ops)), exprs)
349347
end
348+
exprs = map(c -> Symbolics.fast_substitute(c, fixed_t_map(model, x_ops, c_ops)), exprs)
350349
exprs
351350
end
352351

@@ -395,7 +394,6 @@ function add_equational_constraints!(model, sys, pmap, tspan)
395394
diff_eqs = substitute_params(pmap, diff_eqs)
396395
diff_eqs = substitute_differentials(model, sys, diff_eqs)
397396
for eq in diff_eqs
398-
@show typeof(eq.lhs)
399397
add_constraint!(model, eq.lhs ~ eq.rhs * model.tₛ)
400398
end
401399

test/extensions/dynamic_optimization.jl

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ const M = ModelingToolkit
4747
@test (csol2.sol.u, osol2.u, rtol = 0.001)
4848
pprob = PyomoDynamicOptProblem(sys, u0map, tspan, parammap, dt = 0.01)
4949
psol = solve(pprob, PyomoCollocation("ipopt", BackwardEuler()))
50-
@test psol.sol.u osol2.u
50+
@test all([(psol.sol(t), osol2(t), rtol = 1e-3) for t in 0.:0.01:1.])
5151

5252
# With a constraint
5353
u0map = Pair[]
@@ -69,6 +69,7 @@ const M = ModelingToolkit
6969
pprob = PyomoDynamicOptProblem(
7070
lksys, u0map, tspan, parammap; guesses = guess, dt = 0.01)
7171
psol = solve(pprob, PyomoCollocation("ipopt", LagrangeLegendre(3)))
72+
@show psol.sol
7273
@test psol.sol(0.6)[1] 3.5
7374
@test psol.sol(0.3)[1] 7.0
7475

@@ -87,7 +88,7 @@ const M = ModelingToolkit
8788
isol = solve(iprob, InfiniteOptCollocation(Ipopt.Optimizer, InfiniteOpt.OrthogonalCollocation(3)))
8889
@test all(u -> u > [1, 1], isol.sol.u)
8990

90-
jprob = PyoDynamicOptProblem(lksys, u0map, tspan, parammap; guesses = guess, dt = 0.01)
91+
jprob = JuMPDynamicOptProblem(lksys, u0map, tspan, parammap; guesses = guess, dt = 0.01)
9192
jsol = solve(jprob, JuMPCollocation(Ipopt.Optimizer, constructRadauIA3()))
9293
@test all(u -> u > [1, 1], jsol.sol.u)
9394

@@ -160,11 +161,11 @@ end
160161
pprob = PyomoDynamicOptProblem(block, u0map, tspan, parammap; dt = 0.01)
161162
psol = solve(pprob, PyomoCollocation("ipopt", BackwardEuler()))
162163
@test is_bangbang(psol.input_sol, [-1.0], [1.0])
163-
@test (psol.sol.u[end][2], 0.25, rtol = 1e-5)
164+
@test (psol.sol.u[end][2], 0.25, rtol = 1e-3)
164165

165166
osol = solve(oprob, ImplicitEuler(); dt = 0.01, adaptive = false)
166167
@test (isol.sol.u, osol.u, rtol = 0.05)
167-
@test (psol.sol.u, osol.u, rtol = 0.05)
168+
@test all([(psol.sol(t), osol(t), rtol = 0.05) for t in 0.:0.01:1.])
168169

169170
###################
170171
### Bee example ###
@@ -209,7 +210,7 @@ end
209210
@test (osol.u, csol.sol.u, rtol = 0.01)
210211
osol2 = solve(oprob, ImplicitEuler(); dt = 0.01, adaptive = false)
211212
@test (osol2.u, isol.sol.u, rtol = 0.01)
212-
@test (osol2.u, psol.sol.u, rtol = 0.01)
213+
@test all([(psol.sol(t), osol2(t), rtol = 0.01) for t in 0.:0.01:4.])
213214
end
214215

215216
@testset "Rocket launch" begin
@@ -248,7 +249,7 @@ end
248249
isol = solve(iprob, InfiniteOptCollocation(Ipopt.Optimizer))
249250
@test isol.sol[h(t)][end] > 1.012
250251

251-
pprob = PyomoCollocationDynamicOptProblem(rocket, u0map, (ts, te), pmap; dt = 0.001, cse = false)
252+
pprob = PyomoDynamicOptProblem(rocket, u0map, (ts, te), pmap; dt = 0.001, cse = false)
252253
psol = solve(pprob, PyomoCollocation("ipopt"))
253254
@test psol.sol.u[end][1] > 1.012
254255

0 commit comments

Comments
 (0)