@@ -3,6 +3,7 @@ using ModelingToolkit
3
3
using JuMP, InfiniteOpt
4
4
using DiffEqDevTools, DiffEqBase
5
5
using LinearAlgebra
6
+ using StaticArrays
6
7
const MTK = ModelingToolkit
7
8
8
9
struct JuMPControlProblem{uType, tType, isinplace, P, F, K} < :
@@ -14,7 +15,7 @@ struct JuMPControlProblem{uType, tType, isinplace, P, F, K} <:
14
15
model:: InfiniteModel
15
16
kwargs:: K
16
17
17
- function JuMPControlProblem (f, u0, tspan, p, model; kwargs... )
18
+ function JuMPControlProblem (f, u0, tspan, p, model, kwargs... )
18
19
new{typeof (u0), typeof (tspan), SciMLBase. isinplace (f, 5 ),
19
20
typeof (p), typeof (f), typeof (kwargs)}(f, u0, tspan, p, model, kwargs)
20
21
end
@@ -51,14 +52,18 @@ The constraints are:
51
52
- The solver constraints that encode the time-stepping used by the solver
52
53
"""
53
54
function MTK. JuMPControlProblem (sys:: ODESystem , u0map, tspan, pmap;
54
- dt = error (" dt must be provided for JuMPControlProblem." ),
55
+ dt = nothing ,
56
+ steps = nothing ,
55
57
guesses = Dict (), kwargs... )
56
58
MTK. warn_overdetermined (sys, u0map)
57
59
_u0map = has_alg_eqs (sys) ? u0map : merge (Dict (u0map), Dict (guesses))
58
60
f, u0, p = MTK. process_SciMLProblem (ControlFunction, sys, _u0map, pmap;
59
61
t = tspan != = nothing ? tspan[1 ] : tspan, kwargs... )
60
62
61
- model = init_model (sys, tspan[1 ]: dt: tspan[2 ], u0map, pmap, u0)
63
+ pmap = MTK. todict (pmap)
64
+ steps, is_free_t = MTK. process_tspan (tspan, dt, steps)
65
+ model = init_model (sys, tspan, steps, u0map, pmap, u0; is_free_t)
66
+
62
67
JuMPControlProblem (f, u0, tspan, p, model, kwargs... )
63
68
end
64
69
@@ -73,82 +78,115 @@ Related to `JuMPControlProblem`, but directly adds the differential equations
73
78
of the system as derivative constraints, rather than using a solver tableau.
74
79
"""
75
80
function MTK. InfiniteOptControlProblem (sys:: ODESystem , u0map, tspan, pmap;
76
- dt = error (" dt must be provided for InfiniteOptControlProblem." ),
81
+ dt = nothing ,
82
+ steps = nothing ,
77
83
guesses = Dict (), kwargs... )
78
84
MTK. warn_overdetermined (sys, u0map)
79
85
_u0map = has_alg_eqs (sys) ? u0map : merge (Dict (u0map), Dict (guesses))
80
86
f, u0, p = MTK. process_SciMLProblem (ControlFunction, sys, _u0map, pmap;
81
87
t = tspan != = nothing ? tspan[1 ] : tspan, kwargs... )
82
88
83
- model = init_model (sys, tspan[1 ]: dt: tspan[2 ], u0map, pmap, u0)
84
- add_infopt_solve_constraints! (model, sys, pmap)
89
+ pmap = MTK. todict (pmap)
90
+ steps, is_free_t = MTK. process_tspan (tspan, dt, steps)
91
+ model = init_model (sys, tspan, steps, u0map, pmap, u0; is_free_t)
92
+
93
+ add_infopt_solve_constraints! (model, sys, pmap; is_free_t)
85
94
InfiniteOptControlProblem (f, u0, tspan, p, model, kwargs... )
86
95
end
87
96
88
- function init_model (sys, tsteps, u0map, pmap, u0)
97
+ # Initialize InfiniteOpt model.
98
+ function init_model (sys, tspan, steps, u0map, pmap, u0; is_free_t = false )
89
99
ctrls = MTK. unbound_inputs (sys)
90
100
states = unknowns (sys)
91
101
model = InfiniteModel ()
92
102
93
- @infinite_parameter (model, t in [tsteps[1 ], tsteps[end ]], num_supports= length (tsteps))
94
- @variable (model, U[i = 1 : length (states)], Infinite (t))
95
- @variable (model, V[1 : length (ctrls)], Infinite (t))
103
+ if is_free_t
104
+ (ts_sym, te_sym) = tspan
105
+ @variable (model, tf, start = pmap[te_sym])
106
+ hasbounds (te_sym) && begin
107
+ lo, hi = getbounds (te_sym)
108
+ set_lower_bound (tf, lo)
109
+ set_upper_bound (tf, hi)
110
+ end
111
+ pmap[ts_sym] = 0
112
+ pmap[te_sym] = 1
113
+ tspan = (0 , 1 )
114
+ end
115
+
116
+ @infinite_parameter (model, t in [tspan[1 ], tspan[2 ]], num_supports = steps)
117
+ @variable (model, U[i = 1 : length (states)], Infinite (t), start = u0[i])
118
+ c0 = [pmap[c] for c in ctrls]
119
+ @variable (model, V[i = 1 : length (ctrls)], Infinite (t), start = c0[i])
96
120
97
- set_bounds ! (model, sys)
98
- add_jump_cost_function! (model, sys, (tsteps [1 ], tsteps [2 ]), pmap)
99
- add_user_constraints! (model, sys, pmap)
121
+ set_jump_bounds ! (model, sys, pmap )
122
+ add_jump_cost_function! (model, sys, (tspan [1 ], tspan [2 ]), pmap; is_free_t )
123
+ add_user_constraints! (model, sys, pmap; is_free_t )
100
124
101
125
stidxmap = Dict ([v => i for (i, v) in enumerate (states)])
102
126
u0_idxs = has_alg_eqs (sys) ? collect (1 : length (states)) :
103
127
[stidxmap[k] for (k, v) in u0map]
104
- add_initial_constraints! (model, u0, u0_idxs, tsteps [1 ])
128
+ add_initial_constraints! (model, u0, u0_idxs, tspan [1 ])
105
129
return model
106
130
end
107
131
108
- function set_bounds ! (model, sys)
132
+ function set_jump_bounds ! (model, sys, pmap )
109
133
U = model[:U ]
110
134
for (i, u) in enumerate (unknowns (sys))
111
- lo, hi = MTK. getbounds (u)
112
- set_lower_bound (U[i], lo)
113
- set_upper_bound (U[i], hi)
135
+ if MTK. hasbounds (u)
136
+ lo, hi = MTK. getbounds (u)
137
+ set_lower_bound (U[i], Symbolics. fixpoint_sub (lo, pmap))
138
+ set_upper_bound (U[i], Symbolics. fixpoint_sub (hi, pmap))
139
+ end
114
140
end
115
141
116
142
V = model[:V ]
117
143
for (i, v) in enumerate (MTK. unbound_inputs (sys))
118
- lo, hi = MTK. getbounds (v)
119
- set_lower_bound (V[i], lo)
120
- set_upper_bound (V[i], hi)
144
+ if MTK. hasbounds (v)
145
+ lo, hi = MTK. getbounds (v)
146
+ set_lower_bound (V[i], Symbolics. fixpoint_sub (lo, pmap))
147
+ set_upper_bound (V[i], Symbolics. fixpoint_sub (hi, pmap))
148
+ end
121
149
end
122
150
end
123
151
124
- function add_jump_cost_function! (model:: InfiniteModel , sys, tspan, pmap)
152
+ function add_jump_cost_function! (model:: InfiniteModel , sys, tspan, pmap; is_free_t = false )
125
153
jcosts = MTK. get_costs (sys)
126
154
consolidate = MTK. get_consolidate (sys)
127
155
if isnothing (jcosts) || isempty (jcosts)
128
156
@objective (model, Min, 0 )
129
157
return
130
158
end
131
159
jcosts = substitute_jump_vars (model, sys, pmap, jcosts)
160
+ tₛ = is_free_t ? model[:tf ] : 1
132
161
133
162
# Substitute integral
134
163
iv = MTK. get_iv (sys)
135
- jcosts = map (c -> Symbolics. substitute (c, ∫ => Symbolics. Integral (iv in tspan)), jcosts)
164
+ jcosts = map (c -> Symbolics. substitute (c, MTK.∫ () => Symbolics. Integral (iv in tspan)), jcosts)
165
+
136
166
intmap = Dict ()
137
-
138
167
for int in MTK. collect_applied_operators (jcosts, Symbolics. Integral)
168
+ op = MTK. operation (int)
139
169
arg = only (arguments (MTK. value (int)))
140
- lower_bound, upper_bound = (int . domain. domain. left, int . domain. domain. right)
141
- intmap[int] = InfiniteOpt.∫ (arg, iv; lower_bound, upper_bound )
170
+ lo, hi = (op . domain. domain. left, op . domain. domain. right)
171
+ intmap[int] = tₛ * InfiniteOpt.∫ (arg, model[ :t ], lo, hi )
142
172
end
143
173
jcosts = map (c -> Symbolics. substitute (c, intmap), jcosts)
144
174
@objective (model, Min, consolidate (jcosts))
145
175
end
146
176
147
- function add_user_constraints! (model:: InfiniteModel , sys, pmap)
177
+ function add_user_constraints! (model:: InfiniteModel , sys, pmap; is_free_t = false )
148
178
conssys = MTK. get_constraintsystem (sys)
149
179
jconstraints = isnothing (conssys) ? nothing : MTK. get_constraints (conssys)
150
180
(isnothing (jconstraints) || isempty (jconstraints)) && return nothing
151
181
182
+ if is_free_t
183
+ for u in MTK. get_unknowns (conssys)
184
+ x = MTK. operation (u)
185
+ t = only (arguments (u))
186
+ MTK. symbolic_type (t) === NotSymbolic () && error (" Provided specific time constraint in a free final time problem. This is not supported by the JuMP/InfiniteOpt collocation solvers. The offending variable is $u ." )
187
+ end
188
+ end
189
+
152
190
jconstraints = substitute_jump_vars (model, sys, pmap, jconstraints)
153
191
for (i, cons) in enumerate (jconstraints)
154
192
if cons isa Equation
@@ -188,23 +226,24 @@ end
188
226
189
227
is_explicit (tableau) = tableau isa DiffEqDevTools. ExplicitRKTableau
190
228
191
- function add_infopt_solve_constraints! (model:: InfiniteModel , sys, pmap)
229
+ function add_infopt_solve_constraints! (model:: InfiniteModel , sys, pmap; is_free_t = false )
192
230
# Differential equations
193
231
U = model[:U ]
194
232
t = model[:t ]
195
233
D = Differential (MTK. get_iv (sys))
196
234
diffsubmap = Dict ([D (U[i]) => ∂ (U[i], t) for i in 1 : length (U)])
235
+ tₛ = is_free_t ? model[:tf ] : 1
197
236
198
237
diff_eqs = substitute_jump_vars (model, sys, pmap, diff_equations (sys))
199
238
diff_eqs = map (e -> Symbolics. substitute (e, diffsubmap), diff_eqs)
200
- @constraint (model, D[i = 1 : length (diff_eqs)], diff_eqs[i]. lhs== diff_eqs[i]. rhs)
239
+ @constraint (model, D[i = 1 : length (diff_eqs)], diff_eqs[i]. lhs == tₛ * diff_eqs[i]. rhs)
201
240
202
241
# Algebraic equations
203
242
alg_eqs = substitute_jump_vars (model, sys, pmap, alg_equations (sys))
204
- @constraint (model, A[i = 1 : length (alg_eqs)], alg_eqs[i]. lhs== alg_eqs[i]. rhs)
243
+ @constraint (model, A[i = 1 : length (alg_eqs)], alg_eqs[i]. lhs == alg_eqs[i]. rhs)
205
244
end
206
245
207
- function add_jump_solve_constraints! (prob, tableau)
246
+ function add_jump_solve_constraints! (prob, tableau; is_free_t = false )
208
247
A = tableau. A
209
248
α = tableau. α
210
249
c = tableau. c
@@ -214,6 +253,7 @@ function add_jump_solve_constraints!(prob, tableau)
214
253
t = model[:t ]
215
254
tsteps = supports (model[:t ])
216
255
pop! (tsteps)
256
+ tₛ = is_free_t ? model[:tf ] : 1
217
257
dt = tsteps[2 ] - tsteps[1 ]
218
258
219
259
U = model[:U ]
@@ -227,7 +267,7 @@ function add_jump_solve_constraints!(prob, tableau)
227
267
ΔU = sum ([A[i, j] * K[j] for j in 1 : (i - 1 )], init = zeros (nᵤ))
228
268
Uₙ = [U[i](τ) + ΔU[i] * dt for i in 1 : nᵤ]
229
269
Vₙ = [V[i](τ) for i in 1 : nᵥ]
230
- Kₙ = f (Uₙ, Vₙ, p, τ + h * dt)
270
+ Kₙ = tₛ * f (Uₙ, Vₙ, p, τ + h * dt) # scale the time
231
271
push! (K, Kₙ)
232
272
end
233
273
ΔU = dt * sum ([α[i] * K[i] for i in 1 : length (α)])
@@ -237,17 +277,17 @@ function add_jump_solve_constraints!(prob, tableau)
237
277
end
238
278
else
239
279
@variable (model, K[1 : length (α), 1 : nᵤ], Infinite (t), start= tsteps[1 ])
280
+ ΔUs = A * K
281
+ ΔU_tot = dt * (K' * α)
240
282
for τ in tsteps
241
- ΔUs = A * K
242
283
for (i, h) in enumerate (c)
243
- ΔU = ΔUs[i, :]
244
- Uₙ = [U[j] + ΔU[j] * dt for j in 1 : nᵤ]
245
- @constraint (model, [j in 1 : nᵤ], K[i, j]== f (Uₙ, V, p, τ + h * dt)[j],
246
- DomainRestrictions (t => τ), base_name= " solve_K($τ )" )
284
+ ΔU = @view ΔUs[i, :]
285
+ Uₙ = U + ΔU * dt
286
+ @constraint (model, [j = 1 : nᵤ], K[i, j](τ) == tₛ * f (Uₙ, V, p, τ + h * dt)[j],
287
+ DomainRestrictions (t => τ + h * dt ), base_name= " solve_K($τ )" )
247
288
end
248
- ΔU = dt * sum ([α[i] * K[i, :] for i in 1 : length (α)])
249
- @constraint (model, [n = 1 : nᵤ], U[n] + ΔU[n]== U[n](τ + dt),
250
- DomainRestrictions (t => τ), base_name= " solve_U($τ )" )
289
+ @constraint (model, [n = 1 : nᵤ], U[n](τ) + ΔU_tot[n] == U[n](τ + dt),
290
+ DomainRestrictions (t => τ), base_name= " solve_U($τ )" )
251
291
end
252
292
end
253
293
end
@@ -281,7 +321,7 @@ function DiffEqBase.solve(
281
321
delete (model, var)
282
322
end
283
323
end
284
- add_jump_solve_constraints! (prob, tableau)
324
+ add_jump_solve_constraints! (prob, tableau; is_free_t = haskey (model, :tf ) )
285
325
_solve (prob, jump_solver, ode_solver)
286
326
end
287
327
@@ -304,9 +344,10 @@ function _solve(prob::AbstractOptimalControlProblem, jump_solver, solver)
304
344
tstatus = termination_status (model)
305
345
pstatus = primal_status (model)
306
346
! has_values (model) &&
307
- error (" Model not solvable; please report this to github.com/SciML/ModelingToolkit.jl." )
347
+ error (" Model not solvable; please report this to github.com/SciML/ModelingToolkit.jl with a MWE ." )
308
348
309
- ts = supports (model[:t ])
349
+ tf = haskey (model, :tf ) ? value (model[:tf ]) : 1
350
+ ts = tf * supports (model[:t ])
310
351
U_vals = value .(model[:U ])
311
352
U_vals = [[U_vals[i][j] for i in 1 : length (U_vals)] for j in 1 : length (ts)]
312
353
sol = DiffEqBase. build_solution (prob, solver, ts, U_vals)
0 commit comments