我正在尝试用 Javascript 对弹簧上的质量进行 Runge-Kutta 实现,并使用 D3 对其进行可视化。目的是将其与前向欧拉进行比较并评论差异。我的有限元效果很好,绘图也很好,但龙格-库塔朝负方向发射,并且永远不会环绕。
这是一个带有 vis 和代码的 plunkr,但我也会添加 JS(仅适用于 ODE 求解器)。
// *** 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);
这有一些非常基本的概念问题。对于耦合系统,您必须同时评估所有操作。也就是说,在
y'(t)=f(y(t))
中,函数 y(t)
是向量值,f
将向量作为输入,将向量作为输出。欧拉方法可以总结为
k = f(y[i]);
y[i+1] = y[i] + h*k;
允许灵活评估
f
的组成部分。 RK4 遵循类似的方案,斜率 k0,...,k3
都是函数 f
在各个修改点的值。
Euler 步骤当然不是 RK4 步骤的一部分,也不应该与 ODE 的系统函数混淆。
所以你应该按照
的方向使用一些东西function EulerStep(x,v,h,odefnX,odefnV) {
var kx = odefnX(x,v);
var kv = odefnV(x,v);
return [ x+h*kx, v+h*kv ];
}
function RK4Step(x,v,h,odefnX,odefnV) {
var kx0 = odefnX(x,v);
var kv0 = odefnV(x,v);
var kx1 = odefnX(x+0.5*h*kx0,v+0.5*h*kv0);
var kv1 = odefnV(x+0.5*h*kx0,v+0.5*h*kv0);
var kx2 = odefnX(x+0.5*h*kx1,v+0.5*h*kv1);
var kv2 = odefnV(x+0.5*h*kx1,v+0.5*h*kv1);
var kx3 = odefnX(x+ h*kx2,v+ h*kv2);
var kv3 = odefnV(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, odefnX, odefnV) {
// 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, odefnX, odefnV);
}
return values;
}
// ----------------------------------
// Example usage
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;
}
RK4(0.0, 1.0 , 0.01, 100, odefuncX, odefuncV);
// *** Chart variables *** //
var margin = {top: 20, right: 20, bottom: 30, left: 50},
width = 500 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var x = d3.scale.linear()
.range([0, width]);
var y = d3.scale.linear()
.range([height, 0]);
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(y)
.orient("left");
var line = d3.svg.line()
.x(function(d) { return x(d.X); })
.y(function(d) { return y(d.V); });
var odeChart = d3.select("#chart-container").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
// *** Functions for ODE Solvers *** //
function odefunc(y) {
var x=y[0]; var v=y[1];
var k = 1; var m = 0.5; var g = 0;
return [v, (-k/m)*x + g];
}
function EulerStep(y,h,odefn) {
var k = odefn(y);
return [ y[0]+h*k[0], y[1]+h*k[1] ];
}
function RK4Step(y,h, odefn) {
var k0 = odefn(y);
var k1 = odefn([y[0]+0.5*h*k0[0],y[1]+0.5*h*k0[1]]);
var k2 = odefn([y[0]+0.5*h*k1[0],y[1]+0.5*h*k1[1]]);
var k3 = odefn([y[0]+ h*k2[0],y[1]+ h*k2[1]]);
return [ y[0]+h/6*(k0[0]+2*(k1[0]+k2[0])+k3[0]),
y[1]+h/6*(k0[1]+2*(k1[1]+k2[1])+k3[1]) ];
}
// FORWARD EULER
function forwardEuler (x, v, h, n, odefn)
{
// 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+1);
// Initial conditions
values[0] = [x, v];
for (i = 1; i <= n; ++i)
{
values[i] = EulerStep(values[i-1], h,odefn);
}
return values;
}
// 4TH ORDER RUNGE-KUTTA
function RK4 (x, v, h, n, odefn)
{
// Initialize an array to hold the values
var values = new Array(n+1);
// Initial conditions
values[0] = [x, v];
for (i = 1; i <= n; ++i)
{
values[i] = RK4Step(values[i-1], h, odefn);
}
return values;
}
// *** Setting up the data *** //
var rkValues = RK4(1, 0, 0.1, 100, odefunc);
var feValues = forwardEuler(1, 0, 0.025, 400, odefunc);
var rkData = rkValues.map(function(d) {
return {
X: +d[0],
V: +d[1]
};});
var feData = feValues.map(function(d) {
return {
X: +d[0],
V: +d[1]
};});
x.domain(d3.extent(feData, function(d) { return d.X; }));
y.domain(d3.extent(feData, function(d) { return d.V; }));
odeChart.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
odeChart.append("g")
.attr("class", "y axis")
.call(yAxis)
odeChart.append("path")
.datum(rkData)
.attr("class", "rkLine")
.attr("d", line);
odeChart.append("path")
.datum(feData)
.attr("class", "feLine")
.attr("d", line);
body {
font: 10px sans-serif;
}
#chart-container {
width: 600px;
margin: 0 auto;
}
.axis path,
.axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
.x.axis path {
display: none;
}
.rkLine {
fill: none;
stroke: steelblue;
stroke-width: 1.5px;
}
.feLine {
fill: none;
stroke: red;
stroke-width: 1.5px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.17/d3.min.js"></script>
<div id="chart-container"> <h1>Runge Kutta Order 4</h1 ></div>