Skip to content
Snippets Groups Projects
Commit 83414517 authored by nguyed99's avatar nguyed99
Browse files

Add interactive website

parent 1d0860e0
No related branches found
No related tags found
No related merge requests found
Pipeline #58935 passed
<!-- HTML and Pyodide related sections are borrowed from Hendrik Weimer -->
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>Three-body simulator</title>
<link rel="stylesheet" href="site.css">
</head>
<body>
<style>
body { font-size: 100%; font-family: monospace;}
.mtable table td tr { display: block; border-collapse: collapse;}
.mtable td { vertical-align: top; }
input { width: 180px; font-size: 100%; }
.error { color: red; font-size: 75%; }
.potential { width: 250px; }
.controls td { padding-left: 10px; padding-right: 10px;}
table.controls { border-radius: 8px; background: #c2c2c2;}
p.status { font-size: 100%; }
p.observables { font-size: 100%;}
</style>
<table class="mtable" style="width: 100%; max-width: 1024px;">
<tbody>
<tr>
<td id="parent" style="width: 60%;">
<p id="status" class="status">Loading Pyodide...</p>
<canvas id="canvas" style="width: 100%;"></canvas>
<table class="controls">
<tr>
<td>x<sub>0</sub></td>
<td>x<sub>1</sub></td>
<td>x<sub>2</sub></td>
</tr>
<tr>
<td><input id="x0" value="-1.5" ></td>
<td><input id="x1" value="0" ></td>
<td><input id="x2" value="2.0" ></td>
</tr>
<tr>
<td><p class="error" id="errorx0"></p></td>
<td><p class="error" id="errorx1"></p></td>
<td><p class="error" id="errorx2"></p></td>
</tr>
</table>
</td>
<td style="width: 100%;">
<table>
<tr>
<td>
<p class="observables" id="angular_mom">
</td>
</tr>
</table>
</td>
</tr>
</tbody>
</table>
<script type="text/javascript" src="https://cdn.jsdelivr.net/pyodide/v0.24.1/full/pyodide.js"></script>
<script type="text/javascript">
async function main() {
let pyodide = await loadPyodide();
await pyodide.loadPackage(["numpy", "scipy"]);
pyodide.runPython(await (await fetch("threebody.py")).text());
}
main();
</script>
</body>
</html>
\ No newline at end of file
@media (prefers-color-scheme: light) {
:root {
--background-color: #f2f2f2;
--softbox-color: #c2c2c2;
--foreground-color: #222222;
--link-color: blue;
--link-vis-color: rebeccapurple;
--emph-color: #116611;
}
}
@media (prefers-color-scheme: dark) {
:root {
--background-color: #222222;
--softbox-color: #333333;
--foreground-color: #f2f2f2;
--link-color: cyan;
--link-vis-color: rebeccapurple;
--emph-color: #99ff99;
}
}
body {
background: var(--background-color);
color: var(--foreground-color);
font-family: monospace;
max-width: 40em;
}
.box {
border-width: 1px;
border-style: solid;
border-color: var(--foreground-color);
border-radius: 8px;
margin: 1em;
padding: 1em;
}
.softbox {
background: var(--softbox-color);
border-radius: 8px;
margin: 1em;
padding: 1em;
width: fit-content;
}
.linkbox::before {
content: "=> ";
}
.shellbox > *::before {
content: "$ ";
}
.indent {
margin-left: 1em;
}
.list {
list-style-type: none;
}
.bold {
font-weight: bold;
}
a {
color: var(--link-color);
}
a:visited {
color: var(--link-vis-color);
}
.bmtitle {
font-weight: bold;
}
.bmtags {
font-style: italic;
}
.keepws {
white-space: pre;
}
em {
color: var(--emph-color);
font-style: normal;
font-weight: bold;
}
code {
color: var(--emph-color);
}
table {
border-spacing: 1em 0px;
}
\ No newline at end of file
"""
HTML and Pyodide related sections are borrowed from Hendrik Weimer
"""
from js import window, document, console
from pyodide.ffi import create_proxy
import numpy as np
from itertools import combinations
from scipy.spatial.distance import pdist
status = document.getElementById("status")
proxy = dict()
globals = dict()
globals['N'] = 3
globals['m'] = np.array([1, 2, 1])
globals['x'] = np.zeros((globals['N'], 2))
globals['p'] = np.zeros((globals['N'], 2))
M_pixel = np.zeros((2, 2))
globals['firstrun'] = True
def E():
x = globals['x']
p = globals['p']
m = globals['m']
m_combinations = list(combinations(range(3), 2))
m_prod = np.array([m[i[0]] * m[i[1]] for i in m_combinations])
E_kin = np.sum(np.sum(p**2, axis=1) / (2 * m))
E_pot = np.sum(-m_prod / pdist(x))
return E_kin + E_pot
def F(x):
x0 = x[0, :]
x1 = x[1, :]
x2 = x[2, :]
return np.array([
-globals['m'][0] * (globals['m'][1] * (x0 - x1) / np.linalg.norm(x0 - x1)**3 + globals['m'][2] *
(x0 - x2) / np.linalg.norm(x0 - x2)**3),
-globals['m'][1] * (globals['m'][0] * (x1 - x0) / np.linalg.norm(x1 - x0)**3 + globals['m'][2] *
(x1 - x2) / np.linalg.norm(x1 - x2)**3),
-globals['m'][2] * (globals['m'][0] * (x2 - x0) / np.linalg.norm(x2 - x0)**3 + globals['m'][1] *
(x2 - x1) / np.linalg.norm(x2 - x1)**3),
])
def verlet(F, x0, p0, m, dt):
"""
Verlet integrator for one time step
"""
x = x0
p = p0
p = p + 1 / 2 * F(x) * dt
x = x + 1 / m[:, np.newaxis] * p * dt
p = p + 1 / 2 * F(x) * dt
return x, p
def draw_circle(ctx, x, y, r=5):
pixelx = cwidth / 10 * (x + 5)
pixely = cheight / 10 * (y + 5)
ctx.beginPath()
ctx.arc(int(pixelx), int(pixely), r, 0, 2 * np.pi)
ctx.fillStyle = 'blue'
ctx.fill()
ctx.stroke()
def replot_canvas():
dt = 1 / 25 / 10
canvas = document.getElementById("canvas")
ctx = canvas.getContext("2d")
ctx.clearRect(0, 0, cwidth, cheight)
x, p = verlet(F, globals['x'], globals['p'], globals['m'], dt)
globals['x'] = x
globals['p'] = p
for i in range(globals['N']):
draw_circle(ctx, x[i][0], x[i][1], 5 * globals['m'][i]**(1 / 3))
L0 = float(np.cross(x[0, :], p[0, :]))
L1 = float(np.cross(x[1, :], p[1, :]))
L2 = float(np.cross(x[2, :], p[2, :]))
L_tot = L0 + L1 + L2
COM = np.sum(globals['m']) * (globals['m'][0] * x[0, :] + globals['m'][1] * x[1, :] + globals['m'][2] * x[2, :])
angular_mom = document.getElementById("angular_mom")
s = f"L0 = {L0:.6f}Ẑ<br>L1 = {L1:.6f}Ẑ<br>L2 = {L2:.6f}Ẑ<br>L_tot = {L_tot:.6f}Ẑ<br><br>E = {E():.6f}<br> COM = {COM[0]:.6f}X̂, {COM[1]:.6f}Ŷ"
angular_mom.innerHTML = s
def push_queue(func, str):
status.innerHTML = str
proxy[func] = create_proxy(func)
window.setTimeout(proxy[func], 100)
def set_main():
console.log("threebody: entering set_main")
global cwidth, cheight, pwidth
canvas = document.getElementById("canvas")
parentwidth = document.getElementById("parent").clientWidth
cwidth = parentwidth
cheight = int(cwidth * 0.75)
pwidth = cwidth
canvas.setAttribute("width", cwidth)
canvas.setAttribute("height", cheight)
console.log("threebody: exiting set_main")
push_queue(init_ode, "&nbsp;")
def init_ode(foo=0):
console.log("threebody: entering init_ode")
if not globals['firstrun']:
window.clearInterval(globals['timer'])
exit_code = 0
for i in range(3):
error = document.getElementById("errorx" + str(i))
try:
globals['x'][i][0] = document.getElementById("x" + str(i)).value
error.innerHTML = ""
except ValueError:
error.innerHTML = "Input error"
status.innerHTML = ""
exit_code = 1
globals['p'][0][1] = 1
globals['p'][1][1] = 0
globals['p'][2][1] = -1
if exit_code == 0:
console.log("setting timer")
proxy[replot_canvas] = create_proxy(replot_canvas)
globals['timer'] = window.setInterval(proxy[replot_canvas], 40)
if globals['firstrun']:
for i in ["x0", "x1", "x2"]:
ii = document.getElementById(i)
ii.addEventListener("change", proxy[init_ode])
globals['firstrun'] = 0
push_queue(set_main, "Setting up main window...")
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment