Skip to content

Commit 8a18d36

Browse files
ChrisRackauckas-ClaudeChrisRackauckasclaude
authored
Port the Pyomo extension to Pyomo 2 / Symbolics 7 (#4888)
* Port the Pyomo extension to Pyomo 2 / Symbolics 7 MTKPyomoDynamicOptExt could not load at all: its compat was pinned to Pyomo 0.1.0, and every registered Pyomo version up to 1.0.1 declared `Symbolics = "6.39.1 - 6"` while MTKBase requires Symbolics 7.32.1. The extension also called `Symbolics.symstruct` and `Symbolics.Struct` directly, both removed in Symbolics 7. * Compat bumped to Pyomo 2.0.1. * `Symbolics.symstruct(ConcreteModel)` -> `SymbolicConcreteModel`. Symbolics 7 uses the Julia type itself as the symtype. * `pysym_getproperty` / `_getproperty` deleted here; they now live in Pyomo.jl, which can test them against a real model. * Symbolic indexing goes through `Pyomo.pyomo_getindex`. Symbolics 7 moved the symtype out of `BasicSymbolic`'s type parameter, so the old `BasicSymbolic{Struct{PyomoVar}}` `getindex` dispatch is not expressible; the `Symbolics.value` calls that existed only to reach it are gone too. * `Symbolics.substitute(..., fold = false)` -> `fold = Val(false)`. SymbolicUtils takes `fold` as a `Val`; the `Bool` form throws on every `add_constraint!` and `set_objective!`. `Val` is valid at the declared SymbolicUtils floor (4.35.3). `using Pyomo` is uncommented in the test file and Pyomo added to the optimization test environment, where it was not a dependency. Every Pyomo assertion sits behind `if @isdefined(Pyomo)`, so none of them have run. Co-Authored-By: Chris Rackauckas <accounts@chrisrackauckas.com> Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016GYQeAiC2x9nJ7Mmwwxhe2 * Require Pyomo 2.0.2 2.0.1 shipped without the promote_shape, isinteger and operator fixes; CI on this branch failed against it with ArgumentError: Invalid shapes for cos: (SymbolicUtils.Unknown(-1),). Co-Authored-By: Chris Rackauckas <accounts@chrisrackauckas.com> Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016GYQeAiC2x9nJ7Mmwwxhe2 --------- Co-authored-by: ChrisRackauckas-Claude <accounts@chrisrackauckas.com> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 parent 92ca8a4 commit 8a18d36

4 files changed

Lines changed: 16 additions & 23 deletions

File tree

lib/ModelingToolkitBase/Project.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,7 @@ Pkg = "1"
164164
PreallocationTools = "0.4.27, 1"
165165
PrecompileTools = "1.2.1"
166166
Printf = "1"
167-
Pyomo = "0.1.0"
167+
Pyomo = "2.0.2"
168168
REPL = "1"
169169
Random = "1"
170170
ReadOnlyDicts = "1.0.0"

lib/ModelingToolkitBase/ext/MTKPyomoDynamicOptExt.jl

Lines changed: 12 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ struct PyomoDynamicOptModel{T}
6565
dummy_sym::Union{Num, Symbolics.BasicSymbolic}
6666

6767
function PyomoDynamicOptModel(model, U, V, P, tₛ, is_free_final, tsteps)
68-
@variables MODEL_SYM::Symbolics.symstruct(ConcreteModel) T_SYM DUMMY_SYM
68+
@variables MODEL_SYM::SymbolicConcreteModel T_SYM DUMMY_SYM
6969
model.dU = dae.DerivativeVar(U, wrt = model.t, initialize = 0)
7070
return new{typeof(P)}(
7171
model, U, V, P, tₛ, is_free_final, tsteps, nothing,
@@ -94,15 +94,6 @@ struct PyomoDynamicOptProblem{uType, tType, isinplace, P, F, K} <:
9494
end
9595
end
9696

97-
function pysym_getproperty(s::Union{Num, SymbolicT}, name::Symbol)
98-
return Symbolics.wrap(
99-
SymbolicUtils.term(
100-
_getproperty, Symbolics.unwrap(s), Val{name}(), type = Symbolics.Struct{PyomoVar}
101-
)
102-
)
103-
end
104-
_getproperty(s, name::Val{fieldname}) where {fieldname} = getproperty(s, fieldname)
105-
10697
function MTK.PyomoDynamicOptProblem(
10798
sys::System, op, tspan;
10899
dt = nothing, steps = nothing, tune_parameters = false,
@@ -165,7 +156,7 @@ function MTK.add_constraint!(pmodel::PyomoDynamicOptModel, cons; n_idxs = 1)
165156
cons.lhs - cons.rhs 0
166157
end
167158
expr = Symbolics.substitute(
168-
Symbolics.unwrap(expr), SPECIAL_FUNCTIONS_DICT, fold = false
159+
Symbolics.unwrap(expr), SPECIAL_FUNCTIONS_DICT, fold = Val(false)
169160
)
170161

171162
cons_sym = Symbol("cons", hash(cons))
@@ -192,8 +183,8 @@ function MTK.set_variable_bounds!(m::PyomoDynamicOptModel, sys, pmap, tf, tunabl
192183
MTK.add_constraint!(m, var hi)
193184
end
194185
for (i, (lo, hi)) in param_bounds
195-
P_sym = Symbolics.value(pysym_getproperty(m.model_sym, :P))
196-
p_var = P_sym[i]
186+
P_sym = pysym_getproperty(m.model_sym, :P)
187+
p_var = pyomo_getindex(P_sym, i)
197188
MTK.add_constraint!(m, p_var lo)
198189
MTK.add_constraint!(m, p_var hi)
199190
end
@@ -206,7 +197,7 @@ end
206197

207198
function MTK.set_objective!(pmodel::PyomoDynamicOptModel, expr)
208199
@unpack model, model_sym, t_sym, dummy_sym = pmodel
209-
expr = Symbolics.substitute(expr, SPECIAL_FUNCTIONS_DICT, fold = false)
200+
expr = Symbolics.substitute(expr, SPECIAL_FUNCTIONS_DICT, fold = Val(false))
210201
return if SU.query(isequal(Symbolics.unwrap(t_sym)), expr)
211202
f = eval(Symbolics.build_function(expr, model_sym, t_sym))
212203
model.obj = pyomo.Objective(model.t, rule = Pyomo.pyfunc(f))
@@ -243,13 +234,13 @@ function MTK.lowered_integral(m::PyomoDynamicOptModel, arg, lo, hi)
243234
end
244235

245236
function MTK.lowered_derivative(m::PyomoDynamicOptModel, i)
246-
mdU = Symbolics.value(pysym_getproperty(m.model_sym, :dU))
247-
return Symbolics.unwrap(mdU[i, m.t_sym])
237+
mdU = pysym_getproperty(m.model_sym, :dU)
238+
return Symbolics.unwrap(pyomo_getindex(mdU, i, m.t_sym))
248239
end
249240

250241
function MTK.lowered_var(m::PyomoDynamicOptModel, uv, i, t)
251-
X = Symbolics.value(pysym_getproperty(m.model_sym, uv))
252-
var = t isa Union{Num, SymbolicT} ? X[i, m.t_sym] : X[i, t]
242+
X = pysym_getproperty(m.model_sym, uv)
243+
var = t isa Union{Num, SymbolicT} ? pyomo_getindex(X, i, m.t_sym) : pyomo_getindex(X, i, t)
253244
return Symbolics.unwrap(var)
254245
end
255246

@@ -258,9 +249,9 @@ end
258249
function MTK.get_param_for_pmap(m::ConcreteModel, P::PyomoVar, i)
259250
# Create a symbolic variable that will be used in the pmap
260251
# The actual PyomoVar will be accessed via the symbolic representation
261-
@variables MODEL_SYM::Symbolics.symstruct(ConcreteModel)
262-
P_sym = Symbolics.value(pysym_getproperty(MODEL_SYM, :P))
263-
return Symbolics.unwrap(P_sym[i])
252+
@variables MODEL_SYM::SymbolicConcreteModel
253+
P_sym = pysym_getproperty(MODEL_SYM, :P)
254+
return Symbolics.unwrap(pyomo_getindex(P_sym, i))
264255
end
265256

266257
MTK.needs_individual_tunables(m::ConcreteModel) = true

lib/ModelingToolkitBase/test/optimization/Project.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ OrdinaryDiffEqImplicitTableaus = "75f66a49-58fc-43e3-9173-2340726368f7"
1919
OrdinaryDiffEqSDIRK = "2d112036-d095-4a1e-ab9a-08536f3ecdbf"
2020
OrdinaryDiffEqTsit5 = "b1df2697-797e-41e3-8120-5422d3b24e4a"
2121
OrdinaryDiffEqVerner = "79d7bb75-1356-48c1-b8c0-6832512096c2"
22+
Pyomo = "0e8e1daf-01b5-4eba-a626-3897743a3816"
2223
SafeTestsets = "1bc83da4-3b8d-516f-aca4-4fe02f6d838f"
2324
SciMLTesting = "09d9d899-5365-40a9-917a-5f67fddea283"
2425
Setfield = "efcf1570-3423-57d1-acb7-fd33fddbac46"
@@ -35,5 +36,6 @@ CasADi = "1.0.7"
3536
DataInterpolations = "8.8, 9"
3637
OrdinaryDiffEqExplicitTableaus = "2"
3738
OrdinaryDiffEqImplicitTableaus = "2"
39+
Pyomo = "2.0.2"
3840
SafeTestsets = "0.1, 1"
3941
SciMLTesting = "1, 2.1"

lib/ModelingToolkitBase/test/optimization/dynamic_optimization.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ using Ipopt
1010
using DataInterpolations
1111
using DataInterpolations: ConstantInterpolation
1212
using CasADi
13-
# using Pyomo
13+
using Pyomo
1414
using Test
1515

1616
import DiffEqBase: solve

0 commit comments

Comments
 (0)