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>