Skip to content

Instantly share code, notes, and snippets.

@calpa
Created August 8, 2025 09:19
Show Gist options
  • Select an option

  • Save calpa/32b7a96fd54954c5b3d5fc19ab70b343 to your computer and use it in GitHub Desktop.

Select an option

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).
# 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