SOURCE

console 命令行工具 X clear

                    
>
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; // 定义 dy/dx = 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变为原来的2倍
            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');
            }

            // 绘制龙格 - 库塔法 k1 斜率
            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');
            }

            // 绘制龙格 - 库塔法 k2 斜率
            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');
            }

            // 绘制龙格 - 库塔法 k3 斜率
            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');
            }

            // 绘制龙格 - 库塔法 k4 斜率
            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>