Help us understand the problem. What is going on with this article?

作って学ぶPhoenix LiveView、物理シミュレーションの可視化

Phoenix LiveViewを使って物理シミュレーションの可視化をしてみたので紹介します。
ソース: https://github.com/pojiro/ov_simulator

Peek 2020-02-03 14-12.gif

渋滞流のシミュレーションです。
渋滞はある一定以下の車間距離(言い換えると一定以上の密度)になると、
(相転移として)自然に発生することが研究により解明されています。

GIFでは渋滞のクラスタが後方に伝搬していく様子がみれます。

動機

Elixirはメッセージパッシングで(Elixir文脈の)プロセスの状態更新を行います。

Elixirを学び始めた頃、なんとなく、
プロセスの状態更新で物理シミュレーションの時間遷移を表現できそうだなとふわふわと思っていました。
このときはどのように実現するかの手段に対する知識がなくてアイデアで終わりました。

それからElixir、Phoenix、Phoenix LiveViewへと少しづつ学びを進めていた先日、
fukuokaexのもくもく会@piacerex さんが以下を紹介してくださったときに、手段が揃ったと思いました。
Phoenix LiveViewで物理シミュレーションの可視化ができます!

twitterで紹介されている動画のソースコードは https://github.com/pcorey/live_canvas

実現方法

状態更新による時間遷移

自身のプロセスへ状態更新のためのメッセージを投げて積分を回します。
これはサーバーサイトで行います。

  def handle_info(:update, %{assigns: %{particles: particles, step_size: step_size}} = socket) do
    particles = particles |> rk4(step_size)
    Process.send_after(self(), :update, 100)

    {:noreply, assign(socket, particles: particles)}
  end

粒子の状態(位置と速度)を持つparticlesをrk4(4次のルンゲクッタ)で積分し、粒子の状態を更新(時間遷移)させます。

描画

JavaScriptのcanvasを使ってクライアントサイドで行います。
particlesの状態はdatasetを使って、サーバーサイドからクライアントサイドへ渡します。
※データ渡しにdatasetを使う方法は https://github.com/pcorey/live_canvas で学びました。力技でびっくりしました。
 まだ、これがベストプラクティスなのかどうかはちょっと疑ってます。

データをサーバーから

      # particlesが更新されるとphx-hookが動作します
      # particlesはJSONエンコードされ文字列として、JavaScriptへ
      <div class="row" phx-hook="canvases"
        data-particles=<%= Jason.encode!(@particles)%> 
        data-space-size=<%= @space_size%>>
        <div class="column">
          <h2>circuit</h2>
          <canvas width="350px" height="350px" id="canvas1"></canvas>
        </div>
        <div class="column">
          <h2>limit cycle</h2>
          <canvas width="350px" height="350px" id="canvas2"></canvas>
        </div>
      </div>

クライアントへ、そしてcanvasで描画します。

hooks.canvases = {
  mounted() {
    let canvas1 = document.getElementById("canvas1")
    let canvas2 = document.getElementById("canvas2")
    let ctx1 = canvas1.getContext("2d")
    let ctx2 = canvas2.getContext("2d")

    Object.assign(this, {canvas1, ctx1, canvas2, ctx2})
  },
  updated(){
    let {canvas1, ctx1, canvas2, ctx2} = this
    let particles = JSON.parse(this.el.dataset.particles)
    let spaceSize = Number(this.el.dataset.spaceSize)

    let circuitRadius = 150
    let particleRadius = 10

    ctx1.clearRect(0, 0, canvas1.width, canvas1.height)
    ctx2.clearRect(0, 0, canvas2.width, canvas2.height)
    particles.forEach(particle => {
      let color_value = Math.round(particle.velocity / 2.0 * 200)
      // Draw Circuit
      ctx1.fillStyle = `rgba(${color_value}, 0, ${255 - color_value}, 1)`
      ctx1.beginPath()
      ctx1.arc(
        particleRadius + circuitRadius + circuitRadius * Math.cos(2 * Math.PI/spaceSize * particle.position),
        particleRadius + circuitRadius + circuitRadius * Math.sin(2 * Math.PI/spaceSize * particle.position),
        particleRadius, 0, 2 * Math.PI);
      ctx1.fill();

      // Draw limit cycle
      ctx2.fillStyle = `rgba(${color_value}, 0, ${255 - color_value}, 1)`
      ctx2.beginPath()
      ctx2.arc(
        particle.headway / 4.0 * 320,
        320 - particle.velocity / 2.0 * 320,
        particleRadius, 0, 2 * Math.PI);
      ctx2.fill();
    })
  }
};

おわり

Elixirを始めたころのアイデアを一つ実装することができました。

LiveViewを使うことで、サーバー-クライアント間のデータ授受がシンプルに実現できました。
また、数値計算のロジックをElixir、描画のみをcanvasで行うというように明確に分離されるので書きやすかったです。

この記事を読んで、これなら作れると思っていただけたら幸いです。

「いいね」よろしくお願いします。:wink:

fukuokaex
エンジニア/企業向けにElixirプロダクト開発・SI案件開発を支援する福岡のコミュニティ
https://fukuokaex.fun/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした