summaryrefslogtreecommitdiff
path: root/docs/assets/n-body
diff options
context:
space:
mode:
Diffstat (limited to 'docs/assets/n-body')
-rw-r--r--docs/assets/n-body/script.js598
1 files changed, 598 insertions, 0 deletions
diff --git a/docs/assets/n-body/script.js b/docs/assets/n-body/script.js
new file mode 100644
index 0000000..e87d92c
--- /dev/null
+++ b/docs/assets/n-body/script.js
@@ -0,0 +1,598 @@
+// Global variables
+var plotOrbit = true
+var plotClassic = false
+var plotRandom = false
+var plotIndex = 0
+var delay = 10
+var tailLength = 1;
+var nBodies = 256;
+
+/*
+ * Earth - Sun Orbit Plot
+ * Taken from Numerics tutorial
+ */
+
+const G = 6.67e-11;
+const Msun = 2e30;
+const AU = 1.5e11;
+const v0 = Math.sqrt(G * Msun / AU); // SI
+
+function dR(r, v) {
+ const dv = [-G * Msun / Math.pow(r[0] ** 2 + r[1] ** 2, 3 / 2) * r[0], -G * Msun / Math.pow(r[0] ** 2 + r[1] ** 2, 3 / 2) * r[1]];
+ const dr = [...v];
+ return [dr, dv];
+}
+
+// initialize system
+let r = [-AU, 0];
+const theta = Math.atan2(r[1], r[0]);
+let v = [-v0 * Math.sin(theta), v0 * Math.cos(theta)];
+
+const t = Array.from({ length: 1001 }, (_, i) => i / 100 + 0.0); // years
+const yearSec = 365 * 24 * 3600;
+const dt = (t[1] - t[0]) * yearSec; // s
+const x4Plot = Array.from({ length: t.length }, () => 0);
+const y4Plot = Array.from({ length: t.length }, () => 0);
+
+// integrate using RK4!
+for (let i = 0; i < t.length; i++) {
+ const k1 = dR(r, v).map(x => x.map(y => y * dt));
+ const k2 = dR(r.map((ri, j) => ri + k1[0][j] / 2), v.map((vi, j) => vi + k1[1][j] / 2)).map(x => x.map(y => y * dt));
+ const k3 = dR(r.map((ri, j) => ri + k2[0][j] / 2), v.map((vi, j) => vi + k2[1][j] / 2)).map(x => x.map(y => y * dt));
+ const k4 = dR(r.map((ri, j) => ri + k3[0][j]), v.map((vi, j) => vi + k3[1][j])).map(x => x.map(y => y * dt));
+ r = r.map((ri, j) => ri + (k1[0][j] + 2 * k2[0][j] + 2 * k3[0][j] + k4[0][j]) / 6);
+ v = v.map((vi, j) => vi + (k1[1][j] + 2 * k2[1][j] + 2 * k3[1][j] + k4[1][j]) / 6);
+ x4Plot[i] = r[0];
+ y4Plot[i] = r[1];
+}
+
+// make data for plot
+var sun = { x: 0, y: 0 };
+const earth = { x: x4Plot.map(x => x / AU), y: y4Plot.map(y => y / AU) };
+const circle = { x: Array.from({ length: 1001 }, (_, i) => Math.cos(i / 100 * 2 * Math.PI)), y: Array.from({ length: 1001 }, (_, i) => Math.sin(i / 100 * 2 * Math.PI)) };
+
+/*
+ * Generic Functions
+ *
+ *
+ */
+
+function deltaR(coords, masses, nBodies, G) {
+ let x = coords[0];
+ let y = coords[1];
+ let vx = coords[2];
+ let vy = coords[3];
+
+ let delta = math.clone(coords);
+
+ for (let n = 0; n < nBodies; n++) {
+ let xn = x[n];
+ let yn = y[n];
+ let deltaVx = 0.0;
+ let deltaVy = 0.0;
+
+ for (let i = 0; i < nBodies; i++) {
+ if (i !== n) {
+ let sep = Math.sqrt(Math.pow(xn - x[i], 2) + Math.pow(yn - y[i], 2)); // Euclidean distance
+ deltaVx -= G * masses[i] * (xn - x[i]) / Math.pow(sep, 3);
+ deltaVy -= G * masses[i] * (yn - y[i]) / Math.pow(sep, 3);
+ }
+ }
+
+ delta[2][n] = deltaVx;
+ delta[3][n] = deltaVy;
+ }
+
+ delta[0] = vx;
+ delta[1] = vy;
+
+ return delta;
+}
+
+function detectCollisionsEscape(coords, deltaT, maxSep) {
+ const [x, y, vx, vy] = coords;
+ const V = vx.map((v, i) => Math.sqrt(v ** 2 + vy[i] ** 2));
+ const R = V.map(v => v * deltaT);
+ let collision = false, collisionInds = null, escape = false, escapeInd = null;
+
+ for (let n = 0; n < x.length; n++) {
+ const rn = R[n], xn = x[n], yn = y[n];
+ for (let i = 0; i < x.length; i++) {
+ if (i !== n) {
+ const minSep = rn + R[i];
+ const sep = Math.sqrt((xn - x[i]) ** 2 + (yn - y[i]) ** 2);
+ if (sep < minSep) {
+ collision = true;
+ collisionInds = [n, i];
+ } else if (sep > maxSep) {
+ escape = true;
+ escapeInd = n;
+ return [collision, collisionInds, escape, escapeInd];
+ }
+ }
+ }
+ }
+ return [collision, collisionInds, escape, escapeInd];
+}
+
+
+
+function step(coords, masses, deltaT, nBodies = 3, G = 6.67408313131313e-11) {
+ let k1 = math.multiply(deltaT, deltaR(coords, masses, nBodies, G));
+ let k2 = math.multiply(deltaT, deltaR(math.add(coords, math.multiply(k1, 0.5)), masses, nBodies, G));
+ let k3 = math.multiply(deltaT, deltaR(math.add(coords, math.multiply(k2, 0.5)), masses, nBodies, G));
+ let k4 = math.multiply(deltaT, deltaR(math.add(coords, k3), masses, nBodies, G));
+
+ coords = math.add(coords, math.multiply(math.add(k1, math.multiply(2.0, k2), math.multiply(2.0, k3), k4), 1/6));
+
+ return coords;
+}
+
+function nBodyStep(coords, masses, deltaT, maxSep, nBodies, G = 6.67408313131313e-11) { // Similar to our step function before, but keeping track of collisions
+ coords = step(coords, masses, deltaT, nBodies, G); // Update the positions as we did before
+ //console.log(detectCollisionsEscape(coords, deltaT, maxSep));
+ let [collision, collisionInds, escape, escapeInd] = detectCollisionsEscape(coords, deltaT, maxSep); // Detect collisions/escapes
+
+
+ if (collision) { // Do inelastic collision and delete extra body (2 -> 1)
+ const [i1, i2] = collisionInds;
+ const [x1, x2] = [coords[0][i1], coords[0][i2]];
+ const [y1, y2] = [coords[1][i1], coords[1][i2]];
+ const [vx1, vx2] = [coords[2][i1], coords[2][i2]];
+ const [vy1, vy2] = [coords[3][i1], coords[3][i2]];
+ const [px1, px2] = [masses[i1] * vx1, masses[i2] * vx2];
+ const [py1, py2] = [masses[i1] * vy1, masses[i2] * vy2];
+ const px = px1 + px2;
+ const py = py1 + py2;
+ const newM = masses[i1] + masses[i2];
+ const vfx = px / newM;
+ const vfy = py / newM;
+ coords[0][i1] = (x1 * masses[i1] + x2 * masses[i2]) / (masses[i1] + masses[i2]); // Center of mass
+ coords[1][i1] = (y1 * masses[i1] + y2 * masses[i2]) / (masses[i1] + masses[i2]);
+ coords[2][i1] = vfx;
+ coords[3][i1] = vfy;
+ coords[0].splice(i2, 1);
+ coords[1].splice(i2, 1);
+ coords[2].splice(i2, 1);
+ coords[3].splice(i2, 1);
+ masses[i1] = newM;
+ masses.splice(i2, 1);
+ nBodies--;
+ }
+ // Could also implement condition for escape where we stop calculating forces but I'm too lazy for now
+ return [coords, masses, nBodies, collision, collisionInds, escape, escapeInd];
+}
+
+function uniform(min, max) {
+ return Math.random() * (max - min) + min;
+}
+
+function deepCopyCoordsArray(arr) {
+ return arr.map(innerArr => innerArr.slice());
+}
+
+function genNBodyResults(nBodies, tStop, nTPts, nBodiesStop = 10, G = 6.67408313131313e-11) {
+
+ var btn = document.getElementById("startSim3");
+ // Set button text to Solving
+ var prevText = btn.innerHTML;
+ btn.innerHTML = "Solving...";
+
+ let coords = [Array(nBodies).fill(0), Array(nBodies).fill(0), Array(nBodies).fill(0), Array(nBodies).fill(0)];
+ const Mstar = 2e30;
+ const Mp = Mstar / 1e4;
+
+ for (let i = 0; i < nBodies; i++) { // Initialize coordinates on ~Keplerian orbits
+ let accept = false;
+ let r = null;
+ while (!accept) { // Prevent a particle from spawning within 0.2 AU too close to "star"
+ r = Math.random() * 2 * 1.5e11; // Say radius of 2 AU
+ if (r / 1.5e11 > 0.2) {
+ accept = true;
+ }
+ }
+ const theta = uniform(0, 2 * Math.PI);
+ const x = r * Math.cos(theta);
+ const y = r * Math.sin(theta);
+ const v = Math.sqrt(G * Mstar / r);
+ const perturbedV = v + v / 1000 * uniform(-1, 1); // Perturb the velocities ever so slightly
+ const vTheta = Math.atan2(y, x);
+ coords[0][i] = x;
+ coords[1][i] = y;
+ coords[2][i] = -perturbedV * Math.sin(vTheta);
+ coords[3][i] = perturbedV * Math.cos(vTheta);
+ }
+
+ //console.log('Initial coords:', coords);
+
+
+ let masses = Array(nBodies).fill(Mp); // Initialize masses
+ masses[0] = Mstar; // Make index one special as the central star
+ coords[0][0] = 0;
+ coords[1][0] = 0;
+ coords[2][0] = 0;
+ coords[3][0] = 0; // Initialize central star at origin with no velocity
+ const yearSec = 365 * 24 * 3600;
+ const time = Array.from({ length: nTPts }, (_, i) => i * tStop / (nTPts - 1) * yearSec); // Years -> s
+ let t = time[0];
+ const deltaT = time[1] - time[0];
+ let tInd = 0;
+ const coordsRecord = [deepCopyCoordsArray(coords)];
+ const massRecord = [masses.slice()]; // Initialize records with initial conditions
+
+
+ while (tInd < nTPts && nBodies > nBodiesStop) {
+ //console.log('Initial coords:', coords);
+ [coords, masses, nBodies] = nBodyStep(coords, masses, deltaT, 10 * 1.5e11, nBodies, G); // Update
+ coordsRecord.push(deepCopyCoordsArray(coords));
+ massRecord.push(masses.slice()); // Add to records
+ tInd++;
+ t = time[tInd];
+ //console.log(`currently at t = ${(t / yearSec).toFixed(2)} years\r`);
+ }
+ console.log(`final time = ${time[tInd] / yearSec} years with ${nBodies} bodies remaining`);
+
+ // Set button text to Start Simulation
+ btn.innerHTML = prevText;
+
+ return [coordsRecord, massRecord, time.slice(0, tInd + 1)];
+}
+
+
+function initCondGen(nBodies, vRange = [-7e3, 7e3], posRange = [-35, 35]) {
+ const m = Array.from({length: nBodies}, () => Math.random() * 1500 / 10);
+
+ const rad = m.map(x => Math.pow(x, 0.8));
+ const minV = vRange[0], maxV = vRange[1];
+ const minPos = posRange[0], maxPos = posRange[1];
+ const posList = [];
+
+ function checkPos(randPos, n, posList, rad) {
+ for (let i = 0; i < n - 1; i++) {
+ const dist = Math.sqrt(Math.pow(posList[i][0] - randPos[0], 2) + Math.pow(posList[i][1] - randPos[1], 2));
+ if (dist * 1.5e11 < (rad[n] + rad[i])) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ function genPos(nBodies, posList, rad, minPos, maxPos) {
+ posList.push([Math.random() * (maxPos - minPos) + minPos, Math.random() * (maxPos - minPos) + minPos]);
+ for (let n = 1; n < nBodies; n++) {
+ let acceptPos = false;
+ while (acceptPos === false) {
+ const randPos = [Math.random() * (maxPos - minPos) + minPos, Math.random() * (maxPos - minPos) + minPos];
+ acceptPos = checkPos(randPos, n, posList, rad);
+ if (acceptPos === true) {
+ posList.push(randPos);
+ }
+ }
+ }
+ return posList;
+ }
+
+ const pos = genPos(nBodies, posList, rad, minPos, maxPos).map(x => x.map(y => y * 1.5e11));
+ const coords = [new Array(nBodies).fill(0), new Array(nBodies).fill(0), new Array(nBodies).fill(0), new Array(nBodies).fill(0)];
+ const v = [];
+
+ for (let i = 0; i < nBodies; i++) {
+ coords[0][i] = pos[i][0];
+ coords[1][i] = pos[i][1];
+ const V = [Math.random() * (maxV - minV) + minV, Math.random() * (maxV - minV) + minV];
+ v.push(V);
+ coords[2][i] = V[0];
+ coords[3][i] = V[1];
+ }
+
+ return {m: m.map(x => x * 2e30), rad: rad.map(x => x * 7e8), coords: coords};
+}
+
+
+function calculateAndPlot() {
+ try {
+ Plotly.purge("plot");
+ } catch (e) {
+ console.log(e);
+ }
+
+ if (plotOrbit===true) {
+ let traceSun = {
+ x: [sun.x],
+ y: [sun.y],
+ mode: "markers",
+ marker: {
+ symbol: "star",
+ size: 10,
+ color: "gold",
+ },
+ name: "Sun",
+ };
+
+ const traceEarth = {
+ x: earth.x,
+ y: earth.y,
+ mode: "lines",
+ line: {
+ color: "white"
+ },
+ name: "Earth",
+ };
+
+ const traceOrbit = {
+ x: circle.x,
+ y:circle.y,
+ mode: "lines",
+ line: {
+ color: "crimson",
+ dash: "dash"
+ },
+ name: "1 AU Circle",
+ };
+
+ const earthSunLayout = {
+ title: "Earth-Sun Orbit",
+ xaxis: {
+ title: "x [AU]",
+ range: [-1.1,1.1],
+ showgrid: true,
+ gridcolor: "rgba(255,255,255,0.5)",
+ gridwidth: 1,
+ zeroline: true,
+ tickmode: "auto",
+ nticks: 5,
+ },
+ yaxis: {
+ title: "y [AU]",
+ range: [-1.1,1.1],
+ showgrid: true,
+ gridcolor: "rgba(255,255,255,0.5)",
+ gridwidth: 1,
+ zeroline: false,
+ tickmode: "auto",
+ nticks: 5,
+ },
+ margin: {
+ l: 50,
+ r: 50,
+ b: 50,
+ t: 50,
+ pad: 4,
+ },
+ paper_bgcolor: "black",
+ plot_bgcolor: "black",
+ };
+ Plotly.newPlot("plot",[traceSun,traceEarth,traceOrbit], earthSunLayout);
+ } else if (plotRandom==true) {
+
+ var [coordsRecordR, _, tR] = genNBodyResults(nBodies,1,nBodies*2);
+ //console.log(coordsRecordR);
+ const yearSec = 365 * 24 * 3600;
+
+ function createFrame(coordsR) {
+ if (!coordsR || !coordsR[0] || !coordsR[1]) {
+ return [];
+ }
+
+ const traceCentralStar = {
+ x: [coordsR[0][0] / 1.5e11],
+ y: [coordsR[1][0] / 1.5e11],
+ mode: 'markers',
+ type: 'scatter',
+ name: 'Central star',
+ marker: { color: 'gold', symbol: 'star', size: 10 },
+ };
+
+ const xCoords = coordsR[0].slice(1).map(x => x / 1.5e11);
+ const yCoords = coordsR[1].slice(1).map(y => y / 1.5e11);
+
+ const traceOtherBodies = {
+ x: xCoords,
+ y: yCoords,
+ mode: 'markers',
+ type: 'scatter',
+ name: '',
+ marker: { color: 'dodgerblue', symbol: 'circle', size: 2 },
+ };
+
+ return [traceCentralStar, traceOtherBodies];
+ }
+
+
+ function createLayout(i) {
+ return {
+ title: {
+ text: `N-Body Problem`,//= ${Number(t[i] / yearSec).toFixed(3)} years`,
+ x: 0.03,
+ y: 0.97,
+ xanchor: 'left',
+ yanchor: 'top',
+ font: { size: 14 },
+ },
+ xaxis: { title: 'x [AU]', range: [-2.1, 2.1] },
+ yaxis: { title: 'y [AU]', range: [-2.1, 2.1], scaleanchor: 'x', scaleratio: 1 },
+ showlegend: false,
+ margin: { l: 60, r: 40, t: 40, b: 40 },
+ width: 800,
+ height: 800,
+ plot_bgcolor: 'black',
+ };
+}
+
+ function animateNBodyProblem() {
+ const nFrames = tR.length;
+
+ for (let i = 0; i < nFrames; i++) {
+ const frameData = createFrame(coordsRecordR[i]);
+ const layout = createLayout(i);
+ //Plotly.newPlot(plotDiv, frameData, layout);
+ try {
+ Plotly.animate("plot", {
+ data: frameData, layout: layout
+ }, {
+ staticPlot: true,
+ transition: {
+ duration: 0,
+ },
+ frame: {
+ duration: 0,
+ redraw: false,
+ }
+ });
+ } catch (err) {
+ Plotly.newPlot('plot', frameData, layout);
+ }
+ }
+}
+
+animateNBodyProblem();
+
+
+ } else if (plotClassic==true) {
+ // Initial conditions setup
+ let M = [1, 1, 1];
+ let x = [-0.97000436, 0.0, 0.97000436];
+ let y = [0.24208753, 0.0, -0.24208753];
+ let vx = [0.4662036850, -0.933240737, 0.4662036850];
+ let vy = [0.4323657300, -0.86473146, 0.4323657300];
+ let Ei = -1 / Math.sqrt(Math.pow(2 * 0.97000436, 2) + Math.pow(2 * 0.24208753, 2)) - 2 / Math.sqrt(Math.pow(0.97000436, 2) + Math.pow(0.24208753, 2)) + 0.5 * (math.sum(math.add(math.dotPow(vx, 2), math.dotPow(vy, 2))));
+
+ function linspace(start, stop, num) {
+ const step = (stop - start) / (num - 1);
+ return Array.from({length: num}, (_, i) => start + (step * i));
+ }
+
+ let coords = [x, y, vx, vy];
+ const time = linspace(0, 6.3259, 1001);
+ let deltaT = time[1] - time[0];
+
+ let X = math.zeros(3, time.length).toArray();
+ let Y = math.zeros(3, time.length).toArray();
+ let VX = math.zeros(3, time.length).toArray();
+ let VY = math.zeros(3, time.length).toArray();
+
+ for (let i = 0; i < time.length; i++) {
+ coords = step(coords, M, deltaT, 3, 1);
+ X.forEach((_, idx) => X[idx][i] = coords[0][idx]);
+ Y.forEach((_, idx) => Y[idx][i] = coords[1][idx]);
+ VX.forEach((_, idx) => VX[idx][i] = coords[2][idx]);
+ VY.forEach((_, idx) => VY[idx][i] = coords[3][idx]);
+ }
+ function plotClassicFunc() {
+ var tailLength = 1;
+ if (plotIndex < tailLength) {
+ tailLength = 0;
+ } else if (plotIndex > time.length) {
+ plotIndex = 0;
+ } else {
+ tailLength = plotIndex - tailLength;
+ }
+
+ var currentIndex = plotIndex;
+
+ try {
+ time[currentIndex].toFixed(3);
+ } catch (e) {
+ currentIndex = 0;
+ }
+
+ const data = [
+ {
+ x: X[0].slice(tailLength, currentIndex),
+ y: Y[0].slice(tailLength, currentIndex),
+ mode: 'lines+markers',
+ marker: {
+ symbol: 'star',
+ size: 8,
+ line: { width: 0 },
+ },
+ line: {
+ width: 2,
+ },
+ name: '',
+ },
+ {
+ x: X[1].slice(tailLength, currentIndex),
+ y: Y[1].slice(tailLength, currentIndex),
+ mode: 'lines+markers',
+ marker: {
+ symbol: 'star',
+ size: 8,
+ line: { width: 0 },
+ },
+ line: {
+ width: 2,
+ },
+ name: '',
+ },
+ {
+ x: X[2].slice(tailLength, currentIndex),
+ y: Y[2].slice(tailLength, currentIndex),
+ mode: 'lines+markers',
+ marker: {
+ symbol: 'star',
+ size: 8,
+ line: { width: 0 },
+ },
+ line: {
+ width: 2,
+ },
+ name: '',
+ },
+ ];
+
+ // width: 1000, height: 400
+ const layout = {
+ title: '∞ Three-Body Problem: t = ' + time[currentIndex].toFixed(3),
+ xaxis: {
+ title: 'x',
+ range: [-1.1,1.1]
+ },
+ yaxis: {
+ title: 'y',
+ scaleanchor: 'x',
+ scaleratio: 1,
+ range: [-0.5,0.5]
+ },
+ plot_bgcolor: 'black',
+ paper_bgcolor: 'black',
+ font: {
+ color: 'white',
+ },
+ };
+
+ try {
+ Plotly.animate("plot", {
+ data: data, layout: layout
+ }, {
+ staticPlot: true,
+ transition: {
+ duration: 0,
+ },
+ frame: {
+ duration: 0,
+ redraw: false,
+ }
+ });
+ } catch (err) {
+ Plotly.newPlot('plot', data, layout);
+ }
+
+
+ plotIndex += delay;
+ if (plotClassic===true) {
+ try {
+ requestAnimationFrame(plotClassicFunc);
+ }
+ catch (err) {
+ console.log(err)
+ }
+ }
+
+ }
+
+ plotClassicFunc();
+
+ }
+}
+