@@ -3,6 +3,7 @@ using ModelingToolkit
33using  JuMP, InfiniteOpt
44using  DiffEqDevTools, DiffEqBase
55using  LinearAlgebra
6+ using  StaticArrays
67const  MTK =  ModelingToolkit
78
89struct  JuMPControlProblem{uType, tType, isinplace, P, F, K} < :
@@ -14,7 +15,7 @@ struct JuMPControlProblem{uType, tType, isinplace, P, F, K} <:
1415    model:: InfiniteModel 
1516    kwargs:: K 
1617
17-     function  JuMPControlProblem (f, u0, tspan, p, model;  kwargs... )
18+     function  JuMPControlProblem (f, u0, tspan, p, model,  kwargs... )
1819        new{typeof (u0), typeof (tspan), SciMLBase. isinplace (f, 5 ),
1920            typeof (p), typeof (f), typeof (kwargs)}(f, u0, tspan, p, model, kwargs)
2021    end 
@@ -51,14 +52,18 @@ The constraints are:
5152- The solver constraints that encode the time-stepping used by the solver 
5253""" 
5354function  MTK. JuMPControlProblem (sys:: ODESystem , u0map, tspan, pmap;
54-         dt =  error (" dt must be provided for JuMPControlProblem."  ),
55+         dt =  nothing ,
56+         steps =  nothing ,
5557        guesses =  Dict (), kwargs... )
5658    MTK. warn_overdetermined (sys, u0map)
5759    _u0map =  has_alg_eqs (sys) ?  u0map :  merge (Dict (u0map), Dict (guesses))
5860    f, u0, p =  MTK. process_SciMLProblem (ControlFunction, sys, _u0map, pmap;
5961        t =  tspan != =  nothing  ?  tspan[1 ] :  tspan, kwargs... )
6062
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+ 
6267    JuMPControlProblem (f, u0, tspan, p, model, kwargs... )
6368end 
6469
@@ -73,82 +78,115 @@ Related to `JuMPControlProblem`, but directly adds the differential equations
7378of the system as derivative constraints, rather than using a solver tableau. 
7479""" 
7580function  MTK. InfiniteOptControlProblem (sys:: ODESystem , u0map, tspan, pmap;
76-         dt =  error (" dt must be provided for InfiniteOptControlProblem."  ),
81+         dt =  nothing ,
82+         steps =  nothing ,
7783        guesses =  Dict (), kwargs... )
7884    MTK. warn_overdetermined (sys, u0map)
7985    _u0map =  has_alg_eqs (sys) ?  u0map :  merge (Dict (u0map), Dict (guesses))
8086    f, u0, p =  MTK. process_SciMLProblem (ControlFunction, sys, _u0map, pmap;
8187        t =  tspan != =  nothing  ?  tspan[1 ] :  tspan, kwargs... )
8288
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)
8594    InfiniteOptControlProblem (f, u0, tspan, p, model, kwargs... )
8695end 
8796
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 )
8999    ctrls =  MTK. unbound_inputs (sys)
90100    states =  unknowns (sys)
91101    model =  InfiniteModel ()
92102
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])
96120
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 )
100124
101125    stidxmap =  Dict ([v =>  i for  (i, v) in  enumerate (states)])
102126    u0_idxs =  has_alg_eqs (sys) ?  collect (1 : length (states)) : 
103127              [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 ])
105129    return  model
106130end 
107131
108- function  set_bounds ! (model, sys)
132+ function  set_jump_bounds ! (model, sys, pmap )
109133    U =  model[:U ]
110134    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 
114140    end 
115141
116142    V =  model[:V ]
117143    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 
121149    end 
122150end 
123151
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 )
125153    jcosts =  MTK. get_costs (sys)
126154    consolidate =  MTK. get_consolidate (sys)
127155    if  isnothing (jcosts) ||  isempty (jcosts)
128156        @objective (model, Min, 0 )
129157        return 
130158    end 
131159    jcosts =  substitute_jump_vars (model, sys, pmap, jcosts)
160+     tₛ =  is_free_t ?  model[:tf ] :  1 
132161
133162    #  Substitute integral
134163    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+ 
136166    intmap =  Dict ()
137-     
138167    for  int in  MTK. collect_applied_operators (jcosts, Symbolics. Integral)
168+         op =  MTK. operation (int)
139169        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 )
142172    end 
143173    jcosts =  map (c ->  Symbolics. substitute (c, intmap), jcosts)
144174    @objective (model, Min, consolidate (jcosts))
145175end 
146176
147- function  add_user_constraints! (model:: InfiniteModel , sys, pmap)
177+ function  add_user_constraints! (model:: InfiniteModel , sys, pmap; is_free_t  =   false )
148178    conssys =  MTK. get_constraintsystem (sys)
149179    jconstraints =  isnothing (conssys) ?  nothing  :  MTK. get_constraints (conssys)
150180    (isnothing (jconstraints) ||  isempty (jconstraints)) &&  return  nothing 
151181
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+ 
152190    jconstraints =  substitute_jump_vars (model, sys, pmap, jconstraints)
153191    for  (i, cons) in  enumerate (jconstraints)
154192        if  cons isa  Equation
@@ -188,23 +226,24 @@ end
188226
189227is_explicit (tableau) =  tableau isa  DiffEqDevTools. ExplicitRKTableau
190228
191- function  add_infopt_solve_constraints! (model:: InfiniteModel , sys, pmap)
229+ function  add_infopt_solve_constraints! (model:: InfiniteModel , sys, pmap; is_free_t  =   false )
192230    #  Differential equations
193231    U =  model[:U ]
194232    t =  model[:t ]
195233    D =  Differential (MTK. get_iv (sys))
196234    diffsubmap =  Dict ([D (U[i]) =>  ∂ (U[i], t) for  i in  1 : length (U)])
235+     tₛ =  is_free_t ?  model[:tf ] :  1 
197236
198237    diff_eqs =  substitute_jump_vars (model, sys, pmap, diff_equations (sys))
199238    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)
201240
202241    #  Algebraic equations
203242    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)
205244end 
206245
207- function  add_jump_solve_constraints! (prob, tableau)
246+ function  add_jump_solve_constraints! (prob, tableau; is_free_t  =   false )
208247    A =  tableau. A
209248    α =  tableau. α
210249    c =  tableau. c
@@ -214,6 +253,7 @@ function add_jump_solve_constraints!(prob, tableau)
214253    t =  model[:t ]
215254    tsteps =  supports (model[:t ])
216255    pop! (tsteps)
256+     tₛ =  is_free_t ?  model[:tf ] :  1 
217257    dt =  tsteps[2 ] -  tsteps[1 ]
218258
219259    U =  model[:U ]
@@ -227,7 +267,7 @@ function add_jump_solve_constraints!(prob, tableau)
227267                ΔU =  sum ([A[i, j] *  K[j] for  j in  1 : (i -  1 )], init =  zeros (nᵤ))
228268                Uₙ =  [U[i](τ) +  ΔU[i] *  dt for  i in  1 : nᵤ]
229269                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 
231271                push! (K, Kₙ)
232272            end 
233273            ΔU =  dt *  sum ([α[i] *  K[i] for  i in  1 : length (α)])
@@ -237,17 +277,17 @@ function add_jump_solve_constraints!(prob, tableau)
237277        end 
238278    else 
239279        @variable (model, K[1 : length (α), 1 : nᵤ], Infinite (t), start= tsteps[1 ])
280+         ΔUs =  A *  K
281+         ΔU_tot =  dt *  (K'  *  α)
240282        for  τ in  tsteps
241-             ΔUs =  A *  K
242283            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($τ )"  )
247288            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($τ )"  )
251291        end 
252292    end 
253293end 
@@ -281,7 +321,7 @@ function DiffEqBase.solve(
281321            delete (model, var)
282322        end 
283323    end 
284-     add_jump_solve_constraints! (prob, tableau)
324+     add_jump_solve_constraints! (prob, tableau; is_free_t  =   haskey (model,  :tf ) )
285325    _solve (prob, jump_solver, ode_solver)
286326end 
287327
@@ -304,9 +344,10 @@ function _solve(prob::AbstractOptimalControlProblem, jump_solver, solver)
304344    tstatus =  termination_status (model)
305345    pstatus =  primal_status (model)
306346    ! 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 ."  )
308348
309-     ts =  supports (model[:t ])
349+     tf =  haskey (model, :tf ) ?  value (model[:tf ]) :  1 
350+     ts =  tf *  supports (model[:t ])
310351    U_vals =  value .(model[:U ])
311352    U_vals =  [[U_vals[i][j] for  i in  1 : length (U_vals)] for  j in  1 : length (ts)]
312353    sol =  DiffEqBase. build_solution (prob, solver, ts, U_vals)
0 commit comments