Skip to content

Commit d0f2243

Browse files
committed
fix: error when using collocation on fft problems
1 parent a455e61 commit d0f2243

File tree

2 files changed

+80
-21
lines changed

2 files changed

+80
-21
lines changed

docs/src/API/dynamic_opt.md

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
### Solvers
2+
3+
Currently 4 backends are exposed for solving dynamic optimization problems using collocation: JuMP, InfiniteOpt, CasADi, and Pyomo.
4+
5+
Please note that there are differences in how to construct the collocation solver for the different cases. For example, the Python based ones, CasADi and Pyomo, expect the solver to be passed in as a string (CasADi and Pyomo come pre-loaded with Ipopt, but other solvers may need to be manually installed using `pip` or `conda`), while JuMP/InfiniteOpt expect the optimizer object to be passed in directly:
6+
7+
```
8+
JuMPCollocation(Ipopt.Optimizer, constructRK4())
9+
CasADiCollocation("ipopt", constructRK4())
10+
```
11+
12+
**JuMP** and **CasADi** collocation require an ODE tableau to be passed in. These can be constructed by calling the `constructX()` functions from DiffEqDevTools. The list of tableaus can be found [here](https://docs.sciml.ai/DiffEqDevDocs/dev/internals/tableaus/). If none is passed in, both solvers will default to using Radau second-order with five collocation points.
13+
14+
**Pyomo** and **InfiniteOpt** each have their own built-in collocation methods.
15+
16+
1. **InfiniteOpt**: The list of InfiniteOpt collocation methods can be found [in the table on this page](https://infiniteopt.github.io/InfiniteOpt.jl/stable/guide/derivative/). If none is passed in, the solver defaults to `FiniteDifference(Backward())`, which is effectively implicit Euler.
17+
2. **Pyomo**: The list of Pyomo collocation methods can be found [at the bottom of this page](https://github.com/SciML/Pyomo.jl). If none is passed in, the solver defaults to a `LagrangeRadau(3)`.
18+
19+
Some examples of the latter two collocations:
20+
21+
```julia
22+
PyomoCollocation("ipopt", LagrangeRadau(2))
23+
InfiniteOptCollocation(Ipopt.Optimizer, OrthogonalCollocation(3))
24+
```
25+
26+
```@docs; canonical = false
27+
JuMPCollocation
28+
InfiniteOptCollocation
29+
CasADiCollocation
30+
PyomoCollocation
31+
solve(::AbstractDynamicOptProblem)
32+
```
33+
34+
### Problem constructors
35+
36+
```@docs; canonical = false
37+
JuMPDynamicOptProblem
38+
InfiniteOptDynamicOptProblem
39+
CasADiDynamicOptProblem
40+
PyomoDynamicOptProblem
41+
```

ext/MTKPyomoDynamicOptExt.jl

Lines changed: 39 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,15 @@ using Setfield
88
const MTK = ModelingToolkit
99

1010
SPECIAL_FUNCTIONS_DICT = Dict([acos => Pyomo.py_acos,
11-
acosh => Pyomo.py_acosh,
12-
asin => Pyomo.py_asin,
13-
tan => Pyomo.py_tan,
14-
atanh => Pyomo.py_atanh,
15-
cos => Pyomo.py_cos,
16-
log => Pyomo.py_log,
17-
sin => Pyomo.py_sin,
18-
sqrt => Pyomo.py_sqrt,
19-
exp => Pyomo.py_exp])
11+
acosh => Pyomo.py_acosh,
12+
asin => Pyomo.py_asin,
13+
tan => Pyomo.py_tan,
14+
atanh => Pyomo.py_atanh,
15+
cos => Pyomo.py_cos,
16+
log => Pyomo.py_log,
17+
sin => Pyomo.py_sin,
18+
sqrt => Pyomo.py_sqrt,
19+
exp => Pyomo.py_exp])
2020

2121
struct PyomoDynamicOptModel
2222
model::ConcreteModel
@@ -34,7 +34,8 @@ struct PyomoDynamicOptModel
3434
@variables MODEL_SYM::Symbolics.symstruct(ConcreteModel) T_SYM DUMMY_SYM
3535
model.dU = dae.DerivativeVar(U, wrt = model.t, initialize = 0)
3636
#add_time_equation!(model, MODEL_SYM, T_SYM)
37-
new(model, U, V, tₛ, is_free_final, nothing, PyomoVar(model.dU), MODEL_SYM, T_SYM, DUMMY_SYM)
37+
new(model, U, V, tₛ, is_free_final, nothing,
38+
PyomoVar(model.dU), MODEL_SYM, T_SYM, DUMMY_SYM)
3839
end
3940
end
4041

@@ -63,27 +64,34 @@ struct PyomoDynamicOptProblem{uType, tType, isinplace, P, F, K} <:
6364
end
6465
end
6566

66-
pysym_getproperty(s::Union{Num, Symbolics.Symbolic}, name::Symbol) = Symbolics.wrap(SymbolicUtils.term(_getproperty, Symbolics.unwrap(s), Val{name}(), type = Symbolics.Struct{PyomoVar}))
67-
_getproperty(s, name::Val{fieldname}) where fieldname = getproperty(s, fieldname)
67+
function pysym_getproperty(s::Union{Num, Symbolics.Symbolic}, name::Symbol)
68+
Symbolics.wrap(SymbolicUtils.term(
69+
_getproperty, Symbolics.unwrap(s), Val{name}(), type = Symbolics.Struct{PyomoVar}))
70+
end
71+
_getproperty(s, name::Val{fieldname}) where {fieldname} = getproperty(s, fieldname)
6872

6973
function MTK.PyomoDynamicOptProblem(sys::System, op, tspan;
7074
dt = nothing, steps = nothing,
7175
guesses = Dict(), kwargs...)
72-
prob, pmap = MTK.process_DynamicOptProblem(PyomoDynamicOptProblem, PyomoDynamicOptModel, sys, op, tspan; dt, steps, guesses, kwargs...)
76+
prob,
77+
pmap = MTK.process_DynamicOptProblem(PyomoDynamicOptProblem, PyomoDynamicOptModel,
78+
sys, op, tspan; dt, steps, guesses, kwargs...)
7379
conc_model = prob.wrapped_model.model
7480
MTK.add_equational_constraints!(prob.wrapped_model, sys, pmap, tspan)
7581
prob
7682
end
7783

78-
MTK.generate_internal_model(m::Type{PyomoDynamicOptModel}) = ConcreteModel(pyomo.ConcreteModel())
84+
function MTK.generate_internal_model(m::Type{PyomoDynamicOptModel})
85+
ConcreteModel(pyomo.ConcreteModel())
86+
end
7987

8088
function MTK.generate_time_variable!(m::ConcreteModel, tspan, tsteps)
8189
m.steps = length(tsteps)
8290
m.t = dae.ContinuousSet(initialize = tsteps, bounds = tspan)
8391
m.time = pyomo.Var(m.t)
8492
end
8593

86-
function MTK.generate_state_variable!(m::ConcreteModel, u0, ns, ts)
94+
function MTK.generate_state_variable!(m::ConcreteModel, u0, ns, ts)
8795
m.u_idxs = pyomo.RangeSet(1, ns)
8896
init_f = Pyomo.pyfunc((m, i, t) -> (u0[Pyomo.pyconvert(Int, i)]))
8997
m.U = pyomo.Var(m.u_idxs, m.t, initialize = init_f)
@@ -113,7 +121,7 @@ function MTK.add_constraint!(pmodel::PyomoDynamicOptModel, cons; n_idxs = 1)
113121
end
114122
expr = Symbolics.substitute(expr, SPECIAL_FUNCTIONS_DICT)
115123

116-
cons_sym = Symbol("cons", hash(cons))
124+
cons_sym = Symbol("cons", hash(cons))
117125
if occursin(Symbolics.unwrap(t_sym), expr)
118126
f = eval(Symbolics.build_function(expr, model_sym, t_sym))
119127
setproperty!(model, cons_sym, pyomo.Constraint(model.t, rule = Pyomo.pyfunc(f)))
@@ -176,14 +184,21 @@ struct PyomoCollocation <: AbstractCollocation
176184
derivative_method::Pyomo.DiscretizationMethod
177185
end
178186

179-
MTK.PyomoCollocation(solver, derivative_method = LagrangeRadau(5)) = PyomoCollocation(solver, derivative_method)
187+
function MTK.PyomoCollocation(solver, derivative_method = LagrangeRadau(5))
188+
PyomoCollocation(solver, derivative_method)
189+
end
180190

181-
function MTK.prepare_and_optimize!(prob::PyomoDynamicOptProblem, collocation; verbose, kwargs...)
191+
function MTK.prepare_and_optimize!(
192+
prob::PyomoDynamicOptProblem, collocation; verbose, kwargs...)
182193
solver_m = prob.wrapped_model.model.clone()
183194
dm = collocation.derivative_method
184195
discretizer = TransformationFactory(dm)
196+
if MTK.is_free_final(prob.wrapped_model) && !Pyomo.is_finite_difference(dm)
197+
error("The Lagrange-Radau and Lagrange-Legendre collocations currently cannot be used for free final problems.")
198+
end
185199
ncp = Pyomo.is_finite_difference(dm) ? 1 : dm.np
186-
discretizer.apply_to(solver_m, wrt = solver_m.t, nfe = solver_m.steps - 1, scheme = Pyomo.scheme_string(dm))
200+
discretizer.apply_to(solver_m, wrt = solver_m.t, nfe = solver_m.steps - 1,
201+
scheme = Pyomo.scheme_string(dm))
187202

188203
solver = SolverFactory(string(collocation.solver))
189204
results = solver.solve(solver_m, tee = true)
@@ -208,13 +223,16 @@ function MTK.get_t_values(output::PyomoOutput)
208223
Pyomo.pyconvert(Float64, pyomo.value(m.tₛ)) * [Pyomo.pyconvert(Float64, t) for t in m.t]
209224
end
210225

211-
MTK.objective_value(output::PyomoOutput) = Pyomo.pyconvert(Float64, pyomo.value(output.model.obj))
226+
function MTK.objective_value(output::PyomoOutput)
227+
Pyomo.pyconvert(Float64, pyomo.value(output.model.obj))
228+
end
212229

213230
function MTK.successful_solve(output::PyomoOutput)
214231
r = output.result
215232
ss = r.solver.status
216233
tc = r.solver.termination_condition
217-
if Bool(ss == opt.SolverStatus.ok) && (Bool(tc == opt.TerminationCondition.optimal) || Bool(tc == opt.TerminationCondition.locallyOptimal))
234+
if Bool(ss == opt.SolverStatus.ok) && (Bool(tc == opt.TerminationCondition.optimal) ||
235+
Bool(tc == opt.TerminationCondition.locallyOptimal))
218236
return true
219237
else
220238
return false

0 commit comments

Comments
 (0)