D3.js 双摆怪行为

D3.js double pendulum odd behavior

我正在使用 D3.js 制作双摆模拟器。我正在使用 4 阶 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, 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>

你是对的,你没有正确计算 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, 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>