Deprecated: The same idea has already been developed in LabelledArrays. Take a look at it!
This is an API for nested environments, compatible with DifferentialEquations.jl.
An environment may consist of nested environments.
Each environment is a structure (e.g., typeof(env) <: AbstractEnv
), which includes dynamical systems and additional information.
- Currently, only ODE is supported.
See
src/API.jl
for more details.
NestedEnvironments.jl
supports nested environments API.
The dynamical equations and initial condition are treated as structured forms (as NamedTuple).
Compared to the original DifferentialEquations.jl
, you don't need to match the index of derivative calculation.
For example,
function f(x, p, t)
x1 = x.env1 # for example
x2 = x.env2 # for example
dx1 = x2
dx2 = -x1
(; x1 = dx1, x2 = dx2) # NamedTuple
end
instead of
function f(x, p, t)
dx = zero(x)
dx[1] = x[2]
dx[2] = -x[1]
dx
end
. For more details, see the below example.
NestedEnvironments.jl
provides convenient macros such as @readable
and @raw
.
@readable
makes an Array, compatible with DifferentialEquations.jl
, (structured) NamedTuple.
Conversely,
@raw
makes a NamedTuple, default structure of NestedEnvironments.jl
, an Array compatible with DifferentialEquations.jl
.
It provides some predefined environments.
See src/zoo.jl
for more information.
It is highly recommended to run the following code and practice how to use it.
using NestedEnvironments
using DifferentialEquations
using Transducers
using Test
# `BaseEnv` is a kind of syntax sugar for definition of environment; provided by `src/zoo.jl`.
# Note: `NestedEnvironments.initial_condition(env::BaseEnv) = env.initial_state`
struct Env2 <: AbstractEnv
env21::BaseEnv
env22::BaseEnv
end
struct Env <: AbstractEnv
env1::BaseEnv
env2::Env2
gain::Float64
end
# differential equations are regarded as nested envs (NamedTuple)
function dynamics(env::Env)
return function (x, p, t)
x1 = x.env1
x21 = x.env2.env21
x22 = x.env2.env22
ẋ1 = -x1 - env.gain*sum(x21 + x22)
ẋ21 = -x21
ẋ22 = -x22
(; env1 = ẋ1, env2 = (; env21 = ẋ21, env22 = ẋ22))
end
end
# for convenience
function make_env()
env21, env22 = BaseEnv(reshape(collect(1:8), 2, 4)), BaseEnv(reshape(collect(9:16), 2, 4))
env1 = BaseEnv(-1)
env2 = Env2(env21, env22)
gain = 2.0
env = Env(env1, env2, gain)
env
end
# register env; do it in global scope
__env = make_env()
__x0 = NestedEnvironments.initial_condition(__env)
@reg_env __env __x0
# test
function test()
env = make_env()
# initial condition
# if you extend `NestedEnvironments.initial_condition` for all sub environments, then `NestedEnvironments.initial_condition(env::Env)` will automatically complete a readable initial condition as NamedTuple.
x0 = NestedEnvironments.initial_condition(env) # auto-completion of initial condition
@show x0 # x0 = (env1 = -1, env2 = (env21 = [1 3 5 7; 2 4 6 8], env22 = [9 11 13 15; 10 12 14 16]))
t0 = 0.0
tf = 10.0
tspan = (t0, tf)
Δt = 0.01 # saveat; not numerical integration
ts = t0:Δt:tf
prob = ODEProblem(env, dynamics(env), x0, tspan)
@time sol = solve(prob, Tsit5(), saveat=ts)
# readable
xs = sol.u |> Map(_x -> @readable _x) |> collect # nested states
@test xs[1].env1 == x0.env1
@test xs[1].env2.env21 == x0.env2.env21
@test xs[1].env2.env22 == x0.env2.env22
# raw
_x0 = @raw x0
@show _x0 # _x0 = [-1, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]
@test _x0 == sol.u[1]
end