Transducer-based general-purpose simulator with lazy evaluation
Author JinraeKim
0 Stars
Updated Last
1 Year Ago
Started In
February 2021


LazyFym is a general-purpose simulator for dynamical systems.



The origin of the name Fym is from the previous versions of flight (but also general-purpose) simulators: fym in Python and FymEnvs.jl in Julia.


It has taken low priority for now to improve the simulation speed when the time span is very long.


Flexible usage pattern and nested environments

LazyFym is highly based on Julia's pipeline syntax and Transducers.jl. This makes it possible to construct a simulator using Transducers. Since Transducers are composable, it is highly flexible to make your own simulator. Also, LazyFym supports nested environments so that users can deal with complex dynamical systems.

Lazy evaluation

It is possible to lazily evaluate your simulator. To do so, you would need to incorporate packages related to lazy evaluation such as Lazy.jl and InfiniteArrays.jl.


It is not seemingly different from the sequential simulation. For example, you can perform simulation with various initial conditions by replacing collect by tcollect (thread-based) or dcollect (process-based), which are provided by Transducers.jl. You should run Julia REPL or execute codes with an appropriate option, for example, julia -t 4 for thread-based parallel simulation.

Predefined environments

LazyFym provides some predefined environments for reproducible codes. Take a look at src/fymenvs.jl.



LazyFym provides a Type Fym. Fym contains the information of an environment (system), probably consisting of other Fyms as sub-environments (sub-systems).

With other packages

LazyFym is highly based on Transducers.jl so various functionalities provided by Transducers.jl can be applied. Therefore, it is highly recommended to get used to Transducers.jl for the users of LazyFym (e.g., glossary of Transducers.jl). In addition, the following list of packages would be useful:

Quick start

You can also perform numerical simulations with lazy evaluation, nested custom environments, eager or lazy data postprocessing, and parallel simulation. Please take a look at directory test (some examples may be omitted).

using LazyFym
using Transducers

# using Lazy
using InfiniteArrays
using StructArrays
using Random
using Plots, LaTeXStrings

function (env::LazyFym.InputAffineQuadraticCostEnv, x, t)
    u = command(env, x)
    ẋ = LazyFym.(env, x, t, u)
_env = LazyFym.InputAffineQuadraticCostEnv()
command(env, x) = LazyFym.u_optimal(_env, x)  # you can customise it

function initial_condition(env::LazyFym.InputAffineQuadraticCostEnv)
    2*(rand(2) .- 0.5)

function postprocess(env::LazyFym.InputAffineQuadraticCostEnv)
    function _postprocess(_datum)
        t = _datum.t
        x = _datum.x
        x1 = _datum.x[1]
        x2 = _datum.x[2]
        u = command(env, x)
        datum = (; t = t, x = x, x1=x1, x2=x2, u = u)

function single()
    env = LazyFym.InputAffineQuadraticCostEnv()
    t0 = 0.0
    t1 = 5.00
    Δt = 0.01
    ts = t0:Δt:∞
    x0 = initial_condition(env)
    sim(x0) = t -> Sim(env, x0, ts, ẋ) |> TakeWhile(datum -> datum.t <= t) |> Map(postprocess(env)) |> collect |> StructArray
    traj_x0 = sim(x0)
    data = traj_x0(t1)
    # data = @lazy traj_x0(t1);  # for lazy evaluation, see Lazy.jl
    l = @layout [a; b; c]
    p_x1 = plot(data.t, data.x1,
                ylabel=L"x_{1}", label=nothing, ylim=(-2, 3))
    p_x2 = plot(data.t, data.x2,
                ylabel=L"x_{2}", label=nothing, ylim=(-2, 3))
    p_u = plot(data.t, data.u,
               xlabel=L"t", ylabel=L"u", label=nothing, ylim=(-2, 3))
    p = plot(p_x1, p_x2, p_u, layout = l)
    savefig(p, "figures/single.png")

function parallel()
    env = LazyFym.InputAffineQuadraticCostEnv()
    t0 = 0.0
    t1 = 5.00
    Δt = 0.01
    ts = t0:Δt:∞
    num = 10
    x0s = 1:num |> Map(i -> initial_condition(env))
    traj(x0) = Sim(env, x0, ts, ẋ) |> TakeWhile(datum -> datum.t <= t1) |> Map(postprocess(env)) |> collect
    data_parallel = x0s |> Map(x0 -> traj(x0)) |> Map(StructArray) |> tcollect
    # data_parallel_whole = data_parallel |> TCat(Threads.nthreads()) |> collect |> StructArray   # merge data
    l = @layout [a; b; c]
    p_x1 = plot()
    _ = data_parallel |> Map(data -> plot!(p_x1, data.t, data.x1,
                                           ylabel=L"x_{1}", label=nothing,
                                           ylim=(-2, 3))) |> collect
    p_x2 = plot()
    _ = data_parallel |> Map(data -> plot!(p_x2, data.t, data.x2,
                                           ylabel=L"x_{2}", label=nothing,
                                           ylim=(-2, 3))) |> collect
    p_u = plot()
    _ = data_parallel |> Map(data -> plot!(p_u, data.t, data.u,
                                           xlabel=L"t", ylabel=L"u", label=nothing,
                                           ylim=(-2, 3))) |> collect
    p = plot(p_x1, p_x2, p_u, layout = l)
    savefig(p, "figures/parallel.png")

# single()
# parallel()

single parallel

Performance Tips

Provide environment information

Since LazyFym automatically calculate the information of environments (including size, flatten_length, etc.) and may result in performance degeneration, you should consider extend LazyFym functions for your custom environments such as LazyFym.size to improve the simulation speed (about 2~3 times faster in most cases).

Postprocess data after simulation

Postprocessing will make your simulator slow. Postprocessing after obtaining simulation data would be beneficial if your simulation itself has bottleneck.

Used By Packages

No packages found.