Skip to content

Commit dc1e303

Browse files
authored
Refactor to directly support higher order derivatives (#341)
* Refactor to support higher order derivatives * fix test bugs * Minor fixes * fix finitedifference behavior * more fixes
1 parent f1b41c2 commit dc1e303

26 files changed

+748
-441
lines changed

docs/src/develop/extensions.md

Lines changed: 25 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -200,11 +200,12 @@ we provide an API to do just this. A complete template is provided in
200200
to help streamline this process. The extension steps are:
201201
1. Define the new method `struct` that inherits from the correct
202202
[`AbstractDerivativeMethod`](@ref) subtype
203-
2. Extend [`InfiniteOpt.generative_support_info`](@ref InfiniteOpt.generative_support_info(::AbstractDerivativeMethod))
203+
2. Extend [`allows_high_order_derivatives`](@ref)
204+
3. Extend [`InfiniteOpt.generative_support_info`](@ref InfiniteOpt.generative_support_info(::AbstractDerivativeMethod))
204205
if the method is a [`GenerativeDerivativeMethod`](@ref)
205-
3. Extend [`InfiniteOpt.evaluate_derivative`](@ref).
206+
4. Extend [`InfiniteOpt.evaluate_derivative`](@ref).
206207

207-
To exemplify this process let's implement explicit Euler which is already
208+
To exemplify this process let's implement 1st order explicit Euler which is already
208209
implemented via `FiniteDifference(Forward())`, but let's make our own anyway for
209210
the sake of example. For a first order derivative ``\frac{d y(t)}{dt}`` explicit
210211
Euler is expressed:
@@ -228,10 +229,23 @@ support scheme without adding any additional supports. If our desired method
228229
needed to add additional supports (e.g., orthogonal collocation over finite
229230
elements) then we would need to have used [`GenerativeDerivativeMethod`](@ref).
230231

231-
Since, this is a `NonGenerativeDerivativeMethod` we skip step 2. This is
232+
Now we need to decide if this method will directly support higher order derivatives.
233+
In this case, let's say it won't and define:
234+
```jldoctest deriv_ext; output = false
235+
InfiniteOpt.allows_high_order_derivatives(::ExplicitEuler) = false
236+
237+
# output
238+
239+
240+
```
241+
Conversely, we could set the output to `true` if we wanted to directly support higher
242+
order derivatives. In which case, we would need to query [`derivative_order`](@ref)
243+
in [`InfiniteOpt.evaluate_derivative`](@ref) and account for the order as needed.
244+
245+
Since, this is a `NonGenerativeDerivativeMethod` we skip step 3. This is
232246
however exemplified in the extension template.
233247

234-
Now we just need to do step 3 which is to extend
248+
Now we just need to do step 4 which is to extend
235249
[`InfiniteOpt.evaluate_derivative`](@ref). This function generates all the
236250
expressions necessary to build the derivative evaluation equations (derivative
237251
constraints). We assume these relations to be of the form ``h = 0`` where ``h``
@@ -251,11 +265,11 @@ With this in mind let's now extend `InfiniteOpt.evaluate_derivative`:
251265
```jldoctest deriv_ext; output = false
252266
function InfiniteOpt.evaluate_derivative(
253267
dref::GeneralVariableRef,
268+
vref::GeneralVariableRef, # the variable that the derivative acts on
254269
method::ExplicitEuler,
255270
write_model::JuMP.AbstractModel
256-
)::Vector{JuMP.AbstractJuMPScalar}
271+
)
257272
# get the basic derivative information
258-
vref = derivative_argument(dref)
259273
pref = operator_parameter(dref)
260274
# generate the derivative expressions h_i corresponding to the equations of
261275
# the form h_i = 0
@@ -279,7 +293,8 @@ We used [`InfiniteOpt.make_reduced_expr`](@ref) as a convenient helper function
279293
to generate the semi-infinite variables/expressions we need to generate at each
280294
support point. Also note that [`InfiniteOpt.add_generative_supports`](@ref) needs
281295
to be included for `GenerativeDerivativeMethods`, but is not necessary in this
282-
example.
296+
example. We also would have needed to query [`derivative_order`](@ref) and take it
297+
into account if we had selected this method to support higher order derivatives.
283298

284299
Now that we have completed all the necessary steps, let's try it out!
285300
```jldoctest deriv_ext
@@ -296,8 +311,8 @@ julia> evaluate(dy)
296311
297312
julia> derivative_constraints(dy)
298313
2-element Vector{InfOptConstraintRef}:
299-
y(5) - y(0) - 5 ∂/∂t[y(t)](0) = 0.0
300-
y(10) - y(5) - 5 ∂/∂t[y(t)](5) = 0.0
314+
y(5) - y(0) - 5 d/dt[y(t)](0) = 0.0
315+
y(10) - y(5) - 5 d/dt[y(t)](5) = 0.0
301316
```
302317
We implemented explicit Euler and it works! Now go and extend away!
303318

docs/src/guide/derivative.md

Lines changed: 106 additions & 71 deletions
Large diffs are not rendered by default.

docs/src/guide/transcribe.md

Lines changed: 47 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -199,26 +199,26 @@ Thus, we have an optimization problem whose decision space is infinite with
199199
respect to time ``t`` and position ``x``. Now let's transcript it following the
200200
above steps. First, we need to specify the infinite parameter supports and for
201201
simplicity let's choose the following sparse sets:
202-
- ``t \in \{0, 10\}``
202+
- ``t \in \{0, 5, 10\}``
203203
- ``x \in \{[-1, -1]^T, [-1, 1]^T, [1, -1]^T, [1, 1]^T\}``.
204204
To handle the derivative ``\frac{\partial g(t, x)}{\partial t}``, we'll use
205-
backward finite difference so no additional supports will need to be added.
205+
backward finite difference, so no additional supports will need to be added.
206206

207207
Now we expand the two integrals (measures) via a finite approximation using only
208208
the above supports and term coefficients of 1 (note this is not numerically
209209
correct but is done for conciseness in example). Doing this, we obtain the
210210
form:
211211
```math
212212
\begin{aligned}
213-
&&\min_{y(t), g(t, x)} &&& y^2(0) + y^2(10) \\
213+
&&\min_{y(t), g(t, x)} &&& y^2(0) + y^2(5) + y^2(10) \\
214214
&&\text{s.t.} &&& y(0) = 1 \\
215215
&&&&& g(0, x) = 0 \\
216216
&&&&& \frac{\partial g(t, [-1, -1])}{\partial t} + \frac{\partial g(t, [-1, 1])}{\partial t} + \frac{\partial g(t, [1, -1])}{\partial t} + \frac{\partial g(t, [1, 1])}{\partial t} = 42, && \forall t \in [0, 10] \\
217217
&&&&& 3g(t, x) + 2y^2(t) \leq 2, && \forall t \in T, \ x \in [-1, 1]^2. \\
218218
\end{aligned}
219219
```
220220
Notice that the infinite variable ``y(t)`` in the objective measure has been
221-
replaced with finite transcribed variables ``y(0)`` and ``y(10)``. Also, the
221+
replaced with finite transcribed variables ``y(0)``, ``y(5)``, ``y(10)``. Also, the
222222
infinite derivative ``\frac{\partial g(t, x)}{\partial t}`` was replaced with
223223
partially transcribed variables in the second constraint in accordance with the
224224
measure over the positional domain ``x``.
@@ -230,13 +230,14 @@ and the third constraint needs to be transcribed for each unique combination
230230
of the time and position supports. Applying this transcription yields:
231231
```math
232232
\begin{aligned}
233-
&&\min_{y(t), g(t, x)} &&& y^2(0) + y^2(10) \\
233+
&&\min_{y(t), g(t, x)} &&& y^2(0) + y^2(5) + y^2(10) \\
234234
&&\text{s.t.} &&& y(0) = 1 \\
235235
&&&&& g(0, [-1, -1]) = 0 \\
236236
&&&&& g(0, [-1, 1]) = 0 \\
237237
&&&&& g(0, [1, -1]) = 0 \\
238238
&&&&& g(0, [1, 1]) = 0 \\
239239
&&&&& \frac{\partial g(0, [-1, -1])}{\partial t} + \frac{\partial g(0, [-1, 1])}{\partial t} + \frac{\partial g(0, [1, -1])}{\partial t} + \frac{\partial g(0, [1, 1])}{\partial t} = 42\\
240+
&&&&& \frac{\partial g(5, [-1, -1])}{\partial t} + \frac{\partial g(5, [-1, 1])}{\partial t} + \frac{\partial g(5, [1, -1])}{\partial t} + \frac{\partial g(5, [1, 1])}{\partial t} = 42\\
240241
&&&&& \frac{\partial g(10, [-1, -1])}{\partial t} + \frac{\partial g(10, [-1, 1])}{\partial t} + \frac{\partial g(10, [1, -1])}{\partial t} + \frac{\partial g(10, [1, 1])}{\partial t} = 42\\
241242
&&&&& 3g(0, [-1, -1]) + 2y^2(0) \leq 2 \\
242243
&&&&& 3g(0, [-1, 1]) + 2y^2(0) \leq 2 \\
@@ -252,10 +253,14 @@ infinite equation in this case this we only have 2 supports in the time domain
252253
is then transcribed over the spatial domain to yield:
253254
```math
254255
\begin{aligned}
255-
&&& g(10, [-1, -1]) = g(0, [-1, -1]) + 10\frac{\partial g(10, [-1, -1])}{\partial t} \\
256-
&&& g(10, [-1, 1]) = g(0, [-1, 1]) + 10\frac{\partial g(10, [-1, 1])}{\partial t} \\
257-
&&& g(10, [1, -1]) = g(0, [1, -1]) + 10\frac{\partial g(10, [1, -1])}{\partial t} \\
258-
&&& g(10, [1, 1]) = g(0, [1, 1]) + 10\frac{\partial g(10, [1, 1])}{\partial t}
256+
&&& g(5, [-1, -1]) = g(0, [-1, -1]) + 5\frac{\partial g(5, [-1, -1])}{\partial t} \\
257+
&&& g(5, [-1, 1]) = g(0, [-1, 1]) + 5\frac{\partial g(5, [-1, 1])}{\partial t} \\
258+
&&& g(5, [1, -1]) = g(0, [1, -1]) + 5\frac{\partial g(5, [1, -1])}{\partial t} \\
259+
&&& g(5, [1, 1]) = g(0, [1, 1]) + 5\frac{\partial g(5, [1, 1])}{\partial t} \\
260+
&&& g(10, [-1, -1]) = g(5, [-1, -1]) + 5\frac{\partial g(10, [-1, -1])}{\partial t} \\
261+
&&& g(10, [-1, 1]) = g(5, [-1, 1]) + 5\frac{\partial g(10, [-1, 1])}{\partial t} \\
262+
&&& g(10, [1, -1]) = g(5, [1, -1]) + 5\frac{\partial g(10, [1, -1])}{\partial t} \\
263+
&&& g(10, [1, 1]) = g(5, [1, 1]) + 5\frac{\partial g(10, [1, 1])}{\partial t}
259264
\end{aligned}
260265
```
261266

@@ -275,7 +280,7 @@ using InfiniteOpt
275280
inf_model = InfiniteModel()
276281
277282
# Define parameters and supports
278-
@infinite_parameter(inf_model, t in [0, 10], supports = [0, 10])
283+
@infinite_parameter(inf_model, t in [0, 10], supports = [0, 5, 10])
279284
@infinite_parameter(inf_model, x[1:2] in [-1, 1], supports = [-1, 1], independent = true)
280285
281286
# Define variables
@@ -313,56 +318,72 @@ julia> build_optimizer_model!(inf_model)
313318
julia> trans_model = optimizer_model(inf_model);
314319
315320
julia> print(trans_model)
316-
Min y(support: 1)² + y(support: 2)²
321+
Min y(support: 1)² + y(support: 2)² + y(support: 3)²
317322
Subject to
318323
y(support: 1) = 1
319324
g(support: 1) = 0
320-
g(support: 3) = 0
321-
g(support: 5) = 0
325+
g(support: 4) = 0
322326
g(support: 7) = 0
323-
∂/∂t[g(t, x)](support: 1) + ∂/∂t[g(t, x)](support: 3) + ∂/∂t[g(t, x)](support: 5) + ∂/∂t[g(t, x)](support: 7) = 42
324-
∂/∂t[g(t, x)](support: 2) + ∂/∂t[g(t, x)](support: 4) + ∂/∂t[g(t, x)](support: 6) + ∂/∂t[g(t, x)](support: 8) = 42
325-
g(support: 1) - g(support: 2) + 10 ∂/∂t[g(t, x)](support: 2) = 0
326-
g(support: 3) - g(support: 4) + 10 ∂/∂t[g(t, x)](support: 4) = 0
327-
g(support: 5) - g(support: 6) + 10 ∂/∂t[g(t, x)](support: 6) = 0
328-
g(support: 7) - g(support: 8) + 10 ∂/∂t[g(t, x)](support: 8) = 0
327+
g(support: 10) = 0
328+
∂/∂t[g(t, x)](support: 1) + ∂/∂t[g(t, x)](support: 4) + ∂/∂t[g(t, x)](support: 7) + ∂/∂t[g(t, x)](support: 10) = 42
329+
∂/∂t[g(t, x)](support: 2) + ∂/∂t[g(t, x)](support: 5) + ∂/∂t[g(t, x)](support: 8) + ∂/∂t[g(t, x)](support: 11) = 42
330+
∂/∂t[g(t, x)](support: 3) + ∂/∂t[g(t, x)](support: 6) + ∂/∂t[g(t, x)](support: 9) + ∂/∂t[g(t, x)](support: 12) = 42
331+
g(support: 1) - g(support: 2) + 5 ∂/∂t[g(t, x)](support: 2) = 0
332+
g(support: 2) - g(support: 3) + 5 ∂/∂t[g(t, x)](support: 3) = 0
333+
g(support: 4) - g(support: 5) + 5 ∂/∂t[g(t, x)](support: 5) = 0
334+
g(support: 5) - g(support: 6) + 5 ∂/∂t[g(t, x)](support: 6) = 0
335+
g(support: 7) - g(support: 8) + 5 ∂/∂t[g(t, x)](support: 8) = 0
336+
g(support: 8) - g(support: 9) + 5 ∂/∂t[g(t, x)](support: 9) = 0
337+
g(support: 10) - g(support: 11) + 5 ∂/∂t[g(t, x)](support: 11) = 0
338+
g(support: 11) - g(support: 12) + 5 ∂/∂t[g(t, x)](support: 12) = 0
329339
y(support: 1)² + 3 g(support: 1) ≤ 2
330340
y(support: 2)² + 3 g(support: 2) ≤ 2
331-
y(support: 1)² + 3 g(support: 3) ≤ 2
332-
y(support: 2)² + 3 g(support: 4) ≤ 2
333-
y(support: 1)² + 3 g(support: 5) ≤ 2
334-
y(support: 2)² + 3 g(support: 6) ≤ 2
341+
y(support: 3)² + 3 g(support: 3) ≤ 2
342+
y(support: 1)² + 3 g(support: 4) ≤ 2
343+
y(support: 2)² + 3 g(support: 5) ≤ 2
344+
y(support: 3)² + 3 g(support: 6) ≤ 2
335345
y(support: 1)² + 3 g(support: 7) ≤ 2
336346
y(support: 2)² + 3 g(support: 8) ≤ 2
347+
y(support: 3)² + 3 g(support: 9) ≤ 2
348+
y(support: 1)² + 3 g(support: 10) ≤ 2
349+
y(support: 2)² + 3 g(support: 11) ≤ 2
350+
y(support: 3)² + 3 g(support: 12) ≤ 2
337351
```
338352
This precisely matches what we found analytically. Note that the unique support
339353
combinations are determined automatically and are represented visually as
340354
`support: #`. The precise support values can be looked up via `supports`:
341355
```jldoctest trans_example
342356
julia> supports(y)
343-
2-element Vector{Tuple}:
357+
3-element Vector{Tuple}:
344358
(0.0,)
359+
(5.0,)
345360
(10.0,)
346361
347362
julia> supports(g)
348-
8-element Vector{Tuple}:
363+
12-element Vector{Tuple}:
349364
(0.0, [-1.0, -1.0])
365+
(5.0, [-1.0, -1.0])
350366
(10.0, [-1.0, -1.0])
351367
(0.0, [1.0, -1.0])
368+
(5.0, [1.0, -1.0])
352369
(10.0, [1.0, -1.0])
353370
(0.0, [-1.0, 1.0])
371+
(5.0, [-1.0, 1.0])
354372
(10.0, [-1.0, 1.0])
355373
(0.0, [1.0, 1.0])
374+
(5.0, [1.0, 1.0])
356375
(10.0, [1.0, 1.0])
357376
358377
julia> supports(g, ndarray = true) # format it as an n-dimensional array (t by x[1] by x[2])
359-
2×2×2 Array{Tuple, 3}:
378+
3×2×2 Array{Tuple, 3}:
360379
[:, :, 1] =
361380
(0.0, [-1.0, -1.0]) (0.0, [1.0, -1.0])
381+
(5.0, [-1.0, -1.0]) (5.0, [1.0, -1.0])
362382
(10.0, [-1.0, -1.0]) (10.0, [1.0, -1.0])
363383
364384
[:, :, 2] =
365385
(0.0, [-1.0, 1.0]) (0.0, [1.0, 1.0])
386+
(5.0, [-1.0, 1.0]) (5.0, [1.0, 1.0])
366387
(10.0, [-1.0, 1.0]) (10.0, [1.0, 1.0])
367388
```
368389

docs/src/index.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ Accepted infinite/finite problem forms currently include:
6868
- Conic
6969
- Semi-definite
7070
- Indicator
71+
- Anything else supported by JuMP
7172

7273
### Infinite-Dimensional Optimization with InfiniteOpt.jl
7374
See our YouTube overview of infinite-dimensional programming and InfiniteOpt.jl's

docs/src/manual/derivative.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ DerivativeRef
2121
```@docs
2222
derivative_argument(::DerivativeRef)
2323
operator_parameter(::DerivativeRef)
24+
derivative_order(::DerivativeRef)
2425
num_derivatives
2526
all_derivatives
2627
parameter_refs(::DerivativeRef)
@@ -55,6 +56,7 @@ has_derivative_constraints(::DerivativeRef)
5556
derivative_constraints(::DerivativeRef)
5657
delete_derivative_constraints(::DerivativeRef)
5758
evaluate_derivative
59+
allows_high_order_derivatives
5860
generative_support_info(::AbstractDerivativeMethod)
5961
support_label(::AbstractDerivativeMethod)
6062
InfiniteOpt.make_reduced_expr

docs/src/manual/expression.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,7 @@ measure_data(::GeneralVariableRef)
116116
is_analytic(::GeneralVariableRef)
117117
derivative_argument(::GeneralVariableRef)
118118
operator_parameter(::GeneralVariableRef)
119+
derivative_order(::GeneralVariableRef)
119120
derivative_method(::GeneralVariableRef)
120121
evaluate(::GeneralVariableRef)
121122
derivative_constraints(::GeneralVariableRef)

0 commit comments

Comments
 (0)