console
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>欧拉法到龙格 - 库塔法动画演示</title>
<style>
.euler-rk-container {
font-family: Arial, sans-serif;
display: flex;
flex-direction: column;
align-items: center;
margin: 0;
}
.euler-rk-container .controls {
margin: 20px 0;
}
.euler-rk-container #canvas-container {
width: 90%;
max-width: 800px;
position: relative;
}
.euler-rk-container #canvas {
border: 1px solid #999;
width: 100%;
aspect-ratio: 4 / 3;
}
.euler-rk-container .legend {
position: absolute;
top: 20px;
right: 20px;
background-color: rgba(255, 255, 255, 0.8);
padding: 10px;
border: 1px solid #ccc;
}
.euler-rk-container .legend-item {
display: flex;
align-items: center;
margin-bottom: 5px;
}
.euler-rk-container .legend-color {
width: 20px;
height: 20px;
margin-right: 10px;
}
.euler-rk-container .formula {
background-color: #f5f5f5;
padding: 15px;
margin: 20px 0;
font-family: monospace;
width: 90%;
max-width: 800px;
text-align: left;
}
.euler-rk-container .stability {
background-color: #f5f5f5;
padding: 15px;
margin: 20px 0;
width: 90%;
max-width: 800px;
text-align: left;
}
</style>
</head>
<body>
<div class="euler-rk-container">
<div class="controls">
<label>步长 h:
<input type="range" id="hSlider" min="0.1" max="1" step="0.1" value="0.5">
<span id="hValue">0.5</span>
</label>
</div>
<div id="canvas-container">
<canvas id="canvas"></canvas>
<div class="legend">
<div class="legend-item">
<div class="legend-color" style="background-color: #e44;"></div>
<span>欧拉法</span>
</div>
<div class="legend-item">
<div class="legend-color" style="background-color: #2a2;"></div>
<span>四阶龙格 - 库塔法</span>
</div>
<div class="legend-item">
<div class="legend-color" style="background-color: #00f;"></div>
<span>解析解 y = e^x</span>
</div>
<div class="legend-item">
<div class="legend-color" style="background-color: #888;"></div>
<span>欧拉法斜率</span>
</div>
<div class="legend-item">
<div class="legend-color" style="background-color: #ff0;"></div>
<span>龙格 - 库塔法 k1</span>
</div>
<div class="legend-item">
<div class="legend-color" style="background-color: #0ff;"></div>
<span>龙格 - 库塔法 k2</span>
</div>
<div class="legend-item">
<div class="legend-color" style="background-color: #f0f;"></div>
<span>龙格 - 库塔法 k3</span>
</div>
<div class="legend-item">
<div class="legend-color" style="background-color: #0f0;"></div>
<span>龙格 - 库塔法 k4</span>
</div>
</div>
</div>
<div class="formula">
欧拉法: y(n + 1) = y(n) + h * f(x(n), y(n))<br>
四阶龙格 - 库塔法:<br>
k1 = h * f(x(n), y(n))<br>
k2 = h * f(x(n) + h/2, y(n) + k1/2)<br>
k3 = h * f(x(n) + h/2, y(n) + k2/2)<br>
k4 = h * f(x(n) + h, y(n) + k3)<br>
y(n + 1) = y(n) + (k1 + 2 * k2 + 2 * k3 + k4) / 6
</div>
<div class="stability">
<h3>稳定性说明</h3>
<p>
<strong>欧拉法</strong>: 欧拉法是一阶数值方法,局部截断误差为 O(h^2),整体截断误差为 O(h)。其稳定性条件较为苛刻,对于显式欧拉法,当步长 h 过大时,容易出现数值不稳定的情况,计算结果可能会随着计算步数的增加而迅速偏离真实解,甚至发散。例如,对于简单的线性常微分方程 y' = -ay(a > 0),显式欧拉法的稳定条件是 h < 2/a。
</p>
<p>
<strong>四阶龙格 - 库塔法</strong>: 四阶龙格 - 库塔法是高精度数值方法,局部截断误差为 O(h^5),整体截断误差为 O(h^4)。它通常比欧拉法具有更好的稳定性,对步长的限制相对宽松一些,在一定的步长范围内能够保持较好的数值稳定性,更准确地逼近真实解。不过,对于刚性问题,如果步长选择不当,也可能出现稳定性问题,但相比欧拉法,其稳定性能通常更优。
</p>
</div>
</div>
<script>
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
const hSlider = document.getElementById('hSlider');
const hValue = document.getElementById('hValue');
let scaleX, scaleY, offsetX, offsetY;
function f(x, y) {
return y;
}
function exactSolution(x) {
return Math.exp(x);
}
function eulerMethod(x0, y0, h, steps) {
let x = x0;
let y = y0;
const points = [];
const slopes = [];
for (let i = 0; i < steps; i++) {
points.push([x, y]);
const slope = f(x, y);
slopes.push(slope);
y = y + h * slope;
x = x + h;
}
return { points, slopes };
}
function rk4Method(x0, y0, h, steps) {
let x = x0;
let y = y0;
const points = [];
const k1Points = [];
const k2Points = [];
const k3Points = [];
const k4Points = [];
const k1Slopes = [];
const k2Slopes = [];
const k3Slopes = [];
const k4Slopes = [];
for (let i = 0; i < steps; i++) {
points.push([x, y]);
const k1 = h * f(x, y);
k1Points.push([x, y]);
k1Slopes.push(k1 / h);
const k2 = h * f(x + h / 2, y + k1 / 2);
k2Points.push([x + h / 2, y + k1 / 2]);
k2Slopes.push(k2 / h);
const k3 = h * f(x + h / 2, y + k2 / 2);
k3Points.push([x + h / 2, y + k2 / 2]);
k3Slopes.push(k3 / h);
const k4 = h * f(x + h, y + k3);
k4Points.push([x + h, y + k3]);
k4Slopes.push(k4 / h);
y = y + (k1 + 2 * k2 + 2 * k3 + k4) / 6;
x = x + h;
}
return { points, k1Points, k2Points, k3Points, k4Points, k1Slopes, k2Slopes, k3Slopes, k4Slopes };
}
function drawGrid() {
ctx.strokeStyle = '#ddd';
ctx.beginPath();
for (let x = 0; x <= canvas.width; x += scaleX) {
ctx.moveTo(x, 0);
ctx.lineTo(x, canvas.height);
}
for (let y = 0; y <= canvas.height; y += scaleY) {
ctx.moveTo(0, y);
ctx.lineTo(canvas.width, y);
}
ctx.stroke();
}
function drawCurve(points, color) {
ctx.beginPath();
ctx.strokeStyle = color;
ctx.lineWidth = 2;
for (let i = 0; i < points.length; i++) {
const [x, y] = points[i];
const px = x * scaleX + offsetX;
const py = offsetY - y * scaleY;
if (i === 0) {
ctx.moveTo(px, py);
} else {
ctx.lineTo(px, py);
}
}
ctx.stroke();
}
function drawSlope(x, y, slope, color) {
const dx = 0.2 * scaleX;
const dy = slope * dx * scaleY / scaleX;
const px = x * scaleX + offsetX;
const py = offsetY - y * scaleY;
ctx.beginPath();
ctx.strokeStyle = color;
ctx.lineWidth = 1;
ctx.moveTo(px - dx, py + dy);
ctx.lineTo(px + dx, py - dy);
ctx.stroke();
}
function resizeCanvas() {
canvas.width = canvas.offsetWidth;
canvas.height = canvas.offsetHeight;
scaleX = canvas.width / 5;
scaleY = canvas.height / 10;
offsetX = canvas.width * 0.1;
offsetY = canvas.height * 0.9;
}
function animate() {
resizeCanvas();
ctx.clearRect(0, 0, canvas.width, canvas.height);
drawGrid();
const h = parseFloat(hSlider.value);
hValue.textContent = h.toFixed(1);
const x0 = 0;
const y0 = 1;
const steps = Math.floor((8 - x0) / h);
const eulerResult = eulerMethod(x0, y0, h, steps);
const rk4Result = rk4Method(x0, y0, h, steps);
const exactPoints = [];
for (let x = x0; x <= 8; x += 0.01) {
exactPoints.push([x, exactSolution(x)]);
}
drawCurve(exactPoints, '#00f');
drawCurve(eulerResult.points, '#e44');
drawCurve(rk4Result.points, '#2a2');
for (let i = 0; i < eulerResult.points.length; i++) {
const [x, y] = eulerResult.points[i];
const slope = eulerResult.slopes[i];
drawSlope(x, y, slope, '#888');
}
for (let i = 0; i < rk4Result.k1Points.length; i++) {
const [x, y] = rk4Result.k1Points[i];
const slope = rk4Result.k1Slopes[i];
drawSlope(x, y, slope, '#ff0');
}
for (let i = 0; i < rk4Result.k2Points.length; i++) {
const [x, y] = rk4Result.k2Points[i];
const slope = rk4Result.k2Slopes[i];
drawSlope(x, y, slope, '#0ff');
}
for (let i = 0; i < rk4Result.k3Points.length; i++) {
const [x, y] = rk4Result.k3Points[i];
const slope = rk4Result.k3Slopes[i];
drawSlope(x, y, slope, '#f0f');
}
for (let i = 0; i < rk4Result.k4Points.length; i++) {
const [x, y] = rk4Result.k4Points[i];
const slope = rk4Result.k4Slopes[i];
drawSlope(x, y, slope, '#0f0');
}
requestAnimationFrame(animate);
}
window.addEventListener('resize', animate);
hSlider.addEventListener('input', animate);
animate();
</script>
</body>
</html>