Skip to content

Commit a36c73a

Browse files
authored
Provide an API for Reformulating High Order Derivatives into 1st Order Derivatives (#396)
* Add `reformulate_high_order_derivatives` * minor fix * minor fix * minor fix * bug fix for nested derivatives * more fixes * minor updates
1 parent bac0a96 commit a36c73a

File tree

6 files changed

+93
-94
lines changed

6 files changed

+93
-94
lines changed

docs/src/manual/derivative.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ InfiniteOpt.make_indexed_derivative_expr
5454
InfiniteOpt.derivative_expr_data
5555
evaluate_derivative
5656
InfiniteOpt.allows_high_order_derivatives
57+
reformulate_high_order_derivatives!(::DerivativeRef)
5758
generative_support_info(::AbstractDerivativeMethod)
5859
support_label(::AbstractDerivativeMethod)
5960
InfiniteOpt.make_reduced_expr

docs/src/manual/expression.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,7 @@ core_object(::GeneralVariableRef)
167167
parameter_group_int_indices(::GeneralVariableRef)
168168
InfiniteOpt.parameter_group_int_index
169169
InfiniteOpt.parameter_group_int_index(::GeneralVariableRef)
170+
reformulate_high_order_derivatives!(::GeneralVariableRef)
170171
```
171172

172173
## Developer Internal Methods

src/TranscriptionOpt/transcribe.jl

Lines changed: 45 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,40 @@ function _format_infinite_info(
215215
)
216216
end
217217

218+
# Helper function for transcribing infinite variables/derivatives
219+
function _transcribe_infinite_variable(backend, vref, base_name, info)
220+
param_nums = InfiniteOpt._parameter_numbers(vref)
221+
group_idxs = InfiniteOpt.parameter_group_int_indices(vref)
222+
prefs = InfiniteOpt.raw_parameter_refs(vref)
223+
# prepare for iterating over its supports
224+
supp_indices = support_index_iterator(backend, group_idxs)
225+
dims = size(supp_indices)[group_idxs]
226+
vrefs = Array{JuMP.VariableRef, length(dims)}(undef, dims...)
227+
supp_type = typeof(Tuple(ones(length(prefs)), prefs))
228+
supps = Array{supp_type, length(dims)}(undef, dims...)
229+
lookup_dict = sizehint!(Dict{Vector{Float64}, JuMP.VariableRef}(), length(vrefs))
230+
# create a variable for each support
231+
for i in supp_indices
232+
supp = index_to_support(backend, i)[param_nums]
233+
new_info = _format_infinite_info(info, supp)
234+
var_idx = i.I[group_idxs]
235+
tuple_supp = Tuple(supp, prefs)
236+
name = _make_var_name(base_name, param_nums, tuple_supp, var_idx)
237+
var = JuMP.ScalarVariable(new_info)
238+
jump_vref = JuMP.add_variable(backend.model, var, name)
239+
@inbounds vrefs[var_idx...] = jump_vref
240+
lookup_dict[supp] = jump_vref
241+
@inbounds supps[var_idx...] = tuple_supp
242+
end
243+
# save the transcription information
244+
data = transcription_data(backend)
245+
gvref = InfiniteOpt.GeneralVariableRef(vref)
246+
data.infvar_lookup[gvref] = lookup_dict
247+
data.infvar_mappings[gvref] = vrefs
248+
data.infvar_supports[gvref] = supps
249+
return
250+
end
251+
218252
"""
219253
transcribe_infinite_variables!(
220254
backend::TranscriptionBackend,
@@ -234,75 +268,14 @@ function transcribe_infinite_variables!(
234268
model::InfiniteOpt.InfiniteModel
235269
)
236270
for (idx, object) in InfiniteOpt._data_dictionary(model, InfiniteOpt.InfiniteVariable)
237-
# get the basic variable information
238-
var = object.variable
271+
info = object.variable.info
239272
base_name = object.name
240-
param_nums = var.parameter_nums
241-
group_idxs = var.group_int_idxs
242-
prefs = var.parameter_refs
243-
# prepare for iterating over its supports
244-
supp_indices = support_index_iterator(backend, group_idxs)
245-
dims = size(supp_indices)[group_idxs]
246-
vrefs = Array{JuMP.VariableRef, length(dims)}(undef, dims...)
247-
supp_type = typeof(Tuple(ones(length(prefs)), prefs))
248-
supps = Array{supp_type, length(dims)}(undef, dims...)
249-
lookup_dict = sizehint!(Dict{Vector{Float64}, JuMP.VariableRef}(), length(vrefs))
250-
# create a variable for each support
251-
for i in supp_indices
252-
supp = index_to_support(backend, i)[param_nums]
253-
info = _format_infinite_info(var.info, supp)
254-
var_idx = i.I[group_idxs]
255-
tuple_supp = Tuple(supp, prefs)
256-
v_name = _make_var_name(base_name, param_nums, tuple_supp, var_idx)
257-
v = JuMP.ScalarVariable(info)
258-
jump_vref = JuMP.add_variable(backend.model, v, v_name)
259-
@inbounds vrefs[var_idx...] = jump_vref
260-
lookup_dict[supp] = jump_vref
261-
@inbounds supps[var_idx...] = tuple_supp
262-
end
263-
# save the transcription information
264-
ivref = InfiniteOpt.GeneralVariableRef(model, idx)
265-
data = transcription_data(backend)
266-
data.infvar_lookup[ivref] = lookup_dict
267-
data.infvar_mappings[ivref] = vrefs
268-
data.infvar_supports[ivref] = supps
273+
vref = InfiniteOpt.InfiniteVariableRef(model, idx)
274+
_transcribe_infinite_variable(backend, vref, base_name, info)
269275
end
270276
return
271277
end
272278

273-
function _transcribe_derivative_variable(dref, d, backend)
274-
base_name = InfiniteOpt.variable_string(MIME("text/plain"), dispatch_variable_ref(dref))
275-
param_nums = InfiniteOpt._parameter_numbers(d.variable_ref)
276-
group_idxs = InfiniteOpt.parameter_group_int_indices(d.variable_ref)
277-
prefs = InfiniteOpt.raw_parameter_refs(dref)
278-
# prepare for iterating over its supports
279-
supp_indices = support_index_iterator(backend, group_idxs)
280-
dims = size(supp_indices)[group_idxs]
281-
vrefs = Array{JuMP.VariableRef, length(dims)}(undef, dims...)
282-
supp_type = typeof(Tuple(ones(length(prefs)), prefs))
283-
supps = Array{supp_type, length(dims)}(undef, dims...)
284-
lookup_dict = sizehint!(Dict{Vector{Float64}, JuMP.VariableRef}(), length(vrefs))
285-
# create a variable for each support
286-
for i in supp_indices
287-
supp = index_to_support(backend, i)[param_nums]
288-
info = _format_infinite_info(d.info, supp)
289-
var_idx = i.I[group_idxs]
290-
tuple_supp = Tuple(supp, prefs)
291-
d_name = _make_var_name(base_name, param_nums, tuple_supp, var_idx)
292-
d_var = JuMP.ScalarVariable(info)
293-
jump_vref = JuMP.add_variable(backend.model, d_var, d_name)
294-
@inbounds vrefs[var_idx...] = jump_vref
295-
lookup_dict[supp] = jump_vref
296-
@inbounds supps[var_idx...] = tuple_supp
297-
end
298-
# save the transcription information
299-
data = transcription_data(backend)
300-
data.infvar_lookup[dref] = lookup_dict
301-
data.infvar_mappings[dref] = vrefs
302-
data.infvar_supports[dref] = supps
303-
return
304-
end
305-
306279
"""
307280
transcribe_derivative_variables!(
308281
backend::TranscriptionBackend,
@@ -323,24 +296,16 @@ function transcribe_derivative_variables!(
323296
backend::TranscriptionBackend,
324297
model::InfiniteOpt.InfiniteModel
325298
)
299+
# convert any high order derivatives into 1st order ones if required by method
300+
# TODO find way of doing this without modifying `model`
301+
InfiniteOpt.reformulate_high_order_derivatives!(model)
302+
# transcribe all the derivatives
326303
for (idx, object) in InfiniteOpt._data_dictionary(model, InfiniteOpt.Derivative)
327304
# get the basic derivative information
328-
dref = InfiniteOpt.GeneralVariableRef(model, idx)
329-
d = object.variable
330-
method = InfiniteOpt.derivative_method(dref)
331-
# if needed process lower order derivatives
332-
if !InfiniteOpt.allows_high_order_derivatives(method) && d.order > 1
333-
for o in d.order-1:-1:1
334-
if !haskey(model.deriv_lookup, (d.variable_ref, d.parameter_ref, o))
335-
info = InfiniteOpt._DefaultDerivativeInfo
336-
new_d = InfiniteOpt.Derivative(info, d.variable_ref, d.parameter_ref, o)
337-
new_dref = InfiniteOpt.add_derivative(model, new_d)
338-
_transcribe_derivative_variable(new_dref, d, backend)
339-
end
340-
end
341-
end
342-
# process the derivative
343-
_transcribe_derivative_variable(dref, d, backend)
305+
dref = InfiniteOpt.DerivativeRef(model, idx)
306+
base_name = InfiniteOpt.variable_string(MIME("text/plain"), dref)
307+
info = object.variable.info
308+
_transcribe_infinite_variable(backend, dref, base_name, info)
344309
end
345310
return
346311
end
@@ -927,10 +892,6 @@ function transcribe_derivative_evaluations!(
927892
if !InfiniteOpt.has_derivative_constraints(dref)
928893
# generate the evaluation expressions
929894
vref = object.variable.variable_ref
930-
if !InfiniteOpt.allows_high_order_derivatives(method) && order > 1
931-
d_idx = model.deriv_lookup[vref, object.variable.parameter_ref, order - 1]
932-
vref = InfiniteOpt.GeneralVariableRef(model, d_idx)
933-
end
934895
exprs = InfiniteOpt.evaluate_derivative(dref, vref, method, backend)
935896
# prepare the iteration helpers
936897
param_group_int_idx = InfiniteOpt.parameter_group_int_index(pref)

src/derivative_evaluations.jl

Lines changed: 44 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -563,6 +563,42 @@ end
563563
################################################################################
564564
# EVALUATION METHODS
565565
################################################################################
566+
"""
567+
reformulate_high_order_derivatives(dref::DerivativeRef)::Vector{GeneralVariableRef}
568+
569+
Convert higher-order derivatives into first-order derivatives and add them to the
570+
model if the derivative method employed by `dref` does not support higher-order
571+
derivatives. This is intended as an internal method and is automatically called
572+
when evaluating derivatives. For convenience, this method can also be called for
573+
an entire model via `reformulate_high_order_derivatives(model::InfiniteModel)`.
574+
"""
575+
function reformulate_high_order_derivatives!(dref::DerivativeRef)
576+
method = derivative_method(dref)
577+
d = core_object(dref)
578+
new_drefs = GeneralVariableRef[]
579+
# TODO: is there a way to do this without modifying the model?
580+
if !InfiniteOpt.allows_high_order_derivatives(method) && d.order > 1
581+
model = JuMP.owner_model(dref)
582+
ivref = d.variable_ref
583+
for _ in 1:d.order-1
584+
info = _DefaultDerivativeInfo
585+
new_d = Derivative(info, ivref, d.parameter_ref, 1)
586+
new_dref = add_derivative(model, new_d)
587+
push!(new_drefs, new_dref)
588+
ivref = new_dref
589+
end
590+
new_d = Derivative(d.info, ivref, d.parameter_ref, 1)
591+
_set_core_object(dref, new_d)
592+
end
593+
return new_drefs
594+
end
595+
function reformulate_high_order_derivatives!(model::InfiniteModel)
596+
for dref in all_derivatives(model)
597+
reformulate_high_order_derivatives!(dref)
598+
end
599+
return
600+
end
601+
566602
"""
567603
evaluate(dref::DerivativeRef)::Nothing
568604
@@ -594,21 +630,22 @@ Feasibility
594630
0.5 ∂/∂t[T(t)](2) - T(2) + T(1.5) = 0.0
595631
```
596632
"""
597-
function evaluate(dref::DerivativeRef)
633+
function evaluate(dref::DerivativeRef; _init_call::Bool = true)
598634
if !has_derivative_constraints(dref)
599635
# collect the basic info
600636
method = derivative_method(dref)
601637
model = JuMP.owner_model(dref)
602638
constr_list = _derivative_constraint_dependencies(dref)
603639
gvref = GeneralVariableRef(dref)
604640
# recursively build 1st order derivatives if necessary
605-
vref = derivative_argument(dref)
606-
if !allows_high_order_derivatives(method) && derivative_order(dref) > 1
607-
pref = operator_parameter(dref)
608-
vref = _build_add_derivative(vref, pref, derivative_order(dref) - 1)
609-
evaluate(vref)
641+
if _init_call
642+
new_drefs = reformulate_high_order_derivatives!(dref)
643+
for new_dref in new_drefs
644+
evaluate(dispatch_variable_ref(new_dref); _init_call = false)
645+
end
610646
end
611-
# get the expressions
647+
# get the expressions
648+
vref = derivative_argument(dref)
612649
exprs = evaluate_derivative(gvref, vref, method, model)
613650
# add the constraints
614651
for expr in exprs

src/general_variables.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -784,7 +784,7 @@ end
784784
# Define measure queries and their fallbacks
785785
for op = (:derivative_argument, :operator_parameter, :evaluate,
786786
:derivative_constraints, :delete_derivative_constraints,
787-
:derivative_order)
787+
:derivative_order, :reformulate_high_order_derivatives!)
788788
@eval begin
789789
# define the fallback method
790790
function $op(dref::DispatchVariableRef)

test/TranscriptionOpt/transcribe.jl

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -120,8 +120,7 @@
120120
@test IOTO.transcription_variable(dy, tb) isa Matrix{VariableRef}
121121
@test IOTO.transcription_variable(dx3, tb) isa Vector{VariableRef}
122122
@test name(IOTO.transcription_variable(dx, tb)[1]) == "d/dpar[x(par)](0.0)"
123-
@test name(IOTO.transcription_variable(dx3, tb)[1]) == "d^3/dpar^3[x(par)](0.0)"
124-
@test name(IOTO.transcription_variable(deriv(dx, par), tb)[1]) == "d²/dpar²[x(par)](0.0)"
123+
@test name(IOTO.transcription_variable(dx3, tb)[1]) == "d/dpar[d/dpar[d/dpar[x(par)]]](0.0)"
125124
possible = Sys.iswindows() ? "d/dpar[y(par, pars)](1.0, [0.0, 0.0])" : "∂/∂par[y(par, pars)](1.0, [0.0, 0.0])"
126125
@test name(IOTO.transcription_variable(dy, tb)[2, 1]) == possible
127126
@test has_upper_bound(IOTO.transcription_variable(dx, tb)[1])

0 commit comments

Comments
 (0)