Powered by AppSignal & Oban Pro
Would you like to see your link here? Contact us

Output Functions

guides/output_functions.livemd

Output Functions

Mix.install([
  {:integrator, "~> 0.1.2"},
  {:kino_vega_lite, "~> 0.1.7"}
])

Usage

An output function lets you plot the results of an integration or simulation while it’s occurring, or send the data to an animation tool. Let’s see how that works.

First, we’ll need to set up an empty chart to receive the data:

alias VegaLite, as: VL

chart =
  VL.new(
    width: 600,
    height: 400,
    title: "Solution of van der Pol Equation (μ = 1) with ode45"
  )
  |> VL.mark(:line, point: true, tooltip: true)
  |> VL.encode_field(:x, "t", type: :quantitative)
  |> VL.encode_field(:y, "x", type: :quantitative)
  |> VL.encode_field(:color, "x_value", type: :nominal)
  |> Kino.VegaLite.new()
  |> Kino.render()

Now, let’s connect an output function to the simulation, and we’ll inject a Process.sleep(50) on purpose so that the simulation takes a while. Watch the chart above while you run the simulation below.

alias Integrator.SampleEqns

t_initial = Nx.tensor(0.0, type: :f64)
t_final = Nx.tensor(20.0, type: :f64)
x_initial = Nx.tensor([2.0, 0.0], type: :f64)

output_fn = fn t, x ->
  # t and x are lists of Nx tensors

  Enum.zip(t, x)
  |> Enum.map(fn {t, x} ->
    [
      %{t: Nx.to_number(t), x: Nx.to_number(x[0]), x_value: "x[0]"},
      %{t: Nx.to_number(t), x: Nx.to_number(x[1]), x_value: "x[1]"}
    ]
  end)
  |> List.flatten()
  |> Enum.map(fn point ->
    Kino.VegaLite.push(chart, point)

    # Sleep on purpose to slow down the simulation:
    Process.sleep(50)
  end)
end

opts = [output_fn: output_fn, type: :f64]

solution =
  Integrator.integrate(&SampleEqns.van_der_pol_fn/2, [t_initial, t_final], x_initial, opts)