Skip to content

Commit a275b33

Browse files
committed
refactor: use lowered_var instead of manual substitution
1 parent 7fda5b0 commit a275b33

File tree

7 files changed

+123
-181
lines changed

7 files changed

+123
-181
lines changed

Project.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ OffsetArrays = "6fe1bfb0-de20-5000-8ca7-80f57d26f881"
4646
OrderedCollections = "bac558e1-5e72-5ebc-8fee-abe8a469f55d"
4747
OrdinaryDiffEqCore = "bbf590c4-e513-4bbe-9b18-05decba2e5d8"
4848
PrecompileTools = "aea7be01-6a6a-4083-8856-8a6e6704d82a"
49+
Pyomo = "0e8e1daf-01b5-4eba-a626-3897743a3816"
4950
RecursiveArrayTools = "731186ca-8d62-57ce-b412-fbd966d074cd"
5051
Reexport = "189a3867-3050-52da-a836-e630ba90ab69"
5152
RuntimeGeneratedFunctions = "7e49a35a-f44a-4d26-94aa-eba1b4ca6b47"
@@ -81,6 +82,7 @@ MTKDeepDiffsExt = "DeepDiffs"
8182
MTKFMIExt = "FMI"
8283
MTKInfiniteOptExt = "InfiniteOpt"
8384
MTKLabelledArraysExt = "LabelledArrays"
85+
MTKPyomoDynamicOptExt = "Pyomo"
8486

8587
[compat]
8688
ADTypes = "1.14.0"

ext/MTKCasADiDynamicOptExt.jl

Lines changed: 5 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,7 @@ function MTK.add_constraint!(m::CasADiModel, expr)
105105
subject_to!(m.model, expr.lhs - expr.rhs 0)
106106
end
107107
end
108+
108109
MTK.set_objective!(m::CasADiModel, expr) = minimize!(m.model, MX(expr))
109110

110111
function MTK.add_initial_constraints!(m::CasADiModel, u0, u0_idxs, args...)
@@ -114,42 +115,12 @@ function MTK.add_initial_constraints!(m::CasADiModel, u0, u0_idxs, args...)
114115
end
115116
end
116117

117-
function free_t_map(model, tf, x_ops, c_ops)
118-
Dict([[x(tf) => model.U.u[i, end] for (i, x) in enumerate(x_ops)];
119-
[c(tf) => model.V.u[i, end] for (i, c) in enumerate(c_ops)]])
120-
end
121-
122-
function whole_t_map(model, sys)
123-
Dict([[v => model.U.u[i, :] for (i, v) in enumerate(unknowns(sys))];
124-
[v => model.V.u[i, :] for (i, v) in enumerate(MTK.unbound_inputs(sys))]])
125-
126-
end
127-
128-
function fixed_t_map(model::CasADiModel, x_ops, c_ops, exprs)
129-
stidxmap = Dict([v => i for (i, v) in x_ops])
130-
ctidxmap = Dict([v => i for (i, v) in c_ops])
131-
for i in 1:length(exprs)
132-
subvars = MTK.vars(exprs[i])
133-
for st in subvars
134-
MTK.iscall(st) || continue
135-
x = operation(st)
136-
t = only(arguments(st))
137-
MTK.symbolic_type(t) === MTK.NotSymbolic() || continue
138-
if haskey(stidxmap, x)
139-
idx = stidxmap[x]
140-
cv = model.U
141-
else
142-
idx = ctidxmap[x]
143-
cv = model.V
144-
end
145-
exprs[i] = Symbolics.fast_substitute(exprs[i], Dict(x(t) => cv(t)[idx]))
146-
end
147-
jcosts = Symbolics.substitute(jcosts, Dict(x(t) => cv(t)[idx]))
148-
end
149-
exprs
118+
function MTK.lowered_var(m::CasADiModel, uv, i, t)
119+
X = getfield(m, uv)
120+
t isa Union{Num, Symbolics.Symbolic} ? X.u[i, :] : X(t)[i]
150121
end
151122

152-
MTK.lowered_integral(model, expr, args...) = model.tₛ * (model.U.t[2] - model.U.t[1]) * expr
123+
MTK.lowered_integral(model::CasADiModel, expr, args...) = model.tₛ * (model.U.t[2] - model.U.t[1]) * sum(expr)
153124

154125
function add_solve_constraints!(prob::CasADiDynamicOptProblem, tableau)
155126
@unpack A, α, c = tableau

ext/MTKInfiniteOptExt.jl

Lines changed: 7 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ struct InfiniteOptDynamicOptProblem{uType, tType, isinplace, P, F, K} <:
4848
end
4949

5050
MTK.generate_internal_model(m::Type{InfiniteOptModel}) = InfiniteModel()
51-
MTK.generate_time_variable!(m::InfiniteModel, tspan, steps) = @infinite_parameter(m, t in [tspan[1], tspan[2]], num_supports = length(tsteps))
51+
MTK.generate_time_variable!(m::InfiniteModel, tspan, tsteps) = @infinite_parameter(m, t in [tspan[1], tspan[2]], num_supports = length(tsteps))
5252
MTK.generate_state_variable!(m::InfiniteModel, u0::Vector, ns, ts) = @variable(m, U[i = 1:ns], Infinite(m[:t]), start=u0[i])
5353
MTK.generate_input_variable!(m::InfiniteModel, c0, nc, ts) = @variable(m, V[i = 1:nc], Infinite(m[:t]), start=c0[i])
5454

@@ -88,8 +88,8 @@ function MTK.InfiniteOptDynamicOptProblem(sys::ODESystem, u0map, tspan, pmap;
8888
prob
8989
end
9090

91-
MTK.lowered_integral(model, expr, lo, hi) = model.tₛ * InfiniteOpt.(arg, model.model[:t], lo, hi)
92-
MTK.lowered_derivative(model, i) = (model.U[i], model.model[:t])
91+
MTK.lowered_integral(model::InfiniteOptModel, expr, lo, hi) = model.tₛ * InfiniteOpt.(expr, model.model[:t], lo, hi)
92+
MTK.lowered_derivative(model::InfiniteOptModel, i) = (model.U[i], model.model[:t])
9393

9494
function MTK.process_integral_bounds(model, integral_span, tspan)
9595
if MTK.is_free_final(model) && isequal(integral_span, tspan)
@@ -103,23 +103,13 @@ end
103103

104104
function MTK.add_initial_constraints!(m::InfiniteOptModel, u0, u0_idxs, ts)
105105
for i in u0_idxs
106-
fix(m.U[i], u0[i], force = true)
106+
fix(m.U[i](0), u0[i], force = true)
107107
end
108108
end
109109

110-
function MTK.fixed_t_map(model::InfiniteOptModel, x_ops, c_ops, exprs)
111-
Dict([[x_ops[i] => model.U[i] for i in 1:length(model.U)];
112-
[c_ops[i] => model.V[i] for i in 1:length(model.V)]])
113-
end
114-
115-
function MTK.free_t_map(model::InfiniteOptModel, tf, x_ops, c_ops)
116-
Dict([[x(tf) => model.U[i](1) for (i, x) in enumerate(x_ops)];
117-
[c(tf) => model.V[i](1) for (i, c) in enumerate(c_ops)]])
118-
end
119-
120-
function MTK.whole_t_map(model::InfiniteOptModel, sys)
121-
whole_interval_map = Dict([[v => model.U[i] for (i, v) in enumerate(unknowns(sys))];
122-
[v => model.V[i] for (i, v) in enumerate(MTK.unbound_inputs(sys))]])
110+
function MTK.lowered_var(m::InfiniteOptModel, uv, i, t)
111+
X = getfield(m, uv)
112+
t isa Union{Num, Symbolics.Symbolic} ? X[i] : X[i](t)
123113
end
124114

125115
function add_solve_constraints!(prob::JuMPDynamicOptProblem, tableau)

ext/MTKPyomoDynamicOptExt.jl

Lines changed: 56 additions & 99 deletions
Original file line numberDiff line numberDiff line change
@@ -1,42 +1,26 @@
11
module MTKPyomoDynamicOptExt
22
using ModelingToolkit
3-
using PythonCall
3+
using Pyomo
44
using DiffEqBase
55
using UnPack
66
using NaNMath
77
const MTK = ModelingToolkit
88

9-
# import pyomo
10-
const pyomo = PythonCall.pynew()
11-
PythonCall.pycopy!(pyomo, pyimport("pyomo.environ"))
12-
13-
struct PyomoDAEVar
14-
v::Py
15-
end
16-
(v::PyomoDAEVar)(t) = v.v[:, t]
17-
getindex(v::PyomoDAEVar, i::Union{Num, Symbolic}, t::Union{Num, Symbolic}) = wrap(Term{symeltype(v)}(getindex, [v, unwrap(i), unwrap(t)]))
18-
getindex(v::PyomoDAEVar, i::Int) = wrap(Term{symeltype(v)}(getindex, [v, unwrap(i), Colon()]))
19-
20-
for ff in [acos, log1p, acosh, log2, asin, tan, atanh, cos, log, sin, log10, sqrt]
21-
f = nameof(ff)
22-
@eval NaNMath.$f(x::PyomoDAEVar) = Base.$f(x)
23-
end
24-
25-
const SymbolicConcreteModel = Symbolics.symstruct(ConcreteModel)
26-
27-
struct PyomoModel
9+
struct PyomoDynamicOptModel
2810
model::ConcreteModel
29-
U::PyomoDAEVar
30-
V::PyomoDAEVar
31-
tₛ::Union{Int, Py}
11+
U::PyomoVar
12+
V::PyomoVar
13+
tₛ::Union{Int, PyomoVar}
3214
is_free_final::Bool
33-
model_sym::SymbolicConcreteModel
34-
t_sym::Union{Num, BasicSymbolic}
35-
idx_sym::Union{Num, BasicSymbolic}
36-
37-
function PyomoModel(model, U, V, tₛ, is_free_final)
38-
@variables MODEL_SYM::SymbolicConcreteModel IDX_SYM::Int T_SYM
39-
PyomoModel(model, U, V, tₛ, is_free_final, MODEL_SYM, T_SYM, INDEX_SYM)
15+
dU::PyomoVar
16+
model_sym::Union{Num, Symbolics.BasicSymbolic}
17+
t_sym::Union{Num, Symbolics.BasicSymbolic}
18+
idx_sym::Union{Num, Symbolics.BasicSymbolic}
19+
20+
function PyomoDynamicOptModel(model, U, V, tₛ, is_free_final)
21+
@variables MODEL_SYM::Symbolics.symstruct(PyomoDynamicOptModel) IDX_SYM::Int T_SYM
22+
model.dU = dae.DerivativeVar(U, wrt = model.t, initialize = 0)
23+
new(model, U, V, tₛ, is_free_final, PyomoVar(model.dU), MODEL_SYM, T_SYM, IDX_SYM)
4024
end
4125
end
4226

@@ -46,7 +30,7 @@ struct PyomoDynamicOptProblem{uType, tType, isinplace, P, F, K} <:
4630
u0::uType
4731
tspan::tType
4832
p::P
49-
wrapped_model::ConcreteModel
33+
wrapped_model::PyomoDynamicOptModel
5034
kwargs::K
5135

5236
function PyomoDynamicOptProblem(f, u0, tspan, p, model, kwargs...)
@@ -58,99 +42,79 @@ end
5842
function MTK.PyomoDynamicOptProblem(sys::ODESystem, u0map, tspan, pmap;
5943
dt = nothing, steps = nothing,
6044
guesses = Dict(), kwargs...)
61-
prob = MTK.process_DynamicOptProblem(PyomoDynamicOptProblem, PyomoModel, sys, u0map, tspan, pmap; dt, steps, guesses, kwargs...)
62-
prob.wrapped_model.model.dU = pyomo.DerivativeVar(prob.wrapped_model.model.U, wrt = model.t)
45+
prob = MTK.process_DynamicOptProblem(PyomoDynamicOptProblem, PyomoDynamicOptModel, sys, u0map, tspan, pmap; dt, steps, guesses, kwargs...)
46+
conc_model = prob.wrapped_model.model
6347
MTK.add_equational_constraints!(prob.wrapped_model, sys, pmap, tspan)
6448
prob
6549
end
6650

67-
MTK.generate_internal_model(m::Type{PyomoModel}) = pyomo.ConcreteModel()
51+
MTK.generate_internal_model(m::Type{PyomoDynamicOptModel}) = ConcreteModel(pyomo.ConcreteModel())
6852

6953
function MTK.generate_time_variable!(m::ConcreteModel, tspan, tsteps)
7054
m.steps = length(tsteps)
71-
m.t = pyomo.ContinuousSet(initialize = collect(tsteps), bounds = tspan)
55+
m.t = dae.ContinuousSet(initialize = tsteps, bounds = tspan)
7256
end
7357

7458
function MTK.generate_state_variable!(m::ConcreteModel, u0, ns, ts)
7559
m.u_idxs = pyomo.RangeSet(1, ns)
76-
PyomoDAEVar(pyomo.Var(m.u_idxs, m.t))
60+
m.U = pyomo.Var(m.u_idxs, m.t, initialize = 0)
61+
PyomoVar(m.U)
7762
end
7863

79-
function MTK.generate_input_variable!(m::ConcreteModel, u0, nc, ts)
64+
function MTK.generate_input_variable!(m::ConcreteModel, c0, nc, ts)
8065
m.v_idxs = pyomo.RangeSet(1, nc)
81-
PyomoDAEVar(pyomo.Var(m.v_idxs, m.t))
66+
m.V = pyomo.Var(m.v_idxs, m.t, initialize = 0)
67+
PyomoVar(m.V)
8268
end
8369

84-
function MTK.generate_timescale(m::ConcreteModel, guess, is_free_t)
85-
m.tₛ = is_free_t ? pyomo.Var(initialize = guess, bounds = (0, Inf)) : 1
70+
function MTK.generate_timescale!(m::ConcreteModel, guess, is_free_t)
71+
m.tₛ = is_free_t ? PyomoVar(pyomo.Var(initialize = guess, bounds = (0, Inf))) : 1
8672
end
8773

88-
function MTK.add_constraint!(pmodel::PyomoModel, cons)
74+
function MTK.add_constraint!(pmodel::PyomoDynamicOptModel, cons)
8975
@unpack model, model_sym, idx_sym, t_sym = pmodel
76+
@show model.dU
9077
expr = if cons isa Equation
9178
cons.lhs - cons.rhs == 0
9279
elseif cons.relational_op === Symbolics.geq
9380
cons.lhs - cons.rhs 0
9481
else
9582
cons.lhs - cons.rhs 0
9683
end
97-
constraint_f = Symbolics.build_function(expr, model_sym, idx_sym, t_sym)
98-
pyomo.Constraint(rule = constraint_f)
84+
constraint_f = Symbolics.build_function(expr, model_sym, idx_sym, t_sym, expression = Val{false})
85+
@show typeof(constraint_f)
86+
@show typeof(Pyomo.pyfunc(constraint_f))
87+
cons_sym = gensym()
88+
setproperty!(model, cons_sym, pyomo.Constraint(model.u_idxs, model.t, rule = Pyomo.pyfunc(constraint_f)))
9989
end
10090

101-
function MTK.set_objective!(m::PyomoModel, expr) = pyomo.Objective(expr = expr)
91+
function MTK.set_objective!(m::PyomoDynamicOptModel, expr)
92+
m.model.obj = pyomo.Objective(expr = expr)
93+
end
10294

103-
function add_initial_constraints!(model::PyomoModel, u0, u0_idxs)
95+
function MTK.add_initial_constraints!(model::PyomoDynamicOptModel, u0, u0_idxs, ts)
10496
for i in u0_idxs
10597
model.U[i, 0].fix(u0[i])
10698
end
10799
end
108100

109-
function fixed_t_map(m::PyomoModel, sys, exprs)
110-
stidxmap = Dict([v => i for (i, v) in x_ops])
111-
ctidxmap = Dict([v => i for (i, v) in c_ops])
112-
mU = Symbolics.symbolic_getproperty(model_sym, :U)
113-
mV = Symbolics.symbolic_getproperty(model_sym, :V)
114-
fixed_t_map = Dict()
115-
for expr in exprs
116-
vars = MTK.vars(exprs)
117-
for st in vars
118-
MTK.iscall(st) || continue
119-
x = MTK.operation(st)
120-
t = only(MTK.arguments(st))
121-
MTK.symbolic_type(t) === MTK.NotSymbolic() || continue
122-
m.model.t.add(t)
123-
fixed_t_map[x(t)] = haskey(stidxmap, x) ? mU[stidxmap[x], t] : mV[ctidxmap[x], t]
124-
end
125-
end
126-
fixed_t_map
127-
end
128-
129-
function MTK.free_t_map(model, x_ops, c_ops)
130-
mU = Symbolics.symbolic_getproperty(model_sym, :U)
131-
mV = Symbolics.symbolic_getproperty(model_sym, :V)
132-
Dict([[x(tₛ) => mU[i, end] for (i, x) in enumerate(x_ops)];
133-
[c(tₛ) => mV[i, end] for (i, c) in enumerate(c_ops)]])
134-
end
135-
136-
function MTK.whole_t_map(model)
137-
mU = Symbolics.symbolic_getproperty(model_sym, :U)
138-
mV = Symbolics.symbolic_getproperty(model_sym, :V)
139-
Dict([[v => mU[i, t_sym] for (i, v) in enumerate(sts)];
140-
[v => mV[i, t_sym] for (i, v) in enumerate(cts)]])
141-
end
142-
143-
function MTK.lowered_integral(m::PyomoModel, arg)
101+
function MTK.lowered_integral(m::PyomoDynamicOptModel, arg, lo, hi)
144102
@unpack model, model_sym, t_sym = m
145103
arg_f = Symbolics.build_function(arg, model_sym, t_sym)
146-
Integral(model.t, wrt = model.t, rule=arg_f)
104+
dae.Integral(model.t, wrt = model.t, rule=arg_f)
147105
end
148106

149107
MTK.process_integral_bounds(model, integral_span, tspan) = integral_span
150108

151-
function MTK.lowered_derivative(m::PyomoModel, i)
152-
mdU = Symbolics.symbolic_getproperty(model_sym, :dU)
153-
mdU[i, t_sym]
109+
function MTK.lowered_derivative(m::PyomoDynamicOptModel, i)
110+
mdU = Symbolics.symbolic_getproperty(m.model_sym, :dU).val
111+
Symbolics.unwrap(mdU[i, m.t_sym])
112+
end
113+
114+
function MTK.lowered_var(m::PyomoDynamicOptModel, uv, i, t)
115+
X = Symbolics.symbolic_getproperty(m.model_sym, uv).val
116+
var = t isa Union{Num, Symbolics.Symbolic} ? X[i, m.t_sym] : X[i, t]
117+
Symbolics.unwrap(var)
154118
end
155119

156120
struct PyomoCollocation <: AbstractCollocation
@@ -164,25 +128,18 @@ function MTK.prepare_and_optimize!(prob::PyomoDynamicOptProblem, collocation; ve
164128
m = prob.wrapped_model.model
165129
dm = collocation.derivative_method
166130
discretizer = TransformationFactory(dm)
167-
ncp = is_finite_difference(dm) ? 1 : dm.np
168-
discretizer.apply_to(model, wrt = m.t, nfe = m.steps, ncp = ncp, scheme = scheme_string(dm))
131+
ncp = Pyomo.is_finite_difference(dm) ? 1 : dm.np
132+
discretizer.apply_to(m, wrt = m.t, nfe = m.steps, scheme = Pyomo.scheme_string(dm))
169133
solver = SolverFactory(string(collocation.solver))
170-
solver.solve(m)
134+
solver.solve(m, tee = true)
135+
Main.xx[] = solver
171136
end
172137

173-
function MTK.get_U_values(m::PyomoModel)
174-
[pyomo.value(model.U[i]) for i in model.U]
175-
end
176-
177-
function MTK.get_V_values(m::PyomoModel)
178-
[pyomo.value(model.V[i]) for i in model.V]
179-
end
180-
181-
function MTK.get_t_values(m::PyomoModel)
182-
[pyomo.value(model.t[i]) for i in model.t]
183-
end
138+
MTK.get_U_values(m::PyomoDynamicOptModel) = [pyomo.value(m.model.U[i]) for i in m.model.U.index_set()]
139+
MTK.get_V_values(m::PyomoDynamicOptModel) = [pyomo.value(m.model.V[i]) for i in m.model.V.index_set()]
140+
MTK.get_t_values(m::PyomoDynamicOptModel) = Pyomo.get_results(m.model, :t)
184141

185-
function MTK.successful_solve(m::PyomoModel)
142+
function MTK.successful_solve(m::PyomoDynamicOptModel)
186143
ss = m.solver.status
187144
tc = m.solver.termination_condition
188145
if ss == opt.SolverStatus.ok && (tc == opt.TerminationStatus.optimal || tc == opt.TerminationStatus.locallyOptimal)

src/ModelingToolkit.jl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -356,9 +356,9 @@ function FMIComponent end
356356

357357
include("systems/optimal_control_interface.jl")
358358
export AbstractDynamicOptProblem, JuMPDynamicOptProblem, InfiniteOptDynamicOptProblem,
359-
CasADiDynamicOptProblem
359+
CasADiDynamicOptProblem, PyomoDynamicOptProblem
360360
export AbstractCollocation, JuMPCollocation, InfiniteOptCollocation,
361-
CasADiCollocation
361+
CasADiCollocation, PyomoCollocation
362362
export DynamicOptSolution
363363

364364
@public apply_to_variables

0 commit comments

Comments
 (0)