Skip to content

Commit b5b0f44

Browse files
committed
refactor: centralize substitute_differentiasl and substitute_integral
1 parent 8d268e9 commit b5b0f44

File tree

4 files changed

+299
-96
lines changed

4 files changed

+299
-96
lines changed

ext/MTKCasADiDynamicOptExt.jl

Lines changed: 15 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -130,44 +130,32 @@ function MTK.add_initial_constraints!(m::CasADiModel, u0, u0_idxs, args...)
130130
end
131131
end
132132

133-
function MTK.substitute_model_vars(m::CasADiModel, sys, exprs, tspan)
134-
@unpack model, U, V, tₛ = m
135-
iv = MTK.get_iv(sys)
136-
sts = unknowns(sys)
137-
cts = MTK.unbound_inputs(sys)
138-
x_ops = [MTK.operation(MTK.unwrap(st)) for st in sts]
139-
c_ops = [MTK.operation(MTK.unwrap(ct)) for ct in cts]
140-
(ti, tf) = tspan
141-
if MTK.is_free_final(m)
142-
_tf = tₛ + ti
143-
exprs = map(c -> Symbolics.fast_substitute(c, Dict(tf => _tf)), exprs)
144-
free_t_map = Dict([[x(_tf) => U.u[i, end] for (i, x) in enumerate(x_ops)];
145-
[c(_tf) => V.u[i, end] for (i, c) in enumerate(c_ops)]])
146-
exprs = map(c -> Symbolics.fast_substitute(c, free_t_map), exprs)
147-
end
133+
function free_t_map(model, tf, x_ops, c_ops)
134+
Dict([[x(tf) => model.U.u[i, end] for (i, x) in enumerate(x_ops)];
135+
[c(tf) => model.V.u[i, end] for (i, c) in enumerate(c_ops)]])
136+
end
137+
138+
function whole_t_map(model, sys)
139+
Dict([[v => model.U.u[i, :] for (i, v) in enumerate(unknowns(sys))];
140+
[v => model.V.u[i, :] for (i, v) in enumerate(MTK.unbound_inputs(sys))]])
148141

149-
exprs = substitute_fixed_t_vars(m, sys, exprs)
150-
whole_interval_map = Dict([[v => U.u[i, :] for (i, v) in enumerate(sts)];
151-
[v => V.u[i, :] for (i, v) in enumerate(cts)]])
152-
exprs = map(c -> Symbolics.fast_substitute(c, whole_interval_map), exprs)
153142
end
154143

155-
function substitute_fixed_t_vars(model::CasADiModel, sys, exprs)
156-
stidxmap = Dict([v => i for (i, v) in enumerate(unknowns(sys))])
157-
ctidxmap = Dict([v => i for (i, v) in enumerate(MTK.unbound_inputs(sys))])
158-
iv = MTK.get_iv(sys)
144+
function fixed_t_map(model::CasADiModel, x_ops, c_ops, exprs)
145+
stidxmap = Dict([v => i for (i, v) in x_ops])
146+
ctidxmap = Dict([v => i for (i, v) in c_ops])
159147
for i in 1:length(exprs)
160148
subvars = MTK.vars(exprs[i])
161149
for st in subvars
162150
MTK.iscall(st) || continue
163151
x = operation(st)
164152
t = only(arguments(st))
165153
MTK.symbolic_type(t) === MTK.NotSymbolic() || continue
166-
if haskey(stidxmap, x(iv))
167-
idx = stidxmap[x(iv)]
154+
if haskey(stidxmap, x)
155+
idx = stidxmap[x]
168156
cv = model.U
169157
else
170-
idx = ctidxmap[x(iv)]
158+
idx = ctidxmap[x]
171159
cv = model.V
172160
end
173161
exprs[i] = Symbolics.fast_substitute(exprs[i], Dict(x(t) => cv(t)[idx]))
@@ -177,24 +165,7 @@ function substitute_fixed_t_vars(model::CasADiModel, sys, exprs)
177165
exprs
178166
end
179167

180-
MTK.substitute_differentials(model::CasADiModel, sys, eqs) = exprs
181-
182-
function MTK.substitute_integral(m::CasADiModel, exprs, tspan)
183-
@unpack U, model, tₛ = m
184-
dt = U.t[2] - U.t[1]
185-
intmap = Dict()
186-
for int in MTK.collect_applied_operators(exprs, Symbolics.Integral)
187-
op = MTK.operation(int)
188-
arg = only(arguments(MTK.value(int)))
189-
lo, hi = MTK.value.((op.domain.domain.left, op.domain.domain.right))
190-
!isequal((lo, hi), tspan) &&
191-
error("Non-whole interval bounds for integrals are not currently supported for CasADiDynamicOptProblem.")
192-
# Approximate integral as sum.
193-
intmap[int] = dt * tₛ * sum(arg)
194-
end
195-
exprs = map(c -> Symbolics.substitute(c, intmap), exprs)
196-
exprs = MTK.value.(exprs)
197-
end
168+
MTK.lowered_integral(model, expr, args...) = model.tₛ * (model.U.t[2] - model.U.t[1]) * expr
198169

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

ext/MTKInfiniteOptExt.jl

Lines changed: 25 additions & 41 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 = steps)
51+
MTK.generate_time_variable!(m::InfiniteModel, tspan, steps) = @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

@@ -114,55 +114,39 @@ function MTK.InfiniteOptDynamicOptProblem(sys::System, u0map, tspan, pmap;
114114
prob
115115
end
116116

117-
function MTK.substitute_integral(model, exprs, tspan)
118-
intmap = Dict()
119-
for int in MTK.collect_applied_operators(exprs, Symbolics.Integral)
120-
op = MTK.operation(int)
121-
arg = only(arguments(MTK.value(int)))
122-
lo, hi = MTK.value.((op.domain.domain.left, op.domain.domain.right))
123-
if MTK.is_free_final(model) && isequal((lo, hi), tspan)
124-
(lo, hi) = (0, 1)
125-
elseif MTK.is_free_final(model)
126-
error("Free final time problems cannot handle partial timespans.")
127-
end
128-
intmap[int] = model.tₛ * InfiniteOpt.(arg, model.model[:t], lo, hi)
117+
MTK.lowered_integral(model, expr, lo, hi) = model.tₛ * InfiniteOpt.(arg, model.model[:t], lo, hi)
118+
119+
function MTK.process_integral_bounds(model, integral_span, tspan)
120+
if MTK.is_free_final(model) && isequal(integral_span, tspan)
121+
integral_span = (0, 1)
122+
elseif MTK.is_free_final(model)
123+
error("Free final time problems cannot handle partial timespans.")
124+
else
125+
integral_span
129126
end
130-
exprs = map(c -> Symbolics.substitute(c, intmap), exprs)
131127
end
132128

133129
function MTK.add_initial_constraints!(m::InfiniteOptModel, u0, u0_idxs, ts)
134-
@constraint(m.model, initial[i in u0_idxs], m.U[i](ts)==u0[i])
130+
for i in u0_idxs
131+
fix(m.U[i], u0[i], force = true)
132+
end
135133
end
136134

137-
function MTK.substitute_model_vars(model::InfiniteOptModel, sys, exprs, tspan)
138-
whole_interval_map = Dict([[v => model.U[i] for (i, v) in enumerate(unknowns(sys))];
139-
[v => model.V[i] for (i, v) in enumerate(MTK.unbound_inputs(sys))]])
140-
exprs = map(c -> Symbolics.fast_substitute(c, whole_interval_map), exprs)
141-
142-
x_ops = [MTK.operation(MTK.unwrap(st)) for st in unknowns(sys)]
143-
c_ops = [MTK.operation(MTK.unwrap(ct)) for ct in MTK.unbound_inputs(sys)]
144-
145-
(ti, tf) = tspan
146-
if MTK.symbolic_type(tf) === MTK.ScalarSymbolic()
147-
_tf = model.tₛ + ti
148-
exprs = map(c -> Symbolics.fast_substitute(c, Dict(tf => _tf)), exprs)
149-
free_t_map = Dict([[x(_tf) => model.U[i](1) for (i, x) in enumerate(x_ops)];
150-
[c(_tf) => model.V[i](1) for (i, c) in enumerate(c_ops)]])
151-
exprs = map(c -> Symbolics.fast_substitute(c, free_t_map), exprs)
152-
end
135+
MTK.lowered_derivative(model, i) = (model.U[i], model.model[:t])
136+
137+
function MTK.fixed_t_map(model::InfiniteOptModel, x_ops, c_ops, exprs)
138+
Dict([[x_ops[i] => model.U[i] for i in 1:length(model.U)];
139+
[c_ops[i] => model.V[i] for i in 1:length(model.V)]])
140+
end
153141

154-
# for variables like x(1.0)
155-
fixed_t_map = Dict([[x_ops[i] => model.U[i] for i in 1:length(model.U)];
156-
[c_ops[i] => model.V[i] for i in 1:length(model.V)]])
157-
exprs = map(c -> Symbolics.fast_substitute(c, fixed_t_map), exprs)
142+
function MTK.free_t_map(model::InfiniteOptModel, tf, x_ops, c_ops)
143+
Dict([[x(tf) => model.U[i](1) for (i, x) in enumerate(x_ops)];
144+
[c(tf) => model.V[i](1) for (i, c) in enumerate(c_ops)]])
158145
end
159146

160-
function MTK.substitute_differentials(model::InfiniteOptModel, sys, eqs)
161-
U = model.U
162-
t = model.model[:t]
163-
D = Differential(MTK.get_iv(sys))
164-
diffsubmap = Dict([D(U[i]) => (U[i], t) for i in 1:length(U)])
165-
map(e -> Symbolics.substitute(e, diffsubmap), eqs)
147+
function MTK.whole_t_map(model::InfiniteOptModel, sys)
148+
whole_interval_map = Dict([[v => model.U[i] for (i, v) in enumerate(unknowns(sys))];
149+
[v => model.V[i] for (i, v) in enumerate(MTK.unbound_inputs(sys))]])
166150
end
167151

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

ext/MTKPyomoDynamicOptExt.jl

Lines changed: 209 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,209 @@
1+
module MTKPyomoDynamicOptExt
2+
using ModelingToolkit
3+
using PythonCall
4+
using DiffEqBase
5+
using UnPack
6+
using NaNMath
7+
const MTK = ModelingToolkit
8+
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(A)}(getindex, [A, unwrap(i), unwrap(t)]))
18+
19+
for ff in [acos, log1p, acosh, log2, asin, tan, atanh, cos, log, sin, log10, sqrt]
20+
f = nameof(ff)
21+
@eval NaNMath.$f(x::PyomoDAEVar) = Base.$f(x)
22+
end
23+
24+
const SymbolicConcreteModel = Symbolics.symstruct(ConcreteModel)
25+
26+
struct PyomoModel
27+
model::ConcreteModel
28+
U
29+
V
30+
tₛ::Union{Int}
31+
is_free_final::Bool
32+
model_sym::SymbolicConcreteModel
33+
t_sym::Union{Num, BasicSymbolic}
34+
idx_sym::Union{Num, BasicSymbolic}
35+
36+
function PyomoModel(model, U, V, tₛ, is_free_final)
37+
@variables MODEL_SYM::SymbolicConcreteModel IDX_SYM::Int T_SYM
38+
PyomoModel(model, U, V, tₛ, is_free_final, MODEL_SYM, T_SYM, INDEX_SYM)
39+
end
40+
end
41+
42+
struct PyomoDynamicOptProblem{uType, tType, isinplace, P, F, K} <:
43+
AbstractDynamicOptProblem{uType, tType, isinplace}
44+
f::F
45+
u0::uType
46+
tspan::tType
47+
p::P
48+
wrapped_model::ConcreteModel
49+
kwargs::K
50+
51+
function PyomoDynamicOptProblem(f, u0, tspan, p, model, kwargs...)
52+
new{typeof(u0), typeof(tspan), SciMLBase.isinplace(f, 5),
53+
typeof(p), typeof(f), typeof(kwargs)}(f, u0, tspan, p, model, kwargs)
54+
end
55+
end
56+
57+
"""
58+
PyomoDynamicOptProblem(sys::ODESystem, u0, tspan, p; dt, steps)
59+
60+
Convert an ODESystem representing an optimal control system into a Pyomo model
61+
for solving using optimization. Must provide either `dt`, the timestep between collocation
62+
points (which, along with the timespan, determines the number of points), or directly
63+
provide the number of points as `steps`.
64+
"""
65+
function MTK.PyomoDynamicOptProblem(sys::ODESystem, u0map, tspan, pmap;
66+
dt = nothing,
67+
steps = nothing,
68+
guesses = Dict(), kwargs...)
69+
prob = MTK.process_DynamicOptProblem(PyomoDynamicOptProblem, PyomoModel, sys, u0map, tspan, pmap; dt, steps, guesses, kwargs...)
70+
MTK.add_equational_constraints!(prob.wrapped_model, sys, pmap, tspan)
71+
prob
72+
end
73+
74+
MTK.generate_internal_model(m::Type{PyomoModel}) = pyomo.ConcreteModel()
75+
function MTK.generate_time_variable!(m::ConcreteModel, tspan, tsteps)
76+
m.t = pyomo.ContinuousSet(initialize = collect(tsteps), bounds = tspan)
77+
end
78+
79+
function MTK.generate_state_variable!(m::ConcreteModel, u0, ns, ts)
80+
m.u_idxs = pyomo.RangeSet(1, ns)
81+
pyomo.Var(m.u_idxs, m.t)
82+
end
83+
84+
function MTK.generate_input_variable!(m::ConcreteModel, u0, nc, ts)
85+
m.v_idxs = pyomo.RangeSet(1, nc)
86+
pyomo.Var(m.v_idxs, m.t)
87+
end
88+
89+
function MTK.generate_timescale(m::ConcreteModel, guess, is_free_t)
90+
m.tₛ = is_free_t ? pyomo.Var(initialize = guess, bounds = (0, Inf)) : 1
91+
end
92+
93+
function MTK.add_constraint!(pmodel::PyomoModel, cons)
94+
@unpack model, model_sym, idx_sym, t_sym = pmodel
95+
expr = if cons isa Equation
96+
cons.lhs - cons.rhs == 0
97+
elseif cons.relational_op === Symbolics.geq
98+
cons.lhs - cons.rhs 0
99+
else
100+
cons.lhs - cons.rhs 0
101+
end
102+
constraint_f = Symbolics.build_function(expr, model_sym, idx_sym, t_sym)
103+
pyomo.Constraint(rule = constraint_f)
104+
end
105+
106+
function MTK.set_objective!(m::PyomoModel, expr) = pyomo.Objective(expr = expr)
107+
108+
function add_initial_constraints!(model::PyomoModel, u0, u0_idxs)
109+
for i in u0_idxs
110+
model.U[i, 0].fix(u0[i])
111+
end
112+
end
113+
114+
function substitute_fixed_t_vars!(model::PyomoModel, sys, exprs)
115+
stidxmap = Dict([v => i for (i, v) in enumerate(unknowns(sys))])
116+
ctidxmap = Dict([v => i for (i, v) in enumerate(MTK.unbound_inputs(sys))])
117+
iv = MTK.get_iv(sys)
118+
119+
for cons in jconstraints
120+
consvars = MTK.vars(cons)
121+
for st in consvars
122+
MTK.iscall(st) || continue
123+
x = MTK.operation(st)
124+
t = only(MTK.arguments(st))
125+
MTK.symbolic_type(t) === MTK.NotSymbolic() || continue
126+
if haskey(stidxmap, x(iv))
127+
idx = stidxmap[x(iv)]
128+
cv = :U
129+
else
130+
idx = ctidxmap[x(iv)]
131+
cv = :V
132+
end
133+
model.t.add(t)
134+
cons = Symbolics.substitute(cons, Dict(x(t) => model.cv[idx, t]))
135+
end
136+
end
137+
end
138+
139+
function MTK.substitute_model_vars(pmodel::PyomoModel, sys, pmap, exprs, tspan)
140+
@unwrap model, model_sym, idx_sym, t_sym = pmodel
141+
x_ops = [MTK.operation(MTK.unwrap(st)) for st in unknowns(sys)]
142+
c_ops = [MTK.operation(MTK.unwrap(ct)) for ct in MTK.unbound_inputs(sys)]
143+
mU = Symbolics.symbolic_getproperty(model_sym, :U)
144+
mV = Symbolics.symbolic_getproperty(model_sym, :V)
145+
146+
(ti, tf) = tspan
147+
if MTK.symbolic_type(tf) === MTK.ScalarSymbolic()
148+
_tf = model.tₛ + ti
149+
exprs = map(c -> Symbolics.fast_substitute(c, Dict(tf => _tf)), exprs)
150+
free_t_map = Dict([[x(tₛ) => mU[i, end] for (i, x) in enumerate(x_ops)];
151+
[c(tₛ) => mV[i, end] for (i, c) in enumerate(c_ops)]])
152+
exprs = map(c -> Symbolics.fixpoint_sub(c, free_t_map), exprs)
153+
end
154+
155+
whole_interval_map = Dict([[v => mU[i, t_sym] for (i, v) in enumerate(sts)];
156+
[v => mV[i, t_sym] for (i, v) in enumerate(cts)]])
157+
exprs = map(c -> Symbolics.fixpoint_sub(c, whole_interval_map), exprs)
158+
exprs
159+
end
160+
161+
function MTK.substitute_integral!(model::PyomoModel, exprs, tspan)
162+
intmap = Dict()
163+
for int in MTK.collect_applied_operators(exprs, Symbolics.Integral)
164+
op = MTK.operation(int)
165+
arg = only(arguments(MTK.value(int)))
166+
lo, hi = MTK.value.((op.domain.domain.left, op.domain.domain.right))
167+
if MTK.is_free_final(model) && isequal((lo, hi), tspan)
168+
(lo, hi) = (0, 1)
169+
elseif MTK.is_free_final(model)
170+
error("Free final time problems cannot handle partial timespans.")
171+
end
172+
intmap[int] = model.tₛ * InfiniteOpt.(arg, model.model[:t], lo, hi)
173+
end
174+
exprs = map(c -> Symbolics.substitute(c, intmap), exprs)
175+
end
176+
177+
function MTK.substitute_differentials(model::PyomoModel, sys, eqs)
178+
pmodel = prob.model
179+
@unpack model, model_sym, t_sym, idx_sym = pmodel
180+
model.dU = pyomo.DerivativeVar(model.U, wrt = model.t)
181+
182+
mdU = Symbolics.symbolic_getproperty(model_sym, :dU)
183+
mU = Symbolics.symbolic_getproperty(model_sym, :U)
184+
mtₛ = Symbolics.symbolic_getproperty(model_sym, :tₛ)
185+
diffsubmap = Dict([D(mU[i, t_sym]) => mdU[i, t_sym] for i in 1:length(unknowns(sys))])
186+
187+
diff_eqs = substitute_model_vars(model, sys, pmap, diff_equations(sys))
188+
diff_eqs = map(e -> Symbolics.substitute(e, diffsubmap), diff_eqs)
189+
[mtₛ * eq.rhs - eq.lhs == 0 for eq in diff_eqs]
190+
end
191+
192+
struct PyomoCollocation <: AbstractCollocation
193+
solver::Any
194+
derivative_method
195+
end
196+
MTK.PyomoCollocation(solver, derivative_method = 1) = PyomoCollocation(solver, derivative_method)
197+
198+
function MTK.prepare_and_optimize!()
199+
end
200+
function MTK.get_U_values()
201+
end
202+
function MTK.get_V_values()
203+
end
204+
function MTK.get_t_values()
205+
end
206+
function MTK.successful_solve()
207+
end
208+
209+
end

0 commit comments

Comments
 (0)