方程:一维定常黏性 Burgers
The equation: steady one-dimensional viscous Burgers
本节实验:读图 1 的下图,扩散通量比对流通量小多少?再读图 2,同一个 在不同 下需要什么样的源项?
Experiment: In the lower panel of Figure 1, how much smaller is the diffusive flux than the convective one? Then read Figure 2: what source does the same need at different ?
从守恒律到定常方程
From a conservation law to the steady equation
黏性 Burgers 方程在其他一维页面的 Burgers 方程上增加了分子扩散。量 沿 以自身的值为速度被输运,同时按系数 扩散;写成守恒形式,总通量有两部分——对流通量 与扩散通量 ——区间内还有源项 :
The viscous Burgers equation adds molecular diffusion to the Burgers equation of the other one-dimensional pages. A quantity is carried along with its own value as the speed and spreads by diffusion with the coefficient . In conservation form the total flux has two parts, the convective flux and the diffusive flux , and a source acts inside the interval:
本页只研究固定正黏性下的定常解。令 ,并记总通量为 ,得到要离散的方程;因为含二阶导数,两端各需要一个给定值(无黏 Burgers 页面只给入口条件,不需要出口条件):
This page studies steady solutions at a fixed positive viscosity. Setting and writing for the total flux gives the equation to be discretised. Because it contains a second derivative it needs a prescribed value at each end (the inviscid Burgers pages, with an inlet condition only, needed none at the outlet):
使解光滑:陡变被扩散成薄层,层宽与 除以速度(或速度差)同量级。构造解本身没有层; 时这个问题的边界层只能出现在出口 (默认设计下层宽 ),入口 处的层属于伴随 (图 4,第 11、16 节与附录 A)。在本页的格式里,网格 Péclet 数 远小于 1 时离散结果才进入渐近区(第 15 节,数值观察);粗网格上的大误差主要来自两个不重构的端单元,而不是这个层(第 15、16 节)。激波属于单元中心的一维 Burgers 页的附录 B, 与 的极限顺序见它的附录 D(D.4);本页的 始终固定。
With the solution is smooth: a steep change is spread into a thin layer whose width is of the order of divided by the speed (or the speed jump). The manufactured solution has no layer. With a boundary layer of this problem can only sit at the outlet end (width at the default design), while the layer at the inlet end belongs to the adjoint (Figure 4; Sections 11 and 16 and Appendix A). In this page's scheme the discrete results reach their asymptotic range only when the cell Péclet number is well below 1 (Section 15, a numerical observation); the large errors on coarse meshes come mainly from the two unreconstructed end cells, not from the layer (Sections 15 and 16). Shocks belong to Appendix B of the cell-centred 1-D Burgers page and the orders of the limits , to its Appendix D (D.4); here stays fixed.
一个构造解:先给解,再算源项
A manufactured solution: prescribe the solution, derive the source
要把每个导数都和精确答案比较,本页先规定解,再由解构造源项。取 为在平均值 1 上叠加的正弦波,振幅为 ,相位为 ; 对每个设计都是精确解。把它代入方程,就得到源项和两端的给定值:
To compare every derivative with an exact answer, the page prescribes the solution first and builds the source from it. Take to be a sine wave on the mean 1, with amplitude and phase ; it is the exact solution for every design. Inserting it into the equation defines the source and the two end values:
解不依赖 ,源项则依赖:其中 这一项就是源项必须补上的扩散(图 2)。 以 1 为周期,所以两端的给定值相等;默认值下 。
The solution does not depend on , but the source does: the term is the diffusion that the source has to supply (Figure 2). Because has period 1 the two end values are equal; at the defaults .
设计、目标和它们的精确导数
The design, the objective and their exact derivatives
设计输入是 ,共 3 个分量。 与 同时改变源项和两端的给定值; 改变物理通量本身,同时也改变源项。目标是解的一个加权积分,权重 :
The design input is with 3 components. Changing or changes the source and the two end values together; changing changes the physical flux itself and, through , the source. The objective is a weighted integral of the solution with the weight :
对构造解可以直接积分,目标和它的三个导数都有闭式( 不出现,因为 不依赖它):
For the manufactured solution the integral is exact, so the objective and its three derivatives have closed forms ( does not appear because does not depend on it):
默认设计下 ,梯度 。离散梯度随网格加密趋向的就是这些值(第 15 节的数据显示这一点;这是数值观察,不是证明)。有限网格上的离散目标 是另一个函数(第 7 节),切线和伴随算出的是它的梯度,不是这个精确梯度;两者的比较见第 10 至 12 节、第 15 节和第 16 节。
At the defaults and the gradient is . These are the values that the discrete gradients approach as the mesh is refined, as the data of Section 15 show (a numerical observation, not a proof). On a finite mesh the discrete objective is a different function (Section 7), and what the tangent and the adjoint compute is its gradient, not this exact one; the two are compared in Sections 10 to 12, 15 and 16.
图 1 的下图回答了实验的第一问:对流通量约 0.3–0.7,扩散通量最大只有 0.0126,不超过对流通量的 4%。黏性几乎不改变解的通量,却决定了第 11、16 节里伴随场的结构;先记住这一点。
The lower panel of Figure 1 answers the first question of the experiment: the convective flux is about 0.3–0.7 and the diffusive flux is at most 0.0126, no more than 4% of it. Viscosity hardly changes the flux of the solution, yet it decides the structure of the adjoint field in Sections 11 and 16; keep that in mind.
自己动手:改变 、、,看精确解、两部分通量和源项怎样变,并核对闭式的目标与梯度。
Try it yourself: change , and , see how the exact solution, the two parts of the flux and the source change, and check the closed-form objective and gradient.
先看一眼后面要反复用到的结论:解几乎感觉不到黏性(图 1),但它的灵敏度感觉得到。目标的伴随场 (第 11、12、16 节与附录 A)在 处有一个宽度约 的边界层, 越小越陡;离散伴随只有在网格分辨这个层时才能重现它的形状(梯度需要的更少,见第 12 节)。
A look ahead at a result used again and again: the solution hardly feels the viscosity (Figure 1), but its sensitivity does. The adjoint field of the objective (Sections 11, 12, 16 and Appendix A) has a boundary layer of width about at , steeper for smaller ; the discrete adjoint reproduces the shape of such a layer only if the mesh resolves it (the gradient needs less, Section 12).
这个方程用来做什么
What this equation is for
黏性 Burgers 方程保留了流体动量方程的非线性对流项 ,又带上分子黏性项 ,是最简单的“对流加扩散”非线性方程。本课程把它放在无黏 Burgers 与一维 Navier–Stokes 之间,原因有五:
The viscous Burgers equation keeps the nonlinear convection term of the momentum equation and adds the molecular viscosity term : it is the simplest nonlinear convection–diffusion equation. The course puts it between the inviscid Burgers pages and the 1-D Navier–Stokes pages, for five reasons:
- 小。默认网格只有 8 个未知量,每个数组和矩阵都能放在页面上,语句播放器也读得过来。Small. The default mesh has only 8 unknowns, so every array and matrix fits on the page and the statement players stay readable.
- 非线性。数值通量对迹的偏导 随状态变化,所以线性化(Jacobian)依赖于它取在哪个状态;切线与伴随都取在收敛的原始解上。Nonlinear. The derivative of the numerical flux with respect to a trace, , changes with the state, so the linearisation (the Jacobian) depends on where it is taken; the tangent and the adjoint are both evaluated at the converged primal solution.
- 有精确答案可比。构造解给出每个设计下的精确解、精确目标和精确梯度,所以每个导数、每个收敛阶都能和真值比较。Exact answers to compare with. The manufactured solution gives the exact solution, objective and gradient for every design, so every derivative and every convergence rate can be compared with the truth.
- 物理通量依赖导数 。面通量要用相邻单元的差商近似 ,边界面要用半个单元的距离——这正是黏性 Navier–Stokes 页面每个面上要做的事,这里只有一个变量。The physical flux depends on the derivative . A face flux needs a difference quotient of neighbouring cells to approximate and a boundary face needs half a cell of distance: exactly what each face of the viscous Navier–Stokes pages does, here with one variable.
- 设计进入物理通量和源项。 改变源项和边界值, 改变通量本身;第 12 节逐项分清这三条路径。The design enters the physical flux and the source. change the source and the boundary values, changes the flux itself; Section 12 separates the three paths one by one.
本页的数值、图和表来自本页的代码(模型在构建页面时与独立的 Python 实现逐项对照,第 15 节的大网格数据来自 Python 程序)。数值检查是验证证据,不是数学证明;本页不声称非线性方程或格式的存在性、唯一性或收敛定理(本页给出的证明列在附录 C 的 C.4)。
The numbers, figures and tables of this page come from the page's own code (the model is held to an independent Python implementation when the page is built, and the large-grid data of Section 15 come from the Python program). Numerical checks are verification evidence, not mathematical proofs; the page claims no existence, uniqueness or convergence theorem for the nonlinear equation or for the scheme (the proofs it does give are listed in C.4 of Appendix C).
网格:未知量与数组
The mesh: unknowns and arrays
本节实验:数一数, 个单元有几个面?每个内部面和每个边界面各读哪些数据?先答,再对照图 5。
Experiment: Count the faces of cells: which data does each interior face and each boundary face read? Answer first, then compare with Figure 5.
第 1 节给出了方程。本节把区间 分成 个等宽的单元,宽度 ,并说明页面存哪些数据。
Section 1 gave the equation. This section divides into equal control volumes (cells) of width and says what the page stores.
单元、面和未知量
Cells, faces and unknowns
单元 的中心是 ,它夹在面 与面 之间;面 在 。未知量是每个单元上解的平均值:
Cell has its centre at and lies between face and face ; face is at . The unknown is the average of the solution over each cell:
对构造解,精确单元平均有闭式:正弦在一个单元上的平均是 倍的中心值,所以
For the manufactured solution the exact cell average has a closed form: the average of the sine over a cell is times its value at the centre, so
状态的误差始终拿离散解和这个精确平均比较,而不是和中心点的采样值比较(采样值只是 Newton 迭代的初值)。
The errors of the state are always measured between the discrete solution and this exact average, never against point samples at the centres (the samples are only the initial guess of the Newton iteration).
先看网格是怎样一步一步搭起来的:
First see how the mesh is built, step by step:
选一个面,看它读哪些单元;再换一种重构,数一数未知量的个数:
Select a face and see which cells it reads; then change the reconstruction and count the unknowns:
记号
Notation
默认设置:,无限制器线性重构,,即页首控件的初始值;图和文字没有另注时都取默认设置。
Default settings: , unlimited linear reconstruction and , the initial values of the controls at the top; the figures and the text use them unless another setting is stated.
按伴随优化文献的通常写法,设计变量记作 ,分量 、、。残差是 ,状态 Jacobian 是 ,设计 Jacobian 是 ,离散目标是 ,伴随变量是 。
As is usual in the adjoint-optimization literature, the design variables are written , with components , , . The residual is , the state Jacobian is , the design Jacobian is , the discrete objective is and the adjoint variable is .
切线(前向,tangent)与伴随(反向,adjoint)一律用自动微分的记号:程序变量 的切线写 ,伴随写 ,伴随累加写 ,伴随语句按逆序执行; 与 一一对应 Tapenade 等工具里的变量 x_d 与 x_b,本页的伪代码就用这些名字。变分符号 只出现在附录 A 的连续推导里(连续函数的变分);Newton 与 GS 的迭代增量写 。
The tangent (forward) and the adjoint (reverse) always use the notation of automatic differentiation: the forward derivative of a program variable is , its adjoint is , an adjoint accumulation is written and the statements run in reverse order. They correspond one to one to the variables x_d and x_b of AD tools such as Tapenade, and the pseudocode of this page uses these names. The variation symbol appears only in the continuous derivation of Appendix A (variations of continuous functions); the increments of the Newton and GS iterations are written .
有两个与黏性有关的量,不要混淆:物理黏性 (设计分量 ,进入物理通量 );Rusanov 数值通量里的耗散(第 3 节),其波速 是固定常数,一阶格式里它等效于随网格变化的人工黏性 ,不是设计变量。
Two quantities that both look like a viscosity must not be confused: the physical viscosity (the design component , which enters the physical flux ) and the dissipation of the Rusanov numerical flux (Section 3), whose speed is a fixed constant and which acts at first order as an artificial viscosity that changes with the mesh; it is not a design variable.
本页的主要符号
The main symbols of this page
| 符号Symbol | 含义Meaning | 大小Size | 定义处Defined in |
|---|---|---|---|
| 单元数、宽度 、面坐标、单元中心cells, width , face coordinates, cell centres | — | 第 2 节Section 2 | |
| 状态:单元平均state: the cell averages | 第 2 节Section 2 | ||
| 设计:振幅、相位、黏性design: amplitude, phase, viscosity | 3 | 第 1 节Section 1 | |
| 积分残差(不除以 )integrated residual (not divided by ) | 第 5 节Section 5 | ||
| 面通量:对流部分加黏性部分face flux: convective plus viscous part | 第 3 节Section 3 | ||
| 迹、未重构状态、距离、traces, raw states, distance, | — | 第 3 节Section 3 | |
| 斜率与两个单侧差slope and the two one-sided differences | 第 3 节Section 3 | ||
| 对迹的偏导derivatives of with respect to the traces | 第 3 节Section 3 | ||
| 精确通量与积分源项 exact flux and integrated source | 第 3 节Section 3 | ||
| 状态与设计 Jacobianstate and design Jacobians | 第 6、8 节Sections 6 and 8 | ||
| 离散目标与状态梯度discrete objective and state gradient | 第 7 节Section 7 | ||
| 切线与设计种子tangent and design seed | 第 8、10 节Sections 8 and 10 | ||
| 伴随变量与设计伴随(梯度)adjoint variable and design adjoint (the gradient) | 第 9、11 节Sections 9 and 11 | ||
| 连续伴随场the continuous adjoint field | — | 第 1、11、12、16 节、附录 ASections 1, 11, 12 and 16 and Appendix A |
下标的约定: 总是单元, 总是面;数字下标写在 上时指单元中心( 是单元 4 的中心),面坐标写 (面 4 在 )。伪代码里的数组 s[i] 存斜率 ,它不是第 1 节的源项 。
Subscript convention: always names a cell and a face; a number written on names a cell centre ( is the centre of cell 4), and face coordinates are written (face 4 is at ). In the pseudocode the array s[i] stores the slope ; it is not the source of Section 1.
默认网格上的数组()
The arrays on the default mesh ()
| 单元 Cell | 中心 Centre | 目标权重 Objective weight | 精确单元平均 Exact average | 离散解 (无限制器线性)Discrete (unlimited linear) |
|---|---|---|---|---|
| 0 | 0.0625 | 0.13281 | 0.99082 | 1.02465 |
| 1 | 0.1875 | 0.14844 | 1.13117 | 1.10219 |
| 2 | 0.3125 | 0.16406 | 1.19468 | 1.13883 |
| 3 | 0.4375 | 0.17969 | 1.14415 | 1.06834 |
| 4 | 0.5625 | 0.19531 | 1.00918 | 0.91706 |
| 5 | 0.6875 | 0.21094 | 0.86883 | 0.78348 |
| 6 | 0.8125 | 0.22656 | 0.80532 | 0.77176 |
| 7 | 0.9375 | 0.24219 | 0.85585 | 0.86868 |
权重 是第 7 节离散目标 的系数,也是状态梯度 (目标对状态的导数;伴随方程 的右端是 )。
The weights are the coefficients of the discrete objective of Section 7 and also the state gradient (the right-hand side of the adjoint equation is ).
后面各节要用到的所有数组和它们的形状都在图 6 里:左边是向量,右边是矩阵(颜色表示正负与大小,蓝正橙负)。它们都是本页在默认设计 和 下真实算出的量; 是 的五对角矩阵, 只有 3 列,所以一次伴随求解(右端项 )给出梯度的全部 3 个分量,而切线求解要做 3 次。
All the arrays that the later sections use, with their shapes, are in Figure 6: vectors on the left, matrices on the right (colour shows sign and size: blue positive, orange negative). They are real quantities of this page at the default design and : is an five-band matrix and has only 3 columns, so one adjoint solve (right-hand side ) gives all 3 design components while the tangent solves must be done 3 times.
面上的通量:对流、黏性与重构
The flux on a face: convection, viscosity and reconstruction
本节实验:选定一个面和一种重构,这个面读哪几个单元?两个迹 哪个等于单元平均?先预测,再用本节末尾的面板验证。
Experiment: Select a face and a reconstruction: which cells does the face read, and which of the traces equals a cell average? Predict first, then check with the panel at the end of this section.
第 2 节把区间分成了单元。本节把方程写成单元上的通量差,用数值通量把面两侧的数据变成一个面通量(对流部分和黏性部分),再说明二阶格式怎样把单元平均重构成面上的状态。
Section 2 divided the interval into control volumes. This section writes the equation as a flux difference over a control volume, turns the data on the two sides of a face into one face flux (a convective part and a viscous part), and then shows how the second-order scheme reconstructs face states from the cell averages.
从方程到通量差
From the equation to a flux difference
第 1 节的方程是 ,。在单元 (面 到面 )上积分,由微积分基本定理,通量的导数变成两端通量之差,等于单元内的总源项:
The equation of Section 1 is with . Integrate it over cell (face to face ): by the fundamental theorem of calculus the derivative of the flux becomes the difference of the end fluxes, which equals the total source in the cell:
对构造解,源项是 ,所以一个单元的总源项就是精确通量 在这个单元两个面上的差,不需要数值积分;第 5 节的残差用的就是这个精确的积分源项。离散化要做的只剩一件事:用两侧单元的数据近似每个面上的通量。
For the manufactured solution the source is , so the total source of a cell is exactly the difference of the exact flux at its two faces, with no numerical quadrature; the residual of Section 5 uses this exact integrated source. What remains for the discretisation is to approximate the flux at each face from the data of the cells on its two sides.
一个面上的数值通量:对流加黏性
The numerical flux on a face: convective plus viscous
面 的通量分两部分,用不同的输入:对流部分用面两侧重构出的状态 (下面叫“迹”),黏性部分用两侧的未重构状态 (边界面上其中一个是给定值)和它们之间的距离 :
The flux of face has two parts that use different inputs: the convective part uses the states reconstructed on its two sides (called traces below) and the viscous part uses the raw states (on a boundary face one of them is the prescribed value) and the distance between them:
本页其他地方(包括本节开头的公式), 与 表示总通量;面上的重构状态沿用其他一维页面的写法 。
Elsewhere on this page, including the first display of this section, and denote the total flux; for the face states the page follows the other 1-D pages and writes .
对流部分是 Rusanov 通量:两侧物理通量 的平均,减去与状态跳跃成比例的耗散。耗散项里的波速 取固定值,不随状态或设计变化,所以求导时没有隐藏的波速项(通常的波速 在 处不可导,用它构成的 Rusanov 通量在 处不可导);控制面板允许的所有设计与网格上,收敛状态的迹都满足 。Rusanov 通量满足相容性 ,并且同一个 离开左边的单元、进入右边的单元,使内部通量在全局求和中抵消。黏性部分是物理通量 的中心差分近似:内部面 ,边界面 (第 4 节)。
The convective part is the Rusanov flux: the average of the physical fluxes of the two sides, minus a dissipation proportional to the jump of the state. The dissipation speed is a fixed number that depends on neither the state nor the design, so differentiation hides no wave-speed term (the usual wave speed is not differentiable where , and the Rusanov flux built with it is not differentiable where ); on all designs and meshes that the controls allow, the converged states have traces below 2 in magnitude, . It is consistent, , and the same leaves the cell on the left and enters the cell on the right, so the interior fluxes cancel in a global sum. The viscous part is the central-difference approximation of the physical flux : on interior faces and on boundary faces (Section 4).
耗散项 是人工黏性,它的作用可以写成精确的恒等式(下面的面板在当前状态上再验算一遍)。一阶时迹等于单元平均,内部行 的通量差 是
The dissipation term is artificial viscosity, and what it does can be written as exact identities (the panel below checks them again on the current state). At first order the traces equal the cell averages and the flux difference of an interior row , , is
这正是物理黏性 再加人工黏性 的中心格式(除以 后是 的中心差分):一阶格式在内部行上就是黏性 的中心格式。默认 时 ,是 的 12.5 倍; 要小到与 同量级(这里 ),人工黏性才不再主导。无限制器线性重构时,在两侧单元都做了重构的面上(),耗散项是三阶差分:
This is exactly the central scheme for the physical viscosity plus an artificial viscosity (divided by it is the central difference of ): on interior rows the first-order scheme is the central scheme for the viscosity . At the default one has , that is 12.5 times ; has to become comparable with (about here) before the artificial viscosity stops dominating. With unlimited linear reconstruction, on a face whose two neighbouring cells are both reconstructed () the dissipation of the face is a third difference:
行 (,即两个面之差)的耗散是四阶差分,对光滑数据是 :
and that of a row (the difference of two faces) is a fourth difference, for smooth data:
所以远离端点时二阶格式没有二阶导数形式的人工黏性,只有四阶的超黏性;但在面 上,有一侧的迹没有重构,耗散约为 ,相当于 的人工黏性(第 4 节)。一阶格式的人工黏性 是两种阶数的主要差别,第 15 节(状态与梯度的误差)和第 16 节(伴随)把这个差别量出来。光滑限制器时权重随状态变化,没有这样的闭式。
So away from the ends the second-order scheme has no artificial viscosity of second-derivative form, only a fourth-order hyperviscosity; on faces one trace is not reconstructed and the dissipation is about , an artificial viscosity (Section 4). The artificial viscosity of the first-order scheme is the main difference between the two orders, and Section 15 (the errors of the state and the gradient) and Section 16 (the adjoint) put that difference in numbers. With the smooth limiter the weights change with the state and there is no such closed form.
重构:从单元平均到面上的状态
Reconstruction: from cell averages to face states
每个单元用一个斜率 重构成一条线段,线段在单元两端的值就是相邻面的迹。斜率由两个单侧差 、 决定( 是穿过整个单元的变化量,不除以 );两端的单元不重构,:
Each cell is reconstructed as a line segment with one slope , and the values of the segment at the two ends of the cell are the traces of the neighbouring faces. The slope is determined by the two one-sided differences and ( is the change across the whole cell, not divided by ); the two end cells are not reconstructed, :
公式中的 first、none、smooth 是三种重构在代码里的名字,分别对应控制面板中的“一阶”“无限制器线性(二阶)”“光滑限制器”。“none”是无限制器的中心斜率,二阶精度,但不保证单调;“smooth”是 van Albada 型的光滑有理斜率:把它写成中心斜率乘一个因子,,因子严格落在 内。 同号、相近且远大于 时因子接近 1,斜率回到中心斜率;有一个差分为零时因子为 0;在极值处()因子为负,斜率很小并且与中心斜率反向,而不是被截成零。这个限制器没有硬开关, 使它光滑,所以斜率处处可微;本页不声称它具有 TVD 或保正性。
In the formula, “first”, “none” and “smooth” are the code names of the three reconstructions (First order, Unlimited linear (second order) and Smooth limiter in the control panel). The slope “none” is the unlimited central slope: second-order accurate but not monotone. The slope “smooth” is the van Albada form, smooth because of : written as the central slope times a factor, , where the factor lies strictly inside . It is close to 1 when have the same sign, are close to each other and are much larger than , so the slope returns to the central one; it is 0 when one difference vanishes; at an extremum () it is negative, so the slope is small and points against the central slope instead of being cut to zero. There is no hard switch, so the slope is differentiable everywhere; the page claims no TVD or positivity property for it.
无限制器线性重构时,迹是未知量的固定线性组合:内部面 、,所以一个内部面依赖 4 个未知量(紧邻边界的面是 3 个,边界面是 1 个);一阶时只依赖 2 个。光滑限制器时这些权重随状态变化。
With unlimited linear reconstruction the traces are fixed linear combinations of the unknowns: on an interior face and , so an interior face depends on 4 unknowns (3 next to a boundary, 1 on a boundary face) and only on 2 at first order. With the smooth limiter these weights change with the state.
先用滑块看斜率:给定两个单侧差,三种重构在中间单元里画出什么线段。
First look at the slope with the sliders: given the two one-sided differences, what segment does each reconstruction draw in the middle cell.
下面的播放器把这个面的计算拆成七步,每一步写出当时的数值;这七步就是第 5 节、第 8 节的四条语句(取值、重构、通量、分发),只是把迹和通量的两个部分分开显示。按“播放”或用步进按钮,换面、换重构再看一遍。
The player below splits the computation of this face into seven steps and writes the numbers of each step; the seven steps are the four statements gather, reconstruct, flux and scatter of Section 5 and Section 8, with the traces and the two parts of the flux shown separately. Press Play or use the step buttons, and change the face and the reconstruction to see it again.
一个面上的局部导数
Local derivatives on one face
第 8、9 节的切线与伴随,在面上只用到三类局部导数:数值通量对迹的偏导、对未重构状态的偏导,以及迹对未知量的偏导(此外还有通量对设计 的偏导,以及边界值和精确通量对设计的偏导)。数值通量是多项式,逐项求导:
On a face, the tangent and the adjoint of Sections 8 and 9 use only three kinds of local derivative: those of the numerical flux with respect to the traces, those with respect to the raw states, and those of the traces with respect to the unknowns (plus the derivative of the flux with respect to the design and those of the boundary values and the exact fluxes with respect to the design). The numerical flux is a polynomial, so it is differentiated term by term:
有两个性质可以检验这些公式。相容性 沿 求导,得 :两个偏导把波速分成上游和下游两份。当 时 :通量随左状态增加、随右状态减小。默认设计的各面上(无限制器线性) 在 1.374 与 1.565 之间, 在 −0.625 与 −0.426 之间。黏性偏导 是常数,内部面 。
Two properties check these formulas. Consistency differentiated along gives : the two partial derivatives split the wave speed into an upstream and a downstream share. For one has : the flux increases with the left state and decreases with the right one. On the faces of the default design (unlimited linear) lies between 1.374 and 1.565 and between −0.625 and −0.426. The viscous partials are constants, on interior faces.
迹对未知量的导数在无限制器线性重构下就是上面的权重,与状态无关;光滑限制器时多一项: 的导数含 ,它们随状态变化(线性重构时它们是常数 )。图 8 左: 随状态变化,灰带是本页各面迹的范围;右:每个面的通量对每个未知量的导数 ,一阶时每个内部面涉及 2 个未知量,无限制器线性时涉及 4 个。
The derivative of a trace with respect to the unknowns is the weight above and does not depend on the state for unlimited linear reconstruction; with the smooth limiter there is one more term: the derivative of contains , which change with the state (for linear reconstruction they are the constants ). Figure 8 left: against the state, the grey band being the range of the traces of this page; right: the derivative of every face flux with respect to every unknown, ; an interior face touches 2 unknowns at first order and 4 with unlimited linear reconstruction.
一个面上的数值(面 ,)
One face with the page's numbers (face , )
下表是三种重构各自的收敛解在同一个面上的量;每个重构的解不同,所以连未重构状态 也不同。验证:, 由迹算出。
The table lists the quantities of the same face in the converged solution of each reconstruction; the solutions differ, so even the raw states differ. Check: , and follow from the traces.
| 量Quantity | 公式Formula | 一阶first order | 无限制器线性unlimited linear | 光滑限制器smooth limiter |
|---|---|---|---|---|
| 0.950958 | 1.012899 | 0.994602 | ||
| 0.823170 | 0.988274 | 0.949153 | ||
| 0.95096, 0.82317 | 1.06834, 0.91706 | 1.04010, 0.87288 | ||
| 0.523270 | 0.525287 | 0.517980 | ||
| 0.010223 | 0.012102 | 0.013378 | ||
| 0.533493 | 0.537389 | 0.531358 | ||
| 1.4755, −0.5884 | 1.5064, −0.5059 | 1.4973, −0.5254 |
注意一阶时两个迹等于单元平均,对流部分的跳跃 较大,人工黏性起作用;无限制器线性时跳跃小得多。换一个面、换一种重构试试:
Note that at first order the traces equal the cell averages, the jump of the convective part is larger and the artificial viscosity acts; with unlimited linear reconstruction the jump is much smaller. Try another position and another reconstruction:
第 4 节处理两个边界面:它们没有单元可读,用给定的边界值和半个单元的距离。第 5 节把面的计算(清单中的语句 5–9)写成残差代码。
Section 4 writes the two end faces: they have no cell to read and use the prescribed boundary value and half a cell of distance. Section 5 writes the face computation (statements 5–9 of its listing) as the residual code.
边界条件:两个边界面
Boundary conditions: the two end faces
本节实验:面 只有右边有单元。先预测:把精确单元平均代入残差,单元 0 的 和内部单元的 哪个更大、差几阶 ?端点行如果更不准,整个解的误差会因此降阶吗?写下预测,再看图 12。
Experiment: Face has a cell on its right only. Predict first: when the exact cell averages are put into the residual, which is larger, of cell 0 or the of an interior cell, and by how many powers of ? If the end rows are less accurate, does the error of the whole solution lose an order? Write your prediction down, then look at Figure 12.
第 3 节写的是两侧都有单元的面。区间两端的面 与 只有一侧有单元;本节说明它们读什么、怎样求导、有多准,以及为什么它们不太准却不改变整个解的渐近阶。
Section 3 wrote the flux of a face with a cell on each side. The two faces at the ends of the interval, and , have a cell on one side only; this section says what they read, how they are differentiated, how accurate they are, and why their lower accuracy does not change the asymptotic order of the whole solution.
为什么两端各要一个值
Why each end needs a value
把定常方程 从 积到 :
Integrate the steady equation from to :
第一式说明通量只剩一个待定常数 ;第二式是 的一阶常微分方程,又带来一个积分常数。两个常数要两个条件:,。没有黏性()时第二式变成代数关系 ,只剩常数 ,入口给一个值就够了——这就是无黏 Burgers 页面只给入口条件的原因。对构造解 ,,即 。第 5 节会看到,离散解满足同样形式的关系,只是多出一个不为零的常数。
The first relation says that the flux is fixed up to one constant, ; the second is a first-order ordinary differential equation for , which brings a second constant of integration. Two constants need two conditions: and . Without viscosity () the second relation becomes the algebraic , only the constant is left, and one inlet value is enough: this is why the inviscid Burgers pages prescribe an inlet value only. For the manufactured solution one has , that is . Section 5 shows that the discrete solution satisfies a relation of the same form, with an extra constant that is not zero.
边界面读什么
What the end faces read
注意字母 的两种用法:上一小节的 、 是方程的总通量,下面的 是第 3 节的迹(重构到面上的状态)。面 在 ,右边是单元 ,左边没有单元。给定值 顶替缺失的左侧,而且在通量的两部分里都顶替:对流部分的左迹 ,黏性部分的左状态 ;黏性差商的距离是从边界到单元 中心的 。面 对称地用 顶替右侧:
Note the two uses of the letter : the and above are total fluxes of the equation, the below are the traces of Section 3 (states reconstructed at a face). Face is at with cell on its right and no cell on its left. The prescribed value takes the place of the missing left side, in both parts of the flux: the left trace of the convective part is and the left state of the viscous part is ; the distance of the viscous difference quotient is , from the boundary to the centre of cell . Face uses in place of its missing right side, symmetrically:
为什么 就是 ?单元 的斜率 需要左差分 ,而 在区间之外。格式不造虚拟单元,干脆不重构两端的单元:。于是单元 在它两个面上的迹都等于平均值,,而且 ;同理 。后果有两个:两个边界面通量各只依赖一个未知量(三种重构都如此);面 与面 各有一个迹没有重构,它们只有一阶精度(下面会量到)。
Why is just ? The slope of cell would need the left difference , and lies outside the interval. The scheme invents no ghost cell; it simply does not reconstruct the two end cells: . So the traces of cell on both its faces equal its average, and also ; likewise . This has two consequences: each end-face flux depends on a single unknown (for all three reconstructions), and faces and each have one unreconstructed trace, which makes them only first-order accurate (measured below).
两个边界面通量的完整写法
The two end-face fluxes written in full
求导只需第 3 节的局部导数,把 换成边界面的 :
Differentiating them needs only the local derivatives of Section 3, with replaced by the end-face value :
默认设计下(,无限制器线性):,,,,,,,。第 8 节的前向代码取种子 和 时算出的 与这些公式一致(三种重构,最大差 2.2e−16)。
At the defaults (, unlimited linear): , , , , , , , . The forward code of Section 8, seeded with and , returns equal to these formulas (all three reconstructions; largest difference 2.2e−16).
这些导数进入哪里? 含 ,所以 以负号进入对角元 ,贡献 :边界面加强了对角线。 只出现在 里,所以只进入 的第 0 行; 只进入最后一行。
Where do these derivatives go? contains , so enters the diagonal entry with a minus sign and contributes : the boundary face strengthens the diagonal. occurs in only, so it enters only row 0 of ; enters only the last row.
边界值与它的切向量
The boundary data and their tangent
边界值由设计决定:; 以 1 为周期,所以 。它们的切向量(第 8 节前向代码的语句 2)是
The boundary data are functions of the design: , and since has period 1, . Their tangent (statement 2 of the forward code of Section 8) is
这里没有 项: 不依赖 ,边界值也就不依赖 。但 仍然进入边界面,途径是 和 。默认设计下 ,;后者恰好等于 ,因为相位 平移整条波:。
There is no term: does not depend on , so neither do the boundary data. But still enters the end faces, through and . At the defaults and ; the latter is exactly , because the phase shifts the whole wave: .
自己动手:调节 和 ,看边界面通量的两部分、三个偏导和半单元差商怎样变;按钮可把 设为精确平均或求解值。
Try it: adjust and and watch the two parts of the end-face flux, its three partial derivatives and the half-cell difference quotient; the buttons set to the exact average or to the solved value.
半个单元的差商有多准?
How accurate is the half-cell difference quotient?
设 光滑, 是它在单元 0 上的平均。把 在 处展开后逐项积分,得到第一行;减去 再除以 ,得到第二行:
Let be smooth and its average over cell 0. Expanding about and integrating term by term gives the first line; subtracting and dividing by gives the second:
这个差商只有一阶精度,首项误差是 。内部面不同:在面 处展开两侧平均,偶数阶项对称抵消,得到二阶精度:
This quotient is only first-order accurate, with leading error . An interior face is different: expanding the two averages about , the even terms cancel by symmetry, and the quotient is second-order accurate:
常见的错误:把单元平均当成中心点的值。若 是点值 ,二次项是 ,系数是 ;单元平均的二次项是 ,系数是 。图 10 把两者都画了出来。
A common slip: treating the cell average as the value at the centre. If were the point value its quadratic term would be and the coefficient ; the average has the quadratic term and the coefficient . Figure 10 draws both.
用 实测:,。 时差商误差除以 得 1.11695;再减去 后除以 得 −3.7429,对应 。若用中心点值,误差除以 得 0.83863,对应 。展开式的前两项都由数值证实。
Measured with : and . At the error of the quotient divided by is 1.11695; after subtracting and dividing by it is −3.7429, against . With the centre value the error divided by is 0.83863, against . Both terms of the expansion are confirmed numerically.
边界面的对流部分同样只有一阶: 不是面上的值,而跳跃 使 Rusanov 耗散 起作用,这是一个约 的人工黏性通量。记精确平均组成的向量为 。把 在 处线性化(),加上黏性部分,边界面通量在精确平均处的误差是
The convective part of the end face is first order as well: is not the value at the face, and the jump brings in the Rusanov dissipation , an artificial viscous flux of about . Write for the vector of exact averages. Linearising about () and adding the viscous part, the error of the end-face flux at the exact averages is
系数为 −0.31967;实测 时 。
with the coefficient −0.31967; measured at : .
截断误差:端点行与内部行
The truncation error: end rows against interior rows
把精确单元平均代入残差,看它离零有多远:这就是截断误差。对每个面定义面误差 (精确平均处的数值通量减去精确通量)。源项是精确的(第 5 节),所以残差就是相邻两个面误差之差:
Put the exact cell averages into the residual and see how far it is from zero: that is the truncation error. Define for every face the face error (the numerical flux at the exact averages minus the exact flux). The source is exact (Section 5), so the residual is the difference of two neighbouring face errors:
无限制器线性时内部面的 ,并且随 光滑变化,相邻两面之差再小一个 :内部行 。端点处 (上一小节); 也是 ,因为面 1 的左迹 没有重构。无限制器线性时, 的首项系数为 −0.8286:
With unlimited linear reconstruction an interior face has , varying smoothly with , and the difference of two neighbouring faces is one power of smaller: the interior rows are . At the ends (previous subsection), and is too, because the left trace of face 1 is not reconstructed. With unlimited linear reconstruction the leading coefficient of is −0.8286:
于是 与 都是 ,系数 −0.5089 与 0.8286(实测 :−0.5093 与 0.8289):这两行比内部行差两阶(右端的行 、 同样如此)。一阶格式的内部面 (人工黏性 ),内部行是 。它的行 0 也是 :,其中 是内部面的人工黏性通量, 是上面的边界面误差,两者之差的系数为 −0.8174(实测 :−0.8180);行 1 是 。
So and are both , with the coefficients −0.5089 and 0.8286 (measured at : −0.5093 and 0.8289): these two rows are two orders less accurate than the interior rows (and so are rows and at the right end). At first order an interior face has (the artificial viscosity ) and the interior rows are . Its row 0 is as well: , where is the artificial viscous flux of an interior face and the end-face error above; their difference has the coefficient −0.8174 (measured at : −0.8180); its row 1 is .
实测与推导一致:无限制器线性的 从 的 8.11e−2 降到 的 1.24e−4(每加密一次减半),内部行从 1.65e−2 降到 6.55e−11(每次约除以 8)。用残差密度 (通常所说的局部截断误差)来看,内部是 ,而行 0、1、、 趋于常数,并不趋于零:按逐点的说法,端点行是不相容的。可是解的误差照样趋于二阶(图 12 第四幅: 到 4096 之间观测阶 1.96)。
The measurements agree with the derivation: for unlimited linear falls from 8.11e−2 at to 1.24e−4 at (halved at each refinement) and the interior rows from 1.65e−2 to 6.55e−11 (divided by about 8 each time). Seen through the residual density , the usual local truncation error, the interior is while rows 0, 1, and tend to constants, not to zero: in the pointwise sense the end rows are inconsistent. Yet the error of the solution approaches second order all the same (fourth panel of Figure 12: observed order 1.96 between and 4096).
光滑限制器的斜率是中心斜率乘以因子 ,有两个效应让它在这里失去二阶。其一,在 极值所在的单元(、)两个差分异号,因子为负,斜率在任何网格上都很小( 到 64 时不超过较大差分的 11%)。使它变小的原因随网格而变: 时因子为 −0.96, 只有较大差分的 20%; 与 32 时极值不在单元中心, 并不小(较大差分的 86% 与 68%),因子为 −0.24 与 −0.16; 之后(这里 )因子本身接近零( 时 −0.018)。从 起,这几行与一阶格式表现相同:光滑限制器内部行的最大值就出现在 的极值处,与一阶格式的最大值相对只差 0.30%()和 ()。其二, 是固定的,而光滑解的差分 随网格缩小; 时因子约为 ,重构在各处都关掉。这里 ,所以在 附近开始关掉:各单元因子的最大值在 时为 0.66、0.33、0.11、0.03。解的误差的观测阶在 与 4096 之间为 0.99,在 到 256 之间只有 0.44。固定 的光滑限制器在细网格上不是二阶格式。
The smooth slope is the central slope times the factor , and two effects cost it the second order here. First, at the cell of an extremum of (, ) the two differences have opposite signs, the factor is negative, and the slope is small on every mesh (for to 64 at most 11% of the larger difference). What makes it small depends on the mesh. At the factor is −0.96 and is only 20% of the larger difference. At and 32 the extremum is not centred in a cell, is not small (86% and 68% of the larger difference), and the factor is −0.24 and −0.16. Once (here ) the factor itself is near zero (−0.018 at ). From on these rows behave like first order: the largest interior row of the smooth limiter sits at an extremum of and differs from that of the first-order scheme by only 0.30% at and by (relative) at . Second, is fixed while the differences of a smooth solution, , shrink with the mesh; once the factor is about and the reconstruction switches itself off everywhere. Here , so the switch-off begins near : the largest factor over all cells is 0.66, 0.33, 0.11, 0.03 at . The observed order of the error of the solution is 0.99 between and 4096 and only 0.44 between and 256. With a fixed the smooth limiter is not a second-order scheme on fine meshes.
为什么端点行不拖累整体的阶
Why the end rows do not drag the order down
第 5 节会证明: 等价于每个面上 都等于同一个常数 。于是解与精确平均的面通量之差是 。面 0 只依赖 ,而 是 的二次式,两值之差可以精确分解:
Section 5 proves that is the same as saying that is one constant on every face. So the face fluxes of the solution and of the exact averages differ by . Face 0 depends on only and is quadratic in , so the difference of its two values factorises exactly:
所以单元 0 的误差精确地等于
The error of cell 0 is therefore exactly
的面误差 要除以一个分母;当 ()时,分母随 增大,单元 0 的误差就是 。 小时分母约为 ,单元 0 的误差是 : 时分母为 −0.656(); 时已变为 −0.841、−1.173、−1.818(),绝对值随 增大。 只是 与 同时成立()时的极限;固定 而令 ,分母趋于 −0.494。这个关系之所以精确成立,是因为 在每个面上都相同:在各网格、三种重构的计算解上 ( 取各面平均)。用这个 ,上式复现到相对偏差 2e−10;取中间面的值作 时,复现到 4e−10。下表是无限制器线性的实测。
The face error is divided by a denominator; once () the denominator grows like and the error of cell 0 is . Where is small the denominator is about , so the error of cell 0 is : the denominator is −0.656 at (). At () it is already −0.841, −1.173, −1.818, its magnitude growing with . The value is its limit only when and together (); at fixed and it tends to −0.494 instead. The relation is exact because is the same on every face: at the computed solutions (all meshes, all three reconstructions) (with the mean over the faces); with this the relation is reproduced to a relative deviation of 2e−10, and with taken at the middle face to 4e−10. The table lists the measurements for unlimited linear.
| 分母 denominator | |||||
|---|---|---|---|---|---|
| 8 | −0.6561 | −4.056e−2 | −6.276e−2 | 3.383e−2 | 9.212e−2 |
| 64 | −1.8179 | −5.036e−3 | −6.498e−3 | 8.043e−4 | 7.997e−3 |
| 512 | −10.7820 | −6.251e−4 | −2.776e−4 | −3.223e−5 | 3.443e−4 |
| 4096 | −82.4625 | −7.806e−5 | −5.306e−6 | −8.822e−7 | 6.589e−6 |
单元 0 只是一个单元;解的最大误差由 决定。远离两端时,面误差 很小,;按首项 ,得 ,所以最大误差出现在 最小处,约为 。实测 时最大误差位于 到 0.86, 在 1.222 与 1.242 之间,而 。 的观测阶从粗网格的约 1(:1.03)逐渐升到细网格的约 2(:1.96),解的误差跟着它: 时观测阶在 1.15 与 1.52 之间,到 才接近 2。
Cell 0 is only one cell; the largest error of the solution is set by . Away from the ends the face errors are small and ; with the leading term this gives , so the largest error sits where is smallest and is about . Measured for : the maximum lies at to 0.86 and between 1.222 and 1.242, against . The observed order of rises from about 1 on coarse meshes (1.03 for ) to about 2 on fine ones (1.96 for ), and the error of the solution follows it: for its observed order lies between 1.15 and 1.52, and it approaches 2 only for .
用下面的加密播放器一步一步看:每一步把 加倍,左图是截断误差的分布,右图是解的误差 。
Step through it with the refinement player below: each step doubles ; the left panel is the distribution of the truncation error, the right panel the error of the solution .
两个容易误读的地方。其一,端点行的残差密度 不趋于零,这不是程序错误:积分残差 本身仍是 ,而且它是两个面误差之差,解的误差由面误差决定。其二, 时的观测阶还没进入渐近区,不能当作格式的阶;端点行不改变渐近阶,却决定粗网格上误差的大小,并使二阶行为来得很晚(第 15 节测量这一点)。
Two things that are easily misread. First, the residual density of the end rows does not tend to zero, and this is not a bug: the integrated residual itself is still , it is a difference of two face errors, and the error of the solution is governed by the face errors. Second, the orders observed for are not yet asymptotic and must not be taken for the order of the scheme; the end rows do not change the asymptotic order, but they set the size of the error on coarse meshes and delay the second-order behaviour (Section 15 measures this).
残差与 Newton 求解
The residual and the Newton solve
本节实验:第 4 节说边界面通量在精确平均处有 的误差,那么解满足 时,各个面通量 满足什么?先预测,再看图 13。第二问: 时 Newton 法要迭代几次?残差是二次收敛的吗?
Experiment: Section 4 says the end-face fluxes are off by at the exact averages. What, then, do the face fluxes satisfy when the solution satisfies ? Predict, then look at Figure 13. Second question: how many Newton iterations does need, and does the residual fall quadratically?
第 3、4 节给出了每个面的通量。本节把它们装配成残差(它的零点就是离散解),说明“解出来了”是什么意思,再用 Newton 法解 。
Sections 3 and 4 gave the flux of every face. This section assembles them into the residual, the function whose zero is the discrete solution, says what “solved” means, and solves by Newton's method.
残差代码
The residual code
残差由一个面循环算出:每个面只访问一次,通量只算一次,然后分发给面两侧的两个单元(左单元加、右单元减)。下面就是本页 model.js 中 residual 的语句,编号与第 8、9 节的前向、反向代码一致:
The residual is computed by one loop over the faces: each face is visited once, its flux is computed once and scattered to the two cells on its two sides (plus to the left cell, minus to the right cell). These are the statements of residual in this page's model.js, numbered as in the forward and reverse codes of Sections 8 and 9:
residual(U, D) -> res U = (U[0], ..., U[N-1]),D = (a, phi, nu)
1 res = 0
2 uin = u*(0; D); uout = u*(1; D) 边界值(第 4 节)
for i = 1 .. N-2: (s[0] = s[N-1] = 0)
3 l = U[i] - U[i-1]; r = U[i+1] - U[i] 单侧差
4 s[i] = sigma(l, r) 斜率 σ_i(第 3 节;不是源项 s(x))
for f = 0 .. N:
5 if f == 0: qL = UL = uin 取值 + 重构:左侧
else: qL = U[f-1] + s[f-1]/2; UL = U[f-1]
6 if f == N: qR = UR = uout 右侧
else: qR = U[f] - s[f]/2; UR = U[f]
7 d = h/2 if f in (0, N) else h
H = (qL^2 + qR^2)/4 - (qR - qL) - nu*(UR - UL)/d 通量
8 if f >= 1: res[f-1] += H 分发:左单元加
9 if f <= N-1: res[f] -= H 右单元减
for f = 0 .. N:
10 Q[f] = q*(xf[f]; D) 精确通量
for i = 0 .. N-1:
11 res[i] -= Q[i+1] - Q[i] 精确积分源项
return resresidual(U, D) -> res U = (U[0], ..., U[N-1]), D = (a, phi, nu)
1 res = 0
2 uin = u*(0; D); uout = u*(1; D) boundary data (Section 4)
for i = 1 .. N-2: (s[0] = s[N-1] = 0)
3 l = U[i] - U[i-1]; r = U[i+1] - U[i] one-sided differences
4 s[i] = sigma(l, r) slopes σ_i (Section 3; not the source s(x))
for f = 0 .. N:
5 if f == 0: qL = UL = uin gather + reconstruct: left
else: qL = U[f-1] + s[f-1]/2; UL = U[f-1]
6 if f == N: qR = UR = uout right
else: qR = U[f] - s[f]/2; UR = U[f]
7 d = h/2 if f in (0, N) else h
H = (qL^2 + qR^2)/4 - (qR - qL) - nu*(UR - UL)/d flux
8 if f >= 1: res[f-1] += H scatter: plus to the left cell
9 if f <= N-1: res[f] -= H minus to the right cell
for f = 0 .. N:
10 Q[f] = q*(xf[f]; D) exact flux
for i = 0 .. N-1:
11 res[i] -= Q[i+1] - Q[i] exact integrated source
return res语句 5–9 是第 3 节的面计算(5–6 取值并重构,7 通量,8–9 分发);语句 2 提供第 4 节的边界值,语句 3–4 是斜率(两端单元不重构),语句 10–11 是精确源项。model.js 在一个单元循环里算斜率(语句 3–4),在一个面循环里算出所有面的数组(语句 5–7),再用第二个面循环按同样的顺序分发(语句 8–9),最后逐个单元减去源项(语句 10–11);浮点运算的次序完全相同,所以下面的播放器按这段伪代码重放,得到的 与 model.js 逐位相同(已验证)。
Statements 5–9 are the face computation of Section 3 (5–6 gather and reconstruct, 7 the flux, 8–9 the scatter); statement 2 supplies the boundary values of Section 4, statements 3–4 the slopes (the end cells are not reconstructed), statements 10–11 the exact source. model.js computes the slopes in a cell loop (statements 3–4), the arrays of all faces in a face loop (5–7), scatters them in a second face loop in the same order (8–9) and subtracts the source cell by cell (10–11); the floating-point operations happen in the same sequence, so the player below, which replays this pseudocode, gets the same as model.js bit for bit (verified).
精确的积分源项
The exact integrated source
单元残差是 在单元上的积分。通量部分给出 ;源项部分,因为构造时就令 (第 1 节),由微积分基本定理得
The cell residual is the integral of over the cell. The flux part gives ; for the source part, since by construction (Section 1), the fundamental theorem of calculus gives
这里不需要求积公式,对任何 都精确。验算:在每个单元上用 20 点 Gauss 求积积分 ,与 的最大差是 1.7e−16()。所以残差唯一的误差来源是数值通量,第 4 节才能写出 。
No quadrature rule is needed, and the source is exact for every . Check: integrating over every cell with 20 Gauss points gives to 1.7e−16 (). The numerical flux is therefore the only source of error in the residual, which is why Section 4 could write .
守恒:把所有行加起来,内部面的通量成对抵消,只剩两个边界面:。在 Newton 初值处()等式左右两边都是 −0.048765632351。
Conservation: adding all rows, the fluxes of the interior faces cancel in pairs and only the two end faces remain: . At the Newton initial guess () both sides are −0.048765632351.
“解出来了”是什么意思: 等价于各面通量误差相等
What “solved” means: makes the flux error one constant
对所有 成立,就是 对所有 成立:每个面上的通量误差都相同。
for every says for every : the flux mismatch is the same on every face.
这里 是同一个常数(下标 表示通量,它与第 3 节的偏导 无关)。连续解的通量就是 (第 4 节开头);离散解的通量是精确通量加一个常数 —— 个方程定下 个未知量,剩下的这个常数就是边界误差留下的痕迹。默认设计下 (无限制器线性,,取各面平均),各面与它之差不超过 ;一阶与光滑限制器分别是 −0.066659 与 −0.068794。加密后 :无限制器线性在 时是 −5.31e−6,一阶是 −2.83e−4。
Here is one and the same constant (the subscript stands for the flux; it has nothing to do with the partial derivatives of Section 3). The continuous solution has the flux (beginning of Section 4); the discrete solution has the exact flux plus a constant : equations fix unknowns, and this one constant is what the boundary errors leave behind. At the defaults (unlimited linear, , the mean over the faces), every face within of it; first order and the smooth limiter give −0.066659 and −0.068794. Under refinement : −5.31e−6 at for unlimited linear, −2.83e−4 for first order.
实践中“解出来了”指 Newton 修正量 (或 )。残差本身降不到舍入误差平台以下:每个 是两个约 0.5 的通量之差,舍入误差约 到 。
In practice “solved” means a Newton correction (or ). The residual itself cannot fall below its rounding floor: each is a difference of fluxes of about 0.5, with rounding errors of about to .
下面的播放器逐句执行这段伪代码:可以选 Newton 初值、收敛解或精确平均,看每个面的取值、通量和分发怎样把 一点一点累加出来。
The player below executes the pseudocode one statement at a time: choose the Newton initial guess, the converged solution or the exact averages, and watch the gather, the flux and the scatter of each face build up step by step.
Newton 法:model.solve 的做法
Newton's method as model.solve does it
线性化(,第 6 节):
Linearise (, Section 6):
令线性近似 为零,得到 Newton 修正 。本页求解器 solve 的逻辑逐句如下:
Setting the linear approximation to zero gives the Newton correction . The page's solver solve is, statement by statement:
solve(D):
U = (u*(x_0), ..., u*(x_{N-1})) u* 的中心点值:只是初值
for k = 0, 1, ..., 34:
R = residual(U, D); A = R_U(U, D) (N 次前向计算,第 6 节)
解 A ΔU = -R (LU)
记录 ‖R‖∞, ‖ΔU‖∞
if ‖ΔU‖∞ ≤ 1e-13 或 ‖R‖∞ < 1e-15: return U 收敛(不再加 ΔU)
t = 1
至多 25 次:
Ut = U + t ΔU
if 0.1 < min Ut, max Ut < 1.9 且 ‖R(Ut, D)‖∞ < (1 - 1e-4 t) ‖R‖∞:
U = Ut; 进入下一个 k
t = t/2
return U 线搜索失败
return U 迭代次数用完solve(D):
U = (u*(x_0), ..., u*(x_{N-1})) centre samples of u*: an initial guess only
for k = 0, 1, ..., 34:
R = residual(U, D); A = R_U(U, D) (N forward evaluations, Section 6)
solve A ΔU = -R (LU)
record ‖R‖∞, ‖ΔU‖∞
if ‖ΔU‖∞ ≤ 1e-13 or ‖R‖∞ < 1e-15: return U converged (ΔU is not added)
t = 1
at most 25 times:
Ut = U + t ΔU
if 0.1 < min Ut, max Ut < 1.9 and ‖R(Ut, D)‖∞ < (1 - 1e-4 t) ‖R‖∞:
U = Ut; go on with the next k
t = t/2
return U line search failed
return U iteration limit几个细节:初值是 的中心点值——页面知道精确解,但只拿它当起点,结果是离散解而不是 ;先试整步,回溯时步长 减半(这里 是步长;第 8、10、11 节的 是别的参数),直到试探状态留在 内并且残差降到原来的 倍以下;修正量 时停止,返回的是最后一个迭代值(最后的修正不再加上)。默认设计下三种重构的每一步都是整步 (重放线搜索可以逐位重现每个迭代值)。
Some details: the initial guess is the centre samples of (the page knows the exact solution but uses it only as a starting point; the result is the discrete solution, not ); the full step is tried first and the backtracking halves the step length (here is the step length; Sections 8, 10 and 11 use for other parameters) until the trial state stays inside and the residual falls below times its old value; the iteration stops when the correction is and returns the last iterate (the last correction is not added). At the defaults every step of all three reconstructions is a full step (replaying the line search reproduces every iterate bit for bit).
二次收敛,可以精确写出来
Quadratic convergence, written out exactly
一阶与无限制器线性的残差是 的二次多项式: 是迹的二次式,而迹是 的线性函数。所以 Taylor 展开到二次项就结束了,整步之后残差恰好是二次余项:
For first order and unlimited linear reconstruction the residual is a quadratic polynomial in : is quadratic in the traces and the traces are linear in . The Taylor expansion therefore stops at the quadratic term, and after a full step the residual is exactly the quadratic remainder:
其中 是 引起的迹增量。由此 。无限制器线性时由 与 得简单的上界 ;一阶时迹就是单元值, 与 共有的一项 在差中消去,于是内部行 ,行 0 是 ,行 是 ,所以 ,而且不能再小。单用 得不到这个常数,因为 可达 ( 的一阶 Newton 步上 实测为 0.500)。这就是二次收敛。实测:二次余项恒等式的偏差为 4.3e−16;比值 在一阶时为 0.245、0.250、0.250,无限制器线性时为 0.321、0.341、0.453,都在上界之内。光滑限制器的斜率是有理函数,残差不是多项式,没有这个恒等式;光滑限制器时 Newton 法在 用了 7 次迭代,前两步残差只降到上一步的 0.70 倍和 0.36 倍,之后才二次下降。
where are the trace increments of . Hence . For unlimited linear, and give the simple bound . At first order the traces are the cell values and the term shared by and cancels in their difference, so an interior row is , row 0 is and row is : , and no smaller constant works. alone would not give this constant, since it can reach (along the first-order Newton steps at the ratio is 0.500). This is quadratic convergence. Measured: the remainder identity holds to 4.3e−16; the ratio is 0.245, 0.250, 0.250 at first order and 0.321, 0.341, 0.453 for unlimited linear, all within the bounds. The smooth slope is a rational function, the residual is no polynomial and there is no such identity; with the smooth limiter Newton's method needed 7 iterations at , its residual fell only to 0.70 and 0.36 times the previous value in the first two steps, and quadratically after that.
下面的播放器每步执行一次 Newton 迭代,显示迭代值(与精确平均对比)、修正量、步长、残差,以及这一步用的 Newton 矩阵 (第 6 节的主角)。
The player below takes one Newton iteration per step: the iterate against the exact averages, the correction, the step, the residual, and the Newton matrix of this step (the subject of Section 6).
停止准则要考虑舍入误差平台。 在细网格上不一定达得到:光滑限制器 时(带状求解器),迭代值 的残差已是 9.2e−15,修正量却是 3.4e−13,大于 。在舍入误差平台上,试探步能否通过下降判据只取决于舍入:本次运行中 25 次试探都没有通过,线搜索报告失败(同一算法的另一个实现碰巧接受了整步,再迭代一次就停止)。这时的状态其实已经收敛到舍入精度。全局控件的 ,不会遇到这种情况。
A stopping rule must allow for the rounding floor. cannot always be reached on fine meshes: for the smooth limiter at (band solver) the residual of the iterate is already 9.2e−15 while the correction is 3.4e−13, above . At the rounding floor, whether a trial step still passes the decrease test is a matter of rounding: in this run none of the 25 trials passes and the line search reports a failure (another implementation of the same algorithm happened to accept the full step and stopped one iteration later). The state has in fact converged to rounding accuracy. The global controls stop at , where this does not happen.
格式的相容性
Consistency of the scheme
所谓相容性,是指精确解代入离散方程后,误差随网格加密而消失。下表把第 4 节的测量写成残差密度(无限制器线性):内部 ;两端各两行(第 行)趋于常数;同时 趋于零。积分残差 本身在每一行都趋于零(端点行是 ),解也照样收敛。所以“少数几个端点行的局部截断误差是 ”并不意味着格式不相容;起作用的是通量差的形式。
Consistency asks whether the exact solution satisfies the discrete equations up to an error that vanishes under refinement. The table writes the measurements of Section 4 as residual densities (unlimited linear): inside, constants in the two rows at each end (rows ), and tends to zero. The integrated residual itself tends to zero in every row ( in the end rows), and the solution converges all the same. So “a local truncation error of in a few boundary rows” does not mean an inconsistent scheme; what matters is the flux-difference form.
| 8 | 1.32e−1 | −0.6490 | 0.9565 | −0.3475 | −0.3523 | −6.28e−2 |
| 32 | 5.16e−3 | −0.5547 | 0.8673 | −0.2965 | −0.4694 | −1.45e−2 |
| 128 | 2.77e−4 | −0.5206 | 0.8396 | −0.3063 | −0.5008 | −2.61e−3 |
| 512 | 1.70e−5 | −0.5118 | 0.8314 | −0.3080 | −0.5070 | −2.78e−4 |
| 2048 | 1.07e−6 | −0.5096 | 0.8293 | −0.3084 | −0.5084 | −2.06e−5 |
| 4096 | 2.68e−7 | −0.5093 | 0.8289 | −0.3084 | −0.5087 | −5.31e−6 |
Jacobian 与 Gauss–Seidel
The Jacobian and Gauss–Seidel
本节实验:先数一数,一阶与无限制器线性重构时 的第 3 行各有几个非零元?再预测,Gauss–Seidel 解 Newton 修正方程收敛吗? 与 各要扫多少遍——几遍还是几百遍?图 17、图 18 和下面的面板给出答案。
Experiment: Count first: how many nonzero entries does row 3 of have at first order and with unlimited linear reconstruction? Then predict: does Gauss–Seidel converge on the Newton correction system, and how many sweeps does it need for and for : a few, or hundreds? Figure 17, Figure 18 and the panels below answer.
第 5 节把 当作黑盒用在 Newton 法里。本节构造它,看它的结构和可以证明的性质,再用一阶矩阵上的 Gauss–Seidel(GS)近似地解 Newton 修正方程,并实测 GS 收敛有多快。
Section 5 used as a black box inside Newton's method. This section builds it, looks at its structure and at the properties that can be proved, then solves the Newton correction system approximately with Gauss–Seidel (GS) on the first-order matrix and measures how fast GS converges.
用前向代码按列得到
by columns of the forward code
的第 列是残差对 的单位变化的响应,也就是第 8 节前向代码在种子 、 下的输出: 次前向计算得到整个矩阵(model.js 的 jacState)。如果只需要乘积,就不必组装矩阵:一次前向计算就给出 (这里 是任意向量,不是后面几节的切线解 )。
Column of is the response of the residual to a unit change of , that is, the output of the forward code of Section 8 with the seed , : forward evaluations give the whole matrix (jacState in model.js). A product needs no matrix at all: one forward evaluation gives (here is any vector, not the tangent solutions of the later sections).
验算:用组装好的矩阵算出的 与一次前向计算之差为 4.4e−16(三种重构,)。还可以更省:第 行只涉及 ,所以相距 5 的两列不会出现在同一行;把 除以 5 余数相同的列分为一组(同一“颜色”),每组一起作种子,5 次前向计算就得到整条带,与 无关(与逐列组装之差 0)。本页细网格上的计算( 到 4096)就是这样组装带状矩阵、用带状 LU 求解的。
Check: from the assembled matrix and one forward evaluation differ by 4.4e−16 (all three reconstructions, ). One can do even better: row involves only , so two columns five apart never share a row; grouping the columns by mod 5 (one “colour” per group) and seeding each group at once, 5 forward evaluations give the whole band, whatever is (difference from the column-by-column assembly 0). The fine-mesh computations of this page ( up to 4096) assemble the band this way and solve with a band LU.
结构:五对角或三对角
The structure: five diagonals or three
第 行是 。面 涉及 (无限制器线性、光滑限制器)或 (一阶,第 3 节),所以第 行涉及 (五对角)或 (三对角)。一阶时第 3 节已经写出内部行:它是黏性为 的中心格式。逐项求导就得到它的 Jacobian 行:
Row is . Face involves (unlimited linear, smooth limiter) or (first order, Section 3), so row involves (five diagonals) or (three diagonals). At first order Section 3 wrote out the interior row: it is the central scheme for the viscosity . Differentiating it term by term gives its Jacobian row:
一阶时,首末两行由第 4 节的边界面导数得到:
At first order the first and the last row follow from the end-face derivatives of Section 4:
这些公式与前向代码组装的 一致(默认设计的收敛解和 Newton 初值两个状态,最大差 4.4e−16)。
These formulas agree with the assembled by the forward code (at the converged solution of the default design and at the Newton initial guess; largest difference 4.4e−16).
在下面的面板里换重构( 由全局控件决定),看 、一阶矩阵 、它们的差和 的符号:
In the panel below change the reconstruction ( is set by the global controls) and look at , the first-order matrix , their difference and the signs of :
关于 能说什么
What can be said about
符号。一阶时,只要 (Newton 法的范围 保证这一点),: 是 Z 矩阵(非对角元都不正)。重构后最外两条对角线上出现了重构权重:无限制器线性时 ,,出现正的非对角元( 时有 12 个), 不再是 Z 矩阵;光滑限制器时外对角线带有因子 ,可正可负。
Signs. At first order, as long as (Newton's range guarantees it), : is a Z-matrix (no positive off-diagonal entry). With reconstruction the reconstruction weights appear on the two outer diagonals: for unlimited linear and , so positive off-diagonal entries appear (12 of them at ) and is no longer a Z-matrix; for the smooth limiter the outer diagonals carry and have either sign.
行和。一阶内部行的行和是 ,解下降的地方为负: 不是按行对角占优的( 一阶时行和从 −0.124 到 1.712)。
Row sums. The row sum of a first-order interior row is , negative where the solution decreases: is not diagonally dominant by rows (at first order, , the row sums range from −0.124 to 1.712).
列和。守恒律体现在列和上:(第 5 节的守恒)。边界面只依赖端单元,所以三种重构都有
Column sums. Conservation shows in the column sums: (the conservation of Section 5). The end faces depend on the end cells only, so for all three reconstructions
实测(,无限制器线性):内部列和 ,第 0 列和为 0.6477,第 列和为 1.5943,与上式一致。
Measured (, unlimited linear): interior column sums , column 0 sums to 0.6477, column to 1.5943, as the formulas above say.
一阶的 是 M 矩阵。定理(Varga) 设 是对角元为正的 Z 矩阵。若 不可约、按行弱对角占优,且至少有一行严格对角占优,则 非奇异,且 的元素全为正。
At first order A is an M-matrix. Theorem (Varga). Let be a Z-matrix with positive diagonal. If is irreducible, weakly diagonally dominant by rows and strictly diagonally dominant in at least one row, then is nonsingular and every entry of is positive.
一阶 是 M 矩阵的证明: 也是对角元为正的 Z 矩阵,它的行和就是 的列和:内部各列的列和为 0,第 0 列与第 列的列和为正。对这样的矩阵,“行和非负”恰好就是“按行弱对角占优”,所以 按行弱对角占优,且有两行严格占优。 三对角,上、下次对角元都不为零,所以 不可约。由定理,,即 。这对 内的任何状态都成立。实测: 时 。二阶时 不是 Z 矩阵,定理不适用; 时无限制器线性的 有 14 个负元素,最小 −0.0774,但 时它没有超出舍入误差的负元素。
Proof that the first-order is an M-matrix: is also a Z-matrix with positive diagonal, and its row sums are the column sums of : 0 for the interior columns, positive for columns 0 and . For such a matrix a nonnegative row sum is exactly weak diagonal dominance, so is weakly diagonally dominant by rows, strictly in two rows. is tridiagonal with nonzero sub- and superdiagonal entries, so is irreducible. By the theorem , that is . This holds for every state with . Measured: at . At second order is not a Z-matrix and the theorem does not apply; at the inverse of the unlimited linear has 14 negative entries, the smallest −0.0774, but at it has none above the rounding level.
条件数。(用带状 LU 逐列精确算出,不是估计)随 增长:无限制器线性从 的 31.06 到 的 4.458e4;最后一次加倍时变为原来的 3.64 倍,趋近于扩散算子的 (每加倍一次变为 4 倍);粗网格上对流占优,增长较慢。LU 求解不受影响(误差约 ),但迭代法会变慢,下面会看到。
Condition number. (computed exactly column by column with the band LU, not estimated) grows with : for unlimited linear from 31.06 at to 4.458e4 at ; the last doubling multiplies it by 3.64, approaching the of a diffusion operator (a factor of 4 per doubling); on coarse meshes convection dominates and the growth is slower. LU solves do not care (their error is about ), but iterative methods slow down, as we see next.
用一阶矩阵做 Gauss–Seidel
Gauss–Seidel with the first-order matrix
LU 精确地解 (稠密 ,带状 )。经典的替代办法是迭代:每一步只需要一个便宜的近似解。为了与第 17 节一致,本节把要解的线性方程组写成 : 是第 次迭代值, 是精确解, 叫缺陷(线性方程组的残差);它们与坐标 、第 7 节的矩 、第 3 节的单侧差 无关。取同一状态下的一阶 Jacobian (三对角),把它拆成对角、严格下三角和严格上三角三部分:(用 是为了不和状态 混淆)。从零出发在 上做一次前向 GS 扫描,就是逐行解下三角方程组 ;记这个映射为 (这里的 是 GS 映射,与第 8 节的关联矩阵 无关)。缺陷校正是
LU solves exactly (dense , banded ). The classical alternative is an iteration that needs only a cheap approximate solve at each step. To match Section 17, this section writes the linear system as , with the iterates (iteration counter ), the exact solution and the defect (the residual of the linear system); they have nothing to do with the coordinate , the moments of Section 7 or the one-sided differences of Section 3. Take the first-order Jacobian at the same state (tridiagonal) and split it into its diagonal, strictly lower and strictly upper parts, (written so as not to clash with the state ). One forward GS sweep on from zero solves the lower-triangular system row by row; call this map (here is the GS map; it has nothing to do with the incidence matrix of Section 8). Defect correction is
缺陷校正:解 A x = b,用 P(一阶 Jacobian)和松弛因子 ω
x = 0
for m = 0, 1, 2, ...:
r = b - A x 缺陷(只需要乘积 A x)
if ‖r‖∞ ≤ tol: 停止
for i = 0 .. N-1: P 上的一次前向 GS 扫描,从 Δx = 0 开始
Δx_i = (r_i - Σ_{j<i} P_ij Δx_j) / P_ii (P 是三对角:只有 j = i-1)
x = x + ω Δxdefect correction: solve A x = b with P (the first-order Jacobian) and the relaxation ω
x = 0
for m = 0, 1, 2, ...:
r = b - A x defect (needs the product A x only)
if ‖r‖∞ ≤ tol: stop
for i = 0 .. N-1: one forward GS sweep on P, from Δx = 0
Δx_i = (r_i - Σ_{j<i} P_ij Δx_j) / P_ii (P is tridiagonal: only j = i-1)
x = x + ω Δx一阶()且 时,它就是 上的 Gauss–Seidel:,误差传播矩阵 。因为 是 M 矩阵,所以 也是 M 矩阵,逆矩阵非负;又 ,故 。由 Perron–Frobenius 定理,谱半径 (本节的 都指谱半径)本身就是 的特征值,有非负特征向量(最慢的误差模态);在这些网格上它是唯一模为 的特征值(实测,),所以误差方向趋于这个特征向量。这个分裂是 M 矩阵的正则分裂,由 Varga 定理得 :一阶系统上 GS 对任何 、 内的任何状态都收敛。 的最小元素为 0(第 0 列恒为零)。
At first order () with this is Gauss–Seidel on : , with the error propagator . Since is an M-matrix, is an M-matrix too, with a nonnegative inverse; and , so . By Perron–Frobenius, the spectral radius (in this section always denotes a spectral radius) is itself an eigenvalue of , with a nonnegative eigenvector (the slowest error mode); on these meshes it is the only eigenvalue of modulus (measured, ), so the direction of the error tends to this eigenvector. The splitting is a regular splitting of an M-matrix, so by Varga's theorem: on the first-order system GS converges for every and every state with . The smallest entry of is 0 (its column 0 is zero).
有多快?实测 (一阶,收敛解):0.7487()、0.8290()、0.8840()、0.9305()、0.9655(),随 趋于 1。把误差缩小到原来的 ,一阶时要 次扫描: 时 80 次, 时 656 次。GS 在细网格上很慢。
How fast? Measured (first order, converged solution): 0.7487 at , 0.8290 at , 0.8840 at , 0.9305 at , 0.9655 at , tending to 1 with . Reducing the error to of its initial size takes sweeps at first order: 80 at and 656 at . GS is slow on fine meshes.
二阶()时 不再非负(无限制器线性 时最小元素 −0.0723),也没有定理保证收敛。默认设计下实测 都小于 1(无限制器线性:0.5748、0.6786、0.7618、0.8757、0.9481, 到 128),而且在每个 上都比一阶的小:迭代收敛,比一阶更快; 时模最大的特征值是一对复数。这些是一个设计下的测量,不是保证。
At second order () is no longer nonnegative (smallest entry −0.0723 for unlimited linear at ) and no theorem guarantees convergence. At the default design the measured is below 1 throughout (unlimited linear: 0.5748, 0.6786, 0.7618, 0.8757, 0.9481 for to 128), and below the first-order value at every : the iteration converges, faster than at first order; for the eigenvalues of largest modulus are a complex pair. These are measurements at one design, not a guarantee.
松弛。。一阶时 的特征值都是实数,落在区间 内(是 Jacobi 特征值的平方 和 0),所以 ,在 处最小(在第一个 Newton 修正方程上, 时为 1.55, 时为 1.93):精确算术下超松弛有好处。在这个方程上, 所需的扫描次数是一阶 53、141、559,无限制器线性 36、84、500(),都少于 时的次数。但不能只看 一路加大 : 的零特征值是亏损的(代数重数 ,只有一个特征向量), 时它移到 ,迭代可能先放大很多个数量级再衰减——、 时 ,缺陷却在 119 次扫描内涨到 以上(计算在那里停止)。双精度下 本身在 时不收敛;在 中收敛最快的是 (,分别 46、116、209、452 次扫描)。光滑限制器下 稍大也会停滞:、 要 15138 次扫描。
Relaxation. . At first order the eigenvalues of are real and lie in (they are the squares of the Jacobi eigenvalues, and 0), so , smallest at (on the first Newton correction system 1.55 at , 1.93 at ): in exact arithmetic over-relaxation helps. On this system needs 53, 141, 559 sweeps at first order and 36, 84, 500 for unlimited linear (), fewer than in every case. But cannot be raised by looking at alone: the zero eigenvalue of is defective (algebraic multiplicity , a single eigenvector), for it moves to , and the iteration can grow by many orders of magnitude before it decays: at and one has , yet the defect exceeds within 119 sweeps (where the computation stops). In double precision itself does not converge at ; on the grid the fastest convergent is 1.45, 1.55, 1.60, 1.60 for (46, 116, 209, 452 sweeps). With the smooth limiter a moderately large can stall: and need 15138 sweeps.
播放器在一个真实的线性系统上逐行执行 GS:第一个 Newton 修正方程 ( 是 Newton 初值),每一步更新一个单元,显示用到的数值、新值、缺陷,以及与 LU 精确解的误差。
The player executes GS row by row on a real linear system, the first Newton correction system ( the Newton initial guess): each step updates one cell and shows the numbers used, the new value, the defect and the error against the exact LU solution.
最后一个面板把迭代跑到底:选 、看缺陷的历史、量出渐近收敛因子,并与 的全部特征值(复平面上的点)比较;它还画出最慢的误差模态。一阶时这个模态集中在出口 前的最后几个单元( 时最大值在单元 28,单元编号从 0 到 31)。这个位置由 决定,与扫描方向无关: 以对流为主(; 时 在 1.36 与 1.61 之间),所以慢模态向下游增长。反向 GS 扫描(从 扫到 )的谱半径与前向 GS 扫描相同(二者的特征值都是 与 0; 时算得的谱半径最多相差 5e−11,是舍入误差),最慢模态也在出口( 时在单元 29);顺流扫描只缩短暂态( 时 730 对 898 次扫描)。第 17 节再把 GS、GMRES(以 为预处理)和 LU 用在切线与伴随方程上比较。
The last panel runs the iteration to the end: choose , watch the history of the defect, measure the asymptotic convergence factor and compare it with all eigenvalues of (points in the complex plane); it also draws the slowest error mode. At first order this mode sits in the last cells before the outlet end (at its largest entry is in cell 28 of ). The location belongs to , not to the sweep: is convection-dominated (; at , lies between 1.36 and 1.61), so its slow modes grow downstream. A backward GS sweep (from to ) has the same spectral radius (both spectra consist of the squares and zeros; the computed spectral radii differ by at most 5e−11 at , which is rounding) and its slowest mode also sits at the outlet (cell 29 at ); sweeping with the flow only shortens the transient (730 against 898 sweeps at ). Section 17 compares GS, GMRES (with as the preconditioner) and LU on the tangent and adjoint systems.
目标函数:一个线性泛函
The objective: a linear functional
本节实验: 用的是精确单元平均和权重 。它等于 吗?如果不等, 时差多少?先预测,再看图 19。
Experiment: uses the exact cell averages and the weights . Is it equal to ? If not, how large is the difference at ? Predict, then look at Figure 19.
连续问题的目标是 (第 1 节)。本节把它变成单元平均的函数,写出状态梯度 (伴随方程的右端项是 ),并查清离散目标的误差从哪里来。
The objective of the continuous problem is (Section 1). This section turns it into a function of the cell averages, writes the state gradient (the right-hand side of the adjoint equation is ), and finds out where the error of the discrete objective comes from.
从积分到权重
From the integral to the weights
把积分拆到各个单元,并把权重在单元中心展开:。
Split the integral into the cells and expand the weight about the cell centre: .
离散目标保留第一个和,并用未知量代替精确平均:
The discrete objective keeps the first sum, with the unknowns in place of the exact averages:
它是线性的。状态梯度就是权重向量本身,与状态无关;它不显式依赖设计;二阶导数为零:
It is linear. Its state gradient is the weight vector itself, independent of the state; it does not depend on the design explicitly; its second derivative is zero:
所以伴随方程 的右端项 在每个状态、每个设计下都相同,变的只有 ;而且 对设计的导数只经过状态:。 时 从 线性增到 。
So the right-hand side of the adjoint equation is the same at every state and every design; only changes. And the derivative of with respect to the design goes through the state only: . At the weights grow linearly from to .
权重规则是精确的吗?
Is the weight rule exact?
丢掉的只有 ,即 关于各单元中心的一阶矩之和。对光滑的 ,Taylor 展开给出 ,求和后一般是二阶误差:
The only thing dropped is , the sum of the first moments of about the cell centres. For a smooth a Taylor expansion gives , and the sum is in general a second-order error:
对本页的 ,(周期函数),首项消失。实际上整个和都为零:对正弦函数可以精确积分(下式的 是单元 中心的相位),
For this page's , (it is periodic) and the leading term vanishes. In fact the whole sum vanishes: for the sine the moment integrates exactly (below, is the phase of the centre of cell ),
因为 个 次单位根之和为零。所以对每个 , 严格成立。实测: 到 4096 时 (舍入); 时方括号的值是 1.007e−3,矩之和 −1.9e−19。对这个构造解,离散目标没有求积误差。把恒等式 对 求导,得 :梯度也没有求积误差。
because the -th roots of unity add up to zero. So holds exactly for every . Measured: for to 4096 (rounding); at the bracket is 1.007e−3 and the sum of the moments −1.9e−19. For this manufactured solution the discrete objective has no quadrature error. Differentiating the identity with respect to gives : the gradient has no quadrature error either.
是这个构造解(周期)的性质,不是格式的性质。对非周期的解,权重规则丢掉 :,仍是二阶,不会拖累二阶格式。
is a property of this (periodic) manufactured solution, not of the scheme. For a non-periodic solution the weight rule drops : , still second order, so it would not spoil a second-order scheme either.
的误差全是状态误差
The error of is all state error
既然 ,离散目标的误差就只来自解的误差:
Since , the error of the discrete objective comes from the error of the solution alone:
默认设计下():, 在一阶、无限制器线性、光滑限制器下分别是 1.357470、1.408114、1.380649;无限制器线性的 ,与加权误差 相同。权重都为正,所以 是解的带符号误差的正权加权和。
At the defaults (): , and is 1.357470, 1.408114 and 1.380649 for first order, unlimited linear and the smooth limiter; for unlimited linear , the same as the weighted error . The weights are all positive, so is a positively weighted sum of the signed error of the solution.
加密下 的收敛于是就是状态误差的收敛: 在最后一次加倍时的观测阶,无限制器线性是 1.96,一阶是 0.99,光滑限制器是 0.98(第 4 节说明了为什么光滑限制器在细网格上是一阶)。独立的 Python 程序(solve.py)存储的目标值与本页 之差,在一阶与无限制器线性、 到 4096 上不超过 3.1e−12。
Under refinement the convergence of is therefore the convergence of the state error: the observed order of over the last doubling is 1.96 for unlimited linear, 0.99 for first order and 0.98 for the smooth limiter (Section 4 explains why the smooth limiter is first order on fine meshes). The objectives stored by the independent Python program (solve.py) differ from this page's by at most 3.1e−12 for first order and unlimited linear, to 4096.
在当前设计下自己量一遍( 到 256,在浏览器里求解):
Measure it yourself at the current design ( to 256, solved in the browser):
伴随方程的右端项就是 :分量全为负,绝对值随 线性增大(从 到 ),与状态无关。第 9、11 节用它求伴随 。
The right-hand side of the adjoint equation is : a negative vector whose magnitude grows linearly with , from to , and does not depend on the state. Sections 9 and 11 use it to compute the adjoint .
前向:沿着面循环传播切向量
Forward: the tangent through the face loop
本节实验:只给一个未知量一个单位的扰动 ,设计不动:残差的哪几行会变、各变多少?先用第 3 节的模板预测,再在本节的逐列播放器里验证。
Experiment: Seed one unknown, , and hold the design fixed: which rows of the residual change, and by how much? Predict from the stencil of Section 3, then check with the column player of this section.
第 5–7 节写出了残差代码 ,解出了 ,定义了目标 。本节用前向模式对残差代码求导:输入是未知量与设计的一个联合方向 ,输出是每个残差沿这个方向的导数,全程不形成矩阵。第 9 节把同一段代码倒过来运行。
Sections 5–7 built the residual code , solved and defined the objective . This section differentiates the residual code in forward mode: a joint direction of the unknowns and the design goes in, the derivative of every residual along that direction comes out, and no matrix is formed. Section 9 runs the same code backwards.
矩阵乘积可以写成方向导数
A matrix product as a directional derivative
先固定设计(),让所有未知量沿方向 一起变化:。向量 就是状态切向量 ,它规定每个未知量随 变化的快慢。对每个残差在 处关于 求导,得到残差切向量 :一次矩阵向量乘积,却不需要矩阵本身。
First hold the design fixed () and move all unknowns together along a direction : . The vector is the state tangent ; it says how fast each unknown changes with . Differentiating each residual with respect to at gives the residual tangent : a matrix–vector product, computed without the matrix.
再让设计也动起来,,链式法则多出设计 Jacobian 一项。自动微分的前向(切线)模式就是这样计算的:残差代码里的每个中间量 都配一个切向量 ,按 本身的计算顺序算出。
Let the design move as well, , and the chain rule adds the design Jacobian. The forward (tangent) mode of automatic differentiation computes exactly this: every intermediate of the residual code gets a tangent , computed in the same order as itself.
记号:上点 与上横线 是离散程序的前向导数与反向(伴随)导数。代码沿用 Tapenade 等自动微分工具的命名:前向子程序 residual_d 读入种子 u_d 、D_d ,返回 res_d ;第 9 节的 residual_b 读入 res_b ,返回 u_b 与 D_b 。
Notation: the dot and the bar are the forward and the reverse (adjoint) derivatives of the discrete program. The code uses the names of AD tools such as Tapenade: the forward routine residual_d takes the seeds u_d and D_d and returns res_d ; the residual_b of Section 9 takes res_b and returns u_b and D_b .
先看最小的一步:一个面的通量
The smallest step first: one face flux
第 3 节的通量有五个输入:两个迹、两个未重构状态和黏性,( 由网格决定,是常数)。对每个输入求偏导,乘以该输入的切向量,再相加:
The flux of Section 3 has five inputs: the two traces, the two raw states and the viscosity, ( is fixed by the mesh). Differentiate it with respect to each input, multiply by that input's tangent and add:
对流部分 与黏性部分 分别是第 3 节 、 的切向量。五个偏导数在第 3 节都已写出;最后一项值得单独说:黏性是设计变量,在通量中乘以差商 ,所以设计种子 直接作用在每一个面上,系数是 ; 和 没有这样的项,它们只通过两个边界面上的 进入通量。
The convective part and the viscous part are the tangents of the and of Section 3, where all five partial derivatives were written out. The last term deserves a word of its own: the viscosity is a design variable that multiplies the difference quotient in the flux, so a design seed acts directly on every face, with the factor ; and have no such term and reach the fluxes only through on the two boundary faces.
手算一个面:取默认收敛解(无限制器线性)的面 ,这里 ,,,。(i)只扰动未知量 :它在 里的权重是 1,在 里是 (斜率 含有它),在 里是 1、在 里是 0,所以 ,,,,,,这正是第 3 节图 8 里的元素 。(ii)只扰动黏性,:所有状态切向量为零,,这是这个面上 的显式路径。然后像 加到 、从 减去一样,把 加到 、从 减去。
Work one face by hand: face of the converged default solution (unlimited linear), where , , and . (i) Seed only the unknown : its weight is 1 in , in (the slope contains it), 1 in and 0 in , so , , , , and : the entry of Figure 8 in Section 3. (ii) Seed only the viscosity, : every state tangent vanishes and , the explicit -path of this face. Then is added to and subtracted from , exactly as is added to and subtracted from .
从状态切向量到通量切向量:一个面读哪些未知量
From state tangents to flux tangents: which unknowns a face reads
无限制器线性重构时,迹是未知量的固定线性组合(第 3 节),所以迹的切向量是状态切向量的同一组合。对两侧单元都被重构的内部面():
With the unlimited linear reconstruction the traces are fixed linear combinations of the unknowns (Section 3), so their tangents are the same combinations of the state tangents. For an interior face whose two cells are both reconstructed ():
四个系数就是第 3 节图 8 中矩阵 的第 行:面 4 上是 、、、。黏性部分给中间两个系数中左边的加 、右边的加 ,不扩大模板。在默认状态下,每个面 上都有 和 ,所以这两项都被加强;但这不是对每个状态都成立: 可以变正,这时 把它拉向零。只要 ,它的绝对值就变小( 超过 时已经变号;、 时面 6 上 );否则它变号且绝对值变大(同一状态的面 5 上 )。靠近边界时端单元不重构(),所以面 1 读 3 个未知量,面 0 只读 和边界值 (),右端对称。一阶时 ,一个面只读 。光滑限制器时 中的权重不再是 ,而是 ,其中 随状态变化(下表第 4 条语句; 同理);图 23 显示,在默认解的面 4 上,这些权重与 相差很远。
The four coefficients are row of the matrix of Figure 8 in Section 3: at face 4 they are , , and . The viscous part adds to the left and to the right central coefficient and widens nothing. At the default state and on every face , so both entries are strengthened; this does not hold for every state: can turn positive, and then pulls it towards zero. Its magnitude shrinks while (the sign already flips once exceeds ; face 6 for , : ); beyond that it changes sign and grows in magnitude (face 5 of the same state: ). Next to a boundary the end cells are not reconstructed (), so face 1 reads 3 unknowns, face 0 only and the boundary value (), and symmetrically at the right end. At first order and a face reads only . With the smooth limiter the weights in are no longer but with the state-dependent of statement 4 in the table below ( alike); Figure 23 shows that at face 4 of the default solution these weights are far from .
逐句求导:残差代码的前向语句
The residual code differentiated statement by statement
前向模式对第 5 节残差代码的每条赋值语句 (这里 是任意初等函数,不是设计的相位 )应用一次链式法则,,紧跟在原语句之后,按同样的顺序执行。下表对本页代码的每条语句都这样做,同时允许状态种子 与设计种子 (语句 5–9 在面循环 里):
Forward mode applies the chain rule once to every assignment of the residual code of Section 5 (here is any elementary function, not the phase of the design), , placed after it and executed in the same order. The table does it for every statement of this page's code, with a state seed and a design seed both allowed (statements 5–9 run inside the face loop ):
| # | 原始语句Primal statement | 前向语句(同一顺序)Tangent statement (same order) | 所用局部导数Local derivative used |
|---|---|---|---|
| 1 | — | ||
| 2 | , 同理, alike | ,( 相同), (the same for ) | |
| 3 | |||
| 4 | :一阶 ,无限制器 ,光滑限制器随状态变化(第 3 节): first order, unlimited, state-dependent for the smooth limiter (Section 3) | ||
| 5 | ,;面 0:, ; face 0: | ,;面 0:, ; face 0: | |
| 6 | ,;面 :, ; face : | ,;面 :, ; face : | |
| 7 | |||
| 8 | 1 | ||
| 9 | −1 | ||
| 10 | (见下式) (below) | ||
| 11 |
语句 10 的精确通量偏导数来自 ,其中 ,,:
The exact-flux partial derivatives of statement 10 come from , with , and :
设计进入代码的地方恰好有三处,每处是表中的一行:(i)边界值 (语句 2)依赖 和 ,作为面 0 的 和面 的 进入两个边界面;(ii)黏性通过 和显式项 进入每个面的通量(语句 7);(iii)精确通量 (语句 10)依赖全部三个分量,通过源项(语句 11)进入每一行。取 、,代码返回 ;取 、,返回 的第 列 。除了原始代码本来就有的数组,前向代码还多存斜率的切向量 (s_d)、精确通量的切向量 (Q_d)和输出 (以及两个数 、);局部导数()算出后立即使用,不必存下(存下 的是第 9 节的 residual_b)。
The design enters the code in exactly three places, each a row of the table: (i) the boundary data (statement 2) depend on and and enter the two boundary faces as of face 0 and of face ; (ii) the viscosity enters the flux of every face (statement 7), through and the explicit term ; (iii) the exact fluxes (statement 10) depend on all three components and enter every row through the source (statement 11). With and the code returns ; with and it returns the column . Besides the arrays the primal code already has, the tangent code keeps the slope tangents (s_d), the exact-flux tangents (Q_d) and the output (and the two numbers , ); the local derivatives () are used as soon as they are computed and need not be stored (it is the residual_b of Section 9 that keeps ).
为什么是同一顺序:一条前向语句需要它的输入的切向量,而这些切向量由前面原始语句的前向语句算出。所以前向代码可以和原始代码逐句交替、按原始顺序运行;必须倒着运行的是第 9 节的反向模式。
Why the same order: a tangent statement needs the tangents of its inputs, and those are computed by the tangent statements of the earlier primal statements. The tangent code can therefore run interleaved with the primal, statement by statement, in the primal's order; it is the reverse mode of Section 9 that must run backwards.
写成自动微分工具的代码(原始语句同时执行;a_d, phi_d, nu_d 是 的三个分量,xf[f] 是面的位置 ):
As code, in the names of an AD tool (the primal statements run alongside; a_d, phi_d, nu_d are the three components of , xf[f] is the face position ):
residual_d(U, u_d, D, D_d) -> res, res_d
1 res = 0; res_d = 0; s = 0; s_d = 0 (s[0] = s[N-1] = 0 stay)
2 uin = u*(0; D); uout = u*(1; D)
uin_d = -sin(2 pi phi)*a_d - 2 pi a cos(2 pi phi)*phi_d; uout_d = uin_d
for i = 1 .. N-2:
3 l = U[i] - U[i-1]; l_d = u_d[i] - u_d[i-1]
r = U[i+1] - U[i]; r_d = u_d[i+1] - u_d[i]
4 s[i] = sigma(l, r); s_d[i] = sl(l, r)*l_d + sr(l, r)*r_d
for f = 0 .. N:
5 if f == 0: qL = UL = uin; qL_d = UL_d = uin_d
else: qL = U[f-1] + s[f-1]/2; UL = U[f-1]; qL_d = u_d[f-1] + s_d[f-1]/2; UL_d = u_d[f-1]
6 if f == N: qR = UR = uout; qR_d = UR_d = uout_d
else: qR = U[f] - s[f]/2; UR = U[f]; qR_d = u_d[f] - s_d[f]/2; UR_d = u_d[f]
7 d = h/2 if f in (0, N) else h; cL = qL/2 + 1; cR = qR/2 - 1
H = (qL^2 + qR^2)/4 - (qR - qL) - nu*(UR - UL)/d
H_d = cL*qL_d + cR*qR_d + nu/d*(UL_d - UR_d) - nu_d*(UR - UL)/d
8 if f >= 1: res[f-1] += H; res_d[f-1] += H_d
9 if f <= N-1: res[f] -= H; res_d[f] -= H_d
for f = 0 .. N:
10 Q[f] = q*(xf[f]; D); Q_d[f] = dQda(xf[f])*a_d + dQdphi(xf[f])*phi_d + dQdnu(xf[f])*nu_d
for i = 0 .. N-1:
11 res[i] -= Q[i+1] - Q[i]; res_d[i] -= Q_d[i+1] - Q_d[i]播放器在当前网格上逐面运行这段代码。选一个种子:单位状态种子 、光滑的测试种子 ,或设计种子 ;逐条语句前进,看 一点点填满(虚线框是最终值 )。
The player runs this code on the current mesh, face by face. Choose a seed: a unit state seed , the smooth test seed , or a design seed ; step through the statements and watch fill up (dashed frames: the final values ).
A 的每一列是一次前向扫描
Every column of A is one tangent sweep
取种子 ,代码返回 ,即 Jacobian 的第 列:所有残差对一个未知量的导数。这次扫描碰到的面是模板含 的那些面(一阶 2 个;二阶时内部 4 个,靠近边界 3 个),每个面写两行,所以这一列至多有 3 个(一阶)或 5 个(二阶)非零元: 的带宽来自一个面的模板。用 做 次扫描得到整个 ,再用 做三次得到 。播放器在当前网格上逐列演示。
With the seed the code returns , column of the Jacobian: the derivatives of all residuals with respect to one unknown. The faces it touches are those whose stencil contains (two at first order; four at second order, three next to a boundary), and each touched face writes two rows, so the column has at most 3 (first order) or 5 (second order) nonzero entries: the bandwidth of comes from the stencil of one face. sweeps with the seeds build all of ; three more with build . The player steps through these sweeps on the current mesh.
用本页的数字看一个面
One face with the page's own numbers
取默认网格()、设计种子 (振幅 ), 取第 10 节这个方向的切线解(),看内部面 (,单元 3 与 4 之间)和边界面 ()。每种重构有自己的收敛解和自己的 ,所以连原始值也不同:
Take the default mesh (), the design seed (the amplitude ) and for the solution of the tangent solve of Section 10 for that direction (), and look at the interior face (, between cells 3 and 4) and the boundary face (). Each reconstruction has its own converged solution and its own , so even the primal values differ:
| 量Quantity | 公式Formula | 一阶first order | 无限制器线性unlimited linear | 光滑限制器smooth limiter |
|---|---|---|---|---|
| 0.950958 | 1.012899 | 0.994602 | ||
| 0.823170 | 0.988274 | 0.949153 | ||
| 0.95096, 0.82317 | 1.06834, 0.91706 | 1.04010, 0.87288 | ||
| 1.4755, −0.5884 | 1.5064, −0.5059 | 1.4973, −0.5254 | ||
| 0.533493 | 0.537389 | 0.531358 | ||
| −0.306762 | 0.053283 | −0.077448 | ||
| −0.975747 | −0.097658 | −0.389863 | ||
| −0.30676, −0.97575 | 0.35548, −0.47868 | 0.18370, −0.78519 | ||
| 0.175042 | 0.196401 | 0.166392 | ||
| 0.518889 | 0.518889 | 0.518889 | ||
| −0.343846 | −0.322488 | −0.352497 |
| 量Quantity | 公式Formula | 一阶first order | 无限制器线性unlimited linear | 光滑限制器smooth limiter |
|---|---|---|---|---|
| 0.914844 | 0.914844 | 0.914844 | ||
| 1.030680 | 1.024651 | 1.033996 | ||
| 0.1600 | 0.1600 | 0.1600 | ||
| 1.4574, −0.4847 | 1.4574, −0.4877 | 1.4574, −0.4830 | ||
| 0.340441 | 0.344337 | 0.338305 | ||
| −0.425779 | −0.425779 | −0.425779 | ||
| 0.157533 | 0.123822 | 0.171393 | ||
| −0.790220 | −0.768861 | −0.798871 | ||
| −0.446374 | −0.446374 | −0.446374 | ||
| −0.343846 | −0.322488 | −0.352497 |
在面 0 上,左侧输入是边界值:对每种重构都有 ( 与格式无关),而 是内部值的两倍。
On face 0 the left inputs are the boundary data: for every reconstruction ( does not depend on the scheme), and is twice the interior value.
所有面的 还给出一个检验。第 5 节的残差是面值之差,,它的切向量也是。当 解出切线方程时,每个单元都有 ,所以 在每个面上是同一个数:通量切向量跨过单元 的跳跃恰好等于源项切向量 ,即设计注入这个单元的量的切向量。这里源项在每个单元都依赖 、、,所以 每跨过一个单元都会变,但它与 之差不变(图 24):无限制器线性重构时,在全部 9 个面上 ()、()、(),三种重构、三个方向的最大偏差是 。原始解有同样的结构(第 5 节):收敛时每个面上的 是同一个常数,无限制器线性重构时为 (一阶 ,光滑限制器 ),离散通量与精确通量只差这一个常数。
The of all faces make one more check. The residual of Section 5 is a difference of face values, , and so is its tangent. When solves the tangent equation, in every cell, so is the same number on every face: the flux tangent jumps across cell by exactly the source tangent , the tangent of what the design injects into that cell. Here the source depends on , and in every cell, so changes across every cell, but its difference from does not (Figure 24): for the unlimited linear reconstruction (), () and () on all 9 faces, and the largest spread over the three reconstructions and the three directions is . The primal has the same structure (Section 5): at the converged state is one constant on every face, for the unlimited linear reconstruction ( first order, smooth limiter), the one constant by which the discrete flux misses the exact flux.
前向导数与有限差分
The tangent against finite differences
前向导数精确到舍入误差;残差的差商则不然。步长为 时的单侧差商与中心差商是
The tangent is exact up to rounding; a finite-difference quotient of the residual is not. The forward and the central quotient with a step are
的截断误差是 , 的是 ;两者都还有约 的舍入误差( 是机器精度,不是第 3 节限制器的常数 ),其中 (这里约 ;这个 是量级,不是附录 A 的配点数)是残差中相加各项的大小;即使 本身为零(收敛状态正是如此),它也不会变小。所以误差随 的双对数图一般呈 V 形:右支是截断,左支是舍入,没有一个 能达到前向导数的精度。
has a truncation error , one of , and both carry a rounding error of about ( is the machine epsilon, not the limiter constant of Section 3), where (about here; this is a size, not the number of collocation points of Appendix A) is the size of the terms summed into a residual; it does not shrink when itself is zero, as at the converged state. So the error against is in general a V in a log-log plot: truncation on the right, rounding on the left, and no reaches the tangent's accuracy.
本页的残差有一个特殊结构,实验会显示出来。一阶或无限制器线性重构时,通量是迹的二次式,迹是 的线性式, 是 的线性式、 是 的二次式, 只以 和 的形式出现:沿 、、 中的任何方向,残差都是 的二次多项式。于是 根本没有截断误差(误差全是舍入,随 增大而减小);只动 时残差甚至是线性的,两种差商都没有截断误差。只有含 的方向(正弦、余弦),或者光滑限制器时改变斜率的状态方向(任何非常数的 ;限制器是 的有理函数),才让两种差商都出现完整的 V 形;只动 或 时,光滑限制器的残差仍是二次或线性的,因为斜率不依赖设计。实验在当前状态上对 到 计算两种差商,并说明看到的是哪种形状。第 13 节在整个求解上使用这类差商,第 14 节用复数步长去掉舍入那一支。
The residual of this page has a special structure, which the lab shows. With the first-order or the unlimited linear reconstruction the flux is quadratic in the traces, the traces are linear in , is linear and quadratic in , and enters only as and : along any direction in , and the residual is a polynomial of degree 2 in . The central quotient then has no truncation error at all (its error is pure rounding, falling as grows), and along alone the residual is even linear, so neither quotient has one. Only a direction containing (sines and cosines), or, with the smooth limiter, a state direction that changes the slopes (any non-constant : the limiter is a rational function of ), gives both quotients the full V; along or alone even the smooth-limiter residual stays quadratic or linear, because the slopes do not see the design. The lab evaluates both quotients for from to on the current state and says which shape it sees. Section 13 uses such quotients on the whole solve, and Section 14 removes the rounding branch with the complex step.
局部导数怎样组成全局 Jacobian
How local derivatives form the global Jacobian
网格固定时,令 收集面 的通量的五个输入。它们来自这个面读的未知量(集合 :二阶时内部面 读单元 ,面 1 与面 读 3 个单元,面 0 与面 只读 1 个;一阶时读 中存在的单元)和设计。记 、 为 对这些未知量和对 的导数, 为五个通量导数组成的行, 为面与单元的有向关联矩阵(,:面把通量加给左边的单元、从右边的单元减去)。源项行 用的是同一个关联矩阵,,所以 。
At a fixed mesh, let collect the five inputs of the flux of face . They come from the unknowns the face reads (the set : at second order the cells for , three cells for and , one cell for and ; at first order the cells that exist) and from the design. Let and be the derivatives of with respect to those unknowns and to , the row of the five flux derivatives, and the oriented incidence of faces and cells (, : a face adds its flux to the cell on its left and subtracts it from the cell on its right). The source rows use the same incidence, , so .
里没有 一项:本页的源项只依赖设计。 在内部面上只有一行非零,即 那一行 ;面 0 上 与 两行是 ,面 上 与 两行是 。所以内部面上 。设计的三条路径就是三项:面 0 上的 (面 上的 ),每个面上的 乘 ,以及每一行的 。图 25 画出 的和式中的一项。
There is no term in : the source of this page depends on the design only. has one nonzero row on an interior face, the -row ; on face 0 the rows of and hold , on face those of and hold . So on an interior face. The three paths of the design are three terms: on face 0 (and on face ), times on every face, and in every row. Figure 25 draws one term of the sum for .
从程序到前向导数:逐条线性化面循环的四条语句
From program to tangent: linearise the four statements of the face loop
上面的 、、 来自一段短程序:残差代码在每个面上执行四条语句。把这个面读的未知量排成 :
The , and above come from one short program: the residual code runs four statements on every face. Stack the unknowns the face reads as :
(1)取值: 的每一行只有一个 1,从全局数组中取出 的一个未知量。(2)重构:斜率、迹 、未重构状态 和黏性,即通量的全部五个输入(边界面上用 或 )。(3)通量。(4)分发: 的第 列,第 行为 ,第 行为 。前向模式对每条语句用一次链式法则,每个中间变量都有自己的切向量:
(1) Gather: each row of holds a single 1 and picks one unknown of out of the global array. (2) Reconstruct: the slopes, the traces , the raw states and the viscosity, all five inputs of the flux (with or on the boundary faces). (3) Flux. (4) Scatter: column of , in row and in row . Forward mode applies the chain rule once per statement, and every intermediate variable gets a tangent of its own:
取值和分发是线性的:它们的切向量就是把同样的取值和分发作用在切向量上。只有重构和通量带来依赖当前状态的局部导数( 总是如此, 只在光滑限制器时如此)。把(1)代入(2)、(2)代入(3)、(3)代入(4),一个面对 的贡献就是四个初等 Jacobian 的乘积,按程序运行的顺序从右向左作用:
Gather and scatter are linear: their tangents are the same gather and scatter applied to the tangents. Only reconstruct and flux bring local derivatives that depend on the current state ( with the smooth limiter, always). Substituting (1) into (2), (2) into (3) and (3) into (4) gives one face's contribution to as a product of four elementary Jacobians, applied right to left in the order the program runs:
算例:面 4 的前向扫描。设计种子取 (相位 ),未知量取同一方向的切线解 。 不直接进入内部面(),所以 完全来自状态切向量。数字(默认设计,无限制器线性):
A worked example: the forward sweep of face 4. Seed the design with (the phase ) and the unknowns with the tangent solution of the same direction, . The phase does not enter an interior face directly (), so comes entirely from the state tangents. The numbers (default design, unlimited linear):
| 语句Statement | 量Quantity | 原始值Primal value | 切向量Tangent |
|---|---|---|---|
| (1)取值(1) gather | 1.13883 | 0.02123 | |
| 1.06834 | 0.91116 | ||
| 0.91706 | 1.24442 | ||
| 0.78348 | 0.75421 | ||
| 设计种子design seed | 0.07 | 1 | |
| (2)重构(2) reconstruct | 1.01290, 0.98827 | 1.21696, 1.28366 | |
| 1.06834, 0.91706 | 0.91116, 1.24442 | ||
| 0.01 | 0 | ||
| (3)通量(3) flux | 0.53739 | 1.15727 | |
| (4)分发(4) scatter | 0.53739 | 1.15727 | |
| −0.53739 | −1.15727 |
对所有面和源项行求和,这些贡献给出 ,这就是切线方程。若取 (),这个面还会多出 带来的 一项。下面的播放器对任意面、任意方向重放这四步。
Summed over all faces and the source rows, these contributions give : the tangent equation. With () this face would get the extra term from . The player below replays the four steps for any face and any direction.
前向求解用它做什么
What the tangent solve does with it
一次 residual_d 扫描是乘积,不是求解。第 10 节用它解切线方程,求出使 沿设计方向保持成立的状态切向量 :取种子 ,解 ,每个设计分量解一次;目标沿这个方向的导数是 。上面面表里的 就是这样的解,所以每个 都为零(上面检验的九个种子中最大的 是 )。
A sweep of residual_d is a product, not a solve. Section 10 uses it to solve the tangent equation for the state tangent that keeps along a design direction: with the seed it solves , one solve per design component; the derivative of the objective along that direction is . The of the face tables above was such a solution: that is why every vanished (the largest over the nine seeds of the check above is ).
伴随:把同一个面循环倒过来
Reverse: the adjoint of the same face loop
本节实验:给一个残差一个单位权重 ,运行反向代码后,哪些未知量会收到贡献?三个设计分量里哪些会?先从前向模板预测,再用反向播放器(或后面的“列与行”实验)检查。
Experiment: Put a unit weight on one residual, , and run the reverse code: which unknowns receive a contribution, and which of the three design components? Predict from the forward stencil, then check with the reverse player (or the row-and-column lab further down).
第 8 节把一个方向 沿残差代码向前推,得到 。本节把一个权重向量 沿同一段代码向后拉,得到 和 :这是反向(伴随)模式。取 (第 11 节伴随方程的解)时,一次扫描得到的 就是整个梯度 。
Section 8 pushed a direction forward through the residual code and obtained . This section pulls a weight vector backwards through the same code and obtains and : the reverse (adjoint) mode. With , the solution of the adjoint equation of Section 11, one sweep gives the whole gradient as .
伴随为什么存在:转置由内积定义
Why the adjoint exists: the transpose is defined by a dot product
线性映射 的转置 就是把它从内积的一边移到另一边的那个映射:对所有 和 ,
A linear map has a transpose , the map that moves it from one side of a dot product to the other: for all and ,
把这个定义用到第 8 节的线性映射 上。这个映射的转置把残差上的权重 映成输入上的权重 、,定义式就成了自动微分的对偶恒等式:
Apply this to the linear map of Section 8, . Its transpose maps a weight on the residuals to weights on the inputs, and , and the definition becomes the duality identity of AD:
是加权输出 对 的灵敏度。反向代码逐句转置程序来计算 和 ,不形成 或 ;它的代价是一次残差计算的一个不大的常数倍,与输入的个数无关(在本页的实现中实测约为 1 倍;后面“解出来的伴随,以及它省下的代价”一段末尾的计时实验会在你的浏览器里测量)。这个恒等式就是它的检验(后面的点积检验)。
is the sensitivity of the weighted output to . The reverse code computes and by transposing the program statement by statement, without forming or ; its cost is a small constant multiple of one residual evaluation, independent of the number of inputs (about one for this page's implementation, as measured; the cost lab at the end of “The solved adjoint, and what it saves” measures it in your browser). The identity is its test (the dot test further down).
先转置一个面的局部矩阵
First transpose the local matrix of one face
先不管重构。一个内部面有五个输入、两个输出:它对残差切向量的贡献 只落在第 、 两行,第二行是第一行的相反数(右边单元收到 )。这个贡献不是完整的 :相邻的面也往这两行写。把这个 局部矩阵的转置作用在两行的权重上,直接得到反向语句:先把两个种子相减,再乘以五个通量导数:
Leave the reconstruction aside for a moment. One interior face has five inputs and two outputs: its contribution to the residual tangent falls on the rows and only, the second the negative of the first (the right cell receives ). It is not the whole of : the neighbouring faces write into the same rows. The transpose of this local matrix, applied to the weights of the two rows, gives the reverse statements directly: subtract the two seeds, then multiply by the five flux derivatives:
取默认解的面 4,两行的权重用第 11 节伴随解的值 、,于是 ,,,,,。配上第 8 节手算的切向量(种子 :,,,,),恒等式两边在这个面上相等(左边只是面 4 对第 3、4 行的贡献;完整的 还含面 3、面 5 的贡献):
Take face 4 of the default solution and, as the weights of its two rows, the values of the adjoint solution of Section 11, and : then , , , , and . With the hand-worked tangents of Section 8 (seed : , , , , ) both sides of the identity agree on this face (the left side is face 4's share of rows 3 and 4 only; the full also contain the contributions of faces 3 and 5):
逐句倒推:残差代码的反向语句
The residual code reversed statement by statement
下表对第 8 节表中的每条语句写出它的伴随语句 ,从最后一条原始语句倒回第一条;在面循环里,面按 倒序处理,每个面里的语句也倒着执行。种子是 ,其余伴随量(包括每个面、每个单元上的局部伴随 )都从零开始:
The table lists, for every statement of the table of Section 8, its adjoint statements , from the last primal statement back to the first; inside the face loop the faces run from down to 0 and the statements of each face also run backwards. The seed is ; every other adjoint (the local ones of each face and cell, , included) starts at zero:
| # | 原始语句Primal statement | 伴随语句(倒序执行)Adjoint statements (run in reverse order) | 写入的伴随量Adjoints written |
|---|---|---|---|
| 11 | ,(), () | ||
| 10 | () () | ||
| 9 | ;(面循环 ); (face loop ) | ||
| 8 | |||
| 7 | ,,,,, , , , | ||
| 6 | ,;面 :, ; face : | ||
| 5 | ,;面 0:, ; face 0: | ||
| 4 | ,(,在面循环之后), (, after the face loop) | ||
| 3 | ,,, , | ||
| 2 | ,, | ||
| 1 | 无: 是种子(扫描前 ,)nothing: is the seed (, before the sweep) | — |
有三点值得注意。(i)分发的伴随是取值:语句 8、9 把一个数写进两行,它们的伴随 把两行读回一个数。(ii)每个输入的伴随都用 += 累加: 被几个面读(还经过斜率), 被两个面读,设计被每个面和每一行读;链式法则对每次使用给出一项(见下图)。(iii)源项行也由同一个取值倒转:(边界面上取 ),所以源项给设计伴随的贡献是 。语句 2 用了 ( 以 1 为周期)。反向语句要读的原始值()由收敛解 重算(或在一次前向扫描中存下)。
Three things to notice. (i) The adjoint of a scatter is a gather: statements 8 and 9 wrote one number into two rows, and their adjoints read two rows back into one number. (ii) Every input adjoint accumulates with +=: is read by several faces (and through the slopes), by two faces, the design by every face and every row; the chain rule gives one term per use (the figure below). (iii) The source rows are reversed by the same gather: (with on the boundary faces), so the source contributes to the design adjoint. Statement 2 uses ( has period 1). The primal values the reverse statements read () are recomputed from the converged solution (or stored by a forward sweep).
写成自动微分工具生成的 residual_b:
As the residual_b an AD tool would generate:
residual_b(U, D, res_b) -> u_b, D_b # D_b = (a_b, phi_b, nu_b)
(the primal statements 2-4 run first: uin, uout, s[i] and the slope partials sl[i], sr[i])
0 u_b = 0; D_b = 0; s_b = 0; Q_b = 0; uin_b = 0; uout_b = 0
for i = N-1 .. 0:
11' Q_b[i+1] += -res_b[i]; Q_b[i] += res_b[i]
for f = N .. 0:
10' a_b += dQda(xf[f])*Q_b[f]; phi_b += dQdphi(xf[f])*Q_b[f]; nu_b += dQdnu(xf[f])*Q_b[f]
for f = N .. 0:
(recompute qL, qR, UL, UR, d, cL, cR of face f: statements 5-7 of the primal)
H_b = 0
9' if f <= N-1: H_b -= res_b[f]
8' if f >= 1: H_b += res_b[f-1]
7' qL_b = cL*H_b; qR_b = cR*H_b; UL_b = nu/d*H_b; UR_b = -nu/d*H_b
nu_b += -(UR - UL)/d*H_b
6' if f == N: uout_b += qR_b + UR_b
else: u_b[f] += qR_b + UR_b; s_b[f] += -qR_b/2
5' if f == 0: uin_b += qL_b + UL_b
else: u_b[f-1] += qL_b + UL_b; s_b[f-1] += qL_b/2
for i = N-2 .. 1:
4' l_b = sl[i]*s_b[i]; r_b = sr[i]*s_b[i]
3' u_b[i] += l_b - r_b; u_b[i-1] += -l_b; u_b[i+1] += r_b
2' a_b += -sin(2 pi phi)*(uin_b + uout_b)
phi_b += -2 pi a cos(2 pi phi)*(uin_b + uout_b)为什么用 +=:程序的扇出就是反向代码的扇入
Why +=: a fan-out of the program is a fan-in of the reverse code
二阶时,内部的未知量 ()被四个面读:作为面 的右状态和面 的左状态,此外还通过斜率 被面 和 读(共 6 个迹、2 个未重构状态);靠近边界的 被三个面读(端单元不重构);一阶时每个 被两个面读。链式法则对每次使用给出一项,所以 是一个和;每条反向语句必须把自己的项加上去(+=),而不是覆盖前面的项:
At second order an interior unknown () is read by four faces: as the right state of face and the left state of face , and through the slopes by the faces and as well (six traces and two raw states in all); next to a boundary are read by three faces (the end cells are not reconstructed); at first order every is read by two faces. The chain rule gives one term per use, so is a sum, and every reverse statement must add its term (+=) instead of overwriting the earlier ones:
取种子 时这个和是 (第 11 节),所以扇入有已知的总和:在默认解的单元 4 上,四个面加上 、、、,和为 (图 27)。如果用覆盖代替累加,只会留下最后一个面的项。下面的点积检验实验可以植入这个错误。
With the seed the sum is (Section 11), so the fan-in has a known total: at cell 4 of the default solution the four faces add , , , , which sum to (Figure 27). Overwriting instead of adding keeps only the last face's term: the dot-test lab below lets you plant this bug.
分发的伴随是取值,取值的伴随是分发
The adjoint of a scatter is a gather (and of a gather, a scatter)
这是最该记住的一幅图。前向面循环读入这个面的未知量(取值),把通量写进两行残差(分发)。反向面循环读入两个残差权重(取值,即分发的转置),把这个面的伴随写回它读过的未知量的位置(带 += 的分发,即取值的转置)。循环还是同一个循环,只是两端交换了角色:
This is the picture to remember. The forward face loop reads the unknowns of the face (gather) and writes its flux into two residual rows (scatter). The reverse face loop reads two residual weights (a gather: the transposed scatter) and writes the face's adjoints back into the places of the unknowns it read (a scatter with +=: the transposed gather). It is the same loop; its two ends exchange their roles:
面与面之间互不依赖:每个面读固定的状态和种子,加到一个单独的输出数组里。所以反向循环中各个面的先后顺序可以任意(点积实验提供正序 这一选项,检验照样通过)。必须倒过来的是相互依赖的语句的顺序:分发在通量之前,通量在重构之前;斜率语句在原始代码中位于面循环之前,在反向代码中必须位于面循环之后,等读 的两个面都把贡献加进 之后再倒转。
The faces do not depend on each other: each face reads the fixed state and the seed and adds into a separate output array. The face order of the reverse loop is therefore free (the dot-test lab offers the forward order , and the test still passes); what must be reversed is the order of the statements that depend on each other: the scatter before the flux, the flux before the reconstruction; and the slope statements, which run before the face loop in the primal, run after it in reverse, once both faces that read have added their parts to .
反向播放器在当前网格上运行 residual_b。选种子(单位行 、目标的权重 、伴随解 或随机向量),从源项行开始,经过面 ,到斜率和边界值;柱条显示 和 的累加,刚更新的分量加框,虚线框是最终值 、。
The reverse player runs residual_b on the current mesh. Choose the seed (a unit row , the weights of the objective, the adjoint solution , or a random vector) and step from the source rows through the faces to the slopes and the boundary data; the bars show and accumulating, the entries just updated framed, the final values and dashed.
用本页的数字反向走同样的面
The same faces in reverse, with the page's numbers
继续看第 8 节的面 4 和面 0,现在以第 11 节伴随方程的解为种子,。反向语句先把两个种子汇集成 ,再乘以同样的五个偏导数;最后两行把它们与第 8 节面表的切向量(种子 )配对:
Continue with the faces 4 and 0 of Section 8, now seeded with the solution of the adjoint equation of Section 11, . The reverse statements first gather the two seeds into , then multiply by the same five partial derivatives; the last two rows pair them with the tangents of the face table of Section 8 (seed ):
| 量Quantity | 公式Formula | 一阶first order | 无限制器线性unlimited linear | 光滑限制器smooth limiter |
|---|---|---|---|---|
| −0.154553 | −0.190320 | −0.112775 | ||
| −0.228040 | −0.286707 | −0.168858 | ||
| 0.090942 | 0.096276 | 0.059255 | ||
| −0.012364 | −0.015226 | −0.009022 | ||
| −0.158000 | −0.230334 | −0.150869 | ||
| 输出端output side | −0.027053385 | −0.037378968 | −0.018764767 | |
| 输入端input side | −0.027053385 | −0.037378968 | −0.018764767 |
| 量Quantity | 公式Formula | 一阶first order | 无限制器线性unlimited linear | 光滑限制器smooth limiter |
|---|---|---|---|---|
| 0.926060 | 0.924254 | 0.959257 | ||
| 1.497830 | 1.494909 | 1.551524 | ||
| −0.596994 | −0.598616 | −0.616804 | ||
| −1.716338 | −1.623825 | −1.828763 | ||
| 输出端output side | −0.731791340 | −0.710623290 | −0.766322900 | |
| 输入端input side | −0.731791340 | −0.710623290 | −0.766322900 |
面 0 的左边没有单元:,左侧输入的伴随归入边界值,。每个面上输出端与输入端在舍入误差内相等,这就是局部对偶。对整个代码求和时它有了意义: 解出切线方程,所以 ; 解出伴随方程,所以 。恒等式变成 ,即 ,也就是说,伴随给出的梯度分量等于前向给出的方向导数。无限制器线性时两者都是 (一阶 ,光滑限制器 );三种重构、三个分量上 与 相差不超过 。
Face 0 has no cell on its left: , and the adjoints of the left inputs go to the boundary value, . On each face the output and the input side agree to rounding: the local duality. Summed over the whole code it carries meaning: solves the tangent equation, so ; solves the adjoint equation, so . The identity then reads , that is : the gradient component from the adjoint equals the directional derivative from the tangent. For the unlimited linear reconstruction both are (first order , smooth limiter ); over the three reconstructions and the three components and differ by at most .
点积检验
The dot test
对偶恒等式也是检验反向代码的标准方法:取随机种子 和 ,用前向代码算 ,用反向代码算 ;两边必须在舍入误差内相等,而反向语句中一处错误的转置会让两边相差远大于舍入误差。种子要随机:例如取 时, 里的错误根本看不见。图 29 用一组固定的(非随机)种子把它拆成三个层次:残差层 ;面层 (因为 );输入层 。逐项不同,总和在舍入误差内相同。
The duality identity is also the standard test of a reverse code: take random seeds and , run the forward code for and the reverse code for ; the two sides must agree to rounding, and one wrong transpose in the reverse statements makes them differ far beyond rounding. The seeds must be random: with , for instance, an error in would be invisible. Figure 29 splits it into three levels with deterministic seeds: the residual level ; the face level (because ); and the input level . Term by term the levels differ; the totals agree to rounding.
实验在当前网格、重构和设计上用随机种子重复它,还可以植入手写伴随代码里常见的错误。在默认设计下,对实验的全部网格与种子( 到 、三种重构、每种 20 组种子),正确代码两边的相对差至多为 ;植入错误后,两边的相对差在 到 之间(最小的是漏掉 :它只影响一个设计分量)。实验也说明为什么正序运行面循环不是错误,以及一阶时漏掉斜率的倒转为什么不会被发现()。
The lab repeats it with random seeds on the current mesh, reconstruction and design. It also lets you plant one of the bugs a hand-written adjoint typically has. At the default design, over the lab's meshes and seeds ( to , the three reconstructions, 20 seed sets each) the two sides of the correct code differ by at most (relative), those of the planted bugs by between and (the smallest is the forgotten , which affects one design component only). The lab also shows why running the faces in forward order is not a bug, and why forgetting the slope reversal goes unnoticed at first order ().
点积检验只能说明反向代码是前向代码的转置,不能说明二者都是残差的导数:如果两者漏掉同一项(比如斜率),检验照样通过。第 8 节中前向导数与差商的对比,以及第 14 节的复数步长,都把前向代码与残差本身对照。
The dot test checks that the reverse code is the transpose of the forward code, not that either is the derivative of the residual: if both forget the same term (say, the slopes) the test still passes. The comparison with finite differences in Section 8 and the complex step of Section 14 check the forward code against the residual itself.
一列与一行:同一个矩阵的两种读法
A column and a row: one matrix read two ways
取种子 ,一次前向扫描返回 的第 列(第 8 节);取种子 ,一次反向扫描返回 ,即 的第 行,以及 ,即 的第 行。 次前向扫描或 次反向扫描得到同一个矩阵,一个按列读,一个按行读。实验逐步做两种扫描并逐元素比较。前向模式适合输入少、输出多的情形,反向模式适合输入多、输出少的情形:一个目标、许多设计变量。
With the seed one forward sweep returns column of (Section 8); with the seed one reverse sweep returns , row of , and , row of . forward sweeps or reverse sweeps build the same matrix, read by columns or by rows. The lab steps through both and compares them entry by entry. Forward mode suits few inputs and many outputs, reverse mode many inputs and few outputs: one objective and many design variables.
解出来的伴随,以及它省下的代价
The solved adjoint, and what it saves
目标沿设计方向的导数是 ,其中 (第 8 节)。写成 ,再把逆矩阵移到内积的另一边:,其中 ,也就是
The derivative of the objective along a design direction is with (Section 8). Write and move the inverse to the other side of the dot product: with , that is
( 不直接依赖设计,所以梯度里没有 一项。)反向代码提供这个乘积:以 为种子,它返回 (在解处等于 ,可作检验:三种重构的最大偏差是 )和 ,即梯度行的转置:一次扫描得到全部三个分量。前向代码每个设计分量需要一次扫描(以及一次求解 ,第 10 节);反向代码不论有多少设计变量,只需一次伴随求解和一次扫描。这里有 3 个分量,所以是 3 比 1;对于有几百个设计变量的外形设计,就是几百比 1。第 11 节解伴随方程,第 12 节按设计进入残差的位置拆分梯度。下面的实验在你的浏览器里测量一次扫描相当于几次残差计算。
( has no direct design dependence, so the gradient has no term.) The reverse code supplies the product: seeded with it returns (equal to at the solution, a check: the largest deviation over the three reconstructions is ) and , the transpose of the gradient row: all three components from one sweep. The forward code needs one sweep per design component (and one solve each, Section 10); the reverse code needs one adjoint solve and one sweep, whatever the number of design variables. Here there are 3 components, so the saving is 3 to 1; for a shape with hundreds of design variables it is hundreds to one. Section 11 solves the adjoint equation, and Section 12 splits the gradient by the places where the design enters the residual. The lab below measures, in your browser, how many residual evaluations one sweep costs.
从前向到伴随:转置让语句倒着执行
From tangent to adjoint: the transpose runs the statements backwards
先说明反向累加为什么给出矩阵的转置:展平之后,下面只是交换两个有限和( 同理):
First, why reverse accumulation gives the matrix transpose: after flattening, the following merely exchanges two finite sums ( alike):
一个面的前向导数是四个初等 Jacobian 的乘积(第 8 节)。乘积的转置是各转置按相反顺序相乘:
The tangent of one face is the product of four elementary Jacobians (Section 8). The transpose of a product is the product of the transposes in the reverse order:
从右往左读,就是反向扫描的顺序:程序最后执行的分发最先倒转,最先执行的取值最后倒转。把所有伴随置零,以 为种子,对每个面执行:
Read from right to left, this is the order of the reverse sweep: the scatter, which the program ran last, comes first, and the gather, which it ran first, comes last. Zero every adjoint, seed with , and run for every face:
源项行 也按同样的规则倒转:,(没有状态项:)。对偶恒等式逐级成立:每一步用一次转置的定义,把一个初等 Jacobian 移到内积的另一边,所以每一级的内积都相同:
The source rows are reversed by the same rule: , (no state term: ). The duality identity holds stage by stage: each step uses the definition of the transpose once to move one elementary Jacobian across the dot product, so the inner product is the same at every stage:
对所有面求和,再加上源项一对 ,右边正好累加成反向扫描的输出:。
Summed over the faces, with the source pair added, the right side accumulates exactly into the outputs of the reverse sweep: .
算例:面 4 的反向扫描,种子 ,与第 8 节末尾的前向算例(种子 ,相位)配对。最后一列是每一级的内积;第一级 只用面 4 对第 3、4 行的贡献 ,而不是完整的 (后者在这个种子下为零):
A worked example: the reverse sweep of face 4 with the seed , paired with the forward example at the end of Section 8 (seed , the phase). The last column is the inner product at each stage; the first stage, , uses face 4's contributions to rows 3 and 4 only, not the full (which vanish for this seed):
| 语句Statement | 量Quantity | 伴随值Adjoint | 内积Inner product |
|---|---|---|---|
| (0)置零(0) zero | 0 | ||
| (4′)种子(4′) seed | −1.01778, −0.82746 | −0.2202519868 | |
| (4′)取值(4′) gather | −0.190320 | −0.2202519868 | |
| (3′)通量的转置(3′) flux, transposed | −0.286707, 0.096276 | −0.2202519868 | |
| −0.015226, 0.015226, −0.230334 | |||
| (2′)(1′)加回(2′)(1′) add back | 0.071677, −0.277863 | −0.2202519868 | |
| 0.039824, −0.024069 | |||
| 0 |
四个内积在舍入误差内相等(最大相对差 ),对偶在这个面上逐级成立。面 4 不直接依赖 ,所以它对 的贡献是 0; 只经过边界面和源项行进入 。这个面的 虽然不为零,但种子 的 ,所以 不进入这里的内积。下面的播放器对任意面重放这四步,并在每一步显示内积。
The four inner products agree to rounding (largest relative difference ): the duality holds stage by stage on this face. Face 4 does not depend on directly, so its contribution to is 0; the phase reaches only through the boundary faces and the source rows. The of this face is not zero, but the seed has , so it does not enter these inner products. The player below replays the four steps for any face and shows the inner product at each.
前向求解:解随设计怎样变化
The tangent solve: how the solution moves with the design
本节实验:振幅 增加 时, 网格上的每个 变化多少? 又变化多少?先猜哪几个单元变化最大,再读图 30,并用本节的前向求解面板核对。
Experiment: If the amplitude grows by , how much does each of the mesh move, and how much does move? Guess which cells move most, then read Figure 30 and check the guess in this section's tangent-solve panel.
第 8 节逐条语句地对残差代码求导:给定种子 与 ,前向代码返回 。它算的是一个乘积,还没有回答“设计变了,解怎样变”。本节用同一段代码来回答这个问题:设计沿 移动时,什么样的 能让残差一直保持为零?
Section 8 differentiated the residual code statement by statement: for seeds and the tangent code returns . That is a product; it does not yet say how the solution moves when the design moves. This section puts that question to the same code: when the design moves along , which keeps the residual at zero?
从前向代码到一个线性方程组
From the tangent code to one linear system
收敛解随设计变化:在默认设计附近,每个设计 都有自己的离散解 ,满足 (Newton 迭代求出它,第 5 节)。让设计沿直线 移动, 是第 个分量的单位向量( 依次对应 ), 是实数。残差对每个 都是零,所以它对 的导数也是零。在 用链式法则求导——这正是对整个求解过程使用前向模式——得到两项:
The converged state depends on the design: near the default, every design has its own discrete solution with (Newton's method finds it, Section 5). Move the design along the straight line , where is the unit vector of component ( for ) and is a real number. The residual is zero for every , so its derivative with respect to is zero as well. The chain rule at , which is the forward mode applied to the whole solve, gives two terms:
第一项是设计经由状态产生的间接影响,第二项是设计直接改变残差的部分。于是状态切向量 是一个线性方程组的解:矩阵是收敛状态处的状态 Jacobian ,右端是残差自身对设计的导数:
The first term is the indirect effect of the design through the state, the second is the direct change of the residual. So the state tangent solves a linear system whose matrix is the state Jacobian at the converged state and whose right-hand side is the residual's own design derivative:
用第 8 节的话说:,种子是 ;把 送进前向代码,返回的 是零。前向代码对给定的 计算 ,前向求解则是找出使 的那个 。本页对 做 LU 分解来解这个方程组(第 17 节改用 GS 与 GMRES)。这要求 在收敛状态处非奇异:本页每个状态上分解都成功——这是数值检查,不是定理。默认设计下三次求解的线性残差 低于 (舍入误差)。
In the words of Section 8: for the seed ; fed with the seeds , the tangent code returns . The tangent code evaluates for a given ; the tangent solve finds the that makes vanish. The page solves the system with an LU factorisation of (Section 17 uses GS and GMRES instead). This needs to be nonsingular at the converged state: the factorisation succeeds at every state of this page, a numerical check rather than a theorem. At the default design the linear residual of the three solves, , is below (round-off).
是什么
What is
有 个分量:它是每个单元平均对设计分量 的响应,是整个离散解的灵敏度,不只是一个数。目标的导数由它得到一个数:第 7 节的 不显含设计(权重 只依赖网格),所以 ,导数就是加权和
has entries: it is the response of every cell average to the design component , the sensitivity of the whole discrete solution and not of one number. One number follows from it. The objective of Section 7 does not contain the design explicitly (the weights depend on the mesh only), so and its derivative is the weighted sum
默认设计(,无限制器线性重构)下三次求解给出 、、。它们是这个网格上离散目标 的精确导数,与第 1 节的连续梯度 相差很大( 相差 238%)。对 来说,主要原因是两个端单元不做重构:按第 16 节的端单元实验(那里列的是这个梯度的误差,这里是它的值)重构端单元后,同一网格给出 ,而不是 (精确值 )。此外,一个正弦周期只有 8 个单元,而精确梯度只是两个 项之间很小的差(第 12 节): 时 的源项部分与连续值相差 58%,边界部分相差 11%; 时黏性尺度也没被分辨,但第 16 节说明这不是主要原因(第 15 节测量梯度怎样收敛)。实验的答案在图 30 左图: 增加 时,变化最大的是 与 ,约 与 ; 变化约 。
At the default design (, unlimited linear reconstruction) the three solves give , and . These are the exact derivatives of the discrete objective on this mesh, and they are far from the continuous gradient of Section 1 ( is 238% off). For the main reason is that the two end cells are not reconstructed: with them reconstructed (the experiment of Section 16, which tabulates the error of this gradient, here its value) the same mesh gives instead of (exact: ). In addition, 8 cells per period of the sine are few, and the exact gradient is a small difference of two terms of order one (Section 12): at the source part of is 58% off, its boundary part 11%. The viscous scale is not resolved at either, but Section 16 shows that this is not the main cause (Section 15 measures how the gradients converge). The left panel of Figure 30 answers the experiment: when grows by , the cells that move most are and , by about and , and moves by about .
为什么 ?精确解 与 无关,离散解却有关:格式的误差依赖 (默认网格上 ,即网格 Péclet 数,见第 3 节的分辨率面板), 一变,误差就变, 也跟着变。所以 在粗网格上不为零,而连续的 ;第 12 节把这个差别拆开来看。
Why is ? The exact solution does not depend on , but the discrete solution does: the error of the scheme depends on (the cell Péclet number of the default mesh is ; see the resolution panel of Section 3), so when changes the error changes and with it. That is why is not zero on a coarse mesh while the continuous ; Section 12 takes this difference apart.
三个右端项
The three right-hand sides
右端 同样由前向代码算出:取 、,返回的 就是 。代码里只有三处读取设计,所以右端只有三条路径:
The right-hand side comes from the tangent code as well: with and the returned is . Only three places of the code read the design, so the right-hand side has three paths:
- 源项行():每一行都有 ,即精确通量 的变化。它是源项导数在一个单元上的积分,大小约与 成正比。Source rows (): every row has , the change of the exact flux . It is the integral of a derivative of the source over one cell, of a size proportional to .
- 边界值(): 在面 上充当 和 ,在面 上充当 和 ,所以只出现在第一行和最后一行。这一项不随 变小,其中的黏性部分 还随网格加密而变大。Boundary values (): serve as and on face and as and on face , so they appear in the first and the last row only. They do not shrink with , and their viscous part even grows as the mesh is refined.
- 通量本身(只有 ):每个面的黏性通量 显含 ,偏导 分发到面两侧的两行。The flux itself ( only): the viscous flux of every face contains explicitly; its partial derivative is scattered to the two rows beside the face.
为什么首末两行较大的右端项会影响整个解? 是一个叠加:右端项的第 个分量就像加在单元 上的一份源项, 的第 列是状态对单元 上单位源项的响应()。图 32 画出 时的 :源项几乎只向下游(矩阵对角线以下、剖面的右侧)改变状态——单元 5 的源项使下游各单元变化 0.57–1.33,上游各单元最多变化 0.043(单元 5 自身变化的 6.6%);靠近出口时响应回落,因为 是给定的。所以第一行的右端项(入口处的边界值)影响整个区间,最后一行的右端项只影响出口附近。
Why do the large first and last right-hand-side entries affect the whole solution? is a superposition: the -th entry of the right-hand side acts like a portion of source added in cell , and column of is the response of the state to one unit of source in cell (). Figure 32 draws for : a source changes the state mainly downstream (below the diagonal of the matrix, to the right in the profiles): a source in cell 5 moves the downstream cells by 0.57–1.33 and the upstream cells by at most 0.043 (6.6% of the cell's own response); near the outlet the response falls back because is prescribed. So the first right-hand-side entry (the inlet value) acts on the whole interval, the last one only near the outlet.
第 12 节把每一列拆成这些路径,并跟踪每条路径对梯度的贡献。下面的面板对当前设计做完整的前向求解,并用两次完整的非线性重解检查 :中心差商 ,步长取 ,这个值本身并无特别之处。默认设计下三种重构、三个分量的最大差低于 (第 13 节研究步长的选择)。
Section 12 splits every column into these paths and follows each into the gradient. The panel below performs the whole tangent solve for the current design and checks against two full nonlinear re-solves: the central difference , where the step is just a number. At the default design the largest difference over the three reconstructions and the three components is below (Section 13 studies the choice of the step).
“切线”的含义:扰动后重新求解
What “tangent” means: perturb and re-solve
切线解是一个预测:对很小的 ,。验证它只需要非线性求解器本身:在 上重新完整求解,再与预测比较。残差是 与 的光滑函数, 非奇异,所以 光滑(隐函数定理),余项是二阶的:
The tangent solution is a prediction: for small , . Checking it needs nothing but the nonlinear solver: solve the full equations again at and compare with the prediction. The residual is a smooth function of and and is nonsingular, so is smooth (implicit function theorem) and the remainder is of second order:
很小时,把 减半,误差约变为原来的四分之一(在本页测试的范围内,相邻两个步长的误差之比在 3.65–4.37 之间)。默认设计(,无限制器线性)下,在 ()、()、()范围内,比值 分别停留在 0.45–0.60、4.13–4.85、8.7–10.3:在双对数图里误差曲线的斜率接近 2,这就是“切线”的标志。在面板里选一个单元、一个步长;其后的播放器把整条剖面做成动画。
For small , halving divides the error by about four (by 3.65–4.37 in the range tested here). At the default design (, unlimited linear), for (), () and (), the ratio stays within 0.45–0.60, 4.13–4.85 and 8.7–10.3 respectively: on log–log axes the error curve has a slope close to 2, the signature of a tangent. Pick a cell and a step in the panel; the player after it animates the whole profile.
切线只在 很小时才可靠:沿 走 (四分之一个周期)时,预测与重解相差 。导数描述无穷小变化的响应;大步长的优化器必须重新求解。另一个常见错误是在未收敛的状态上求导:只有在 处, 才是解的导数;Newton 迭代中途的 给出的是另一个函数的切线。
The tangent line is reliable only for small : a step in (a quarter of the period) misses the re-solved state by . A derivative describes the response to an infinitesimal change; an optimiser that takes large steps must re-solve. Another common slip is to differentiate at a state that has not converged: only where is the derivative of the solution; the of an intermediate Newton iterate gives the tangent of a different function.
连续切线问题
The continuous tangent
对第 1 节的连续问题用同样的方法求导,得到连续切线方程。记 。对 ,只有源项与两端的给定值依赖 ;对 ,通量 本身含 ,于是右端多出 :
Differentiating the continuous problem of Section 1 in the same way gives the continuous tangent equations. Write . For only the source and the two end values depend on ; for the flux contains itself, which adds to the right-hand side:
对构造解不需要求解: 对每个设计都是解,所以它对设计的导数就是切线问题的解:,,(解与 无关;事实上 ,右端为零)。本页用一个独立的 Chebyshev 配点求解器(;附录 A 用 )解前两个边值问题,与 、 的闭式相差不到 ; 的问题右端与边界值都恒为零,解自然是零,这一项检验是平凡的。离散的 逼近这些函数的单元平均 、、;图 33 与下表给出收敛情况。
For the manufactured solution no solve is needed: solves the problem for every design, so its derivative with respect to the design solves the tangent problem: , , (the solution does not depend on ; indeed , so the right-hand side vanishes). An independent Chebyshev collocation solver (; Appendix A uses ) of the first two boundary-value problems reproduces the closed forms of and to within ; for the right-hand side and the boundary data vanish identically, so its zero solution is a trivial check. The discrete approximate the cell averages , and of these functions; Figure 33 and the table below show the convergence.
| N | |||
|---|---|---|---|
| 8 | 5.25e−1 | 3.35e−1 | 6.54e−1 |
| 32 | 1.14e−1 | 5.05e−2 | 3.83e−1 |
| 128 | 1.96e−2 | 1.32e−2 | 2.08e−1 |
| 512 | 2.02e−3 | 1.61e−3 | 3.22e−2 |
| 2048 | 1.49e−4 | 1.24e−4 | 2.67e−3 |
| 4096 | 3.83e−5 | 3.22e−5 | 7.03e−4 |
无限制器线性重构时切线解的观测阶趋近 2( 到 之间为 1.96、1.95、1.93),一阶重构时趋近 1(1.00、0.99、1.11)——与状态本身的阶相同(第 15 节)。 时 仍有 ,是 误差的 18 倍: 是最后收敛的梯度分量(第 12 节)。
With unlimited linear reconstruction the observed orders of the tangents approach 2 (1.96, 1.95 and 1.93 between and ), with first-order reconstruction they approach 1 (1.00, 0.99, 1.11): the orders of the state itself (Section 15). At , is still , 18 times the error of : is the last gradient component to converge (Section 12).
代价:每个设计分量解一次
The cost: one solve per design component
每次前向求解回答一个问题:对一个设计方向的响应。设计有 个分量,就要用同一个矩阵 、 个右端项解 次;这里 。好处是,每次求解给出整个向量 ,于是状态的任何其他目标,导数只需再做一次内积。当目标少而设计分量多时,第 11 节把计算倒过来:用 解一次,就得到 的全部分量(图 34)。
Each tangent solve answers one question: the response to one design direction. A design with components needs solves with the same matrix and right-hand sides; here . In return each solve gives the whole vector , so the derivative of any other objective of the state is one more dot product. When the objectives are few and the design components many, Section 11 turns the computation around: one solve with gives every component of at once (Figure 34).
伴随求解:一次转置求解得到全部梯度
The adjoint solve: one transposed solve for the whole gradient
本节实验:先预测 是正还是负、 在哪里最大,在图 36 里核对;再看图 38:粗网格上 为什么离连续伴随 那么远?
Experiment: Predict first: is positive or negative, and where is largest? Check it in Figure 36. Then look at Figure 38: why is so far from the continuous adjoint on the coarse mesh?
第 10 节每个设计分量要解一次线性方程组。只有一个目标而设计分量很多时,这样做很浪费:我们要的只是 个数 ,而不是 个向量 。本节把求解移到内积的另一侧。
Section 10 needed one linear solve per design component. With one objective and many design components that is wasteful: what we want are numbers , not vectors . This section moves the solve to the other side of the dot product.
从 次前向求解到一次转置求解
From tangent solves to one transposed solve
对任意种子 ,前向求解给出 ,目标的方向导数是 。把前者代入后者,从左边把因子合并:
For any seed the tangent solve gives , and the directional derivative of the objective is . Insert the first into the second and group the factors from the left:
向量 (伴随)与 无关:用转置矩阵解一次,就对所有设计方向都适用。梯度就是反向模式的设计伴随:
The vector , the adjoint, does not depend on : one solve with the transposed matrix serves every design direction. The gradient is the design adjoint of reverse mode:
用第 9 节的话说:以 为种子运行反向代码,它返回 ( 满足伴随方程时正好等于 )和 ,即梯度。第 9 节的对偶恒等式 取 、、(于是 ),就是 :前向与伴随给出同一个数。默认设计下,三种重构的反向代码返回的 都满足 ;下表比较两条路线:
In the words of Section 9: run the reverse code with the seed ; it returns , which equals exactly when solves the adjoint equation, and , the gradient. The duality identity of Section 9 with , and (so that ) reads : the tangent and the adjoint give the same number. At the default design the reverse code returns with for each of the three reconstructions, and the table compares the two routes:
| 重构Reconstruction | 前向 Tangent | 伴随 Adjoint | 差Difference | |
|---|---|---|---|---|
| 一阶first order | −0.745986130503 | −0.745986130503 | 3.3e−16 | |
| 一阶first order | −0.357341645754 | −0.357341645754 | 2.2e−16 | |
| 一阶first order | −0.245483618467 | −0.245483618467 | 1.4e−16 | |
| 无限制器线性unlimited linear | −0.486649677166 | −0.486649677166 | −2.8e−16 | |
| 无限制器线性unlimited linear | −0.061038915769 | −0.061038915769 | −7.2e−16 | |
| 无限制器线性unlimited linear | −0.309871355162 | −0.309871355162 | 2.8e−16 | |
| 光滑限制器smooth limiter | −0.656527469353 | −0.656527469353 | 1.1e−16 | |
| 光滑限制器smooth limiter | −0.168097300931 | −0.168097300931 | −2.5e−16 | |
| 光滑限制器smooth limiter | −0.290780443262 | −0.290780443262 | 6.1e−16 |
右端的负号是一种约定:若写 ,梯度就是 。本页取 ,使 与附录 A 的连续形式 符号一致: 与 同号,也不差一个因子 ( 是单元上的积分残差)。
The minus sign on the right-hand side is a convention: with the gradient would be . This page keeps so that and its continuous counterpart of Appendix A carry the same sign: and have the same sign, and there is no factor between them ( is a residual integrated over a cell).
转置矩阵:倒着走的面循环
The transposed matrix: the face loop run backwards
是 关于对角线的镜像: 的第 行是 的第 列,即未知量 进入的那些残差行。图 35 画出 时的两者。反向代码从不形成转置矩阵(本页的直接求解为了方便确实形成并分解了 ;只有用 residual_b 的无矩阵路线从不形成它):第 9 节的反向面循环从最后一个面走到第一个面,给每个面种子 (它两侧单元伴随之差),再把 、 和 送回这个面读过的单元。每个面送回的正是它在 中各列的那几项,所以这个循环不形成矩阵就完成了与 的乘法。
is mirrored about its diagonal: row of is column of , the residual rows that the unknown enters. Figure 35 shows both for . The reverse code never forms a transposed matrix (the page's direct solves do form and factor , for convenience; only the matrix-free route through residual_b never forms it): the reverse face loop of Section 9 visits the faces from the last to the first, gives each face the seed (the difference of the adjoints of the two cells it separates) and sends , and back to the cells the face read. Each face sends back exactly its own entries of the columns of , so the loop applies without forming it.
转置也颠倒了信息传播的方向。看一阶格式()的第 3 行: 的第 3 行是 (依次乘 ),上游的系数 的绝对值比下游的 大:残差 主要取决于上游单元—— 时原始问题的信息从左向右传。 的第 3 行是 的第 3 列 (依次乘 ),大系数换到了下游一侧:伴随从右向左传,从 传向 。连续伴随方程 (附录 A)也是这样:它沿反方向输运,它的入口边界层(下文简称层)在 。
The transposition also reverses the direction in which information travels. Take row 3 of the first-order scheme (): row 3 of is (multiplying ); the upstream coefficient is larger in magnitude than the downstream : the residual leans on the upstream cell, and with the information of the primal problem travels from left to right. Row 3 of is column 3 of , (multiplying ): the large coefficient has moved to the downstream side, and the adjoint travels from right to left, from towards . The continuous adjoint equation of Appendix A does the same: it transports in the reverse direction, and its inlet boundary layer (from here on: the layer) sits at .
伴随场
The adjoint field
图 36 是默认设计下的 。三种重构的 在每个单元都为负;无限制器线性时 在单元 1 最大(),在单元 7 最小()。实线是连续伴随 (附录 A,由独立的 Chebyshev 求解器算出),,同号、无因子 。
Figure 36 shows at the default design. In all three reconstructions is negative in every cell; with unlimited linear reconstruction is largest in cell 1 () and smallest in cell 7 (). The solid line is the continuous adjoint of Appendix A (computed by an independent Chebyshev solver): , with the same sign and no factor .
的含义:在一个单元加一点源项
What means: add a little source to one cell
给单元 的积分源项加上一个数 :第 行变成 ,其余行不变。前向种子 给出 ,即 ;用伴随方程 :
Add a number to the integrated source of cell : row becomes and the other rows do not change. The forward seed gives , that is , and with the adjoint equation, :
所以 是“单元 多一个单位源项时 增加多少”。:任何单元加源项都使 增大。图 37 不用伴随直接测量这个关系:对每个 重新完整求解两次非线性方程(),中心差商与 的最大相对差为 。加在单元 1、2 的源项影响最大:它们向下游影响几乎所有单元,而 对整个区间加权求和。加在单元 0 的源项影响反而较小。一个启发式的解释:一部分扰动经入口面离开—— 时主要通过入口面通量的对流(Rusanov)部分 ,黏性部分 较小;在连续问题里 ,紧贴入口的源项完全不起作用。
So is how much grows when cell receives one more unit of source; means that a source added in any cell raises . Figure 37 measures this relation without the adjoint: for every it solves the full nonlinear equations twice (), and the central difference agrees with to a relative . Sources in cells 1 and 2 act most: they influence nearly every cell downstream, and sums over the whole interval. A source in cell 0 acts less. A heuristic reading: part of the disturbance leaves through the inlet face, at mainly through the convective (Rusanov) part of the inlet flux, , rather than through its viscous part, ; in the continuous problem , and a source right at the inlet has no effect at all.
图 32 的影响矩阵给出同一件事的另一种读法: 是第 列——单元 的源项在各单元引起的变化——按目标权重 加起来。切线解把扰动向下游送,伴随把下游的权重收集回来。在面板里换一个单元试试:
The influence matrix of Figure 32 gives a second reading of the same fact: is column , the change that a source in cell causes in every cell, summed with the objective weights . The tangent sends a disturbance downstream; the adjoint gathers the downstream weights back. Try another cell in the panel:
与连续伴随比较
Comparison with the continuous adjoint
连续伴随在 有一个宽约 的层: 从 陡降,。 的单元 0 比整个层宽十倍以上,所以 无法“看到”层的形状。 是 的单侧差商估计():图 38 与下表显示,它在 时只有 , 时 , 时 。要 ,即 上千,层才被分辨。这个标准比第 3 节分辨率面板用的标准严:那里层宽内有三个单元(,本例 )就算分辨; 要到 才与 相差不到 5%。离开层(), 收敛到 ;观测阶要到 上千才接近 2(最后三对网格为 1.86、1.93、1.96)。第 16 节系统地研究这种一致性。
The continuous adjoint has a thin layer of width about at : drops steeply from , with . Cell 0 of the mesh is more than ten times wider than the whole layer, so cannot see its shape. is the one-sided estimate of (since ): Figure 38 and the table show that it is only at , at and at . The layer is resolved only when , with in the thousands. This criterion is stricter than that of the resolution panel of Section 3, which counts the layer resolved when three cells fit into its width (, here); comes within 5% of only from on. Away from the layer () converges to ; the observed order approaches 2 only for in the thousands (1.86, 1.93, 1.96 for the last three pairs of meshes). Section 16 studies this consistency systematically.
| N | |||||
|---|---|---|---|---|---|
| 8 | −14.79 | −142.66 | 2.612 | 2.203 | 1.2e−1 |
| 16 | −26.42 | −142.66 | 2.486 | 2.203 | 4.4e−2 |
| 32 | −44.10 | −142.66 | 2.368 | 2.203 | 2.3e−2 |
| 64 | −67.02 | −142.66 | 2.234 | 2.203 | 1.0e−2 |
| 256 | −111.08 | −142.66 | 2.112 | 2.203 | 1.4e−3 |
| 1024 | −133.18 | −142.66 | 2.157 | 2.203 | 1.2e−4 |
| 4096 | −140.16 | −142.66 | 2.190 | 2.203 | 8.1e−6 |
不要从粗网格的 读 :层没被分辨时这个差商的绝对值小得多, 时只有真值的十分之一。但这并不意味着梯度错了:第 12 节说明,进入梯度的是边界值的伴随 ,它在 时已在连续值 的 5% 以内。
Do not read off on a coarse mesh: while the layer is unresolved this quotient is far too small in magnitude, a tenth of the true slope at . That does not make the gradient wrong: Section 12 shows that what enters the gradient is the adjoint of the boundary value, , which at is already within 5% of its continuous value .
组装梯度:谁的贡献
Assembling the gradient: who contributes
是逐单元的和。图 39 画出每个单元的贡献 和累加和。 时 的梯度几乎全来自内部单元(首末两个单元合计 ), 的梯度是两个大数之差:首末两个单元贡献 ,内部单元贡献 ,合计只有 。加密后首末两个单元承载边界值的贡献,内部单元承载源项的贡献: 时 的这两部分是 与 , 是 与 。梯度是 的两部分之差,第 12 节把它们与连续梯度的两项一一对应。
is a sum over the cells. Figure 39 draws the contribution of every cell and the running sum. At the -gradient comes almost entirely from the interior cells (the first and last cell together give ), while the -gradient is the difference of two large numbers: the first and last cell contribute , the interior cells , together only . On finer meshes the end cells carry the contribution of the boundary values and the interior cells that of the source: at the two parts are and for , and for . The gradient is a difference of two parts of order one; Section 12 matches them with the two terms of the continuous gradient.
下面的面板把本节的内容放在一起,网格、重构与黏性都可以改(带状求解器, 到 ): 与 、各单元的贡献,以及梯度的四种算法——前向、伴随、两次完整重解的中心差商和连续问题的精确梯度。
The panel below puts this section together, with the mesh, the reconstruction and the viscosity of your choice (banded solver, up to ): against , the contribution of each cell, and the gradient by four routes: tangent, adjoint, central differences of two full re-solves, and the exact gradient of the continuous problem.
一次求解得到全部分量:代价
One solve for every component: the cost
两条路线的代价可以直接数。用不加预处理的 GMRES 解每个线性方程组,不形成矩阵,矩阵–向量乘积只由代码给出:前向路线的每次迭代调用一次 residual_d(第 8 节),伴随路线的每次迭代调用一次 residual_b(第 9 节)。图 40 数出调用次数:设计只有 三个分量时(),前向路线的调用约是伴随路线的三倍;若再给每个单元一个源项分量(,上面“ 的含义”里的 ),前向路线的调用随 增长得快得多,而伴随路线的调用次数与 无关: 时是 3279 次对 64 次。 的求解与伴随求解每次约用 次迭代( 时为 64、64、63、64),单元源项的求解在较大的网格上用得少些( 时 31–64 次,平均 48),所以 时调用次数之比是 51,而不是 。两条路线的梯度最大相差 。与直接求解相比,这个差全在前向路线的单元源项分量上(最多差 );两条路线的 分量以及伴随路线的单元分量与直接求解相差约 (舍入误差)。
The cost of the two routes can be counted directly. Solve every linear system with unpreconditioned GMRES, without forming the matrix (the matrix–vector products come from the code): each iteration of the tangent route calls residual_d once (Section 8), each iteration of the adjoint route calls residual_b once (Section 9). Figure 40 counts the calls. With the three design components () the tangent route makes about three times as many calls as the adjoint route. Give every cell one more source component as well (, the of “What means” above), and the calls of the tangent route grow much faster with , while the calls of the adjoint route do not depend on : 3279 calls against 64 at . The solves for and the adjoint solve take about iterations each (64, 64, 63, 64 at ), while the solves for the extra cell sources take fewer on the larger meshes (31–64, 48 on average, at ), so with the ratio of the calls is 51 rather than . The gradients of the two routes differ by at most . Against a direct solve, all of it sits in the cell-source components of the tangent route (off by up to ); the components of both routes and the cell components of the adjoint route agree with the direct solve to about (round-off).
用 LU 分解直接求解时情况不同: 分解一次后(部分选主元:), 个前向右端项与一个伴随右端项( 用同一组因子)都只需三角求解(前代与回代),差别小得多。伴随的优势在矩阵不显式形成、每次求解都要迭代的大问题里才显著——这正是 CFD 的常态。若有 个目标,伴随路线要解 次,前向路线仍是 次:哪条路线便宜取决于 与 谁大。
With a direct LU solve the picture is different: once is factored (with partial pivoting, ), the tangent right-hand sides and the one adjoint right-hand side ( reuses the same factors) each cost only triangular solves, and the difference is small. The adjoint pays off in large problems where the matrix is never formed and every solve is an iteration, which is the normal case in CFD. With objectives the adjoint route needs solves and the tangent route still : which route is cheaper depends on whether or is larger.
设计导数:设计从哪里进入残差
The design derivatives: where the design enters the residual
本节实验:先预测振幅的梯度 主要来自源项行还是两个边界面;再想一想:连续问题的 是精确的,为什么 时 ?
Experiment: Predict first: where does the amplitude gradient come from, the source rows or the two boundary faces? And why is at , when the continuous exactly?
第 10 节与第 11 节把 当作一个整体使用。本节把它打开:每个设计分量从哪里进入残差,每条进入路径对梯度贡献多少,以及这些部分怎样收敛到连续梯度的两项。
Section 10 and Section 11 used as a whole. This section opens it: where each design component enters the residual, how much each path contributes to the gradient, and how the pieces converge to the two terms of the continuous gradient.
每个设计变量改变什么
What each design variable changes
构造解的公式(第 1 节)给出每个量对每个设计变量的精确导数。下表列出它们,最后一列是连续目标的精确梯度:
The formulas of the manufactured solution (Section 1) give the exact derivative of every quantity with respect to every design variable. The table lists them; the last column is the exact gradient of the continuous objective:
| 设计变量Design variable | (), | 通量 Flux | |||
|---|---|---|---|---|---|
| 只经边界面 、 上的边界值only through the boundary values at faces and | |||||
| 同上likewise | |||||
| 每个面:every face: |
默认设计下精确梯度是 。 的一行最特别:解与目标都不依赖 ,但源项的精确通量 依赖( 是源项必须补上的扩散),离散通量也依赖。
At the default design the exact gradient is . The row is the special one: neither the solution nor the objective depends on , but the exact flux of the source does ( is the diffusion the source has to supply), and so does the discrete flux.
矩阵
The matrix
是 矩阵,每列是第 10 节的一个右端项(差一个负号)。下表是 的 。 与 两列不含状态:它们只来自精确通量 和边界面上的 、、,所以三种重构的这两列完全相同(最大差 ); 列含 ,随重构而变。
is an matrix; each column is one right-hand side of Section 10 (up to the sign). The table shows for . The and columns contain no state: they come only from the exact flux and from , and on the boundary faces, so they are the same for the three reconstructions (largest difference ). The column contains and changes with the reconstruction.
| 单元 Cell | () () | () () | () () |
|---|---|---|---|
| 0 | −0.060278 | 2.121669 | 1.181870 |
| 1 | −0.739248 | −0.585875 | −0.320041 |
| 2 | −0.097399 | −1.134801 | −0.103733 |
| 3 | 0.620327 | −0.768764 | −0.065025 |
| 4 | 0.775561 | 0.071256 | −0.186947 |
| 5 | 0.457660 | 0.619323 | −0.327502 |
| 6 | 0.070781 | 0.780947 | 0.091534 |
| 7 | −0.039596 | 1.534174 | 0.748130 |
每一列的四个部分
The four pieces of a column
取 、,第 8 节的前向面语句是下式第一行。只有两个边界面上 、 不为零(面 上 ,面 上 ), 只在 时出现。三项按残差的分发方式( 加、 减)进入各行,源项语句再加上第四部分:
With and , the tangent face statement of Section 8 is the first line below. Only on the two boundary faces are and nonzero ( on face , on face ), and occurs only for . The three terms enter the rows the way the residual is scattered (added to , subtracted from ), and the source statement adds the fourth piece:
边界部分只出现在第一行和最后一行,其余行为零; 就是边界面的 ()。 只有 一列,而且每个面都有,包括两个边界面(那里 或 ,)。本页从前向代码自己的中间量读出这四部分;四部分之和与前向代码算出的 相差不到 ;check_d.js 还用独立的复数步长实现逐项核对了每一部分。图 41 把它们画成堆叠柱。
The boundary pieces occur in the first and the last row only; is the of a boundary face (). has only the column, and it has an entry from every face, the two boundary faces included (there or and ). The page reads the four pieces off the intermediate values of the tangent code; their sum differs from the of the tangent code by less than , and check_d.js holds each piece separately to an independent complex-step implementation. Figure 41 draws them as stacked bars.
梯度从哪里来
Where the gradient comes from
把伴随与每一部分做内积,梯度 就被分成同样的四部分 、、、。两个边界部分合起来有一个简单的形式。第 9 节的反向语句在面 上把 累加到 ,在面 上把 累加到 ;以 为种子,、,于是
Dotting the adjoint with every piece splits the gradient into the same four parts, , , and . The two boundary parts together take a simple form. The reverse statements of Section 9 accumulate into on face and into on face ; with the seed , and , so
是 对入口值的导数(源项与出口值不变)。默认设计下 、,由反向代码算出,与上面三个式子都相差不到 (三种重构)。下表与图 42 给出梯度的四部分。
is the derivative of with respect to the inlet value (with the source and the outlet value fixed). At the default design the reverse code gives and , in agreement with all three formulas above to within (three reconstructions). The table and Figure 42 give the four parts of the gradient.
| 重构Reconstruction | 分量Component | 梯度Gradient | ||||
|---|---|---|---|---|---|---|
| 一阶first order | −0.03053 | −0.63467 | −0.08078 | 0.00000 | −0.745986 | |
| 一阶first order | 1.55327 | −1.69488 | −0.21573 | 0.00000 | −0.357342 | |
| 一阶first order | 1.57400 | 0.00000 | 0.00000 | −1.81948 | −0.245484 | |
| 无限制器线性unlimited linear | 0.19868 | −0.61124 | −0.07408 | 0.00000 | −0.486650 | |
| 无限制器线性unlimited linear | 1.76912 | −1.63232 | −0.19784 | 0.00000 | −0.061039 | |
| 无限制器线性unlimited linear | 1.81677 | 0.00000 | 0.00000 | −2.12664 | −0.309871 | |
| 光滑限制器smooth limiter | 0.06174 | −0.63979 | −0.07848 | 0.00000 | −0.656527 | |
| 光滑限制器smooth limiter | 1.75002 | −1.70854 | −0.20958 | 0.00000 | −0.168097 | |
| 光滑限制器smooth limiter | 1.82607 | 0.00000 | 0.00000 | −2.11685 | −0.290780 |
实验的第一问有了答案:、无限制器线性时, 的梯度 主要来自边界部分 (绝对值比梯度本身还大),符号相反的源项部分 抵消了其中约 29%; 更极端: 与 相加只剩 。边界部分几乎全是对流部分,黏性部分只占一小份。 时,这和第 11 节逐单元的“谁的贡献”图看起来不同:单元 0 里源项部分()与边界部分()几乎抵消,首末两个单元合计只有 ;只有更细的网格上首末单元才承载边界部分(: 对 )。
The first question of the experiment has its answer: at with unlimited linear reconstruction, the -gradient comes mainly from the boundary part , which is larger in magnitude than the gradient itself; the source part has the opposite sign and cancels about 29% of it. is more extreme: and add up to only . The boundary part is almost entirely convective; the viscous part is a small share. At this differs from the per-cell picture of Section 11: in cell 0 the source piece () and the boundary piece () nearly cancel, so the first and the last cell together show only . Only on finer meshes do the end cells carry the boundary part (: against ).
连续梯度:两个相互抵消的大项
The continuous gradient: two large terms that cancel
对连续问题做同样的分解。附录 A 由连续伴随 (,)推出梯度公式;它也有一个源项部分和一个边界部分,后者只有黏性:
The continuous problem splits the same way. Appendix A derives the gradient formula from the continuous adjoint (, ); it too has a source part and a boundary part, and the latter is purely viscous:
用独立求出的 (Chebyshev 配点,)计算两项: 的源项积分 与边界项 , 的 与 ——各自都是 ,相加才得到小的精确梯度 (与闭式相差不到 )。边界项几乎全来自 : 对 、 分别是 与 , 只贡献 与 。 的两部分 与 逐点抵消:,源项补上的扩散恰好是通量里 的作用。
With an independently computed (Chebyshev collocation, ) the two terms are: for the source integral and the boundary term , for and : each of order one, and only their sum is the small exact gradient (within of the closed form). The boundary term comes almost entirely from : is for and for , while adds only and . The two parts of , and , cancel at every point: , the diffusion that the source supplies is exactly what does in the flux.
离散的两部分怎样收敛
How the discrete parts converge
离散梯度的源项部分是 ,在 时收敛到 ;边界部分的黏性项是 ,在单侧差商趋于 、 时收敛到连续边界项,对流项 随 消失。图 43 用带状求解器从 测到 :两部分分别趋向连续的两项。
The source part of the discrete gradient is , which tends to as ; the viscous boundary piece is , which tends to the continuous boundary term as the one-sided quotients tend to and , and the convective piece vanishes as . Figure 43 measures it with the banded solver from to : each part approaches its continuous term.
边界部分与连续边界项的相对差在 时是 11%, 时 1.1%,可它内部的组成变化很大。图 44 跟踪入口值的伴随 : 时它已是 ,与 相差 4.8%; 时两者相差 , 时 ——而 时 仍是 ,离 还差 12%。粗网格上是对流部分 在承担它( 时占 90%),细网格上换成黏性部分 ( 时占 98%)。出口值的伴随没有这么准: 是 的 5.2 倍()、3.0 倍()、2.0 倍()和 1.44 倍(); 到 时,边界部分的误差有 58%、52%、54%、59%、68%、78% 来自出口面。 的边界部分大,所以 时边界部分的误差是 的主要误差(参见第 16 节)。
The boundary part differs from the continuous boundary term by 11% at and by 1.1% at , yet its make-up changes a lot. Figure 44 follows the adjoint of the inlet value, : at it is already , 4.8% from ; at the two differ by and at by , while at the quotient is still , 12% short of . On coarse meshes the convective piece carries it (90% at ); on fine meshes the viscous piece takes over (98% at ). The adjoint of the outlet value is less accurate: is 5.2 times at , 3.0 times at , 2.0 at and 1.44 at , and for to the outlet face carries 58%, 52%, 54%, 59%, 68%, 78% of the error of the boundary part. The boundary part of is large, so at the error of the boundary part is the dominant error of (compare Section 16).
为什么层没被分辨时边界值的伴随仍然对?把连续伴随方程跨过入口层积分,从 积到层外几个层宽处:,右端 只贡献与区间长度同阶的一小份,于是 约等于层外的 ——入口的灵敏度等于层外伴随的对流“通量”。事实上 ,其中外解在入口的值是 ,等于 ,给出 ,与 相近。梯度需要的是这个积分量,不是层的形状;离散伴随在单元 0 里守恒地传递同样的通量,所以粗网格上 已经接近它。这是一个启发式的解释,上面的数字才是证据。
Why is the adjoint of the boundary value right before the layer is resolved? Integrate the continuous adjoint equation across the inlet layer, from to a point a few layer widths in: and the right-hand side contributes only in proportion to the length of that interval, so is close to just outside the layer: the sensitivity to the inlet value equals the convective flux of the adjoint outside the layer. Indeed , where the outer solution has , here , which gives , close to . The gradient needs this integrated quantity, not the shape of the layer; the discrete adjoint carries the same flux conservatively through the first cell, so is near it on coarse meshes already. This is a heuristic reading; the numbers above are the evidence.
| N | 对流占比Convective share | ||||||
|---|---|---|---|---|---|---|---|
| 8 | 0.1987 | −0.6853 | 1.7691 | −1.8302 | 1.4949 | 90% | −14.8 |
| 32 | 0.3828 | −0.6340 | 1.8216 | −1.6930 | 1.4452 | 69% | −44.1 |
| 128 | 0.4554 | −0.6192 | 1.7523 | −1.6536 | 1.4284 | 36% | −91.0 |
| 512 | 0.4709 | −0.6170 | 1.7345 | −1.6476 | 1.4266 | 12% | −124.9 |
| 2048 | 0.4726 | −0.6168 | 1.7324 | −1.6472 | 1.4266 | 3% | −137.8 |
| 4096 | 0.4727 | −0.6168 | 1.7323 | −1.6471 | 1.4266 | 2% | −140.2 |
| 连续continuous | 0.4728 | −0.6168 | 1.7323 | −1.6471 | 1.4266 | 0% | −142.7 |
黏性导数
The viscosity derivative
连续的 精确为零,离散的却不为零。 的 列有一个精确的形式。源项部分是 ,即 ,通量部分是 的差;记面上导数近似的误差
The continuous is exactly zero; the discrete one is not. The column of has an exact form. Its source piece is , that is , its flux piece the difference of ; with the error of the face gradient
(这里的 与第 6 节的误差传播矩阵 无关。对 逐行核对,差不到 。)若面上的导数近似等于精确导数, 列就是零。 是面上导数近似的误差按伴随在该面的跳跃加权求和;伴随在入口层里跳得最厉害,所以从 起几乎全部来自层: 的面在 时贡献 93%, 时 96%, 时 98%;更粗的网格上并非如此( 时 49%, 时层旁的面贡献 ,总和只有 )。连续问题里只有一个类比,不能算作印证:通量部分写成 时, 中有 来自 ;写成 时,同一区间只给 ——积分“来自哪里”取决于它的写法。图 45 显示两部分的收敛:源项部分从 起与 相差不到 1%(),通量部分要到 才如此(); 先变号,在 附近达到 ,最后以趋近 2 的观测阶趋于零(最后三对网格为 1.66、1.82、1.91; 时 )。
(Here has nothing to do with the error propagator of Section 6. Checked row by row for : the difference is below .) If the discrete face gradients were exact, the column would vanish. is the sum of the face-gradient errors weighted by the jump of the adjoint across the face, and the adjoint jumps most inside the inlet layer, so from on almost everything comes from the layer: the faces with give 93% at , 96% at and 98% at . On coarser meshes they do not (49% at ; at the faces near the inlet give of a total of only ). The continuous problem offers an analogy, not an agreement: written as , the flux part takes of its from ; written as , the same interval gives : where an integral comes from depends on how it is written. Figure 45 shows the convergence of the two parts: the source part is within 1% of from on (), the flux part only from on (); changes sign, peaks at near and finally decays with an observed order approaching 2 (1.66, 1.82, 1.91 for the last three pairs of meshes; at ).
误差集中在入口旁的面上:未重构端单元的一阶迹(第 16 节)与伴随在层内的大跳跃在那里相遇,所以图 46 把 7 种网格、6 种黏性的 对网格 Péclet 数 (层宽 内单元数的倒数)作图。 时,在小于 1 的同一个 Péclet 数下,各网格的值相差约 1%(例如 Péclet 数 0.179 时 给出 ),Péclet 数为 1.43 时相差 4%; 与 的点在同一 Péclet 数下相差 4–12%;Péclet 数明显大于 1 时,粗网格的值互相分开(2.86 时相差 60%)。这是数值观察,不是定理,也不说明层是原因:按第 16 节的端单元实验重构端单元后(那里没有列出这个分量), 时 从 变为 ,Péclet 数不变。
The error sits at the faces next to the inlet, where the first-order traces of the unreconstructed end cell (Section 16) meet the large jumps of the adjoint in its layer, so Figure 46 plots for 7 meshes and 6 viscosities against the cell Péclet number (the inverse of the number of cells per layer width ). For , at equal Péclet numbers below 1, the meshes agree to about 1% (at 0.179, for example, give ), and at a Péclet number of 1.43 to 4%; the points with and differ by 4–12% at equal Péclet numbers; clearly above 1 the coarse meshes split apart (60% apart at 2.86). This is a numerical observation, not a theorem, and it does not make the layer the cause: with the end cells reconstructed (the experiment of Section 16, which does not tabulate this component) at changes from to , at the same Péclet number.
误差分解
The error budget
把梯度的误差 拆成两部分各自的误差(源项部分减源项积分,边界部分减边界项; 则是源项部分与通量部分)。图 47 显示了三件事。 的误差在每个网格上都主要来自源项部分( 时 对 )。 的两部分误差在每个网格上都符号相反、部分抵消: 时边界部分的误差较大(),从 起源项部分的误差较大。 的误差从 起主要来自通量部分( 时 对 ),也就是来自入口旁的面。
Split the error of the gradient, , into the errors of its two parts (the source part minus the source integral, the boundary part minus the boundary term; for the source part and the flux part). Figure 47 shows: the error of comes mainly from the source part on every mesh ( against at ); for the two errors have opposite signs on every mesh and partly cancel, the boundary part's being the larger at () and the source part's from on; the error of comes mainly from the flux part from on ( against at ), that is from the faces next to the inlet.
离散梯度 是离散目标的精确导数——前向、伴随、复数步长与差分在本页检验过的每个网格上都一致(第 13、14 节)。它与连续梯度的差是离散化误差,不是求导错误。本节说明了这个误差来自哪里:主要出在入口旁的几个单元。第 16 节说明了原因:两个端单元不做重构(),面 0、1 与 、 的迹只有一阶精度;重构端单元后, 的层同样没被分辨, 的梯度误差却从 0.343 降到 0.030(),从 0.107 降到 ()。、 的误差经由源项部分: 的单元承担了 的源项部分误差的 115%、107%、103%、102%(、、、,其余单元部分抵消),以及 的约四分之三(77%、76%、76%、76%)。 的误差经由通量部分。最粗的网格上还有边界部分: 时 的最大误差项 一半以上来自出口面。
The discrete gradient is the exact derivative of the discrete objective: tangent, adjoint, complex step and differences agree on every mesh tested on this page (Sections 13 and 14). Its difference from the continuous gradient is a discretisation error, not a differentiation error. This section showed where that error comes from: mostly the cells next to the inlet. Section 16 shows why: the two end cells are not reconstructed (), so the traces of faces 0, 1 and , are only first-order accurate; with the end cells reconstructed the -gradient error drops from 0.343 to 0.030 at and from 0.107 to at , although the layer of is just as unresolved. For and it comes through the source part: the cells with carry 115%, 107%, 103%, 102% of the source-part error of (; the other cells partly compensate) and about three quarters of that of (77%, 76%, 76%, 76%). For it comes through the flux part. On the coarsest meshes the boundary part adds to it: at more than half of the largest error term, , comes from the outlet face.
有限差分验证:每个扰动都重新求解
Finite-difference check: re-solve at every perturbed design
本节实验:先猜一猜,把步长 从 一路缩到 ,中心差分会不会越来越接近伴随梯度?在哪个 最准、能有几位有效数字一致?然后用本节的步长扫描实验检验。
Experiment: guess first: as the step shrinks from to , does the central difference keep getting closer to the adjoint gradient? At which is it most accurate, and how many significant digits agree? Then test the guess with the step-scan lab of this section.
第 10、11 节用两种方法算出了离散目标的梯度:三个前向求解 给出 ,一个伴随求解 给出 ;默认设计下三种重构的两组结果相差不超过 。但这两条路都用到求导代码(第 8 节的前向语句和第 9 节的反向语句)。有限差分不用任何求导代码:它只在附近的设计上重新运行原始求解器,所以是对第 8–12 节的一次独立检验。
Sections 10 and 11 computed the gradient of the discrete objective in two ways: three tangent solves give , and one adjoint solve gives ; at the defaults the two differ by at most for all three reconstructions. Both paths use derivative code (the tangent statements of Section 8 and the reverse statements of Section 9). A finite difference uses no derivative code at all: it only runs the primal solver again at nearby designs, which makes it an independent check of Sections 8–12.
被求导的函数是 ,即这套网格、这种重构下离散解的目标值。差分验证的是 ,不是连续问题的精确梯度。默认网格上两者相差很远:无限制器线性重构的伴随梯度是 ,精确梯度是 。这个差别是离散误差,第 15、16 节才讨论;本节只问一件事:伴随算出的数是不是 的导数。
The function being differentiated is , the objective of the discrete solution on this mesh with this reconstruction. The finite difference checks , not the exact gradient of the continuous problem. On the default mesh the two are far apart: the adjoint gradient with unlimited linear reconstruction is , the exact gradient . That difference is discretisation error, the subject of Sections 15 and 16; this section asks one thing only: is the number the adjoint computes the derivative of ?
两个差商
Two difference quotients
记 (第 13 至 15 节里下标 、、 对应 :,,)。把 在 处做 Taylor 展开,单侧差商和中心差商分别是
Write (in Sections 13 to 15 the subscripts , , stand for : , , ). Expanding in a Taylor series about gives the one-sided and the central quotient:
中心差商里偶次项相减抵消,所以它的截断误差是二阶的。步长 只是一个数:它不是网格宽度 ,也不是导数记号。
In the central quotient the even powers cancel, so its truncation error is second order. The step is just a number: it is neither the mesh width nor a derivative symbol.
每个扰动设计都要完整重算
Every perturbed design needs a full re-solve
本身不含 (第 7 节:),设计只通过状态影响目标。所以只改 、不重新求解,差商就是 ,这就是“冻结状态”的错误。每个扰动设计 都需要自己收敛的状态 ,网格、重构和 Newton 迭代都与基准求解相同(初值是扰动设计下 在单元中心的值,修正量不超过 时停止)。图 48 上图是改变振幅后的状态;下图说明状态差商 随 趋向第 10 节的切线解 :、、 时最大偏差为 、、,相邻两次之比为 11.0 和 10.1,约 10 倍(一阶)。
contains no of its own (Section 7: ); the design acts on the objective only through the state. Change without solving again and the quotient is : the "frozen state" bug. Every perturbed design needs its own converged state , with the mesh, the reconstruction and the Newton iteration of the base solve (started from the values of at the cell centres of the perturbed design, stopped when the correction is at most ). The top panel of Figure 48 shows the state after a change of the amplitude; the bottom panel shows the state quotient approaching the tangent solution of Section 10 as : the largest deviation is , and for , and , smaller by factors of 11.0 and 10.1: about 10 each time (first order).
完整的验证流程如下(伪代码;本页的扫描实验就是这样算的):
The complete check reads (pseudocode; the step-scan lab of this page computes exactly this):
U0 = solve(D) # Newton: initial guess u*(x_i; D), stop when |dU| <= 1e-13
J0 = w . U0
for k in (a, phi, nu):
for tau in 1e-1, 1e-2, ..., 1e-12:
Up = solve(D + tau e_k) # a full re-solve: the state changes
Um = solve(D - tau e_k) # for nu only if nu - tau > 0
FD_f = (w . Up - J0) / tau
FD_c = (w . Up - w . Um) / (2 tau)
compare with Dbar_k = (R_D^T psi)_k # the adjoint gradient of Section 11一次中心差分要 2 次非线性求解,三个分量 6 次;扫描 12 个步长最多 72 次( 的点不算),再加 1 次基准求解。伴随只要 1 次线性求解就给出全部三个分量(第 11 节)。这里设计只有 3 个分量,差分的负担不重;设计有成千上万个分量时,差分只能抽查少数几个方向。
One central difference costs 2 nonlinear solves, 6 for the three components; a scan over 12 steps costs up to 72 (the points with are skipped), plus the base solve. The adjoint gives all three components with one linear solve (Section 11). With 3 design components the finite differences are affordable here; with thousands of components they can only spot-check a few directions.
截断误差与舍入误差:V 形
Truncation against rounding: the V
两种误差相互竞争。截断误差随 减小(单侧差商斜率 1,中心差商斜率 2)。舍入误差正相反: 只有约 16 位有效数字,它的舍入误差 不随 变小,差商却要把它除以 :
Two errors compete. The truncation error shrinks with (slope 1 for the one-sided quotient, slope 2 for the central one). The rounding error does the opposite: carries about 16 significant digits, its rounding error does not shrink with , and the quotient divides it by :
在双对数图上右支是斜率 2 的直线,左支是斜率 的直线,合起来是一个 V 形。令两项之和对 的导数为零,得到最佳步长 。这些高阶导数可以由伴随梯度差分得到:在 两侧各做一次伴随求解(、 的步长是 , 是 ;差分的是精确导数,不是目标值),两侧梯度的中心差分是 ,再用上 处的梯度做二阶差分得到 。默认设计下对 得 、,于是 ,最小误差约 。扫描中实际最好的是 ,误差约 。
On log-log axes the right branch is a line of slope 2 and the left branch a line of slope : together they form a V. Setting the derivative of the sum to zero gives the best step . The higher derivatives come from differencing the adjoint gradient: one adjoint solve on each side of (with the step for and , for ; what is differenced is an exact derivative, not the objective); the central difference of the two gradients is , and the second difference with the gradient at is . At the defaults this gives and for the amplitude, so and a smallest error of about . The best point of the actual scan is , with an error of about .
图 50 对三个分量各做一次扫描。右支都贴着斜率 1、2 的参考线: 时,实测中心差分误差与预测 之比是 1.0000、1.0000、1.0001(、、)。左支沿着舍入线上升: 的所有中心差分点都在舍入线之下(最多是它的 0.80 倍); 的单侧差分在 处仍以截断误差为主(实测 ,)。 分量的右支高得多: 很小, 对 的高阶导数很大(), 一旦不比 小很多,截断误差就很大。按相对步长 衡量, 与 两个分量其实相近:,。 时 ,中心差分根本不能用,图和表里没有这些点。
Figure 50 scans each of the three components. All right branches follow the reference lines of slopes 1 and 2: at the measured central error over the prediction is 1.0000, 1.0000, 1.0001 for , , . The left branches rise along the rounding line: every central-difference point with lies below it (at most 0.80 of it); the one-sided point at is still truncation (measured , ). The right branch of lies much higher: is small, the derivatives of with respect to are large (), and as soon as is not much smaller than the truncation error is large. Measured in the relative step the two components are in fact alike: and . For the central difference cannot even be formed (), so those points are missing from the figure and the tables.
下表是三种重构、三个分量的中心差分误差 ( 是第 11 节的伴随梯度),粗体是每一列的最小值:
The table lists the central-difference errors for the three reconstructions and components ( is the adjoint gradient of Section 11); bold is the smallest value of each column:
| 一阶first order | 无限制器线性unlimited linear | 光滑限制器smooth limiter | |||||||
|---|---|---|---|---|---|---|---|---|---|
| 1.7e−3 | 3.9e−2 | — | 2.7e−3 | 8.1e−3 | — | 5.4e−3 | 7.6e−2 | — | |
| 1.7e−5 | 4.1e−4 | — | 2.6e−5 | 8.6e−5 | — | 5.4e−5 | 9.9e−3 | — | |
| 1.7e−7 | 4.1e−6 | 1.6e−4 | 2.6e−7 | 8.6e−7 | 1.2e−4 | 5.4e−7 | 9.2e−5 | 1.4e−4 | |
| 1.7e−9 | 4.1e−8 | 1.6e−6 | 2.6e−9 | 8.6e−9 | 1.2e−6 | 5.4e−9 | 9.2e−7 | 1.4e−6 | |
| 2.0e−11 | 4.0e−10 | 1.6e−8 | 3.5e−11 | 9.2e−11 | 1.2e−8 | 7.0e−11 | 9.2e−9 | 1.4e−8 | |
| 8.0e−11 | 7.6e−11 | 3.0e−10 | 5.4e−11 | 6.9e−11 | 1.9e−10 | 2.6e−10 | 1.5e−10 | 1.3e−10 | |
| 9.7e−10 | 5.9e−10 | 5.2e−10 | 5.7e−11 | 7.4e−10 | 1.2e−9 | 8.1e−10 | 4.1e−10 | 1.3e−9 | |
| 2.1e−9 | 2.2e−8 | 7.3e−9 | 1.1e−9 | 2.5e−8 | 4.4e−9 | 3.6e−9 | 1.8e−9 | 9.1e−10 | |
| 3.1e−8 | 6.6e−8 | 1.5e−8 | 1.5e−7 | 3.6e−8 | 1.1e−7 | 8.1e−8 | 1.3e−8 | 4.4e−8 | |
| 4.8e−7 | 1.4e−6 | 2.2e−6 | 1.0e−6 | 1.1e−6 | 1.4e−6 | 7.0e−7 | 4.6e−7 | 1.9e−6 | |
| 重构Reconstruction | 分量Component | 伴随梯度 Adjoint gradient | 中心差分最佳 Best central | 误差Error | 单侧差分最佳 Best one-sided | 误差Error |
|---|---|---|---|---|---|---|
| 一阶first order | −0.745986 | 2.0e−11 | 9.0e−9 | |||
| 一阶first order | −0.357342 | 7.6e−11 | 5.9e−10 | |||
| 一阶first order | −0.245484 | 3.0e−10 | 1.5e−8 | |||
| 无限制器线性unlimited linear | −0.486650 | 3.5e−11 | 1.1e−9 | |||
| 无限制器线性unlimited linear | −0.061039 | 6.9e−11 | 1.4e−8 | |||
| 无限制器线性unlimited linear | −0.309871 | 1.9e−10 | 1.2e−7 | |||
| 光滑限制器smooth limiter | −0.656527 | 7.0e−11 | 7.5e−9 | |||
| 光滑限制器smooth limiter | −0.168097 | 1.5e−10 | 5.4e−8 | |||
| 光滑限制器smooth limiter | −0.290780 | 1.3e−10 | 6.8e−8 |
读表:中心差分在最佳步长处与伴随梯度相差 到 ,这些梯度有 8 到 10 位有效数字一致;单侧差分的最佳误差是 到 。最佳步长因分量和重构而异,事先并不知道,所以只算一个 的差分不够,要扫描。常用的折中 对 很好,对一阶与无限制器线性的 也很好;但对 由截断误差主导(,因为这一步长的 ),对光滑限制器的 也是(,光滑限制器的 )。
Reading the tables: at its best step the central difference differs from the adjoint gradient by to , so 8 to 10 significant digits of these gradients agree; the best one-sided errors are to . The best step differs by component and reconstruction and is not known in advance, so a difference at one is not enough: scan. The common compromise is excellent for , and for with the first-order and unlimited linear schemes; but for the truncation error dominates (; for that step is ), and so it does for with the smooth limiter (, where ).
自己扫一遍:改变上方的网格、重构或设计(点“求解并更新”),选一个分量,看 V 形怎样移动。
Scan it yourself: change the mesh, the reconstruction or the design above (press Solve & update), pick a component and watch the V move.
割线如何变成切线
How the secant turns into the tangent
单侧差商 有一个几何意义:它是曲线 上过 与 两点的割线的斜率;切线的斜率是 ,也就是伴随给出的 。下面的播放器把 每步缩小 10 倍:前两步割线与切线还看得出区别,之后在图上已经重合,差别只留在数字里——看一致的有效数字位数先增后减。
The one-sided quotient has a geometric meaning: it is the slope of the secant through the points and of the curve ; the slope of the tangent is , the of the adjoint. The player below divides by 10 at every step: for the first two steps the secant and the tangent can be told apart, after that they coincide on the drawing and the difference lives only in the digits: watch the number of agreeing significant digits rise and then fall.
误差下限由谁决定:Newton 迭代的停止准则
What sets the error floor: the Newton stopping criterion
两个 都不是由精确离散解算出的:Newton 迭代在修正量不超过 时停止,最后一个修正量并不加上去。这个容差会不会就是误差下限?量一量。默认设计下基准求解的修正量依次是 、、、、:二次收敛一步就越过了容差,返回的状态已经准确到舍入水平。所以默认容差 并不决定误差下限,舍入才决定(左支贴着舍入线,见上)。
Neither value of comes from the exact discrete solution: Newton stops when the correction is at most , and the last correction is not added. Is that tolerance the error floor? Measure it. At the defaults the corrections of the base solve are , , , , : quadratic convergence jumps past the tolerance in one step, and the state returned is already accurate to rounding. So the default tolerance does not set the error floor; rounding does (the left branch follows the rounding line, see above).
放宽容差,情形就变了(图 51,分量 )。容差 时只加上两个修正量(第三个 已低于容差),误差在 到 之间停在 到 ,不再随 变化。原因是:只要两次求解的迭代步数相同,只迭代 步的 Newton 迭代值 (这里 数迭代步;第 5 节记作 )本身就是 的光滑函数,差商算出的是它的导数,偏差是一个固定的量,而不是被 放大的噪声。这里 :沿 Newton 迭代逐步求导, 的导数与 相差 。容差 时加上三个修正量, 的导数只差 : 时的实测误差 就是这个偏差; 再小,舍入又占了上风。
Loosen the tolerance and the picture changes (Figure 51, component ). With the tolerance only two corrections are added (the third, , is below the tolerance), and from to the error stays at to , no longer depending on . The reason: as long as both solves take the same number of steps, the Newton iterate after steps ( counts steps here; Section 5 writes ) is itself a smooth function of ; the quotient computes its derivative, and the deviation is a fixed bias, not noise amplified by . Here : differentiating along the Newton iteration, the derivative of differs from by . With the tolerance three corrections are added, and the derivative of differs by only : the measured error at is this bias; at smaller rounding takes over again.
热启动(以扰动前的解为初值)加一个绝对容差最危险:第一个修正量约为 ,它一旦小于容差,求解器就原样返回初值,两个 完全相等,差商为 0。容差 时 的点全是这样,误差就是梯度本身。
The most dangerous combination is a warm start (from the solution before the perturbation) with an absolute tolerance: the first correction is about , and once it is below the tolerance the solver returns its initial value unchanged, the two values of are identical and the quotient is 0. With the tolerance this happens at every : the error is the gradient itself.
差分验证的四个陷阱:(1)不重新求解,差商是 ;(2)容差太松,差商趋向只迭代 步的迭代值的导数,而不是 的导数,误差停在一个与 无关的平台上;(3)热启动加绝对容差,小 时求解器不动,差商为 0;(4)热启动只走一步时, 的差商(当 与 无关时)就是把 换成它的中心差分近似 后的切线结果。对 ( 是 的二次函数, 与 无关),它就是切线结果本身(、容差 时与 只差 );对 差 ;对 连 都变了(差 )。这时差分已不独立,Jacobian 里的错误会被它原样复制。可靠的做法:每个扰动点冷启动、收敛到舍入水平、扫描多个 。另外 的步长必须小于 本身。
Four traps of the finite-difference check: (1) without a re-solve the quotient is ; (2) with a loose tolerance the quotient tends to the derivative of the iterate after Newton steps, not to the derivative of , and the error stays on a plateau independent of ; (3) with a warm start and an absolute tolerance the solver does not move for small and the quotient is 0; (4) when a warm start takes a single step, the quotient of is (when does not depend on ) the tangent result with replaced by its central difference . For ( is quadratic in and does not depend on ) that is the tangent result itself (at and the tolerance it differs from by only ); for the difference is ; for even changes (difference ). Then the difference is no longer independent and copies any error of the Jacobian. The safe recipe: a cold start at every perturbed design, convergence to rounding, a scan over many . And the step in must be smaller than itself.
差分能做什么、不能做什么
What finite differences can and cannot do
- 能:只用原始求解器就检验整条链——前向语句、反向语句、、转置和线性求解。例如漏掉第 8 节通量对 的显式导数 ,伴随梯度的 分量会变成 ,差分却给出 :差别远大于差分误差。Can: check the whole chain with the primal solver only: tangent statements, reverse statements, , the transpose and the linear solves. Leave out the explicit -derivative of the flux, of Section 8, and the -component of the adjoint gradient becomes while the difference gives : a gap far larger than any difference error.
- 不能:给出超过约 10 位的数字,而且只有扫描之后才知道哪个 可信;最佳 随分量、网格和重构变化。Cannot: deliver more than about 10 digits, and only a scan tells which to trust; the best changes with the component, the mesh and the reconstruction.
- 不划算:每个分量 2 次非线性求解;伴随 1 次线性求解给出全部分量。Not cheap: 2 nonlinear solves per component; the adjoint gives all components with one linear solve.
- 不回答离散误差的问题:差分与伴随都是 的导数,它们离精确梯度多远是第 15、16 节的事。第 14 节的复数步长去掉了 V 形的舍入支。Does not answer the question of discretisation error: the difference and the adjoint are both derivatives of ; how far they are from the exact gradient is the business of Sections 15 and 16. The complex step of Section 14 removes the rounding branch of the V.
复数步长验证:没有相减的差商
Complex-step check: a quotient without subtraction
本节实验:先算一算,在双精度里 等于多少?那么 时第 13 节的中心差分给出什么?复数步长 又给出什么?读完本节,用复数步长实验验证。
Experiment: work it out first: what is in double precision? Then what does the central difference of Section 13 return at ? And what does the complex step return? After the section, check with the complex-step lab.
第 13 节的差分受步长两头夹击: 大时截断误差大; 小时两个几乎相等的 相减,舍入被 放大。双精度里 就等于 本身,所以 的差分是 。复数步长去掉了相减这一步。
The differences of Section 13 are squeezed from both ends of the step: at large the truncation error is large; at small two nearly equal values of are subtracted and the rounding is amplified by . In double precision is itself, so the difference at is . The complex step removes the subtraction.
虚数方向上的一步
A step in the imaginary direction
设 作为 的函数可以解析延拓到复平面(下一小节说明为什么可以)。把实的步长换成虚的 ,在 处展开:
Suppose , as a function of , extends analytically into the complex plane (the next subsection says why it does). Replace the real step by the imaginary step and expand about :
虚部与实部分开:
Separate the imaginary and the real parts:
导数直接从虚部读出,不需要两个相近的数相减,舍入不再被 放大。函数里含有求解器时,这个舍入就是前向求解的舍入,约为 的若干倍:这里 在 0.44 到 1.20 之间,而 只有 0.061;后面表中复数步长与伴随、切线梯度之差在 到 之间。截断误差 与中心差分的大小相同、符号相反。 时,无限制器线性的三个分量的截断误差都不超过 ,完全看不见。图 52 用一个看得见的 画出这一步。
The derivative is read off the imaginary part directly; nothing nearly equal is subtracted, so the rounding is no longer amplified by . When the function contains a solver, the rounding is that of the tangent solve, a modest multiple of : here lies between 0.44 and 1.20, while is only 0.061; in the table below the complex step differs from the adjoint and the tangent gradients by to . The truncation error has the size of the central difference's, with the opposite sign. At it is at most for the three components of the unlimited linear scheme: invisible. Figure 52 draws the step with a visible .
对代码的要求:只用解析运算
What the code must be: analytic operations only
复数步长要求程序的每一步都是某个解析函数在实轴上的限制。加、减、乘、除和 、 都是;绝对值、、、按符号分支的 if 都不是。第 5 节的残差只用了加减乘除,设计里的 、 通过 和 进入:Rusanov 速度是常数 ,没有 ;三种斜率是 、 和光滑限制器 。光滑限制器的分母在实轴上不小于 ,所以它在每个实状态附近都是解析的,没有开关:三种重构都可以做复数步长。 可逆时,由隐函数定理,离散解随设计解析地变化, 也就可以解析延拓。minmod 之类的限制器在开关处不可导,复数步长会默默地跟着实部选中的分支走。
The complex step needs every operation of the program to be the restriction of an analytic function to the real axis. Addition, subtraction, multiplication, division, and are; an absolute value, , and an if on a sign are not. The residual of Section 5 uses only the four operations, and the and of the design enter through and : the Rusanov speed is the constant , there is no , and the three slopes are , and the smooth limiter . The denominator of the smooth limiter is at least on the real axis, so it is analytic near every real state and has no switch: all three reconstructions can be complex-stepped. Where is invertible, the implicit function theorem makes the discrete solution an analytic function of the design, and with it . A limiter such as minmod is not differentiable at its switch, and the complex step would silently follow the branch the real parts select.
model.js 的 residualComplex 就是第 5 节的残差代码,只把算术换成复数:实部和虚部各存一个数组,语句一条不改;,。其中没有一行是手工求导的,所以它是对第 8 节前向语句的独立检验。
The function residualComplex of model.js is the residual code of Section 5 with the arithmetic replaced: the real and the imaginary parts are two arrays, and not one statement changes; and . Not one line of it is differentiated by hand, which makes it an independent check of the tangent statements of Section 8.
复数版本必须用代数平方 ,不能用模的平方 :共轭会把虚部消掉。目标 是线性的,所以 。
The complex version must use the algebraic square , never the squared modulus : the conjugate cancels the imaginary part. The objective is linear, so .
状态也变成复数
The state becomes complex
设计 让残差变成复数,离散解也随之是复数。在 处展开:
The design makes the residual complex, and with it the discrete solution. Expand about :
虚部除以 就是第 10 节的切线解。函数里含有求解器时,复数步长本身就是一次复数求解:要解复数残差 。用 Newton 法,Jacobian 取实部的 :不动点 与所用矩阵无关,舍去复 Jacobian 的 虚部不改变结果;但它使修正量的虚部改变 (只有接近收敛时才可忽略),并使虚部晚一步收敛。把残差的虚部展开:
Its imaginary part over is the tangent solution of Section 10. When the function contains a solver, the complex step is itself a complex solve: the complex residual must be solved. Use Newton with the real Jacobian : the fixed point does not depend on the matrix, so dropping the imaginary part of the complex Jacobian leaves the result unchanged; it changes the imaginary part of a correction by (negligible only near convergence) and makes the imaginary part lag one step. Expand the imaginary part of the residual:
令它为零,正是第 10 节的切线方程 :虚部的一次 Newton 修正就是一次前向求解(乘以 )。于是
Setting it to zero is exactly the tangent equation of Section 10: one Newton correction of the imaginary part is one tangent solve (times ). Hence
下面的播放器从页面的初值 出发做复数 Newton 迭代(),每一步用当前实部的 Jacobian。 时实部与第 5 节的原始迭代逐位相同(; 时 项约为 ,远小于 实部的最后一位)。虚部晚一步: 恰好是在实部迭代值 处求得的切线解(每一步都取整步时成立,默认设计下正是如此;差不超过 )。残差实部在 4 次修正后到达舍入水平,虚部在 5 次修正后(图 53)。
The player below runs the complex Newton iteration from the page's initial guess (), with the Jacobian of the current real part at every step. At the real part is the primal iteration of Section 5 bit for bit (; at the terms, about , are far below the last bit of the real parts). The imaginary part lags one step: is exactly the tangent solution at the real iterate (as long as every step is a full step, as at the defaults; to or better). The real residual reaches rounding after 4 corrections, the imaginary one after 5 (Figure 53).
图 54 把收敛后的虚部除以 与第 10 节的切线解画在一起:三个分量的最大差约为 、、。表中是三种重构下 的复数步长梯度,与伴随梯度、切线梯度相差都不超过 ,而第 13 节的中心差分即使在最佳步长处,误差也有 到 。
Figure 54 draws the converged imaginary part over over the tangent solution of Section 10: the largest differences for the three components are about , , . The table lists the complex-step gradients at for the three reconstructions: they differ from the adjoint and the tangent gradients by at most , while the central difference of Section 13 gets only to to at its best step.
| 重构Reconstruction | 分量Component | () at | 与伴随之差Minus adjoint | 与切线之差Minus tangent | 中心差分最佳误差Best central FD |
|---|---|---|---|---|---|
| 一阶first order | −0.745986130503 | 3.3e−16 | 6.7e−16 | 2.0e−11 | |
| 一阶first order | −0.357341645754 | 2.2e−16 | 4.4e−16 | 7.6e−11 | |
| 一阶first order | −0.245483618467 | 5.6e−16 | 4.2e−16 | 3.0e−10 | |
| 无限制器线性unlimited linear | −0.486649677166 | 2.8e−16 | 0 | 3.5e−11 | |
| 无限制器线性unlimited linear | −0.061038915769 | 1.0e−15 | 3.2e−16 | 6.9e−11 | |
| 无限制器线性unlimited linear | −0.309871355162 | 7.8e−16 | 5.0e−16 | 1.9e−10 | |
| 光滑限制器smooth limiter | −0.656527469353 | 5.6e−16 | 6.7e−16 | 7.0e−11 | |
| 光滑限制器smooth limiter | −0.168097300931 | 8.0e−16 | 5.6e−16 | 1.5e−10 | |
| 光滑限制器smooth limiter | −0.290780443262 | 5.6e−16 | 1.2e−15 | 1.3e−10 |
步长扫描:没有 V 形
The step scan: no V
把第 13 节的扫描延长到 (图 55)。 大时复数步长与中心差分的误差大小相同、符号相反: 时 、 分量的比值是 、; 时是 、。 时,分量 的复数步长误差一直低于 ; 分量(以及光滑限制器的 分量)在 时截断误差仍然可见:一阶、无限制器线性、光滑限制器的 分量与光滑限制器的 分量,实测误差依次为 1.82、1.26、1.32、1.00,Taylor 预测 依次为 1.59、1.21、1.37、0.92(单位 )。两者相差不超过 ,这是舍入: 时截断项由实部的 偏移带出(光滑限制器的 分量还有一部分来自残差虚部的 项,它们同样处在舍入水平),而这个偏移只有 的 3.9 到 7.7 倍,只占 的最后几位。三种重构在 的所有点都停在舍入水平(不超过 ),直到 。中心差分则在 到 见底后又回升。
Extend the scan of Section 13 down to (Figure 55). At large the complex step and the central difference have errors of the same size and opposite sign: at their ratio is and for and , at and . For the complex-step error of component stays below ; for (and for the smooth limiter's ) the truncation is still visible at : for the -components of first order, unlimited linear and the smooth limiter and for the smooth limiter's -component the measured errors are 1.82, 1.26, 1.32, 1.00 and the Taylor predictions are 1.59, 1.21, 1.37, 0.92 (in units of ). The two differ by at most , which is rounding: at the truncation is carried by the shift of the real part (for the smooth limiter's -component partly by the terms of the imaginary part of the residual, which are at rounding level too), and that shift is only 3.9 to 7.7 times , the last few bits of . Every point of the three reconstructions with stays at rounding level (at most ), down to . The central difference instead bottoms out at to and rises again.
用复数步长检验 Jacobian 的每一列
Every column of the Jacobians by the complex step
同一个复数程序还能给出 Jacobian 的列。给状态分量 一个虚扰动:,这就是 的列 ,没有用到任何前向语句;给设计虚扰动则得到 。默认设计下,所有列与第 8 节前向语句算出的列的最大差:
The same complex program also gives the columns of the Jacobians. An imaginary perturbation of state component gives , column of , without any tangent statement; an imaginary perturbation of the design gives . At the defaults, the largest differences from the columns computed by the tangent statements of Section 8 are:
| 重构Reconstruction | ( 列) ( columns) | (3 列) (3 columns) |
|---|---|---|
| 一阶first order | 4.4e−16 | 4.4e−16 |
| 无限制器线性unlimited linear | 4.4e−16 | 4.4e−16 |
| 光滑限制器smooth limiter | 8.9e−16 | 4.4e−16 |
这是对第 8、9 节的又一项独立检验:前向语句给出的 与“只换算术”的程序一致;第 9 节的反向语句又是它们的转置(点积检验)。选一列看看:
This is one more independent check of Sections 8 and 9: the and of the tangent statements agree with the program whose only change is its arithmetic, and the reverse statements of Section 9 are their transpose (the dot-product test). Pick a column:
代价与局限
Cost and limits
- 每个分量一次复数求解(中心差分要两次实数求解);复数运算比实数贵几倍(一次复数乘法是 4 次实数乘法);不用调步长, 永远可用。One complex solve per component (the central difference needs two real ones); complex arithmetic costs a few times more than real (one complex multiplication is 4 real ones); no step to tune: always works.
- 要求代码是解析的,并且要有一个复数版本的残差;遇到 、、限制器开关要逐个分析。It needs the code to be analytic and a complex version of the residual; an , a or a limiter switch must be analysed one by one.
- 和差分一样,求解次数随设计分量数增长,而伴随一次求解给出全部分量;它验证的也是 的导数,不回答离散误差的问题(第 15 节)。Like the difference, its number of solves grows with the number of design components, where the adjoint gives all of them with one solve; and it too verifies the derivative of , not the discretisation error (Section 15).
精度阶:网格加密下的误差
Order of accuracy: the errors under refinement
本节实验:先预测,网格加密一倍(),无限制器线性格式的误差缩小几倍?一阶格式呢?从 到 就能看到吗?然后用收敛实验和加密播放器检验。
Experiment: predict first: when the mesh is refined once (), by what factor does the error of the unlimited linear scheme shrink, and that of the first-order scheme? Can you see it already from to ? Then check with the convergence lab and the refinement player.
第 13、14 节检验了导数:伴随梯度就是 的导数(差分验证到误差不超过 ,复数步长到舍入水平)。本节问另一个问题:网格加密时,离散解、离散目标和离散梯度是否趋向构造解的精确值,趋向得多快。不同网格上的 是不同的函数,所以必须先在每个网格上把非线性方程解到舍入水平,再看离散误差。
Sections 13 and 14 checked the derivatives: the adjoint gradient is the derivative of (to or better by differences, to rounding by the complex step). This section asks a different question: as the mesh is refined, do the discrete solution, objective and gradient approach the exact values of the manufactured solution, and how fast? on different meshes are different functions, so on every mesh the nonlinear equations are first solved to rounding and only then is the discretisation error measured.
量什么、和什么比
What is measured, against what
单元平均 与精确单元平均 比较(第 2 节的闭式),目标与 比较,梯度与第 1 节的精确梯度比较;观测阶是相邻两套网格的误差之比以 2 为底的对数( 是 个单元的网格上的某个误差):
The cell averages are compared with the exact cell averages (the closed form of Section 2), the objective with , the gradient with the exact gradient of Section 1; the observed order is the base-2 logarithm of the ratio of the errors on two successive meshes ( is one of these errors on the mesh with cells):
两条事实让这些量互相联系。第一, 精确成立:对每个 ,,因此各单元的一阶矩 加起来恰好为零(本页在 到 实测 )。所以 ,目标误差是状态误差的加权和(权重之和为 )。第二,对设计求导,( 是 的单元平均),所以梯度误差 是切线解误差的加权和。 不依赖 ,所以 , 分量的误差就是离散梯度 本身。
Two facts tie these quantities together. First, holds exactly: for every , , so the first moments of the cells add up to exactly zero (measured for to : ). Hence : the objective error is a weighted sum of the state error (the weights add up to ). Second, differentiating with respect to the design, ( is the cell average of ), so the gradient error is a weighted sum of the error of the tangent solution. As does not depend on , : the error of the -component is the discrete gradient itself.
内部的格式:一阶就是多了黏性
The scheme in the interior: first order is extra viscosity
先回顾第 3 节的两个精确恒等式,它们解释了下面看到的阶。一阶重构(迹等于单元平均)时,内部行 是
First recall the two exact identities of Section 3; they explain the orders seen below. With first-order reconstruction (the traces are the cell averages) an interior row is
除以 ,这正是 的中心差分格式:Rusanov 耗散 等于在物理黏性上加了一个数值黏性 。格式求解的是一个黏性错了 的问题,所以误差是 ;而且 时数值黏性比物理黏性还大(默认 :; 时两者相等)。无限制器线性重构时,内部面 的耗散项是一个三阶差分,
Divided by , this is the central-difference scheme for : the Rusanov dissipation adds a numerical viscosity to the physical one. The scheme solves a problem whose viscosity is wrong by , so its error is ; and for the numerical viscosity is larger than the physical one (default : ; the two are equal at ). With unlimited linear reconstruction the dissipation term of an interior face is a third difference,
两个面相减,一行得到 倍的四阶差分(超黏性),没有二阶导数形式的人工黏性,内部是二阶格式。这两个恒等式第 3 节已经给出并验算过(那里的面板“数值耗散的真面目”在当前状态上再算一遍),伴随方面的推论见第 16 节。本节测量它们对误差的影响。
so a row receives times a fourth difference (a hyperviscosity): no artificial viscosity of second-derivative form, and the interior scheme is second order. Section 3 states both identities and checks them (its panel "What the numerical dissipation really is" recomputes them on the current state); their consequences for the adjoint are in Section 16. This section measures their effect on the errors.
三种重构, 到
Three reconstructions, to
图 56 与下面两张表是本页的模型在 上的结果(默认设计;每个网格都用与上方控件相同的 Newton 迭代解到舍入水平,线性方程用带状 LU 求解)。读法:
Figure 56 and the two tables below are the results of this page's model on (default design; every mesh solved to rounding by the Newton iteration of the controls above, the linear systems by a banded LU). How to read them:
- 一阶:状态误差的观测阶从 0.71、0.86、0.90 稳步升到 0.99(),趋向 1。First order: the observed order of the state error climbs steadily from 0.71, 0.86, 0.90 to 0.99 (): it approaches 1.
- 无限制器线性:粗网格上的阶只有 0.94、1.00(、),像一阶格式;从 起逐渐升高(1.11、1.29、1.51、1.70、1.84、1.91、1.96),二阶要到 才显现(从这里起观测阶不低于 1.83)。原因见下面的 Péclet 数和端单元两小节。Unlimited linear: on coarse meshes the order is only 0.94, 1.00 (, ), like a first-order scheme; from on it rises (1.11, 1.29, 1.51, 1.70, 1.84, 1.91, 1.96), and second order shows only for (from there on the observed order is at least 1.83). The two subsections on the Péclet number and the end cells explain why.
- 光滑限制器:中等网格上接近无限制器线性,之后反而变慢, 时阶为 0.99,误差与一阶格式只差 0.2%:它在细网格上退化为一阶格式(见本节末)。Smooth limiter: close to unlimited linear on intermediate meshes, then slower; at its order is 0.99 and its error differs from the first-order one by only 0.2%: on fine meshes it degenerates to the first-order scheme (end of this section).
| 一阶first order | 无限制器线性unlimited linear | 光滑限制器smooth limiter | ||||
|---|---|---|---|---|---|---|
| 8 | 8.8e−2 | — | 5.2e−2 | — | 7.0e−2 | — |
| 16 | 5.4e−2 | 0.71 | 2.7e−2 | 0.94 | 2.8e−2 | 1.32 |
| 32 | 3.0e−2 | 0.86 | 1.4e−2 | 1.00 | 1.4e−2 | 0.96 |
| 64 | 1.6e−2 | 0.90 | 6.3e−3 | 1.11 | 7.5e−3 | 0.95 |
| 128 | 8.4e−3 | 0.92 | 2.6e−3 | 1.29 | 4.5e−3 | 0.73 |
| 256 | 4.4e−3 | 0.94 | 9.1e−4 | 1.51 | 3.2e−3 | 0.49 |
| 512 | 2.2e−3 | 0.96 | 2.8e−4 | 1.70 | 2.0e−3 | 0.66 |
| 1024 | 1.1e−3 | 0.97 | 7.8e−5 | 1.84 | 1.1e−3 | 0.87 |
| 2048 | 5.8e−4 | 0.99 | 2.1e−5 | 1.91 | 5.7e−4 | 0.96 |
| 4096 | 2.9e−4 | 0.99 | 5.3e−6 | 1.96 | 2.9e−4 | 0.99 |
| 一阶first order | 无限制器线性unlimited linear | 光滑限制器smooth limiter | ||||
|---|---|---|---|---|---|---|
| 8 | 6.0e−1 | — | 3.4e−1 | — | 5.1e−1 | — |
| 16 | 3.8e−1 | 0.65 | 2.1e−1 | 0.73 | 2.1e−1 | 1.27 |
| 32 | 2.2e−1 | 0.80 | 1.1e−1 | 0.95 | 1.1e−1 | 1.00 |
| 64 | 1.2e−1 | 0.85 | 4.9e−2 | 1.12 | 4.6e−2 | 1.23 |
| 128 | 6.7e−2 | 0.88 | 2.0e−2 | 1.32 | 1.8e−2 | 1.36 |
| 256 | 3.5e−2 | 0.91 | 6.8e−3 | 1.54 | 1.5e−2 | 0.20 |
| 512 | 1.8e−2 | 0.94 | 2.1e−3 | 1.72 | 1.4e−2 | 0.13 |
| 1024 | 9.4e−3 | 0.97 | 5.8e−4 | 1.85 | 8.7e−3 | 0.69 |
| 2048 | 4.8e−3 | 0.98 | 1.5e−4 | 1.92 | 4.7e−3 | 0.91 |
| 4096 | 2.4e−3 | 0.99 | 3.9e−5 | 1.96 | 2.4e−3 | 0.97 |
自己做一遍:选重构和误差量,网格从 加密到 (当前设计,在浏览器里计算;带状 LU 使每个网格的求解只需几毫秒)。
Do it yourself: pick a reconstruction and an error, refine from to (the current design, computed in the browser; the banded LU keeps every solve to milliseconds).
加密播放器一次只显示一个网格:离散解与精确单元平均、误差的分布,以及到这一步为止的收敛曲线。
The refinement player shows one mesh at a time: the discrete solution against the exact cell averages, the distribution of the error, and the convergence curve up to this step.
Python 程序算到 65536 个单元
The Python runs to 65536 cells
发布仓库里的独立 Python 程序(python/burgers_viscous/solve.py,稀疏 Jacobian、SciPy 的 LU)把无限制器线性算到 和 ,一阶算到 (图 57)。在共同的网格 到 上,本页模型与 Python 程序的状态误差相差不超过 ,梯度误差相差不超过 (Python 的 Newton 迭代在修正量 时停止,这就是差别的来源)。对状态和梯度 分量而言,这证实了预期的阶(无限制器线性趋于 2,一阶趋于 1):无限制器线性格式中,梯度 分量误差的观测阶在 达到 1.99,状态误差的观测阶为 1.99;从 到 的平均阶是 2.00;一阶格式在 达到 0.99(梯度)和 0.99(状态)。
The independent Python program of the publish repository (python/burgers_viscous/solve.py, a sparse Jacobian, SciPy's LU) ran unlimited linear to and and first order to (Figure 57). On the common meshes to this page's model and the Python program differ by at most in the state errors and in the gradient errors (the Python Newton iteration stops at a correction of ; that is where the differences come from). For the state and the gradient component this confirms the expected orders (unlimited linear tends to 2, first order to 1): the observed order of the error of the gradient component of unlimited linear reaches 1.99 at and that of the state error 1.99; from to the mean order is 2.00; first order reaches 0.99 (gradient) and 0.99 (state) at .
Python 程序自己的梯度检验(JSON 中的 gradient_check)用的正是第 13 节说的最危险的组合:以扰动前的解为初值热启动,加绝对容差 。本页在 到 上重算:每次都加上了 2 个修正量,第二个是 到 ,只比容差大一点。所以那个检验仍然独立,但余量很小。
The Python program's own gradient check (gradient_check in the JSON) uses exactly the combination Section 13 calls the most dangerous: a warm start from the unperturbed solution with the absolute tolerance . Recomputed here for to : every solve added 2 corrections, the second of to , just above the tolerance. So that check is still independent, but narrowly.
| 状态State | 目标Objective | 梯度 分量Gradient component | 梯度 分量Gradient component | 梯度 分量Gradient component | 求解Solve | ||||||
|---|---|---|---|---|---|---|---|---|---|---|---|
| Newton 修正Newton corr. | 秒seconds | ||||||||||
| 32 | 1.4e−2 | — | 2.0e−2 | 1.1e−1 | — | 4.3e−2 | — | 9.5e−2 | — | 2.4e−16 | 0.0604 |
| 64 | 6.3e−3 | 1.11 | 9.5e−3 | 4.9e−2 | 1.12 | 2.8e−2 | 0.64 | 1.8e−1 | −0.93 | 3.2e−16 | 0.0082 |
| 128 | 2.6e−3 | 1.29 | 3.9e−3 | 2.0e−2 | 1.32 | 1.4e−2 | 1.04 | 1.6e−1 | 0.20 | 8.6e−12 | 0.0088 |
| 256 | 9.1e−4 | 1.51 | 1.4e−3 | 6.8e−3 | 1.54 | 5.3e−3 | 1.36 | 8.5e−2 | 0.89 | 7.9e−14 | 0.0090 |
| 512 | 2.8e−4 | 1.70 | 4.2e−4 | 2.1e−3 | 1.72 | 1.7e−3 | 1.62 | 3.3e−2 | 1.37 | 5.2e−16 | 0.0105 |
| 1024 | 7.8e−5 | 1.84 | 1.2e−4 | 5.8e−4 | 1.85 | 4.9e−4 | 1.79 | 1.0e−2 | 1.66 | 2.1e−16 | 0.0127 |
| 2048 | 2.1e−5 | 1.91 | 3.1e−5 | 1.5e−4 | 1.92 | 1.3e−4 | 1.89 | 3.0e−3 | 1.82 | 1.9e−16 | 0.0176 |
| 4096 | 5.3e−6 | 1.96 | 8.1e−6 | 3.9e−5 | 1.96 | 3.5e−5 | 1.94 | 7.9e−4 | 1.91 | 1.1e−11 | 0.0240 |
| 8192 | 1.4e−6 | 1.98 | 2.0e−6 | 9.9e−6 | 1.98 | 8.8e−6 | 1.97 | 2.0e−4 | 1.95 | 7.0e−13 | 0.0433 |
| 16384 | 3.4e−7 | 1.99 | 5.2e−7 | 2.5e−6 | 1.99 | 2.2e−6 | 1.99 | 5.2e−5 | 1.98 | 4.5e−14 | 0.0781 |
| 65536 | 2.1e−8 | 2.00 | 3.2e−8 | 1.6e−7 | 2.00 | 1.4e−7 | 1.99 | 3.3e−6 | 1.99 | 2.4e−16 | 0.2993 |
| 状态State | 目标Objective | 梯度 分量Gradient component | 梯度 分量Gradient component | 梯度 分量Gradient component | 求解Solve | ||||||
|---|---|---|---|---|---|---|---|---|---|---|---|
| Newton 修正Newton corr. | 秒seconds | ||||||||||
| 32 | 3.0e−2 | — | 4.1e−2 | 2.2e−1 | — | 2.3e−2 | — | 5.2e−1 | — | 2.0e−12 | 0.0074 |
| 64 | 1.6e−2 | 0.90 | 2.3e−2 | 1.2e−1 | 0.85 | 6.8e−3 | 1.76 | 3.2e−1 | 0.69 | 1.9e−14 | 0.0081 |
| 128 | 8.4e−3 | 0.92 | 1.2e−2 | 6.7e−2 | 0.88 | 1.1e−2 | −0.70 | 1.5e−1 | 1.08 | 2.1e−16 | 0.0077 |
| 256 | 4.4e−3 | 0.94 | 6.7e−3 | 3.5e−2 | 0.91 | 8.4e−3 | 0.40 | 5.8e−2 | 1.38 | 1.8e−16 | 0.0093 |
| 512 | 2.2e−3 | 0.96 | 3.5e−3 | 1.8e−2 | 0.94 | 5.2e−3 | 0.71 | 1.9e−2 | 1.59 | 1.5e−16 | 0.0099 |
| 1024 | 1.1e−3 | 0.97 | 1.8e−3 | 9.4e−3 | 0.97 | 2.9e−3 | 0.85 | 6.0e−3 | 1.68 | 5.4e−12 | 0.0123 |
| 2048 | 5.8e−4 | 0.99 | 9.0e−4 | 4.8e−3 | 0.98 | 1.5e−3 | 0.93 | 1.9e−3 | 1.66 | 3.5e−13 | 0.0133 |
| 4096 | 2.9e−4 | 0.99 | 4.5e−4 | 2.4e−3 | 0.99 | 7.7e−4 | 0.96 | 6.4e−4 | 1.56 | 2.2e−14 | 0.0203 |
上表:无限制器线性(含 单元的一行,阶按两次加密平均);下表:一阶。误差经过零点的地方,阶没有意义(见本节末)。二阶收敛不只是范数的性质:把误差分布乘以 ,四套网格的曲线逐渐重合(图 58: 依次为 101.0、110.5、113.2、113.8)。
Top table: unlimited linear (with the row of cells, its orders averaged over two refinements); bottom: first order. Where an error passes through zero, its order means nothing (end of this section). Second-order convergence is not only a property of the norms: multiplied by , the error profiles of four meshes come together (Figure 58: is 101.0, 110.5, 113.2 and 113.8).
图 59 是程序在 JSON 里记录的每个网格的耗时(记录的平台字符串 macOS-26.6.2-arm64-arm-64bit-Mach-O 只写了操作系统和处理器架构,不是机器型号)。一个网格的耗时包括 Newton 求解、三个前向求解与一个伴随求解、两次差分检验的求解;无限制器线性的各个网格(refinement_none.json)还包括与连续伴随的比较(在单元中心求连续伴随的值,加两个数值积分),其中 这个网格还包括一次连续伴随边值问题的求解; 个单元的那次单独运行和一阶的各个网格都不含这些。稀疏 LU 使耗时大致随 线性增长: 个单元 0.30 秒。
Figure 59 shows the time per mesh that the program recorded in the JSON (the recorded platform string, macOS-26.6.2-arm64-arm-64bit-Mach-O, names the operating system and the architecture, not the machine). The time of a mesh includes the Newton solve, three tangent solves and one adjoint solve, and the two solves of the difference check; the unlimited-linear rows (refinement_none.json) also include the comparison with the continuous adjoint (evaluating it at the cell centres and two quadratures), and the row the solution of the continuous-adjoint boundary-value problem; the row of cells (a separate run) and the first-order rows include none of this. The sparse LU keeps the time roughly linear in : 0.30 seconds for cells.
观测阶如何依赖网格 Péclet 数
How the observed order depends on the cell Péclet number
单元宽度与黏性长度 之比就是网格 Péclet 数 (默认 , 时 )。把六个黏性 的加密以 为横轴画在一起(图 60,无限制器线性):误差本身随 不同,但观测阶几乎只是 的函数。除去每个黏性最粗的一对网格(,这些点落在共同曲线之下),同一个 处六个黏性的阶最多相差 0.07()和 0.09()。 时阶为 0.94 到 1.04, 时为 1.49 到 1.54, 时至少 1.91;默认 时网格序列要到 才满足 。
The ratio of the cell width to the viscous length is the cell Péclet number (at the defaults , and at ). Draw the refinements of six viscosities with as the horizontal axis (Figure 60, unlimited linear): the errors themselves differ with , but the observed order is almost a function of alone. Leaving out the coarsest pair of each viscosity (, whose points lie below the common curve), at the same the orders of the six viscosities differ by at most 0.07 () and 0.09 (). The order is 0.94 to 1.04 at , 1.49 to 1.54 at , and at least 1.91 for ; with the default the mesh sequence reaches only at .
起作用的不是解的边界层( 是一个正弦波,没有层),而是离散误差在出口处宽度约 的层: 时 从 处()的 升到 处的 。这个层由端单元产生:端单元重构后,同一段上的 只在 到 之间。对状态误差而言,阶对 的依赖来自端单元:端单元重构后(下一小节), 时状态误差在 为 22.9、11.4、5.7、2.9、1.4 处的观测阶依次是 2.56、2.31、2.13、2.09、2.02, 时已是二阶。端单元重构后,梯度误差的阶仍然上升得很慢(见下一小节);原因本页不作推断。
The layer that matters is not one of the solution ( is a sine wave and has none) but one of the discrete error, at the outlet, with a width of about : at , rises from at () to at . The end cells produce it: with them reconstructed, stays between and on the same stretch. For the state error, the dependence of the order on comes from the end cells: with the end cells reconstructed (next subsection), the observed orders of the state error at are 2.56, 2.31, 2.13, 2.09, 2.02 at of 22.9, 11.4, 5.7, 2.9, 1.4, second order already at . With the end cells reconstructed, the order of the gradient error still rises slowly (next subsection); the page claims no cause for that.
一阶格式没有这样的过渡:它的误差由数值黏性 决定,几乎与 无关—— 时六个黏性的梯度 分量误差都在 到 之间。
The first-order scheme shows no such transition: its error is set by the numerical viscosity and hardly depends on : at the errors of the gradient component for the six viscosities all lie between and .
自己选几个黏性:当前的 ,每个 从 加密到 (在浏览器里计算)。
Pick some viscosities: the current , every refined from to (computed in the browser).
两个不重构的端单元
The two end cells that are not reconstructed
第 3 节规定 :两端的单元不重构,面 、 和 、 用的迹只有一阶精度,这四个面的通量误差是 。它们对误差贡献多大?做一个实验(只是本节的实验,不是本页的格式):用虚单元值 、(过边界值的直线)按内部公式给端单元一个斜率,其他一切不变;这个变体的 Jacobian 用复数步长得到,所以它的切线与伴随梯度也是精确的。结果(图 61):状态误差小了 到 倍,从 起状态误差的观测阶就在 1.76 到 2.54 之间; 时 从 110.5 降到 1.23,内部的误差也一起变小。所以无限制器线性格式的状态误差在粗网格上表现为一阶、数值偏大,主要是这两个端单元造成的。梯度 分量的误差在 时小 到 倍,比值几乎不变,所以从 起它的观测阶与本页格式相差不到 0.05;更粗的网格上变体的阶忽高忽低( 到 :4.17、2.11、0.28、1.03,本页格式为 0.73、0.95、1.12、1.32),也不是二阶。端单元决定梯度误差的大小,而不是它迟迟才到二阶。
Section 3 sets : the two end cells are not reconstructed, the traces used by faces , and , are only first-order accurate, and the fluxes of these four faces carry errors. How much do they contribute? An experiment (an experiment of this section only, not the page's scheme): give the end cells a slope by the interior formula with the ghost values and (the line through the boundary value), with everything else unchanged; the Jacobians of this variant come from the complex step, so its tangent and adjoint gradients are exact too. The result (Figure 61): the state error is to times smaller, and the observed order of the state error lies between 1.76 and 2.54 from on; at , drops from 110.5 to 1.23, the interior error with it. So the first-order look of the state error of the unlimited linear scheme on coarse meshes, and its size, come mainly from these two end cells. The error of the gradient component is to times smaller for , an almost constant ratio, so from on its observed order equals the page scheme's to within 0.05; on coarser meshes the variant's order is erratic ( to : 4.17, 2.11, 0.28, 1.03, against 0.73, 0.95, 1.12, 1.32) and not second order. The end cells set the size of the gradient error, not its slow approach to second order.
这个变体只用于说明误差的来源。本页其他各节(以及 Python 程序)用的都是不重构端单元的格式,第 13、14 节验证的也是它的导数。
The variant only serves to locate the error. Every other section of this page (and the Python program) uses the scheme without end-cell reconstruction, and Sections 13 and 14 verified its derivatives.
固定 的光滑限制器在细网格上是一阶的
With a fixed the smooth limiter is first order on fine meshes
光滑限制器的分母里有一个固定的 。网格加密时单侧差 越来越小,一旦比 小,就有
The denominator of the smooth limiter contains a fixed . Under refinement the one-sided differences keep shrinking, and once they are smaller than ,
斜率修正 变成 ,迹与一阶格式的迹只差 ,格式的主误差就是一阶格式的 。转折发生在 ,即 。图 62 在每个网格的解上量光滑斜率与中心斜率之比(内部单元的中位数): 时 0.63,先升到 的 0.92,然后一路降到 的 。反过来,若 ,由 的公式(取 ),迹与无限制器线性的迹之差约为 ;要保持二阶, 不仅要比 缩小得更快,还要满足 。本页不讨论这种做法。
The slope correction becomes , the traces differ from the first-order ones by , and the leading error of the scheme is the error of the first-order scheme. The switch happens at , i.e. . Figure 62 measures the ratio of the smooth slope to the central slope on the solution of every mesh (the median over the interior cells): 0.63 at , it first rises to 0.92 at , then falls all the way to at . Conversely, where the formula of (with ) puts the traces about away from the unlimited linear ones; to stay second order, must not only shrink faster than but satisfy . This page does not pursue that.
观测阶说明了什么,没有说明什么
What the observed orders show, and what they do not
- 证实:对状态和梯度 分量而言,无限制器线性的阶趋于 2(Python,:1.99、1.99),一阶趋于 1(:0.99、0.99);本页模型在 到 上重现了这些数据(状态误差相差不超过 ,梯度误差相差不超过 )。Confirmed: for the state and the gradient component , the orders of unlimited linear tend to 2 (Python, : 1.99, 1.99) and those of first order to 1 (: 0.99, 0.99); this page's model reproduces these data on to (to at most in the state errors and in the gradient errors).
- 梯度误差不单调。有符号的梯度误差会变号:一阶格式的 分量在 与 之间变号;无限制器线性格式的 分量在 与 之间变号;无限制器线性格式的 分量在 与 之间变号;光滑限制器的 分量在 与 之间变号;光滑限制器的 分量在 与 、 与 之间变号。变号处误差经过零点,相邻网格的“阶”可以是负数或大于 2,没有意义。The gradient errors are not monotone. The signed gradient errors change sign: the -component of the first-order scheme between and ; the -component of the unlimited linear scheme between and ; the -component of the unlimited linear scheme between and ; the -component of the smooth limiter between and ; the -component of the smooth limiter between and , and . Where they do, the error passes through zero and the "order" of two successive meshes can be negative or larger than 2; it means nothing there.
- 一阶格式的 分量还没有进入渐近区。 到 的观测阶依次是 0.69、1.08、1.38、1.59、1.68、1.66、1.56;本页用同一格式继续加密,阶降为 1.42、1.27、1.16、1.09( 到 ),趋向 1,到 仍是 1.09。The -component of first order is not yet asymptotic. Its observed orders from to are 0.69, 1.08, 1.38, 1.59, 1.68, 1.66, 1.56; continued with the same scheme on this page, they fall to 1.42, 1.27, 1.16, 1.09 ( to ), towards the order 1, and are still 1.09 at .
- 光滑限制器不是二阶格式。 固定时它在细网格上退化为一阶(上一小节)。The smooth limiter is not a second-order scheme. With a fixed it degenerates to first order on fine meshes (previous subsection).
- 无限制器线性的状态误差在粗网格上表现为一阶,这主要来自两个不重构的端单元;端单元也使梯度误差在 时大 123 到 275 倍( 时 11 倍),但从 起不改变它的阶。The state error of unlimited linear looks first order on coarse meshes; comes mainly from the two end cells that are not reconstructed; the end cells also make the gradient error 123 to 275 times larger for (11 times at ), but from on they do not change its order.
数据的来源
Where the data come from
本节的 Python 数字都读自下列文件;构建页面时检查每个文件记录的实现哈希与当前的 solve.py(SHA-256 0d36ac3febcd7e74…)一致,并且标明 mathematical_proof = false。它们和本页的计算一样是数值证据,不是数学证明。
The Python numbers of this section are read from the files below; when the page is built, the implementation hash recorded in every file is checked against the current solve.py (SHA-256 0d36ac3febcd7e74…), and every file must say mathematical_proof = false. Like this page's own computations, they are numerical evidence, not mathematical proof.
| 文件File | 命令Command | 版本 · 生成时间Version · generated | 环境Environment | 文件 SHA-256File SHA-256 | 数学证明Mathematical proof |
|---|---|---|---|---|---|
examples/refinement_first.json | python3 solve.py --grids 32,64,128,256,512,1024,2048,4096 --nu .01 --reconstruction first --gradient-check --output examples/refinement_first.json | 1.0.0 · 2026-10-02T08:35:51Z | Python 3.14.3 · NumPy 2.4.6 · SciPy 1.18.1 macOS-26.6.2-arm64-arm-64bit-Mach-O | 867fa572cfd79723… | 否(false)false |
examples/refinement_none.json | python3 solve.py --grids 32,64,128,256,512,1024,2048,4096,8192,16384 --nu .01 --reconstruction none --gradient-check --continuous-adjoint --output examples/refinement_none.json | 1.0.0 · 2026-10-02T08:35:50Z | Python 3.14.3 · NumPy 2.4.6 · SciPy 1.18.1 macOS-26.6.2-arm64-arm-64bit-Mach-O | 1bfc1b10868196bf… | 否(false)false |
examples/large_65536.json | python3 solve.py --cells 65536 --nu .01 --reconstruction none --gradient-check --output examples/large_65536.json | 1.0.0 · 2026-10-02T08:35:53Z | Python 3.14.3 · NumPy 2.4.6 · SciPy 1.18.1 macOS-26.6.2-arm64-arm-64bit-Mach-O | 1241daca39fb2433… | 否(false)false |
本节的结论都是在一个设计(,,;Péclet 研究另有五个 )上的数值观察。本页没有证明一般的收敛定理,也不声称这些阶对别的设计、别的源项或 成立。
The conclusions of this section are numerical observations at one design (, , ; five more values of in the Péclet study). The page proves no general convergence theorem and does not claim these orders for other designs, other sources, or .
伴随一致性:离散伴随会趋于连续伴随吗?
Adjoint consistency: does the discrete adjoint approach the continuous one?
本节实验:先估计一下, 时第一个单元里的 离 大约有多远?要多少个单元, 与 的相对差才不到 10%——一百个左右,还是一千个左右?然后用本节的加密播放器验证。
Experiment: Estimate first how far is from in the first cell at , and how many cells needs to come within 10% of , about a hundred or about a thousand. Then check with the refinement player of this section.
第 11 节解出了伴随 (),第 12 节用它给出离散目标 的梯度 (精确到舍入误差)。附录 A 则从微分方程出发,推出连续伴随 和连续梯度公式。两者回答不同的问题: 是有限网格上那段程序的导数, 是连续问题的导数。本节把它们放在一起比较:随网格加密, 是否趋于 ?离散梯度(包括边界项)是否趋于连续梯度?粗网格上的差别从哪里来?
Section 11 solved for the adjoint () and Section 12 used it for the gradient of the discrete objective , exact up to rounding. Appendix A starts from the differential equation and derives the continuous adjoint and the continuous gradient formula. The two answer different questions: is a derivative of the program on a finite mesh, a derivative of the continuous problem. This section puts them side by side: does approach as the mesh is refined? Does the discrete gradient, boundary term included, approach the continuous one? Where do the differences on coarse meshes come from?
连续伴随与连续梯度
The continuous adjoint and the continuous gradient
连续伴随(附录 A 用变分逐步推出)是一个线性两点边值问题。连续梯度有两部分:源项积分和边界项。这里 , 在 、 处就是边界值 、 的导数:
The continuous adjoint (derived step by step with variations in Appendix A) solves a linear two-point boundary-value problem. The continuous gradient has two parts, a source integral and a boundary term. Here , and at and is the derivative of the boundary values , :
对 ,通量本身含 ,公式变为 ;对这一族构造解它等于零。伴随方程的对流项 与原方程符号相反: 从 向 传播,所以 要靠 处宽约 的边界层来满足。默认设计下(201 个点的 Chebyshev 配点),,;源项积分为 ,边界项为 ,两者之和与精确梯度 相差 量级(本页和 Python 审计都核对过)。注意两部分都是 ,梯度却小得多:它是两个大数之差。
For the flux itself contains and the formula becomes , which is zero for this family. The convection term of the adjoint equation has the opposite sign of the primal one: travels from towards , so is met by a boundary layer at of width about . At the defaults (Chebyshev collocation on 201 points) and ; the source integral is , the boundary term , and their sum differs from the exact gradient by about (checked by this page and by the Python audit). Note that both parts are while the gradient is much smaller: it is the difference of two large numbers.
“一致”在这里指什么,哪些只是证据
What “consistent” means here, and what is only evidence
本节说离散伴随与连续伴随一致,指的是固定设计、加密网格时测到三件事:(1) 逐点误差 和单元 L1 范数 趋于零;(2) 离散梯度 趋于连续梯度;(3) 离散梯度的源项部分和边界部分分别趋于源项积分和边界项。本节的测量用模型算到 ,用 Python 数据算到 。这只是本问题上的数值证据,不是定理:本页不给出一般数据下的收敛定理。
In this section the discrete adjoint is called consistent with the continuous one when three things are measured at a fixed design under mesh refinement: (1) the pointwise error and the cell L1 norm tend to zero; (2) the discrete gradient tends to the continuous gradient; (3) its source part and its boundary part tend to the source integral and the boundary term separately. The measurements go to with the model and to with the Python data. This is numerical evidence for this one problem, not a theorem: the page gives no convergence theorem for general data.
为什么可以直接拿 和 比——同号,也不差一个网格宽度的因子?比较两个拉格朗日函数:
Why can be compared with directly, with the same sign and without a factor of the mesh width? Compare the two Lagrangians:
是积分形式的残差:;同样 。所以当 时 :同号,没有因子 。伴随方程也对得上:,右端项 。如果残差除以单元宽度(“密度”形式),乘子就会变成 ;比较之前要先确认这种约定。
is an integrated residual, , and likewise . So when : the same sign and no factor . The adjoint equations match too: and the right-hand side is . Had the residual been divided by the cell width (a density), the multiplier would be ; check this convention before comparing.
读图: 时 ,而 。单元宽 是伴随层宽的 11.4 倍,第一个单元根本“看不见” 的这个层。远离入口处误差小得多: 时只有 0.119,不到最大值的 1/4。在下面的面板里换 、重构和设计,看误差的位置和大小怎样变:
Reading the figure: at , while . The cell width is 11.4 times the width of the adjoint's layer; the first cell simply does not see the layer of . Away from the inlet the error is much smaller: 0.119 for , less than 1/4 of the maximum. Change , the reconstruction and the design in the panel below and watch where the error sits and how large it is:
加密:误差与收敛阶
Refinement: the errors and their orders
表中是 的单元 L1 范数和最大值(模型算到 ;最后一列是 Python 数据 adjoint_l1_reference_error,它的 由 SciPy 的 solve_bvp 独立求得,在共同网格上与本页至少有 4 位有效数字相同)。“阶”是相邻两层的观测阶 。
The table lists the cell L1 norm and the maximum of (the model up to ; the last column is the Python field adjoint_l1_reference_error, whose comes independently from SciPy's solve_bvp and shares at least 4 significant digits with this page on the common meshes). “Order” is the observed order between neighbouring levels.
| N | 一阶 L1first L1 | 阶order | 一阶最大值first max | 无限制器 L1none L1 | 阶order | 无限制器最大值none max | Python L1(无限制器)Python L1 (none) |
|---|---|---|---|---|---|---|---|
| 8 | 1.36e−1 | 5.53e−1 | 1.25e−1 | 5.54e−1 | — | ||
| 16 | 9.21e−2 | 0.57 | 5.86e−1 | 7.05e−2 | 0.83 | 6.10e−1 | — |
| 32 | 5.34e−2 | 0.79 | 4.69e−1 | 3.37e−2 | 1.07 | 4.85e−1 | 3.37e−2 |
| 64 | 2.89e−2 | 0.88 | 3.72e−1 | 1.41e−2 | 1.26 | 2.94e−1 | 1.41e−2 |
| 128 | 1.52e−2 | 0.93 | 2.57e−1 | 5.40e−3 | 1.38 | 1.94e−1 | 5.40e−3 |
| 256 | 7.86e−3 | 0.95 | 1.65e−1 | 1.82e−3 | 1.57 | 8.56e−2 | 1.82e−3 |
| 512 | 4.01e−3 | 0.97 | 9.36e−2 | 5.47e−4 | 1.74 | 2.92e−2 | 5.47e−4 |
| 1024 | 2.03e−3 | 0.98 | 5.03e−2 | 1.51e−4 | 1.85 | 8.60e−3 | 1.51e−4 |
| 2048 | 1.02e−3 | 0.99 | 2.61e−2 | 3.99e−5 | 1.92 | 2.34e−3 | 3.99e−5 |
| 4096 | 5.11e−4 | 1.00 | 1.33e−2 | 1.03e−5 | 1.96 | 6.10e−4 | 1.03e−5 |
| 8192 | — | — | — | — | 2.60e−6 | ||
| 16384 | — | — | — | — | 6.54e−7 |
结论(测量):两种格式的离散伴随都收敛到连续伴随,无限制器线性二阶、一阶格式一阶,最大误差和 L1 误差同阶。粗网格上情形不同:无限制器线性格式的最大误差在默认设计下停在头两个单元里, 时 0.554, 时反而升到 0.610, 时仍有 0.294。这些网格都分辨不出伴随在入口处的层(),但误差主要并非来自伴随的入口层,下一小节的实验说明了这一点。
Conclusion (measured): for both schemes the discrete adjoint converges to the continuous one, at second order for unlimited linear reconstruction and at first order for the first-order scheme, with the same order in the maximum and the L1 norm. Coarse meshes are different: at the default design the maximum error of the unlimited linear scheme stays in the first two cells, 0.554 at , even rising to 0.610 at , and still 0.294 at . None of these meshes resolves the adjoint's layer at the inlet (), but the error does not come mainly from the layer: the experiment of the next subsection shows it.
粗网格上的误差从哪里来:两个端单元
Where the coarse-mesh error comes from: the two end cells
第 3 节规定 :两端的单元不重构。第 15 节用一个实验测过它们对状态误差的影响:用虚单元值 、 按内部公式给两个端单元一个斜率,其他一切不变(这只是实验,不是本页的格式)。这里用同一段代码(第 15 节的变体,Jacobian 由复数步长得到)看伴随和梯度。网格分辨伴随层的能力完全没变,伴随的最大误差和 分量的梯度误差却大幅下降:
Section 3 sets : the two end cells are not reconstructed. Section 15 measured what this does to the state error with an experiment: give the two end cells a slope by the interior formula with the ghost values and , with everything else unchanged (an experiment only, not the page's scheme). Here the same code (the variant of Section 15, whose Jacobians come from the complex step) is used for the adjoint and the gradient. The mesh resolves the adjoint's layer no better than before, yet the largest adjoint error and the -gradient error drop sharply:
| N | (本页) (page) | (端单元重构) (end cells reconstructed) | 的误差(本页)-gradient error (page) | 的误差(端单元重构)-gradient error (end cells reconstructed) |
|---|---|---|---|---|
| 8 | 0.554 | 0.238 | −3.43e−1 | −3.03e−2 |
| 16 | 0.610 | 0.261 | −2.07e−1 | −1.68e−3 |
| 32 | 0.485 | 0.182 | −1.07e−1 | −3.90e−4 |
| 64 | 0.294 | 0.049 | −4.94e−2 | −3.22e−4 |
重构端单元后,伴随的层同样没被分辨, 分量的梯度误差却从 时的 0.343 降到 0.030, 时从 0.107 降到 ; 在 时从 0.554 降到 0.238, 时从 0.294 降到 0.049。 时 变为 −1.313,接近 在第一个单元上的平均 −1.351,而斜率比 仍只有 0.147。所以粗网格上,无限制器线性重构的伴随最大误差 和 分量的梯度误差主要来自两个只有一阶精度的端单元,而不是伴随的层没被分辨本身。光滑限制器的情形相近: 时重构端单元后, 分量的梯度误差降到原来的 1/5.6 至 1/28,伴随最大误差降到原来的 1/1.9 至 1/7.2。对无限制器线性重构, 的 L1 误差并非如此: 时重构端单元后反而略大(0.141,本页格式为 0.125); 与 分量的梯度误差在 时也只分别降为 1/2.2 与 1/1.4(光滑限制器的 分量梯度误差甚至从 0.291 增大到 0.412)。
With the end cells reconstructed the adjoint's layer is just as unresolved, but the -gradient error drops from 0.343 to 0.030 at and from 0.107 to at ; drops from 0.554 to 0.238 at and from 0.294 to 0.049 at . At , becomes −1.313, close to the mean of over the first cell, −1.351, while the slope ratio is still only 0.147. So on coarse meshes the largest adjoint error, , and the -gradient error of unlimited linear reconstruction come mainly from the two end cells, which are only first-order accurate, not from the adjoint's unresolved layer itself. The smooth limiter behaves similarly: for reconstructing its end cells makes the -gradient error 5.6 to 28 times and the largest adjoint error 1.9 to 7.2 times smaller. For unlimited linear reconstruction the L1 error of does not: at it is even slightly larger with the end cells reconstructed (0.141 against 0.125), and the - and -gradient errors fall only by factors of 2.2 and 1.4 at (with the smooth limiter the -gradient error even grows, from 0.291 to 0.412).
播放器逐层演示加密过程,可以在本页格式、端单元重构的实验和本节最后要讲的错误版本之间切换:
The player shows the refinement level by level; it switches between the page's scheme, the end-cell experiment and the wrong version of the last part of this section:
一阶格式就是黏性为 ν + h 的中心格式
First order is the central scheme with viscosity ν + h
为什么一阶离散伴随的层那么宽?看一阶格式(,,)的一个内部单元 ()。它的两个面都是内部面(),代入第 3 节的通量:
Why is the layer of the first-order discrete adjoint so wide? Take the first-order scheme (, , ) and an interior cell (). Both of its faces are interior faces (); insert the flux of Section 3:
抵消,两个跳跃合成一个二阶差分,耗散与黏性合并:
The terms cancel, the two jumps combine into one second difference, and the dissipation joins the viscosity:
方括号正是 的中心差分:Rusanov 耗散系数 相当于额外黏性 (即 )。再看转置: 的第 列只有三个非零元,
The bracket is exactly the central difference of : the Rusanov dissipation coefficient acts as an added viscosity (that is ). Now transpose: column of has three nonzero entries,
所以伴随方程的第 行是
so row of the adjoint equation reads
除以 :一阶离散伴随的内部各行,正是黏性为 的连续伴随 的中心格式(对流系数取离散状态 )。这是恒等式,不是近似:Python 审计用 sympy 符号化简验证,本页在收敛解上逐行核对,差在 以内。所以一阶离散伴随的层宽约 而不是 : 时前者是后者的 13.5 倍。两个端行含边界面(距离 ),不属于这个形式。
Divide by : the interior rows of the first-order discrete adjoint are the central scheme for the continuous adjoint with viscosity , (with the discrete state as the convection coefficient). This is an identity, not an approximation: the Python audit proves it with sympy, and this page checks it row by row on the converged states, to within . The layer of the first-order discrete adjoint is therefore about wide instead of : 13.5 times wider at . The two end rows contain the boundary faces (distance ) and are not of this form.
二阶格式在内部没有这一项。无限制器线性重构 时,面 的迹之差是一个三阶差分,行 的耗散是一个四阶差分:
In the interior the second-order scheme has no such term. With unlimited linear reconstruction, , the jump of the traces of a face is a third difference, and the dissipation of a row is a fourth difference:
这是超黏性:单位长度上为 ,没有二阶导数型的人工黏性(sympy 证明;本页在随机状态上核对,差在 以内)。但这两个恒等式只在上述范围内成立。两端的单元不重构(),所以面 与 的迹只有一阶精度:用精确单元平均代入(), 在面 0、1、、 上分别为 0.5001、0.4998、0.5002、0.4999,在面 2 上只有 。也就是 :无限制器线性重构里这四个面都带额外黏性 ;一阶格式里只有面 和面 如此,面 与 和每个内部面一样带 ( 时面 1 上的比值为 1.0000);光滑限制器在 时接近无限制器线性, 后面 与 也带 ( 时比值为 0.9992)。耗散矩阵仍是对称的(数值上反对称部分为 0.0),但两个端行是 和 ,不是四阶差分。所以在两端附近,无限制器线性重构的伴随看到的黏性是 ——伴随的入口层正在那里。
This is a hyperviscosity, per unit length, with no second-derivative artificial viscosity (sympy proof; checked by this page on random states to within ). But the two identities hold only in these ranges. The end cells are not reconstructed (), so the traces of faces and are only first-order accurate: with the exact cell averages (), is 0.5001, 0.4998, 0.5002 and 0.4999 on faces 0, 1, and , against on face 2. That is : in unlimited linear reconstruction these four faces carry an added viscosity ; in the first-order scheme only faces and do, faces and carry like every interior face (ratio 1.0000 on face 1 at ); the smooth limiter is close to unlimited linear while , and once it also gives on faces and (ratio 0.9992 at ). The dissipation matrix is still symmetric (its antisymmetric part is 0.0 numerically), but its two end rows are and , not fourth differences. Near both ends the adjoint of unlimited linear reconstruction therefore sees the viscosity , exactly where the adjoint's inlet layer lies.
测量结果: 时两个最大偏差都在伴随的层里;相对 的比相对 的下降快—— 到 1024 的观测阶分别是 1.77 与 0.90。这是过渡阶段: 到 4096 两者都约为一阶(1.07 与 0.97),这时到 的最大偏差已离开层( 时在 )。在层内, 时它只有到 的偏差的 1/26:一阶伴随在层里的主要误差来自额外黏性 。
Measured: up to both largest deviations sit in the adjoint's layer, and the one from falls faster than the one from , with observed orders 1.77 and 0.90 from to 1024. This is a transient: from to 4096 both are about first order (1.07 and 0.97), and the largest deviation from has left the layer (at for ). Inside the layer it is 1/26 of the deviation from at : the leading error of the first-order adjoint in the layer comes from the added viscosity .
等效黏性解释的是一阶离散伴随的层,不是全部误差。 时等效伴随问题的层宽约 0.15,大约一个单元,点离两条曲线都远。在 L1 范数下两个距离相差小得多(:0.00203 与 0.00140,之比 1.45;最大偏差之比为 17.1):到 的 L1 距离有 90% 在层外,到 的却有 71% 在层内(层取 )。到 的 L1 距离中, 的部分来自内部各行的对流系数——离散状态 本身有 误差:把它换成 ,这个距离从 降到 (,观测阶 1.91);两个端行只贡献 。到 的 L1 距离中 的部分则是额外黏性 ,主要在层里(这个距离的 69% 在层内):换成 后它并不减小( 时为 与 )。第一个单元的边界面只带额外黏性 ;这可以直观地(非严格地)解释 为什么落在两条曲线之间( 到 4096 的每个网格上都是如此)。
The effective viscosity explains the layer of the first-order discrete adjoint, not every error. At the layer of the effective adjoint problem is itself about 0.15 wide, about one cell, and the dots are far from both curves. In the L1 norm the two distances differ much less (: 0.00203 and 0.00140, a ratio of 1.45, against 17.1 for the largest deviations): 90% of the L1 distance to lies outside the layer, while 71% of that to lies inside it (the layer taken as ). The part of the distance to comes from the convection coefficient of the interior rows, the discrete state , which has its own error: with in its place this distance falls from to (, observed order 1.91); the two end rows contribute only . The part of the distance to is the added viscosity , mostly in the layer (69% of this distance lies there): with it does not fall ( and at ). The boundary face of the first cell carries only the added viscosity ; heuristically, this is why lies between the two curves (as it does on every mesh, to 4096).
边界项与伴随的入口层
The boundary term and the inlet layer of the adjoint
连续梯度的边界项含 ,而 越小, 越大。先看 的边界层结构(连续伴随问题的形式渐近分析,不是证明)。层外黏性项可以略去,,且 (伴随从 向上游传):
The boundary term of the continuous gradient contains , while grows as decreases. First the structure of the layer of (a formal asymptotic analysis of the continuous adjoint problem, not a proof). Outside the layer the viscous term can be dropped, , with (the adjoint travels upstream from ):
,而边界条件要求 ,所以 处必须有层。层内令 ,主导项满足 ,解为 ;由 及与 的匹配得到
while the boundary condition asks for , so there must be a layer at . Inside it, with , the leading terms satisfy , with the solutions ; and matching with give
斜率按 增长,边界项 却在 时保持 :它等于层两侧的跳跃 乘以 ,与黏性无关。数值验证: 时 Chebyshev 解给出 ,渐近值为 −1.42887; 较小时差别约与 成正比(下一小节的表)。
The slope grows like , but the boundary term stays as : it equals the jump across the layer times , independent of the viscosity. Numerically, at the Chebyshev solution gives against the asymptotic −1.42887; for small the difference is roughly proportional to (the table of the next subsection).
离散情形: 在 ,而 ,所以 是 的单侧差商。但梯度用到的不是这个斜率,而是第 12 节的入口值伴随 : 只进入面 0,,所以
Discretely, sits at and , so is a one-sided difference quotient for . But the gradient does not use this slope; it uses the adjoint of the inlet value of Section 12, : enters face 0 only, , so
是 对入口值的灵敏度;它的连续对应是 (梯度公式的边界项在 的部分)。同一个数也可以用一次切线求解得到:,( 时两种算法都给出 1.494909,相差 )。入口值的扰动 像状态一样向下游传:它在入口处光滑,边界层在出口( 时第一个单元里 0.9999, 处 0.904,最后一个单元里 0.011)。这解释了为什么粗网格上 已经很准,而斜率 还差得很远。看 前面的系数:物理黏性 ;面 0 的 Rusanov 耗散跨过距离 给出的额外黏性 ;以及对流导数 给出的 (第 12 节把 整个算作对流部分;这里把其中的耗散 1 单独列出,因为它像黏性一样起作用)。表中两种量并排(无限制器线性,默认设计):
is the sensitivity of to the inlet value; its continuous counterpart is (the part of the boundary term of the gradient formula at ). The same number comes from one tangent solve: , (at both give 1.494909, differing by ). The perturbation of the inlet value travels downstream like the state: it is smooth at the inlet and has its layer at the outlet (at : 0.9999 in the first cell, 0.904 at , 0.011 in the last cell). This explains why is already accurate on coarse meshes while the slope is still far off. Look at the factor in front of : the physical viscosity ; the added viscosity of the Rusanov dissipation of face 0 across the distance ; and from the convective derivative (Section 12 counts all of as the convective part; here its dissipation 1 is listed separately because it acts like a viscosity). The table puts the two quantities side by side (unlimited linear, default design):
| N | 与 之比ratio to | 黏性部分 viscous part | 与 之比ratio to | ||
|---|---|---|---|---|---|
| 8 | −14.79 | 0.104 | −0.1479 | 1.4949 | 1.0479 |
| 16 | −26.42 | 0.185 | −0.2642 | 1.4677 | 1.0288 |
| 32 | −44.10 | 0.309 | −0.4410 | 1.4452 | 1.0131 |
| 64 | −67.02 | 0.470 | −0.6702 | 1.4333 | 1.0047 |
| 128 | −91.02 | 0.638 | −0.9102 | 1.4284 | 1.0013 |
| 256 | −111.08 | 0.779 | −1.1108 | 1.4269 | 1.0003 |
| 512 | −124.89 | 0.875 | −1.2489 | 1.4266 | 1.0000 |
| 1024 | −133.18 | 0.934 | −1.3318 | 1.4266 | 1.0000 |
| 2048 | −137.76 | 0.966 | −1.3776 | 1.4266 | 1.0000 |
| 4096 | −140.16 | 0.983 | −1.4016 | 1.4266 | 1.0000 |
斜率 收敛得很慢: 时只有 的 77.9%,要到 才进入 10% 以内(一阶格式也要到 );只看黏性部分, 时入口项只再现了 78%。 则在 时与连续值只差 4.8%, 时差 0.03%。这是默认设计下的结果,不是一般规律:、 时, 与连续值之比在 时只有 0.706, 时为 0.814。Python 数据(到 )给出同样的斜率比。
The slope converges slowly: at it is only 77.9% of , and it comes within 10% only at (the first-order scheme also needs ); the viscous part alone reproduces only 78% of the inlet term at . differs from its continuous value by 4.8% already at and by 0.03% at . This holds at the default design and is not general: with and the ratio of to its continuous value is 0.706 at and 0.814 at . The Python data (to ) give the same slope ratios.
黏性 ν 的作用
The effect of ν
把 从 0.1 减到 0.001(、 不变),连续伴随的层越来越薄, 按 增长, 则趋于渐近值 。差值除以 (表中“差”一列)从 时的 0.758 变到 时的 0.188:只有 较小时差值才约与 成正比。源项积分和边界项各自随 变化,它们的和——精确梯度——不变(精确梯度不依赖 )。下表用 Chebyshev 解(201 个点;Python 审计 audit_f.py 用 241 与 321 个点比对,差在 量级),分量 :
Decrease from 0.1 to 0.001 (, fixed): the layer of the continuous adjoint gets thinner, grows like , and tends to the asymptotic value . The difference divided by (the column “gap”) goes from 0.758 at to 0.188 at : the difference is roughly proportional to only for small . The source integral and the boundary term each change with ; their sum, the exact gradient, does not (the exact gradient does not depend on ). The table uses the Chebyshev solutions (201 points; the Python audit audit_f.py compares 241 with 321 points and finds differences of order ), component :
| 差gap | 源项积分source integral | 边界项boundary term | 和 sum | ||||
|---|---|---|---|---|---|---|---|
| 0.1 | −13.530 | −1.35304 | −1.42887 | 0.758 | 0.5257 | −0.6697 | −0.144008 |
| 0.05 | −28.095 | −1.40474 | −1.42887 | 0.482 | 0.5018 | −0.6458 | −0.144008 |
| 0.02 | −71.155 | −1.42310 | −1.42887 | 0.288 | 0.4808 | −0.6248 | −0.144008 |
| 0.01 | −142.657 | −1.42657 | −1.42887 | 0.230 | 0.4728 | −0.6168 | −0.144008 |
| 0.005 | −285.568 | −1.42784 | −1.42887 | 0.205 | 0.4686 | −0.6126 | −0.144008 |
| 0.002 | −714.240 | −1.42848 | −1.42887 | 0.192 | 0.4661 | −0.6101 | −0.144008 |
| 0.001 | −1428.677 | −1.42868 | −1.42887 | 0.188 | 0.4652 | −0.6092 | −0.144008 |
在面板里拖动 ,同时看离散伴随:固定网格时 越小,层越难分辨, 离 越远;面板同时给出这时的 与连续值之比。
Drag in the panel and watch the discrete adjoint at the same time: on a fixed mesh, the smaller , the harder the layer is to resolve and the further is from ; the panel also gives the ratio of to its continuous value.
梯度误差的逐项分解
The gradient error, part by part
第 12 节在固定网格上分清了设计进入残差的三条路径。对 与 ()只有两条:每一行的积分源项,以及读边界值的面 0 和面 。所以离散梯度与连续梯度可以逐项对齐。离散梯度的三部分是 (i) 源项行、(ii) 入口面、(iii) 出口面:
Section 12 separated the three paths by which the design enters the residual, on a fixed mesh. For and () there are only two: the integrated source of every row, and faces 0 and , which read the boundary values. So the discrete and the continuous gradient can be lined up term by term. The three parts of the discrete gradient are (i) the source rows, (ii) the inlet face and (iii) the outlet face:
它们的连续对应是 (i′) 源项积分、(ii′) 入口项、(iii′) 出口项:
Their continuous counterparts are (i′) the source integral, (ii′) the inlet term and (iii′) the outlet term:
源项行对应源项积分,因为 。下表是分量 (无限制器线性);连续值为:源项积分 0.4728,入口项 −0.6074,出口项 −0.0094,梯度 −0.1440。三部分之和与 相差不到 (本页与审计都核对过)。
The source rows correspond to the source integral because . The table is for component (unlimited linear); the continuous values are: source integral 0.4728, inlet term −0.6074, outlet term −0.0094, gradient −0.1440. The three parts add up to to within (checked by this page and by the audit).
| N | 源项行source rows | 入口面inlet face | 其中黏性部分its viscous part | 出口面outlet face | 与精确值之差minus exact | |
|---|---|---|---|---|---|---|
| 8 | 0.1987 | −0.6365 | −0.0630 | −0.0488 | −0.4866 | −3.4e−1 |
| 16 | 0.3028 | −0.6249 | −0.1125 | −0.0285 | −0.3506 | −2.1e−1 |
| 32 | 0.3828 | −0.6153 | −0.1878 | −0.0186 | −0.2511 | −1.1e−1 |
| 64 | 0.4304 | −0.6103 | −0.2854 | −0.0135 | −0.1934 | −4.9e−2 |
| 128 | 0.4554 | −0.6082 | −0.3876 | −0.0110 | −0.1639 | −2.0e−2 |
| 256 | 0.4667 | −0.6076 | −0.4729 | −0.0099 | −0.1508 | −6.8e−3 |
| 512 | 0.4709 | −0.6074 | −0.5317 | −0.0096 | −0.1461 | −2.1e−3 |
| 1024 | 0.4723 | −0.6074 | −0.5671 | −0.0094 | −0.1446 | −5.8e−4 |
三点观察。(1) 入口面最准: 时为 −0.6365,连续值为 −0.6074(原因见上一小节),虽然其中的黏性部分只有 −0.0630。(2) 粗网格上误差主要在源项行: 时为 0.1987,连续值为 0.4728。逐个单元看 :第一个单元贡献 −0.335,绝对值比源项总误差 −0.274 还大(其余单元部分抵消)。这个误差主要来自不重构的端单元:在端单元重构的实验里,第一个单元只贡献 −0.044,源项总误差只有 −0.021。对 ,出口面的通量只有一阶精度, 时它的误差最大(−0.105;源项行 0.037,入口面 −0.078);细网格上它在梯度中的那部分仍按二阶收敛(最细两层之间的阶为 1.96)。(3) 各部分都是 ,梯度却只有 −0.144:各部分只要差零点几,梯度的相对误差就达到 。 时 ,是精确值的 3.38 倍。
Three observations. (1) The inlet face is the most accurate part: −0.6365 at against −0.6074 (the reason is in the previous subsection), although its viscous part is only −0.0630. (2) On coarse meshes the error sits mainly in the source rows: 0.1987 against 0.4728 at . Cell by cell, is −0.335 in the first cell alone, larger in magnitude than the whole source error −0.274 (the other cells partly cancel). This error comes mainly from the unreconstructed end cell: in the experiment with the end cells reconstructed, the first cell contributes only −0.044 and the whole source error is −0.021. For the outlet face, whose flux is only first-order accurate, has the largest error at (−0.105, against 0.037 in the source rows and −0.078 at the inlet face); its part of the gradient still converges at second order on fine meshes (order 1.96 between the two finest levels). (3) The parts are while the gradient is only −0.144: errors of a few tenths in the parts are relative errors of the gradient. At , , 3.38 times the exact value.
反面例子:把边界面距离取成 h
A counterexample: the boundary-face distance taken as h
一个看似无害的错误是:把边界面的黏性距离取成 (“两个状态之间的距离是一个单元”),而不是本页的 ( 在 , 在 )。错误版本的残差记为 ,它只在两个端行不同:
A harmless-looking slip: take the viscous distance of the boundary faces as (“the distance between the two states is one cell”) instead of the page's ( is at , at ). The residual of the slip, , differs only in the two end rows:
错误版本的切线语句和反向语句写在本节的模块里,用点积检验和复数步长核对,误差在 量级(与模型同样的检查)。Python 审计用原始稀疏求解器、只改它的距离数组,独立地复现了下面的每个数。
Its tangent and reverse statements are written in this section's module and checked like the model's, by the dot test and complex step, to . The Python audit reproduces every number below independently with the original sparse solver, changing only its distance array.
的首行不相容:代入精确单元平均,本页的 是 ( 时 ,第 1 行同样是 ,内部各行是 ),错误版本的 却趋于常数 ( 时为 −0.00581)。但解仍然收敛: 对 的导数约为 ,解只需把 放大一倍——相当于把边界值放到 ,一个 的移动。 时测得 :本页 0.497,错误版本 0.977。
The first row of is inconsistent: with the exact cell averages inserted, the page's is ( at ; row 1 is too, the interior rows ), while the slip's tends to the constant (−0.00581 at ). Yet the solution still converges: depends on with a derivative of about , and the solution only has to double , which amounts to placing the boundary value at , an shift. At , is 0.497 for the page and 0.977 for the slip.
教训有三条。粗网格上的吻合不能证明边界处理正确: 时错误版本的状态误差和 分量的梯度误差更小(但它的 分量的梯度误差在每个网格上都更大, 时为 0.285,本页为 0.157; 分量的梯度误差在 时也更大)。把收敛阶测到伴随的层被分辨之后,错误才显出来:二阶格式降为一阶(最细两层之间状态误差的阶 0.88,梯度 0.88,仍在向 1 增加)。边界关系 是更灵敏的检验: 直接暴露了边界位置错了半个单元; 却暴露不了,它对错误版本仍趋于连续值。错误版本自己的切线、伴随和点积检验都完全正确——它们检验的是程序的导数,而不检验程序离散的究竟是哪个方程。
Three lessons. Agreement on coarse meshes does not prove a boundary treatment right: for the slip has the smaller state and -gradient errors (though its -gradient error is larger on every mesh, 0.285 against 0.157 at , and its -gradient error is larger at ). Measuring the order beyond the resolution of the adjoint's layer exposes it: the second-order scheme drops to first order (state error order 0.88 and gradient 0.88 between the two finest levels, still rising towards 1). The boundary relation is the sharper test: shows directly that the boundary sits half a cell off; cannot show it, since for the slip it still tends to the continuous value. The slip's own tangent, adjoint and dot test are all perfectly right; they test the derivative of the program, not which equation the program discretises.
本节测到的:离散伴随与连续伴随一致——(无限制器线性二阶、一阶格式一阶),离散梯度连同源项部分与边界部分都趋于连续值。但在粗网格上(),本页格式的梯度虽是 的导数(精确到舍入误差),却离连续梯度很远: 时 ,连续值为 −0.144。这个误差主要来自两个不重构的端单元(第 15 节),而不是伴随的层没被分辨本身:重构端单元后伴随的层同样没被分辨, 分量的梯度误差却从 时的 0.343 降到 0.030, 时从 0.107 降到 。这些都只是本问题上的数值证据。
What this section measured: the discrete adjoint is consistent with the continuous one: (second order for unlimited linear, first order for the first-order scheme), and the discrete gradient, with its source part and its boundary part, tends to the continuous values. But on coarse meshes () the gradient of the page's scheme, although it is the derivative of , exact up to rounding, is far from the continuous one: against −0.144 at . Most of this error comes from the two unreconstructed end cells (Section 15), not from the adjoint's unresolved layer itself: reconstructing them leaves the adjoint's layer just as unresolved but reduces the -gradient error from 0.343 to 0.030 at and from 0.107 to at . All of this is numerical evidence for this one problem.
GS 与 GMRES:迭代及其转置
GS and GMRES: the iterations and their transposes
本节实验:先估计一下, 时(无限制器线性)切线方程的 GS 要迭代多少次?伴随方程是否一样多?然后用本节的收敛面板验证。
Experiment: Estimate first how many GS iterations the tangent system needs at (unlimited linear), and whether the adjoint system needs the same number. Then check with the convergence panel of this section.
第 10 节和第 11 节用 LU 解 与 。全局控件的网格很小,模型直接分解稠密矩阵;第 11、12、15–17 节的面板用带状 LU: 只有五条带,分解和回代都是 。二维、三维问题里直接分解会产生大量填充,迭代法取而代之。本节为本页自己的两个方程构造两种迭代,如实测量它们,并说明对伴随最重要的结构:伴随迭代是切线迭代的转置。
Section 10 and Section 11 solve and with LU. The meshes of the global controls are small and the model factors the dense matrix; the panels of Sections 11, 12 and 15–17 use a band LU: has only five bands, so factorisation and solution cost . In two and three dimensions a direct factorisation fills in and iterations take over. This section builds two iterations for the page's own two systems, measures them honestly, and shows the structure that matters most for adjoints: the adjoint iteration is the transpose of the tangent iteration.
两个方程与直接解法
The two systems and the direct solve
两个方程都写成 :切线方程取 、、;伴随方程取 、、。本节里 只表示这个未知向量,不表示坐标, 也与第 8 节的 无关。
Both systems are written : the tangent system has , , ; the adjoint system , , . In this section is only this unknown vector, never the coordinate, and has nothing to do with the of Section 8.
有两条下带、两条上带(一阶格式各一条):一个面的通量涉及 ,一行用两个面。整条带只要前向代码的五次乘积,而不是 次:每个种子是每隔五列取一列的单位向量 之和(按 除以 5 的余数分成五组);同一种子所含各列在任一行中都不会同时有非零元,所以每次乘积给出带的五分之一(核对:与模型逐列求出的 Jacobian 完全相同,差为 0)。部分选主元的 LU 分解仍保持带状(行交换另需两条上带): 时分解加一次回代共 1885 次运算(乘加 1504 次、除法 381 次,行交换 123 次),每个未知量约 14.7 次;伴随方程分解 ,代价相同。
has two sub- and two superdiagonals (one each at first order): the flux of a face involves and a row uses two faces. The whole band takes five products of the tangent code, not : each seed is the sum of the unit vectors of every fifth column (one seed per residue of modulo 5); the columns summed in one seed never both have a nonzero in the same row, so each product delivers a fifth of the band (checked: identical to the model's column-by-column Jacobian, difference 0). LU with partial pivoting keeps the band (the row interchanges need two more superdiagonals): at factorisation and one solve take 1885 operations (1504 multiply-adds and 381 divisions, with 123 row interchanges), about 14.7 per unknown; the adjoint factorises at the same cost.
以一阶 Jacobian 作预处理的 GS 缺陷校正
GS defect correction with the first-order Jacobian
设 是一阶残差在同一状态处的 Jacobian(三条带;一阶格式时 )。在 上从零出发做一次 GS 扫描,按单元顺序依次求出:
Let be the Jacobian of the first-order residual at the same state (three bands; for the first-order scheme). One GS sweep from zero on runs through the cells in order:
是 的严格下三角部分, 是扫描的输出(不是第 1 节的相位 )。缺陷校正把扫描 作用在精确算子的残差上:
with the strictly lower part of and the output of the sweep (not the phase of Section 1). The defect correction applies the sweep to the residual of the exact operator:
减去精确解 :误差满足 (切线方程)。对任意初值收敛当且仅当误差传播矩阵的谱半径 ;进入渐近阶段后,误差每迭代一次约乘以 。迭代只需要 :按无矩阵(matrix-free)方式实现时,切线方程用第 8 节的 residual_d,伴随方程用第 9 节的 residual_b;需要存储的只有 的三条带。
Subtract the solution : the error obeys (tangent system). The iteration converges from every start exactly when the spectral radius of the error propagation matrix is below one, ; once the asymptotic phase is reached, every iteration multiplies the error by about . The iteration needs only products : implemented matrix-free, the tangent system uses residual_d of Section 8 and the adjoint system residual_b of Section 9; only the three bands of are stored.
伴随迭代是切线迭代的转置
The adjoint iteration is the transpose of the tangent iteration
从零出发做 次切线迭代,结果对 是线性的:
Run the tangent iteration times from zero; the result is linear in :
逐项转置,并把 穿过幂次():
Transpose term by term and move through the power ():
这正是伴随方程迭代 的 步映射。而 :解 从最后一个单元开始,
This is the -step map of the adjoint iteration . And : solving starts from the last cell,
即在 上反向扫描。由此得到四个结论,都已测量:
a backward sweep on . Four consequences, all measured:
- 迭代的点积检验。对任意 、 和每个 ,。随机的 (,)测得最大相对差 ;Python 审计显式构造两个 步矩阵(,,三种重构),差在 量级。Dot test of the iterations. for all , and every . With random (, ) the largest relative difference is ; the Python audit forms both -step matrices explicitly (, , three reconstructions) and finds differences of order .
- 截断的梯度在每一步都相等。令 、,则 ,也就是 :切线 GS 与伴随 GS 迭代 次后给出同一个导数,此时两者都还没收敛(图 71,最大差 )。The truncated gradients agree at every step. With and , , which is : tangent GS and adjoint GS give the same derivative after iterations, while neither has converged (Figure 71, largest difference ).
- 转置的扫描是反向的。伴随把信息从出口带向入口(第 16 节),反向扫描顺着这个方向。在 上做前向扫描也是伴随方程的一种合法迭代,但它不是切线迭代的转置。粗网格上它慢得多(:246 次,转置迭代 70 次),但网格 Péclet 数 降到约 2 以下后就不再如此(:前向扫描 267 次,转置迭代 267 次;:前向扫描 7883 次,转置迭代 7517 次;光滑限制器在 时前向扫描反而更快:278 次,转置迭代 296 次)。The transposed sweep runs backward. The adjoint carries information from the outlet to the inlet (Section 16), and the backward sweep follows that direction. A forward sweep on is also a valid iteration for the adjoint system, but it is not the transpose of the tangent iteration. On coarse meshes it is much slower (: 246 iterations against 70 for the transposed one), but not once the cell Péclet number drops below about 2 (: forward sweep 267, transposed 267; : forward sweep 7883, transposed 7517; with the smooth limiter at the forward sweep is even faster: 278 against 296 for the transposed one).
- 同样的渐近速度。 与 相似,所以两个误差传播矩阵的特征值相同;测得的谱半径相差约 (受舍入误差限制:在双精度下,这类非对称矩阵的特征值只能算到这个精度,numpy 的 LAPACK 给出同类的差别)。迭代次数仍可略有不同(右端项不同,见下表)。The same asymptotic rate. is similar to , so the two error propagation matrices have the same eigenvalues; the measured spectral radii differ by about (limited by rounding: in double precision the eigenvalues of such non-symmetric matrices cannot be computed more accurately, and numpy's LAPACK shows differences of the same kind). The iteration counts can still differ a little (different right-hand sides; table below).
GMRES
GMRES
GMRES 保留已经产生的方向,把它们组合起来。用同一个扫描 作右预处理,第 步的近似解为
GMRES keeps the directions it has generated and combines them. Right-preconditioned with the same sweep , the -th approximation is
用 Arnoldi 过程(修正 Gram–Schmidt 做两遍)和 Givens 旋转计算;本页每一步都记录真实残差 。重启版本每走固定步数(5、10 或 30 步)就从当前解重新开始,以限制存储。伴随版本用 与 。由定义可得两点:不重启时 GMRES 至多 步就得到精确解(测量: 时 8 步, 时 16 步);GMRES 对 不是线性的, 通过极小化依赖 ,所以没有 GS 那样的 步转置:切线与伴随 GMRES 的导数在收敛前不同(、 时差可达 0.273),收敛后才一致(差 )。
computed with the Arnoldi process (modified Gram–Schmidt, two passes) and Givens rotations; the page records the true residual after every step. The restarted versions start again from the current iterate after a fixed number of steps (5, 10 or 30) to bound the storage. The adjoint version uses and . Two facts follow from the definition: without restarts GMRES reaches the exact solution in at most steps (measured: 8 steps at , 16 at ); and GMRES is not linear in , since depends on through the minimisation, so there is no -step transposition as for GS: the tangent and adjoint GMRES derivatives differ before convergence (up to 0.273 for at ) and agree once both have converged (difference ).
测量:迭代次数、谱半径、代价
Measurements: iterations, spectral radius, cost
下表数的是使 的迭代次数(“切线/伴随”,切线方程取 ,即振幅 ),无限制器线性重构,默认设计;GMRES() 即不重启( 步内得到精确解), 的次数见收敛面板;、512 只列重启的 GMRES。 是 的谱半径, 是由它预测的次数。第 6 节的图 18 用另一个标准(缺陷的最大范数降到 ,对象是第一个 Newton 修正方程),所以同一个 的次数不同。Python 审计用自己的稠密 GS、GMRES(numpy)复现了每一个次数(重启的 GMRES 在 时相差不超过 2%),用 numpy 的特征值复现了每一个谱半径。
The table counts the iterations until (“tangent / adjoint”, the tangent system for , the amplitude ), unlimited linear reconstruction, default design; GMRES() is GMRES without restart (exact within steps); the counts for are in the convergence panel; for and 512 only the restarted GMRES is listed. is the spectral radius of , and the count it predicts. Figure 18 of Section 6 uses another criterion (the defect in the maximum norm down to , on the first Newton correction system), so its counts for the same differ. The Python audit reproduces every count with its own dense GS and GMRES (numpy; the restarted GMRES within 2% for ) and every spectral radius with numpy's eigenvalues.
| N | GS | GMRES(5) | GMRES(10) | GMRES(30) | GMRES() | ||||
|---|---|---|---|---|---|---|---|---|---|
| 8 | 45 / 45 | 60 / 60 | 102 / 105 | 0.5748 | 42 | 20 / 23 | 8 / 8 | 8 / 8 | 8 / 8 |
| 16 | 70 / 70 | 91 / 92 | 153 / 155 | 0.6786 | 59 | 51 / 42 | 39 / 31 | 16 / 16 | 16 / 16 |
| 32 | 114 / 117 | 148 / 151 | 248 / 253 | 0.7618 | 85 | 64 / 62 | 69 / 53 | 29 / 29 | 29 / 29 |
| 64 | 253 / 267 | 321 / 339 | 523 / 552 | 0.8757 | 173 | 100 / 94 | 124 / 129 | 88 / 86 | 47 / 45 |
| 128 | 642 / 704 | 806 / 884 | 1300 / 1425 | 0.9481 | 432 | 163 / 137 | 211 / 207 | 315 / 449 | 84 / 81 |
| 256 | 271 / 272 | 358 / 288 | 614 / 658 | ||||||
| 512 | 1088 / 895 | 602 / 665 | 920 / 839 |
三种重构对比(GS,,切线/伴随,以及 ):
The three reconstructions side by side (GS, , tangent / adjoint, and ):
| N | 一阶first | 无限制器none | 光滑smooth | (一阶) (first) | (无限制器) (none) | (光滑) (smooth) |
|---|---|---|---|---|---|---|
| 8 | 75 / 80 | 45 / 45 | 66 / 71 | 0.7487 | 0.5748 | 0.7168 |
| 16 | 109 / 127 | 70 / 70 | 87 / 91 | 0.8290 | 0.6786 | 0.7617 |
| 32 | 193 / 205 | 114 / 117 | 122 / 125 | 0.8840 | 0.7618 | 0.7870 |
| 64 | 363 / 385 | 253 / 267 | 280 / 296 | 0.9305 | 0.8757 | 0.9016 |
| 128 | 818 / 888 | 642 / 704 | 730 / 794 | 0.9655 | 0.9481 | 0.9601 |
结论(测量,不是定理):(1) GS 在测到的所有情形都收敛,但慢,而且网格越细越慢:无限制器线性 从 0.575()升到 0.948(),次数从 45 升到 642。(2) 欠松弛在这里没有帮助:设 是 中使 最大的特征值,若 落在圆盘 内,则 随 从 1 减小而增大;测得的所有情形里 ,所以 随 减小而变大。(3) 给出量级。无限制器线性的实际次数总是更多(:预测 432,实测 642):开始的 300 次迭代里残差平均每次只乘以 0.980,最后 300 次才是 0.951(),预测忽略了渐近阶段之前的过渡。在 时,一阶与光滑限制器的切线方程实测次数反而比预测略少(一阶:实测 75,预测 80;光滑限制器:实测 66,预测 69)。(4) GMRES 次数少得多(不重启时 伴随 81 次)。重启后的迭代次数随重启长度的变化却不单调: 时伴随 GMRES(30) 要 449 次,比 GMRES(10) 的 207 次和 GMRES(5) 的 137 次都多, 时 GMRES(5) 又最慢(895 次,GMRES(10) 665 次);更长的重启不保证更快,这里只有不重启的 GMRES 稳妥(至多 步)。(5) 代价:一次 GS 迭代约 次运算,带状 LU 分解加回代约 ;GMRES 每一步再加上正交化,每个已存方向约 。一维问题里直接求解的代价只相当于约 1.8 次 GS 迭代:这里讲迭代是为了它们的结构,以及二维、三维问题,而不是为了一维里的速度。
Conclusions (measured, not theorems): (1) GS converges in every case measured, but slowly, and the more slowly the finer the mesh: for unlimited linear rises from 0.575 () to 0.948 () and the count from 45 to 642. (2) Under-relaxation does not help here: let be the eigenvalue of with the largest ; if lies in the disk , then grows as decreases from 1; in every case measured , so grows as decreases. (3) gives the order of magnitude. For unlimited linear the actual counts are always higher (at : 432 predicted, 642 measured): over the first 300 iterations the residual is multiplied on average by only 0.980 per iteration, over the last 300 by 0.951 (); the prediction ignores the transient before the asymptotic phase. For the tangent system at , first order and the smooth limiter need slightly fewer iterations than predicted (first order 75 measured, 80 predicted; smooth 66 measured, 69 predicted). (4) GMRES needs far fewer iterations (81 for the adjoint at without restart). The restarted counts, however, are not monotone in the restart length: at the adjoint GMRES(30) needs 449, more than GMRES(10) with 207 and GMRES(5) with 137, and at GMRES(5) is the slowest (895, GMRES(10) 665); a longer restart guarantees nothing, and only unrestarted GMRES is dependable here (at most steps). (5) Cost: a GS iteration takes about operations, the band LU factorisation and solve about ; a GMRES step adds the orthogonalisation, about per stored direction. In one dimension the direct solve costs about 1.8 GS iterations: the iterations are taught here for their structure and for two and three dimensions, not for speed in one.
| N | 伴随:反向扫描(转置)adjoint: backward sweep (transpose) | 伴随:前向扫描adjoint: forward sweep | |
|---|---|---|---|
| 16 | 5.72 | 70 | 246 |
| 64 | 1.43 | 267 | 267 |
| 128 | 0.71 | 704 | 690 |
| 256 | 0.36 | 2171 | 2289 |
| 512 | 0.18 | 7517 | 7883 |
不要把 GS 的“截断导数相等”套用到 GMRES 上:GS 的 步映射是线性的,转置关系在每一步成立;GMRES 不是线性映射,只有收敛后的结果满足对偶关系。用迭代解求梯度时,切线与伴随的结果只能在收敛后比较(GMRES),或在相同 、严格转置的迭代之间比较(GS,此时应在舍入误差内一致);这时的差别指向导数代码或转置的错误。
Do not carry the “equal truncated derivatives” of GS over to GMRES: the -step map of GS is linear and the transposition holds at every step; GMRES is not a linear map, and only its converged results satisfy the duality. When gradients come from iterative solves, compare the tangent and adjoint values only at convergence (GMRES) or at equal with exactly transposed iterations (GS, where they must agree to rounding); a difference there points to an error in the derivative code or the transposition.
总结:一个网格、一个通量、三个方向
Summary: one mesh, one flux, three directions
本节实验:对三个方向,各说出种子是什么、解哪个方程、得到什么;然后用本节的总览面板核对——它对当前控件把所有计算重做一遍。
Experiment: For each of the three directions, say what is seeded, which equation is solved and what comes out. Then check with the dashboard of this section, which redoes every calculation for the current controls.
本页只算了一件事:离散目标 对设计 的导数。它用三种方式计算,三者共用网格、面通量和重构;变化的只是传播的数据和数据流的方向。
The page computed one thing, the derivative of the discrete objective with respect to the design , in three ways that share the mesh, the face flux and the reconstruction; only the data that are propagated and the direction of the data flow change.
三个方向
Three directions
| 原始Primal | 前向(tangent)Forward (tangent) | 伴随(adjoint)Reverse (adjoint) | |
|---|---|---|---|
| 种子Seed | () () | (来自 ,) (from , ) | |
| 运行的代码Code that runs | 残差代码(取值、重构、通量、分发)the residual code (gather, reconstruct, flux, scatter) | residual_d,同序residual_d, same order | residual_b,逆序,累加 +=residual_b, reverse order, accumulating += |
| 要解的方程Equation solved | (Newton 法) (Newton) | ||
| 输出Output | ,, | ||
| 每个梯度的求解次数Solves per gradient | —— | 每个设计分量一次(3)one per design component (3) | 1(与分量个数无关)1 (any number of components) |
| 本页位置Where | 第 5 节Section 5 | 第 8 节、第 10 节Section 8, Section 10 | 第 9 节、第 11 节、第 12 节Section 9, Section 11, Section 12 |
切线与伴随由对偶恒等式连在一起:对任意种子 (第 9 节的点积检验),在解上它变成 。代价的差别也由此而来:切线每个设计分量要一次求解,伴随每个目标要一次求解;本页有 3 个分量、1 个目标。
The tangent and the adjoint are tied by the duality identity: for any seeds (the dot test of Section 9), which at the solution becomes . The difference in cost comes from the same place: the tangent needs one solve per design component, the adjoint one solve per objective; this page has 3 components and 1 objective.
下面的播放器用当前控件,把这条链一步一步走一遍:每一步显示那一刻的数组。
The player below walks the chain step by step with the current controls, showing the arrays of each moment.
验证了什么,怎样验证
What was verified, and how
每一种检查回答一个不同的问题:点积检验问反向代码是不是前向代码的转置;“切线 = 伴随”问的是两个求解是否一致;复数步长和差分问导数是不是这个程序的导数;Python 数据问另一份独立实现是否给出同样的数;连续公式问连续推导本身;收敛阶问离散是否逼近连续。下表的数是本页在构建时运行各项检查所得的最大值,范围见各行所列的网格与种子(除非另注,取默认设计);各节打印的是它们自己的网格上的值,可能更小;“见”一栏给出解释该检查的小节;Python 审计(audit/audit_f.py 与 audit/ 中其他脚本)独立地复现了它们。
Each check answers a different question: the dot test asks whether the reverse code is the transpose of the tangent code; tangent = adjoint asks whether the two solves agree; complex step and finite differences ask whether the derivative is the derivative of this program; the Python data ask whether an independent implementation gives the same numbers; the continuous formula checks the continuous derivation itself; the orders ask whether the discretisation approaches the continuum. The numbers in the table are the largest values found by the checks that this page runs at build time, over the meshes and seeds named in each row (default design unless noted); the sections print the values for their own meshes, which can be smaller, and “See” names the section that explains the check; the Python audit (audit/audit_f.py and the other scripts in audit/) reproduces them independently.
| 检查Check | 最大偏差Largest discrepancy | 见See |
|---|---|---|
| 点积检验:前向代码与反向代码(随机状态与种子,,相对)dot test of the tangent and reverse code (random states and seeds, , relative) | 1.5e−15 | 第 9 节Section 9 |
| 切线梯度与伴随梯度之差(三种重构,)tangent gradient minus adjoint gradient (3 reconstructions, ) | 6.3e−15 | 第 11 节Section 11 |
| 贯穿整个求解的复数步长与伴随之差()complex step through the solve minus adjoint () | 1.7e−15 | 第 14 节Section 14 |
| 扰动解的复残差 complex residual of the perturbed state, | 1.2e−15 | 第 14 节Section 14 |
| GS 的 步转置(相对)-step transposition of GS (relative) | 3.7e−15 | 第 17 节Section 17 |
| 中心差分(,完整重算)与伴随之差central differences (, full re-solves) minus adjoint | 1.6e−8 | 第 13 节Section 13 |
| 与 Python 数据中的梯度之差()difference from the gradients of the Python data () | 3.9e−11 | 第 15 节Section 15 |
| 的 L1 范数与 Python 数据之差(相对)L1 norm of against the Python data (relative) | 2.1e−7 | 第 16 节Section 16 |
| 连续梯度公式与精确梯度之差continuous gradient formula minus exact gradient | 1.0e−13 | 附录 AAppendix A |
收敛阶(Python 数据中最细的两层):无限制器线性的状态 L1 误差阶 1.99,梯度分量 的误差阶 1.99( 到 16384);一阶格式为 0.99 与 0.99( 到 4096)。伴随本身: 的 L1 误差阶,无限制器线性 1.96(模型,),1.99(Python,;见第 15、16 节)。
Orders of convergence (between the two finest levels of the Python data): for unlimited linear reconstruction the state L1 error converges at 1.99 and the error of the gradient component at 1.99 ( to 16384); for the first-order scheme at 0.99 and 0.99 ( to 4096). The adjoint itself: the L1 error of converges at 1.96 for unlimited linear (model, ) and 1.99 (Python, ; see Sections 15 and 16).
总览面板对当前控件把这些检查全部重做:原始解、切线与伴随梯度、对偶、点积检验、复数步长、中心差分、精确梯度和连续公式,放在一张表里。
The dashboard redoes all of these checks for the current controls: the primal solution, the tangent and adjoint gradients, the duality, the dot test, complex step, central differences, the exact gradient and the continuous formula, in one table.
离散梯度是什么,不是什么
What the discrete gradient is, and what it is not
是离散目标 (即这个网格上这段程序的目标)的梯度,精确到舍入误差(上表)。它不是连续目标 的梯度,只有加密网格才能逼近后者(第 15 节、第 16 节)。默认设计、 时,离散梯度是 ,连续梯度(闭式)是 。分量 的差别主要来自两个不重构的端单元(第 15 节、第 16 节),而不是伴随在入口处的层没被分辨:重构端单元后,它的误差从 0.343 降到 0.030; 与 分量改善得少(从 0.146 到 0.068,从 0.310 到 0.225)。用 的优化器优化的是这个网格上的 ;结果是否接近连续最优,取决于离散化在重要的地方是否足够准——这里是两个端单元和入口处伴随的黏性层。
is the gradient of the discrete objective , the objective of this program on this mesh, exact up to rounding (table above). It is not the gradient of the continuous objective ; that one is approached only as the mesh is refined (Section 15, Section 16). At the defaults with the discrete gradient is and the continuous one (closed form) . The difference in the component comes mainly from the two unreconstructed end cells (Section 15, Section 16), not from the unresolved layer of the adjoint at the inlet: with them reconstructed its error falls from 0.343 to 0.030; the and components improve less (from 0.146 to 0.068 and from 0.310 to 0.225). An optimiser that uses optimises on that mesh; whether the result is close to the continuous optimum depends on whether the discretisation is accurate where it matters, here the two end cells and the viscous layer of the adjoint at the inlet.
本页的局限
The limits of this page
- 固定的正黏性(控件中 ),没有激波。激波属于单元中心的一维 Burgers 页的附录 B, 与 的极限顺序见它的附录 D(D.4);本页每次计算的 都固定(第 16 节只比较几个固定的 ,渐近分析也只是形式推导)。A fixed positive viscosity ( in the controls) and no shocks. Shocks belong to Appendix B of the cell-centred 1-D Burgers page and the orders of the limits , to its Appendix D (D.4); here stays fixed in every computation (Section 16 compares several fixed values and gives formal asymptotics only).
- 一个光滑的构造解(周期的 ,所以 )、一个目标、三个设计分量;Rusanov 速度固定为 ,光滑限制器取 。One smooth manufactured solution (periodic , so ), one objective and three design components; the Rusanov speed is fixed at and the smooth limiter uses .
- 本页不给出非线性问题或格式的存在性、唯一性或收敛定理:每个观测到的收敛阶都只是本问题上的数值证据(第 5–7 节与附录 A 的 A.4 中的证明只针对那里写明的线性方程组、单步估计和恒等式);第 16 节的渐近分析是形式推导。No existence, uniqueness or convergence theorem for the nonlinear problem or the scheme is given: every observed order is numerical evidence for this one problem (the proofs of Sections 5–7 and of A.4 in Appendix A concern the linear systems, one-step estimates and identities named there); the asymptotics of Section 16 are formal.
- 第 17 节的迭代是为了讲清结构,没有调优;一维里带状 LU 更便宜。The iterations of Section 17 are there to show their structure and are not tuned; in one dimension the band LU is cheaper.
接下来读什么
Where to go next
- 附录 A:用变分推出连续伴随与梯度公式,以及它的边界项。Appendix A: the continuous adjoint and the gradient formula with its boundary term, derived with variations.
- 附录 B:格点格式——它与本页的差别,以及两种格式的一组数字对照。Appendix B: the node-centred scheme, where it differs from this page, and one comparison of the numbers of the two schemes.
- 附录 C:怎样复现本页的数字和 Python 数据。Appendix C: how to reproduce the numbers of this page and the Python data.
要记住的
What to remember
- 切线与伴随用自动微分的记号:切线是 、按原顺序;伴随是 、按逆序累加 ;二者由对偶 连在一起。The tangent and the adjoint use the notation of automatic differentiation: the tangent is in the original order, the adjoint accumulated with in reverse order; the duality ties them together.
- 核心就是一个面:取值、重构、通量、分发;切线与伴随复用同样的局部导数 和重构权重。One face is everything: gather, reconstruct, flux, scatter; the tangent and the adjoint reuse the same local derivatives and reconstruction weights.
- 伴随梯度是离散目标的导数,精确到舍入误差:用点积检验、复数步长和差分验证它;用加密和连续伴随检验它离连续梯度多远。The adjoint gradient is the derivative of the discrete objective, exact up to rounding: verify it with the dot test, complex step and finite differences; check how far it is from the continuous gradient with refinement and the continuous adjoint.
- 积分形式的残差使 ,同号、无因子 。粗网格上,无限制器线性重构的伴随最大误差和 分量的梯度误差很大,主要因为两端单元不重构(第 15 节、第 16 节);连续伴随在入口有宽 的层,要分辨它还需要 。An integrated residual makes , with the same sign and no factor . On coarse meshes the largest adjoint error and the -gradient error of unlimited linear reconstruction are large, mainly because the two end cells are not reconstructed (Section 15, Section 16); the continuous adjoint has a layer of width at the inlet, and resolving it also needs .
- 一阶格式等于黏性为 的中心格式。边界关系 能暴露粗网格吻合所掩盖的边界错误;入口值伴随 不能(对错误版本它仍趋于 );在默认设计下它在粗网格上就接近连续值,但这不是一般规律(第 16 节)。The first-order scheme is the central scheme with viscosity . The boundary relation exposes boundary errors that agreement on coarse meshes hides; the adjoint of the inlet value, , does not (for the slip it still tends to ); at the default design it is already close to its continuous value on coarse meshes, but not in general (Section 16).
- 伴随迭代应当是切线迭代的转置:GS 转置后反向扫描,截断导数在每一步都相等;GMRES 不是线性的,只有收敛后才满足对偶关系。The adjoint iteration should be the transpose of the tangent iteration: transposed GS sweeps backward and the truncated derivatives agree at every step; GMRES is not linear and satisfies the duality only at convergence.
附录 A:连续伴随
Appendix A: The continuous adjoint
本节实验: 越小,连续伴随在 处的斜率 越陡。梯度公式里的边界项 会随 消失吗?先猜,再用图 80 和本附录的 实验台验证。
Experiment: as decreases, the slope of the continuous adjoint at steepens. Does the boundary term of the gradient formula vanish as ? Guess first, then check with Figure 80 and the lab of this appendix.
正文各节对离散程序求导:前向用点 ,伴随用横线 。本附录换一个对象:直接对连续方程求伴随。这样得到的 正是离散伴随 在网格加密时应当逼近的函数(第 16 节);推导也说明它的边界条件从哪里来。这里用变分记号:、、 是连续函数的变分; 只出现在本附录(以及明确说“连续”的句子里)。
The main sections differentiate the discrete code: the tangent with dots , the adjoint with bars . This appendix takes a different object, the continuous equation itself, and derives its adjoint. The result is the function that the discrete adjoint should approach under mesh refinement (Section 16), and the derivation shows where its boundary conditions come from. The notation here is variational: , , are variations of continuous functions; appears only in this appendix (and in sentences that say continuous).
A.1 状态方程、目标与变分
A.1 The state equation, the objective and their variations
连续问题就是第 1 节的方程:总通量 ,
The continuous problem is the equation of Section 1: with the total flux ,
设计改变 时,源项改变 ,两端给定值改变 、,状态随之改变 。把通量线性化:(最后一项只在 本身改变时出现,因为 写在通量里)。于是线性化的状态方程是
When the design changes by , the source changes by , the two prescribed end values by , , and the state follows with . Linearise the flux: (the last term appears only when itself changes, because sits inside the flux). The linearised state equation is therefore
每次只改变一个分量 ,除以 ,就得到连续切线解:对 、 有 ,,;对 有 ,两端 。它是离散切线解 的连续对应物。
Moving one component at a time and dividing by gives the continuous tangent : for and , with and ; for , with at both ends. It is the continuous counterpart of the discrete tangent .
A.2 拉格朗日函数与两次分部积分
A.2 The Lagrangian and two integrations by parts
把方程乘以一个待定的函数 ,加到目标上:
Multiply the equation by a function , still to be chosen, and add it to the objective:
对状态方程的任何解,方括号内的量都为零,所以对任意 都有 ,变分也相同:。这正是正文 的连续版本,正负号约定一致(第 16 节比较 与 时无须变号)。对设计求变分:
On any solution of the state equation the bracket vanishes, so for every , and so do their variations: . This is the continuous version of the main text's with the same sign convention (Section 16 compares with without a change of sign). Vary the design:
中间一项含未知的 的导数。我们要把导数从 挪到 上,一步一步来:(1)分部积分一次;(2)展开括号;(3)对 再分部积分一次:
The middle term contains derivatives of the unknown . Move them from onto , one step at a time: (1) integrate by parts once; (2) expand the bracket; (3) integrate by parts again:
代回 ,按“内部”和“两端”整理:
Put this back into and sort it into the interior and the two ends:
A.3 选 λ:伴随方程及其边界条件
A.3 Choosing λ: the adjoint equation and its boundary conditions
只有解完切线问题才知道。我们选 ,让所有含 的项都消失,图 77 画出了这四项:
is known only after solving the tangent problem. Choose so that every term containing disappears; Figure 77 draws the four pieces:
- 内部。被积函数为零:。右端项 来自目标的权重。Interior. The integrand vanishes: . The right side comes from the weight of the objective.
- 两端的第一项 含扰动状态在两端的斜率 、——它们是切线问题的输出,事先不知道。唯一的办法是让它们的系数为零:。这样 也一起消失。The first end term contains the end slopes , of the perturbed state: they are outputs of the tangent problem, unknown beforehand. The only way out is to make their coefficients zero: . Then disappears as well.
- 两端的第二项 只含给定端值的变分,是已知的:它留下来,成为梯度的一部分。The second end term contains only variations of the prescribed end values, which are known: it stays and becomes part of the gradient.
二阶方程配两个条件,数目正好。若再对 加条件,问题就超定;少一个,就留下一个算不出的项。结果是连续伴随问题与梯度公式:
A second-order equation with two conditions: exactly the right number. One more condition on would overdetermine the problem; one fewer would leave a term that cannot be evaluated. The result is the continuous adjoint problem and the gradient formula:
分量没有边界项(端值不依赖 ),但多了物理通量本身的一项 。对构造解,源项 给出 ,所以在 上被积函数 处处为零,,与 一致。
The component has no boundary term (the end values do not depend on ) but one more term from the physical flux itself, . For the manufactured solution the source gives , so the integrand vanishes identically at and , in agreement with .
常见错误:漏掉通量项,只算 。默认设计下这个数是 1.8769,而正确答案是 0:通量项 恰好把它抵消(图 79 右)。离散的情形相同:第 12 节的 路径有一条直接穿过通量 。
A common mistake: dropping the flux term and computing only . At the defaults that number is 1.8769, whereas the correct answer is 0: the flux term cancels it exactly (Figure 79, right). The discrete case is the same: one of the paths of Section 12 runs straight through the flux .
下面的播放器把这三步逐行算出来:取一个检验函数 代替 ,用本页的 计算每一行的每一项。前六行对任何 都相等(只用到分部积分和伴随方程);第七行还要用切线方程,所以只对真正的切线解成立。
The player below evaluates the three steps line by line: a test function stands in for , and every term of every line is computed with this page's . The first six lines agree for any (they use only integration by parts and the adjoint equation); the seventh also uses the tangent equation, so it holds only for a true tangent.
A.4 伴随问题适定吗:能证明的部分
A.4 Is the adjoint problem well posed? What can be proved
(A.3–A.4 中 表示一般的函数——检验函数或 定义域里的元素; 才是切线解。)记状态线性算子 (带齐次端值 ),伴随算子 (带 )。下面三条中,(1)、(3) 在本页完整证明;(2) 证明一个方向,另一方向引用 Fredholm 择一定理。它们对任何连续的 和任何 成立,只有第三条要求 。
(In A.3–A.4, is a generic function, a test function or an element of the domain of ; the tangent is .) Write the linear state operator (with homogeneous end values ) and the adjoint operator (with ). Of the three statements below, (1) and (3) are proved in full here; (2) proves one direction and cites the Fredholm alternative for the other. They hold for every continuous and every , and only the third needs .
(1) 唯一可解。伴随方程对 是一阶的:记 ,则 等价于 。积分两次:
(1) Unique solvability. The adjoint equation is first order in : with , the equation is the same as . Integrate twice:
已经满足; 是关于 的一个线性方程,系数 ,所以 唯一确定,解存在且唯一。右端项为零时同一计算给出 、:在这组边界条件下,伴随齐次问题只有零解。对 积分一次得 常数 ,同样的积分因子对任意连续的 解出唯一的 ( 确定那个常数):状态算子 可逆。
holds by construction; is one linear equation for with the coefficient , so is determined uniquely and the solution exists and is unique. With a zero right side the same computation gives and : the adjoint operator has no nonzero homogeneous solution under these boundary conditions. Integrating once gives constant , and the same integrating factor solves it uniquely for every continuous ( fixes the constant): the state operator is invertible.
(2) 为什么两者总是同时可逆(简短论证)。A.2 的分部积分在 、 时两端项全为零,剩下 Green 恒等式 。若 而 ,则对所有 有 :任何满足 的右端项 都不在 的值域里, 不可逆。反方向( 不可逆 伴随有非零齐次解)是正则二阶边值问题的 Fredholm 择一定理,属于标准结论,本页不再证明;对本问题,(1) 已直接证明:状态算子与伴随算子的齐次问题都只有零解。离散的对应物是 与 秩相同。
(2) Why the two are always invertible together (a short argument). With and every end term of the integration by parts of A.2 vanishes, and what is left is Green's identity . If with , then for every : no right side with lies in the range of , and is not invertible. The converse ( not invertible a nonzero homogeneous solution of the adjoint problem) is the Fredholm alternative for regular second-order boundary-value problems, a standard result not re-proved here; for this problem (1) has already shown directly that the homogeneous state and adjoint problems have only the zero solution. The discrete counterpart is that and have the same rank.
(3) 最大值原理()。(a) 伴随方程的右端项 。在 的内部极大点 、,于是 ,与 矛盾;所以最大值只在两端取到, 于 (这一步不需要 的符号)。(b) 若 ,取常数 ,则 ;同样的论证说明 没有内部极小,它的最小值在端点取到: 处是 , 处是 ,所以最小值是 。令 :
(3) The maximum principle (). (a) The right side of the adjoint equation, , is negative. At an interior maximum of , and , so , contradicting ; hence the maximum is attained only at the ends and on (this step needs no sign of ). (b) If , take a constant ; then , and the same argument shows that has no interior minimum: its minimum is attained at an end: the value is at and at , so the minimum is . Letting ,
默认设计 ,下界是 ,即 。它与 无关: 时 本身保持有界,变陡的只是 处的斜率——那是一个边界层,不是 的爆破。本附录中各个 的解都满足这两个不等式(check_g.js 逐点检查)。
At the defaults and the lower bound is , i.e. . It does not depend on : as the function itself stays bounded, and only its slope at steepens. That is a boundary layer, not a blow-up of . Every solution of this appendix, for every , satisfies the two inequalities (checked point by point by check_g.js).
本页没有证明的:离散伴随 收敛到 (第 16 节给的是数值证据);下面 A.6 的渐近展开(形式推导,用数值核对); 变号或出现激波时的任何结论。
Not proved on this page: the convergence of the discrete adjoint to (Section 16 gives numerical evidence); the asymptotic expansion of A.6 below (a formal derivation, checked numerically); anything for a that changes sign or contains a shock.
A.5 求解:本页的谱方法
A.5 Solving it: the page’s spectral solver
model.js 的 continuousAdjoint(D, M) 用 Chebyshev 配点法解这个两点边值问题:取 个点 ——它们在两端加密,正好落在边界层里;对过这些点的插值多项式求导,得到微分矩阵 与 (无衬线的 不是设计 );在内部点上要求 ,两端点上 ;解一个稠密线性方程组, 由 给出。它与有限体积模型只共用稠密 LU(experiments/numerics.js)和 的公式;离散方法(配点)与有限体积、与离散伴随无关。真正独立的核对是下面的积分因子法(不用配点,也不用 LU)、Python 审计 audit/audit_continuous.py 的 NumPy 求解,以及附录 B 的 check_dn.py。
continuousAdjoint(D, M) of model.js solves this two-point boundary-value problem by Chebyshev collocation: take the points , which cluster at both ends, right inside the boundary layer; differentiating the interpolating polynomial through them gives the differentiation matrices and (the sans-serif is not the design ); require at the interior points and at the two end points; solve one dense linear system, and read from . With the finite-volume model it shares only the dense LU (experiments/numerics.js) and the formula of ; its discretisation (collocation) has nothing to do with the finite volumes or the discrete adjoint. The truly independent checks are the integrating-factor method below (no collocation, no LU), the NumPy solver of the Python audit audit/audit_continuous.py, and check_dn.py for Appendix B.
收敛性:在 401 个采样点上, 与 的 最大相差不到 3e−13, 的相对差不到 3e−13。另一条完全独立的路径是 A.4 的积分因子公式,用 Gauss 求积直接计算:它与谱方法的 相对差不到 2e−14。Python 审计 audit/audit_continuous.py 用 NumPy 比较 与 。本附录正文与图里伴随的数值用 ;切线问题用 ;第 11–18 节用 (201 个点,model.js 的默认值),打印的数字与本附录在所示位数上一致;实验台按 选 (96 到 256,保证边界层被分辨,见 check_g.js)。
Convergence: on 401 sample points, for and differs by at most 3e−13, and by at most 3e−13 relative. A second, completely independent path is the integrating-factor formula of A.4, evaluated by Gauss quadrature: its agrees with the spectral one to within 2e−14 relative. The Python audit audit/audit_continuous.py compares with in NumPy. The adjoint numbers in the text and figures of this appendix use ; the tangent problems use ; Sections 11–18 use (201 points, the default of model.js), and the numbers printed there agree with this appendix to the digits shown; the labs choose from (96 to 256, enough to resolve the layer, as check_g.js verifies).
默认设计下 、。把梯度公式逐项算出(Clenshaw–Curtis 求积,用同一组点):“源项积分”是 (对 是 ,与图 79 右图相同;这一行的第三列放通量项 ),“边界项”是 。
At the defaults and . The gradient formula term by term (Clenshaw–Curtis quadrature on the same points): the source integral is (for , , as in the right panel of Figure 79, with the flux term in the third column of that row), the boundary term .
| 分量Component | 源项积分Source integral | 边界项Boundary term | 和Sum | 精确梯度Exact gradient | 和与精确之差Sum minus exact |
|---|---|---|---|---|---|
| 0.472777 | −0.616785 | −0.144007698 | −0.144007698 | −6e−14 | |
| 1.732274 | −1.647118 | 0.085155858 | 0.085155858 | 1e−15 | |
| 1.876920 | −1.876920 | 0 | 0 | 0 |
细看这张表。源项积分和边界项各自是 的量,和却小得多: 的两部分(1.73、−1.65)约是和的 20 倍,几乎抵消; 的两部分(0.47、−0.62)约是和的 3.3–4.3 倍;和与精确值相差不到 7e−14。所以边界项不能省:省掉它, 分量会错二十倍。
Read the table carefully. The source integral and the boundary term are each of order one while their sum is much smaller: for the two parts (1.73 and −1.65) are about 20 times the sum and nearly cancel; for (0.47 and −0.62) they are about 3.3–4.3 times the sum; the sums agree with the exact values to within 7e−14. So the boundary term cannot be dropped: without it the component would be wrong by a factor of twenty.
在任意设计下检查对偶:用本附录自己的配点法解三个连续切线问题,再与伴随公式比较。
Check the duality at any design: solve the three continuous tangent problems with this appendix's own collocation and compare with the adjoint formula.
默认设计下(切线问题取 ),切线解与精确导数 (、、)相差不到 2e−13;两条路径给出同一个导数:
At the defaults (tangent problems with ) the tangent solutions agree with the exact derivatives (, , ) to better than 2e−13, and the two paths give the same derivative:
| 分量Component | 切线解 Tangent | 伴随公式Adjoint formula | 两者之差Difference | 切线解的误差Error of the tangent |
|---|---|---|---|---|
| −0.1440076980 | −0.1440076980 | 5e−14 | 1e−13 | |
| 0.0851558583 | 0.0851558583 | 2e−13 | 2e−13 | |
| 0.0000000000 | 0.0000000000 | 0 | 0 |
这就是连续层次的对偶恒等式:左边一次切线求解给一个分量,右边一次伴随求解给全部分量。它与离散的 是同一件事:分部积分扮演了转置的角色。
This is the duality identity at the continuous level: on the left one tangent solve gives one component, on the right one adjoint solve gives all of them. It is the same thing as the discrete , with integration by parts playing the role of the transpose.
A.6 x = 0 处的边界层
A.6 The boundary layer at x = 0
为什么层在 ?伴随的对流项 ()把信息从 带向 ——与流动方向相反。所以 是伴随的“上游”, 的条件只能靠一个薄层来满足。
Why is the layer at ? The adjoint convection term (with ) carries information from towards , against the flow. So is upstream for the adjoint, and the condition at can only be met through a thin layer.
外解。略去 ,得 ;从上游端 积分,得
Outer solution. Drop : , integrated from the upstream end :
内解。在 附近 ,层内 变化快、 的作用可略,方程近似为 ,解为 。要求 、在层外与外解匹配,得复合近似:
Inner solution. Near , ; inside the layer varies fast and the source is negligible, so the equation is approximately , whose solutions are . Requiring and a match with the outer solution outside the layer gives the composite approximation:
实验的答案: 趋于一个非零常数,所以边界项 在 时不消失,而是趋于 。这也解释了离散的情形:边界面的黏性项 乘 ,只有网格分辨了宽度 的层,乘积 才逼近 (第 16 节)。但进入梯度的是整个入口项 ;在默认设计下,它在粗得多的网格上就已准确(第 16 节)。
The answer to the experiment: tends to a nonzero constant, so the boundary term does not vanish as ; it tends to . This also explains the discrete picture: the viscous boundary term multiplies , and the product approaches only once the mesh resolves the layer of width (Section 16). What enters the gradient, however, is the whole inlet term , and at the default design it is accurate on much coarser meshes (Section 16).
误差有多大。由 A.4, 的精确表达式为 ( 是 A.4 积分因子公式里 时的 )。对两个积分分别展开——分母用 Laplace 方法,;分子用外展开加端点修正——得到两项形式渐近展开
How large is the error. The exact formula is , where is the of the integrating-factor formula of A.4 with . Expanding the two integrals separately (Laplace's method for the denominator, ; an outer expansion with an end correction for the numerator) gives the formal two-term expansion
下表用谱方法核对(每个 一次求解):一项近似的误差除以 趋于 ,两项近似的剩余除以 保持有界。默认 时一项近似差 2.3e−3(约 0.16%)。
The table checks this against the spectral solver (one solve per ): the error of the one-term approximation divided by tends to , and the remainder of the two-term approximation divided by stays bounded. At the default the one-term approximation is off by 2.3e−3 (about 0.16%).
| 误差 error | 两项后误差 after two terms | ||||
|---|---|---|---|---|---|
| 0.1 | −13.5304 | −1.353042 | 7.58e−2 | 0.7582 | 5.74 |
| 0.03 | −47.2758 | −1.418273 | 1.06e−2 | 0.3531 | 5.61 |
| 0.01 | −142.6568 | −1.426568 | 2.30e−3 | 0.2297 | 4.50 |
| 0.003 | −476.0922 | −1.428277 | 5.89e−4 | 0.1962 | 3.82 |
| 0.001 | −1428.6769 | −1.428677 | 1.88e−4 | 0.1883 | 3.59 |
自己调 :实验台每次用本页的谱方法重新求解,画出 、层内放大与复合近似,并给出 、 和梯度的两部分。
Move yourself: the lab re-solves with this page's spectral solver each time, draws , the zoom into the layer with the composite approximation, and reports , and the two parts of the gradient.
播放器把 从 0.2 一步步减到 0.001:看层怎样变薄, 的点怎样落到渐近线上,而梯度的和始终不变。
The player lowers step by step from 0.2 to 0.001: watch the layer thin, the points of settle onto the asymptote, and the sum of the gradient stay put.
附录 B:格点格式
Appendix B: The node-centred scheme
本节实验:同一个问题,把未知量从单元移到节点上,离散梯度会更准吗?对三种重构各猜一次,再看 B.2 的表。
Experiment: The same problem with the unknowns moved from the cells to the nodes: is the discrete gradient more accurate? Guess for each of the three reconstructions, then read the table of B.2.
本页的推导和检查针对未知量放在单元里的有限体积格式。同一个问题——同样的方程、构造解、设计 、目标 、Rusanov 通量、三种重构和自动微分记号——也可以把未知量放在网格节点上:这是格点页的内容。它与本页平行(同样的 18 节和三个附录),每个数字都是针对那个格式重新推导、重新测量的。本附录只说明两种格式在哪里不同,并把两者的一组数字并排放在一起。
The derivations and checks of this page are for the finite-volume scheme with the unknowns in the cells. The same problem — the same equation, manufactured solution, design , objective , Rusanov flux, the three reconstructions and the automatic-differentiation notation — can also be discretised with the unknowns at the mesh nodes; that is the subject of the node-centred page. It runs parallel to this one (the same 18 sections and three appendices), with every number re-derived and re-measured for that scheme. This appendix only says where the two schemes differ and puts one set of numbers side by side.
B.1 两种格式的差别
B.1 Where the two schemes differ
图 81 画出两个网格,下表列出两种格式的差别。
Figure 81 draws the two meshes; the table lists where the schemes differ.
| 方面Aspect | 格心(本页)Cell-centred (this page) | 格点(伙伴页)Node-centred (the partner page) |
|---|---|---|
| 未知量Unknowns | 个单元平均 () cell averages () | 个节点值 (,含两个边界节点) nodal values (, the two boundary nodes included) |
| 控制体Control volumes | 个单元,宽 the cells, width | 节点周围的对偶控制体:内部宽 ,两端宽 (两端的控制体只在目标函数的权重里出现)the dual intervals around the nodes: width inside, at the two ends (the end intervals enter only the weights of the objective) |
| 面Faces | 个面;面 与面 读给定值,离单元中心 (第 4 节) faces; faces and read the prescribed values, from the cell centre (Section 4) | 个面,每个面夹在相邻两个节点之间,两侧都有节点 faces, each between two consecutive nodes, with a node on each side |
| 边界条件Boundary conditions | 进入两个边界面的通量;两个端单元不重构(斜率取 0)inside the flux of the two boundary faces; the two end cells are not reconstructed (slope 0) | 两个约束行 、;边界节点的斜率取单侧差分(一阶格式取 0)two constraint rows , ; the slopes at the boundary nodes are one-sided differences (zero for first order) |
| 精确的参照值Exact reference values | 精确单元平均,带 因子(第 2 节)exact cell averages, with the factor (Section 2) | 点值 point values |
| 目标函数Objective | 单元中心的中点和;对周期的 与它的精确单元平均,当 时恰好等于 (第 7 节)midpoint sum over the cell averages; for the periodic and its exact cell averages it equals exactly when (Section 7) | 梯形和;trapezoid sum; |
| 伴随里的边界数据Boundary data in the adjoint | 入口值的伴随 来自边界面;斜率 要到 才接近 (第 16 节)the adjoint of the inlet value, , comes from the boundary face; the slope approaches only for (Section 16) | 约束行的乘子 、 本身:,它逼近的是 the multipliers , of the constraint rows themselves: , aimed at |
B.2 一个对照:目标与梯度的误差
B.2 One comparison: the errors of the objective and the gradient
下表在默认设计(,,)下,对两种格式、三种重构和 、 两个网格,给出离散目标与精确值之差 (),以及离散梯度 与精确梯度 的最大分量误差。两个模型在构建页面时都被运行,数字由 check_g.js 与两个独立的 Python 参考逐项对照。
At the default design (, , ), for both schemes, the three reconstructions and the two meshes and , the table gives the difference between the discrete objective and its exact value (), and the largest component error of the discrete gradient against the exact gradient . Both models are run when the page is built, and check_g.js holds the numbers to the two independent Python references.
| 重构Reconstruction | 格心 cell | 格心 cell | 格点 node | 格点 node | |
|---|---|---|---|---|---|
| 一阶first order | 8 | 1.1e−1 | 6.0e−1 | 1.9e−1 | 1.0e0 |
| 一阶first order | 64 | 2.3e−2 | 3.2e−1 | 3.0e−2 | 1.6e−1 |
| 无限制器线性unlimited linear | 8 | 6.3e−2 | 3.4e−1 | 7.3e−3 | 3.3e−1 |
| 无限制器线性unlimited linear | 64 | 9.5e−3 | 1.8e−1 | 2.4e−4 | 1.4e−2 |
| 光滑限制器smooth limiter | 8 | 9.1e−2 | 5.1e−1 | 8.5e−3 | 4.7e−1 |
| 光滑限制器smooth limiter | 64 | 1.1e−2 | 1.3e−1 | 1.3e−3 | 1.1e−1 |
在这六行里,无限制器线性与光滑限制器的格点误差都比格心的小;一阶的格点目标误差反而大,梯度误差在 时更大(1.0e0 对 6.0e−1)、 时则更小(1.6e−1 对 3.2e−1)。这只是对这两个网格的描述:两个网格不构成收敛研究,加密时的行为——每个分量、每种重构随 的变化——在格点页的第 15、16 节测量。
In these six rows the node-centred errors are the smaller ones for unlimited linear and the smooth limiter; for first order the node-centred objective error is the larger one, and the gradient error is larger at (1.0e0 against 6.0e−1) and smaller at (1.6e−1 against 3.2e−1). That describes these two meshes only: two meshes are not a convergence study, and the behaviour under refinement — each component, each reconstruction, as a function of — is measured in Sections 15 and 16 of the node-centred page.
图 82 把表扩展到四个网格;下面的实验台列出同样的数字,并给出三个梯度分量和 Newton 迭代次数。
Figure 82 extends the table to four meshes; the lab below lists the same numbers with the three gradient components and the number of Newton iterations.
边界数据的伴随给出另一个对照(无限制器线性,,默认设计):格点格式里,约束行的乘子给出 ,与连续值 相差 1.4%;本页的 (第 16 节)是 1.4949,相差 4.8%。
The adjoint of the boundary data gives a second comparison (unlimited linear, , default design): on the node-centred scheme the multiplier of the constraint row gives , 1.4% from the continuous value ; the of this page (Section 16) is 1.4949, 4.8% from it.
这是数值证据,不是定理:它只对这一个问题、这两个网格成立。格点页的页首与各节说明哪些结论只属于那个格式。
This is numerical evidence, not a theorem: it holds for this one problem and these two meshes. The node-centred page says in its own sections which statements belong to that scheme only.
附录 C:复现与数据
Appendix C: Reproduction and data
本节实验:页面上的一个数字从哪里来?挑一个——比如第 15 节里 的梯度误差——按图 83 追到产生它的程序、记录它的文件和核对它的检查。
Experiment: where does a number on this page come from? Pick one, say the gradient error of Section 15 at , and follow Figure 83 to the program that produced it, the file that records it and the check that holds it.
本页的数字有三个来源:浏览器里的模型(model.js,正文各节与附录 A 的连续伴随)、附录的数值(figs_g.js),以及独立的 Python 包算出的大网格数据。三者各有核对;本附录把它们列清楚,并给出复现的命令。
The numbers of this page have three sources: the model in the browser (model.js: the main sections and the continuous adjoint of Appendix A), the numerics of the appendices (figs_g.js), and the large-grid data of an independent Python package. Each is checked; this appendix lists how, and gives the commands to reproduce them.
data.js 在每次构建时核对哈希与标注,构建程序把数字写进页面。中排:浏览器里运行的两份代码(model.js、figs_g.js)与构建程序 build.js。下排:把它们与独立计算核对的检查(箭头指向被核对的对象)。data.js checks hashes and labels at every build, and the builder writes the numbers into the page. Middle: the two pieces of code that run in the browser (model.js, figs_g.js) and the builder build.js. Bottom: the checks that hold them to independent computations (the arrows point at what is checked).C.1 Python 包
C.1 The Python package
Python 包位于 python/burgers_viscous/,与页面一起发布,只依赖 NumPy 与 SciPy。各文件的作用:
The package lives in python/burgers_viscous/, published with the page; it needs only NumPy and SciPy. What each file does:
| 文件File | 作用Role |
|---|---|
solve.py | 正文的单元中心模型(残差、Newton、前向与伴随、加密研究、完整重算的差分核对);写出 examples/ 的数据the cell-centred model of the main text (residual, Newton, tangent and adjoint, refinement study, FD check with full re-solves); writes the data of examples/ |
fd_scan.py | 差分步长扫描,每个步长两次完整求解(examples/fd_scan.json)the FD step scan with two full solves per step (examples/fd_scan.json) |
test_solver.py | 单元测试:Jacobian 与复数步长比较、转置、加密、连续伴随的边界配对、原 JavaScript 内核的快照unit tests: Jacobian against complex step, transposes, refinement, the continuous boundary pairing, a snapshot of the original JavaScript kernel |
requirements.txt | 依赖:NumPy、SciPydependencies: NumPy, SciPy |
README.md | 方程、格式、命令与限制equation, scheme, commands and limits |
examples/ | 存储的数据 refinement_none.json、refinement_first.json、large_65536.json、fd_scan.json、javascript_reference.json、tests.log;其中三个另有 CSV 版本(refinement_none.csv、refinement_first.csv、large_65536.csv)the stored data refinement_none.json, refinement_first.json, large_65536.json, fd_scan.json, javascript_reference.json, tests.log; three of them also as CSV (refinement_none.csv, refinement_first.csv, large_65536.csv) |
C.2 复现:命令与出处
C.2 Reproduction: commands and provenance
每个结果文件都记录了产生它的命令、程序版本、生成时间、Python/NumPy/SciPy 版本、平台和实现文件的 SHA-256,并标注 mathematical_proof = false。下面的命令直接取自这些记录(解释器路径换成 python3);在包的目录里运行:
Every result file records the command that produced it, the program version, the generation time, the Python/NumPy/SciPy versions, the platform and the SHA-256 of the implementation, and is labelled mathematical_proof = false. The commands below are taken from these records (with the interpreter path replaced by python3); run them in the package directory:
# examples/refinement_none.json (2026-10-02T08:35:50Z)
python3 solve.py --grids 32,64,128,256,512,1024,2048,4096,8192,16384 \
--nu .01 --reconstruction none --gradient-check \
--continuous-adjoint --output examples/refinement_none.json
# examples/refinement_first.json (2026-10-02T08:35:51Z)
python3 solve.py --grids 32,64,128,256,512,1024,2048,4096 --nu .01 \
--reconstruction first --gradient-check \
--output examples/refinement_first.json
# examples/large_65536.json (2026-10-02T08:35:53Z)
python3 solve.py --cells 65536 --nu .01 --reconstruction none \
--gradient-check --output examples/large_65536.json| 文件File | 页面上的用途Used in | 生成(UTC)Generated (UTC) | 版本Version | Python / NumPy / SciPyPython / NumPy / SciPy |
|---|---|---|---|---|
examples/refinement_none.json | 第 7、15、16、18 节,C.2 的实验台与图 84(无限制器线性,N = 32 … 16384)Sections 7, 15, 16, 18, the lab of C.2 and Figure 84 (unlimited linear, N = 32 … 16384) | 2026-10-02T08:35:50Z | 1.0.0 | 3.14.3 / 2.4.6 / 1.18.1 |
examples/refinement_first.json | 第 7、15、16、18 节,C.2 的实验台与图 84(一阶,N = 32 … 4096)Sections 7, 15, 16, 18, the lab of C.2 and Figure 84 (first order, N = 32 … 4096) | 2026-10-02T08:35:51Z | 1.0.0 | 3.14.3 / 2.4.6 / 1.18.1 |
examples/large_65536.json | 第 15 节与图 84(N = 65536)Section 15 and Figure 84 (N = 65536) | 2026-10-02T08:35:53Z | 1.0.0 | 3.14.3 / 2.4.6 / 1.18.1 |
SHA-256(构建时计算):实现文件 solve.py 与页面读入的三个结果文件。C.1 列出的 examples/fd_scan.json,本页既不读取,也不计算哈希;它由 fd_scan.py 生成,其 implementation_sha256 是 solve.py 的哈希,source_sha256 另记了 solve.py 与 fd_scan.py 两个文件的哈希。data.js 拒绝实现哈希与当前程序不符的数据,所以页面上的数字一定来自这个程序的当前版本。
SHA-256 (computed at build time): the implementation file solve.py and the three result files the page reads. examples/fd_scan.json, listed in C.1, is neither read nor hashed by the page; it was produced by fd_scan.py, its implementation_sha256 is the hash of solve.py, and its source_sha256 records the hashes of both solve.py and fd_scan.py. data.js refuses data whose implementation hash differs from the current program, so the numbers on this page come from the current version of this program.
| 文件File | SHA-256SHA-256 |
|---|---|
examples/large_65536.json | 1241daca39fb2433ca6510c4edf58fac5475abf88d3a294596f2dc73dac5cd17 |
examples/refinement_first.json | 867fa572cfd7972361d20f954bb14905ef2b07bcb00ace4ac12d2fffbf6ff048 |
examples/refinement_none.json | 1bfc1b10868196bfd8527baa5b28d758e2505fdc4b21a0d3b5dd2502a78ecdfa |
solve.py | 0d36ac3febcd7e74c8f21cf95a1068554264c0982955dbbd6a7ebf9856586e3e |
elapsed_seconds)。这些是实测,不是性能保证;本页不依赖它们。elapsed_seconds of the JSON). These are measurements, not performance guarantees; nothing on this page depends on them.在浏览器里复现:选一次存储的计算,实验台用本页的代码重新求解,并逐位比较目标与梯度。
Reproduce in the browser: pick a stored run, the lab re-solves it with this page's code and compares the objective and the gradient digit by digit.
C.3 本页自己的检查
C.3 The page’s own checks
在源码目录 viscous_burgers/ 里运行(Node.js;Python 3 需要 NumPy、SciPy、SymPy):
Run in the source directory viscous_burgers/ (Node.js; Python 3 with NumPy, SciPy and SymPy):
# the page and its data (data.js checks every hash and label) node build.js # the browser model against the independent Python reference, and planted faults node check_model.js node check_model.js --selftest # the numerics of the appendices (continuous adjoint, the comparison of Appendix B, SHA-256) node check_g.js node check_g.js --selftest # the independent Python reference and its audits (reference.json is what check_model.js reads) python3 audit/audit_math.py python3 audit/audit_solve.py python3 audit/audit_continuous.py python3 audit/make_reference.py # the built page in headless Chrome: console errors, empty labs, both languages node tools/probe.js # the tests of the Python package itself (in python/burgers_viscous/) python3 -m unittest -v test_solver.py
C.4 各项说法如何核对
C.4 What is verified, and how
| 页面上的说法Claim on the page | 检查Check | 方式与容差How, tolerance |
|---|---|---|
| 残差、Jacobian、前向与伴随语句(正文模型)residual, Jacobians, tangent and reverse statements (main model) | check_model.js;audit/audit_math.pycheck_model.js; audit/audit_math.py | 与独立 Python 参考及复数步长比较,1e−12;点积检验against the independent Python reference and complex step, 1e−12; dot test |
| 前向 = 伴随,与完整重算的差分比较tangent = adjoint, against FD with full re-solves | check_model.js;audit/audit_solve.pycheck_model.js; audit/audit_solve.py | 1e−12;差分 1e−71e−12; FD 1e−7 |
| 连续伴随(附录 A 的谱方法)continuous adjoint (the spectral solver of Appendix A) | check_model.js;check_g.jscheck_model.js; check_g.js | 与 Python 求解器比较 1e−10;与积分因子法比较 1e−10(相对)against the Python solver 1e−10; against the integrating factor 1e−10 (relative) |
| 梯度公式与边界项、对偶恒等式gradient formula with its boundary term, the duality identity | check_g.js;audit/audit_continuous.pycheck_g.js; audit/audit_continuous.py | 与精确梯度比较 1e−12;切线解与伴随之差 1e−11against the exact gradient 1e−12; tangent against adjoint 1e−11 |
| 边界层的两项展开the two-term layer expansion | check_g.jscheck_g.js | 数值:误差、剩余 有界(形式推导,非证明)numerical: error, remainder bounded (formal derivation, not a proof) |
| 附录 B 的对照(两个模型的目标与梯度、)the comparison of Appendix B (objective and gradient of the two models, ) | check_g.jscheck_g.js | 与两个独立的 Python 参考比较,1e−9against the two independent Python references, 1e−9 |
| 数据的出处与完整性provenance and integrity of the data | data.js(每次构建)data.js (every build) | SHA-256、标注、字段SHA-256, labels, fields |
| 页面能运行、手机上不横向滚动the page runs, no sideways scroll on phones | tools/probe.js;experiments/check_site_widths.jstools/probe.js; experiments/check_site_widths.js | 无控制台错误,375/360/320 pxno console error, 375/360/320 px |
这些检查都是数值证据:它们说明代码与公式一致、与独立计算一致、与存储的数据一致,但不证明非线性问题或格式的存在性、唯一性或收敛定理。本页的证明是附录 A.4 以及第 5–7 节的短证明(Newton 的二次余项、一阶 Jacobian 的 M 矩阵性质与 GS 收敛、周期解下 的精确性);其余标明为“未证明”的地方,就是未证明。
All these checks are numerical evidence: they show that the code agrees with the formulas, with independent computations and with the stored data, but they do not prove existence, uniqueness or convergence theorems for the nonlinear problem or the scheme. The proofs on this page are those of Appendix A.4 and the short proofs of Sections 5–7 (the quadratic remainder of Newton's method, the M-matrix property and the Gauss–Seidel convergence for the first-order Jacobian, the exactness of for the periodic solution); whatever is labelled unproved is unproved.
C.5 浏览器模型的源码
C.5 The source of the browser model
下面是页面运行的 model.js 全文,SHA-256 为 1c04a44882e03bc8f507991db1b4bef536696e352eed3711e9e8aef902e8eb49(构建时计算)。实验台可以在你的浏览器里重新计算这个哈希。
Below is the full model.js the page runs, with SHA-256 1c04a44882e03bc8f507991db1b4bef536696e352eed3711e9e8aef902e8eb49 (computed at build time). The lab can recompute this hash in your browser.
model.js 全文(点击展开)model.js in full (click to open)
'use strict';
/* The cell-centred viscous Burgers model of the page: the residual code, its tangent code and its reverse code, written statement by
statement exactly as the page prints them (Sections 3, 5, 8, 9), and the solves built on them. Nothing here is generated by an AD tool:
viscous_burgers/audit/ (Python, complex step, dot tests, an independent spectral solver) holds every formula of this file to account, and
check_model.js compares this file with it.
Problem. q(u) = u^2/2 - nu u_x, q_x = s(x; D) on (0,1), u(0) = u_in, u(1) = u_out. Manufactured: u*(x; D) = 1 + a sin(2 pi (x - phi)),
D = (a, phi, nu), s = q*_x, u_in = u*(0), u_out = u*(1). Objective J = int_0^1 (1 + x) u dx = 3/2 - a cos(2 pi phi)/(2 pi).
Mesh. N cells of width h = 1/N; face f at x_f = f h (f = 0..N); cell i between faces i and i+1; U_i = the average of u over cell i.
Face flux H_f = (qL^2 + qR^2)/4 - (qR - qL) - nu (UR - UL)/d_f (Rusanov, fixed speed 2, plus the central viscous flux)
Residual R_i = H_{i+1} - H_i - (Q_{i+1} - Q_i), Q_f = q*(x_f; D), J_h = sum_i w_i U_i, w_i = h (1 + x_i). */
(function (root) {
const TWO_PI = 2 * Math.PI;
const NUM = typeof module !== 'undefined' ? require('../experiments/numerics') : root.EduNumerics;
/* ---------- the exact functions of the manufactured solution (D = [a, phi, nu]) ---------- */
const ustar = (x, D) => 1 + D[0] * Math.sin(TWO_PI * (x - D[1]));
const ustarX = (x, D) => TWO_PI * D[0] * Math.cos(TWO_PI * (x - D[1]));
const qstar = (x, D) => { const u = ustar(x, D); return 0.5 * u * u - D[2] * ustarX(x, D); };
/* tangent of Q = q*(x; D) for the seed Dd and its reverse (accumulated into Db) */
function qstarD(x, D, Dd) {
const z = TWO_PI * (x - D[1]), s = Math.sin(z), c = Math.cos(z), u = 1 + D[0] * s;
return (u * s - D[2] * TWO_PI * c) * Dd[0] + (u * (-TWO_PI * D[0] * c) - D[2] * TWO_PI * TWO_PI * D[0] * s) * Dd[1] + (-TWO_PI * D[0] * c) * Dd[2];
}
function qstarB(x, D, Qb, Db) {
const z = TWO_PI * (x - D[1]), s = Math.sin(z), c = Math.cos(z), u = 1 + D[0] * s;
Db[0] += (u * s - D[2] * TWO_PI * c) * Qb;
Db[1] += (u * (-TWO_PI * D[0] * c) - D[2] * TWO_PI * TWO_PI * D[0] * s) * Qb;
Db[2] += (-TWO_PI * D[0] * c) * Qb;
}
/* boundary data and their tangents: u_in = u*(0), u_out = u*(1) (equal, u* is periodic) */
const uin = D => ustar(0, D), uout = D => ustar(1, D);
const uinD = (D, Dd) => Math.sin(TWO_PI * (0 - D[1])) * Dd[0] + (-TWO_PI * D[0] * Math.cos(TWO_PI * (0 - D[1]))) * Dd[1];
const uoutD = (D, Dd) => Math.sin(TWO_PI * (1 - D[1])) * Dd[0] + (-TWO_PI * D[0] * Math.cos(TWO_PI * (1 - D[1]))) * Dd[1];
const exactObjective = D => 1.5 - D[0] * Math.cos(TWO_PI * D[1]) / TWO_PI;
const exactGradient = D => [-Math.cos(TWO_PI * D[1]) / TWO_PI, D[0] * Math.sin(TWO_PI * D[1]), 0];
/* the cell average of u*: 1 + a sinc(h) sin(2 pi (x_i - phi)), sinc(h) = sin(pi h)/(pi h) */
const exactAverage = (x, h, D) => x.map(xi => 1 + D[0] * (Math.sin(Math.PI * h) / (Math.PI * h)) * Math.sin(TWO_PI * (xi - D[1])));
function make(opts) {
const N = opts.N, kind = opts.kind || 'none', eps = opts.eps === undefined ? 0.01 : opts.eps;
if (!Number.isInteger(N) || N < 4 || N > 4096) throw Error('viscous Burgers: N must be an integer in [4, 4096]');
if (!['first', 'none', 'smooth'].includes(kind)) throw Error('viscous Burgers: the reconstruction is first, none or smooth');
if (!(eps > 0)) throw Error('viscous Burgers: eps > 0');
const h = 1 / N, x = Array.from({length: N}, (_, i) => (i + 0.5) * h), xf = Array.from({length: N + 1}, (_, f) => f * h);
const w = x.map(xi => h * (1 + xi));
const zeros = n => new Array(n).fill(0);
/* the slope of cell i from its two one-sided differences m = U_i - U_{i-1}, p = U_{i+1} - U_i, and its partial derivatives */
function sigma(m, p) {
if (kind === 'first') return 0;
if (kind === 'none') return 0.5 * (m + p);
return m * p * (m + p) / (m * m + p * p + eps * eps);
}
function sigmaD(m, p) {
if (kind === 'first') return [0, 0];
if (kind === 'none') return [0.5, 0.5];
const den = m * m + p * p + eps * eps, num = m * p * (m + p);
return [((2 * m * p + p * p) * den - 2 * m * num) / (den * den), ((m * m + 2 * m * p) * den - 2 * p * num) / (den * den)];
}
const dist = f => (f === 0 || f === N ? h / 2 : h);
/* ---------- gather + reconstruct (statements 5-6 of the residual code of Sections 5, 8 and 9), for every face: the page's arrays ---------- */
function faces(U, D) {
const nu = D[2], s = zeros(N), qL = zeros(N + 1), qR = zeros(N + 1), UL = zeros(N + 1), UR = zeros(N + 1), d = zeros(N + 1), H = zeros(N + 1);
for (let i = 1; i < N - 1; i++) s[i] = sigma(U[i] - U[i - 1], U[i + 1] - U[i]);
for (let f = 0; f <= N; f++) {
if (f === 0) { qL[f] = uin(D); UL[f] = uin(D); } else { qL[f] = U[f - 1] + 0.5 * s[f - 1]; UL[f] = U[f - 1]; }
if (f === N) { qR[f] = uout(D); UR[f] = uout(D); } else { qR[f] = U[f] - 0.5 * s[f]; UR[f] = U[f]; }
d[f] = dist(f);
H[f] = 0.25 * (qL[f] * qL[f] + qR[f] * qR[f]) - (qR[f] - qL[f]) - nu * (UR[f] - UL[f]) / d[f];
}
return {s, qL, qR, UL, UR, d, H};
}
const sourceRows = D => { const S = zeros(N); for (let i = 0; i < N; i++) S[i] = qstar(xf[i + 1], D) - qstar(xf[i], D); return S; };
/* ---------- PRIMAL: the residual code ---------- */
function residual(U, D) {
const F = faces(U, D), S = sourceRows(D), R = zeros(N);
for (let f = 0; f <= N; f++) { if (f >= 1) R[f - 1] += F.H[f]; if (f <= N - 1) R[f] -= F.H[f]; }
for (let i = 0; i < N; i++) R[i] -= S[i];
return R;
}
const objective = U => U.reduce((acc, u, i) => acc + w[i] * u, 0);
/* ---------- TANGENT: residual_d(U, Ud, D, Dd) = A Ud + R_D Dd, in the order of the primal code; tangentFaces returns every intermediate tangent ---------- */
function tangentFaces(U, Ud, D, Dd) {
const nu = D[2], nud = Dd[2], uind = uinD(D, Dd), uoutd = uoutD(D, Dd);
const s = zeros(N), sd = zeros(N);
for (let i = 1; i < N - 1; i++) {
const m = U[i] - U[i - 1], p = U[i + 1] - U[i], md = Ud[i] - Ud[i - 1], pd = Ud[i + 1] - Ud[i], g = sigmaD(m, p);
s[i] = sigma(m, p); sd[i] = g[0] * md + g[1] * pd;
}
const T = {s, sd, qL: zeros(N + 1), qLd: zeros(N + 1), qR: zeros(N + 1), qRd: zeros(N + 1), UL: zeros(N + 1), ULd: zeros(N + 1), UR: zeros(N + 1), URd: zeros(N + 1),
cL: zeros(N + 1), cR: zeros(N + 1), Hd: zeros(N + 1), Qd: zeros(N + 1), Sd: zeros(N), uind, uoutd};
const Rd = zeros(N);
for (let f = 0; f <= N; f++) {
let qL, qLd, UL, ULd, qR, qRd, UR, URd;
if (f === 0) { qL = uin(D); qLd = uind; UL = qL; ULd = uind; } else { qL = U[f - 1] + 0.5 * s[f - 1]; qLd = Ud[f - 1] + 0.5 * sd[f - 1]; UL = U[f - 1]; ULd = Ud[f - 1]; }
if (f === N) { qR = uout(D); qRd = uoutd; UR = qR; URd = uoutd; } else { qR = U[f] - 0.5 * s[f]; qRd = Ud[f] - 0.5 * sd[f]; UR = U[f]; URd = Ud[f]; }
const cL = 0.5 * qL + 1, cR = 0.5 * qR - 1, df = dist(f);
const Hd = cL * qLd + cR * qRd - nu * (URd - ULd) / df - nud * (UR - UL) / df;
T.qL[f] = qL; T.qLd[f] = qLd; T.qR[f] = qR; T.qRd[f] = qRd; T.UL[f] = UL; T.ULd[f] = ULd; T.UR[f] = UR; T.URd[f] = URd; T.cL[f] = cL; T.cR[f] = cR; T.Hd[f] = Hd;
if (f >= 1) Rd[f - 1] += Hd;
if (f <= N - 1) Rd[f] -= Hd;
}
for (let f = 0; f <= N; f++) T.Qd[f] = qstarD(xf[f], D, Dd);
for (let i = 0; i < N; i++) { T.Sd[i] = T.Qd[i + 1] - T.Qd[i]; Rd[i] -= T.Sd[i]; }
T.Rd = Rd;
return T;
}
const residualD = (U, Ud, D, Dd) => tangentFaces(U, Ud, D, Dd).Rd;
/* ---------- REVERSE: residual_b(U, D, Rb) = (A^T Rb, R_D^T Rb), the statements of residual_d in reverse order, accumulating with += ---------- */
function reverseFaces(U, D, Rb) {
const nu = D[2], Ub = zeros(N), Db = [0, 0, 0];
const Qb = zeros(N + 1); // the adjoint of Q_f: statements R_i -= Q_{i+1} - Q_i run first in reverse
for (let i = N - 1; i >= 0; i--) { Qb[i + 1] += -Rb[i]; Qb[i] += Rb[i]; }
for (let f = N; f >= 0; f--) qstarB(xf[f], D, Qb[f], Db);
const s = zeros(N), sm = zeros(N), sp = zeros(N), sb = zeros(N);
for (let i = 1; i < N - 1; i++) { const m = U[i] - U[i - 1], p = U[i + 1] - U[i], g = sigmaD(m, p); s[i] = sigma(m, p); sm[i] = g[0]; sp[i] = g[1]; }
const B = {Qb, Hb: zeros(N + 1), qLb: zeros(N + 1), qRb: zeros(N + 1), ULb: zeros(N + 1), URb: zeros(N + 1), sb, nub: zeros(N + 1)};
let uinb = 0, uoutb = 0;
for (let f = N; f >= 0; f--) {
let qL, UL, qR, UR;
if (f === 0) { qL = uin(D); UL = qL; } else { qL = U[f - 1] + 0.5 * s[f - 1]; UL = U[f - 1]; }
if (f === N) { qR = uout(D); UR = qR; } else { qR = U[f] - 0.5 * s[f]; UR = U[f]; }
const df = dist(f), Hb = (f >= 1 ? Rb[f - 1] : 0) - (f <= N - 1 ? Rb[f] : 0);
const cL = 0.5 * qL + 1, cR = 0.5 * qR - 1, qLb = cL * Hb, qRb = cR * Hb, ULb = nu / df * Hb, URb = -nu / df * Hb;
B.Hb[f] = Hb; B.qLb[f] = qLb; B.qRb[f] = qRb; B.ULb[f] = ULb; B.URb[f] = URb; B.nub[f] = -(UR - UL) / df * Hb;
Db[2] += B.nub[f];
if (f === 0) uinb += qLb + ULb; else { Ub[f - 1] += qLb + ULb; sb[f - 1] += 0.5 * qLb; }
if (f === N) uoutb += qRb + URb; else { Ub[f] += qRb + URb; sb[f] += -0.5 * qRb; }
}
for (let i = N - 2; i >= 1; i--) { const mb = sm[i] * sb[i], pb = sp[i] * sb[i]; Ub[i] += mb - pb; Ub[i - 1] += -mb; Ub[i + 1] += pb; }
const zin = TWO_PI * (0 - D[1]), zout = TWO_PI * (1 - D[1]);
Db[0] += Math.sin(zin) * uinb + Math.sin(zout) * uoutb;
Db[1] += -TWO_PI * D[0] * (Math.cos(zin) * uinb + Math.cos(zout) * uoutb);
B.Ub = Ub; B.Db = Db; B.uinb = uinb; B.uoutb = uoutb;
return B;
}
const residualB = (U, D, Rb) => { const B = reverseFaces(U, D, Rb); return {Ub: B.Ub, Db: B.Db}; };
/* ---------- the Jacobians by columns of the tangent code ---------- */
function jacState(U, D) {
const A = Array.from({length: N}, () => zeros(N)), z = [0, 0, 0];
for (let j = 0; j < N; j++) { const e = zeros(N); e[j] = 1; const col = residualD(U, e, D, z); for (let i = 0; i < N; i++) A[i][j] = col[i]; }
return A;
}
function jacDesign(U, D) {
const z = zeros(N), cols = [0, 1, 2].map(k => residualD(U, z, D, [0, 1, 2].map(m => (m === k ? 1 : 0))));
return Array.from({length: N}, (_, i) => [cols[0][i], cols[1][i], cols[2][i]]);
}
const transpose = A => A[0].map((_, j) => A.map(r => r[j]));
const infnorm = v => v.reduce((m, z) => Math.max(m, Math.abs(z)), 0);
/* ---------- the primal solve: Newton with a backtracking line search and the original positive box 0.1 < U < 1.9 ---------- */
function solve(D, o = {}) {
let U = o.U0 ? o.U0.slice() : x.map(xi => ustar(xi, D)); // centre samples of u*: an initial guess only
const history = [], states = [], tol = o.tol === undefined ? 1e-13 : o.tol, maxit = o.maxit || 35;
for (let it = 0; it < maxit; it++) {
const R = residual(U, D), A = jacState(U, D), dU = NUM.solve(NUM.factor(A), R.map(v => -v));
const rn = infnorm(R), dn = infnorm(dU);
history.push({iteration: it, residual: rn, correction: dn}); states.push(U.slice());
if (dn <= tol || rn < 1e-15) return {U, history, states, converged: true};
let step = 1, ok = false;
for (let k = 0; k < 25; k++) {
const T = U.map((u, i) => u + step * dU[i]);
if (Math.min(...T) > 0.1 && Math.max(...T) < 1.9 && infnorm(residual(T, D)) < (1 - 1e-4 * step) * rn) { U = T; ok = true; break; }
step *= 0.5;
}
if (!ok) return {U, history, states, converged: false, reason: 'line search failed'};
}
return {U, history, states, converged: false, reason: 'iteration limit'};
}
/* ---------- the tangent solves A v_k = -R_D e_k and the adjoint solve A^T psi = -g (g = w) ---------- */
function sensitivities(U, D) {
const A = jacState(U, D), RD = jacDesign(U, D), FA = NUM.factor(A), FAT = NUM.factor(transpose(A));
const v = [0, 1, 2].map(k => NUM.solve(FA, RD.map(r => -r[k])));
const psi = NUM.solve(FAT, w.map(z => -z));
const gradTangent = v.map(vk => vk.reduce((acc, z, i) => acc + w[i] * z, 0));
const gradAdjoint = [0, 1, 2].map(k => RD.reduce((acc, r, i) => acc + psi[i] * r[k], 0));
return {A, RD, v, psi, gradTangent, gradAdjoint};
}
/* ---------- complex step: the SAME residual code with complex arithmetic (real and imaginary parts as two arrays); only the smooth operations the
residual uses occur: + - * / and sin, cos of a complex argument ---------- */
function residualComplex(Ure, Uim, Dre, Dim) {
const C = {add: (a, b) => [a[0] + b[0], a[1] + b[1]], sub: (a, b) => [a[0] - b[0], a[1] - b[1]], mul: (a, b) => [a[0] * b[0] - a[1] * b[1], a[0] * b[1] + a[1] * b[0]],
div: (a, b) => { const d = b[0] * b[0] + b[1] * b[1]; return [(a[0] * b[0] + a[1] * b[1]) / d, (a[1] * b[0] - a[0] * b[1]) / d]; },
sc: (t, a) => [t * a[0], t * a[1]], k: t => [t, 0],
sin: a => [Math.sin(a[0]) * Math.cosh(a[1]), Math.cos(a[0]) * Math.sinh(a[1])], cos: a => [Math.cos(a[0]) * Math.cosh(a[1]), -Math.sin(a[0]) * Math.sinh(a[1])]};
const U = Ure.map((r, i) => [r, Uim[i]]), a = [Dre[0], Dim[0]], phi = [Dre[1], Dim[1]], nu = [Dre[2], Dim[2]];
const ustarC = xx => C.add(C.k(1), C.mul(a, C.sin(C.sc(TWO_PI, C.sub(C.k(xx), phi)))));
const qstarC = xx => { const z = C.sc(TWO_PI, C.sub(C.k(xx), phi)), u = C.add(C.k(1), C.mul(a, C.sin(z))), ux = C.mul(C.sc(TWO_PI, a), C.cos(z)); return C.sub(C.sc(0.5, C.mul(u, u)), C.mul(nu, ux)); };
const sigmaC = (m, p) => {
if (kind === 'first') return C.k(0);
if (kind === 'none') return C.sc(0.5, C.add(m, p));
return C.div(C.mul(C.mul(m, p), C.add(m, p)), C.add(C.add(C.mul(m, m), C.mul(p, p)), C.k(eps * eps)));
};
const s = Array.from({length: N}, () => C.k(0));
for (let i = 1; i < N - 1; i++) s[i] = sigmaC(C.sub(U[i], U[i - 1]), C.sub(U[i + 1], U[i]));
const R = Array.from({length: N}, () => C.k(0));
for (let f = 0; f <= N; f++) {
const qL = f === 0 ? ustarC(0) : C.add(U[f - 1], C.sc(0.5, s[f - 1])), UL = f === 0 ? ustarC(0) : U[f - 1];
const qR = f === N ? ustarC(1) : C.sub(U[f], C.sc(0.5, s[f])), UR = f === N ? ustarC(1) : U[f];
const H = C.sub(C.sub(C.sc(0.25, C.add(C.mul(qL, qL), C.mul(qR, qR))), C.sub(qR, qL)), C.sc(1 / dist(f), C.mul(nu, C.sub(UR, UL))));
if (f >= 1) R[f - 1] = C.add(R[f - 1], H);
if (f <= N - 1) R[f] = C.sub(R[f], H);
}
for (let i = 0; i < N; i++) R[i] = C.sub(R[i], C.sub(qstarC(xf[i + 1]), qstarC(xf[i])));
return {re: R.map(z => z[0]), im: R.map(z => z[1])};
}
return {N, h, kind, eps, x, xf, w, dist, sigma, sigmaD, faces, sourceRows, residual, residualD, residualB, tangentFaces, reverseFaces, objective, jacState, jacDesign, transpose, solve, sensitivities, residualComplex,
exactAverage: D => exactAverage(x, h, D), infnorm};
}
/* ---------- the continuous adjoint, by an independent method: Chebyshev collocation of -u lam' - nu lam'' = -(1 + x), lam(0) = lam(1) = 0 ---------- */
function continuousAdjoint(D, M = 200) {
const xc = Array.from({length: M + 1}, (_, k) => Math.cos(Math.PI * k / M)), c = xc.map((_, k) => (k === 0 || k === M ? 2 : 1) * (k % 2 ? -1 : 1));
const Dm = Array.from({length: M + 1}, () => new Array(M + 1).fill(0));
for (let i = 0; i <= M; i++) { let row = 0; for (let j = 0; j <= M; j++) if (i !== j) { Dm[i][j] = (c[i] / c[j]) / (xc[i] - xc[j]); row += Dm[i][j]; } Dm[i][i] = -row; }
const x = xc.map(z => (z + 1) / 2), d1 = Dm.map(r => r.map(z => 2 * z));
const d2 = d1.map((r, i) => r.map((_, j) => r.reduce((acc, z, k) => acc + z * d1[k][j], 0)));
const L = d1.map((r, i) => r.map((z, j) => -ustar(x[i], D) * z - D[2] * d2[i][j])), rhs = x.map(z => -(1 + z));
for (const i of [0, M]) { L[i] = new Array(M + 1).fill(0); L[i][i] = 1; rhs[i] = 0; }
const lam = NUM.solve(NUM.factor(L), rhs), lamX = d1.map(r => r.reduce((acc, z, j) => acc + z * lam[j], 0));
/* barycentric interpolation of lam and lam_x at arbitrary points */
const bw = xc.map((_, k) => (k === 0 || k === M ? 0.5 : 1) * (k % 2 ? -1 : 1));
const interp = (f, z) => { let num = 0, den = 0; for (let k = 0; k <= M; k++) { const dz = z - x[k]; if (Math.abs(dz) < 1e-15) return f[k]; const t = bw[k] / dz; num += t * f[k]; den += t; } return num / den; };
return {x, lam, lamX, at: z => interp(lam, z), atX: z => interp(lamX, z)};
}
const api = {make, ustar, ustarX, qstar, qstarD, uin, uout, exactObjective, exactGradient, exactAverage, continuousAdjoint, TWO_PI,
DEFAULT_D: [0.2, 0.07, 0.01], DEFAULTS: {N: 8, kind: 'none'}};
if (typeof module !== 'undefined') module.exports = api; else root.ViscousBurgers = api;
})(typeof window !== 'undefined' ? window : globalThis);