Skip to content

Commit f3594a6

Browse files
Update README.md
1 parent 671354e commit f3594a6

File tree

1 file changed

+15
-261
lines changed

1 file changed

+15
-261
lines changed

README.md

Lines changed: 15 additions & 261 deletions
Original file line numberDiff line numberDiff line change
@@ -34,48 +34,6 @@ a function can be used, just with the extra functionality ignored.
3434

3535
## Basic Usage
3636

37-
### ParameterizedFunction Constructor
38-
39-
The easiest way to make a `ParameterizedFunction` is to use the constructor:
40-
41-
```julia
42-
pf = ParameterizedFunction(f,params)
43-
```
44-
45-
The form for `f` is `f(t,u,params,du)`
46-
where `params` is any type which defines the parameters. The
47-
resulting `ParameterizedFunction` has the function call `pf(t,u,params,du)`
48-
which matches the original function, and a call `pf(t,u,du)` which uses internal
49-
parmaeters which can be used with a differential equation solver. Note that the
50-
internal parameters can be modified at any time via the field: `pf.p = ...`.
51-
52-
An additional version exists for `f(t,u,params)` which will then act as the
53-
not inplace version `f(t,u)` in the differential equation solvers.
54-
55-
#### Example
56-
57-
```julia
58-
pf_func = function (t,u,p,du)
59-
du[1] = p[1] * u[1] - p[2] * u[1]*u[2]
60-
du[2] = -3 * u[2] + u[1]*u[2]
61-
end
62-
63-
pf = ParameterizedFunction(pf_func,[1.5,1.0])
64-
```
65-
66-
And now `pf` can be used in the differential equation solvers and the ecosystem
67-
functionality which requires explicit parameters (parameter estimation, etc.).
68-
69-
Note that the not inplace version works the same:
70-
71-
```julia
72-
pf_func2 = function (t,u,p)
73-
[p[1] * u[1] - p[2] * u[1]*u[2];-3 * u[2] + u[1]*u[2]]
74-
end
75-
76-
pf2 = ParameterizedFunction(pf_func2,[1.5,1.0])
77-
```
78-
7937
### ODE Macros
8038

8139
A helper macro is provided to make it easier to define a `ParameterizedFunction`,
@@ -87,17 +45,9 @@ you can use the following command:
8745
f = @ode_def LotkaVolterra begin
8846
dx = a*x - b*x*y
8947
dy = -c*y + d*x*y
90-
end a=>1.5 b=>1 c=3 d=1
48+
end a b c d
9149
```
9250

93-
Note that the syntax for parameters here is that `=>` will
94-
put these inside the parameter type, while `=` will inline the number (i.e. replace
95-
each instance of `c` with `3`). Inlining slightly decreases the function cost and
96-
so is preferred in any case where you know that the parameter will always be constant.
97-
This will silently create the `LotkaVolterra` type and thus `g=LotkaVolterra(a=1.0,b=2.0)`
98-
will create a different function where `a=1.0` and `b=2.0`. However, at any time
99-
the parameters of `f` can be changed by using `f.a =` or `f.b = `.
100-
10151
The macro also defines the Jacobian `f'`. This is defined as an in-place Jacobian `f(Val{:jac},t,u,J)`.
10252
This is calculated using SymEngine.jl automatically, so it's no effort on your part.
10353
The symbolic inverse of the Jacobian is also computed, and an in-place function
@@ -130,36 +80,6 @@ opts = Dict{Symbol,Bool}(
13080
and calls the function `ode_def_opts(name::Symbol,opts,ex::Expr,params)`. Note that
13181
params is an iterator holding expressions for the parameters.
13282

133-
#### Extra Little Tricks
134-
135-
There are some extra little tricks you can do. Since `@ode_def` is a macro,
136-
you cannot directly make the parameters something that requires a runtime value.
137-
Thus the following will error:
138-
139-
```julia
140-
vec = rand(1,4)
141-
f = @ode_def LotkaVolterraExample begin
142-
dx = ax - bxy
143-
dy = -cy + dxy
144-
end a=>vec[1] b=>vec[2] c=>vec[3] d=vec[4]
145-
```
146-
147-
To do the same thing, instead initialize it with values of the same type, and simply
148-
replace them:
149-
150-
```julia
151-
vec = rand(1,4)
152-
f = @ode_def LotkaVolterraExample begin
153-
dx = ax - bxy
154-
dy = -cy + dxy
155-
end a=>1.0 b=>1.0 c=>1.0 d=vec[4]
156-
f.a,f.b,f.c = vec[1:3]
157-
```
158-
159-
Notice that when using `=`, it can inline expressions. It can even inline expressions
160-
of time, like `d=3*t` or `d=2π`. However, do not use something like `d=3*x` as that will
161-
fail to transform the `x`.
162-
16383
In addition, one can also use their own function inside of the macro. For example:
16484

16585
```julia
@@ -185,151 +105,6 @@ to be more specific about what to not calculate. In increasing order of calculat
185105
@ode_def_nohes
186106
```
187107

188-
### Finite Element PDEs
189-
190-
Similar macros for finite element method definitions also exist. For the finite
191-
element solvers, the definitions use `x[:,1]` instead of `x` and `x[:,2]` instead of `y`.
192-
To more easily define systems of equations for finite element solvers, we can
193-
use the `@fem_def` macro. The first argument is the function signature. This
194-
is required in order to tell the solver linearity. Other than that, the macro
195-
usage is similar to before. For example,
196-
197-
```julia
198-
l = @fem_def (t,x,u) BirthDeath begin
199-
du = 1-x*α*u
200-
dv = 1-y*v
201-
end α=0.5
202-
```
203-
204-
defines a system of equations
205-
206-
```julia
207-
l = (t,x,u) -> [1-.5*x[:,1]*u[:,1] 1-x[:,2]*u[:,2]]
208-
```
209-
210-
which is in the form for the FEM solver.
211-
212-
## The ParameterizedFunction Interface
213-
214-
The ParameterizedFunction interface is as follows:
215-
216-
- ParameterizedFunction is a type which is a subtype of Function
217-
- The type must hold the parameters.
218-
- Hessians, Inverse Jacobians, Inverse Hessians, explicit parameter functions,
219-
parameter derivatives, and parameter Jacobians.
220-
- The standard call `(p::TypeName)(t,u,du)` must be overloaded for the function
221-
calculation. All other functions are optional.
222-
223-
Solvers can interface with ParameterizedFunctions as follows:
224-
225-
```julia
226-
f.a # accesses the parameter a
227-
f(t,u,du) # Call the function
228-
f(t,u,params,du) # Call the function to calculate with parameters params (vector)
229-
f(Val{:tgrad},t,u,J) # Call the explicit t-gradient function
230-
f(Val{:a},t,u,2.0,du) # Call the explicit parameter function with a=2.0
231-
f(Val{:deriv},Val{:a},t,u,2.0,df) # Call the explicit parameter derivative function with a=2.0
232-
f(Val{:paramjac},t,u,params,J) # Call the explicit parameter Jacobian function
233-
f(Val{:jac},t,u,J) # Call the explicit Jacobian function
234-
f(Val{:expjac},t,u,γ,J) # Call the explicit exponential Jacobian function exp(γJ)
235-
f(Val{:invjac},t,u,iJ) # Call the explicit Inverse Jacobian function
236-
f(Val{:invW},t,u,γ,iW) # Call the explicit inverse Rosenbrock-W function (M - γJ)^(-1)
237-
f(Val{:invW_t},t,u,γ,iW) # Call the explicit transformed inverse Rosenbrock-W function (M/γ - J)^(-1)
238-
f(Val{:hes},t,u,H) # Call the explicit Hessian function
239-
f(Val{:invhes},t,u,iH) # Call the explicit Inverse Hessian function
240-
```
241-
242-
To test for whether certain overloads exist, the following functions are provided
243-
by traits in [DiffEqBase.jl](https://github.com/JuliaDiffEq/DiffEqBase.jl):
244-
245-
```julia
246-
has_jac(f)
247-
has_expjac(f)
248-
has_invjac(f)
249-
has_tgrad(f)
250-
has_hes(f)
251-
has_invhes(f)
252-
has_invW(f)
253-
has_invW_t(f)
254-
has_paramjac(f)
255-
has_paramderiv(f)
256-
```
257-
258-
These are compile-time checks and thus the inappropriate branches will compile
259-
way when a function (usually an ODE/SDE solver) is dispatched on `f`. It is
260-
requested that solvers should only use the explicit functions when they exist
261-
to help with performance.
262-
263-
In addition, the following functions are provided:
264-
265-
- `param_values(f)` : Returns an array of the values for each of the parameters
266-
- `num_params(f)` : Returns the number of parameters for `f`
267-
268-
## Internals: How it Works
269-
270-
This shows how to manually build a ParameterizedFunction to give to
271-
a solver.
272-
273-
### Template
274-
275-
An example of explicitly defining a parameterized function is as follows. This serves
276-
as a general template for doing so:
277-
278-
```julia
279-
type LotkaVolterra <: AbstractParameterizedFunction{true}
280-
a::Float64
281-
b::Float64
282-
end
283-
f = LotkaVolterra(0.0,0.0)
284-
(p::LotkaVolterra)(t,u,du) = begin
285-
du[1] = p.a * u[1] - p.b * u[1]*u[2]
286-
du[2] = -3 * u[2] + u[1]*u[2]
287-
end
288-
```
289-
290-
### Explanation
291-
292-
Let's go step by step to see what this template does. The first part defines a
293-
type:
294-
295-
```julia
296-
type LotkaVolterra <: AbstractParameterizedFunction{true}
297-
a::Float64
298-
b::Float64
299-
end
300-
```
301-
302-
The fields are the parameters for our function. The abstract type is parameterized by whether the function is written in-place or not. Then we built the type:
303-
304-
```julia
305-
f = LotkaVolterra(0.0,0.0)
306-
```
307-
308-
We put in values for the parameters and told it that we will be defining each of
309-
those functions. First we define the main overload. This is required even if none
310-
of the other functions are provided. The function for the main overload is the
311-
differential equation, so for the Lotka-Volterra equation:
312-
313-
```julia
314-
(p::LotkaVolterra)(t,u,du) = begin
315-
du[1] = p.a * u[1] - p.b * u[1]*u[2]
316-
du[2] = -3 * u[2] + u[1]*u[2]
317-
end
318-
```
319-
320-
Note how we represented the parameters in the equation. If you did this and set
321-
the booleans to false, the result is `f` is a `ParameterizedFunction`,
322-
but `f(t,u,du)` acts like the function:
323-
324-
```julia
325-
function f(t,u,du)
326-
du[1] = 0.0 * u[1] - 0.0 * u[1]*u[2]
327-
du[2] = -3 * u[2] + u[1]*u[2]
328-
end
329-
```
330-
331-
At anytime the function parameters can be accessed by the fields (`f.a`, `f.b`).
332-
333108
### Extra Functions
334109

335110
#### Jacobian Function
@@ -360,43 +135,22 @@ function (p::LotkaVolterra)(::Type{Val{:invjac}},t,u,J)
360135
end
361136
```
362137

363-
#### Hessian and Inverse Hessian
364-
365-
These are the same as the Jacobians, except with value types `:hes` and `:invhes`.
366-
367-
#### Explicit Parameter Functions
368-
369-
For solvers which need to auto-differentiate parameters (local sensitivity analysis),
370-
explicit parameter functions are required. For our example, we do the following:
371-
372-
```julia
373-
function (p::LotkaVolterra)(::Type{Val{:a}},t,u,a,du)
374-
du[1] = a * u[1] - p.b * u[1] * u[2]
375-
du[2] = -3 * u[2] + 1 * u[1] * u[2]
376-
nothing
377-
end
378-
function (p::LotkaVolterra)(::Type{Val{:b}},t,u,b,du)
379-
du[1] = p.a * u[1] - b * u[1] * u[2]
380-
du[2] = -3 * u[2] + 1 * u[1] * u[2]
381-
nothing
382-
end
383-
```
384-
385-
#### Explicit Parameter Derivatives
138+
#### Parameter Jacobian
386139

387140
For solvers which need parameters derivatives, specifying the functions can increase
388141
performance. For our example, we allow the solvers to use the explicit derivatives
389-
in the parameters `a` and `b` by:
390-
391-
```julia
392-
function (p::LotkaVolterra)(::Type{Val{:deriv}},::Type{Val{:a}},t,u,a,du)
393-
du[1] = 1 * u[1]
394-
du[2] = 1 * 0
395-
nothing
396-
end
397-
function (p::LotkaVolterra)(::Type{Val{:deriv}},::Type{Val{:b}},t,u,b,du)
398-
du[1] = -(u[1]) * u[2]
399-
du[2] = 1 * 0
400-
nothing
142+
in the parameters by:
143+
144+
```julia
145+
function (p::LotkaVolterra)(::Type{Val{:paramjac}},J,u,p,t)
146+
J[1, 1] = u[1] * 1
147+
J[1, 2] = -(u[1]) * u[2]
148+
J[1, 3] = 0 * 1
149+
J[1, 4] = 0 * 1
150+
J[2, 1] = 0 * 1
151+
J[2, 2] = 0 * 1
152+
J[2, 3] = -(u[2])
153+
J[2, 4] = u[1] * u[2]
154+
nothing
401155
end
402156
```

0 commit comments

Comments
 (0)