Skip to content

Commit 66ac7b3

Browse files
Merge pull request #1660 from SciML/optprobconsexpr
Constraints and expression handling in `OptimizationProblem`
2 parents 44405a7 + 4992044 commit 66ac7b3

File tree

5 files changed

+177
-40
lines changed

5 files changed

+177
-40
lines changed

Project.toml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,9 +79,13 @@ Unitful = "1.1"
7979
julia = "1.6"
8080

8181
[extras]
82+
AmplNLWriter = "7c4d4715-977e-5154-bfe0-e096adeac482"
8283
BenchmarkTools = "6e4b80f9-dd63-53aa-95a3-0cdb28fa8baf"
8384
ForwardDiff = "f6369f11-7733-5829-9624-2563aa707210"
85+
Ipopt = "b6b21f68-93f8-5de0-b562-5493be1d77c9"
86+
Ipopt_jll = "9cc047cb-c261-5740-88fc-0cf96f7bdcc7"
8487
Optimization = "7f7a1694-90dd-40f0-9382-eb1efda571ba"
88+
OptimizationMOI = "fd9f6733-72f4-499f-8506-86b2bdd0dea1"
8589
OptimizationOptimJL = "36348300-93cb-4f02-beb5-3c3902f8871e"
8690
OrdinaryDiffEq = "1dea7af3-3e70-54e6-95c3-0bf5283fa5ed"
8791
Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
@@ -93,4 +97,4 @@ Sundials = "c3572dad-4567-51f8-b174-8c6c989267f4"
9397
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
9498

9599
[targets]
96-
test = ["BenchmarkTools", "ForwardDiff", "Optimization", "OptimizationOptimJL", "OrdinaryDiffEq", "Random", "ReferenceTests", "SafeTestsets", "SteadyStateDiffEq", "Test", "StochasticDiffEq", "Sundials"]
100+
test = ["AmplNLWriter", "BenchmarkTools", "ForwardDiff", "Ipopt", "Ipopt_jll", "Optimization", "OptimizationOptimJL", "OptimizationMOI", "OrdinaryDiffEq", "Random", "ReferenceTests", "SafeTestsets", "SteadyStateDiffEq", "Test", "StochasticDiffEq", "Sundials"]

src/systems/control/controlsystem.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,6 @@ function runge_kutta_discretize(sys::ControlSystem, dt, tspan;
223223
reduce(vcat, reduce(vcat, control_timeseries)))
224224

225225
OptimizationSystem(reduce(+, losses, init = 0), opt_states, ps,
226-
equality_constraints = equalities, name = nameof(sys),
226+
constraints = equalities, name = nameof(sys),
227227
checks = false)
228228
end

src/systems/optimization/optimizationsystem.jl

Lines changed: 139 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,7 @@ struct OptimizationSystem <: AbstractTimeIndependentSystem
2626
"""Array variables."""
2727
var_to_name::Any
2828
observed::Vector{Equation}
29-
equality_constraints::Vector{Equation}
30-
inequality_constraints::Vector
29+
constraints::Vector
3130
"""
3231
Name: the name of the system. These are required to have unique names.
3332
"""
@@ -41,24 +40,22 @@ struct OptimizationSystem <: AbstractTimeIndependentSystem
4140
parameters are not supplied in `ODEProblem`.
4241
"""
4342
defaults::Dict
44-
function OptimizationSystem(op, states, ps, var_to_name, observed, equality_constraints,
45-
inequality_constraints, name, systems, defaults;
43+
function OptimizationSystem(op, states, ps, var_to_name, observed,
44+
constraints, name, systems, defaults;
4645
checks::Bool = true)
4746
if checks
4847
check_units(op)
4948
check_units(observed)
50-
check_units(equality_constraints)
51-
all_dimensionless([states; ps]) || check_units(inequality_constraints)
49+
all_dimensionless([states; ps]) || check_units(constraints)
5250
end
53-
new(op, states, ps, var_to_name, observed, equality_constraints,
54-
inequality_constraints, name, systems, defaults)
51+
new(op, states, ps, var_to_name, observed,
52+
constraints, name, systems, defaults)
5553
end
5654
end
5755

5856
function OptimizationSystem(op, states, ps;
5957
observed = [],
60-
equality_constraints = Equation[],
61-
inequality_constraints = [],
58+
constraints = [],
6259
default_u0 = Dict(),
6360
default_p = Dict(),
6461
defaults = _merge(Dict(default_u0), Dict(default_p)),
@@ -85,7 +82,7 @@ function OptimizationSystem(op, states, ps;
8582
isempty(observed) || collect_var_to_name!(var_to_name, (eq.lhs for eq in observed))
8683
OptimizationSystem(value(op), states, ps, var_to_name,
8784
observed,
88-
equality_constraints, inequality_constraints,
85+
constraints,
8986
name, systems, defaults; checks = checks)
9087
end
9188

@@ -133,6 +130,13 @@ function DiffEqBase.OptimizationProblem(sys::OptimizationSystem, args...; kwargs
133130
DiffEqBase.OptimizationProblem{true}(sys::OptimizationSystem, args...; kwargs...)
134131
end
135132

133+
function rep_pars_vals!(e::Expr, p)
134+
rep_pars_vals!.(e.args, Ref(p))
135+
replace!(e.args, p...)
136+
end
137+
138+
function rep_pars_vals!(e, p) end
139+
136140
"""
137141
```julia
138142
function DiffEqBase.OptimizationProblem{iip}(sys::OptimizationSystem,u0map,
@@ -160,9 +164,26 @@ function DiffEqBase.OptimizationProblem{iip}(sys::OptimizationSystem, u0map,
160164
dvs = states(sys)
161165
ps = parameters(sys)
162166

167+
defs = defaults(sys)
168+
defs = mergedefaults(defs, parammap, ps)
169+
defs = mergedefaults(defs, u0map, dvs)
170+
171+
u0 = varmap_to_vars(u0map, dvs; defaults = defs, tofloat = false)
172+
p = varmap_to_vars(parammap, ps; defaults = defs, tofloat = false, use_union)
173+
lb = varmap_to_vars(lb, dvs; check = false, tofloat = false, use_union)
174+
ub = varmap_to_vars(ub, dvs; check = false, tofloat = false, use_union)
175+
163176
f = generate_function(sys, checkbounds = checkbounds, linenumbers = linenumbers,
164177
expression = Val{false})
165178

179+
obj_expr = toexpr(equations(sys))
180+
pairs_arr = p isa SciMLBase.NullParameters ?
181+
[Symbol(_s) => Expr(:ref, :x, i) for (i, _s) in enumerate(dvs)] :
182+
[
183+
[Symbol(_s) => Expr(:ref, :x, i) for (i, _s) in enumerate(dvs)]...,
184+
[Symbol(_p) => p[i] for (i, _p) in enumerate(ps)]...,
185+
]
186+
rep_pars_vals!(obj_expr, pairs_arr)
166187
if grad
167188
grad_oop, grad_iip = generate_gradient(sys, checkbounds = checkbounds,
168189
linenumbers = linenumbers,
@@ -190,20 +211,46 @@ function DiffEqBase.OptimizationProblem{iip}(sys::OptimizationSystem, u0map,
190211
hess_prototype = nothing
191212
end
192213

193-
_f = DiffEqBase.OptimizationFunction{iip}(f,
194-
SciMLBase.NoAD();
195-
grad = _grad,
196-
hess = _hess,
197-
hess_prototype = hess_prototype)
214+
if length(sys.constraints) > 0
215+
@named cons_sys = NonlinearSystem(sys.constraints, dvs, ps)
216+
cons = generate_function(cons_sys, checkbounds = checkbounds,
217+
linenumbers = linenumbers,
218+
expression = Val{false})[1]
219+
cons_j = generate_jacobian(cons_sys; expression = Val{false}, sparse = sparse)[2]
220+
cons_h = generate_hessian(cons_sys; expression = Val{false}, sparse = sparse)[2]
221+
222+
cons_expr = toexpr(equations(cons_sys))
223+
rep_pars_vals!.(cons_expr, Ref(pairs_arr))
224+
225+
if sparse
226+
cons_jac_prototype = jacobian_sparsity(cons_sys)
227+
cons_hess_prototype = hessian_sparsity(cons_sys)
228+
else
229+
cons_jac_prototype = nothing
230+
cons_hess_prototype = nothing
231+
end
198232

199-
defs = defaults(sys)
200-
defs = mergedefaults(defs, parammap, ps)
201-
defs = mergedefaults(defs, u0map, dvs)
233+
_f = DiffEqBase.OptimizationFunction{iip}(f,
234+
SciMLBase.NoAD();
235+
grad = _grad,
236+
hess = _hess,
237+
hess_prototype = hess_prototype,
238+
cons = cons,
239+
cons_j = cons_j,
240+
cons_h = cons_h,
241+
cons_jac_prototype = cons_jac_prototype,
242+
cons_hess_prototype = cons_hess_prototype,
243+
expr = obj_expr,
244+
cons_expr = cons_expr)
245+
else
246+
_f = DiffEqBase.OptimizationFunction{iip}(f,
247+
SciMLBase.NoAD();
248+
grad = _grad,
249+
hess = _hess,
250+
hess_prototype = hess_prototype,
251+
expr = obj_expr)
252+
end
202253

203-
u0 = varmap_to_vars(u0map, dvs; defaults = defs, tofloat = false)
204-
p = varmap_to_vars(parammap, ps; defaults = defs, tofloat = false, use_union)
205-
lb = varmap_to_vars(lb, dvs; check = false, tofloat = false, use_union)
206-
ub = varmap_to_vars(ub, dvs; check = false, tofloat = false, use_union)
207254
OptimizationProblem{iip}(_f, u0, p; lb = lb, ub = ub, kwargs...)
208255
end
209256

@@ -272,18 +319,74 @@ function OptimizationProblemExpr{iip}(sys::OptimizationSystem, u0,
272319
p = varmap_to_vars(parammap, ps; defaults = defs, tofloat = false, use_union)
273320
lb = varmap_to_vars(lb, dvs; check = false, tofloat = false, use_union)
274321
ub = varmap_to_vars(ub, dvs; check = false, tofloat = false, use_union)
275-
quote
276-
f = $f
277-
p = $p
278-
u0 = $u0
279-
grad = $_grad
280-
hess = $_hess
281-
lb = $lb
282-
ub = $ub
283-
_f = OptimizationFunction{iip}(f, SciMLBase.NoAD();
284-
grad = grad,
285-
hess = hess,
286-
hess_prototype = hess_prototype)
287-
OptimizationProblem{$iip}(_f, u0, p; lb = lb, ub = ub, kwargs...)
322+
323+
obj_expr = toexpr(equations(sys))
324+
pairs_arr = p isa SciMLBase.NullParameters ?
325+
[Symbol(_s) => Expr(:ref, :x, i) for (i, _s) in enumerate(dvs)] :
326+
[
327+
[Symbol(_s) => Expr(:ref, :x, i) for (i, _s) in enumerate(dvs)]...,
328+
[Symbol(_p) => p[i] for (i, _p) in enumerate(ps)]...,
329+
]
330+
rep_pars_vals!(obj_expr, pairs_arr)
331+
332+
if length(sys.constraints) > 0
333+
@named cons_sys = NonlinearSystem(sys.constraints, dvs, ps)
334+
cons = generate_function(cons_sys, checkbounds = checkbounds,
335+
linenumbers = linenumbers,
336+
expression = Val{false})[1]
337+
cons_j = generate_jacobian(cons_sys; expression = Val{false}, sparse = sparse)[2]
338+
339+
cons_h = generate_hessian(cons_sys; expression = Val{false}, sparse = sparse)[2]
340+
341+
cons_expr = toexpr(equations(cons_sys))
342+
rep_pars_vals!.(cons_expr, Ref(pairs_arr))
343+
344+
if sparse
345+
cons_jac_prototype = jacobian_sparsity(cons_sys)
346+
cons_hess_prototype = hessian_sparsity(cons_sys)
347+
else
348+
cons_jac_prototype = nothing
349+
cons_hess_prototype = nothing
350+
end
351+
quote
352+
f = $f
353+
p = $p
354+
u0 = $u0
355+
grad = $_grad
356+
hess = $_hess
357+
lb = $lb
358+
ub = $ub
359+
cons = $cons
360+
cons_j = $cons_j
361+
cons_h = $cons_h
362+
_f = OptimizationFunction{iip}(f, SciMLBase.NoAD();
363+
grad = grad,
364+
hess = hess,
365+
hess_prototype = hess_prototype,
366+
cons = cons,
367+
cons_j = cons_j,
368+
cons_h = cons_h,
369+
cons_jac_prototype = cons_jac_prototype,
370+
cons_hess_prototype = cons_hess_prototype,
371+
expr = obj_expr,
372+
cons_expr = cons_expr)
373+
OptimizationProblem{$iip}(_f, u0, p; lb = lb, ub = ub, kwargs...)
374+
end
375+
else
376+
quote
377+
f = $f
378+
p = $p
379+
u0 = $u0
380+
grad = $_grad
381+
hess = $_hess
382+
lb = $lb
383+
ub = $ub
384+
_f = OptimizationFunction{iip}(f, SciMLBase.NoAD();
385+
grad = grad,
386+
hess = hess,
387+
hess_prototype = hess_prototype,
388+
expr = obj_expr)
389+
OptimizationProblem{$iip}(_f, u0, p; lb = lb, ub = ub, kwargs...)
390+
end
288391
end
289392
end

test/controlsystem.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ sys = runge_kutta_discretize(sys, dt, tspan)
1616
u0 = rand(length(states(sys))) # guess for the state values
1717
prob = OptimizationProblem(sys, u0, [0.1, 0.1], grad = true)
1818
sol = solve(prob, BFGS())
19+
@test prob.f(sol.minimizer, prob.p) < prob.f(u0, prob.p)
1920

2021
# issue #819
2122
@testset "Combined system name collisions" begin

test/optimizationsystem.jl

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
1-
using ModelingToolkit, SparseArrays, Test, Optimization, OptimizationOptimJL
1+
using ModelingToolkit, SparseArrays, Test, Optimization, OptimizationOptimJL,
2+
OptimizationMOI, Ipopt, AmplNLWriter, Ipopt_jll
23

34
@variables x y
45
@parameters a b
56
loss = (a - x)^2 + b * (y - x^2)^2
67
sys1 = OptimizationSystem(loss, [x, y], [a, b], name = :sys1)
7-
sys2 = OptimizationSystem(loss, [x, y], [a, b], name = :sys2)
8+
9+
cons2 = [x^2 + y^2 ~ 0, y * sin(x) - x ~ 0]
10+
sys2 = OptimizationSystem(loss, [x, y], [a, b], name = :sys2, constraints = cons2)
811

912
@variables z
1013
@parameters β
@@ -47,6 +50,32 @@ sol = solve(prob, BFGS(initial_stepnorm = 0.0001), allow_f_increases = true)
4750
sol = solve(prob2, BFGS(initial_stepnorm = 0.0001), allow_f_increases = true)
4851
@test sol.minimum < -1e9
4952

53+
#inequality constraint, the bounds for constraints lcons !== ucons
54+
prob = OptimizationProblem(sys2, [x => 0.0, y => 0.0], [a => 1.0, b => 100.0],
55+
lcons = [-1.0, -1.0], ucons = [500.0, 500.0], grad = true,
56+
hess = true)
57+
sol = solve(prob, IPNewton(), allow_f_increases = true)
58+
@test sol.minimum < 1.0
59+
sol = solve(prob, Ipopt.Optimizer())
60+
@test sol.minimum < 1.0
61+
sol = solve(prob, AmplNLWriter.Optimizer(Ipopt_jll.amplexe))
62+
@test sol.minimum < 1.0
63+
64+
#equality constraint, lcons == ucons
65+
cons2 = [0.0 ~ x^2 + y^2]
66+
sys2 = OptimizationSystem(loss, [x, y], [a, b], name = :sys2, constraints = cons2)
67+
prob = OptimizationProblem(sys2, [x => 0.0, y => 0.0], [a => 1.0, b => 1.0], lcons = [1.0],
68+
ucons = [1.0], grad = true, hess = true)
69+
sol = solve(prob, IPNewton())
70+
@test sol.minimum < 1.0
71+
@test prob.f.cons(sol.minimizer, [1.0, 1.0]) [1.0]
72+
sol = solve(prob, Ipopt.Optimizer())
73+
@test sol.minimum < 1.0
74+
@test prob.f.cons(sol.minimizer, [1.0, 1.0]) [1.0]
75+
sol = solve(prob, AmplNLWriter.Optimizer(Ipopt_jll.amplexe))
76+
@test sol.minimum < 1.0
77+
@test prob.f.cons(sol.minimizer, [1.0, 1.0]) [1.0]
78+
5079
rosenbrock(x, p) = (p[1] - x[1])^2 + p[2] * (x[2] - x[1]^2)^2
5180
x0 = zeros(2)
5281
_p = [1.0, 100.0]

0 commit comments

Comments
 (0)