<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>微分方程求解中的迭代思想</title>
  <script>
  MathJax = {
    tex: {
      packages: {'[+]': ['ams', 'bm', 'unicode', 'mhchem']},
      inlineMath: [['$', '$'], ['\\(', '\\)']],
      displayMath: [['$$', '$$'], ['\\[', '\\]']],
      processEscapes: true
    },
    chtml: {
      fontURL: 'https://cdn.jsdelivr.net/npm/mathjax@3/es5/output/chtml/fonts/woff-v2'
    }
  };
  </script>
  <script src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-chtml.js" id="MathJax-script" async></script>
  <script src="https://cdn.jsdelivr.net/npm/echarts@5.5.0/dist/echarts.min.js" defer></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/mathjs/12.4.1/math.min.js"></script>
  <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Noto+Serif+SC:wght@400;700&display=swap" rel="stylesheet">
  <link href="https://fonts.googleapis.com/icon?family=Material+Icons+Outlined" rel="stylesheet">

<style>
body {
    font-family: "Inter", "Noto Serif SC", serif, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
    line-height: 1.8;
    background-color: #f8f9fa; /* Lighter gray */
    color: #343a40; /* Darker text */
    padding: 15px;
    box-sizing: border-box;
}
.container {
    max-width: 1000px;
    margin: 20px auto;
    padding: 30px 40px;
    background-color: #ffffff;
    border-radius: 8px;
    box-shadow: 0 8px 25px rgba(0,0,0,0.1);
}
.material-icons-outlined {
    vertical-align: middle;
    font-size: 1.2em;
    margin-right: 0.35em;
    line-height: 1;
}
h1 {
    font-size: 2.3rem;
    color: #007bff; /* Primary blue */
    border-bottom: 2px solid #007bff;
    padding-bottom: 0.5em;
    margin-bottom: 1em;
    display: flex;
    align-items: center;
    font-family: "Noto Serif SC", serif; font-weight: 700;
}
h1 > .material-icons-outlined { font-size: 1.25em; margin-right: 0.4em; }

h2 {
    font-size: 1.75rem;
    color: #17a2b8; /* Info blue/teal */
    border-bottom: 1px solid #dee2e6; /* Lighter border */
    padding-bottom: 0.4em;
    margin-top: 2.5em;
    margin-bottom: 1.2em;
    display: flex;
    align-items: center;
    font-family: "Noto Serif SC", serif; font-weight: 700;
}
h2 > .material-icons-outlined { font-size: 1.15em; margin-right: 0.4em; }

h3 {
    font-size: 1.4rem;
    color: #28a745; /* Success green */
    margin-top: 2em;
    margin-bottom: 1em;
    font-family: "Inter", sans-serif; font-weight: 600;
}
h3 > .material-icons-outlined { font-size: 1.1em; margin-right: 0.4em; }

h4 {
    font-size: 1.15rem;
    color: #6c757d; /* Muted/secondary text */
    margin-top: 1.8em;
    margin-bottom: 0.8em;
    font-weight: 600;
}
p { margin-bottom: 1.2em; color: #495057; font-size: 1.05rem; }
ul { padding-left: 25px; margin-bottom: 1.2em; }
li { margin-bottom: 0.6em; color: #495057; font-size: 1.05rem; }
strong, .strong-emphasis { font-weight: 600; color: #0056b3; } /* Darker blue for emphasis */
.text-highlight { background-color: #e9ecef; padding: 0.1em 0.3em; border-radius: 0.2rem; font-family: "Fira Code", monospace; color: #c82333;} /* For code/math terms */

.math-formula {
    font-size: 1.1em;
    padding: 15px 20px;
    background-color: #e9f5ff; /* Very light blue */
    border-radius: 6px;
    text-align: left;
    margin: 1em 0 1.5em 0;
    overflow-x: auto;
    border: 1px solid #bce0ff; /* Light blue border */
}
.interactive-demo {
    margin-top: 2em;
    padding: 20px;
    background-color: #fdfdfe;
    border: 1px solid #e0e5ec;
    border-radius: 8px;
    box-shadow: 0 4px 12px rgba(0,0,0,0.05);
}
.interactive-demo h4 { margin-top: 0; color: #fd7e14; /* Orange for demo titles */ } /* Orange for demo titles */
.input-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: 18px; margin-bottom: 1.2em; }
.input-group { display: flex; flex-direction: column; }
.input-group label { margin-bottom: .4em; font-weight: 500; color: #343a40; font-size:0.9rem; }
.input-group input[type="text"], .input-group input[type="number"], .input-group select {
    padding: .55em .75em; border-radius: .25rem; border: 1px solid #ced4da; font-size: 0.95rem;
    font-family:"Inter", sans-serif; transition: border-color 0.2s ease, box-shadow 0.2s ease;
}
.input-group input:focus, .input-group select:focus { border-color:#007bff; box-shadow: 0 0 0 0.2rem rgba(0,123,255,.25); outline:none;}
button.action-button {
    background-color: #28a745; color: white; padding: .6em 1.3em; border: none; border-radius: .25rem;
    font-size: 1rem; cursor: pointer; transition: background-color 0.2s ease, transform 0.1s ease;
    display: inline-block; margin-top: 0.5em; box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
button.action-button:hover { background-color: #218838; transform: translateY(-1px); }
.plot-container { width: 100%; height: 400px; margin-top: 1.5em; border:1px solid #dee2e6; border-radius:.25rem; padding:10px; box-sizing:border-box;}
.results-text { margin-top: 1em; font-family: "Fira Code", monospace; font-size: 0.9rem; white-space: pre-wrap; background-color: #f8f9fa; padding: 10px; border-radius: 4px; border: 1px solid #e9ecef; max-height: 200px; overflow-y: auto;}
.error-message { color: #dc3545; font-weight: bold; margin-top: 10px; }
.status-message { color: #007bff; font-weight: 500; margin: 0.5em 0; min-height: 1.3em; }
hr.section-divider { border: 0; height: 1px; background-image: linear-gradient(to right, rgba(0, 0, 0, 0), rgba(0, 0, 0, 0.15), rgba(0, 0, 0, 0)); margin: 3em 0; }

</style>
</head>
<body>
    <div class="container">
        <h1><span class="material-icons-outlined">loop</span>微分方程求解中的迭代思想与方法</h1>
        <p>在求解微分方程时,“迭代法”这一术语的含义根据我们是寻求连续解还是离散解而有所不同。下面将分别探讨这两种情况,并提供相应的演示。</p>

        <hr class="section-divider">

        <h2><span class="material-icons-outlined">waves</span>1. 针对连续解的迭代思想</h2>
        <p>严格来说,直接求解微分方程(得到一个函数作为解)的方法通常不直接称为“迭代法”。但某些方法在概念上确实采用了迭代或逐步逼近的思想,直接作用于函数空间。</p>

        <h3><span class="material-icons-outlined">functions</span>Picard 迭代法 (Picard Iteration)</h3>
        <p>Picard 迭代法是常微分方程初值问题解的<strong class="strong-emphasis">存在唯一性证明</strong>(Picard-Lindelöf定理)中的核心。它从一个初始函数(通常是基于初值的常数函数)开始,通过反复积分微分方程的右端项来构造一个函数序列 $u_0(t), u_1(t), u_2(t), \dots$。</p>
        <p>对于初值问题 $y'(t) = f(t, y(t))$, $y(t_0) = y_0$,迭代格式为:</p>
        <div class="math-formula">
            $u_0(t) = y_0$ <br>
            $u_{k+1}(t) = y_0 + \int_{t_0}^{t} f(s, u_k(s)) ds$
        </div>
        <p>在特定条件下(例如 $f$ 满足Lipschitz条件),这个函数序列会<strong class="strong-emphasis">收敛到微分方程的精确解</strong>。这可以被视为在函数空间中的一种迭代过程。</p>
        
        <div class="interactive-demo">
            <h4><span class="material-icons-outlined">play_circle_outline</span>Picard 迭代法演示</h4>
            <p>求解 $y' = y$, $y(0) = 1$。真实解为 $y(t) = e^t$。</p>
            <div class="input-grid">
                <div class="input-group">
                    <label for="picardIterations">迭代次数:</label>
                    <input type="number" id="picardIterations" value="3" min="0" max="10">
                </div>
                <div class="input-group">
                    <label for="picardTMax">绘制范围 $t_{max}$:</label>
                    <input type="number" id="picardTMax" value="2" min="0.1" max="5" step="0.1">
                </div>
            </div>
            <button id="runPicard" class="action-button">运行 Picard 迭代</button>
            <div class="status-message" id="picardStatus"></div>
            <div class="plot-container" id="picardPlot"></div>
            <div class="results-text" id="picardResults">迭代结果函数将显示在此...</div>
        </div>

        <h3><span class="material-icons-outlined">calculate</span>函数空间中的 Newton-Raphson 方法</h3>
        <p>对于非线性偏微分方程或边值问题,有时可以将问题抽象为在某个函数空间中寻找算子方程 $F(u)=0$ 的根。Newton-Raphson 方法可以推广到函数空间(如 Newton-Kantorovich 方法),通过<strong class="strong-emphasis">线性化算子</strong>并迭代求解线性问题来逼近非线性问题的解。每一步迭代都会得到一个新的近似函数。</p>
        <p>这些方法直接作用于函数,试图逐步逼近真实的函数解。</p>
        
        <hr class="section-divider">

        <h2><span class="material-icons-outlined">grid_on</span>2. 针对离散解的迭代法</h2>
        <p>这才是《微分方程数值解法》课程中通常重点讨论“迭代法”的语境。当使用有限差分法、有限元法等将微分方程离散化后,会得到一个大型的代数方程组(通常是线性的,有时非线性)。迭代法是求解这类代数方程组的有效手段,尤其当方程组<strong class="strong-emphasis">规模很大且系数矩阵稀疏</strong>时。</p>

        <h3><span class="material-icons-outlined">linear_scale</span>基本迭代法 (用于线性方程组 $Ax=b$)</h3>
        <p>这些方法都基于对矩阵 $A$ 进行某种分裂 $A = M - N$,然后构造迭代格式 $Mx^{(k+1)} = Nx^{(k)} + b$。</p>
        <ul>
            <li><strong class="text-highlight">雅可比迭代法 (Jacobi Iteration)</strong></li>
            <li><strong class="text-highlight">高斯-赛德尔迭代法 (Gauss-Seidel Iteration)</strong></li>
            <li><strong class="text-highlight">逐次超松弛迭代法 (Successive Over-Relaxation, SOR)</strong></li>
        </ul>

        <div class="interactive-demo">
            <h4><span class="material-icons-outlined">play_circle_outline</span>Jacobi 迭代法演示</h4>
            <p>求解线性方程组 $Ax=b$。示例:</p>
            <div class="math-formula">
            $\begin{pmatrix} 4 & -1 & 0 \\ -1 & 4 & -1 \\ 0 & -1 & 3 \end{pmatrix} \begin{pmatrix} x_1 \\ x_2 \\ x_3 \end{pmatrix} = \begin{pmatrix} 2 \\ 6 \\ 5 \end{pmatrix}$
            </div>
            <p>(精确解: $x_1=1, x_2=2, x_3=7/3 \approx 2.333$)</p>
            <div class="input-grid">
                <div class="input-group">
                    <label for="jacobiMatrixA">矩阵 A (JSON格式, e.g., [[4,-1,0],[-1,4,-1],[0,-1,3]]):</label>
                    <input type="text" id="jacobiMatrixA" value="[[4,-1,0],[-1,4,-1],[0,-1,3]]">
                </div>
                <div class="input-group">
                    <label for="jacobiVectorB">向量 b (JSON格式, e.g., [2,6,5]):</label>
                    <input type="text" id="jacobiVectorB" value="[2,6,5]">
                </div>
                <div class="input-group">
                    <label for="jacobiMaxIter">最大迭代次数:</label>
                    <input type="number" id="jacobiMaxIter" value="10" min="1" max="100">
                </div>
                 <div class="input-group">
                    <label for="jacobiTolerance">容差 (用于收敛判断):</label>
                    <input type="number" id="jacobiTolerance" value="0.0001" min="1e-10" max="1e-1" step="1e-5">
                </div>
            </div>
            <button id="runJacobi" class="action-button">运行 Jacobi 迭代</button>
            <div class="status-message" id="jacobiStatus"></div>
            <div class="results-text" id="jacobiResults">迭代过程及结果将显示在此...</div>
             <div class="error-message" id="jacobiError"></div>
        </div>

        <h3><span class="material-icons-outlined">model_training</span>更高级的迭代法</h3>
        <p>这些方法通常用于大型稀疏线性系统,特别是对称正定系统。</p>
        <ul>
            <li><strong class="text-highlight">共轭梯度法 (Conjugate Gradient Method, CG)</strong></li>
            <li><strong class="text-highlight">预处理共轭梯度法 (Preconditioned Conjugate Gradient Method, PCG)</strong></li>
        </ul>

        <h3><span class="material-icons-outlined">splitscreen</span>特定类型问题的迭代思想方法</h3>
        <ul>
            <li><strong class="strong-emphasis">交替方向隐式迭代法 (Alternating Direction Implicit, ADI)</strong>:ADI 本身是针对多维抛物或椭圆问题的差分格式构造方法。但它的每一步也涉及到求解一系列可以迭代求解的三对角系统,并且整个过程可以看作是一种迭代逼近多维问题解的方式。</li>
        </ul>
        
        <hr class="section-divider">

        <h2><span class="material-icons-outlined">summarize</span>总结</h2>
        <p>直接求解<strong class="strong-emphasis">连续问题</strong>时,像 Picard 迭代法和函数空间中的 Newton 法体现了迭代思想,它们作用于函数本身。</p>
        <p>当我们谈论求解<strong class="strong-emphasis">离散解</strong>(即求解离散化后产生的代数方程组)时,迭代法指的是像雅可比、高斯-赛德尔、SOR、共轭梯度等用于求解线性(或非线性)代数系统的方法。这些方法在教材中主要是在讨论椭圆型方程的有限差分法(以及抛物型或双曲型方程的隐式格式)离散化后得到的代数方程组的求解时出现。</p>

    </div>

<script>
document.addEventListener('DOMContentLoaded', () => {
    // --- Picard Iteration Demo ---
    const picardIterationsInput = document.getElementById('picardIterations');
    const picardTMaxInput = document.getElementById('picardTMax');
    const runPicardButton = document.getElementById('runPicard');
    const picardPlotDiv = document.getElementById('picardPlot');
    const picardResultsDiv = document.getElementById('picardResults');
    const picardStatusDiv = document.getElementById('picardStatus');
    let picardChart = echarts.init(picardPlotDiv);

    // Factorial function for Picard
    function factorial(n) {
        if (n === 0 || n === 1) return 1;
        let result = 1;
        for (let i = 2; i <= n; i++) result *= i;
        return result;
    }

    // Function to generate Picard iterates for y' = y, y(0) = 1
    // u_k(t) = 1 + t + t^2/2! + ... + t^k/k!
    function getPicardIterate(k, t) {
        let sum = 0;
        for (let i = 0; i <= k; i++) {
            sum += Math.pow(t, i) / factorial(i);
        }
        return sum;
    }
    
    function getPicardIterateString(k) {
        if (k < 0) return "N/A";
        let terms = [];
        for (let i = 0; i <= k; i++) {
            if (i === 0) terms.push("1");
            else if (i === 1) terms.push("t");
            else terms.push(`t^${i}/${i}!`);
        }
        return `u_${k}(t) = ${terms.join(" + ")}`;
    }


    runPicardButton.addEventListener('click', () => {
        picardStatusDiv.textContent = "正在生成Picard迭代...";
        const numIterations = parseInt(picardIterationsInput.value);
        const tMax = parseFloat(picardTMaxInput.value);
        const points = 100;
        const step = tMax / points;

        let seriesData = [];
        let legendData = [];
        let resultsText = "";

        // True solution: e^t
        let trueSolutionData = [];
        for (let i = 0; i <= points; i++) {
            const t = i * step;
            trueSolutionData.push([t, Math.exp(t)]);
        }
        seriesData.push({
            name: 'e^t (真实解)',
            type: 'line',
            smooth: true,
            data: trueSolutionData,
            lineStyle: { width: 2, color: '#28a745' }
        });
        legendData.push('e^t (真实解)');

        // Picard iterates
        for (let k = 0; k <= numIterations; k++) {
            let iterateData = [];
            for (let i = 0; i <= points; i++) {
                const t = i * step;
                iterateData.push([t, getPicardIterate(k, t)]);
            }
            const seriesName = `u_${k}(t)`;
            seriesData.push({
                name: seriesName,
                type: 'line',
                smooth: true,
                data: iterateData,
                lineStyle: { opacity: 0.7 }
            });
            legendData.push(seriesName);
            resultsText += getPicardIterateString(k) + "\n";
        }
        
        picardResultsDiv.textContent = resultsText;

        const option = {
            tooltip: { trigger: 'axis' },
            legend: { data: legendData, type: 'scroll', top: 10 },
            grid: { left: '3%', right: '4%', bottom: '3%', containLabel: true },
            xAxis: { type: 'value', name: 't', min: 0, max: tMax },
            yAxis: { type: 'value', name: 'u(t)' },
            series: seriesData
        };
        picardChart.setOption(option, true);
        picardStatusDiv.textContent = "Picard迭代已生成。";
        MathJax.typesetPromise(); // Retypeset if new MathJax content is added (though not in this specific demo's text output)
    });
    
    // Initial Picard plot
    runPicardButton.click();


    // --- Jacobi Iteration Demo ---
    const jacobiMatrixAInput = document.getElementById('jacobiMatrixA');
    const jacobiVectorBInput = document.getElementById('jacobiVectorB');
    const jacobiMaxIterInput = document.getElementById('jacobiMaxIter');
    const jacobiToleranceInput = document.getElementById('jacobiTolerance');
    const runJacobiButton = document.getElementById('runJacobi');
    const jacobiResultsDiv = document.getElementById('jacobiResults');
    const jacobiErrorDiv = document.getElementById('jacobiError');
    const jacobiStatusDiv = document.getElementById('jacobiStatus');

    runJacobiButton.addEventListener('click', () => {
        jacobiErrorDiv.textContent = "";
        jacobiStatusDiv.textContent = "正在运行Jacobi迭代...";
        jacobiResultsDiv.textContent = "";

        try {
            const A_str = jacobiMatrixAInput.value;
            const b_str = jacobiVectorBInput.value;
            const maxIter = parseInt(jacobiMaxIterInput.value);
            const tol = parseFloat(jacobiToleranceInput.value);

            const A = JSON.parse(A_str);
            const b = JSON.parse(b_str);

            if (!Array.isArray(A) || !A.every(row => Array.isArray(row)) || !Array.isArray(b)) {
                throw new Error("矩阵A或向量b的格式不正确。请输入有效的JSON数组。");
            }
            const n = A.length;
            if (n === 0 || A.some(row => row.length !== n) || b.length !== n) {
                 throw new Error("矩阵A必须是方阵,且A和b的维度必须匹配。");
            }
            // Check for zero diagonal elements
            for(let i = 0; i < n; i++) {
                if (Math.abs(A[i][i]) < 1e-9) { // Using a small tolerance for zero
                    throw new Error(`Jacobi迭代失败:矩阵A的对角元素 A[${i}][${i}] 过小或为零。方法可能不收敛或导致除零错误。`);
                }
            }


            let x = Array(n).fill(0); // Initial guess
            let x_new = Array(n).fill(0);
            let resultsText = "初始猜测 x^(0): [" + x.map(val => val.toFixed(5)).join(", ") + "]\n\n";

            let converged = false;
            for (let k = 0; k < maxIter; k++) {
                for (let i = 0; i < n; i++) {
                    let sigma = 0;
                    for (let j = 0; j < n; j++) {
                        if (i !== j) {
                            sigma += A[i][j] * x[j];
                        }
                    }
                    x_new[i] = (b[i] - sigma) / A[i][i];
                }

                resultsText += `迭代 ${k + 1}:\nx_new: [${x_new.map(val => val.toFixed(5)).join(", ")}]\n`;
                
                let norm_diff = 0;
                for (let i = 0; i < n; i++) {
                    norm_diff += Math.pow(x_new[i] - x[i], 2);
                }
                norm_diff = Math.sqrt(norm_diff);
                resultsText += `||x_new - x||_2 = ${norm_diff.toExponential(5)}\n\n`;

                x = [...x_new]; // Update x for next iteration

                if (norm_diff < tol) {
                    resultsText += `在 ${k + 1} 次迭代后收敛。\n`;
                    converged = true;
                    break;
                }
            }
            
            if (!converged) {
                 resultsText += `达到最大迭代次数 ${maxIter},可能未完全收敛。\n`;
            }
            resultsText += "\n最终解 x: [" + x.map(val => val.toFixed(5)).join(", ") + "]";
            jacobiResultsDiv.textContent = resultsText;
            jacobiStatusDiv.textContent = "Jacobi迭代完成。";

        } catch (e) {
            jacobiErrorDiv.textContent = "错误: " + e.message;
            jacobiStatusDiv.textContent = "迭代失败。";
            console.error(e);
        }
    });
    
    // Initial typeset for any static MathJax on page
    MathJax.typesetPromise();
});
</script>
</body>
</html>

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

This Pen doesn't use any external JavaScript resources.