简体   繁体   中英

D3.js double pendulum odd behavior

I'm using D3.js to make a double pendulum simulator. I'm using 4th order Runge-Kutta to integrate the ODE's and I'm pretty sure I'm getting the correct values for theta and phi as I checked the output data with mathamatica. However the length of second pendulum keeps changing as it moves which should not be occurring and the path traced out by the pendulum doesn't seem correct either. Since I'm almost certain I am getting the correct values for phi and theta from the integration I think the issue may be how I calculate the x and y coordinates but I'm not sure.

 var width = 710, height = 710; var margin = {top: -20, right: 30, bottom: 40, left: 40}; var g = 9.81; //so length is in meters var l = 0.4; //length of pendulum 1 (top pendulum) var ell = 0.5; //ibid pendulum 2 (bottom pendulum) var m = 5; //mass of pendulum 1 var M = 5; // ibid pendulum 2 var theta = 40 * (Math.PI/180); //angle of top pendulum var phi = 10 * (Math.PI/180); //angle of bottom pendulum var thetaDot = 0; var phiDot = 0; var xBall1 = l*Math.sin(theta); var yBall1 = -l*Math.cos(theta); var xBall2 = ell*Math.sin(phi); var yBall2 = -ell*Math.cos(phi) + yBall1; var t = 0; var stepSize =.01; function uDot(theta, thetaDot, phi, phiDot) { //first ODE return(thetaDot); } function vDot(theta, thetaDot, phi, phiDot) { //second ODE val = -g*(2*m + M)*Math.sin(theta) - M*g*Math.sin(theta - (2*phi)) - 2*Math.sin(theta-phi)*M*((phiDot*phiDot*ell) + thetaDot*thetaDot*l*Math.cos(theta-phi)); val = val/( l*(2*m + M - M*Math.cos(2*theta - 2*phi)) ); return(val); } function wDot(theta, thetaDot, phi, phiDot) { //third ODE return(phiDot); } function sDot(theta, thetaDot, phi, phiDot) { val = 2*Math.sin(theta-phi)*(thetaDot*thetaDot*l*(m+M) + g*(m+M)*Math.cos(theta) + phiDot*phiDot*ell*M*Math.cos(theta-phi)); val = val/( ell*(2*m + M - M*Math.cos(2*theta - 2*phi)) ); return(val); } function RK4() { /* 4th order Runge-Kutta solver for system of 4 equations with 4 variables */ k0 = stepSize * uDot(theta, thetaDot, phi, phiDot); l0 = stepSize * vDot(theta, thetaDot, phi, phiDot); q0 = stepSize * wDot(theta, thetaDot, phi, phiDot); p0 = stepSize * sDot(theta, thetaDot, phi, phiDot); k1 = stepSize * uDot(theta+(0.5*k0), thetaDot+(0.5*l0), phi+(0.5*q0), phiDot+(0.5*p0)); l1 = stepSize * vDot(theta+(0.5*k0), thetaDot+(0.5*l0), phi+(0.5*q0), phiDot+(0.5*p0)); q1 = stepSize * wDot(theta+(0.5*k0), thetaDot+(0.5*l0), phi+(0.5*q0), phiDot+(0.5*p0)); p1 = stepSize * sDot(theta+(0.5*k0), thetaDot+(0.5*l0), phi+(0.5*q0), phiDot+(0.5*p0)); k2 = stepSize * uDot(theta+(0.5*k1), thetaDot+(0.5*l1), phi+(0.5*q1), phiDot+(0.5*p1)); l2 = stepSize * vDot(theta+(0.5*k1), thetaDot+(0.5*l1), phi+(0.5*q1), phiDot+(0.5*p1)); q2 = stepSize * wDot(theta+(0.5*k1), thetaDot+(0.5*l1), phi+(0.5*q1), phiDot+(0.5*p1)); p2 = stepSize * sDot(theta+(0.5*k1), thetaDot+(0.5*l1), phi+(0.5*q1), phiDot+(0.5*p1)); k3 = stepSize * uDot(theta+k2, thetaDot+l2, phi+q2, phiDot+p2); l3 = stepSize * vDot(theta+k2, thetaDot+l2, phi+q2, phiDot+p2); q3 = stepSize * wDot(theta+k2, thetaDot+l2, phi+q2, phiDot+p2); p3 = stepSize * sDot(theta+k2, thetaDot+l2, phi+q2, phiDot+p2); theta = theta + ((1/6) * (k0 + 2*k1 + 2*k2 + k3)); thetaDot = thetaDot + ((1/6) * (l0 + 2*l1 + 2*l2 + l3)); phi = phi + ((1/6) * (q0 + 2*q1 + 2*q2 + q3)); phiDot = phiDot + ((1/6) * (p0 + 2*p1 + 2*p2 + p3)); } d3.select("canvas").attr("width", width).attr("height", height); var canvas = d3.select("canvas"); var context = canvas.node().getContext("2d"); var svg = d3.select("#doublePendulum").attr("width", width).attr("height", height).append("g").attr("transform", "translate(" + margin.left + "," + margin.top + ")"); var xAxis = d3.scaleLinear().domain([-1.5, 1.5]).range([0, width]); svg.append("g").attr("transform", "translate(0," + height + ")").call(d3.axisBottom(xAxis)); var yAxis = d3.scaleLinear().domain([-1.5, 1.5]).range([height, 0]); svg.append("g").call(d3.axisLeft(yAxis)); var rod1 = svg.append('line') /* put before circle code so it doesn't go over the circle */.attr('id', 'rod1').attr('x2', xAxis(0)).attr('y2', yAxis(0)).attr('x1', xAxis(xBall1)).attr('y1', yAxis(yBall1)).attr('stroke', '#000000').attr('stroke-width', '2px'); var ball1 = svg.append('circle').attr('id', 'ball1').attr('cx', xAxis(xBall1)).attr('cy', yAxis(yBall1)).attr('r', 10).style('fill', '#000000'); var rod2 = svg.append('line').attr('id', 'rod2').attr('x2', xAxis(xBall1)).attr('y2', yAxis(yBall1)).attr('x1', xAxis(xBall2)).attr('y1', yAxis(yBall2)).attr('stroke', '#000000').attr('stroke-width', '2px'); var ball2 = svg.append('circle').attr('id', 'ball2').attr('cx', xAxis(xBall2)).attr('cy', yAxis(yBall2)).attr('r', 10).style('fill', '#000000'); function update() { context.beginPath(); context.strokeStyle = '#0026FF'; context.moveTo(xAxis(xBall2) + margin.left, yAxis(yBall2) + margin.top); t +=.01; RK4(); xBall1 = l*Math.sin(theta); yBall1 = -l*Math.cos(theta); xBall2 = ell*Math.sin(phi); yBall2 = -ell*Math.cos(phi) + yBall1; context.lineTo(xAxis(xBall2) + margin.left, yAxis(yBall2) + margin.top); context.stroke(); d3.select('#rod1').attr('y2', yAxis(0)).attr('x2', xAxis(0)).attr('y1', yAxis(yBall1)).attr('x1', xAxis(xBall1)); d3.select('#ball1').attr('cx', xAxis(xBall1)).attr('cy', yAxis(yBall1)); d3.select('#rod2').attr('y2', yAxis(yBall1)).attr('x2', xAxis(xBall1)).attr('y1', yAxis(yBall2)).attr('x1', xAxis(xBall2)); d3.select('#ball2').attr('cx', xAxis(xBall2)).attr('cy', yAxis(yBall2)); } var runApp = setInterval(function () { update(); }, 5);
 <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script> <div style="position: relative;"> <canvas style="position: absolute;"></canvas> <svg id="doublePendulum" style="position: absolute; z-index: 1;"></svg> </div>

You are correct, you aren't calculating the x,y positions correctly, more specifically, you are not calculating x correctly:

xBall1 = l*Math.sin(theta);
yBall1 = -l*Math.cos(theta);
xBall2 = ell*Math.sin(phi);
yBall2 = -ell*Math.cos(phi) + yBall1;

This is the same as how you calculate the initial positions, but after initialization, xBall2 is dependent on xBall1.

xBall1 = l*Math.sin(theta);
xBall2 = ell*Math.sin(phi) + xBall1;

Without looking at the rest of the internals, it doesn't quite feel right but I'm not sure what the resulting pattern should look like. However, doing this keeps both pendulums the same length throughout, so I'd venture it is correct:

 var width = 710, height = 710; var margin = {top: -20, right: 30, bottom: 40, left: 40}; var g = 9.81; //so length is in meters var l = 0.4; //length of pendulum 1 (top pendulum) var ell = 0.5; //ibid pendulum 2 (bottom pendulum) var m = 5; //mass of pendulum 1 var M = 1; // ibid pendulum 2 var theta = 40 * (Math.PI/180); //angle of top pendulum var phi = 10 * (Math.PI/180); //angle of bottom pendulum var thetaDot = 0; var phiDot = 0; var xBall1 = l*Math.sin(theta); var yBall1 = -l*Math.cos(theta); var xBall2 = ell*Math.sin(phi); var yBall2 = -ell*Math.cos(phi) + yBall1; var t = 0; var stepSize =.01; function uDot(theta, thetaDot, phi, phiDot) { //first ODE return(thetaDot); } function vDot(theta, thetaDot, phi, phiDot) { //second ODE val = -g*(2*m + M)*Math.sin(theta) - M*g*Math.sin(theta - (2*phi)) - 2*Math.sin(theta-phi)*M*((phiDot*phiDot*ell) + thetaDot*thetaDot*l*Math.cos(theta-phi)); val = val/( l*(2*m + M - M*Math.cos(2*theta - 2*phi)) ); return(val); } function wDot(theta, thetaDot, phi, phiDot) { //third ODE return(phiDot); } function sDot(theta, thetaDot, phi, phiDot) { val = 2*Math.sin(theta-phi)*(thetaDot*thetaDot*l*(m+M) + g*(m+M)*Math.cos(theta) + phiDot*phiDot*ell*M*Math.cos(theta-phi)); val = val/( ell*(2*m + M - M*Math.cos(2*theta - 2*phi)) ); return(val); } function RK4() { /* 4th order Runge-Kutta solver for system of 4 equations with 4 variables */ k0 = stepSize * uDot(theta, thetaDot, phi, phiDot); l0 = stepSize * vDot(theta, thetaDot, phi, phiDot); q0 = stepSize * wDot(theta, thetaDot, phi, phiDot); p0 = stepSize * sDot(theta, thetaDot, phi, phiDot); k1 = stepSize * uDot(theta+(0.5*k0), thetaDot+(0.5*l0), phi+(0.5*q0), phiDot+(0.5*p0)); l1 = stepSize * vDot(theta+(0.5*k0), thetaDot+(0.5*l0), phi+(0.5*q0), phiDot+(0.5*p0)); q1 = stepSize * wDot(theta+(0.5*k0), thetaDot+(0.5*l0), phi+(0.5*q0), phiDot+(0.5*p0)); p1 = stepSize * sDot(theta+(0.5*k0), thetaDot+(0.5*l0), phi+(0.5*q0), phiDot+(0.5*p0)); k2 = stepSize * uDot(theta+(0.5*k1), thetaDot+(0.5*l1), phi+(0.5*q1), phiDot+(0.5*p1)); l2 = stepSize * vDot(theta+(0.5*k1), thetaDot+(0.5*l1), phi+(0.5*q1), phiDot+(0.5*p1)); q2 = stepSize * wDot(theta+(0.5*k1), thetaDot+(0.5*l1), phi+(0.5*q1), phiDot+(0.5*p1)); p2 = stepSize * sDot(theta+(0.5*k1), thetaDot+(0.5*l1), phi+(0.5*q1), phiDot+(0.5*p1)); k3 = stepSize * uDot(theta+k2, thetaDot+l2, phi+q2, phiDot+p2); l3 = stepSize * vDot(theta+k2, thetaDot+l2, phi+q2, phiDot+p2); q3 = stepSize * wDot(theta+k2, thetaDot+l2, phi+q2, phiDot+p2); p3 = stepSize * sDot(theta+k2, thetaDot+l2, phi+q2, phiDot+p2); theta = theta + ((1/6) * (k0 + 2*k1 + 2*k2 + k3)); thetaDot = thetaDot + ((1/6) * (l0 + 2*l1 + 2*l2 + l3)); phi = phi + ((1/6) * (q0 + 2*q1 + 2*q2 + q3)); phiDot = phiDot + ((1/6) * (p0 + 2*p1 + 2*p2 + p3)); } d3.select("canvas").attr("width", width).attr("height", height); var canvas = d3.select("canvas"); var context = canvas.node().getContext("2d"); var svg = d3.select("#doublePendulum").attr("width", width).attr("height", height).append("g").attr("transform", "translate(" + margin.left + "," + margin.top + ")"); var xAxis = d3.scaleLinear().domain([-1.5, 1.5]).range([0, width]); svg.append("g").attr("transform", "translate(0," + height + ")").call(d3.axisBottom(xAxis)); var yAxis = d3.scaleLinear().domain([-1.5, 1.5]).range([height, 0]); svg.append("g").call(d3.axisLeft(yAxis)); var rod1 = svg.append('line') /* put before circle code so it doesn't go over the circle */.attr('id', 'rod1').attr('x2', xAxis(0)).attr('y2', yAxis(0)).attr('x1', xAxis(xBall1)).attr('y1', yAxis(yBall1)).attr('stroke', '#000000').attr('stroke-width', '2px'); var ball1 = svg.append('circle').attr('id', 'ball1').attr('cx', xAxis(xBall1)).attr('cy', yAxis(yBall1)).attr('r', 10).style('fill', '#000000'); var rod2 = svg.append('line').attr('id', 'rod2').attr('x2', xAxis(xBall1)).attr('y2', yAxis(yBall1)).attr('x1', xAxis(xBall2)).attr('y1', yAxis(yBall2)).attr('stroke', '#000000').attr('stroke-width', '2px'); var ball2 = svg.append('circle').attr('id', 'ball2').attr('cx', xAxis(xBall2)).attr('cy', yAxis(yBall2)).attr('r', 10).style('fill', '#000000'); // for debug: var i = 0; // function update() { context.beginPath(); context.strokeStyle = '#0026FF'; context.moveTo(xAxis(xBall2) + margin.left, yAxis(yBall2) + margin.top); t +=.01; RK4(); xBall1 = l*Math.sin(theta); yBall1 = -l*Math.cos(theta); xBall2 = ell*Math.sin(phi) + xBall1; yBall2 = -ell*Math.cos(phi) + yBall1; context.lineTo(xAxis(xBall2) + margin.left, yAxis(yBall2) + margin.top); context.stroke(); d3.select('#rod1').attr('y2', yAxis(0)).attr('x2', xAxis(0)).attr('y1', yAxis(yBall1)).attr('x1', xAxis(xBall1)); d3.select('#ball1').attr('cx', xAxis(xBall1)).attr('cy', yAxis(yBall1)); d3.select('#rod2').attr('y2', yAxis(yBall1)).attr('x2', xAxis(xBall1)).attr('y1', yAxis(yBall2)).attr('x1', xAxis(xBall2)); d3.select('#ball2').attr('cx', xAxis(xBall2)).attr('cy', yAxis(yBall2)); // for debug: if(i%50 == 0) { var dx = yBall1 - yBall2; var dy = xBall1 - xBall2; var h1 = Math.sqrt(dx*dx+dy*dy); var h0 = Math.sqrt(yBall1*yBall1+xBall1*xBall1); console.log("Pendulum Lengths: " + h0 + ", " + h1 ); } i++; // } var runApp = setInterval(function () { update(); }, 5);
 <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script> <div style="position: relative;"> <canvas style="position: absolute;"></canvas> <svg id="doublePendulum" style="position: absolute; z-index: 1;"></svg> </div>

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM