# Some questions on Transducers.jl

I’m recently interested in Transducers.jl.
However, I cannot understand some methods provided in the package.

For example, anybody can explain `foldxl` example in detail? I cannot figure out how the answer comes.

Also, is there any way to incorporate multiple inputs?
For example, I’d like to use a function `f(x, y, z) = x+y+z` like `xs |> Scan(f, y0, z0) |> collect` to compute functions recursively where `xs = (x1, x2, ..., xn)`.

`Transducers.Scan` expects a 2-arg function as the first argument [^1]. So, I’m not sure how you want to use `f`. Maybe it’s helpful to write down what you want to do in plain code (ok to be non-efficient or lengthy), so that people can know exactly what you want to compute.

[^1]: Transducers.jl APIs typically expect 2-arg (or sometimes 1-arg) function.

2 Likes

Oh, you’re right. I should’ve attached an example.

I’m trying to write a code for a numerical simulator in FP style (for practice).

The following code is a simple Euler integration.
Since the differential equation is in general given as multi-input functions (like `dyn(y, t)`),
I need a multi-argument way to use Transducers.jl (or, `...` functionality).
To reuse codes, multi-input support is necessary.

+) I’m interested in Transducers but I often feel it’s awkward and makes me hard to code fast. It would be delightful if you share your experience when using Transducers.jl and coding in FP style in aspects of 1) coding speed, 2) performance of codes, and 3) advantages.

``````julia> function euler(func)
_euler(y, t, dt) = y + func(y, t) * dt
end
euler (generic function with 1 method)

julia> dyn(y, t) = -y
dyn (generic function with 1 method)

julia> method = euler(dyn)
(::var"#_euler#2"{typeof(dyn)}) (generic function with 1 method)

julia> ts = 0:0.01:1
0.0:0.01:1.0

julia> ys = zeros(size(ts))
101-element Array{Float64,1}:
0.0
0.0
0.0
0.0
0.0
0.0
0.0
0.0
0.0
0.0
0.0
0.0
0.0
⋮

julia> ys = 2
2

julia> for i in 1:length(ts)-1
ys[i+1] = method(ys[i], ts[i], ts[i+1]-ts[i])
end

julia> ys
101-element Array{Float64,1}:
2.0
1.98
1.9602
1.940598
1.9211920200000001
1.9019800998
1.882960298802
1.86413069581398
1.8454893888558404
1.8270344949672819
1.808764150017609
1.790676508517433
1.7727697434322587
⋮

``````

EDIT: wrapping code makes it work as follows.

``````function integrator(func, y0, ts, method, args...; kwargs...)
_ts = ts[1:end-1]
Δts = diff(ts)
propagate = method(func)
_propagate(y, t_Δt) = propagate(y, t_Δt..., args...; kwargs...)
ys = zip(_ts, Δts) |> Scan(_propagate, y0) |> collect
return ys
end
``````

Before anything, I think you’d want to check out DifferentialEquations.jl universe, if you haven’t.

A short answer for your question is that Tnrasducers.jl is not very beneficial for this type of computation, unless you need to do something very specific.

Having said that, it is certainly possible to use Transducers.jl for this

``````euler(dyn, y0, dt) = Scan(y0) do y, i
y + dyn(y, i * dt) * dt
end
# e.g., 1:10 |> euler(dyn, 1.0, 0.01) |> collect
``````

or, if you want the input to be the sequence of times:

``````euler2(dyn, y0, t0) = ScanEmit((y0, t0)) do (y, t_prev), t
dt = t - t_prev
y_next = y + dyn(y, t) * dt
y_next, (y_next, t)
end
# e.g., 0.001:0.01:0.1 |> euler2(dyn, 1.0, 0.0) |> collect
``````

(Side note: you can think of `ScanEmit` as a “discrete dynamical system executor”. So, if you can express your simulator as a discrete dynamical system, you can always use `ScanEmit`.)

However, I don’t think it is always a good idea to implement this kind of code with Trasudcers.jl, unless you have some specific technical requirements. For example:

1. Fuse post-processing of the output of the system you are simulating; e.g., you are interested in a subset of the state variables or you want to filter out some outputs.
2. Fuse event detection predicate functions in a very efficient manner.
3. Simulating a system that is driven by input data (as in x(t) of dy/dt = f(y(t), x(t), t)) and the input data is stored in a complex data structure (e.g., nested array)

I’m probably the worst person to answer this question, since I designed the API for how I want to code.

But there are things very hard to express without transducers. For example, doing something like `Transducers.Consecutive` in parallel without transducers is very tricky (= very slow coding speed).

Transducers.jl APIs are fast sometimes and slow sometimes. I’ve been trying to reduce the slow cases but there are always possible improvements.

I think parallelism is the strongest selling point. Check out [ANN] Folds.jl: threaded, distributed, and GPU-based high-level data-parallel interface for Julia

3 Likes

Thanks a lot I’ve seen DifferentialEquations.jl but I sometimes felt it’s not suitable for my work including reinforcement learning and optimal control in continuous time with complex nested dynamical systems.

(I have no idea how to cite a certain part of your answer but) the examples you shared look nice.
It would be much easier to write codes with Transducers.jl if I follow your examples.

And I agree with that some functionality of Transducers.jl (especially `ScanEmit`) seems identical to discrete dynamical system and numerical simulation can be expressed as discrete dynamical system in general so I’ve been interested in numerical simulation using Transducers.jl.

Actually, I think that convenient post-processing and event detection are the most expected points when using Transducers.jl for numerical simulation. Thanks for clarifying it.

I’m also interested in `Folds.jl`.
I will practice and try to be familiar with Transducers universe.
Thank you again!

I should’ve mentioned that stateful transducers like the `euler` example I wrote is not parallelizable (in the sense you can’t do `... |> euler(dyn, y0, t) |> tcollect` etc.). (Well, unless when the system is linear, but then you don’t need to simulate it.)

I agree with you.
It’s probably impossible in principle to apply parallelism to a single episode simulation for a causal dynamical system.
Instead, parallelism can be applied (very easily) to simulation of multiple episodes (for example, various initial conditions).

It’s actually already on progress to develop a package for transuder-based dynamical system simulator and I think that it provides flexible and convenient interface. For more information, see LazyFym.jl and a question about parallelism.

1 Like