File size: 5,902 Bytes
6d9631e 58a87ec |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 |
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Dark‑Matter Hybrid Simulation (Pyodide‑powered)</title>
<style>
:root{font-family:system-ui,sans-serif;background:#0b0e19;color:#e4e7ef}
h1{text-align:center;margin:0.5rem 0;font-size:1.4rem}
#controls{display:flex;justify-content:center;gap:1rem;margin:0.5rem 0;flex-wrap:wrap}
.ctrl{background:#1c2135;border:1px solid #2f3653;border-radius:0.5rem;padding:0.3rem 0.6rem;font-size:0.9rem;cursor:pointer;transition:background .3s}
.ctrl:hover{background:#2b3351}
#sim{display:block;margin:0 auto 1rem;border:1px solid #222b44;background:#000;border-radius:0.5rem}
label{font-size:0.8rem}
input[type=range]{width:8rem}
#status{font-size:0.8rem;text-align:center;margin-bottom:0.4rem;color:#9ac1ff}
</style>
<!-- Pyodide -->
<script src="https://cdn.jsdelivr.net/pyodide/v0.25.0/full/pyodide.js"></script>
</head>
<body>
<h1>Dark‑Matter Hybrid (Classical + Quantum) Toy Model</h1>
<canvas id="sim" width="600" height="600"></canvas>
<div id="status">Pyodide loading…</div>
<div id="controls">
<button id="toggle" class="ctrl" disabled>▶︎ Start</button>
<button id="reset" class="ctrl" disabled>⎌ Reset</button>
<button id="qView" class="ctrl" disabled>🌌 Quantum View</button>
<label>Particles
<input id="count" type="range" min="100" max="1000" step="100" value="500" disabled/>
<span id="countVal">500</span>
</label>
<label>Gravity
<input id="grav" type="range" min="0.1" max="2" step="0.1" value="1" disabled/>
<span id="gravVal">1.0</span>
</label>
</div>
<script>
/* ---------------- Pyodide bootstrap ---------------- */
let pyodideReady = loadPyodide({indexURL:"https://cdn.jsdelivr.net/pyodide/v0.25.0/full/"}).then(async pyodide=>{
await pyodide.loadPackage(["numpy"]);
// define a tiny Schrödinger‑like density generator on the Python side
const pyCode = `
import json, math, numpy as np
def quantum_density(size:int=64):
# 2‑D Gaussian wave‑packet as stand‑in for toy quantum result
x = np.linspace(-3,3,size)
X,Y = np.meshgrid(x,x)
Z = np.exp(-(X**2+Y**2))
Z /= Z.max() # normalize 0‑1
return json.dumps(Z.tolist())
`;
pyodide.runPython(pyCode);
return pyodide;
});
async function getQuantumDensity(size=64){
const pyodide = await pyodideReady;
const jsonStr = await pyodide.runPythonAsync(`quantum_density(${size})`);
return JSON.parse(jsonStr);
}
/* ---------------- Canvas & Globals ---------------- */
const canvas = document.getElementById("sim");
const ctx = canvas.getContext("2d");
const W = canvas.width, H = canvas.height;
const qCanvas = document.createElement("canvas");
qCanvas.width=W; qCanvas.height=H;
const qCtx = qCanvas.getContext("2d");
const $ = id=>document.getElementById(id);
const softening=4;
let G=1, particles=[], running=false, rafId;
let quantumOn=false, densityLoaded=false;
const status = $("status");
const rand = (min,max)=>Math.random()*(max-min)+min;
const makeParticle = ()=>({x:rand(0,W),y:rand(0,H),vx:rand(-0.1,0.1),vy:rand(-0.1,0.1),m:1+Math.random()*0.5});
function init(n=500){particles = Array.from({length:n}, makeParticle); densityLoaded=false; draw();}
function update(dt){
const n=particles.length;
for(let i=0;i<n;i++){
const p=particles[i]; let ax=0, ay=0;
for(let j=0;j<n;j++) if(i!==j){
const q=particles[j]; const dx=q.x-p.x, dy=q.y-p.y; const r2=dx*dx+dy*dy+softening; const inv=Math.pow(r2,-1.5);
ax+=G*q.m*dx*inv; ay+=G*q.m*dy*inv;
}
p.vx+=ax*dt; p.vy+=ay*dt;
}
for(const p of particles){p.x=(p.x+p.vx*dt+W)%W; p.y=(p.y+p.vy*dt+H)%H;}
}
function draw(){
ctx.fillStyle="black"; ctx.fillRect(0,0,W,H);
ctx.fillStyle="white"; for(const p of particles) ctx.fillRect(p.x,p.y,2,2);
if(quantumOn && densityLoaded) ctx.drawImage(qCanvas,0,0);
}
function step(){update(0.5); draw(); if(running) rafId=requestAnimationFrame(step);}
function buildDensityTexture(grid){
const size=grid.length; const scale=W/size; const img=qCtx.createImageData(W,H);
for(let i=0;i<size;i++){
for(let j=0;j<size;j++){
const val=Math.min(grid[i][j]*255*20,255);
for(let dx=0;dx<scale;dx++){
for(let dy=0;dy<scale;dy++){
const x=j*scale+dx, y=i*scale+dy, idx=(y*W+x)*4;
img.data[idx]=0; img.data[idx+1]=val; img.data[idx+2]=255; img.data[idx+3]=Math.min(val+30,255);
}
}
}
}
qCtx.putImageData(img,0,0);
densityLoaded=true; draw();
}
async function computeQuantum(){
status.textContent="Computing quantum density…";
try{
const grid=await getQuantumDensity(64);
buildDensityTexture(grid);
status.textContent="Quantum density ready ✔";
}catch(e){
console.error(e);
status.textContent="Quantum compute error";
quantumOn=false;
$("qView").textContent="🌌 Quantum View";
}
}
/* ---------------- UI Wiring (activated after Pyodide ready) ---------------- */
pyodideReady.then(()=>{
status.textContent="Ready";
["toggle","reset","qView","count","grav"].forEach(id=>$(id).disabled=false);
$("toggle").onclick=()=>{running=!running; $("toggle").textContent=running?"❚❚ Pause":"▶︎ Start"; if(running) step(); else cancelAnimationFrame(rafId);} ;
$("reset").onclick=()=>init(+$("count").value);
$("count").oninput=e=>$("countVal").textContent=e.target.value;
$("count").onchange=()=>init(+$("count").value);
$("grav").oninput=e=>{G=+e.target.value; $("gravVal").textContent=G.toFixed(1);} ;
$("qView").onclick=()=>{
quantumOn=!quantumOn;
$("qView").textContent=quantumOn?"🖥 Classical View":"🌌 Quantum View";
if(quantumOn && !densityLoaded) computeQuantum(); else draw();
};
init();
});
</script>
</body>
</html>
|