Runge Kutta problems in JS

2019-05-21 15:03发布

问题:

I'm attempting a Runge-Kutta implementation for a mass on a spring in Javascript and visualizing it with D3. The purpose is to compare it to Forward Euler and comment on the differences. My FE works fine and plots fine, but the Runge-Kutta is shooting off in a negative direction and never wrapping around.

Here's a plunkr with the vis and the code, but I'll add the JS (only for the ODE solvers) too.

// *** Functions for ODE Solvers *** //

function FEx (x, v, h)
{
    return x + h*v;
}

function FEv (x, v, h)
{
    var k = 1; var m = 0.5; var g = 0;

    return v + h*( (-k/m)*x + g );
}

function RKx (x, v, h)
{
    var k1 = FEx(x, v, h);
    var k2 = FEx(x+h/2*k1, v+h/2, h);
    var k3 = FEx(x+h/2*k2, v+h/2, h);
    var k4 = FEx(x+h*k3, v+h, h);

    return x + h/6*(k1 + 2*k2 + 2*k3 + k4);
}

function RKy (x, v, h)
{
    var k1 = FEv(x, v, h);
    var k2 = FEv(x+h/2, v+h/2*k1, h);
    var k3 = FEv(x+h/2, v+h/2*k2, h);
    var k4 = FEv(x+h, v+h*k3, h);

    return v + h/6*(k1 + 2*k2 + 2*k3 + k4);
}

// FORWARD EULER
function forewardEuler (x, v, h, n)
{
    // Initialize an array to hold the values
    // JS doesn't really support multi-dimensional arrays
    // so this is a "jagged" nested array
    var values = new Array(n);
    for(i = 0; i < values.length; i++)
        values[i] = new Array(2);

    // Initial conditions
    values[0] = [x, v];

    for (i = 1; i < n; ++i)
    {
        values[i][0] = FEx(values[i-1][0], values[i-1][1], h);
        values[i][1] = FEv(values[i-1][0], values[i-1][1], h);
    }

    return values;
}

// 4TH ORDER RUNGE-KUTTA 
function RK4 (x, v, h, n)
{
    // Initialize an array to hold the values
    var values = new Array(n);
    for(i = 0; i < values.length; i++)
        values[i] = new Array(2);

    // Initial conditions
    values[0] = [x, v];

    for (i = 1; i < n; ++i)
    {
        values[i][0] = RKx(values[i-1][0], values[i-1][1], h);
        values[i][1] = RKy(values[i-1][0], values[i-1][1], h);
    }

    return values;
}

// *** Setting up the data *** //

var rkValues = RK4(1, 0, 0.1, 100);
var feValues = forewardEuler(1, 0, 0.1, 100);

回答1:

This has some very basic conceptual problems. For a coupled system you have to evaluate all operations simultaneously. That is, in y'(t)=f(y(t)) the function y(t) is vector valued, f has vectors as inputs and vectors as outputs. The Euler method can then be summarized as

k = f(y[i]);
y[i+1] = y[i] + h*k;

allowing flexibility in how the components of f are evaluated. RK4 follows then a similar scheme, the slopes k0,...,k3 are all values of the function f at various modified points.

The Euler step certainly is not a part of the RK4 step and should also not be mixed up with the system function of the ODE.

So you should use something in the direction of

function odefuncX(x,v) {return v;}

function odefuncV(x,v) { 
    var k = 1; var m = 0.5; var g = 0;
    return (-k/m)*x + g;
}

function EulerStep(x,v,h) {
    var kx = odefuncX(x,v);
    var kv = odefuncV(x,v);
    return [ x+h*kx, v+h*kv ];
}

function RK4Step(x,v,h) {
    var kx0 = odefuncX(x,v);
    var kv0 = odefuncV(x,v);

    var kx1 = odefuncX(x+0.5*h*kx0,v+0.5*h*kv0);
    var kv1 = odefuncV(x+0.5*h*kx0,v+0.5*h*kv0);

    var kx2 = odefuncX(x+0.5*h*kx1,v+0.5*h*kv1);
    var kv2 = odefuncV(x+0.5*h*kx1,v+0.5*h*kv1);

    var kx3 = odefuncX(x+    h*kx2,v+    h*kv2);
    var kv3 = odefuncV(x+    h*kx2,v+    h*kv2);

    return [ x+h/6*(kx0+2*(kx1+kx2)+kx3),
             v+h/6*(kv0+2*(kv1+kv2)+kv3) ];
}

// 4TH ORDER RUNGE-KUTTA 
function RK4 (x, v, h, n) {
    // Initialize an array to hold the values
    var values = new Array(n);

    // Initial conditions
    values[0] = [x, v];
    for (i = 1; i < n; ++i) {
        values[i] = RK4Step(values[i-1][0], values[i-1][1], h); 
    }
    return values;
}

See forked Plunker