Created
August 8, 2025 09:19
-
-
Save calpa/32b7a96fd54954c5b3d5fc19ab70b343 to your computer and use it in GitHub Desktop.
Pyodide demo: interactive Monte Carlo π estimation with NumPy and Matplotlib (one-click run).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # Pyodide + NumPy 互動蒙特卡羅:一鍵估算 π 值與圖形化教學 | |
| import js, io, base64 | |
| import numpy as np | |
| import matplotlib.pyplot as plt | |
| # ---------- UI:頂端標籤 + 控制面板 ---------- | |
| tag = js.document.createElement("div") | |
| tag.textContent = "Pyodide × NumPy Monte Carlo π" | |
| tag.style.cssText = ("position:absolute;top:0;left:50%;transform:translateX(-50%);z-index:10000;" | |
| "background:rgba(0,0,0,.7);color:#fff;padding:6px 10px;border-radius:0 0 8px 8px;") | |
| js.document.body.append(tag) | |
| panel = js.document.createElement("div") | |
| panel.style.cssText = ("position:fixed;inset:auto 16px 16px auto;right:16px;bottom:16px;z-index:9999;" | |
| "background:#f5f5f5;border-radius:12px;box-shadow:0 10px 30px rgba(0,0,0,.15);" | |
| "padding:16px;min-width:280px;font:14px/1.4 system-ui,-apple-system,Segoe UI,Roboto;") | |
| panel.innerHTML = """ | |
| <div style="font-weight:600;margin-bottom:8px;">蒙特卡羅估算 π</div> | |
| <label>樣本數 N:</label> | |
| <input id="mc-n" type="number" value="50000" min="1000" step="1000" style="width:120px;margin-left:6px;"> | |
| <button id="mc-run" style="margin-left:8px;padding:6px 10px;background:#172966;color:#fff;border:none;border-radius:8px;cursor:pointer;"> | |
| 一鍵估算 | |
| </button> | |
| <div id="mc-msg" style="margin-top:8px;color:#333;"></div> | |
| """ | |
| js.document.body.append(panel) | |
| # 圖像容器(置中 Modal) | |
| overlay = js.document.createElement("div") | |
| overlay.style.cssText = ("position:fixed;inset:0;background:rgba(0,0,0,.5);display:flex;" | |
| "align-items:center;justify-content:center;z-index:9000;") | |
| modal = js.document.createElement("div") | |
| modal.style.cssText = ("background:#fff;padding:16px;border-radius:12px;" | |
| "box-shadow:0 10px 30px rgba(0,0,0,.2);max-width:95vw;max-height:80vh;") | |
| img = js.document.createElement("img") | |
| img.style.cssText = "max-width:90vw;max-height:70vh;display:block;" | |
| modal.append(img) | |
| overlay.append(modal) | |
| js.document.body.append(overlay) | |
| # ---------- 核心計算 + 視覺化 ---------- | |
| def run_mc(event=None): | |
| # 讀 N | |
| N_input = js.document.getElementById("mc-n") | |
| try: | |
| N = int(N_input.value) | |
| except Exception: | |
| N = 50000 | |
| N_input.value = "50000" | |
| # 生成隨機點於 [-1,1]x[-1,1] | |
| # 使用向量化加速 | |
| pts = np.random.uniform(-1.0, 1.0, size=(N, 2)) | |
| r2 = np.sum(pts**2, axis=1) | |
| inside = (r2 <= 1.0) | |
| count_in = int(np.count_nonzero(inside)) | |
| pi_hat = 4.0 * count_in / N | |
| # 理論 p = π/4(落在四分之一圓的機率),用已知 π 近似估計標準誤 | |
| p = np.pi / 4.0 | |
| se = 4.0 * np.sqrt(p * (1 - p) / N) # 標準誤 | |
| lo, hi = pi_hat - 1.96 * se, pi_hat + 1.96 * se | |
| # 圖:只畫前 2000 點(節省效能) | |
| k = min(N, 2000) | |
| pts_show = pts[:k] | |
| inside_show = inside[:k] | |
| plt.figure(figsize=(6, 6), dpi=120) | |
| # 方形邊界 | |
| plt.plot([-1, 1, 1, -1, -1], [-1, -1, 1, 1, -1], color='#999', linewidth=1) | |
| # 單位圓 | |
| t = np.linspace(0, 2*np.pi, 400) | |
| plt.plot(np.cos(t), np.sin(t), color='#172966', linewidth=2, label='Unit circle') | |
| # 點散佈 | |
| if k > 0: | |
| A = pts_show[inside_show] | |
| B = pts_show[~inside_show] | |
| if A.size > 0: | |
| plt.scatter(A[:,0], A[:,1], s=8, alpha=0.7, label='Inside') | |
| if B.size > 0: | |
| plt.scatter(B[:,0], B[:,1], s=8, alpha=0.5, label='Outside') | |
| plt.gca().set_aspect('equal', adjustable='box') | |
| plt.xlim(-1.05, 1.05); plt.ylim(-1.05, 1.05) | |
| plt.title(f"Monte Carlo π ≈ {pi_hat:.6f} (N={N})", fontsize=14, fontweight='bold') | |
| plt.legend(loc='lower left', fontsize=9) | |
| plt.tight_layout() | |
| # 轉成 data URL 並顯示 | |
| buf = io.BytesIO() | |
| plt.savefig(buf, format="png", bbox_inches="tight", facecolor="white") | |
| buf.seek(0) | |
| data_url = "data:image/png;base64," + base64.b64encode(buf.read()).decode("ascii") | |
| plt.close() | |
| img.src = data_url | |
| img.alt = "Monte Carlo π" | |
| # 文案:估計值 + 誤差 + 95% CI + 收斂提示 | |
| msg = js.document.getElementById("mc-msg") | |
| msg.innerHTML = ( | |
| f"估計 π 值:<b>{pi_hat:.6f}</b><br>" | |
| f"標準誤(SE):≈ <b>{se:.6f}</b> 95% CI:[{lo:.6f}, {hi:.6f}]<br>" | |
| f"<span style='opacity:.75'>提示:標準誤隨樣本數 N 以 ~1/√N 收斂,增加 N 可降低不確定性。</span>" | |
| ) | |
| # 綁定一鍵估算 | |
| js.document.getElementById("mc-run").onclick = run_mc | |
| # 初次自動跑一次,確保有畫面 | |
| run_mc() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment