问题描述
我正在使用 D3.js 制作双摆模拟器。我正在使用四阶 Runge-Kutta 来集成 ODE,并且我很确定我在使用 mathamatica 检查输出数据时得到了正确的 theta 和 phi 值。然而,第二个钟摆的长度随着它的移动而不断变化,这不应该发生,而且钟摆描绘的路径似乎也不正确。由于我几乎可以肯定我从积分中获得了正确的 phi 和 theta 值,我认为问题可能在于我如何计算 x 和 y 坐标,但我不确定。
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,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,phiDot) { //third ODE
return(phiDot);
}
function sDot(theta,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,phiDot);
l0 = stepSize * vDot(theta,phiDot);
q0 = stepSize * wDot(theta,phiDot);
p0 = stepSize * sDot(theta,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),phiDot+(0.5*p0));
q1 = stepSize * wDot(theta+(0.5*k0),phiDot+(0.5*p0));
p1 = stepSize * sDot(theta+(0.5*k0),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),phiDot+(0.5*p1));
q2 = stepSize * wDot(theta+(0.5*k1),phiDot+(0.5*p1));
p2 = stepSize * sDot(theta+(0.5*k1),phiDot+(0.5*p1));
k3 = stepSize * uDot(theta+k2,thetaDot+l2,phi+q2,phiDot+p2);
l3 = stepSize * vDot(theta+k2,phiDot+p2);
q3 = stepSize * wDot(theta+k2,phiDot+p2);
p3 = stepSize * sDot(theta+k2,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','2px');
var ball2 = svg.append('circle')
.attr('id','ball2')
.attr('cx',xAxis(xBall2))
.attr('cy',yAxis(yBall2))
.attr('r','#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>
解决方法
您是对的,您没有正确计算 x,y 位置,更具体地说,您没有正确计算 x:
xBall1 = l*Math.sin(theta);
yBall1 = -l*Math.cos(theta);
xBall2 = ell*Math.sin(phi);
yBall2 = -ell*Math.cos(phi) + yBall1;
这与计算初始位置的方式相同,但初始化后,xBall2 依赖于 xBall1。
xBall1 = l*Math.sin(theta);
xBall2 = ell*Math.sin(phi) + xBall1;
不看内部的其余部分,感觉不太正确,但我不确定最终的模式应该是什么样子。但是,这样做可以使两个钟摆始终保持相同的长度,所以我敢说这是正确的:
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,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,phiDot) { //third ODE
return(phiDot);
}
function sDot(theta,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,phiDot);
l0 = stepSize * vDot(theta,phiDot);
q0 = stepSize * wDot(theta,phiDot);
p0 = stepSize * sDot(theta,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),phiDot+(0.5*p0));
q1 = stepSize * wDot(theta+(0.5*k0),phiDot+(0.5*p0));
p1 = stepSize * sDot(theta+(0.5*k0),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),phiDot+(0.5*p1));
q2 = stepSize * wDot(theta+(0.5*k1),phiDot+(0.5*p1));
p2 = stepSize * sDot(theta+(0.5*k1),phiDot+(0.5*p1));
k3 = stepSize * uDot(theta+k2,thetaDot+l2,phi+q2,phiDot+p2);
l3 = stepSize * vDot(theta+k2,phiDot+p2);
q3 = stepSize * wDot(theta+k2,phiDot+p2);
p3 = stepSize * sDot(theta+k2,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','2px');
var ball2 = svg.append('circle')
.attr('id','ball2')
.attr('cx',xAxis(xBall2))
.attr('cy',yAxis(yBall2))
.attr('r','#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>