@@ -213,10 +213,11 @@ we provide an API to do just this. A complete template is provided in
213
213
to help streamline this process. The extension steps are:
214
214
1 . Define the new method ` struct ` that inherits from the correct
215
215
[ ` AbstractDerivativeMethod ` ] ( @ref ) subtype
216
- 2 . Extend [ ` allows_high_order_derivatives ` ] ( @ref )
216
+ 2 . Extend [ ` InfiniteOpt. allows_high_order_derivatives` ] ( @ref )
217
217
3 . Extend [ ` InfiniteOpt.generative_support_info ` ] (@ref InfiniteOpt.generative_support_info(::AbstractDerivativeMethod))
218
218
if the method is a [ ` GenerativeDerivativeMethod ` ] ( @ref )
219
- 4 . Extend [ ` InfiniteOpt.evaluate_derivative ` ] ( @ref ) .
219
+ 4 . Extend [ ` InfiniteOpt.derivative_expr_data ` ] ( @ref )
220
+ 5 . Extend [ ` InfiniteOpt.make_indexed_derivative_expr ` ] ( @ref ) .
220
221
221
222
To exemplify this process let's implement 1st order explicit Euler which is already
222
223
implemented via ` FiniteDifference(Forward()) ` , but let's make our own anyway for
@@ -252,50 +253,66 @@ InfiniteOpt.allows_high_order_derivatives(::ExplicitEuler) = false
252
253
253
254
```
254
255
Conversely, we could set the output to ` true ` if we wanted to directly support higher
255
- order derivatives. In which case, we would need to query [ ` derivative_order ` ] ( @ref )
256
- in [ ` InfiniteOpt.evaluate_derivative ` ] ( @ref ) and account for the order as needed.
256
+ order derivatives. In which case, we would need to take the order into account in steps 4 and 5.
257
257
258
258
Since, this is a ` NonGenerativeDerivativeMethod ` we skip step 3. This is
259
259
however exemplified in the extension template.
260
260
261
- Now we just need to do step 4 which is to extend
262
- [ ` InfiniteOpt.evaluate_derivative ` ] ( @ref ) . This function generates all the
261
+ For step 4, we extend [ ` InfiniteOpt.derivative_expr_data ` ] ( @ref ) .
262
+ This function generates all the needed data to make the
263
263
expressions necessary to build the derivative evaluation equations (derivative
264
264
constraints). We assume these relations to be of the form `` h = 0 `` where `` h ``
265
- is a vector of expressions and is what the output of
266
- ` InfiniteOpt.evaluate_derivative ` should be. Thus, mathematically `` h `` should
267
- be of the form:
265
+ is a vector of expressions. Thus, mathematically `` h `` should be of the form:
268
266
``` math
269
267
\begin{aligned}
270
- &&& y(t_{1 }) - y(0 ) - (t_{1 } - t_{0 })\frac{d y(0 )}{dt} \\
268
+ &&& y(t_{2 }) - y(t_{1} ) - (t_{2 } - t_{1 })\frac{d y(t_1 )}{dt} \\
271
269
&&& \vdots \\
272
270
&&& y(t_{n+1}) - y(t_n) - (t_{n+1} - t_{n})\frac{d y(t_n)}{dt} \\
273
- &&& \vdots \\
274
- &&& y(t_{k}) - y(k-1) - (t_{k} - t_{k-1})\frac{d y(k-1)}{dt} \\
275
271
\end{aligned}
276
272
```
277
- With this in mind let's now extend ` InfiniteOpt.evaluate_derivative ` :
273
+ The required data must include the support indices used for each derivative
274
+ variable and then any other constants needed. In this case, we will need the
275
+ indices `` \{1, \dots, n\} `` and no additional data (additional data is exemplified
276
+ in the extension template). With this in mind let's now extend
277
+ ` InfiniteOpt.derivative_expr_data ` :
278
278
``` jldoctest deriv_ext; output = false
279
- function InfiniteOpt.evaluate_derivative (
279
+ function InfiniteOpt.derivative_expr_data (
280
280
dref::GeneralVariableRef,
281
- vref::GeneralVariableRef, # the variable that the derivative acts on
282
- method::ExplicitEuler ,
283
- write_model::JuMP.AbstractModel
281
+ order::Int,
282
+ supps::Vector{Float64} ,
283
+ method::ExplicitEuler
284
284
)
285
- # get the basic derivative information
286
- pref = operator_parameter(dref)
287
- # generate the derivative expressions h_i corresponding to the equations of
288
- # the form h_i = 0
289
- supps = supports(pref, label = All)
290
- exprs = Vector{JuMP.AbstractJuMPScalar}(undef, length(supps) - 1)
291
- for i in eachindex(exprs)
292
- d = InfiniteOpt.make_reduced_expr(dref, pref, supps[i], write_model)
293
- v1 = InfiniteOpt.make_reduced_expr(vref, pref, supps[i], write_model)
294
- v2 = InfiniteOpt.make_reduced_expr(vref, pref, supps[i + 1], write_model)
295
- change = supps[i + 1] - supps[i]
296
- exprs[i] = JuMP.@expression(write_model, v2 - v1 - change * d)
297
- end
298
- return exprs
285
+ # generate the support indices to be used for each call of `make_indexed_derivative_expr`
286
+ idxs = 1:length(supps)-1
287
+ # return the indexes and the other iterators
288
+ return (idxs, ) # output must be a tuple
289
+ end
290
+
291
+ # output
292
+
293
+
294
+ ```
295
+
296
+ Finally, we just need to extend [ ` InfiniteOpt.make_indexed_derivative_expr ` ] ( @ref ) .
297
+ This will be used to create derivative expressions for each index determined (and additional datum) produced by ` derivative_expr_data ` .
298
+ ``` jldoctest deriv_ext; output = false
299
+ function InfiniteOpt.make_indexed_derivative_expr(
300
+ dref::GeneralVariableRef,
301
+ vref::GeneralVariableRef,
302
+ pref::GeneralVariableRef,
303
+ order::Int,
304
+ idx,
305
+ supps::Vector{Float64}, # ordered
306
+ write_model::JuMP.AbstractModel,
307
+ ::ExplicitEuler,
308
+ # put extra data args here (none in this case)
309
+ )
310
+ # generate the derivative expression h corresponding to the equation of
311
+ # the form h = 0
312
+ d = InfiniteOpt.make_reduced_expr(dref, pref, supps[idx], write_model)
313
+ v1 = InfiniteOpt.make_reduced_expr(vref, pref, supps[idx], write_model)
314
+ v2 = InfiniteOpt.make_reduced_expr(vref, pref, supps[idx + 1], write_model)
315
+ return JuMP.@expression(write_model, -(supps[idx+1] - supps[idx]) * d + v2 - v1)
299
316
end
300
317
301
318
# output
@@ -304,10 +321,14 @@ end
304
321
```
305
322
We used [ ` InfiniteOpt.make_reduced_expr ` ] ( @ref ) as a convenient helper function
306
323
to generate the semi-infinite variables/expressions we need to generate at each
307
- support point. Also note that [ ` InfiniteOpt.add_generative_supports ` ] ( @ref ) needs
308
- to be included for ` GenerativeDerivativeMethods ` , but is not necessary in this
309
- example. We also would have needed to query [ ` derivative_order ` ] ( @ref ) and take it
310
- into account if we had selected this method to support higher order derivatives.
324
+ support point.
325
+
326
+ !!! note
327
+ If your new derivative method is not compatible can not be broken
328
+ up into the ` derivative_expr_data ` -` make_indexed_derivative_expr `
329
+ workflow, then you can instead extend [ ` InfiniteOpt.evaluate_derivative ` ] ( @ref ) .
330
+ This is discouraged where possible since it may make your method incompatible
331
+ with backends that depend on the preferred workflow.
311
332
312
333
Now that we have completed all the necessary steps, let's try it out!
313
334
``` jldoctest deriv_ext
@@ -324,8 +345,8 @@ julia> evaluate(dy)
324
345
325
346
julia> derivative_constraints(dy)
326
347
2-element Vector{InfOptConstraintRef}:
327
- y(5) - y(0) - 5 d/dt[y(t)](0) = 0. 0
328
- y(10) - y(5) - 5 d/dt[y(t)](5) = 0. 0
348
+ - 5 d/dt[y(t)](0) + y(5) - y(0) = 0
349
+ -5 d/dt[y(t)](5) + y(10) - y(5) = 0
329
350
```
330
351
We implemented explicit Euler and it works! Now go and extend away!
331
352
0 commit comments