Many of the methods require the definition of a
Basis on observables or functional forms. A
Basis is generated via:
Basis(eqs::AbstractVector, states::AbstractVector; parameters::AbstractArray = , iv = nothing, simplify = false, linear_independent = false, name = gensym(:Basis), pins = , observed = , eval_expression = false, kwargs...)
eqs is either a vector containing symbolic functions using 'ModelingToolkit.jl' or a general function with the typical DiffEq signature
h(u,p,t), which can be used with an
Num or vector of
states are the dependent variables used to describe the Basis, and
parameters are the optional parameters in the
iv represents the independent variable of the system - in most cases the time. Additional arguments are
simplify, which simplifies
eqs before creating a
linear_dependent breaks up
eqs in linear independent elements which are unique.
name is an optional name for the
observed can be using in accordance to ModelingToolkits documentation.
eval_expression is used to generate a callable function from the eqs. If set to
false, callable code will be returned.
true will use
eval on code returned from the function, which might cause worldage issues.
mutable struct Basis <: ModelingToolkit.AbstractSystem
A basis over the variables
u with parameters
p and independent variable
iv. It extends an
AbstractSystem as defined in
f can either be a Julia function which is able to use ModelingToolkit variables or a vector of
eqs. It can be called with the typical DiffEq signature, meaning out of place with
f(u,p,t) or in place with
f(du, u, p, t). If
linear_independent is set to
true, a linear independent basis is created from all atom function in
simplify_eqs is set to
simplify is called on
f. Additional keyworded arguments include
name, which can be used to name the basis, and
observed for defining observeables.
The equations of the basis
Dependent (state) variables
Internal function representation of the basis
Name of the basis
using ModelingToolkit using DataDrivenDiffEq @parameters w[1:2] t @variables u[1:2](t) Ψ = Basis([u; sin.(w.*u)], u, parameters = p, iv = t)
The keyword argument
eval_expression controls the function creation behavior.
eval_expression=true means that
eval is used, so normal world-age behavior applies (i.e. the functions cannot be called from the function that generates them). If
eval_expression=false, then construction via GeneralizedGenerated.jl is utilized to allow for same world-age evaluation. However, this can cause Julia to segfault on sufficiently large basis functions. By default eval_expression=false.
We start by crearting some variables and parameters using
using LinearAlgebra using DataDrivenDiffEq using Plots using ModelingToolkit @variables u[1:3] @parameters w[1:2]
To define a basis, simply write down the equations you want to be included as a
Vector. Possible used parameters have to be given to the constructor.
h = [u; u; cos(w*u+w*u)] b = Basis(h, u, parameters = w)
Basis are callable with the signature of functions to be used in
DifferentialEquations. So, the function value at a single point looks like:
x = b([1;2;3])
Or, in place
dx = similar(x) b(dx, [1;2;3])
Notice that since we did not use any numerical values for the parameters, the basis uses the symbolic values in the result.
To use numerical values, simply pass this on in the function call. Here, we evaluate over a trajectory with two parameters and 40 timestamps.
X = randn(3, 40) Y = b(X, [2;4], 0:39) nothing # hide
Suppose we want to add another equation, say
Basis behaves like an array, so we can simply
push!(b, sin(u)) size(b)
To ensure that a basis is well-behaved, functions already present are not included again.
push!(b, sin(u)) size(b)
We can also define functions of the independent variable and add them
t = independent_variable(b) push!(b, cos(t*π)) println(b)
Additionally, we can iterate over a
[eq for eq in basis] or index specific equations, like
We can also chain
Basis via just using it in the constructor
@variables x[1:2] y = [sin(x); cos(x); x] t = independent_variable(b) b2 = Basis(b(y, parameters(b), t), x, parameters = w, iv = t) println(b2)
You can also use
merge to create the union of two
b3 = merge(b, b2) println(b3)
which combines all the used variables and parameters ( and assumes the same independent_variable ):
If you have a function already defined as pure code, you can use this also to create a
Basis. Only the signature has to be consistent, so use
f(u, p, t) = [u; u; cos(p*u+p*u)] b_f = Basis(f, u, parameters = w) println(b_f)
This works for every function defined over
Nums. So to create a
Basis from a
Flux model, simply extend the activations used:
using Flux NNlib.σ(x::Num) = 1 / (1+exp(-x)) c = Chain(Dense(3,2,σ), Dense(2, 1, σ)) ps, re = Flux.destructure(c) @parameters p[1:length(ps)] g(u, p, t) = re(p)(u) b = Basis(g, u, parameters = p)
Missing docstring for
jacobian. Check Documenter's build log for details.
dynamics(basis) Returns the internal function representing the dynamics of the `Basis`.
push!(basis, eq, simplify_eqs = true; eval_expression = false) Push the equations(s) in `eq` into the basis and update all internal fields accordingly. `eq` can either be a single equation or an array. If `simplify_eq` is true, the equation will be simplified.
deleteat!(basis, inds, eval_expression = false) Delete the entries specified by `inds` and update the `Basis` accordingly.
merge(x::Basis, y::Basis; eval_expression = false) Return a new `Basis`, which is defined via the union of `x` and `y` .
merge!(x::Basis, y::Basis; eval_expression = false) Updates `x` to include the union of both `x` and `y`.