mirror of
https://gitlab.com/Anson-Projects/projects.git
synced 2025-06-15 14:36:47 +00:00
Merge branch 'pendulum' into 'master'
Double Pendulum See merge request Anson-Projects/projects!8
This commit is contained in:
commit
590f8cb106
1
.gitignore
vendored
1
.gitignore
vendored
@ -7,3 +7,4 @@ posts/*/\.jupyter_cache/
|
||||
!/.quarto/_freeze/
|
||||
!/.quarto/_freeze/*
|
||||
/.quarto/
|
||||
**/.DS_Store
|
||||
|
@ -1,7 +1,7 @@
|
||||
build:
|
||||
stage: build
|
||||
image:
|
||||
name: gcr.io/kaniko-project/executor:v1.21.0-debug
|
||||
name: gcr.io/kaniko-project/executor:v1.23.2-debug
|
||||
entrypoint: [""]
|
||||
script:
|
||||
- /kaniko/executor
|
||||
@ -9,6 +9,7 @@ build:
|
||||
--dockerfile "${CI_PROJECT_DIR}/Dockerfile"
|
||||
--destination "${CI_REGISTRY_IMAGE}:${CI_COMMIT_BRANCH}"
|
||||
--destination "${CI_REGISTRY_IMAGE}:latest"
|
||||
--cleanup
|
||||
|
||||
staging:
|
||||
cache:
|
||||
|
Binary file not shown.
155
posts/2025-05-10-double-pendulum-redux/index.qmd
Normal file
155
posts/2025-05-10-double-pendulum-redux/index.qmd
Normal file
@ -0,0 +1,155 @@
|
||||
---
|
||||
title: "Double Pendulum"
|
||||
description: |
|
||||
Lets create a double pendulum in Observable JS!
|
||||
date: 2024-05-09
|
||||
categories:
|
||||
- Observable JS
|
||||
- Code
|
||||
- Math
|
||||
draft: false
|
||||
freeze: true
|
||||
image: FeistyCompetentGarpike-mobile.mp4
|
||||
image-alt: "My original Double Pendulum done in Python and Processing.js"
|
||||
---
|
||||
|
||||
Quarto (which this blog is built on) recently added support for [Observable JS](https://observablehq.com/@observablehq/observable-javascript), which lets you make really cool interactive and animated visualizations. I have an odd fixation with finding new tools to visualize data, and while JS is far from the first tool I want to grab I figure I should give OJS a shot. Web browsers have been the best way to distribute and share applications for a long time now so I think its time that I invest some time to learn something better than a plotly diagram or jupyter notebook saved as a pdf to share data.
|
||||
|
||||
{fig-alt="My original Double Pendulum done in Python and Processing.js"}
|
||||
|
||||
Many years ago I hit the front page the [/r/python](https://www.reddit.com/r/Python/comments/ci1cg4/double_pendulum_made_with_processingpy/) with a double pendulum I made after watching the wonderful [Daniel Shiffman](https://thecodingtrain.com/showcase/author/anson-biggs) of the Coding Train. The video was posted on gfycat which is now defunct but the internet archive has saved it: [https://web.archive.org/web/20201108021323/https://gfycat.com/feistycompetentgarpike-daniel-shiffman-double-pendulum-coding-train](https://web.archive.org/web/20201108021323/https://gfycat.com/feistycompetentgarpike-daniel-shiffman-double-pendulum-coding-train)
|
||||
|
||||
I originally used Processing's Python bindings to make the animation. So, a lot of the hard work was done (mostly by Daniel), and this animation seems to be a crowd pleaser so I went ahead and ported it over. Keeping the code hidden since its not the focus here, but feel free to expand it and peruse.
|
||||
|
||||
|
||||
```{ojs}
|
||||
//| echo: false
|
||||
|
||||
// Interactive controls
|
||||
viewof length1 = Inputs.range([50, 300], {step: 10, value: 200, label: "Length of pendulum 1"})
|
||||
viewof length2 = Inputs.range([50, 300], {step: 10, value: 200, label: "Length of pendulum 2"})
|
||||
viewof mass1 = Inputs.range([10, 100], {step: 5, value: 40, label: "Mass of pendulum 1"})
|
||||
viewof mass2 = Inputs.range([10, 100], {step: 5, value: 40, label: "Mass of pendulum 2"})
|
||||
|
||||
```
|
||||
|
||||
|
||||
|
||||
```{ojs}
|
||||
//| code-fold: true
|
||||
//| column: page
|
||||
|
||||
pendulum = {
|
||||
const width = 900;
|
||||
const height = 600;
|
||||
const canvas = DOM.canvas(width, height);
|
||||
const ctx = canvas.getContext("2d");
|
||||
const gravity = .1;
|
||||
const traceCanvas = DOM.canvas(width, height);
|
||||
const traceCtx = traceCanvas.getContext("2d");
|
||||
traceCtx.fillStyle = "white";
|
||||
traceCtx.fillRect(0, 0, width, height);
|
||||
|
||||
const centerX = width / 2;
|
||||
const centerY = 200;
|
||||
|
||||
// State variables
|
||||
let angle1 = Math.PI / 2;
|
||||
let angle2 = Math.PI / 2;
|
||||
let angularVelocity1 = 0;
|
||||
let angularVelocity2 = 0;
|
||||
let previousPosition2X = -1;
|
||||
let previousPosition2Y = -1;
|
||||
|
||||
|
||||
function animate() {
|
||||
// Physics calculations (same equations as Python)
|
||||
let numerator1Part1 = -gravity * (2 * mass1 + mass2) * Math.sin(angle1);
|
||||
let numerator1Part2 = -mass2 * gravity * Math.sin(angle1 - 2 * angle2);
|
||||
let numerator1Part3 = -2 * Math.sin(angle1 - angle2) * mass2;
|
||||
let numerator1Part4 = angularVelocity2 * angularVelocity2 * length2 +
|
||||
angularVelocity1 * angularVelocity1 * length1 * Math.cos(angle1 - angle2);
|
||||
let denominator1 = length1 * (2 * mass1 + mass2 - mass2 * Math.cos(2 * angle1 - 2 * angle2));
|
||||
let angularAcceleration1 = (numerator1Part1 + numerator1Part2 + numerator1Part3 * numerator1Part4) / denominator1;
|
||||
|
||||
let numerator2Part1 = 2 * Math.sin(angle1 - angle2);
|
||||
let numerator2Part2 = angularVelocity1 * angularVelocity1 * length1 * (mass1 + mass2);
|
||||
let numerator2Part3 = gravity * (mass1 + mass2) * Math.cos(angle1);
|
||||
let numerator2Part4 = angularVelocity2 * angularVelocity2 * length2 * mass2 * Math.cos(angle1 - angle2);
|
||||
let denominator2 = length2 * (2 * mass1 + mass2 - mass2 * Math.cos(2 * angle1 - 2 * angle2));
|
||||
let angularAcceleration2 = (numerator2Part1 * (numerator2Part2 + numerator2Part3 + numerator2Part4)) / denominator2;
|
||||
|
||||
// Update velocities and angles
|
||||
angularVelocity1 += angularAcceleration1;
|
||||
angularVelocity2 += angularAcceleration2;
|
||||
angle1 += angularVelocity1;
|
||||
angle2 += angularVelocity2;
|
||||
|
||||
// Calculate positions
|
||||
let position1X = length1 * Math.sin(angle1);
|
||||
let position1Y = length1 * Math.cos(angle1);
|
||||
let position2X = position1X + length2 * Math.sin(angle2);
|
||||
let position2Y = position1Y + length2 * Math.cos(angle2);
|
||||
|
||||
// Clear and draw to canvas
|
||||
ctx.fillStyle = "white";
|
||||
ctx.fillRect(0, 0, width, height);
|
||||
ctx.drawImage(traceCanvas, 0, 0);
|
||||
|
||||
// Draw pendulum
|
||||
ctx.save();
|
||||
ctx.translate(centerX, centerY);
|
||||
|
||||
// First arm and mass
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(0, 0);
|
||||
ctx.lineTo(position1X, position1Y);
|
||||
ctx.strokeStyle = "black";
|
||||
ctx.lineWidth = 2;
|
||||
ctx.stroke();
|
||||
|
||||
ctx.beginPath();
|
||||
ctx.arc(position1X, position1Y, mass1/2, 0, 2 * Math.PI);
|
||||
ctx.fillStyle = "black";
|
||||
ctx.fill();
|
||||
|
||||
// Second arm and mass
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(position1X, position1Y);
|
||||
ctx.lineTo(position2X, position2Y);
|
||||
ctx.stroke();
|
||||
|
||||
ctx.beginPath();
|
||||
ctx.arc(position2X, position2Y, mass2/2, 0, 2 * Math.PI);
|
||||
ctx.fill();
|
||||
|
||||
ctx.restore();
|
||||
|
||||
// Draw trace line
|
||||
if (previousPosition2X !== -1 && previousPosition2Y !== -1) {
|
||||
traceCtx.save();
|
||||
traceCtx.translate(centerX, centerY);
|
||||
traceCtx.beginPath();
|
||||
traceCtx.moveTo(previousPosition2X, previousPosition2Y);
|
||||
traceCtx.lineTo(position2X, position2Y);
|
||||
traceCtx.strokeStyle = "black";
|
||||
traceCtx.stroke();
|
||||
traceCtx.restore();
|
||||
}
|
||||
|
||||
previousPosition2X = position2X;
|
||||
previousPosition2Y = position2Y;
|
||||
|
||||
requestAnimationFrame(animate);
|
||||
}
|
||||
|
||||
animate();
|
||||
return canvas;
|
||||
}
|
||||
```
|
||||
|
||||
## Conclusion
|
||||
|
||||
I think this is far from an idiomatic implementation so I'll keep this brief. I don't think I used JS or Observable as well as I could have so treat this as a beginner stabbing into the dark because thats essentially what the code is.
|
||||
|
||||
This was quite a bit more work than the [original Python implementation](https://gitlab.com/MisterBiggs/double_pendulum/blob/master/double_pendulum.pyde), but running real time, having beaufitul defaults, and being interactive without a backend make this leagues better than anything offered by any other language. There is definitely a loss of energy in the system over time that I attribute to Javascript being a mess, but I doubt that I would ever move all of my analysis to JS anyways so I don't think it matters. Its also very likely I'm doing something bad with my timesteps.
|
Loading…
x
Reference in New Issue
Block a user