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, github: "woodward/integrator"},
  {:kino_vega_lite, "~> 0.1"}
])

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
alias Integrator.SampleEqns
alias Integrator.Point

chart =
  VL.new(
    width: 600,
    height: 400,
    title: "Solution of van der Pol Equation (μ = 1) with Dormand-Prince45"
  )
  |> 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.


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

output_fn = fn points ->
  points = if is_list(points), do: points, else: [points]
  points = points |> Enum.map(&Point.to_number(&1))

  points
  |> Enum.map(fn point -> 
    %Point{t: t, x: x} = point
    
      data = 
      [
        %{t: t, x: List.first(x), x_value: "x[0]"},
        %{t: t, x: List.last(x), x_value: "x[1]"}
      ]

    data |> Enum.each(& Kino.VegaLite.push(chart, &1))
  end)
end

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

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