2026年4月21日火曜日

ヒマワリ

ひまわりからの続き


図1:黄金比がつくる向日葵の種のシミュレーション


放送大学の自然科学の授業で,岸根順一郎さんが向日葵の種の配列のシミュレーションを見せていた。自然は黄金比を選んでいるというわけだ。バラメタを少し変えるといろいろな模様が出現する。

自分でも試してみたくて,バイブコーディングに走る。ChatGPT Auto で試したところ,Juliaに慣れていないせいか,なかなかエラーが消えない。しょうがないので,ChatGPT Thinking-5.4 でリセットしてからお願いする。プロンプトは以下のとおり。
Q(koshix):
Jupyter環境で,次のJulia プログラムをつくってください。 向日葵の種(塗りつぶした色つき小円でよい)の分布が 1粒ずつ内側から充足されていくインラインアニメーション。 これで意味が分かりますか? なるべく簡素で高速なものにしてください。

種の大きさや色相,パラメタの変更機能などを注文すると,「Julia で毎回 GIF や mp4 を作るより、Jupyter セル内に HTML/JavaScript の小さな UI を埋め込む 方がずっと軽くて速いです。」ということで,修正版ができた。饅頭の薄皮だけJuliaで中身の餡子はすべてJavascriptというよくわからないけど,よくあるパターンらしい。

最後に,Gemini Pro に投げてコード全体を簡素化してもらったところ,少しスッキリした。黄金比の部分を変えると,様々なパターンが現われる。

この中からJavascriptのコードを含むhtmlを取り出して,独立で動くように修正し,角度係数αを,1.60〜1.83まで0.01刻みで変化させた結果を合成した画像も,ほとんどそのまま出力してくれた。プロンプトを0.1刻みと間違えて入力しても,正しく解釈してくれる。



図2:α = 1.60〜1.83までの向日葵パターンの変化



using UUIDs

function sunflower_simulator()
    uid = "sf_" * replace(string(uuid4()), "-" => "")

    html = """
    <div id="$uid" style="font-family: sans-serif; line-height: 1.4;">
      <div style="margin-bottom: 0.6em;">
        <label>角度係数 α (2π/α): <input id="$(uid)_alpha" type="number" value="1.61803398875" step="0.0001" style="width: 8em;"></label>
        <span style="margin-left:1em;"></span>
        <label>粒数 N: <input id="$(uid)_N" type="number" value="500" step="50" style="width: 5em;"></label>
        <span style="margin-left:1em;"></span>
        <label>速度: <input id="$(uid)_step" type="number" value="5" step="1" min="1" style="width: 4em;"></label>
        <span style="margin-left:1em;"></span>
        <button id="$(uid)_run">実行</button>
      </div>

      <div style="margin-bottom: 0.5em; color: #444; font-size: 0.95em;">
        α を黄金比 φ ≈ 1.618 にすると、回転角 2π/α ≈ 222.5° になります。<br>
        ひまわり配列でよく使う黄金角 137.5° とは、向きが逆なだけで本質的に同じです。
      </div>

      <canvas id="$(uid)_canvas" width="520" height="520" style="border:1px solid #ddd; background:white;"></canvas>
      <div id="$(uid)_info" style="margin-top:0.5em; color:#333;"></div>
    </div>

    <script>
    (function() {
      // DOM要素の取得を簡素化
      const get = id => document.getElementById(`$(uid)_\${id}`);
      const [alphaIn, nIn, stepIn, runBtn, canvas, info] = ["alpha", "N", "step", "run", "canvas", "info"].map(get);
      const ctx = canvas.getContext("2d");
      let animId = null;

      function runOnce() {
        if (animId) cancelAnimationFrame(animId);
        
        const alpha = parseFloat(alphaIn.value);
        const N = Math.max(1, parseInt(nIn.value));
        const step = Math.max(1, parseInt(stepIn.value));

        if (!isFinite(alpha) || alpha === 0) {
          info.textContent = "α は 0 でない数を入れてください。";
          return;
        }

        info.textContent = `α = \${alpha.toFixed(6)}, 回転角 = \${(360 / alpha).toFixed(3)}°, 粒数 = \${N}, 速度 = \${step}`;

        // シード配列の生成 (Array.from でスッキリ記述)
        const angle = 2 * Math.PI / alpha;
        const seeds = Array.from({length: N}, (_, i) => {
          const k = i + 1;
          const r = Math.sqrt(k);
          return {
            x: r * Math.cos(k * angle),
            y: r * Math.sin(k * angle),
            color: `hsl(\${25 + 55 * (k / N)}, 90%, 59%)` // HSV(h, 78%, 96%) とほぼ同等のHSL色
          };
        });

        const W = canvas.width, H = canvas.height;
        const scale = 0.48 * Math.min(W, H) / (Math.sqrt(N) + 1);
        const rad = Math.max(0.9, 140 / Math.sqrt(N + 20));

        let k = 0;
        
        // 描画とアニメーションループを統合
        function tick() {
          k = Math.min(k + step, N);
          
          ctx.clearRect(0, 0, W, H);
          ctx.save();
          ctx.translate(W / 2, H / 2);
          
          for (let i = 0; i < k; i++) {
            ctx.beginPath();
            ctx.arc(seeds[i].x * scale, seeds[i].y * scale, rad, 0, 2 * Math.PI);
            ctx.fillStyle = seeds[i].color;
            ctx.fill();
          }
          ctx.restore();

          if (k < N) animId = requestAnimationFrame(tick);
        }
        
        tick();
      }

      runBtn.onclick = runOnce;

      // Enterキーでの実行イベントをまとめる
      [alphaIn, nIn, stepIn].forEach(el => 
        el.addEventListener("keydown", e => e.key === "Enter" && runOnce())
      );
    })();
    </script>
    """

    display("text/html", html)
end

sunflower_simulator()

0 件のコメント: