← 首页← Home

二维标量对流方程的离散伴随:格点格式

Discrete adjoints for two-dimensional scalar advection: node-centred

算例是一张环形(O 型)混合网格:内圈是物面,外圈是远场特征边界,贴壁一层用四边形、外层用三角形。物面称为无粘壁面,在通量内部用一个弱闭合处理:每个壁面半面上令 h=0。它是让边循环最简单的离散闭合,不是这个方程在物面上的物理条件(第 4 节);粘性壁面是另一对页面——二维标量对流扩散方程——的题目。未知量放在网格节点上,控制体是围绕节点的中位对偶体。本页先介绍方程和它的作用,再把对偶控制体是怎么“围”出来的一步步画清楚,然后把同一个边循环依次改写成 matrix-free 前向(计算 Av)和 matrix-free 伴随(计算 ATw)。每个循环都可以逐句播放。

The test case is an annular (O-type) hybrid mesh: the inner ring is the body surface, the outer ring a far-field characteristic boundary, with quadrilaterals in the layer against the wall and triangles outside it. The body is called the inviscid wall and is closed weakly, inside the flux: every wall half-face gets h=0. That is a discrete closure that keeps the edge loop simple, not the physical boundary condition of this equation at the body (Section 4); a viscous wall is the subject of another pair of pages, the two-dimensional scalar advection–diffusion equation. The unknowns live on the mesh nodes, and the control volume is the median dual carved out around each node. After introducing the equation and what it is for, this page draws, step by step, how that dual control volume is built, and then rewrites the same edge loop into matrix-free forward (computing Av) and matrix-free adjoint (computing ATw). Every loop can be played statement by statement.

全页路线图:从设计输入到梯度设计输入 DD = (u∞, X)49 个分量(1 + 48)第 12 节求解R(u, D) = 0Jacobi 迭代第 5、6 节状态 u24 个未知量每个节点 1 个第 2 节目标 L类似阻力的表面积分g = (∂L/∂u)T第 7 节梯度 dL/dD49 个分量第 12 节前向(tangent)路线:从左往右,每个设计分量一次D˙ = ek第 k 个设计分量Av = −RD D˙​一次线性求解v = u˙​状态的切向量L˙ = gTv + (∇DL)T D˙​方向导数dL/dDk梯度的一个分量49 个设计分量 → 49 次线性求解,每次得到梯度的一个分量(第 8、10 节)伴随(adjoint)路线:从右往左,只解一次dL/dD = ∂L/∂D + ψTRD全部 49 个分量ψ伴随变量ATψ = −g一次转置求解g = (∂L/∂u)T目标对状态的导数1 次转置求解,再做乘积 ψTRD(不再求解),得到全部 49 个分量(第 9、11、12 节)代价:49 个设计分量 ⇒ 前向 49 次线性求解,伴随 1 次全页路线图:从设计输入到梯度原始计算前向 ×49伴随 ×1从上往下从下往上设计输入 DD = (u∞, X)第 12 节求解R(u, D) = 0第 5、6 节状态 u24 个未知量第 2 节目标 Lg = (∂L/∂u)T第 7 节梯度 dL/dD49 个分量第 12 节D˙ = ek第 k 个设计分量Av = −RD D˙​一次线性求解v = u˙​状态的切向量L˙ = gTv+(∇DL)T D˙​方向导数dL/dDk梯度的一个分量dL/dD =∂L/∂D + ψTRD全部 49 个分量ψ伴随变量ATψ = −g一次转置求解g = (∂L/∂u)T目标对状态的导数49 个设计分量:前向 49 次线性求解,伴随 1 次
Page roadmap: from the design input to the gradientdesign input DD = (u∞, X)49 components(1 + 48)Section 12solveR(u, D) = 0Jacobi iterationSections 5–6state u24 unknownsone per nodeSection 2objective Ldrag-likesurface integralg = (∂L/∂u)TSection 7gradient dL/dD49 componentsSection 12Tangent (forward) route: left to right, once per design componentD˙ = ekdesign component kAv = −RD D˙​one linear solvev = u˙​tangent of the stateL˙ = gTv + (∇DL)T D˙​directional derivativedL/dDkone gradient component49 design components → 49 linear solves, each giving one gradient component (Sections 8 and 10)Adjoint route: right to left, a single solvedL/dD = ∂L/∂D + ψTRDall 49 componentsψadjoint variableATψ = −gone transposed solveg = (∂L/∂u)Tobjective vs state1 transposed solve, then the product ψTRD (no further solve) gives all 49 components (Sections 9, 11 and 12)Cost: 49 design components ⇒ 49 tangent solves against 1 adjoint solvePage roadmap: from the design input to the gradientPrimalTangent ×49Adjoint ×1top to bottombottom to topdesign input DD = (u∞, X)Section 12solveR(u, D) = 0Sections 5–6state u24 unknownsSection 2objective Lg = (∂L/∂u)TSection 7gradient dL/dD49 componentsSection 12D˙ = ekcomponent kAv = −RD D˙​one linear solvev = u˙​state tangentL˙ = gTv+(∇DL)T D˙​along component kdL/dDkone componentdL/dD =∂L/∂D + ψTRDall 49 componentsψadjoint variableATψ = −gtransposed solveg = (∂L/∂u)Tobjective vs state49 design components:49 tangent solves, 1 adjoint solve
全页路线图 · 从设计输入到梯度。上排是原始计算:由设计输入求解离散方程,得到状态,再算出目标。前向(tangent)路线顺着计算方向,每个设计分量解一次线性方程,得到梯度的一个分量;伴随(adjoint)路线逆着计算方向,只解一次转置方程,再做一次乘积就得到全部分量。本页默认设置下有 49 个设计分量:49 次前向求解对 1 次伴随求解。方框下方的节号可以点击。Page roadmap · From the design input to the gradient. The top row is the primal computation: solve the discrete equations for the design input, obtain the state, then evaluate the objective. The tangent (forward) route follows the direction of computation and solves one linear system per design component, each giving one gradient component; the adjoint route runs against it, solves a single transposed system and obtains every component with one more product. With this page's default settings there are 49 design components: 49 tangent solves against one adjoint solve. The section numbers under the boxes are links.

阅读方式。本页有四个边值问题,各自单独计算,数值从不混用:

How to read this page. Four boundary-value problems appear on this page, each computed on its own; their numbers are never mixed:

  1. 主线:内圈(无粘壁面)用零通量闭合 h=0,远场 u∞=1。第 1–14 节和第 17 节讲的都是它;第 4 节说明它在迎风一侧多给了一个条件、没有连续极限,第 15 节最后给出它在加密网格上的结果。
  2. 构造解(第 15 节):内圈按特征方向处理,或在每个壁面半面上规定精确通量,用来测格式的精度阶。
  3. 特征边界对照(附录 A):精确解 u=1,用来比较连续伴随与离散伴随。
  4. 新增(选读):第 2–17 节末尾标着“新增(选读)”的扩展,是内外边界都按特征方向处理的光滑模型,分一阶与二阶,它的连续伴随在附录 B。跳过它不影响主线。
  1. The main line: the zero-flux closure h=0 on the inner ring (the inviscid wall) and u∞=1 at the far field. Sections 1–14 and 17 are about it; Section 4 explains why it over-determines the windward side and has no continuum limit, and the end of Section 15 shows it under refinement.
  2. The manufactured problems (Section 15): the inner ring treated by characteristics, or given the exact flux on every wall half-face, to measure the scheme's order of accuracy.
  3. The characteristic-boundary contrast (Appendix A): exact solution u=1, to compare continuous and discrete adjoints.
  4. Added (optional): the extension at the end of Sections 2–17, marked “Added (optional)”: a smooth model with both boundaries treated by characteristics, at first and second order, with its continuous adjoint in Appendix B. Skipping it leaves the main line intact.

新增部分用到的 Newton 法、GMRES、预处理的转置和计算磁带,在第一次出现的地方各有几句说明;GMRES 的逐步讲解见一维 Burgers 页面第 17 节。新增表格采用从 0 开始的代码索引:未知量 i 就是原网格图中的第 i+1 个节点;计算面按逐个单元走边时第一次遇到的顺序编号,与主线的面号不同(例如二阶时新增表中的面 3 是主线的 f17;节点页的面号还随精度改变,因为二阶把对偶面拆成段),所以表格同时给出主线面号 f#。下方的精度开关只作用于新增面板,原有播放器保持原样。

Newton's method, GMRES, the transposed preconditioner and the computational tape used by the added blocks are each explained in a few sentences where they first appear; GMRES is taught step by step in Section 17 of the 1-D Burgers page. Added tables use zero-based code indices: unknown i is node i+1 of the original mesh diagrams; computational faces are numbered in the order the cells first meet their edges, which differs from the main line's face numbers (for instance, at second order face 3 of the added tables is the main line’s f17; on the node page the numbering also changes with the order, since second order splits dual faces into segments), so the tables also give the main-line label f#. The order selector below controls only the added panels; the original players are unchanged.

查看重构View reconstruction · 新模型连续伴随Continuous adjoint of added model

第 1 节Section 1

方程:二维标量守恒律

The equation: a two-dimensional scalar conservation law

从守恒律到要离散的方程

From a conservation law to the equation this page discretises

本页的未知量是平面上的一个标量场 u(x,y)u(x,y)。它不是速度,本页也不给它指定具体的物理含义;它服从一条守恒律:在任意一块固定区域 Ω\Omega 里,uu 的总量只能通过边界流进流出,单位时间穿过边界的量由通量 F(u)F(u) 的法向分量给出(𝒏\boldsymbol n 是边界的外法向):

The unknown of this page is a scalar field u(x,y)u(x,y) in the plane. It is not a velocity, and the page gives it no particular physical meaning; it obeys a conservation law: in any fixed region Ω\Omega the total amount of uu changes only by what flows across the boundary, and the amount crossing the boundary per unit time is the normal component of the flux F(u)F(u) (𝒏\boldsymbol n the outward normal of the boundary):

ddt∫Ωu dA+∮∂ΩF(u)⋅𝒏 ds=0.\frac{d}{dt}\int_\Omega u\,dA+\oint_{\partial\Omega}F(u)\cdot\boldsymbol n\,ds=0.

由散度定理,边界积分等于 ∇⋅F(u)\nabla\cdot F(u) 在 Ω\Omega 上的积分;区域可以任意取,所以被积函数本身必须为零,这就是守恒律的微分形式。

By the divergence theorem the boundary integral is the integral of ∇⋅F(u)\nabla\cdot F(u) over Ω\Omega; the region is arbitrary, so the integrand itself must vanish, and that is the differential form of the conservation law.

本页要离散的是二维标量守恒律:

The page discretises a two-dimensional scalar conservation law:

∂u∂t+∇·F(u)=0,F(u)=12u2β,β=(1,0.5).

通量 F 对 u 是非线性的(∂F/∂u=uβ),所以离散之后得到的 R(u) 同样非线性——这正是后面要迭代求解、并在收敛状态 u* 处线性化的原因。

The flux F is nonlinear in u (with ∂F/∂u=uβ), so the discrete R(u) is nonlinear too — which is exactly why it is solved below by iteration and linearised at the converged state u*.

本页求的是定常解 ∂u/∂t=0\partial u/\partial t=0,要离散的就是 ∇⋅F(u)=0\nabla\cdot F(u)=0。时间导数只在第 3 节用来写出有限体积形式,第 6 节直接解离散方程 R(u)=0R(u)=0。𝜷\boldsymbol\beta 是常矢量,由乘积法则 ∇⋅(12u2𝜷)=u 𝜷⋅∇u\nabla\cdot(\tfrac12u^2\boldsymbol\beta)=u\,\boldsymbol\beta\cdot\nabla u,对光滑解方程也可以写成

This page computes the steady solution, ∂u/∂t=0\partial u/\partial t=0, so the equation to discretise is ∇⋅F(u)=0\nabla\cdot F(u)=0. The time derivative serves only to write the finite-volume form in Section 3; Section 6 solves the discrete equations R(u)=0R(u)=0 directly. Since 𝜷\boldsymbol\beta is a constant vector, the product rule gives ∇⋅(12u2𝜷)=u 𝜷⋅∇u\nabla\cdot(\tfrac12u^2\boldsymbol\beta)=u\,\boldsymbol\beta\cdot\nabla u, and for a smooth solution the equation can also be written

∂u∂t+u 𝜷⋅∇u=0.\frac{\partial u}{\partial t}+u\,\boldsymbol\beta\cdot\nabla u=0.

它说 uu 以速度 u𝜷u\boldsymbol\beta 被输运:方向处处是 𝜷\boldsymbol\beta,快慢由 uu 自己决定。沿任一条平行于 𝜷\boldsymbol\beta 的直线取弧长 ss,𝜷⋅∇u=∣𝜷∣ ∂u/∂s\boldsymbol\beta\cdot\nabla u=|\boldsymbol\beta|\,\partial u/\partial s,方程就成了一维 Burgers 方程 ∂u/∂t+∣𝜷∣ ∂(u2/2)/∂s=0\partial u/\partial t+|\boldsymbol\beta|\,\partial(u^2/2)/\partial s=0,不同的直线之间互不影响。所以它是一维 Burgers 页面方程(去掉源项)在二维的推广:非线性对流不变,只多了一个空间方向。定常时 u ∂u/∂s=0u\,\partial u/\partial s=0,凡 u≠0u\ne0 的地方,uu 沿每条这样的直线保持不变。

It says that uu is transported with the velocity u𝜷u\boldsymbol\beta: the direction is 𝜷\boldsymbol\beta everywhere, the speed is set by uu itself. Along any straight line parallel to 𝜷\boldsymbol\beta, with arc length ss, 𝜷⋅∇u=∣𝜷∣ ∂u/∂s\boldsymbol\beta\cdot\nabla u=|\boldsymbol\beta|\,\partial u/\partial s, and the equation becomes the one-dimensional Burgers equation ∂u/∂t+∣𝜷∣ ∂(u2/2)/∂s=0\partial u/\partial t+|\boldsymbol\beta|\,\partial(u^2/2)/\partial s=0, with no interaction between different lines. So it is the equation of the 1-D Burgers page (without its source) carried into two dimensions: the same nonlinear convection, with one more space direction. In a steady state u ∂u/∂s=0u\,\partial u/\partial s=0: wherever u≠0u\ne0, uu is constant along each such line.

图 1.1 把本页的问题画在一张图上。(a) 是区域和边界:物体与远场圆之间的环形区域,远场分成入流和出流两段,物面是琥珀色的八边形,紫色圆点是 24 个节点。(b) 是本页算出的基准解 u∗u^*(第 2 节起讲怎样算):它在 0.786 到 1.536 之间,离来流值 1 最远的都在贴着物面的一层。下面两小节解释这张图:哪里给边界条件,以及本页的问题由哪些数据组成。

Figure 1.1 draws this page’s problem in one picture. (a) is the domain and its boundaries: the annulus between the body and the far-field circle, the far field split into an inflow and an outflow part, the body an amber octagon, and the 24 nodes as violet dots. (b) is the baseline solution u∗u^* that the page computes (Section 2 onwards shows how): it lies between 0.786 and 1.536, and the values furthest from the free stream 1 all sit in the layer against the body. The next two subsections explain the picture: where the boundary takes data, and which data make up the problem.

(a) 区域、边界与设计变量(a) Domain, boundaries and design variables物体bodyβ入流:u = u∞ = 1inflow: u = u∞ = 1出流:不给条件outflow: no condition(b) 本页的基准解 u*(b) This page’s baseline solution u*β1.5361 = u∞0.786物面:离散闭合 h = 0wall: the discrete closure h = 0远场入流(β·n < 0)far-field inflow (β·n < 0)远场出流(β·n > 0)far-field outflow (β·n > 0)24 个节点:坐标 X24 nodes: coordinates X虚线:物面曲线与远场圆dashed: body curve and far-field circle设计变量 D = (u∞, X):来流值 1 个 + 24 个节点的坐标 48 个 = 49 个分量;未知量 24 个(每个节点一个)design D = (u∞, X): the free stream (1) + the coordinates of 24 nodes (48) = 49 components; 24 unknowns (one per node)
图 1.1 · 本页的问题一览。(a) 区域与边界:物体与远场圆之间的环形区域。虚线是物面曲线和远场圆,网格在每条曲线上取 8 个节点、用直线边相连,所以计算用的物面(琥珀色)和远场都是八边形。远场半面按 β·n 的符号分成入流(蓝,给定 u=u∞=1)和出流(红,不给条件);物面用离散闭合 h=0(第 4 节)。紫色圆点是 24 个节点,它们的坐标 X 与来流值 u∞ 一起组成设计变量 D,共 49 个分量。(b) 本页用 Jacobi 迭代(第 6 节)解出的基准解 u*,画在 24 个中位对偶控制体上,颜色越深 u* 越大:壁面节点上从 0.786 到 1.536,其余节点上从 0.917 到 1.082,离来流值 1 最远的都在贴着物面的一层。Figure 1.1 · The problem at a glance. (a) Domain and boundaries: the annulus between the body and the far-field circle. The dashed curves are the body curve and the far-field circle; the mesh puts 8 nodes on each and joins them with straight edges, so the body (amber) and the far field it computes with are octagons. The far-field half-faces split by the sign of β·n into inflow (blue, where u = u∞ = 1 is given) and outflow (red, no condition); the wall is closed by the discrete closure h = 0 (Section 4). The violet dots are the 24 nodes: their coordinates X and the free-stream value u∞ make up the design variables D, 49 components in all. (b) The baseline solution u* that this page computes with the Jacobi iteration (Section 6), drawn on its 24 median-dual control volumes; darker means larger u*: from 0.786 to 1.536 on the wall nodes, from 0.917 to 1.082 elsewhere, so the values furthest from the free stream 1 all sit in the layer against the body.

特征速度:边界上哪里给条件

The characteristic speed: where the boundary takes data

∂F/∂u=u𝜷\partial F/\partial u=u\boldsymbol\beta 叫做特征速度:uu 的一个小扰动以这个速度移动,方向是 𝜷\boldsymbol\beta,大小是 u∣𝜷∣u|\boldsymbol\beta|。在边界上要看它的法向分量:记 𝒏\boldsymbol n 为指向区域外的法向,信息穿过边界的速度是 u 𝜷⋅𝒏/∣𝒏∣u\,\boldsymbol\beta\cdot\boldsymbol n/|\boldsymbol n|。

∂F/∂u=u𝜷\partial F/\partial u=u\boldsymbol\beta is called the characteristic velocity: a small disturbance of uu moves with it, in the direction 𝜷\boldsymbol\beta and at the speed u∣𝜷∣u|\boldsymbol\beta|. At a boundary its normal component is what counts: with 𝒏\boldsymbol n the normal pointing out of the domain, information crosses the boundary at the speed u 𝜷⋅𝒏/∣𝒏∣u\,\boldsymbol\beta\cdot\boldsymbol n/|\boldsymbol n|.

  • u 𝜷⋅𝒏<0u\,\boldsymbol\beta\cdot\boldsymbol n<0:特征线从外面进入区域,边界必须给出 uu 的值(入流);
  • u 𝜷⋅𝒏<0u\,\boldsymbol\beta\cdot\boldsymbol n<0: the characteristics enter the domain, and the boundary must supply the value of uu (inflow);
  • u 𝜷⋅𝒏>0u\,\boldsymbol\beta\cdot\boldsymbol n>0:特征线从区域里离开,uu 由区域内部决定,边界上不能再规定它(出流)。
  • u 𝜷⋅𝒏>0u\,\boldsymbol\beta\cdot\boldsymbol n>0: the characteristics leave the domain, uu is decided inside it, and the boundary must not prescribe it as well (outflow).

本页的解处处为正,所以只需看 𝜷⋅𝒏\boldsymbol\beta\cdot\boldsymbol n 的符号,判据是纯几何的。远场是半径 1.9 的圆:上游一侧 𝜷⋅𝒏<0\boldsymbol\beta\cdot\boldsymbol n<0,是入流,给定来流值 u=u∞=1u=u_\infty=1;下游一侧是出流,不给条件。本页的 16 个远场半面中 8 个入流、8 个出流。第 4 节把这两种情形写成边界半面上的右状态。

The solution of this page is positive everywhere, so only the sign of 𝜷⋅𝒏\boldsymbol\beta\cdot\boldsymbol n matters and the test is purely geometric. The far field is a circle of radius 1.9: on the upstream side 𝜷⋅𝒏<0\boldsymbol\beta\cdot\boldsymbol n<0, an inflow boundary with the free-stream value u=u∞=1u=u_\infty=1 given; on the downstream side an outflow boundary with no condition. Of this page’s 16 far-field half-faces, 8 are inflow and 8 outflow. Section 4 turns the two cases into the right state of a boundary half-face.

物面(内圈)不同。𝜷\boldsymbol\beta 是常矢量,物体是闭曲线,所以绕物体一圈 𝜷⋅𝒏\boldsymbol\beta\cdot\boldsymbol n 必然正负都有(这里 𝒏\boldsymbol n 指出区域,也就是指向物体内部):迎风一侧 𝜷⋅𝒏>0\boldsymbol\beta\cdot\boldsymbol n>0,从远场来的特征线在这里离开区域;背风一侧 𝜷⋅𝒏<0\boldsymbol\beta\cdot\boldsymbol n<0,特征线从物面进入区域。本页 16 个壁面半面中 10 个在迎风一侧、6 个在背风一侧。“不可穿透”的物理条件 F(u)⋅𝒏=12u2 𝜷⋅𝒏=0F(u)\cdot\boldsymbol n=\tfrac12u^2\,\boldsymbol\beta\cdot\boldsymbol n=0 会在 𝜷⋅𝒏≠0\boldsymbol\beta\cdot\boldsymbol n\ne0 的地方都要求 u=0u=0,没有正值解能满足它。本页对内圈采用离散闭合:每个壁面半面上令数值通量 h=0h=0。它让边循环最简单,但不是这个方程在物面上的物理条件;第 4 节说明它在迎风一侧多给了一个条件、没有连续极限。各节末尾“新增(选读)”的模型把内圈也按特征方向处理。

The body (the inner ring) is different. 𝜷\boldsymbol\beta is a constant vector and the body a closed curve, so 𝜷⋅𝒏\boldsymbol\beta\cdot\boldsymbol n takes both signs around it (here 𝒏\boldsymbol n points out of the domain, that is, into the body): on the windward side 𝜷⋅𝒏>0\boldsymbol\beta\cdot\boldsymbol n>0 and the characteristics arriving from the far field leave the domain there; on the leeward side 𝜷⋅𝒏<0\boldsymbol\beta\cdot\boldsymbol n<0 and characteristics enter the domain from the wall. Of this page’s 16 wall half-faces, 10 are on the windward side and 6 on the leeward side. A physical no-penetration condition F(u)⋅𝒏=12u2 𝜷⋅𝒏=0F(u)\cdot\boldsymbol n=\tfrac12u^2\,\boldsymbol\beta\cdot\boldsymbol n=0 would require u=0u=0 wherever 𝜷⋅𝒏≠0\boldsymbol\beta\cdot\boldsymbol n\ne0, which no positive solution satisfies. This page closes the inner ring with a discrete closure instead: the numerical flux is set to h=0h=0 on every wall half-face. That keeps the edge loop as simple as it can be, but it is not the physical boundary condition of this equation at the body; Section 4 explains that it over-determines the windward side and has no continuum limit. The optional “Added” model at the end of the sections treats the inner ring by characteristics as well.

图 1.2 把这两件事画出来。(a) 是基准解上的特征速度 u∗𝜷u^*\boldsymbol\beta:箭头全都平行,长短随 u∗u^* 变,这就是非线性。(b) 是每个边界半面上的 u∗𝜷⋅𝒏/∣𝒏∣u^*\boldsymbol\beta\cdot\boldsymbol n/|\boldsymbol n|:负的是入流,正的是出流;远场的点紧贴着光滑远场圆的曲线,物面的点在迎风与背风之间变号。

Figure 1.2 draws both. (a) is the characteristic velocity u∗𝜷u^*\boldsymbol\beta of the baseline solution: the arrows are all parallel and their lengths change with u∗u^*, which is the nonlinearity. (b) is u∗𝜷⋅𝒏/∣𝒏∣u^*\boldsymbol\beta\cdot\boldsymbol n/|\boldsymbol n| on every boundary half-face: negative for inflow, positive for outflow; the far-field points hug the curve of the smooth far-field circle, and the wall points change sign between the windward and the leeward side.

(a) 基准解上的特征速度 u*β(a) The characteristic velocity u*β at the baseline迎风壁面:β·n > 0,特征线离开区域windward wall: β·n > 0, characteristics leave背风壁面:β·n < 0,特征线从物面进入leeward wall: β·n < 0, characteristics enter−1.5−1.0−0.50.00.51.01.50°90°180°270°360°面中点的极角polar angle of the face midpoint(b) 穿过边界的速度 u β·n/|n|(b) The speed across the boundary, u β·n/|n|> 0:流出,不给条件> 0: out, no condition< 0:流入,要给 u< 0: in, u must be given远场半面:入流 8 个、出流 8 个far-field half-faces: 8 in, 8 out壁面半面:迎风 10 个(实心)、背风 6 个(空心)wall half-faces: 10 windward (filled), 6 leeward (open)远场圆,u = u∞far-field circle, u = u∞物面曲线,u = u∞body curve, u = u∞点:本页的半面,u 取相邻节点的 u*points: this page’s half-faces, with u the adjacent node’s u*
图 1.2 · 特征速度。(a) 基准解上的特征速度 u*β,画在每个节点(箭头的中点在该点):方向处处是 β,长度与 u* 成正比,最长的在节点 6(u*=1.536)。边界按 β·n 的符号着色(n 指出区域):远场入流蓝、出流红;物面迎风一侧(β·n>0,琥珀实线)特征线离开区域进入物体,背风一侧(β·n<0,琥珀虚线)特征线从物面进入区域。(b) 每个边界半面上信息穿过边界的速度 uβ·n/|n|,u 取相邻节点的 u*;横轴是半面中点的极角,远场绕原点、物面绕物体中心量。负值是入流,正值是出流。细线是同一个量在光滑远场圆和光滑物面曲线上、取 u=u∞ 的值。远场 16 个半面中 8 个入流、8 个出流;物面 16 个半面中 10 个迎风、6 个背风。Figure 1.2 · The characteristic speed. (a) The characteristic velocity u*β of the baseline solution, drawn at every node (each arrow centred on its point): its direction is β everywhere and its length is proportional to u*; the longest is at node 6 (u* = 1.536). The boundary is coloured by the sign of β·n (n pointing out of the domain): far-field inflow blue, outflow red; on the windward part of the wall (β·n > 0, amber solid) the characteristics leave the domain into the body, on the leeward part (β·n < 0, amber dashed) they enter the domain from the wall. (b) The speed at which information crosses the boundary, u β·n/|n|, on every boundary half-face, with u the adjacent node’s u*, against the polar angle of the half-face midpoint, measured about the origin for the far field and about the body centre for the wall. Negative means inflow, positive outflow. The thin curves are the same quantity on the smooth far-field circle and the smooth body curve, with u = u∞. Of the 16 far-field half-faces, 8 are inflow and 8 outflow; of the 16 wall half-faces, 10 are windward and 6 leeward.

本页的问题

The problem on this page

  • 区域与几何:物体与远场圆之间的环形区域。物面是一条非对称的闭曲线,以 (0.10,−0.04)(0.10,{-0.04}) 为中心、半径 r=0.55 (1+0.28cos⁡θ+0.12sin⁡2θ)r=0.55\,(1+0.28\cos\theta+0.12\sin2\theta),有弯度,相对远场圆心有偏置;远场是以原点为圆心、半径 1.9 的圆。网格在两条曲线上各放 8 个节点,中间再放一圈,用直线边连成四边形和三角形(第 2 节)。
  • Domain and geometry: the annulus between the body and the far-field circle. The body is an asymmetric closed curve with centre (0.10,−0.04)(0.10,{-0.04}) and radius r=0.55 (1+0.28cos⁡θ+0.12sin⁡2θ)r=0.55\,(1+0.28\cos\theta+0.12\sin2\theta), cambered and off-centre relative to the far field; the far field is the circle of radius 1.9 about the origin. The mesh puts 8 nodes on each curve and a ring in between, joined by straight edges into quadrilaterals and triangles (Section 2).
  • 边界数据:远场入流给定 u=u∞=1u=u_\infty=1;远场出流不给条件;物面用离散闭合 h=0h=0(第 4 节)。
  • Boundary data: u=u∞=1u=u_\infty=1 at far-field inflow; nothing at far-field outflow; the discrete closure h=0h=0 on the wall (Section 4).
  • 设计变量:D=(u∞,X)D=(u_\infty,X),来流值 u∞u_\infty 和全部 24 个节点的坐标 XX(48 个数),共 49 个分量。按伴随优化文献的通常写法,设计变量记作 DD。移动节点就改变了物体的形状和网格,所以本页的梯度里有几何导数(第 12 节),这是一维 Burgers 页面没有的。
  • Design variables: D=(u∞,X)D=(u_\infty,X), the free-stream value u∞u_\infty and the coordinates XX of all 24 nodes (48 numbers), 49 components in all. As is usual in the adjoint-optimization literature, the design variables are written DD. Moving the nodes changes the shape of the body and the mesh, so the gradient of this page contains geometric derivatives (Section 12), which the 1-D Burgers page does not have.
  • 基准解:本页用 Jacobi 迭代(第 6 节)解出的收敛解 u∗u^*,24 个未知量(每个节点一个),取值从节点 1 的 0.786 到节点 6 的 1.536。均匀来流 u≡u∞u\equiv u_\infty 满足除壁面节点以外的每一行离散方程(第 2 节),所以 u∗u^* 离开 1 的部分全部来自物体。
  • Baseline solution: the converged solution u∗u^* that the Jacobi iteration of Section 6 computes, 24 unknowns (one per node), from 0.786 at node 1 to 1.536 at node 6. The uniform stream u≡u∞u\equiv u_\infty satisfies every row of the discrete equations except those of the wall nodes (Section 2), so every departure of u∗u^* from 1 comes from the body.
  • 目标:第 7 节取物面上的类阻力积分 L=∑f∈wall12uL(f)2 (𝒏f⋅𝒆)L=\sum_{f\in\mathrm{wall}}\tfrac12u_{L(f)}^2\,(\boldsymbol n_f\cdot\boldsymbol e),𝒆=𝜷/∣𝜷∣\boldsymbol e=\boldsymbol\beta/|\boldsymbol\beta|,也就是把 12u2\tfrac12u^2 投影到来流方向、沿物面求和;在基准解上 L=0.693L=0.693。
  • Objective: Section 7 takes the drag-like wall integral L=∑f∈wall12uL(f)2 (𝒏f⋅𝒆)L=\sum_{f\in\mathrm{wall}}\tfrac12u_{L(f)}^2\,(\boldsymbol n_f\cdot\boldsymbol e), 𝒆=𝜷/∣𝜷∣\boldsymbol e=\boldsymbol\beta/|\boldsymbol\beta|, that is, 12u2\tfrac12u^2 projected onto the free-stream direction and summed along the body; at the baseline solution L=0.693L=0.693.

这个方程的作用

What this equation is for

在流体力学里,它是二维非线性对流的一个简单模型。真实的无黏流动(Euler 方程)被流动带着走的是质量、动量和能量,输运速度就是流速本身,而且是一组方程;这里只有一个标量,输运方向固定为 𝜷\boldsymbol\beta,只保留“输运快慢取决于被输运的量”这一个非线性。沿 𝜷\boldsymbol\beta 看它就是 Burgers 方程:非定常时快的部分会追上慢的部分,波形变陡,可以形成激波;本页只求定常解,定常时特征线是互相平行的直线,不会相交。这类标量模型常用来在处理方程组之前,先检验数值通量、边界处理和伴随程序。

In fluid dynamics it is a simple model of nonlinear convection in two dimensions. In a real inviscid flow (the Euler equations) the flow carries mass, momentum and energy, the transport velocity is the flow velocity itself, and the equations form a system; here a single scalar is carried in the fixed direction 𝜷\boldsymbol\beta, and only one nonlinearity is kept: the speed of transport depends on the quantity transported. Along 𝜷\boldsymbol\beta it is the Burgers equation: in unsteady flow faster parts overtake slower ones, waves steepen and shocks can form; this page computes steady solutions only, whose characteristics are parallel straight lines that never cross. Scalar models of this kind are commonly used to test numerical fluxes, boundary treatments and adjoint codes before moving on to systems.

为什么用标量方程,而不是 Euler / N–S。本页要讲的是离散伴随的结构:边循环怎么写、怎么对它逐项求导、怎么把它转置。真实的 Euler 方程在每个对偶面上的通量是 4 维(二维)或 5 维(三维)的向量,它的局部导数是 4×4 或 5×5 的块,再加上 Roe 的特征分解、限制器、N–S 的粘性项与湍流模型,∂h/∂uL 就从一个数变成一个矩阵——这些细节会把“收集—计算—分发”这条主线淹没。换成标量守恒律之后,两个局部导数 ∂h/∂uL、∂h/∂uR 各自只是一个数,整条链路才能逐句播放、逐项和差分对照。但骨架完全一样:边循环的写法、边界条件只在一维通量内部起作用、前向与伴随的转置关系,在真实求解器里逐字成立——把标量换成状态向量、把局部导数换成块矩阵即可。

Why a scalar equation rather than Euler / N–S. What this page is about is the structure of the discrete adjoint: how the edge loop is written, how it is differentiated term by term, how it is transposed. In the real Euler equations the flux on each dual face is a 4-vector (two-dimensional) or 5-vector (three-dimensional) whose local derivatives are 4×4 or 5×5 blocks, and with Roe’s characteristic decomposition, limiters, the N–S viscous terms and a turbulence model on top, ∂h/∂uL turns from a number into a matrix — details that would drown the "gather — compute — scatter" thread. With a scalar conservation law the two local derivatives ∂h/∂uL and ∂h/∂uR are each just one number, so the whole chain can be played statement by statement and checked term by term against differences. But the skeleton is identical: how the edge loop is written, the fact that boundary conditions act inside the one-dimensional flux, and the transpose relation between forward and adjoint all hold verbatim in a real solver — replace the scalar by a state vector and the local derivatives by block matrices.

在本课程里,它是第一个二维页面,也是从一维 Burgers 走向二维非结构网格的一步。一维时面就是点,法向只有 ±1\pm1,网格等距;这里每条网格边对应一个对偶面(从边中点连到相邻单元中心的折线),法向 𝒏\boldsymbol n 由节点坐标算出,网格是四边形和三角形混合的非结构网格,边界是闭曲线,哪里入流、哪里出流由几何决定。法向依赖节点坐标,所以坐标本身成了设计变量,梯度里出现几何导数 ∂R/∂X\partial R/\partial X 与 ∂L/∂X\partial L/\partial X(第 12 节)。方程仍是标量,面上的局部导数仍是一个数,每一步都可以逐句播放、逐项核对。

In this course it is the first two-dimensional page, the step from 1-D Burgers to unstructured meshes in two dimensions. In one dimension a face is a point, its normal is ±1\pm1 and the mesh is uniform; here each mesh edge carries a dual face (the polyline from the edge midpoint to the centres of the neighbouring cells) with a normal 𝒏\boldsymbol n computed from the node coordinates, the mesh is an unstructured mix of quadrilaterals and triangles, and the boundaries are closed curves whose inflow and outflow parts are decided by the geometry. The normals depend on the node coordinates, so the coordinates themselves become design variables and the gradient gains the geometric derivatives ∂R/∂X\partial R/\partial X and ∂L/∂X\partial L/\partial X (Section 12). The equation is still scalar, the local derivatives on a face are still single numbers, and every step can be played statement by statement and checked term by term.

图 1.3 把本页放在整个网站的路线上,每个方框都链接到那一页的第 1 节。上排是标量方程:从一维 Burgers 到本页,多了一个空间方向;再到二维对流扩散,加上扩散项,物面就可以、也必须给一个 Dirichlet 条件。下排是方程组:拟一维 Euler、二维 Euler、二维层流 N–S、二维 RANS–SA。同一列的两页维数相同、物理相近:本页对应二维 Euler,对流扩散对应层流 N–S。每点的未知量从标量的 1 个增加到方程组的 3–5 个,但每一页的残差都由面循环组装,前向与伴随沿同一个循环求导。另有一维层流 N–S 页,在一维 Euler 方程上加入黏性和热传导。

Figure 1.3 places this page on the route through the site; every box links to Section 1 of its page. The upper row holds the scalar equations: from 1-D Burgers to this page one space direction is added; on to 2-D advection–diffusion a diffusion term is added, which lets, and requires, the body take a Dirichlet condition. The lower row holds the systems: quasi-1-D Euler, 2-D Euler, 2-D laminar N–S and 2-D RANS–SA. The two pages in one column share the dimension and the kind of physics: this page matches 2-D Euler, advection–diffusion matches laminar N–S. The unknowns per point grow from 1 for a scalar to 3–5 for a system, yet every page assembles its residual with a face loop, and the tangent and the adjoint differentiate that loop. The 1-D laminar N–S page, besides, adds viscosity and heat conduction to the one-dimensional Euler equations.

一维 Burgers每点 1 个未知量非线性对流 u ux设计在源项里1-D Burgers1 unknown per pointnonlinearconvection u uxdesign in the source二维标量对流每点 1 个未知量第二个空间方向面法向、网格坐标2-D scalar advection1 unknown per pointa second direction,face normals,mesh coordinates本页this page二维对流扩散每点 1 个未知量扩散:面上要用梯度Dirichlet 壁面2-D advection–diffusion1 unknown per pointdiffusion: facegradients, aDirichlet wall拟一维 Euler每点 3 个未知量质量、动量、能量压力、变截面积Quasi-1-D Euler3 unknowns per pointmass, momentum,energy; pressure,varying area二维 Euler每点 4 个未知量第二个空间方向面法向、网格坐标2-D Euler4 unknowns per pointa second direction,face normals,mesh coordinates二维层流 N–S每点 4 个未知量黏性应力、热传导面上要用梯度2-D laminar N–S4 unknowns per pointviscous stress,heat conduction,face gradients二维 RANS–SA每点 5 个未知量湍流模型:多一个输运方程2-D RANS–SA5 unknowns per pointturbulence model:one more transportequation上排:标量方程下排:方程组同一列:维数相同、物理相近每个方框链接到该页的第 1 节upper row: one scalarlower row: systemsone column: the samedimension and physicseach box links toSection 1 of its page每一页都用面循环组装残差:收集两侧状态 → 重构 → 算通量 → 分发到两侧Every page assembles its residual with a face loop: gather → reconstruct → flux → scatter.前向与伴随沿同一个循环求导;黏性页面在通量循环之前先算一遍梯度The tangent and the adjoint differentiate the same loop; the viscous pages first compute gradients.
图 1.3 · 从本页到网站上的其他方程。上排是标量方程,下排是方程组;同一列的两页维数相同、物理相近(一维;二维无黏;二维加扩散或黏性)。主线从一维 Burgers 往下到拟一维 Euler,再沿下排向右。每个方框都是链接,打开那一页的第 1 节。每点的未知量取自各页的默认设置:标量方程 1 个,方程组 3 到 5 个;每一页都用面循环组装残差,前向与伴随沿同一个循环求导。Figure 1.3 · From this page to the other equations of the site. The upper row holds the scalar equations, the lower row the systems; the two pages in one column share the dimension and the kind of physics (one dimension; two dimensions without viscosity; two dimensions with diffusion or viscosity). The main line runs from 1-D Burgers down to quasi-1-D Euler and on along the lower row. Every box is a link to Section 1 of its page. The unknowns per point come from each page’s default settings: 1 for a scalar equation, 3 to 5 for a system; every page assembles its residual with a face loop, and the tangent and the adjoint differentiate that loop.

本页要回答的问题

The question this page answers

本页只回答一个问题:设计变量 DD 改变一点,目标 LL 改变多少,也就是梯度 dL/dDdL/dD 的全部 49 个分量。页首的全页路线图把后面各节串成一条线:前向(切线)每个设计分量要解一次线性方程,49 个分量就要解 49 次;伴随只解一次转置方程 ATψ=−gA^T\psi={-g},再做一次乘积就得到全部分量(第 11、12 节)。

This page answers one question: how much does the objective LL change when the design variables DD change a little, that is, what are all 49 components of the gradient dL/dDdL/dD? The page roadmap at the top strings the later sections together: the tangent (forward) route solves one linear system per design component, 49 solves for 49 components; the adjoint route solves one transposed system ATψ=−gA^T\psi={-g} and gets every component with one more product (Sections 11 and 12).

要在计算机上回答这个问题,先得把方程写到网格上,因为计算机只能存有限个数。第 2 节先搭网格:节点、围绕节点的对偶控制体、对偶面和它们的法向;第 3 节再把方程在每个控制体上积分,化成每个对偶面上的一个数,也就是数值通量。

To answer it on a computer the equation must first be written on a mesh, since a computer stores only finitely many numbers. Section 2 builds the mesh (the nodes, the dual control volumes around them, the dual faces and their normals); Section 3 then integrates the equation over each control volume and reduces it to one number per dual face: the numerical flux.

第 2 节Section 2

网格:混合网格与对偶控制体的构造

Mesh: a hybrid grid and the construction of dual control volumes

第 1 节的方程对区域里的每一点都成立,计算机却只能存有限个数。本节先搭网格,再围绕每个节点造出对偶控制体:每个节点存一个未知量,相邻两个控制体的公共边界就是对偶面;最后看对偶控制体的外法向为什么首尾相接后闭合。

The equation of Section 1 holds at every point of the domain, but a computer stores only finitely many numbers. This section builds the mesh and then carves a dual control volume around every node: each node stores one unknown, and the common boundary of two neighbouring control volumes is a dual face. Last, it shows why the outward normals of a dual control volume close when placed head to tail.

算例网格

The test mesh

算例是一张绕物体的环形 O 型网格,3 圈节点、每圈 8 个,共 24 个节点。贴着物面的一层用四边形(8 个),外面一层用三角形(16 个)——这正是实际 CFD 常用的混合网格:近壁面用规则的四边形层,外场用三角形。物体本身是非对称的:它有弯度,而且相对远场圆心有偏置,所以离散解、类似阻力的目标函数(第 7 节)和形状梯度都不带任何对称性。两圈边界的意义完全不同:

The test case is an annular O-type mesh around a body: 3 rings of nodes, 8 to a ring, 24 nodes in all. The layer against the body uses quadrilaterals (8 of them) and the layer outside it triangles (16) — precisely the hybrid mesh that real CFD uses: regular quadrilateral layers near the wall, triangles in the far field. The body itself is asymmetric: it has camber and is offset from the centre of the far-field circle, so the discrete solution, the drag-like objective (Section 7) and the shape gradient carry no symmetry whatever. The two rings of boundary mean quite different things:

  • 内圈(8 条边):无粘壁面,也就是物面,用离散闭合 h=0 处理(第 4 节);
  • Inner ring (8 edges): inviscid wall — the body surface, closed by the discrete closure h=0 (Section 4);
  • 外圈(8 条边):远场特征边界。
  • Outer ring (8 edges): far-field characteristic boundary.

有限体积法需要一组不重叠、铺满全域的控制体。把未知量放在单元里,控制体就是单元本身(格心格式);也可以把未知量放在节点上,这时控制体必须另外造出来,做法是围绕每个节点“切”出一块,这就是中位对偶控制体(图 2.1)。本页采用后者,所以 N=24。

A finite-volume method needs a set of control volumes that do not overlap and cover the whole domain. Put the unknowns in the cells and the control volume is the cell itself (the cell-centred scheme); put them on the nodes instead and the control volumes have to be constructed, by "cutting" a piece out around every node — the median-dual control volume (Figure 2.1). This page takes the latter, so N=24.

(a) 单元中心:控制体 = 单元本身24 个单元 = 24 个未知量(蓝底=四边形,浅色=三角形)未知量画在单元中心,边就是面(b) 节点中心:控制体 = 中位对偶体12345678910111213141516171819202122232424 个节点 = 24 个未知量,实线围出对偶控制体深色:内部节点 n9 与壁面节点 n1 的控制体两张图是同一张混合网格(内层四边形 + 外层三角形),只是未知量放的位置不同。内圈是无粘壁面,外圈是远场特征边界。(a) Cell-centred: the control volume is the cell itself24 cells = 24 unknowns (blue fill = quadrilateral, pale = triangle)the unknowns are drawn at the cell centres, and an edge is a face(b) Node-centred: the control volume is the median dual12345678910111213141516171819202122232424 nodes = 24 unknowns, with the solid lines enclosing the dual control volumedark: interior node n9 and wall node n1 and their control volumesOne hybrid mesh in both (quadrilaterals inside, triangles outside); only where the unknowns sit differs.Inner ring: inviscid wall; outer ring: characteristic far field.
图 2.1 · 同一张混合 O 型网格上的两种选择。本页采用 (b):未知量在节点上,控制体是围绕节点的中位对偶体;相邻两个对偶体之间的公共边界称为对偶面,它和网格的一条边一一对应。Figure 2.1 · Two choices on the same hybrid O-type mesh. This page uses (b): the unknowns sit on the nodes and the control volume is the median dual around each node; the common boundary between two neighbouring duals is called a dual face, and it corresponds one-to-one with an edge of the mesh.

一步一步围出控制体

Building a control volume step by step

构造规则只有一条,而且对三角形和四边形完全一样:在每个单元内,把每条边的中点与单元中心相连。这些线段把单元分成若干块,分别归属它的各个顶点;把所有相邻单元里属于同一个节点的小块拼起来,就是这个节点的控制体。图 2.2 可以逐步播放这个过程,也可以点击任意节点换一个看(试试壁面节点、远场节点和内部节点的区别)。

There is only one construction rule, and it is exactly the same for triangles and quadrilaterals: inside each cell, join the midpoint of every edge to the cell centre. Those segments cut the cell into pieces, each belonging to one of its vertices; collect the pieces belonging to the same node across all its neighbouring cells and you have that node’s control volume. Figure 2.2 plays the process step by step, and any node can be clicked to see another one (try the difference between a wall node, a far-field node and an interior node).

图 2.2Figure 2.2
演示:中位对偶控制体的构造
Demo: constructing the median-dual control volume
按 ▶ 逐步播放 0–6 步;点击网格中任意节点换一个重看
Press ▶ to play steps 0–6; click any node in the mesh to watch another one
选中节点与它的相邻单元the selected node and its neighbouring cells 边中点 medge midpoint m 单元中心 xccell centre xc 中点—中心段(控制体边界)midpoint-to-centre segment (the control-volume boundary)

对偶面与它的法向

The dual face and its normal

网格的每一条边 (i,j) 对应一个对偶面:它由相邻单元各贡献的一段拼成——内部边有两个相邻单元,所以对偶面是折线 xc1→m→xc2;边界边只有一个单元,只贡献一段。面的法向由各段旋转 90° 后相加得到(图 2.3):

Every edge (i,j) of the mesh corresponds to one dual face, assembled from one segment contributed by each adjacent cell — an interior edge has two adjacent cells, so the dual face is the polyline xc1→m→xc2; a boundary edge has only one cell and contributes a single segment. The face normal is obtained by rotating each segment through 90° and adding them (Figure 2.3):

nij=Σt∋(i,j)±(dy,−dx),d=xc−m,m=12(xi+xj),

符号取成让 nij 由节点 i 指向节点 j。这样一条边只需要存一个法向向量。|nij| 是这条折线的等效直线长度——等于折线两端之间的距离(内部边就是两个单元中心;边界边的对偶面只有一段,两端是边中点与那一个单元中心),而不是折线自身的长度(算例中折线最多长 13.4%)。通量只用到矢量 nij,所以边循环和格心格式的面循环形式完全一样。

The sign is chosen so that nij points from node i to node j. An edge then needs to store just one normal vector. |nij| is the equivalent straight-face length of that polyline — the distance between its two ends (for an interior edge, the two cell centres; the dual face of a boundary edge has a single segment, running from the edge midpoint to its one cell centre), not the polyline's own length, which runs up to 13.4% longer in this mesh. The flux only ever uses the vector nij, which is why the edge loop has exactly the same form as the face loop of a cell-centred scheme.

四边形的一个重要差别。中点—中心连线把三角形恰好分成面积相等的三块,所以三角形网格上 Vi=13Σ(相邻三角形面积)。但对一般四边形,四块面积并不相等(只有平行四边形才是四分之一)。所以控制体体积必须把各个子块的面积逐个加起来,不能套用固定分数——图 2.3(b) 给出了算例中真实四边形的四块占比。

One important difference for quadrilaterals. The midpoint-to-centre lines cut a triangle into three pieces of exactly equal area, so on a triangular mesh Vi=13Σ(area of the adjacent triangles). For a general quadrilateral, though, the four pieces are not equal (only a parallelogram gives quarters). So the control-volume area has to be accumulated piece by piece and cannot be taken as a fixed fraction — Figure 2.3(b) gives the four shares for a real quadrilateral from the test case.

Vi 在哪里用到。要说清楚:第 5–14 节的定常问题里,R、A、diag(A)、L 与 dR/dX 都用不到 Vi。残差只是对偶面通量之和,几何只通过对偶面的法向进入(第 12 节);Vi 乘的是时间导数 Vidui/dt(第 3 节)。本页只有第 15 节用到它:构造解的源项按 s(xi)Vi 进入每个控制体,误差范数也按 Vi 加权。一般来说,非定常问题、伪时间推进的局部时间步和体积分源项都要用到 Vi;上面逐块累加的做法就是为这些场合准备的,在本页只服务于第 15 节。

Where Vi is used. To be clear: in the steady problem of Sections 5–14 none of R, A, diag(A), L or dR/dX uses Vi. The residual is a sum of dual-face fluxes, and the geometry enters only through the dual-face normals (Section 12); Vi multiplies the time derivative, Vidui/dt (Section 3). On this page only Section 15 uses it: the manufactured source enters each control volume as s(xi)Vi, and the error norm is weighted by Vi. In general, unsteady problems, the local time step of pseudo-time marching and volume source terms all need Vi; the piece-by-piece accumulation above is there for those, and on this page it serves Section 15 alone.

(a) 真实网格中一条内部边 (n9, n10) 的对偶面xc1d1xc2d2nm910对偶面 = 折线 xc1 → m → xc2,n = rot(d1) − rot(d2),rot(d) = (dy, −dx)(符号使 n 由 i 指向 j)。这里 n = (-0.41, 0.28),|n| = 0.5。两侧单元一个是四边形、一个是三角形,规则完全相同。(b) 真实四边形单元:四块面积并不相等xc1910221.8%28.6%28.2%21.4%四边形被中点—中心连线分成四块,占比 21.8% / 28.6% / 28.2% / 21.4%:一般不是 25%。三角形才恰好是 ⅓。所以 Vi 必须把子块面积逐个加起来,不能用固定的分数。(a) The dual face of an interior edge (n9, n10) in the real meshxc1d1xc2d2nm910dual face = the polyline xc1 → m → xc2, n = rot(d1) − rot(d2), rot(d) = (dy, −dx) (signs make n run from i to j).here n = (-0.41, 0.28), |n| = 0.5.One adjacent cell is a quadrilateral and the other a triangle; the rule is identical.(b) A real quadrilateral cell: the four areas are not equalxc1910221.8%28.6%28.2%21.4%midpoint-to-centre lines cut the quadrilateral into 21.8% / 28.6% / 28.2% / 21.4%: in general not 25%.Only a triangle gives exactly ⅓, so Vi must sum the sub-piece areas, not use a fixed fraction.
图 2.3 · (a)(上):混合网格中一条内部边的对偶面由两段拼成,法向是两段旋转后的矢量和;两侧的单元一个是四边形、一个是三角形,规则完全相同。(b)(下):一个真实四边形被中点—中心连线分成四块,面积并不相等,这就是 Vi 必须逐块累加的原因。Figure 2.3 · (a), top: in a hybrid mesh the dual face of an interior edge is assembled from two segments, and the normal is the vector sum of the two rotated segments; the cells on either side are a quadrilateral and a triangle, and the rule is identical. (b), bottom: a real quadrilateral cut into four pieces by the midpoint-to-centre lines — the areas are not equal, which is why Vi has to be accumulated piece by piece.

边界节点:用半条边把控制体封口

Boundary nodes: sealing the control volume with half-edges

内部节点的控制体被相邻单元内的线段完整围住,边界节点却不是:它的控制体在区域边界上是“敞开”的,必须用节点到相邻边界边中点的两条半边补上(图 2.4)。这两条半边就是边界半面,外法向取该边界边外法向的一半:

The control volume of an interior node is enclosed completely by the segments inside its neighbouring cells; a boundary node’s is not — it is left "open" along the domain boundary and has to be closed with the two half-edges running from the node to the midpoints of its adjacent boundary edges (Figure 2.4). Those two half-edges are the boundary half-faces, and their outward normal is half the outward normal of the boundary edge:

nbnd=12(yb−ya,−(xb−xa)),

边界边 (a,b) 的两个端点各得一份。算例中内外圈各有 8 条边界边,共 16 条,所以有 32 个边界半面(16 个壁面 + 16 个远场);加上 48 条边对应的 48 个内部对偶面,边循环一共要走 80 个面。

Each of the two endpoints of a boundary edge (a,b) receives one share. The test case has 8 boundary edges on the inner ring and 8 on the outer, 16 in all, giving 32 boundary half-faces (16 wall + 16 far-field); together with the 48 interior dual faces of the 48 edges, the edge loop walks 80 faces in total.

(a) 壁面节点 n1 的控制体(内圈 = 无粘壁面)n(e49)n(e50)2891红色:3 个内部对偶面琥珀:2 条壁面半面,把控制体封口V1 = 0.1423无粘:半面上 h = 0,封住控制体而不贡献通量。(a) Wall node n1 and its control volume (inner ring: inviscid wall)n(e49)n(e50)2891red: 3 interior dual facesamber: 2 wall half-faces, sealing the control volumeV1 = 0.1423Inviscid: h = 0 closes the volume with no flux.
图 2.4 · 混合网格中壁面节点的中位对偶控制体:内部由中点—中心段围住,靠近物面的一侧用两条半边封口。每条边界半面的外法向是所在边界边外法向的一半,所以把同一条边界边的两个半面加起来,正好还原整条边的外法向(图中标出的两条半面分属相邻的两条边界边)。Figure 2.4 · The median-dual control volume of a wall node in a hybrid mesh: enclosed inside by midpoint-to-centre segments, and sealed on the side facing the body by two half-edges. The outward normal of each boundary half-face is half that of the boundary edge it lies on, so adding the two half-faces of the same boundary edge recovers that edge’s outward normal (the two half-faces labelled here belong to two neighbouring boundary edges).

图 2.5 可以点击任意节点或对偶面,查看它的两侧节点、法向与类型。

Figure 2.5 lets you click any node or dual face and inspect the nodes on either side of it, its normal and its type.

图 2.5Figure 2.5
交互:点击节点或对偶面
Interactive: click a node or a dual face
点击任意节点:只画出它自己那些对偶面的法向 n;再点击(或悬停)某个对偶面,查看它两侧的节点与数值
Click any node to draw only the normals n of its own dual faces; then click (or hover) a dual face to see the nodes on either side and the numbers
选中的控制体the selected control volume 它的邻居its neighbours 选中的对偶面及其法向the selected dual face and its normal 该节点其余内部面的法向normals of the node’s other interior faces 边界半面法向(指向域外):壁面boundary half-face normals (out of the domain): wall入流inflow出流outflow

闭合性。每个控制体是一个封闭多边形,所以它全部外法向之和为零。选中任一节点,把它“作为 i”的面取 +n、“作为 j”的面取 −n(边界半面取 +n)相加,结果都是 (0,0)——上面图 2.2 的信息栏里可以直接读到(“Σ 外法向”一行)。这一点对四边形和三角形同样成立。

Closure. Every control volume is a closed polygon, so all its outward normals sum to zero. Select any node, add +n for the faces where it acts as i and −n for those where it acts as j (boundary half-faces take +n), and the result is always (0,0) — the construction demo above (Figure 2.2) reads it out directly, on its "Σ outward normals" row. This holds for quadrilaterals and triangles alike.

逐步推导:外法向为什么闭合,闭合换来了什么

Step by step: why the outward normals close, and what that buys

上面“闭合”一段说,每个控制体所有面的外法向加起来为零。下面把它推一遍,并看它换来的结果:均匀来流满足除壁面节点以外的每一行残差(图 2.6)。

The “Closure” paragraph above says that the outward normals of every control volume sum to zero. Here is the derivation, and what it buys: a uniform stream satisfies every residual row except those of the wall nodes (Figure 2.6).

第一步,本页的法向都是某个线段转 90° 得到的。记

Step 1. Every normal on this page is some segment turned by 90°. With

Q=(01−10),𝒏ij=∑t∋(i,j)st Q(𝒙c,t−𝒎),𝒏bnd=12Q(𝒙b−𝒙a),\begin{gathered}Q=\begin{pmatrix}0&1\\-1&0\end{pmatrix},\\ \boldsymbol n_{ij}=\sum_{t\ni(i,j)}s_t\,Q(\boldsymbol x_{c,t}-\boldsymbol m),\qquad \boldsymbol n_{\mathrm{bnd}}=\tfrac12Q(\boldsymbol x_b-\boldsymbol x_a),\end{gathered}

对偶面 (i,j)(i,j) 的法向是它的一两段折线(边中点 𝒎\boldsymbol m 到相邻单元中心 𝒙c,t\boldsymbol x_{c,t})各自转 90° 之和,st=±1s_t={\pm1} 让它从 ii 指向 jj;边界半面的法向是半条边界边转 90°。

the dual-face normal of edge (i,j)(i,j) is the sum of its one or two segments (from the edge midpoint 𝒎\boldsymbol m to the neighbouring cell centres 𝒙c,t\boldsymbol x_{c,t}), each turned by 90°, with st=±1s_t={\pm1} making it point from ii to jj; a boundary half-face normal is half a boundary edge turned by 90°.

第二步,节点 ii 的对偶控制体的边界是一条闭合折线:它由这些折线段和(对边界节点)两段半边组成。逆时针走一圈时,σif𝒏f\sigma_{if}\boldsymbol n_f(σif=+1\sigma_{if}={+1} 若 i=L(f)i=L(f),−1-1 若 i=R(f)i=R(f))正好是 QQ 作用在走过的那几段上:“从 ii 指向 jj”就是“指向对偶控制体外面”。

Step 2. The boundary of the dual cell of node ii is a closed polyline made of these segments and, for a boundary node, the two half-edges. Walking it counter-clockwise, σif𝒏f\sigma_{if}\boldsymbol n_f (σif=+1\sigma_{if}={+1} if i=L(f)i=L(f), −1-1 if i=R(f)i=R(f)) is exactly QQ applied to the segments walked: “from ii to jj” means “out of the dual cell”.

第三步,一条闭合折线的线段向量相加,中间的点两两抵消:

Step 3. The segment vectors of a closed polyline telescope:

∑f∈∂Viσif𝒏f=Q∑k(𝝃k+1−𝝃k)=𝟎,\sum_{f\in\partial V_i}\sigma_{if}\boldsymbol n_f=Q\sum_{k}(\boldsymbol\xi_{k+1}-\boldsymbol\xi_k)=\boldsymbol 0,

𝝃k\boldsymbol\xi_k 是对偶多边形的顶点(节点本身、边中点、单元中心),最后一个点回到第一个点。

where 𝝃k\boldsymbol\xi_k are the vertices of the dual polygon (the node itself, edge midpoints, cell centres) and the last one returns to the first.

−n3+n18+n19+n20+n17(a) 中间环节点 9 的对偶控制体(a) dual cell of middle-ring node 9外法向(半长)outward normals (half length)同一组向量首尾相接the same vectors, head to tail|Σ σn| = 0+n50+n2+n3+n1+n49(b) 壁面节点 1 的对偶控制体(b) dual cell of wall node 1外法向(半长)outward normals (half length)同一组向量首尾相接the same vectors, head to tail|Σ σn| = 5.6×10⁻¹⁷
图 2.6 · 对偶控制体的外法向首尾相接后闭合。左半边画出两个对偶控制体各自的外法向 σn(σ=+1 表示该节点是面的 L,−1 表示是 R;为了看得清,箭头画成半长),紫色是内部对偶面,琥珀色是壁面半面;右半边把同一组向量按逆时针顺序首尾相接(放大画出),箭头链回到起点(圆点)。每个对偶面的法向是它的折线段各自转 90° 之和,所以这条链就是整个对偶多边形转了 90°。下方是本页代码算出的和的模;全部 24 个控制体中最大的是 2.5×10⁻¹⁶。Figure 2.6 · The outward normals of a dual cell close when placed head to tail. On the left, the outward normals σn of two dual cells (σ = +1 where the node is the face’s L, −1 where it is R; drawn at half length for legibility): violet for interior dual faces, amber for wall half-faces. On the right, the same vectors in counter-clockwise order, head to tail and enlarged: the chain returns to its start (the dot). Each dual-face normal is the sum of its segments turned by 90°, so the chain is the whole dual polygon turned by 90°. Below: the modulus of the sum as this page’s code computes it; the largest over all 24 control volumes is 2.5×10⁻¹⁶.

第四步,看它换来了什么。取均匀状态 u≡u∞u\equiv u_\infty。凡是跑通量的对偶面两侧都是 u∞u_\infty:内部对偶面两侧相同,入流半面 uR=u∞=uLu_R=u_\infty=u_L,出流半面 uR=uLu_R=u_L。于是耗散项 −ε∣𝒏∣(uR−uL)-\varepsilon|\boldsymbol n|(u_R-u_L) 为零,通量只剩中心项,对没有壁面的节点用第三步:

Step 4, what it buys. Take the uniform state u≡u∞u\equiv u_\infty. Every dual face that runs the flux sees u∞u_\infty on both sides: interior dual faces trivially, inflow half-faces because uR=u∞=uLu_R=u_\infty=u_L, outflow half-faces because uR=uLu_R=u_L. The dissipation −ε∣𝒏∣(uR−uL)-\varepsilon|\boldsymbol n|(u_R-u_L) vanishes and only the central term is left; for a node with no wall half-face, Step 3 gives

hf=14vn,f(u∞2+u∞2)=12u∞2 𝜷⋅𝒏f,Ri(u∞)=∑fσifhf=12u∞2 𝜷⋅∑fσif𝒏f=0.\begin{gathered}h_f=\tfrac14v_{n,f}(u_\infty^2+u_\infty^2)=\tfrac12u_\infty^2\,\boldsymbol\beta\cdot\boldsymbol n_f,\\ R_i(u_\infty)=\sum_{f}\sigma_{if}h_f=\tfrac12u_\infty^2\,\boldsymbol\beta\cdot\sum_{f}\sigma_{if}\boldsymbol n_f=0.\end{gathered}

第五步,壁面节点少了壁面那两项:壁面上 h=0h=0,而不是 12u∞2𝜷⋅𝒏f\tfrac12u_\infty^2\boldsymbol\beta\cdot\boldsymbol n_f(ff 是壁面半面)。把缺的项从第三步的零里减掉:

Step 5. A wall node misses the wall terms: on the wall h=0h=0 instead of 12u∞2𝜷⋅𝒏f\tfrac12u_\infty^2\boldsymbol\beta\cdot\boldsymbol n_f (ff the wall half-face). Subtracting the missing terms from the zero of Step 3:

Ri(u∞)=−12u∞2 (vn,f+vn,f′).R_i(u_\infty)={-\tfrac12u_\infty^2\,(v_{n,f}+v_{n,f'})}.

这里 ff、f′f' 是节点 ii 的两个壁面半面。

Here ff and f′f' are the two wall half-faces of node ii.

所以均匀来流只在 8 个壁面节点上不满足离散方程,u∗u^* 离开 u∞u_\infty 全是由这几行引起的。这也是第 6 节的 Jacobi 迭代从 u0≡1u^0\equiv1 出发时看到的第一个残差:它只在壁面节点上非零。

So the uniform stream fails the discrete equations only in the 8 wall nodes, and every departure of u∗u^* from u∞u_\infty is caused by these rows. It is also the first residual the Jacobi iteration of Section 6 meets when it starts from u0≡1u^0\equiv1: non-zero only at the wall nodes.

核对:全部 24 个对偶控制体中 ∣∑σ𝒏∣|\sum\sigma\boldsymbol n| 最大为 2.5×10−16;在 u≡u∞u\equiv u_\infty 处,16 个非壁面节点的 ∣R∣|R| 最大为 1.7×10−16,8 个壁面节点与第五步公式之差最大为 5.6×10−17(本页代码在构建时计算)。

Check: the largest ∣∑σ𝒏∣|\sum\sigma\boldsymbol n| over all 24 dual cells is 2.5×10−16; at u≡u∞u\equiv u_\infty the largest ∣R∣|R| in the 16 nodes away from the wall is 1.7×10−16, and the 8 wall nodes differ from the formula of Step 5 by at most 5.6×10−17 (computed by this page’s code at build time).

网格、对偶控制体和对偶面已经齐了。第 3 节把第 1 节的方程在每个对偶控制体上积分:积分之后只剩穿过各个对偶面的通量,空间离散于是归结为一个问题——一个对偶面上的通量怎么算。

The mesh, the dual control volumes and the dual faces are in place. Section 3 integrates the equation of Section 1 over each dual control volume: after integration only the fluxes through the dual faces remain, so the spatial discretisation comes down to one question, how to compute the flux on one dual face.

新增(选读):光滑特征边界模型
Added (optional): smooth characteristic-boundary model

网格:保留原来的混合 O 型网格

Mesh: retain the original hybrid O-grid

仍使用原来的 24 个节点、8 个四边形和 16 个三角形,内外边界的坐标与连接关系不变。改变的是边界的数学含义和空间重构,不是重建一个新的几何算例。新增面板里的数组从 0 开始编号:未知量 i 就是原网格图中的第 i+1 个节点。计算面按逐个单元走边时第一次遇到的顺序编号,与主线的面号不同(主线把内部面、壁面面和远场面分组排序),所以新增表格在面号后的括号里同时给出主线面号 f#。

The original 24 nodes, eight quadrilaterals and sixteen triangles remain unchanged, including boundary coordinates and connectivity. We change the boundary interpretation and spatial reconstruction, not the geometry. Arrays in the added panels are numbered from 0: unknown i is node i+1 of the original mesh diagrams. Computational faces are numbered in the order the cells first meet their edges, which differs from the main line (it sorts interior, wall and far-field faces into groups), so the added tables give the main-line label f# in brackets after each face number.

未知量放在原始节点。每个对偶控制体由节点、边中点和原始单元中心连接而成;内部对偶面可能由两段折线组成。一阶沿用合成法向的旧通量;二阶在各段上分别重构和积分,不能把不同位置的面值用一个中点代替。

Unknowns live at primal nodes. Dual volumes join nodes, edge midpoints and primal-cell centres. An interior dual face may have two segments. First order retains the original summed-normal flux; second order reconstructs and integrates each segment separately. Distinct trace locations cannot be represented by a single midpoint.

𝒏f=(yb−ya,−(xb−xa)),ℓf=∥𝒏f∥,∑f∈∂Vi𝒏if=0.\boldsymbol n_f=(y_b-y_a,-(x_b-x_a)),\quad\ell_f=\|\boldsymbol n_f\|,\quad\sum_{f\in\partial V_i}\boldsymbol n_{if}=0.

图中保留原始网格,格点页叠加对偶线段。选择一个未知量和显示变量(状态 u、误差 u − u_ref 或伴随 ψ);表格列出该未知量的位置、体积 V、状态、参考值与最小二乘梯度。计算面及其积分点在图 3.3 中查看。

The diagram retains the primal grid and overlays dual segments on the node page. Select an unknown and a field (the state u, its error u − u_ref or the adjoint ψ); the table lists that unknown’s position, volume V, state, reference value and least-squares gradient. Faces and their quadrature points are shown in Figure 3.3.

图 2.7Figure 2.7
第 3 节Section 3

对偶面上的通量:有限体积形式与一维通量

The flux on a dual face: the finite-volume form and the one-dimensional flux

第 2 节搭好了对偶控制体和对偶面。本节把第 1 节的方程在每个对偶控制体上积分,把体积分换成穿过各个对偶面的通量之和,再给出一个对偶面上的数值通量:它只看面两侧的两个状态。

Section 2 built the dual control volumes and the dual faces. This section integrates the equation of Section 1 over each dual control volume, turns the volume integral into a sum of fluxes through the dual faces, and then gives the numerical flux on one dual face, which looks only at the two states on either side of it.

有限体积离散

Finite-volume discretisation

在节点 i 的对偶控制体上积分并用高斯公式,体积分变成对偶控制体边界上的通量之和:

Integrating over the dual control volume of node i and applying the divergence theorem turns the volume integral into a sum of fluxes over the boundary of that dual control volume:

Viduidt+Ri(u)=0,Ri=Σe∋i±He.

到这里,空间离散剩下的唯一问题就是:一个对偶面上的 He 怎么算。

At this point the only question left in the spatial discretisation is how to evaluate He on a single dual face.

为了方便说明,本页采用一阶空间离散。面上的左右状态直接取两侧节点当前的值,不做任何重构——也就是把解看成分片常数,因此空间精度是一阶的。真实求解器会在这一步加上二阶重构(MUSCL 或最小二乘梯度)再配一个限制器,那会带来两件事:一是模板从“两侧节点”扩大到邻居的邻居,面通量的局部导数不再只涉及两个节点;二是限制器里有 min、max 这类切换,在切换点上不可微;切换点之外它一般可微,而且导数不为零。所以限制器不能照搬第 4 节远场入流/出流开关的做法:那个开关 sign(vn) 在冻结状态附近是常数,导数本来就是零,冻结它不丢掉任何东西,因而是精确的;把限制器在 u* 处冻结,丢掉的却是一个非零的导数,得到的就不再是真实 Jacobian 的转置(一维 Burgers 页面附录 D.2 说明了限制器的导数为什么不能丢)。但边循环、前向、伴随的结构一点不变:仍然是收集 → 算一维通量 → 分发,仍然靠转置得到伴随。一阶的代价只体现在精度上,不体现在本页要讲的那套代数上。

For ease of explanation this page uses a first-order spatial discretisation. The left and right states on a face are taken straight from the current values at the two nodes, with no reconstruction at all — the solution is treated as piecewise constant, so the spatial accuracy is first order. A real solver would add second-order reconstruction here (MUSCL or a least-squares gradient) together with a limiter, and that brings two things: the stencil grows from "the two nodes" to the neighbours’ neighbours, so the local derivatives of a face flux involve more than two nodes; and a limiter contains switches such as min and max, so it is not differentiable at a switch, while away from the switches it usually is, with a non-zero derivative. So a limiter cannot be treated like the far-field inflow/outflow switch of Section 4: that switch, sign(vn), is constant near the frozen state, its derivative is zero anyway, and freezing it drops nothing, so it is exact; freezing a limiter at u* drops a non-zero derivative, and what results is no longer the transpose of the true Jacobian (Appendix D.2 of the 1-D Burgers page explains why a limiter's derivative must be kept). But the structure of the edge loop, of the forward mode and of the adjoint does not change at all: still gather → evaluate the one-dimensional flux → scatter, still transpose to get the adjoint. First order costs accuracy only, and none of the algebra this page is about.

每个对偶面都是一个一维问题

Every dual face is a one-dimensional problem

(a) 真实网格:n9 与 n10 的控制体共用一个对偶面910uLuR红线是公共对偶面 e17,它把两个控制体分开;箭头是面法向 n,由 n9 指向 n10,|n| = 0.5。边界半面没有“对面的节点”,右状态由边界条件给出。(b) 沿法向 n 看:一维左右状态H(uL, uR)uLuR节点 n9 一侧节点 n10 一侧对偶面 e17n两侧各是一个常数状态,面上只需解这一个一维问题。(a) Real mesh: n9 and n10 have control volumes sharing one dual face910uLuRThe red line is dual face e17, between the two volumes; the arrow n runs from n9 to n10, |n| = 0.5.A boundary half-face has no node opposite it, so its right state is given by the boundary condition.(b) Looking along the normal n: a one-dimensional left and right stateH(uL, uR)uLuRnode n9 sidenode n10 sidedual face e17nEach side is a single constant state, so only this one-dimensional problem has to be solved on the face.
图 3.1 · 一个对偶面只把两个节点分开:节点 i 的值作为左状态 uL,节点 j 的值作为右状态 uR。沿法向看过去,这个对偶面就是一维的“左右状态”问题——下面所有内容都建立在这一个函数上。Figure 3.1 · A dual face separates exactly two nodes: the value at node i is the left state uL, the value at node j the right state uR. Looked at along the normal, this dual face is the one-dimensional "left and right state" problem — everything below is built on that single function.

通过一个面的通量只和法向分量 F·n 有关。沿法向看,面两侧就是一维问题:左边常数状态 uL,右边常数状态 uR(图 3.1)。数值通量取左右平均 − 常数耗散系数 ×(右状态 − 左状态):

The flux through a face depends only on the normal component F·n. Along the normal, the two sides of the face form a one-dimensional problem: a constant state uL on the left and a constant state uR on the right (Figure 3.1). The numerical flux is taken as the average of the two sides − a constant dissipation coefficient × (right state − left state):

H(uL,uR)=12[F(uL)+F(uR)]·n左右平均−ε|n|(uR−uL)常数耗散,ε=0.8.
H(uL,uR)=12[F(uL)+F(uR)]·naverage−ε|n|(uR−uL)dissipation,ε=0.8.

记 vn=β·n,由 F(u)·n=12u2vn,中心项就是 14vn(uL2+uR2)。在图 3.2 里拖动滑块,观察两项如何组成通量。以下把对偶面上的这个数记作 h=H(uL,uR)。

Writing vn=β·n and using F(u)·n=12u2vn, the central term is 14vn(uL2+uR2). Drag the sliders in Figure 3.2 to watch how the two terms make up the flux. From here on this number on a dual face is written h=H(uL,uR).

ε 取多大:单调性条件。第 6 节会写出这个通量对两侧状态的两个局部导数:cL=12vnuL+ε|n|,cR=12vnuR−ε|n|。单调的通量要求 cL≥0、cR≤0:左状态增大时通量不减,右状态增大时通量不增。不论 vn 的符号如何,只要 ε≥12|u||β·n̂|(n̂=n/|n|)两式就都成立;要对任意方向的面都成立,就是 |u|≤2ε/|β|=1.43。常数 ε=0.8 只在这个范围里保证单调。本页的 u* 在节点 6 上是 1.536,已经越过这个界:对偶面 (6,14) 上 cL=−0.018,是本算例唯一一个符号不对的局部导数(格心页面的 u* 最大是 1.299,没有越界)。第 15 节会看到,网格加密以后迎风壁面上的 u 一层比一层大,越界的面也越来越多。

How large ε must be: the monotonicity condition. Section 6 writes out the two local derivatives of this flux with respect to the two states: cL=12vnuL+ε|n| and cR=12vnuR−ε|n|. A monotone flux needs cL≥0 and cR≤0: the flux must not decrease when the left state grows, nor increase when the right state grows. Whatever the sign of vn, both hold when ε≥12|u||β·n̂| (with n̂=n/|n|); for faces of every orientation that is |u|≤2ε/|β|=1.43. The constant ε=0.8 guarantees monotonicity only in that range. This page's u* is 1.536 at node 6, beyond the bound: on the dual face (6,14), cL=−0.018, the one local derivative of the wrong sign in this test case (the cell-centred page's u* reaches 1.299 at most and stays inside). Section 15 shows the windward wall values growing with every refinement, and more and more faces crossing the bound.

图 3.2Figure 3.2
交互:一个对偶面上的一维通量
Interactive: the one-dimensional flux on a dual face
本面板取 |n| = 1(算例中对偶面的等效长度大多不同,外圈 16 个远场半面一样长;这里只看函数本身)
This panel uses |n| = 1 (in the test case the dual faces mostly differ in equivalent length, though the 16 far-field half-faces share one; here only the function itself is on show)

到这里,每个内部对偶面都有了两侧节点的状态,数值通量可以直接算。边界半面只有一侧的节点(R=0),缺的右状态要由第 1 节的边界数据补上:远场入流半面取 u∞,出流半面取节点自己的值,壁面半面则直接令 h=0。第 4 节把这三种情形写进同一个一维通量。

Every interior dual face now has the states of its two nodes, and its numerical flux can be evaluated. A boundary half-face has a node on one side only (R=0); its missing right state comes from the boundary data of Section 1: the free stream u∞ on far-field inflow half-faces and the node’s own value on outflow half-faces, while a wall half-face gets h=0. Section 4 writes the three cases into the same one-dimensional flux.

新增(选读):光滑特征边界模型
Added (optional): smooth characteristic-boundary model

方程、数值通量与一阶/二阶重构

Equation, numerical flux and first/second-order reconstruction

方程和内部数值通量形式保持不变:β=(1,0.5)、ε=0.8。u 是正的标量,不是二维速度。非线性来自 u²;特征方向与 β 一致,特征速度大小随 u 改变。此处只研究光滑正解,不加入激波或限制器。

Keep the equation and interior flux form: β=(1,0.5), ε=0.8. The positive scalar u is not a two-dimensional velocity. Nonlinearity comes from u²: characteristics follow β, with speed depending on u. This example studies smooth positive solutions only, without shocks or limiters.

∇⋅(u22𝜷)=0,Hf=𝜷⋅𝒏f4[(uf−)2+(uf+)2]−εℓf(uf+−uf−).\nabla\cdot\left(\frac{u^2}{2}\boldsymbol\beta\right)=0,\qquad H_f=\frac{\boldsymbol\beta\cdot\boldsymbol n_f}{4}\big[(u_f^-)^2+(u_f^+)^2\big]-\varepsilon\ell_f(u_f^+-u_f^-).
ui(xf)=ui+∇hui⋅(xf−xi),Mi∇hui=bi,Mi=∑j∈NiwijdijdijT,bi=∑j∈Niwijdij(uj−ui),wij=1/∥dij∥2.u_i(x_f)=u_i+\nabla_hu_i\cdot(x_f-x_i),\quad M_i\nabla_hu_i=b_i,\quad M_i=\sum_{j\in N_i}w_{ij}d_{ij}d_{ij}^T,\quad b_i=\sum_{j\in N_i}w_{ij}d_{ij}(u_j-u_i),\quad w_{ij}=1/\|d_{ij}\|^2.

一阶把梯度设为零,直接使用控制体值。二阶由相邻控制体做距离平方反比加权(w_ij=1/‖d_ij‖²)的最小二乘,求一个 2×2 系统;每条直线段采用两点 Gauss 积分。若 u 是线性函数,梯度应精确恢复到舍入误差;这是重构实现的第一道检查。节点坐标改变时,d、w、M、b 以及重构位置全部参与求导。

First order sets the gradients to zero and uses the control-volume values directly. Second order uses inverse-distance-squared weighted least squares over adjacent control volumes, solving a 2×2 system, followed by two-point Gauss quadrature on each straight segment. Linear fields should reproduce their gradients to roundoff. Geometry derivatives include d, w, M, b and reconstruction locations.

for unknown i:
    M = sum(w * outer(dx,dx))
    b = sum(w * dx * (u[j]-u[i]))
    grad[i] = solve2(M,b) if order==2 else [0,0]
for face segment:
    for Gauss point xq:
        uL = u[L] + dot(grad[L],xq-x[L])
        uR = reconstruct_neighbor_or_apply_boundary(xq)
        H += weight * numerical_flux(uL,uR,normal)

为什么梯度来自一个小线性系统?把每个邻点的增量 u_j−u_i 近似为 d_ij·∇_hu_i,最小化加权平方误差。令对 ∇_hu_i 的导数为零,就得到上面的正规方程。二维至少需要两个不共线的方向;det M 接近零意味着邻域无法可靠确定某个方向的梯度。交互表列出实际 M、行列式和各邻点增量。

Why a small linear system? Approximate each neighbour jump u_j−u_i by d_ij·∇_hu_i and minimise the weighted squared mismatch. Setting its derivative with respect to ∇_hu_i to zero gives the normal equations above. Two dimensions require at least two non-collinear directions; a nearly zero determinant means that the stencil cannot reliably resolve one gradient component. The panel shows the actual matrix, determinant and neighbour jumps.

min⁡∇hui12∑j∈Niwij[dij⋅∇hui−(uj−ui)]2,xq±=xa+xb2±xb−xa23.\min_{\nabla_hu_i}\frac12\sum_{j\in N_i}w_{ij}\left[d_{ij}\cdot\nabla_hu_i-(u_j-u_i)\right]^2,\quad x_{q\pm}=\frac{x_a+x_b}{2}\pm\frac{x_b-x_a}{2\sqrt3}.

通量的第一项近似物理输运,第二项抑制相邻状态的非物理振荡。ε 固定为 0.8,不对谱半径取最大值,因此这里没有波速选择分支。二阶改的是两个面迹的近似及积分;不会把 ε 简单除以网格尺度。边界值和耗散同样逐积分点求值。

The first flux term approximates physical transport; the second damps nonphysical inter-state oscillation. The fixed ε=0.8 avoids a spectral-radius maximum branch. Second order changes only the two face traces and the quadrature; ε is not rescaled with the mesh size. Boundary data and dissipation are evaluated at each quadrature point.

图 3.3Figure 3.3
第 4 节Section 4

边界条件

Boundary conditions

内圈和外圈的处理方式完全不同:外圈按特征方向给数据,内圈用一个离散闭合。两者都只在上一节那个一维通量内部起作用:边界半面没有“右节点”,远场由边界条件给出右状态 uR,无粘壁面则直接令 h=0。把各个对偶面的通量拼成残差的边循环在第 5 节展开——它本身一行都不用改。

The inner and outer rings are treated quite differently: the outer ring takes data by the characteristic direction, the inner ring gets a discrete closure. Both act only inside the one-dimensional flux of the previous section: a boundary half-face has no "right node", so at the far field the boundary condition supplies the right state uR, while the inviscid wall sets h=0 outright. The edge loop that assembles the fluxes of all the dual faces into the residual is unfolded in Section 5 — it needs no change at all.

内圈:零通量闭合 h=0

Inner ring: the zero-flux closure h=0

本页把内圈叫作无粘壁面,并在它的每个半面上直接令数值通量为零:

This page calls the inner ring the inviscid wall and sets the numerical flux on every one of its half-faces to zero:

hwall=0.

这是离散问题的一个闭合,选它只为一件事:让边循环尽可能简单——壁面半面不给 R、A、AT 任何贡献。它不是这个方程在物面上的物理条件,这一点要先说清楚:它在物面的迎风一侧多给了一个条件,也没有连续极限。本方程的输运速度 uβ 会穿过物面:β 是常向量,沿闭合的物面 β·n 必然有正有负。零物理通量 12u2β·n=0 在 β·n≠0 的地方要求 u=0。在迎风一侧(β·n>0,n 指向物体),特征线从远场带着来流值 u∞=1 到达物面,并在那里离开计算域:再要求 u=0,问题就过度确定了。在背风一侧(β·n<0),特征线从物面进入计算域,u=0 是一个退化的入流值:特征速度 uβ·n 在那里为零。所以带这个壁面的连续问题没有光滑的正值解,离散解在加密时也不趋于任何极限:第 15 节实测,网格每加密一层,Lh 大约翻一倍(图 4.2)。

This is a closure of the discrete problem, chosen for one reason: it keeps the edge loop as simple as it can be — a wall half-face contributes nothing to R, A or AT. It is not the physical boundary condition of this equation at the body, and that is best said up front: it over-determines the windward side of the body and has no continuum limit. The transport velocity uβ of this equation crosses the body: β is a constant vector, so β·n takes both signs around the closed wall. A zero physical flux 12u2β·n=0 forces u=0 wherever β·n≠0. On the windward side (β·n>0, with n pointing into the body) the characteristics arrive from the far field carrying the free-stream value u∞=1 and leave the domain there: demanding u=0 as well over-determines the problem. On the leeward side (β·n<0) the characteristics enter the domain from the wall, and u=0 is a degenerate inflow value: the characteristic speed uβ·n vanishes there. So the continuous problem with this wall has no smooth positive solution, and the discrete solutions approach no limit under refinement either: Section 15 measures Lh roughly doubling with every refinement (Figure 4.2).

壁面节点 n1 的控制体:两条壁面半面把它封口,两条的通量都是 0h = 01琥珀 = 2 条壁面半面(h = 0);红色 = 内部对偶面,它们才带来非零贡献。Wall node n1: two wall half-faces seal its control volume, and the flux across both is zeroh = 01Amber = the two wall half-faces (h = 0); red = the interior dual faces, the ones that contribute.
图 4.1 · 无粘壁面画在真实网格上。壁面节点的控制体被两条壁面半面封口;两条半面各有自己的法向与长度,但在离散闭合 h=0 下一维通量恒为 0——所以它们不改变残差。Figure 4.1 · The inviscid wall drawn on the real mesh. A wall node's control volume is sealed by two wall half-faces; each has its own normal and length, yet under the discrete closure h=0 the one-dimensional flux is identically zero — so they do not change the residual.

于是壁面半面不改变残差——它只负责把控制体封口(图 4.1)。这一点在第 5 节的演示里可以直接看到:播放到壁面半面时,h=0(图 5.2)。

So a wall half-face does not change the residual — its only job is to seal the control volume (Figure 4.1). This is directly visible in the demo of Section 5: when the play reaches a wall half-face, h=0 (Figure 5.2).

这并不影响本页的主线。第 5–14 节的残差、Jacobian、切线、伴随与梯度,都是这个固定离散问题在给定网格上的精确导数,与它有没有连续极限无关,而本页要讲的正是这个离散伴随。有连续极限的内边界见各节末尾的“新增”扩展(选读):那里内外边界都按特征方向处理。

None of this touches the main line of the page. The residual, the Jacobian, the tangent, the adjoint and the gradient of Sections 5–14 are exact derivatives of this fixed discrete problem on the given mesh, whether or not it has a continuum limit, and that discrete adjoint is what the page is about. An inner boundary with a continuum limit is the optional “Added” extension at the end of each section, where both boundaries are treated by characteristics.

图 4.2Figure 4.2
交互:特征线与零通量壁面
Interactive: the characteristics and the zero-flux wall
左:沿 β 的特征线与按 β·n 着色的边界;右:本页离散问题在逐层加密网格上的壁面值与 Lh(第 15 节的研究,全部用本页的 Jacobi 迭代求解)
Left: the characteristics along β and the boundary coloured by β·n; right: the wall values and Lh of this page's discrete problem on the refined meshes (the study of Section 15, every level solved by the page's Jacobi iteration)

图 4.2 左边沿 β 画出特征线:从远场入流边进入的特征线,一部分绕过物体、从远场出流边离开,另一部分撞上迎风一侧的壁面(实线箭头);背风一侧的壁面发出特征线(虚线)。右边的滑块取本页离散问题在逐层加密网格上的解:迎风一侧的壁面值随加密增大,第 0 层就超过第 3 节的单调性界 1.43,Lh 也随之大约翻倍;到第 2 层(288 个节点),从 u0≡1 出发的 Jacobi 迭代就不再收敛。

The left of Figure 4.2 draws the characteristics along β: of those entering through the far-field inflow faces, some pass the body and leave through the outflow faces, the others run into the windward side of the wall (solid arrows); the leeward side of the wall emits characteristics (dashed). The slider on the right steps through this page's discrete problem on the refined meshes: the windward wall values grow with refinement and exceed the monotonicity bound 1.43 of Section 3 already on level 0, and Lh roughly doubles with them; at level 2 (288 nodes) the Jacobi iteration from u0≡1 no longer converges.

外圈:远场特征边界

Outer ring: the far-field characteristic boundary

远场要按特征方向决定用哪一侧的信息。标量守恒律 F(u)=12u2β 的波速是 uvn(vn=β·n,n 指向域外)。算例中 u>0,所以 sign(uvn)=sign(vn),判据是纯几何的:

At the far field the characteristic direction decides which side supplies the information. The wave speed of the scalar law F(u)=12u2β is uvn (with vn=β·n and n pointing out of the domain). In the test case u>0, so sign(uvn)=sign(vn) and the test is purely geometric:

  • 入流(vn<0,特征从域外进来):右状态取自由来流 uR=u∞=1.0;
  • Inflow (vn<0, the characteristic enters from outside): the right state is the free stream, uR=u∞=1.0;
  • 出流(vn≥0,特征从域内出去):右状态由内部外推,uR=uL。
  • Outflow (vn≥0, the characteristic leaves the domain): the right state is extrapolated from inside, uR=uL.
外圈的远场半面:按 vn 的符号分成入流与出流,每个外圈节点各有两条1718192021222324蓝色 = 入流(vn < 0,8 条):uR = uinf = 1红色 = 出流(vn >= 0,8 条):uR = uL(内部外推)The outer ring's far-field half-faces, split by the sign of vn; each outer node has two1718192021222324blue = inflow (vn negative, 8 of them): uR = uinf = 1red = outflow (vn >= 0, 8 of them): uR = uL, extrapolated from inside
图 4.3 · 远场半面按 vn 的符号分成入流与出流。入流取自由来流 uR=u∞,出流由内部外推 uR=uL。因为算例中 u>0,判据 sign(uvn) 退化成纯几何的 sign(vn),开关在冻结状态处固定。Figure 4.3 · The far-field half-faces split by the sign of vn into inflow and outflow. Inflow takes the free stream uR=u∞; outflow extrapolates from inside, uR=uL. Because u>0 in the test case the test sign(uvn) degenerates to the purely geometric sign(vn), and the switch stays fixed at the converged state.

算例的 8 条远场边里 4 条入流、4 条出流(图 4.3)。开关在冻结状态 u* 处固定(frozen switch),前向与伴随都在这个固定的分支上线性化。

Of the 8 far-field edges in the test case, 4 are inflow and 4 outflow (Figure 4.3). The switch is fixed at the frozen state u* (a frozen switch), and both the forward and the adjoint mode linearise on that fixed branch.

这是把边界条件收在右状态与一维通量这一层的好处:边循环的骨架——收集、算通量、分发——与边界种类无关(代码里这些分支都在 flux 一侧,下面第 5 节的清单把它们逐行摊开,只为能逐句播放)。

That is the benefit of keeping the boundary conditions at the level of the right state and the one-dimensional flux: the skeleton of the edge loop — gather, evaluate, scatter — does not depend on how many kinds of boundary there are (in the code these branches all sit on the flux side; the listing of Section 5 below spells them out line by line only so they can be played statement by statement).

为什么这里没有无滑移壁面。本页的方程是纯对流的(无粘):边界上只能在特征进入区域的地方给数据。物面是一条闭合曲线,β 是常数,所以 β·n 沿物面必然有正有负(本算例 u>0,特征速度 uβ·n 也是如此);在特征离开区域的那部分物面上再规定 u=uw,问题就多给了一个条件:没有扩散,那里没有任何机制能把壁面值带进流场。上面的零通量闭合 h=0 在迎风一侧恰好也多给了这样一个条件(它等于要求 u=0);本页保留它,只把它当作离散闭合。无滑移这类 Dirichlet 壁面条件属于带扩散项的方程——拉普拉斯算子是二阶的,它让 u=uw 在整条物面上都适定。所以壁面上的 Dirichlet 条件放在另一对页面里讲:二维标量对流扩散方程(格点)。

Why there is no no-slip wall here. The equation on this page is purely convective (inviscid): boundary data can be given only where characteristics enter the domain. The body is a closed curve and β is constant, so β·n must take both signs along it (with u>0 in this test case, so does the characteristic speed uβ·n); prescribing u=uw where characteristics leave the domain gives the problem one condition too many: without diffusion nothing can carry the wall value into the flow there. The zero-flux closure h=0 above gives exactly such an extra condition on the windward side (there it amounts to demanding u=0); the page keeps it only as a discrete closure. A Dirichlet wall such as no-slip belongs to an equation with diffusion — the Laplacian is second order, and it makes u=uw well posed along the whole wall. So the Dirichlet wall is the subject of another pair of pages: the two-dimensional scalar advection–diffusion equation (node-centred).

逐步推导:远场开关为什么冻结得住

Step by step: why the far-field switch can be frozen

上面说开关在收敛解处冻结,前向与伴随都在这个分支上线性化。下面一步步说明:这对状态导数是精确的,对几何导数也在一个算得出来的范围内精确(图 4.4)。

Above, the switch is frozen at the converged state and both the tangent and the adjoint are linearised on that branch. Step by step: this is exact for the state derivatives, and exact for the geometric derivatives within a margin that can be computed (Figure 4.4).

第一步,远场半面的右状态由一个开关给出(core_node.js 的 local()):

Step 1. The right state of a far-field half-face comes from a switch (local() in core_node.js):

uR={u∞,vn<0,uL,vn≥0,vn=𝜷⋅𝒏.u_R=\begin{cases}u_\infty,&v_n<0,\\u_L,&v_n\ge0,\end{cases}\qquad v_n=\boldsymbol\beta\cdot\boldsymbol n.

第二步,沿法向的物理通量是 F(u)⋅𝒏=12u2vnF(u)\cdot\boldsymbol n=\tfrac12u^2v_n,它对 uu 的导数 u vnu\,v_n 就是特征速度;u vn<0u\,v_n<0 的地方信息从区域外面进来。本页远场上 u>0u>0(远场节点中 u∗u^* 最小是 0.955),所以 sign⁡(u vn)=sign⁡(vn)\operatorname{sign}(u\,v_n)=\operatorname{sign}(v_n):开关只看几何,不看 uu。

Step 2. Along the normal, the physical flux is F(u)⋅𝒏=12u2vnF(u)\cdot\boldsymbol n=\tfrac12u^2v_n; its derivative with respect to uu, u vnu\,v_n, is the characteristic speed, and information enters the domain where u vn<0u\,v_n<0. On this page u>0u>0 at the far field (the smallest u∗u^* in a far-field node is 0.955), so sign⁡(u vn)=sign⁡(vn)\operatorname{sign}(u\,v_n)=\operatorname{sign}(v_n): the switch looks at the geometry, never at uu.

第三步,于是在固定网格上,每个面对所有 uu 都停在同一个分支上,右状态是 uu 的线性函数,导数就是分支上的常数:

Step 3. On a fixed mesh every face therefore stays on one branch for every uu, the right state is a linear function of uu, and its derivatives are the constants of that branch:

vn<0:∂uR∂uL=0,∂uR∂u∞=1;vn≥0:∂uR∂uL=1,∂uR∂u∞=0.\begin{gathered}v_n<0:\quad \frac{\partial u_R}{\partial u_L}=0,\quad \frac{\partial u_R}{\partial u_\infty}=1;\\ v_n\ge0:\quad \frac{\partial u_R}{\partial u_L}=1,\quad \frac{\partial u_R}{\partial u_\infty}=0.\end{gathered}

开关本身是 vnv_n 的阶梯函数,对 uu 的导数处处为零,所以“冻结开关”没有丢掉任何一项:第 8、9 节的前向与伴随是这个离散残差的精确导数。

The switch itself is a step function of vnv_n whose derivative with respect to uu is zero everywhere, so freezing it drops nothing: the tangent and the adjoint of Sections 8 and 9 are exact derivatives of this discrete residual.

第四步,几何导数要多想一步。代码只在建半面时判断一次开关(每个半面存下的 inflow 标志;setCoords 移动坐标时不重判),所以差分与复数步长求导的一直是这一个分支上的函数。它与“每次都重判”的函数只在某个远场半面的 vnv_n 变号时才不同。把一个节点移动一段有限的位移 Δ𝒙\Delta\boldsymbol x,相邻远场半面的法向变化 ∣Δ𝒏∣≤12∣Δ𝒙∣|\Delta\boldsymbol n|\le \tfrac12|\Delta\boldsymbol x|,于是 ∣Δvn∣≤∣𝜷∣ ∣Δ𝒏∣|\Delta v_n|\le|\boldsymbol\beta|\,|\Delta\boldsymbol n|。

Step 4. Geometric derivatives need one more thought. The code decides the switch once, when the half-faces are built (the inflow flag stored on each half-face; setCoords moves the coordinates without deciding again), so finite differences and the complex step always differentiate the function on that one branch. It differs from a function that re-decides only where some far-field vnv_n changes sign. Moving one node by a finite displacement Δ𝒙\Delta\boldsymbol x changes the normal of an adjacent far-field half-face by ∣Δ𝒏∣≤12∣Δ𝒙∣|\Delta\boldsymbol n|\le \tfrac12|\Delta\boldsymbol x|, so ∣Δvn∣≤∣𝜷∣ ∣Δ𝒏∣|\Delta v_n|\le|\boldsymbol\beta|\,|\Delta\boldsymbol n|.

第五步,本页远场半面上 ∣vn∣|v_n| 最小是 ∣vn∣min⁡=0.0576|v_n|_{\min}=0.0576,所以只要满足下式,就没有半面换分支:

Step 5. The smallest ∣vn∣|v_n| on this page’s far field is ∣vn∣min⁡=0.0576|v_n|_{\min}=0.0576, so no half-face changes branch as long as

∣Δ𝒙∣<∣vn∣min⁡12∣𝜷∣=0.103.|\Delta\boldsymbol x|<\frac{|v_n|_{\min}}{\tfrac12|\boldsymbol\beta|}=0.103.

第 13 节的差分步长 10−610^{-6},连同它的步长扫描里最大的 10−110^{-1},都在这个范围之内;第 14 节的复数步长只动虚部,而开关看的是实部。

The difference step 10−610^{-6} of Section 13, and even the largest step 10−110^{-1} of its sweep, are inside this range; the complex step of Section 14 moves only the imaginary part, while the switch looks at the real part.

核对:16 个远场半面在基准几何上存下的开关与 vnv_n 的符号全部一致(入流 8 个、出流 8 个);min⁡∣vn∣/∣𝒏∣=0.0793\min|v_n|/|\boldsymbol n|=0.0793;上面的界是 0.103(本页代码在构建时计算)。

Check: the switch stored on the 16 far-field half-faces agrees with the sign of vnv_n at the base geometry (8 inflow, 8 outflow); min⁡∣vn∣/∣𝒏∣=0.0793\min|v_n|/|\boldsymbol n|=0.0793; the bound above is 0.103 (computed by this page’s code at build time).

−1.0−0.50.00.51.00°90°180°270°360°外法向 n 的方向(度)direction of the outward normal n (degrees)开关switch出流面 β·n/|n| ≥ 0outflow β·n/|n| ≥ 0入流面 β·n/|n| < 0inflow β·n/|n| < 0(a) β·n/|n| 随面的方向变化(a) β·n/|n| against the face direction01−1.0−0.50.00.51.0β·n/|n|(远场半面)β·n/|n| (far-field half-faces)∂uR/∂u∞∂uR/∂uL(b) 右状态对 uL、u∞ 的导数(b) derivatives of the right state没有面no face|β·n|/|n| < 0.0793
图 4.4 · 远场开关只看几何。(a) 横轴是外法向 n 的方向角,曲线是 β·n/|n|=|β|cos∠(n, β),在两条紫色虚线处过零,那里就是入流与出流的分界;点是本页 16 个远场半面(蓝色入流 8 个,红色出流 8 个;同一条边的两个半面方向相同,外圈表示两点重合)。(b) 右状态 uR 对两个输入的导数是 β·n/|n| 的阶梯函数:入流一侧 ∂uR/∂u∞=1、∂uR/∂uL=0,出流一侧反过来。阶梯只在 0 处跳;底部的刻线是各个面的位置,紫色带 |β·n|/|n|<0.0793 里一个面也没有,所以每个面都在平台上,导数就是平台值。Figure 4.4 · The far-field switch looks only at the geometry. (a) Along the axis is the direction of the outward normal n; the curve is β·n/|n| = |β|cos∠(n, β), which crosses zero at the two violet dashed lines, the border between inflow and outflow. The dots are this page’s 16 far-field half-faces (8 inflow in blue, 8 outflow in red; the two half-faces of one edge share a direction, and an outer ring marks the overlap). (b) The derivatives of the right state uR with respect to its two inputs are step functions of β·n/|n|: ∂uR/∂u∞ = 1 and ∂uR/∂uL = 0 on the inflow side, the other way round on the outflow side. The steps jump only at 0; the ticks along the bottom are the faces, and the violet band |β·n|/|n| < 0.0793 holds none of them, so every face sits on a plateau and its derivatives are the plateau values.
新增(选读):光滑特征边界模型
Added (optional): smooth characteristic-boundary model

边界条件:内外边界统一按特征方向处理

Boundaries: characteristic conditions on both inner and outer boundaries

新增模型不使用零通量分支;本节上方保留主线的模型供对照。保持固定 β 时,封闭边界不可能处处与 β 相切;零物理通量 u²β·n/2=0 会在 β·n≠0 的位置要求 u=0,与光滑正值解不相容(这正是上方主线所说的:零通量闭合在迎风一侧多给了一个条件)。新增模型的内边界允许标量通量通过,称为“内边界”,不再称为壁面。法向始终指向计算域外,内边界法向因此指向孔洞。

The added model does not use the zero-flux branch; the main-line model above is kept for comparison. A fixed β cannot be tangent everywhere to a closed contour, so a zero physical flux u²β·n/2=0 would require u=0 wherever β·n≠0, which no smooth positive solution satisfies (this is what the main line above says: the zero-flux closure over-determines the windward side). The inner boundary of the added model lets the scalar flux through; it is called the inner boundary, not a wall. Normals point out of the computational domain, hence into the hole at the inner boundary.

ue(x,y)=u∞+0.2sin⁡[π(y−0.5x)],u∞=1,𝜷⋅∇ue=0,∇⋅(12ue2𝜷)=0.u_e(x,y)=u_\infty+0.2\sin[\pi(y-0.5x)],\quad u_\infty=1,\quad \boldsymbol\beta\cdot\nabla u_e=0,\quad\nabla\cdot\left(\tfrac12u_e^2\boldsymbol\beta\right)=0.
uf+={uin(xf,yf)=ue(xf,yf),𝜷⋅𝒏f<0,uf−,𝜷⋅𝒏f≥0.u_f^+=\begin{cases}u_{\rm in}(x_f,y_f)=u_e(x_f,y_f),&\boldsymbol\beta\cdot\boldsymbol n_f<0,\\u_f^-,&\boldsymbol\beta\cdot\boldsymbol n_f\ge0.\end{cases}

入流给定函数 u_in,出流由内部解决定;不在出流再指定一个 Dirichlet 值。内外入流都取同一个解析场,因而特征线穿过孔洞后重新进入计算域时不会引入不匹配的数据。二阶在每个 Gauss 点计算 u_in,不把它冻结为面中心常数。u_in 的平均水平就是主线的来流值 u∞,它是设计变量 D=(u∞, X) 的一个分量(新增面板按存储顺序给参数编号:先 48 个坐标,u∞ 在最后,编号 48);几何或入流方向的切线必须包含 u˙in=∇uin⋅x˙f+u˙∞\dot u_{\rm in}=\nabla u_{\rm in}\cdot\dot x_f+\dot u_\infty(点表示前向导数,见主线第 8 节)。

Specify u_in on inflow and let the interior determine outflow; do not impose another Dirichlet value there. Both inner and outer inflow sample the same analytic field, avoiding incompatible data when a characteristic re-enters after crossing the hole. Second order evaluates u_in at each Gauss point rather than freezing it at the face centre. The mean level of u_in is the main line’s free-stream value u∞, a component of the design D = (u∞, X) (the added panels number the parameters in storage order: the 48 coordinates first, u∞ last, index 48); the tangent along a geometry or inflow direction includes u˙in=∇uin⋅x˙f+u˙∞\dot u_{\rm in}=\nabla u_{\rm in}\cdot\dot x_f+\dot u_\infty (the dot is the forward derivative, Section 8 of the main line).

图 4.5Figure 4.5
第 5 节Section 5

残差 = 边循环:收集、计算、分发

The residual = the edge loop: gather, compute, scatter

先固定离散问题与符号

Fix the discrete problem and its notation

本页每个节点存储 1 个未知量。以下用 𝐮\mathbf u 表示把所有位置按代码顺序排成的列向量(标量问题每个位置只有一个分量)。它是一个有限维向量,不是连续函数。物理方程中的空间导数只用于说明模型;求离散伴随时,求导对象是本页实际装配的有限维函数。

Each node stores 1 unknown. Below, 𝐮\mathbf u stacks all locations in code order (a scalar problem has one component per location). It is a finite-dimensional vector, not a continuous function. Spatial derivatives in the physical equations explain the model; discrete differentiation acts on the actual finite-dimensional functions assembled on this page.

𝐮∈ℝnu,D∈ℝna,\mathbf u\in\mathbb R^{n_u},\quad D\in\mathbb R^{n_a},
R:ℝnu×ℝna→ℝnu,L:ℝnu×ℝna→ℝ.R:\mathbb R^{n_u}\times\mathbb R^{n_a}\to\mathbb R^{n_u},\quad L:\mathbb R^{n_u}\times\mathbb R^{n_a}\to\mathbb R.

DD 是本页全部设计变量排成的向量:来流值 u∞u_\infty 与全部节点坐标 XX(新增的光滑模型另有自己的参数,见新增部分)。第 10、11 节把 DD 写成具体的 u∞u_\infty,第 12 节写成 XX,例如 dL/dX=∂L/∂X+ψT∂R/∂XdL/dX=\partial L/\partial X+\psi^T\partial R/\partial X;第 7 节的 g=(∂L/∂u)Tg=(\partial L/\partial u)^T 就是这里的状态梯度。

DD stacks all design variables of this page: the free-stream value u∞u_\infty and all node coordinates XX (the added smooth model has its own parameters; see the added blocks). Sections 10 and 11 write DD as the concrete u∞u_\infty and Section 12 as XX, e.g. dL/dX=∂L/∂X+ψT∂R/∂XdL/dX=\partial L/\partial X+\psi^T\partial R/\partial X; the g=(∂L/∂u)Tg=(\partial L/\partial u)^T of Section 7 is the state gradient here.

A=∂R∂𝐮∈ℝnu×nu,RD=∂R∂D∈ℝnu×na,A=\frac{\partial R}{\partial \mathbf u}\in\mathbb R^{n_u\times n_u},\quad R_D=\frac{\partial R}{\partial D}\in\mathbb R^{n_u\times n_a},
g=(∂L∂𝐮)T∈ℝnu,∇DL=(∂L∂D)T∈ℝna.g=\Big(\frac{\partial L}{\partial \mathbf u}\Big)^{T}\in\mathbb R^{n_u},\quad \nabla_DL=\Big(\frac{\partial L}{\partial D}\Big)^{T}\in\mathbb R^{n_a}.

这里 gg、∇DL\nabla_DL 都是列向量,它们的转置才是偏导数行。求导采用自动微分(AD)的记号:程序变量 xx 上加点的 x˙\dot x 是前向(tangent)模式沿给定种子方向的导数,加横线的 xˉ\bar x 是反向(adjoint)模式累加到 xx 上的权重,例如 𝐮˙\dot{\mathbf u} 与 𝐮ˉ\bar{\mathbf u}。两者都是对本页有限维代码求导;变分符号 δ\delta 只用于附录 A、B 的连续伴随里连续函数的变分。

Both gg and ∇DL\nabla_DL are columns; their transposes are the derivative rows. Derivatives use the notation of automatic differentiation (AD): a dot x˙\dot x on a program variable xx is its forward (tangent) derivative along the chosen seed direction, and a bar xˉ\bar x is the weight accumulated into xx in reverse (adjoint) mode, for example 𝐮˙\dot{\mathbf u} and 𝐮ˉ\bar{\mathbf u}. Both differentiate the finite-dimensional code of this page; the variation symbol δ\delta is kept for variations of continuous functions in the continuous adjoints of Appendices A and B.

残差计算对每条边做三件事:从两端节点收集状态,调用一维通量计算 h,再把 h 分发回两个节点——节点 i 加、节点 j 减。同一个 h 以相反符号进入两个控制体,这就是守恒。

Computing the residual does three things on every edge: gather the states from the two nodes, call the one-dimensional flux to compute h, and scatter h back to the two nodes — added at node i, subtracted at node j. The same h enters the two control volumes with opposite signs, and that is conservation.

反过来看一个节点:它的残差就是自己所有对偶面的通量之和,符号由它在每个面上是 i 还是 j 决定(图 5.1)。内部节点的面数等于它的相邻节点数,随网格而变——这是节点中心格式与格心格式在数据结构上的主要差别。

Seen from a node instead: its residual is the sum of the fluxes of all its own dual faces, each signed by whether the node is i or j on that face (Figure 5.1). An interior node has as many faces as it has neighbouring nodes, which varies across the mesh — the main data-structure difference between a node-centred and a cell-centred scheme.

(a) 内部节点 n9:每个相邻节点贡献一个 ±h−h3+h17+h18+h19+h209R9 = − h3 + h17 + h18 + h19 + h20作为 i 取 +h(流出),作为 j 取 −h(流入)(b) 壁面节点 n1:再加两个壁面半面(无粘)+h1+h2+h3+h49+h501R1 = h1 + h2 + h3 + h49 + h50琥珀色是无粘壁面半面:h 恒为 0每个内部对偶面的同一个 h 同时出现在两个控制体里,符号相反;边界半面只出现在一个控制体里。图中控制体形状与真实网格完全一致。无粘壁面 h = 0,残差只由内部对偶面决定。(a) Interior node n9: each neighbouring node contributes one ±h−h3+h17+h18+h19+h209R9 = − h3 + h17 + h18 + h19 + h20+h where it acts as i (outflow), −h where it acts as j (inflow)(b) Wall node n1: plus two wall half-faces (inviscid)+h1+h2+h3+h49+h501R1 = h1 + h2 + h3 + h49 + h50amber is the inviscid wall half-face: h is always 0The same h of an interior dual face appears in two control volumes with opposite signs; a boundary half-face appears in only one. The control-volume shapes drawn here match the real mesh exactly.Inviscid wall: h = 0, so interior dual faces set the residual.
图 5.1 · 边循环跑完之后,每个节点累加到的就是这些项之和——下面演示里底部“残差”一行的数字,正是这样一项一项加出来的。在收敛解 u* 上,每个控制体的各项互相抵消,残差为零。Figure 5.1 · Once the edge loop has run, what each node has accumulated is exactly this sum of terms — the numbers on the "residual" row at the bottom of the demo below are built up one term at a time. At the converged solution u* the terms of each control volume cancel and the residual is zero.

图 5.2 逐句执行这个循环。网格上:紫色是当前对偶面,箭头从节点指向面(收集)、再从面指向节点(分发)。面板底部是两条数组:上面是输入的节点状态,下面是正在累加的残差。可以按 ▶ 连续播放,也可以用“下一步”或键盘左右键逐句走,或用右上角的下拉框直接跳到某个面(例如跳到一个壁面半面,看无粘壁面的 h=0)。

Figure 5.2 executes this loop statement by statement. On the mesh: violet is the current dual face, and the arrows run from the nodes to the face (gather) and back from the face to the nodes (scatter). At the bottom of the panel are two arrays: above, the input node states; below, the residual being accumulated. You can press ▶ to play continuously, step through statement by statement with "next" or the left and right arrow keys, or jump straight to a face with the dropdown at the top right (for example to a wall half-face, to see h=0 on an inviscid wall).

图 5.2Figure 5.2

逐步推导:内部通量在求和中两两抵消

Step by step: interior fluxes cancel in the sum

上面说同一个 hh 以相反的符号进入两个节点,这就是守恒。把所有节点的残差加起来,就能看到它的整体形式。

Above, the same hh enters two nodes with opposite signs, and that is conservation. Adding the residuals of all nodes shows its global form.

第一步,面循环的分发写成一个带符号的关联矩阵 ℬ\mathcal B(第 8 节“局部导数怎样组成全局 Jacobian”一段用的也是它):

Step 1. Write the scatter of the face loop as a signed incidence matrix ℬ\mathcal B (the same ℬ\mathcal B as in the block “How local derivatives form the global Jacobian” of Section 8):

Ri(u)=∑fℬif hf(u),ℬL(f)f=+1,ℬR(f)f=−1.R_i(u)=\sum_f\mathcal B_{if}\,h_f(u),\qquad \mathcal B_{L(f)f}=+1,\qquad \mathcal B_{R(f)f}={-1}.

其余元素为零。第二步,交换求和次序:

All other entries are zero. Step 2, exchange the order of summation:

∑iRi(u)=∑fhf(u)∑iℬif.\sum_iR_i(u)=\sum_fh_f(u)\sum_i\mathcal B_{if}.

内部对偶面那一列里恰有一个 +1+1 和一个 −1-1,列和为零;边界半面没有右节点,列和为 +1+1。第三步,所以对任意 uu(不只是收敛解)

An interior dual face’s column holds exactly one +1+1 and one −1-1 and sums to zero; a boundary half-face has no right node, and its column sums to +1+1. Step 3: for every uu, not only the converged one,

∑iRi(u)=∑f∈∂Ωhhf(u)=∑f∈farhf(u),\sum_iR_i(u)=\sum_{f\in\partial\Omega_h}h_f(u)=\sum_{f\in\mathrm{far}}h_f(u),

壁面上 h=0h=0,不出现。第四步,在收敛解处每一行都是零,于是远场通量之和也是零:从入流半面进来的通量(h<0h<0)恰好从出流半面出去(图 5.3)。

the wall has h=0h=0 and drops out. Step 4: at the converged state every row is zero, so the far-field fluxes sum to zero as well; what enters through the inflow half-faces (h<0h<0) leaves through the outflow half-faces (Figure 5.3).

核对:对一个没有收敛的测试状态 uu,左边是 −0.4135,两边之差 2.8×10−16;在 u∗u^* 处,入流合计 −1.9641,出流合计 1.9641,总和 −2.2×10−16(本页代码在构建时计算)。

Check: for an unconverged test state uu the left side is −0.4135 and the two sides differ by 2.8×10−16; at u∗u^* the inflow total is −1.9641, the outflow total 1.9641, and the sum −2.2×10−16 (computed by this page’s code at build time).

h = 0(a) 收敛解处穿过边界的通量(a) the fluxes through the boundary at u*−0.50−0.250.000.250.50f65f68f70f72f74f76f78f66(b) 远场半面通量 hf(按方向排列)(b) far-field fluxes hf, ordered by direction入流 Σ h = −1.9641inflow Σ h = −1.9641出流 Σ h = 1.9641outflow Σ h = 1.9641全部边界:Σ h = −2.2×10⁻¹⁶whole boundary: Σ h = −2.2×10⁻¹⁶
图 5.3 · 内部面在求和中两两抵消,只剩边界通量。(a) 本页收敛解 u* 处每个远场半面的通量 hf,箭头长度与 |hf| 成正比:蓝色入流面上 hf<0,通量流入区域;红色出流面上 hf>0,通量流出;琥珀色壁面上 h=0。(b) 同一组通量按面的方向排成柱状:入流合计 −1.9641,出流合计 1.9641,两者之和是 −2.2×10⁻¹⁶,这正是 ΣiRi(u*)。Figure 5.3 · Interior faces cancel in pairs in the sum, and only the boundary fluxes remain. (a) The flux hf of every far-field half-face at this page’s converged state u*, with arrows proportional to |hf|: on the blue inflow faces hf < 0 and the flux enters the domain; on the red outflow faces hf > 0 and it leaves; on the amber wall h = 0. (b) The same fluxes as bars, ordered by the direction of the face: the inflow total is −1.9641, the outflow total 1.9641, and their sum is −2.2×10⁻¹⁶, which is exactly ΣiRi(u*).
新增(选读):光滑特征边界模型
Added (optional): smooth characteristic-boundary model

残差:梯度收集、面计算与守恒分发

Residual: gradient gathering, face evaluation and conservative scatter

残差仍是流出通量的积分和,不除以体积。本例没有源项。内部面向左加 H、向右减 H,因此全域求和时内部贡献抵消,只剩边界通量。二阶先计算所有梯度,再做面循环;格点面分段的每一份贡献都向同一对控制体守恒分发。

The residual remains the sum of integrated outward fluxes, without volume normalisation. There is no source. Each internal face adds H to the left and subtracts H from the right, so global summation cancels interior contributions and leaves boundary flux. Second order computes gradients before the face loop; every dual-face segment conservatively scatters to the same adjacent volumes.

Ri(u,X,u∞)=∑fℬifHf,ℬLf=+1,  ℬRf=−1,R(u∗,X,u∞)=0.R_i(u,X,u_\infty)=\sum_f \mathcal B_{if}H_f,\quad \mathcal B_{Lf}=+1,\;\mathcal B_{Rf}=-1,\qquad R(u^*,X,u_\infty)=0.
grad = least_squares(u, coordinates)
R[:] = 0
for f in computational_faces:
    H = integrate_reconstructed_flux(f,grad)
    R[f.L] += H
    if f.R exists: R[f.R] -= H
# No special zero-flux branch on the inner boundary.
图 5.4Figure 5.4
第 6 节Section 6

求解:Jacobian 与 Jacobi 迭代

The solve: the Jacobian and Jacobi iteration

从残差到 Jacobian

From residual to Jacobian

第 5 节的 residual(u) 就是离散残差 R(u)∈ℝN,每个分量对应一个节点。它的 Jacobian 矩阵 A=∂R/∂u 是 N×N 的(算例中 24×24)。本节先把它显式写出来,看清它的结构;下面求解 R(u)=0 只用到它的对角元,第 8、9 节再讲怎样不形成 A 也能乘它。

The residual(u) of Section 5 is the discrete residual R(u)∈ℝN, with one component per node. Its Jacobian A=∂R/∂u is N×N (24×24 in the test case). This section writes it out explicitly, to show its structure; solving R(u)=0 below needs only its diagonal, and Sections 8 and 9 show how to multiply by A without ever forming it.

A 的每一份贡献都来自一个对偶面:对第 3 节那个一维通量求局部导数,

Every contribution to A comes from one dual face: differentiate the one-dimensional flux of Section 3 locally,

cL=∂h∂uL=12vnuL+ε|n|,cR=∂h∂uR=12vnuR−ε|n|.

再把这两个数按和残差完全相同的符号累加到 A 的四个位置:ALL+=cL、ALR+=cR、ARL−=cL、ARR−=cR。边界半面只有 L 一侧,所以只落在 ALL 上。

and accumulate those two numbers into four entries of A with exactly the signs the residual used: ALL+=cL, ALR+=cR, ARL−=cL, ARR−=cR. A boundary half-face has only an L side, so it lands on ALL alone.

出流半面的导数为什么会“合并”。出流时 uR=uL,同一个 uL 同时进入通量的两个槽位,链式法则要把两个局部导数相加,耗散项正好抵消:

Why the derivatives "merge" on an outflow half-face. At outflow uR=uL, so the same uL enters both slots of the flux; the chain rule adds the two local derivatives and the dissipation terms cancel exactly:

cL=(12vnuL+ε|n|)∂h/∂uL+(12vnuL−ε|n|)∂h/∂uR=vnuL.

于是边界半面对 A 的贡献全都只落在 ALL 上,边界条件决定的只是这一项的大小:无粘壁面 cL=cR=0;入流 uR=u∞ 是常数,cR=0,只剩 cL;出流按上式合并成 vnuL。图 6.1 在真实网格上对照壁面节点与外圈节点。

So a boundary half-face reaches A only through ALL, and the boundary condition decides only how large that entry is: an inviscid wall gives cL=cR=0; at inflow uR=u∞ is a constant, so cR=0 and only cL remains; at outflow the two merge into vnuL as above. Figure 6.1 compares a wall node with the outer nodes on the real mesh.

这也把第 3 节的一阶、无重构落到了实处:算例中 A 共 120 个非零元,没有一个落在相邻节点之外(每个节点有 3–5 个相邻节点)。

This also pins down the first-order, no-reconstruction claim of Section 3: in the test case A has 120 non-zeros and not one of them falls outside the neighbouring nodes (each node has 3–5 of them).

(a) 壁面节点 n1:每条面对 Jacobian 的贡献e1(内部) cL = 0.18 cR = −0.18e2(内部) cL = 0.11 cR = −0.3e3(内部) cL = 0.79 cR = −0.24e49(壁面半面) cL = 0 cR = 0e50(壁面半面) cL = 0 cR = 0壁面半面只负责封口:cL、cR 全是 0,对 A 没有任何贡献。(b) 外圈节点的远场半面:同一个节点的两条半面可以落在不同分支上e71 n20 vn = 0.06 出流 uR = 1.03 cL = 0.06e72 n20 vn = −0.53 入流 uR = 1 cL = 0.31e79 n24 vn = −0.06 入流 uR = 1 cL = 0.55e80 n24 vn = 0.53 出流 uR = 1.03 cL = 0.55入流 uR 是常数,cL = ½ vn uL + ε|n|;出流 uR = uL,两项合并成 cL = vn uL。(a) Wall node n1: what each of its faces contributes to the Jacobiane1 (interior) cL = 0.18 cR = −0.18e2 (interior) cL = 0.11 cR = −0.3e3 (interior) cL = 0.79 cR = −0.24e49 (wall half-face) cL = 0 cR = 0e50 (wall half-face) cL = 0 cR = 0A wall half-face only seals the volume: cL and cR are both zero, so it adds nothing to A.(b) Far-field half-faces of the outer nodes: a node's two may take different branchese71 n20 vn = 0.06 outflow uR = 1.03 cL = 0.06e72 n20 vn = −0.53 inflow uR = 1 cL = 0.31e79 n24 vn = −0.06 inflow uR = 1 cL = 0.55e80 n24 vn = 0.53 outflow uR = 1.03 cL = 0.55Inflow: uR is constant and cL = ½ vn uL + ε|n|. Outflow: uR = uL, and the two merge into cL = vn uL.
图 6.1 · 边界条件在 Jacobian 里的样子。(a) 壁面节点:两条壁面半面 cL=cR=0,对 A 没有任何贡献;内部对偶面照常贡献。(b) 外圈节点的远场半面:同一个节点的两条半面可以落在不同分支上——入流半面给 ALL 加 12vnuL+ε|n|,出流半面加合并后的 vnuL,两者累加到同一个对角元上。Figure 6.1 · What the boundary conditions look like in the Jacobian. (a) A wall node: its two wall half-faces have cL=cR=0 and add nothing to A, while the interior dual faces contribute as usual. (b) The far-field half-faces of the outer nodes: one node's two half-faces may take different branches — the inflow half-face adds 12vnuL+ε|n| to ALL, the outflow half-face adds the merged vnuL, and both accumulate into the same diagonal entry.

怎么把 R(u)=0 解出来

How R(u)=0 is actually solved

上面已经把 residual(u) 和它的 Jacobian 都造出来了,却还没有说这个非线性方程组是怎么解开的。本页的 primal 方程、切线方程和伴随方程(切线在第 10 节,伴随在第 11 节)用的是同一个 Jacobi 迭代:每一轮算一次残差,再逐节点除以 Jacobian 的对角元:

Both residual(u) and its Jacobian have now been built, but nothing has yet been said about how the nonlinear system is solved. On this page the primal equation, the tangent equation and the adjoint equation (Sections 10 and 11) are all solved by one and the same Jacobi iteration: every sweep evaluates the residual once and divides it, node by node, by the diagonal of the Jacobian:

uk+1=uk−diag(A(uk))−1R(uk)
A=∂R∂u

diag(A) 不需要把 A 造出来:diagonal(u) 就是残差的那个边循环,每个面只把 cL 加到左节点、把 −cR 加到右节点,也就是它在 A 里的四个元素中落在对角线上的那两个。所以一轮只有一次 residual 和一次 diagonal:不形成矩阵,也不解线性方程组,每个节点各自更新。初值取均匀来流 u0≡1,判据是残差的均方根 ‖R‖<10−13(图 6.2)。

diag(A) does not need A to be formed: diagonal(u) is the residual's own edge loop, in which each face adds only cL to its left node and −cR to its right node, the two of its four entries in A that land on the diagonal. So a sweep is one residual and one diagonal: no matrix is formed and no linear system is solved; every node is updated on its own. The initial guess is the uniform free stream u0≡1, and the criterion is the rms of the residual, ‖R‖<10−13 (Figure 6.2).

为什么用 Jacobi,而不用 LU。本页只有 24 个未知量,理论上最快的办法是直接分解:把 A 整个造出来,用 Newton 法,每一步做一次稠密 LU 分解,k=4 时残差就已低于 10−13。可那样就说明不了 matrix-free。Jacobi 是最简单的迭代法:对 A 它只要对角元,其余的一切都经由残差作用进来(切线方程里是 residual_d,伴随方程里是 residual_b),这正是 matrix-free 的意思。代价是线性收敛:在解附近,误差每一轮乘以迭代矩阵 I−diag(A)−1A,速率由它的谱半径决定,本算例是 0.797,判据在 k=122 时满足。切线与伴随的迭代矩阵与它同谱,所以进入渐近阶段以后,三条收敛曲线以同一个速率下降(第 10、11 节)。

Why Jacobi, and not LU. With only 24 unknowns the fastest method in principle is a direct factorisation: form the whole of A, run Newton, and factorise with one dense LU per step; the residual is below 10−13 by k=4. But that would show nothing about matrix-free. Jacobi is the simplest iterative method there is: of A it needs only the diagonal, and everything else comes in through the residual (through residual_d in the tangent equation and residual_b in the adjoint equation), which is exactly what matrix-free means. The price is linear convergence: near the solution the error is multiplied every sweep by the iteration matrix I−diag(A)−1A, at a rate set by its spectral radius, 0.797 here, and the criterion is met at k=122. The tangent and adjoint iteration matrices have the same spectrum, so once in the asymptotic phase all three convergence curves fall at the same rate (Sections 10 and 11).

与一维 Burgers 页面的区别。一维 Burgers 页面用二阶重构,用 Gauss–Seidel 迭代和 GS 预处理的 GMRES 求解(GMRES 在那里的第 17 节讲)。本页退回一阶和 Jacobi,是为了让二维的边循环保持最小:一阶时每个面只连接两个节点,Jacobian 的每一行只有它自己和它的邻居;Jacobi 的每一轮只调用一次残差和一次对角元,所有节点各自更新,结果与编号顺序无关。代价是收敛慢(上面的谱半径),本页的网格很小,付得起。各节末尾的“新增”扩展(选读)再回到二阶重构与 GMRES。

How this differs from the 1-D Burgers pages. The 1-D Burgers pages use second-order reconstruction and solve with Gauss–Seidel iteration and GS-preconditioned GMRES (GMRES is taught in their Section 17). This page steps back to first order and Jacobi to keep the two-dimensional edge loop at its smallest: at first order every face couples just two nodes, so a row of the Jacobian holds only the unknown itself and its neighbours; and a Jacobi sweep is one residual and one diagonal, every node updated on its own, with a result that does not depend on the numbering. The price is slow convergence (the spectral radius above), affordable on a mesh this small. The optional “Added” extension at the end of each section returns to second-order reconstruction and GMRES.

固定轮次,但会检查。代码固定跑 300 轮,而不是一满足判据就退出:余下的轮次把 u* 一直带到舍入误差平台(从 k=153 起),页面上每一个实测常数都是在这些轮次下量出来的;得到的 u* 与 Newton 法的解最多差 4.4×10−16。真正要紧的是有没有检查:它记录判据在第几轮满足,若 300 轮内从未满足就直接报错,而不会把一个未收敛的状态当成 u* 返回。

A fixed sweep count, but a checked one. The code runs a fixed 300 sweeps rather than exiting at the criterion: the remaining sweeps take u* down to its round-off floor (from k=153 on), and every measured constant on this page was produced under those sweeps; the u* it returns is within 4.4×10−16 of Newton's. What actually matters is whether it is checked: it records the sweep at which the criterion was met, and raises an error if 300 sweeps never meet it, rather than returning an unconverged state as u*.

图 6.2Figure 6.2
演示:primal 求解(Jacobi 迭代)
Demo: the primal solve (Jacobi iteration)
网格上是当前轮次的 u;r、diag(A) 与 Δu 在下方数组里逐控制体给出the mesh shows u at the current sweep; r, diag(A) and Δu are given per control volume in the arrays below
一轮之内的三步:1 = 残差 r,2 = 对角元 diag(A),3 = 逐节点更新 u ← u − r / diag(A)
The three steps within one sweep: 1 = the residual r, 2 = the diagonal diag(A), 3 = the update u ← u − r / diag(A), node by node
Jacobi 轮次 k
Jacobi sweep k

算例状态

State of the test case

用 Jacobi 迭代求解 R(u)=0 得到收敛解 u*:

The Jacobi iteration solves R(u)=0 for the converged state u*:

此后 u* 冻结:前向与伴随都是在这个状态上的线性化。图 5.2 播放到最后,就能看到残差逐面累加、最终抵消为零的过程。

From here on u* is frozen: the forward and adjoint modes are both linearisations about this state. Play Figure 5.2 to the end to watch the residual accumulate face by face and finally cancel to zero.

新增(选读):光滑特征边界模型
Added (optional): smooth characteristic-boundary model

Jacobian 与原始求解

Jacobian and primal solution

一阶保留原来的 Jacobi 形式 u←u−diag(A)⁻¹R,diag(A) 是一阶残差 Jacobian 的对角。边界数据与主线不同,收敛流场也就不同,主线给出的常数并不适用于新增模型。二阶精确 Jacobian 包含重构邻点,不能只沿用原来的两个面邻点导数。

First order retains u←u−diag(A)⁻¹R with the first-order residual Jacobian diagonal. The boundary data differ from the main line’s, so the converged field differs too, and the main line’s quoted numbers do not carry over. The second-order exact Jacobian includes reconstruction neighbours; the original two face-state derivatives alone are insufficient.

Newton 法、GMRES 与预处理,用最少的话说。二阶残差 R(u) 仍是非线性的。Newton 法在当前 u 处把 R 线性化:解线性方程组 AΔu=−R 得到修正 Δu,再令 u←u+αΔu;α 从 1 开始,只要新状态不全为正、或真残差没有下降,就把 α 减半(回溯)。线性方程组用 GMRES 解:它每一步只要一次乘积 Av(与主线第 8 节 residual_d 同一类的前向运算),在已经得到的方向张成的子空间里找使残差最小的组合,所以同样不必组装 A。预处理 B 先把每个新方向“近似地解一下”:这里是用一阶近似 Jacobian P 做三次 Gauss–Seidel 扫描,GMRES 实际处理的是 AB,好的 B 让 GMRES 用少得多的方向就收敛。GMRES 的逐步讲解见一维 Burgers 页面第 17 节。

Newton, GMRES and preconditioning, in a few words. The second-order residual R(u) is still nonlinear. Newton’s method linearises R at the current u: it solves the linear system AΔu=−R for a correction Δu and sets u←u+αΔu, where α starts at 1 and is halved (backtracking) as long as the new state is not all positive or the true residual has not decreased. The linear system is solved by GMRES, which needs one product Av per step (a forward operation of the same kind as residual_d in Section 8 of the main line) and picks, in the span of the directions produced so far, the combination with the smallest residual; so A is never assembled here either. A preconditioner B first solves each new direction approximately — here three Gauss–Seidel sweeps with the first-order approximate Jacobian P — so GMRES works with AB, and a good B lets it converge with far fewer directions. GMRES is taught step by step in Section 17 of the 1-D Burgers page.

二阶使用带回溯的 Newton 修正,线性系统由 GS 预处理 GMRES 求解。预处理仅保留一阶近似,执行三次 GS 扫描;外层乘积始终使用完整二阶残差的精确前向算子。回溯检查未知量和面重构状态为正,并要求真残差下降。精确算子没有组装成全局矩阵。

Second order uses backtracked Newton corrections solved by GS-preconditioned GMRES. Three GS sweeps of a first-order approximation form the preconditioner; outer products always use the exact derivative of the second-order residual. Backtracking requires positive unknowns and face traces and a decrease in the true residual. The exact global matrix is not assembled.

AΔu=−R,A=Ru,unew=u+αΔu,P≈A from first order.A\Delta u=-R,\quad A=R_u,\quad u^{new}=u+\alpha\Delta u,\quad P\approx A\text{ from first order}.

先看一阶的一次 Jacobi:所有分量都从同一轮旧状态计算 R 与 diag(A),然后同时更新。它不同于立即覆盖分量的 GS。对内部面,两个状态导数为 c_L 和 c_R;左侧对角加 c_L,右侧对角减 c_R。入流外侧 u_in 与状态无关;出流 u⁺=u⁻,必须把 c_L+c_R 合并到同一个对角项。

A first-order Jacobi sweep evaluates R and diag(A) from the same old state, then updates all components simultaneously. GS instead immediately reuses updated components. For an interior face, add c_L to the left diagonal and −c_R to the right diagonal. Inflow u_in is independent of the state; outflow has u⁺=u⁻, so c_L+c_R belongs on a single diagonal entry.

uik+1=uik−Ri(uk)Aii(uk),zim+1=bi−∑j<iPijzjm+1−∑j>iPijzjmPii.u_i^{k+1}=u_i^k-\frac{R_i(u^k)}{A_{ii}(u^k)},\qquad z_i^{m+1}=\frac{b_i-\sum_{j<i}P_{ij}z_j^{m+1}-\sum_{j>i}P_{ij}z_j^m}{P_{ii}}.

二阶中,P 是按一阶残差组装的稀疏近似。每次预处理从 z=0 开始做三次 GS,形成一个固定线性映射 B;GMRES 对 A B 构造 Krylov 子空间,得到修正 Δu=B y。A v 由完整二阶前向计算给出。即使 P 忽略重构邻点,收敛时检查的仍是完整系统 AΔu+R,而不是近似系统 PΔu+R。

For second order, P is the sparse first-order approximate Jacobian. Each preconditioner application starts at z=0 and performs three GS sweeps, defining a fixed linear map B. GMRES builds its Krylov space for A B and forms Δu=B y. Exact A v comes from the full second-order tangent. Although P omits reconstruction neighbours, convergence checks AΔu+R, not PΔu+R.

if order == 1:
    R, d = residual_and_diagonal(u_old)
    u_new[:] = u_old[:] - R[:] / d[:]
else:
    A(v) = exact_residual_jvp(u,v)
    B(r) = three_GS_sweeps(first_order_P,r,start=0)
    du = right_preconditioned_GMRES(A,-R,B)
    alpha = backtrack_until_positive_and_residual_decreases()
    u_new = u_old + alpha*du
图 6.3Figure 6.3
第 7 节Section 7

目标函数

The objective function

类似阻力的表面积分

A drag-like surface integral

取类似阻力的表面积分作为目标函数 L:把物面上的“压力” 12u2 投影到来流方向再沿物面积分——它与气动优化里的阻力形式相同:

Take a drag-like surface integral as the objective L: project the "pressure" 12u2 on the body onto the free-stream direction and integrate along the surface — the same form as drag in aerodynamic optimisation:

L=Σ壁面12uL2(n·e),e=β|β|=(0.894, 0.447).
L=Σwall12uL2(n·e),e=β|β|=(0.894, 0.447).

这里的物面是无粘壁面。L 只依赖壁面上的状态,所以 g=(∂L/∂u)T 只在壁面节点上非零(1–8 号)。后面会看到,伴随解 ψ 却遍布整个流场:物面上的量通过一连串面通量受到上游的影响。

The body here is an inviscid wall. L depends only on the states at the wall, so g=(∂L/∂u)T is non-zero only on the wall nodes themselves, nodes 1–8. As will be seen, the adjoint solution ψ nevertheless spreads over the whole field: quantities on the body are influenced from upstream through a chain of face fluxes.

为什么要投影到来流方向。n·e 是有正负的:迎风面与背风面的贡献方向相反。因为物体封闭,Σ壁面n=0,所以均匀流场给出的 L 恒等于零——它完全来自物面上 u 的不均匀分布。这正是它比“载荷大小之和 Σ12u2|n|”更像真实气动量的地方。图 7.1 把每个壁面半面的贡献和它们的总和画了出来。

Why project onto the free-stream direction. n·e has a sign: the windward and leeward contributions point opposite ways. Because the body is closed, Σwalln=0, so a uniform field gives L≡0 — it comes entirely from the non-uniform distribution of u over the body. That is exactly what makes it more like a real aerodynamic quantity than "the sum of load magnitudes Σ12u2|n|". Figure 7.1 draws the contribution of every wall half-face and their sum.

要记住 L 是什么。L 是这个离散问题的一个泛函,形式上像阻力。但本页的零通量壁面没有连续极限(第 4 节),L 也就不收敛到任何物理阻力:第 15 节实测,网格每加密一层,Lh 大约翻一倍(0.693、1.524)。第 8–14 节算的是这个固定离散 L 的精确梯度,这一点不受影响。

Keep in mind what L is. L is a functional of this discrete problem, drag-like in form. But the zero-flux wall has no continuum limit (Section 4), so L does not converge to any physical drag: Section 15 measures Lh roughly doubling with every refinement (0.693, 1.524). What Sections 8–14 compute is the exact gradient of this fixed discrete L, and that is not affected.

(a) 物面上每个半面的贡献 ½u²(n·e)12345678e = β/|β|来流方向琥珀色是壁面;数字是节点号;深色箭头为迎风(n·e > 0),浅色为背风(n·e < 0)。每条壁面边被两端节点各分一半,共 16 个半面。(b) 16 个半面的贡献加起来就是 L(数值只标在奇数号半面上)−0.084e49−0.087e51−0.089e530.068e550.097e570.199e590.177e610.023e63L = 0.6932合计正负部分相互抵消了一部分,剩下的净值就是 L。若把 u 换成均匀值,它们会恰好抵消:实测 0.0e+0。所以 L 完全来自物面上 u 的不均匀分布。(a) Contribution ½u²(n·e) of each body half-face12345678e = β/|β|free-stream directionAmber = wall; numbers are node indices; dark arrows windward (n·e > 0), light ones leeward (n·e < 0).Each wall edge is halved between its two end nodes: 16 half-faces in all.(b) The 16 half-face contributions add up to L (values on the odd-numbered ones only)−0.084e49−0.087e51−0.089e530.068e550.097e570.199e590.177e610.023e63L = 0.6932totalPositive and negative parts partly cancel, leaving L; uniform u cancels exactly: measured 0.0e+0.So L comes entirely from non-uniform u on the body.
图 7.1 · 目标函数 L 的构成:上图是物面上每个半面的贡献 12uL2(n·e)——法向与来流同向的迎风一侧为正,背风一侧为负;下图把这些贡献画成柱状图,正负相互抵消了一部分,剩下的净值就是 L。把 u 换成均匀值,它们会精确抵消为零。Figure 7.1 · How the objective L is made up: above, the contribution 12uL2(n·e) of each half-face on the body — positive on the windward side, where the normal runs with the free stream, and negative on the leeward side; below, the same contributions as a bar chart, where the positive and negative parts partly cancel and what is left over is L. Replace u by a uniform value and they cancel exactly to zero.

逐步推导:g=(∂L/∂u)Tg=(\partial L/\partial u)^T

Step by step: g=(∂L/∂u)Tg=(\partial L/\partial u)^T

第一步,LL 是壁面半面上的和,每一项只含该半面左节点的值:

Step 1. LL is a sum over the wall half-faces, and each term contains only the value of that half-face’s left node:

L=∑f∈wall12uL(f)2 (𝒏f⋅𝒆).L=\sum_{f\in\mathrm{wall}}\tfrac12u_{L(f)}^2\,(\boldsymbol n_f\cdot\boldsymbol e).

第二步,对 uiu_i 求偏导。𝒏f\boldsymbol n_f 与 𝒆\boldsymbol e 都不含 uu,只有 L(f)=iL(f)=i 的项留下,平方项的导数是 uiu_i:

Step 2. Differentiate with respect to uiu_i. Neither 𝒏f\boldsymbol n_f nor 𝒆\boldsymbol e contains uu; only the terms with L(f)=iL(f)=i survive, and the square differentiates to uiu_i:

gi=∂L∂ui=∑f∈wall,  L(f)=iui (𝒏f⋅𝒆).g_i=\frac{\partial L}{\partial u_i}=\sum_{f\in\mathrm{wall},\;L(f)=i}u_i\,(\boldsymbol n_f\cdot\boldsymbol e).

第三步,每个壁面节点是两个壁面半面(两侧壁面边各一半)的 LL,所以 gi=ui 𝒆⋅(𝒏f+𝒏f′)g_i=u_i\,\boldsymbol e\cdot(\boldsymbol n_f+\boldsymbol n_{f'})(ff、f′f' 是这两个半面);其余 16 个节点 gi=0g_i=0。这就是 core_node.js 的 dJdu:g[f.L − 1] += u[f.L − 1] · (n·e),对每个壁面半面执行一次。

Step 3. Every wall node is the LL of two wall half-faces (half of each neighbouring wall edge), so gi=ui 𝒆⋅(𝒏f+𝒏f′)g_i=u_i\,\boldsymbol e\cdot(\boldsymbol n_f+\boldsymbol n_{f'}) with ff, f′f' these two half-faces, and gi=0g_i=0 in the other 16 nodes. This is dJdu in core_node.js: g[f.L − 1] += u[f.L − 1] · (n·e), once per wall half-face.

第四步,u>0u>0,所以 gig_i 的符号就是 𝒆⋅(𝒏f+𝒏f′)\boldsymbol e\cdot(\boldsymbol n_f+\boldsymbol n_{f'}) 的符号:迎风一侧为正,背风一侧为负;在迎风转背风的节点(3、8),两个半面的 𝒏⋅𝒆\boldsymbol n\cdot\boldsymbol e 异号,由两者之和决定。它的分布画在第 11 节(图 11.2)。

Step 4. Since u>0u>0, the sign of gig_i is that of 𝒆⋅(𝒏f+𝒏f′)\boldsymbol e\cdot(\boldsymbol n_f+\boldsymbol n_{f'}): positive on the windward side, negative on the leeward side; where the wall turns from windward to leeward (nodes 3 and 8) the two half-faces differ in the sign of 𝒏⋅𝒆\boldsymbol n\cdot\boldsymbol e and their sum decides. Its distribution is drawn in Section 11 (Figure 11.2).

核对:gg 有 8 个非零元;与逐个 uiu_i 作中心差分(步长 10−610^{-6})的 LL 相比,最大差 8.4×10−11(本页代码在构建时计算)。

Check: gg has 8 non-zeros; against central differences of LL in each uiu_i (step 10−610^{-6}) the largest difference is 8.4×10−11 (computed by this page’s code at build time).

从离散目标得到伴随右端

From a discrete objective to the adjoint right-hand side

不能只对连续积分写一个形式导数就结束。先确定代码实际使用的求积点、权重、重构状态及边界值,再对这个标量表达式求导。一般的求积和及其链式法则是:

A formal derivative of a continuous integral is not enough. First identify the quadrature points, weights, reconstructed states and boundary values actually used by the code, then differentiate that scalar expression. A general quadrature sum and its chain rule are:

L=∑qωq(D) θq(𝐮,D),L=\sum_q\omega_q(D)\,\theta_q(\mathbf u,D),
L˙=∑q[ωq θ˙q+θq ω˙q].\dot L=\sum_q\left[\omega_q\,\dot\theta_q+\theta_q\,\dot\omega_q\right].

本页的 L=∑f∈wall12uL(f)2 (𝒏f⋅𝒆)L=\sum_{f\in\mathrm{wall}}\tfrac12u_{L(f)}^2\,(\boldsymbol n_f\cdot\boldsymbol e) 是这种加权和:每个壁面半面一项(每条壁面边被两端节点各分一半,共 16 个半面),ωf=1\omega_f=1,θf=12uL(f)2(𝒏f⋅𝒆)\theta_f=\tfrac12u_{L(f)}^2(\boldsymbol n_f\cdot\boldsymbol e);面法向随节点坐标变化,所以 θf\theta_f 同时依赖状态与设计变量。

The L=∑f∈wall12uL(f)2 (𝒏f⋅𝒆)L=\sum_{f\in\mathrm{wall}}\tfrac12u_{L(f)}^2\,(\boldsymbol n_f\cdot\boldsymbol e) of this page is such a weighted sum: one term per wall half-face (each wall edge is halved between its two end nodes, 16 half-faces in all), ωf=1\omega_f=1 and θf=12uL(f)2(𝒏f⋅𝒆)\theta_f=\tfrac12u_{L(f)}^2(\boldsymbol n_f\cdot\boldsymbol e); the face normal moves with the node coordinates, so θf\theta_f depends on both the state and the design variables.

求 gg 时固定所有设计变量;求 ∇DL\nabla_DL 时固定全部离散状态槽位。固定解析目标函数并不一定意味着移动网格上的目标采样值不变:如果采样点随设计移动,还要保留该位置依赖;固定网格及固定采样值时,相应项才为零。目标中的体积权重已经进入 gg,不应在伴随右端再乘一次体积。

Hold every design variable fixed for gg, and every stored state entry fixed for ∇DL\nabla_DL. A fixed analytic target function need not have fixed samples on a moving mesh: if the evaluation locations move with the design, keep that dependence; the corresponding terms vanish only when the mesh and samples are fixed. Objective volume weights already enter gg and must not be multiplied into the adjoint right-hand side again.

伴随右端本身也可以由一次反向扫描得到:以 Lˉ=1\bar L=1 为种子,把上面的求积和逐项反过来执行,

The adjoint right-hand side is itself obtained by a reverse sweep: seed Lˉ=1\bar L=1 and run the terms of the quadrature sum above backwards,

Lˉ=1,θˉq=ωq Lˉ,𝐮ˉ+=(∂θq∂𝐮)Tθˉq,\bar{L}=1,\qquad \bar\theta_q=\omega_q\,\bar{L},\qquad \bar{\mathbf u}\mathrel{\text{+=}}\Big(\frac{\partial \theta_q}{\partial \mathbf u}\Big)^T\bar\theta_q,
Dˉ+=(∂θq∂D)Tθˉq+θq(∂ωq∂D)TLˉ.\bar D\mathrel{\text{+=}}\Big(\frac{\partial \theta_q}{\partial D}\Big)^T\bar\theta_q+\theta_q\Big(\frac{\partial\omega_q}{\partial D}\Big)^T\bar{L}.

从 𝐮ˉ←0\bar{\mathbf u}\leftarrow0、Dˉ←0\bar D\leftarrow0 开始,扫完全部求积点后 𝐮ˉ=g\bar{\mathbf u}=g、Dˉ=∇DL\bar D=\nabla_DL:一个状态被多个求积点读取,所以要先置零、再累加。前向的 L˙\dot L 与反向的这两个结果满足同一个对偶关系 Lˉ L˙=𝐮ˉT𝐮˙+DˉTD˙\bar L\,\dot L=\bar{\mathbf u}^T\dot{\mathbf u}+\bar D^T\dot D。

Starting from 𝐮ˉ←0\bar{\mathbf u}\leftarrow0 and Dˉ←0\bar D\leftarrow0, after all quadrature points 𝐮ˉ=g\bar{\mathbf u}=g and Dˉ=∇DL\bar D=\nabla_DL: a state is read by several quadrature points, so its adjoint is zeroed first and then accumulated. The forward L˙\dot L and these two reverse results satisfy one duality, Lˉ L˙=𝐮ˉT𝐮˙+DˉTD˙\bar L\,\dot L=\bar{\mathbf u}^T\dot{\mathbf u}+\bar D^T\dot D.

新增(选读):光滑特征边界模型
Added (optional): smooth characteristic-boundary model

目标函数:内边界上的数学泛函

Objective: a mathematical inner-boundary functional

保留原来的有方向内边界积分形式,但不再解释为不可穿透物面的真实受力。令 e=β/|β|,目标 L 是内边界上 u²/2 的投影积分。离散目标 L_h 用边界状态 u⁺:入流面上是给定的 u_in,出流面上 u⁺=u⁻(二阶时是重构的面迹)。一阶取控制体值,二阶用同样的 Gauss 积分,目标的导数也要穿过重构。这与附录 B 的连续推导一致:给定入流数据时,入流边界上的状态不随设计改变(附录中的变分在那里为零),目标在那里不依赖状态。

Keep the original direction-projected inner-boundary integral, but no longer interpret it as a physical force on an impermeable body. With e=β/|β|, the objective L integrates u²/2 against e·n over the inner boundary. The discrete objective L_h uses the boundary state u⁺: the prescribed u_in on inflow faces and u⁺=u⁻ on outflow faces (the reconstructed trace at second order). First order takes the control-volume value, second order the same Gauss quadrature, and the derivatives of the objective pass through the reconstruction too. This matches the continuous derivation of Appendix B: with the inflow data fixed, the state on the inflow boundary does not change with the design (the appendix’s variation vanishes there), so the objective does not depend on the state there.

L=∫Γinner12u2𝒆⋅𝒏 ds,Lh=∑f∈Γinner∑qwq2(uf,q+)2𝒆⋅𝒏f,uf,q+={uin(xf,q),𝜷⋅𝒏f<0,uf,q−,𝜷⋅𝒏f≥0.L=\int_{\Gamma_{inner}}\frac12u^2\boldsymbol e\cdot\boldsymbol n\,ds,\qquad L_h=\sum_{f\in\Gamma_{inner}}\sum_q\frac{w_q}{2}(u_{f,q}^+)^2\boldsymbol e\cdot\boldsymbol n_f,\qquad u^+_{f,q}=\begin{cases}u_{\rm in}(x_{f,q}),&\boldsymbol\beta\cdot\boldsymbol n_f<0,\\u^-_{f,q},&\boldsymbol\beta\cdot\boldsymbol n_f\ge0.\end{cases}

为什么入流面上要用 u⁺。本页之前的版本在入流面上也用内侧迹 u⁻。那样离散目标在入流边界上依赖状态,连续目标却不依赖,离散伴随就在内边界上只连着入流半面的节点(入流与出流交界处的两个特征角点不计,见附录 B)上多出一个不随加密消失的误差。实测一阶第 0–4 层,这些节点上的 max|ψ_h−ψ_ref| 用 u⁻ 时是 0.412、0.559、0.656、0.710、0.738,越加密越大;改用 u⁺ 后是 0.017、0.009、0.008、0.007、0.007(附录 B 的表)。

Why u⁺ on the inflow faces. An earlier version of this page used the interior trace u⁻ on inflow faces too. Then the discrete objective depends on the state on the inflow boundary while the continuous one does not, and the discrete adjoint picks up an error on the inner-boundary nodes whose half-faces are all inflow (the two characteristic corners between inflow and outflow are left out; see Appendix B) that refinement never removes. Measured at first order on levels 0–4: on those nodes, max|ψ_h−ψ_ref| is 0.412, 0.559, 0.656, 0.710, 0.738 with u⁻, growing with refinement, and 0.017, 0.009, 0.008, 0.007, 0.007 with u⁺ (the table in Appendix B).

由于解析场在孔洞内部也满足散度为零,封闭内边界上的积分恰好为零:连续的 L 与它对全部设计变量的导数 dL/dD 都是零。粗网格上的 L_h 与 dL/dD 一般不为零,它们就是离散误差,加密时应当趋于零。附录 B 的表逐层给出实测。二阶第 0–4 层:L_h 为 1.74×10⁻²、3.30×10⁻²、1.60×10⁻²、2.93×10⁻³、4.68×10⁻⁴,dL/du∞ 为 1.16×10⁻²、2.74×10⁻²、8.41×10⁻³、1.61×10⁻⁴、4.10×10⁻⁵,48 个基准网格坐标上的 max|dL/dX| 为 1.68×10⁻¹、1.42×10⁻¹、5.56×10⁻²、1.65×10⁻²、4.42×10⁻³。一阶趋零慢得多:L_h 为 3.38×10⁻²、4.38×10⁻²、4.33×10⁻²、3.85×10⁻²、2.90×10⁻²,max|dL/dX| 为 1.71×10⁻¹、1.86×10⁻¹、1.79×10⁻¹、1.50×10⁻¹、1.07×10⁻¹。这一退化的连续目标不是优化算例的有效性能指标;在这里用来检验实际的离散求导链,以及它在加密时是否趋向连续值。

The analytic field is divergence-free even inside the hole, so the closed inner-boundary integral is exactly zero: the continuous L and its derivative dL/dD with respect to every design variable vanish. On a coarse mesh L_h and dL/dD generally do not; they are discretisation errors and should vanish under refinement. The table in Appendix B gives the measurements level by level. Second order, levels 0–4: L_h is 1.74×10⁻², 3.30×10⁻², 1.60×10⁻², 2.93×10⁻³, 4.68×10⁻⁴, dL/du∞ is 1.16×10⁻², 2.74×10⁻², 8.41×10⁻³, 1.61×10⁻⁴, 4.10×10⁻⁵, and max|dL/dX| over the 48 coordinates of the base mesh is 1.68×10⁻¹, 1.42×10⁻¹, 5.56×10⁻², 1.65×10⁻², 4.42×10⁻³. First order goes to zero much more slowly: L_h is 3.38×10⁻², 4.38×10⁻², 4.33×10⁻², 3.85×10⁻², 2.90×10⁻² and max|dL/dX| is 1.71×10⁻¹, 1.86×10⁻¹, 1.79×10⁻¹, 1.50×10⁻¹, 1.07×10⁻¹. This degenerate continuous objective is not a useful optimisation performance metric; here it tests the actual discrete differentiation chain, and whether it approaches the continuous values under refinement.

图 7.2Figure 7.2
第 8 节Section 8

matrix-free 前向:计算 Av

Matrix-free forward mode: computing Av

为什么需要 ATw:梯度

Why ATw is needed: the gradient

设计变量 X∈ℝM 取网格坐标(本页取全部节点坐标,外形由其中壁面节点那部分决定)。对 R(u(X),X)=0 用链式法则得 Adu/dX=−∂R/∂X,于是

The design variables X∈ℝM are the mesh coordinates (all node coordinates on this page; the shape is the wall node part of them). Applying the chain rule to R(u(X),X)=0 gives Adu/dX=−∂R/∂X, hence

dLdX=∂L∂X−∂L∂u1×NA−1N×N∂R∂XN×M.

先算右边的乘积,要对每个设计变量各解一次线性方程组(M 次);先算左边的乘积,只需解一次伴随方程 ATψ=−(∂L/∂u)T,然后

Evaluate the right-hand pair first and one linear system has to be solved per design variable (M of them); evaluate the left-hand pair first and only a single adjoint equation ATψ=−(∂L/∂u)T is needed, after which

dLdX=∂L∂X+ψT∂R∂X.

形状优化中 M 常达数千,而伴随的求解次数始终是 1(每个目标函数一次)。

In shape optimisation M often runs to thousands, while the adjoint always needs exactly one solve (one per objective).

把这条乘积链画出来就一目了然(图 8.1):同一串矩阵,只是加括号的方式不同。先乘右边那一对,得到的中间量有 M 列,于是要解 M 次;先乘左边那一对,得到的中间量只有一行,于是只解一次——这一行就是 −ψT。

Drawing the product chain makes it obvious (Figure 8.1): the same string of matrices, bracketed two different ways. Multiply the right-hand pair first and the intermediate has M columns, so it costs M solves; multiply the left-hand pair first and the intermediate is a single row, so it costs one — and that row is −ψT.

(a) 同一条乘积链,两种加括号的方式dL/dX(1×48)=∂L/∂X(1×48)−∂L/∂u(1×24)A-1(24×24)∂R/∂X(24×48)φ = −A-1 ∂R/∂X (24×48)前向:每个设计变量解一次 → 48 次ψT = −(∂L/∂u) A-1 (1×24)伴随:每个目标函数解一次 → 1 次(b) 右端有几列,就要解几次Aφ=−∂R/∂X右端 48 列 → 解 48 次ATψ=−(∂L/∂u)T右端 1 列 → 解 1 次算例:N = 24 个未知量,M = 48 个设计变量(每个节点两个坐标),一个目标函数 L。(a) The same product chain, bracketed two waysdL/dX(1×48)=∂L/∂X(1×48)−∂L/∂u(1×24)A-1(24×24)∂R/∂X(24×48)φ = −A-1 ∂R/∂X (24×48)Forward: one solve per design variable → 48 solvesψT = −(∂L/∂u) A-1 (1×24)Adjoint: one solve per objective → 1 solve(b) As many solves as there are columns on the rightAφ=−∂R/∂X48 columns on the right → 48 solvesATψ=−(∂L/∂u)T1 column on the right → 1 solveTest case: N = 24 unknowns, M = 48 design variables (two coordinates per node), and one objective L.
图 8.1 · 同一条乘积链的两种加括号方式。(a) 上方的括号:先算 A−1∂R/∂X(前向/直接法),中间结果是 N×M,要对每个设计变量解一次线性方程组;(a) 下方的括号:先算 (∂L/∂u)A−1(伴随),中间结果是 1×N,只需解一次。两条路给出同一个 dL/dX。(b) 把这两个线性方程组上下画出来:未知量与右端有几列,就要解几次——算例中是 M=48 次对 1 次。Figure 8.1 · The same product chain, bracketed two ways. (a), upper bracket: evaluate A−1∂R/∂X first (the forward, or direct, method); the intermediate is N×M and one linear system must be solved per design variable. (a), lower bracket: evaluate (∂L/∂u)A−1 first (the adjoint method); the intermediate is 1×N and a single solve suffices. Both routes give the same dL/dX. (b) draws the two linear systems one above the other: however many columns the unknown and the right-hand side have is how many solves it costs — M=48 against 1 in this test case.

“伴随”就是转置

"Adjoint" means transpose

对任意矩阵 A 和向量 v,w,恒有

For any matrix A and vectors v,w, it always holds that

⟨w,Av⟩=wTAv=(ATw)Tv=⟨ATw,v⟩.

满足这一关系的算子称为伴随算子,对实矩阵就是转置。前向乘积把“状态上的扰动”映射成“残差的变化”;伴随乘积方向相反,把“残差上的权重”映射成“状态上的敏感度”。

An operator satisfying this relation is called the adjoint; for a real matrix it is the transpose. The forward product maps "a perturbation of the state" to "the change in the residual"; the adjoint product runs the other way, mapping "a weight on the residual" to "a sensitivity on the state".

什么是 matrix-free

What matrix-free means

第 8、9 两节要回答的就是一个问题:怎样在不形成矩阵的情况下计算这两个乘积。

Sections 8 and 9 answer a single question: how to compute these two products without forming the matrix.

v↦Av前向乘积w↦ATw伴随乘积
v↦Avforward productw↦ATwadjoint product

实际网格的 N 可达 105–108。GMRES 等 Krylov 迭代解法从不读取单个矩阵元素,只需要“给一个向量、返回乘积”的函数(Jacobi 另外只需要对角元)。matrix-free 就是不形成、不存储 A,直接写函数 residual_d(u, v) 返回 Av、residual_b(u, w) 返回 ATw。

On a real mesh N can reach 105–108. Krylov solvers such as GMRES never read an individual matrix entry; all they need is a function that takes a vector and returns the product (Jacobi needs only the diagonal besides). matrix-free means exactly that: never form or store A, and instead write residual_d(u, v) returning Av and residual_b(u, w) returning ATw.

记号。从这里起,前向与伴随都用自动微分的记号:程序变量 x 的前向导数(切线)记作 ẋ,反向导数(伴随)记作 x̄。前向乘积的种子是 u̇=v,结果是 Ṙ=Av;伴随乘积的种子是 R̄=w,结果是 ū=ATw。两者满足对偶关系 R̄TṘ=ūTu̇,也就是上面的 ⟨w,Av⟩=⟨ATw,v⟩。代码沿用 Tapenade 的命名:x_d 就是 ẋ,x_b 就是 x̄(residual_d、flux_d、h_d;residual_b、h_b、uL_b)。变分符号 δ 只出现在附录的连续伴随里,表示连续函数的变分;正文里的点和横线是对离散代码的前向与反向求导。

Notation. From here on the forward and the adjoint mode are written in the notation of algorithmic differentiation: the forward derivative (tangent) of a program variable x is ẋ, its reverse derivative (adjoint) x̄. The forward product has the seed u̇=v and the result Ṙ=Av; the adjoint product has the seed R̄=w and the result ū=ATw. The two satisfy the duality R̄TṘ=ūTu̇, which is ⟨w,Av⟩=⟨ATw,v⟩ above. The code follows Tapenade's names: x_d is ẋ and x_b is x̄ (residual_d, flux_d, h_d; residual_b, h_b, uL_b). The variation symbol δ appears only in the continuous adjoint of the appendices, as the variation of a continuous function; the dot and the bar in the main text are forward- and reverse-mode derivatives of the discrete code.

前向容易,伴随难。Av 可以用一次扰动近似(η 是一个小步长):Av≈[R(u+ηv)−R(u)]/η。ATw 没有一次扰动的公式。它的第 j 个分量是 (ATw)j=wT(Aej)≈wT[R(u+ηej)−R(u)]/η:要对每个未知量各扰动一次,一共 N 次残差计算,才得到整个 ATw。实际网格的 N 太大,这条路走不通,所以 ATw 要靠把残差计算反过来求导:第 9 节的边循环只走一遍。

Forward is easy, adjoint is not. Av can be approximated with a single perturbation (η a small step): Av≈[R(u+ηv)−R(u)]/η. ATw has no single-perturbation formula. Its j-th component is (ATw)j=wT(Aej)≈wT[R(u+ηej)−R(u)]/η: one perturbation per unknown, N residual evaluations in all, give the whole of ATw. On a real mesh N is far too large for that, so ATw is obtained by differentiating the residual computation in reverse: the edge loop of Section 9, run once.

对一维通量求导

Differentiating the one-dimensional flux

h=H(uL,uR) 只有两个输入。两侧状态的切线(微小扰动)为 u̇L、u̇R 时,由链式法则

h=H(uL,uR) has only two inputs. When the two states carry tangents (small perturbations) u̇L and u̇R, the chain rule gives

ḣ=cLu̇L+cRu̇R,cL=12vnuL+ε|n|,cR=12vnuR−ε|n|.

cL、cR 是局部导数,在冻结状态 u* 处取值。下表四种面的差别只体现在这两个系数上:

cL and cR are the local derivatives, evaluated at the frozen state u*. The four kinds of face below differ only in these two coefficients:

面的类型Face type右状态 uRRight state uR切线 u̇RTangent u̇RcLcR
内部对偶面Interior dual faceu[j]v[j]12vnuL+ε|n|12vnuR−ε|n|
无粘壁面半面Inviscid wall half-face—(h≡0)— (h≡0)—00
远场半面(入流)Far-field half-face (inflow)u∞(常数)u∞ (constant)012vnuL+ε|n|不用not used
远场半面(出流)Far-field half-face (outflow)uL(外推)uL (extrapolated)u̇LvnuL(两项相加)vnuL (the two terms combine)不用not used

组装:与残差完全相同的边循环

Assembly: exactly the same edge loop as the residual

把 residual 里的“状态”换成“扰动”、flux 换成 flux_d,其余一行不改:收集两端扰动,计算 ḣ,按同样的符号分发(图 8.2)。

Replace "state" by "perturbation" in residual, and flux by flux_d; not one other line changes. Gather the perturbations at the two ends, compute ḣ, scatter it with the same signs (Figure 8.2).

(a) 一个真实对偶面 e17:h˙ 只算一次h˙ = cL·u˙L + cR·u˙Ru˙L+h˙u˙R−h˙910收集两端切线 → 同一个 h˙,符号相反(b) 一个真实控制体:所有对偶面的 h˙ 按符号相加−e3+e17+e18+e19+e209R˙[9] = Σ ± h˙(e)箭头指向节点:每个对偶面送来一个 h˙,按符号累加到 R˙[9]。箭头旁的 +ek​ / −ek​ 是该对偶面的编号与符号:n9 作为 i 取 +,作为 j 取 −。和原始残差是同一张图:只是把状态 u 换成切线 u˙=v、把 h 换成 h˙,符号规则一模一样。无粘壁面半面上 cL = 0。(a) One real dual face e17: h˙ is computed onceh˙ = cL·u˙L + cR·u˙Ru˙L+h˙u˙R−h˙910gather the tangents at the two ends → one h˙, with opposite signs(b) One real control volume: the h˙ of all its dual faces added with signs−e3+e17+e18+e19+e209R˙[9] = Σ ± h˙(e)the arrows point to the node: each dual face delivers one h˙, accumulated with its sign into R˙[9].beside each arrow, +ek​ / −ek​ gives the dual face and its sign: n9 takes + where it is i and − where it is j.Same picture as the primal residual: u becomes the tangent u˙ = v, h becomes h˙; the sign rule is identical.On an inviscid wall half-face cL = 0.
图 8.2 · 前向的边循环与 residual 逐行对应:每个对偶面上把两端扰动按局部导数合成一个 ḣ,再以 +、− 分发到两端节点;每个控制体的 Ṙ 就是它所有对偶面的 ḣ 按符号(作为 i 取 +、作为 j 取 −)求和。Figure 8.2 · The forward edge loop corresponds line for line to residual: on every dual face the perturbations at the two ends are combined through the local derivatives into a single ḣ, which is then scattered to the two end nodes with + and −; the Ṙ of a control volume is the signed sum (+ where it is i, − where it is j) of the ḣ of all its dual faces.

图 8.3 的默认种子是 v=ei(只扰动一个内部节点)。播放时注意:只有该节点的那些对偶面得到非零的 ḣ,最后只有它和它的邻居非零——这正是 A 的相应一列。点击网格中的控制体可以逐个修改种子(0 → 1 → −1 → 0)。

The default seed in Figure 8.3 is v=ei (perturbing a single interior node). Watch as it plays: only the dual faces of that node pick up a non-zero ḣ, and at the end only the node itself and its neighbours are non-zero — which is precisely the corresponding column of A. Click a control volume in the mesh to cycle its seed entry (0 → 1 → −1 → 0).

图 8.3Figure 8.3

为什么这就是 Av

Why this is exactly Av

每个面的计算是线性的:ḣ=cLu̇L+cRu̇R,再以 +,− 分发到 i,j。所以一个内部对偶面对 A 的贡献只落在第 i,j 行、第 i,j 列交叉处的 2×2 块上(边界半面只贡献 Aii,无粘壁面半面什么也不贡献):

The computation on each face is linear: ḣ=cLu̇L+cRu̇R, then scattered to i,j with +,−. So the contribution of one interior dual face to A falls only on the 2×2 block where rows i,j meet columns i,j (a boundary half-face contributes only Aii, and an inviscid wall half-face contributes nothing at all):

​AiiAijAjiAjj+=​cLcR−cL−cR.

这一点在 图 8.3 的“矩阵视图”里可以直接看到:播放时,当前对偶面写入的块会被高亮(内部对偶面是 2×2 块,远场半面只有对角元,无粘壁面半面什么也不写),右边两列分别是显式矩阵乘积与边循环的累加结果——循环走完,两列完全相同。

This is visible directly in the "matrix view" of Figure 8.3: as it plays, the block written by the current dual face is highlighted (a 2×2 block for an interior dual face, just the diagonal entry for a far-field half-face, nothing at all for an inviscid wall half-face), and the two columns on the right hold the explicit matrix product and the running total of the edge loop — once the loop has finished, the two columns agree exactly.

逐步推导:把残差循环逐行求导

Step by step: differentiating the residual loop line by line

上面的表格给出了四种对偶面的 cLc_L、cRc_R。它们不是另外推出来的:把第 5 节的 residual 循环逐行求导,就同时得到它们和 residual_d 的每一行。

The table above gives cLc_L and cRc_R for the four kinds of dual face. They are not derived separately: differentiating the residual loop of Section 5 line by line gives them, and every line of residual_d with them.

第一步,写下一个内部对偶面在 residual 里执行的三行(local() 与 residual()):

Step 1. The three lines an interior dual face executes in residual (local() and residual()):

uL←u[L],  uR←u[R];h←H(uL,uR);r[L]  +=  h,  r[R]  −=  h;u_L\leftarrow u[L],\ \ u_R\leftarrow u[R];\qquad h\leftarrow H(u_L,u_R);\qquad r[L]\;\text{+=}\;h,\ \ r[R]\;\text{−=}\;h;
H(uL,uR)=14vn (uL2+uR2)−ε∣𝒏∣ (uR−uL).H(u_L,u_R)=\tfrac14v_n\,(u_L^2+u_R^2)-\varepsilon|\boldsymbol n|\,(u_R-u_L).

第二步,给输入一个任意方向的种子 u˙=v\dot u=v:把输入换成 u+t vu+t\,v,对 tt 在 t=0t=0 处求导,每个程序变量 xx 的导数记作 x˙\dot x(第 8 节的记号)。收集是线性的,它的切线就是同样的收集:u˙L=v[L]\dot u_L=v[L],u˙R=v[R]\dot u_R=v[R]。

Step 2. Seed the input with an arbitrary direction, u˙=v\dot u=v: replace the input by u+t vu+t\,v and differentiate with respect to tt at t=0t=0, writing x˙\dot x for the derivative of each program variable xx (the notation of Section 8). The gather is linear, so its tangent is the same gather: u˙L=v[L]\dot u_L=v[L], u˙R=v[R]\dot u_R=v[R].

第三步,通量这一行用链式法则,再逐项求偏导:中心项 14vnu2\tfrac14v_nu^2 的导数是 12vnu\tfrac12v_nu,耗散项 −ε∣𝒏∣(uR−uL)-\varepsilon|\boldsymbol n|(u_R-u_L) 对 uLu_L 是 +ε∣𝒏∣+\varepsilon|\boldsymbol n|、对 uRu_R 是 −ε∣𝒏∣-\varepsilon|\boldsymbol n|:

Step 3. The flux line takes the chain rule, then the partial derivatives term by term: the central term 14vnu2\tfrac14v_nu^2 differentiates to 12vnu\tfrac12v_nu, and the dissipation −ε∣𝒏∣(uR−uL)-\varepsilon|\boldsymbol n|(u_R-u_L) gives +ε∣𝒏∣+\varepsilon|\boldsymbol n| for uLu_L and −ε∣𝒏∣-\varepsilon|\boldsymbol n| for uRu_R:

h˙=∂H∂uLu˙L+∂H∂uRu˙R,cL=∂H∂uL=12vnuL+ε∣𝒏∣,cR=∂H∂uR=12vnuR−ε∣𝒏∣.\begin{gathered}\dot h=\frac{\partial H}{\partial u_L}\dot u_L+\frac{\partial H}{\partial u_R}\dot u_R,\\ c_L=\frac{\partial H}{\partial u_L}=\tfrac12v_nu_L+\varepsilon|\boldsymbol n|,\qquad c_R=\frac{\partial H}{\partial u_R}=\tfrac12v_nu_R-\varepsilon|\boldsymbol n|.\end{gathered}

第四步,分发同样是线性的:R˙[L]  +=  h˙\dot R[L]\;\text{+=}\;\dot h,R˙[R]  −=  h˙\dot R[R]\;\text{−=}\;\dot h。三行合起来就是 residual_d,代码里把 h˙\dot h、R˙\dot R 写作 h_d、r_d。

Step 4. The scatter is linear too: R˙[L]  +=  h˙\dot R[L]\;\text{+=}\;\dot h, R˙[R]  −=  h˙\dot R[R]\;\text{−=}\;\dot h. Together the three lines are residual_d, whose code writes h˙\dot h and R˙\dot R as h_d and r_d.

第五步,边界半面只改第二个槽位的收集:

Step 5. Boundary half-faces change only the gather of the second slot:

  • 入流:uR=u∞u_R=u_\infty 不依赖 uu,所以 u˙R=0\dot u_R=0,h˙=cLu˙L\dot h=c_L\dot u_L(cRc_R 那一项留给第 10 节的 ff);
  • inflow: uR=u∞u_R=u_\infty does not depend on uu, so u˙R=0\dot u_R=0 and h˙=cLu˙L\dot h=c_L\dot u_L (the cRc_R term is left for the ff of Section 10);
  • 出流:uR=uLu_R=u_L,所以 u˙R=u˙L\dot u_R=\dot u_L,两个槽位的导数相加,耗散项正好抵消:h˙=(cL+cR)u˙L=vnuLu˙L\dot h=(c_L+c_R)\dot u_L=v_nu_L\dot u_L;
  • outflow: uR=uLu_R=u_L, so u˙R=u˙L\dot u_R=\dot u_L; the two slot derivatives add and the dissipation cancels: h˙=(cL+cR)u˙L=vnuLu˙L\dot h=(c_L+c_R)\dot u_L=v_nu_L\dot u_L;
  • 壁面:h≡0h\equiv0,所以 h˙=0\dot h=0。
  • wall: h≡0h\equiv0, so h˙=0\dot h=0.

第六步,所有对偶面加起来,R˙\dot R 对 vv 是线性的,它就是 AvAv:每个内部对偶面贡献上面那个 2×22\times2 块,边界半面只贡献对角元。

Step 6. Summed over all dual faces, R˙\dot R is linear in vv, and it is AvAv: every interior dual face contributes the 2×22\times2 block above, and a boundary half-face only a diagonal entry.

核对:对一个测试方向 vv,residual_d(u∗,v)(u^*,v) 与 [R(u∗+ηv)−R(u∗−ηv)]/(2η)[R(u^*+\eta v)-R(u^*-\eta v)]/(2\eta)(小步长 η=10−6\eta=10^{-6})的最大相对差 1.3×10−10,与显式矩阵乘积 AvAv 的最大差 5.6×10−16;一个内部对偶面上 cLc_L、cRc_R 与通量公式的中心差分之差 1.9×10−11;每个出流半面上 cLc_L 与 vnuLv_nu_L 之差最大 1.1×10−16(本页代码在构建时计算)。

Check: for a test direction vv, residual_d(u∗,v)(u^*,v) and [R(u∗+ηv)−R(u∗−ηv)]/(2η)[R(u^*+\eta v)-R(u^*-\eta v)]/(2\eta) (a small step η=10−6\eta=10^{-6}) differ by 1.3×10−10 relative, and residual_d and the explicit product AvAv by 5.6×10−16; on one interior dual face, cLc_L and cRc_R differ from central differences of the flux formula by 1.9×10−11; on every outflow half-face, cLc_L and vnuLv_nu_L differ by at most 1.1×10−16 (computed by this page’s code at build time).

逐步推导:一个对偶面的每一句及其切线(含几何)

Step by step: every statement of one dual face and its tangent, geometry included

上面把碰到状态的三行逐行求了导。可是一个对偶面在算通量之前,还要先由节点坐标算出它的法向、长度和法向速度;把这些也写出来,residual 里一个对偶面执行的是下表的各句(壁面半面只执行几何——它自己的一句 𝒏=12Q(𝒙b−𝒙a)\boldsymbol n=\tfrac12Q(\boldsymbol x_b-\boldsymbol x_a),见下面第一步——和最后那句目标函数:它没有长度、法向速度、状态、通量与分发)。按原来的顺序逐句求导,就得到前向程序:X˙=0\dot X=0 时它就是本节的 residual_d,u˙=0\dot u=0、u˙∞=0\dot u_\infty=0 时它就是第 12 节的 gradient_d。两者本是同一个程序。

The derivation above differentiated the three lines that touch the state. Before the flux, however, a dual face also computes its normal, its length and its normal speed from the node coordinates; with these written out, one dual face of residual executes the statements of the table below (a wall half-face runs only the geometry, its own statement 𝒏=12Q(𝒙b−𝒙a)\boldsymbol n=\tfrac12Q(\boldsymbol x_b-\boldsymbol x_a) (Step 1 below), and the objective's statement at the end: no length, normal speed, state, flux or scatter). Differentiating them one by one, in the same order, gives the forward program: with X˙=0\dot X=0 it is residual_d of this section, with u˙=0\dot u=0 and u˙∞=0\dot u_\infty=0 it is gradient_d of Section 12. The two are one program.

##原始语句primal statement切线语句(代码名:x_d 即 ẋ)tangent statement (code: x_d is ẋ)
1x[q] ← X[q] (q in t, t′)x_d[q] ← X_d[q]
2m ← (x[i] + x[j]) / 2m_d ← (x_d[i] + x_d[j]) / 2
3xc[t] ← mean of x[q], q in txc_d[t] ← mean of x_d[q], q in t
4d[t] ← xc[t] − md_d[t] ← xc_d[t] − m_d
5n ← sum over t of s_t·(d[t].y, −d[t].x)n_d ← sum over t of s_t·(d_d[t].y, −d_d[t].x)
6len ← sqrt(n·n)len_d ← (n·n_d) / len
7vn ← β·nvn_d ← β·n_d
8uL ← u[i]; uR ← u[j] (入流 uinf,出流 uL)uL ← u[i]; uR ← u[j] (inflow uinf, outflow uL)uL_d ← u_d[i]; uR_d ← u_d[j] (入流 uinf_d,出流 uL_d)uL_d ← u_d[i]; uR_d ← u_d[j] (inflow uinf_d, outflow uL_d)
9h ← vn·(uL² + uR²)/4 − ε·len·(uR − uL)h_d ← cL·uL_d + cR·uR_d + hv·vn_d + hl·len_d
10r[i] += h; r[j] −= hr_d[i] += h_d; r_d[j] −= h_d
11(壁面)11 (wall)L += uL²·(n·e)/2L_d += uL·(n·e)·uL_d + uL²·(e·n_d)/2

字母分工。第 8–12 节里,kk 只数设计分量(X˙=𝒆k\dot X=\boldsymbol e_k、φk\varphi_k、dL/dXkdL/dX_k),一个单元的顶点用 qq 数;不带下标的 𝒆\boldsymbol e 是目标的方向 𝜷/∣𝜷∣\boldsymbol\beta/|\boldsymbol\beta|,带下标的 𝒆k\boldsymbol e_k 是第 kk 个单位向量;下标 LL、RR 指一个面的左侧与右侧,不带下标的 LL 是目标。tt、t′t' 是一个对偶面两侧的两个单元,这里的 tt 从不表示时间。本页播放器里的 e13 之类是对偶面的名字(就是 f13),与目标的方向 𝒆\boldsymbol e 无关。

Letters. In Sections 8–12, kk only counts the design components (X˙=𝒆k\dot X=\boldsymbol e_k, φk\varphi_k, dL/dXkdL/dX_k), and the vertices of a cell are counted by qq; 𝒆\boldsymbol e without an index is the direction 𝜷/∣𝜷∣\boldsymbol\beta/|\boldsymbol\beta| of the objective, 𝒆k\boldsymbol e_k is the kk-th unit vector; the subscripts LL and RR name the left and the right side of a face, and LL without a subscript is the objective. tt and t′t' are the two cells on either side of a dual face; tt never means time here. The e13 of this page's players is the name of a dual face (it is f13), unrelated to the objective's direction 𝒆\boldsymbol e.

第一步,逐句的局部导数。记 ℓ=∣𝒏∣\ell=|\boldsymbol n|(代码里的 len)。长度与法向速度对法向求导:

Step 1, the local derivatives of each statement. Write ℓ=∣𝒏∣\ell=|\boldsymbol n| (len in the code). The length and the normal speed differentiate as

ℓ=(nx2+ny2)1/2 ⇒ ℓ˙=𝒏⋅𝒏˙ℓ,vn=𝜷⋅𝒏 ⇒ v˙n=𝜷⋅𝒏˙.\ell=(n_x^2+n_y^2)^{1/2}\ \Rightarrow\ \dot\ell=\frac{\boldsymbol n\cdot\dot{\boldsymbol n}}{\ell},\qquad v_n=\boldsymbol\beta\cdot\boldsymbol n\ \Rightarrow\ \dot v_n=\boldsymbol\beta\cdot\dot{\boldsymbol n}.

通量 h=14vn(uL2+uR2)−εℓ(uR−uL)h=\tfrac14v_n(u_L^2+u_R^2)-\varepsilon\ell(u_R-u_L) 有四个输入,每个输入一项偏导:

The flux h=14vn(uL2+uR2)−εℓ(uR−uL)h=\tfrac14v_n(u_L^2+u_R^2)-\varepsilon\ell(u_R-u_L) has four inputs, and each gives one partial derivative:

h˙=(12vnuL+εℓ)⏟cLu˙L+(12vnuR−εℓ)⏟cRu˙R+14(uL2+uR2)⏟∂h/∂vnv˙n+(−ε(uR−uL))⏟∂h/∂ℓℓ˙.\begin{gathered}\dot h=\underbrace{\big(\tfrac12v_nu_L+\varepsilon\ell\big)}_{c_L}\dot u_L+\underbrace{\big(\tfrac12v_nu_R-\varepsilon\ell\big)}_{c_R}\dot u_R\\ {}+\underbrace{\tfrac14(u_L^2+u_R^2)}_{\partial h/\partial v_n}\dot v_n+\underbrace{\big({-\varepsilon}(u_R-u_L)\big)}_{\partial h/\partial\ell}\dot\ell.\end{gathered}

代码里这四个偏导依次叫 cL、cR、hv、hl,就是上表 h_d 一句里的四个系数:hv 是 ∂h/∂vn\partial h/\partial v_n,hl 是 ∂h/∂ℓ\partial h/\partial\ell。前两项就是上面的 cLu˙L+cRu˙Rc_L\dot u_L+c_R\dot u_R;后两项是几何项,代入 v˙n\dot v_n、ℓ˙\dot\ell 后合成第 12 节的 ∂h/∂𝒏\partial h/\partial\boldsymbol n:

In the code these four partial derivatives are called cL, cR, hv and hl, the four coefficients of the h_d statement in the table above: hv is ∂h/∂vn\partial h/\partial v_n and hl is ∂h/∂ℓ\partial h/\partial\ell. The first two terms are the cLu˙L+cRu˙Rc_L\dot u_L+c_R\dot u_R above; the last two are the geometric terms, and with v˙n\dot v_n and ℓ˙\dot\ell substituted they combine into the ∂h/∂𝒏\partial h/\partial\boldsymbol n of Section 12:

14(uL2+uR2) 𝜷⋅𝒏˙−ε(uR−uL) 𝒏ℓ⋅𝒏˙=∂h∂𝒏⋅𝒏˙.\tfrac14(u_L^2+u_R^2)\,\boldsymbol\beta\cdot\dot{\boldsymbol n}-\varepsilon(u_R-u_L)\,\frac{\boldsymbol n}{\ell}\cdot\dot{\boldsymbol n}=\frac{\partial h}{\partial\boldsymbol n}\cdot\dot{\boldsymbol n}.

法向由线性的几何语句算出,所以它的切线就是同样的线性组合:𝒎˙=12(𝒙˙i+𝒙˙j)\dot{\boldsymbol m}=\tfrac12(\dot{\boldsymbol x}_i+\dot{\boldsymbol x}_j),𝒙˙c,t=1∣t∣∑q∈t𝒙˙q\dot{\boldsymbol x}_{c,t}=\tfrac1{|t|}\sum_{q\in t}\dot{\boldsymbol x}_q,𝒏˙=∑tstQ(𝒙˙c,t−𝒎˙)\dot{\boldsymbol n}=\sum_ts_tQ(\dot{\boldsymbol x}_{c,t}-\dot{\boldsymbol m});边界半面 𝒏˙=12Q(𝒙˙b−𝒙˙a)\dot{\boldsymbol n}=\tfrac12Q(\dot{\boldsymbol x}_b-\dot{\boldsymbol x}_a)。

The normal comes from linear geometric statements, so its tangent is the same linear combination: 𝒎˙=12(𝒙˙i+𝒙˙j)\dot{\boldsymbol m}=\tfrac12(\dot{\boldsymbol x}_i+\dot{\boldsymbol x}_j), 𝒙˙c,t=1∣t∣∑q∈t𝒙˙q\dot{\boldsymbol x}_{c,t}=\tfrac1{|t|}\sum_{q\in t}\dot{\boldsymbol x}_q, 𝒏˙=∑tstQ(𝒙˙c,t−𝒎˙)\dot{\boldsymbol n}=\sum_ts_tQ(\dot{\boldsymbol x}_{c,t}-\dot{\boldsymbol m}); a boundary half-face has 𝒏˙=12Q(𝒙˙b−𝒙˙a)\dot{\boldsymbol n}=\tfrac12Q(\dot{\boldsymbol x}_b-\dot{\boldsymbol x}_a).

第二步,边界上的三种行:

Step 2, the three kinds of boundary row:

  • 壁面:没有通量语句(h≡0h\equiv0),所以壁面半面对 AA、对 RDR_D、对 ff 都没有贡献;它只执行几何和目标那一句,L˙+=uL(𝒏⋅𝒆)u˙L+12uL2 𝒆⋅𝒏˙\dot L\mathrel{\text{+=}}u_L(\boldsymbol n\cdot\boldsymbol e)\dot u_L+\tfrac12u_L^2\,\boldsymbol e\cdot\dot{\boldsymbol n},前一项是第 7 节的 gTu˙g^T\dot u,后一项是显式的 ∂L/∂X\partial L/\partial X。
  • The wall: there is no flux statement (h≡0h\equiv0), so a wall half-face contributes nothing to AA, to RDR_D or to ff; it runs only the geometry and the objective's statement, L˙+=uL(𝒏⋅𝒆)u˙L+12uL2 𝒆⋅𝒏˙\dot L\mathrel{\text{+=}}u_L(\boldsymbol n\cdot\boldsymbol e)\dot u_L+\tfrac12u_L^2\,\boldsymbol e\cdot\dot{\boldsymbol n}, whose first term is the gTu˙g^T\dot u of Section 7 and whose second is the explicit ∂L/∂X\partial L/\partial X.
  • 入流:uR←u∞u_R\leftarrow u_\infty,所以 u˙R←u˙∞\dot u_R\leftarrow\dot u_\infty;cRc_R 那一项变成 cRu˙∞c_R\dot u_\infty,就是设计导数 ∂h/∂u∞\partial h/\partial u_\infty(第 10 节的 ff)。几何的两项照样在:远场半面也随它的节点移动。
  • Inflow: uR←u∞u_R\leftarrow u_\infty, so u˙R←u˙∞\dot u_R\leftarrow\dot u_\infty; the cRc_R term becomes cRu˙∞c_R\dot u_\infty, the design derivative ∂h/∂u∞\partial h/\partial u_\infty (the ff of Section 10). Both geometric terms remain: a far-field half-face moves with its nodes too.
  • 出流:uR←uLu_R\leftarrow u_L,所以 u˙R←u˙L\dot u_R\leftarrow\dot u_L;两个槽位的项合成 vnuLu˙Lv_nu_L\dot u_L,而 uR−uL=0u_R-u_L=0 使 ℓ˙\dot\ell 那一项为零:h˙=vnuLu˙L+12uL2v˙n\dot h=v_nu_L\dot u_L+\tfrac12u_L^2\dot v_n。
  • Outflow: uR←uLu_R\leftarrow u_L, so u˙R←u˙L\dot u_R\leftarrow\dot u_L; the two slot terms merge into vnuLu˙Lv_nu_L\dot u_L, and uR−uL=0u_R-u_L=0 removes the ℓ˙\dot\ell term: h˙=vnuLu˙L+12uL2v˙n\dot h=v_nu_L\dot u_L+\tfrac12u_L^2\dot v_n.

第三步,本页的一个例子(图 8.4 逐句播放)。取对偶面 f13(节点 6、14;本页的播放器把对偶面写作 e 加编号,这里是 e13),种子 X˙=𝒆k\dot X=\boldsymbol e_k,kk 是壁面节点 7 的 xx 坐标;状态的种子取这个 X˙\dot X 的切线解 u˙=φk\dot u=\varphi_k(第 10 节解它;任何 u˙\dot u 都能逐句代入,取这一个是为了让整个循环的 R˙\dot R 为零)。逐句得到 𝒙˙c,t′=(0.25, 0.00)\dot{\boldsymbol x}_{c,t'}=(0.25,\,0.00),𝒏˙=(0.0000, −0.2500)\dot{\boldsymbol n}=(0.0000,\,{-0.2500}),ℓ˙=0.1630\dot\ell=0.1630,v˙n=−0.1250\dot v_n={-0.1250},u˙L=0.3724\dot u_L=0.3724,u˙R=0.0152\dot u_R=0.0152,以及

Step 3, an example on this page (Figure 8.4 plays it statement by statement). Take dual face f13 (nodes 6 and 14; the players of this page write a dual face e and its number, here e13) and the seed X˙=𝒆k\dot X=\boldsymbol e_k, kk the xx coordinate of wall node 7; as the state seed take the tangent solution of this X˙\dot X, u˙=φk\dot u=\varphi_k (Section 10 solves it; any u˙\dot u can be put through the statements, and this one makes the loop's R˙\dot R vanish). Statement by statement: 𝒙˙c,t′=(0.25, 0.00)\dot{\boldsymbol x}_{c,t'}=(0.25,\,0.00), 𝒏˙=(0.0000, −0.2500)\dot{\boldsymbol n}=(0.0000,\,{-0.2500}), ℓ˙=0.1630\dot\ell=0.1630, v˙n=−0.1250\dot v_n={-0.1250}, u˙L=0.3724\dot u_L=0.3724, u˙R=0.0152\dot u_R=0.0152, and

h˙=(−0.0184)⋅0.3724⏟cLu˙L+(−0.7671)⋅0.0152⏟cRu˙R+0.8591⋅(−0.1250)⏟(∂h/∂vn)v˙n+0.3989⋅0.1630⏟(∂h/∂ℓ)ℓ˙=−0.0609.\begin{gathered}\dot h=\underbrace{({-0.0184})\cdot0.3724}_{c_L\dot u_L}+\underbrace{({-0.7671})\cdot0.0152}_{c_R\dot u_R}\\ {}+\underbrace{0.8591\cdot({-0.1250})}_{(\partial h/\partial v_n)\dot v_n}+\underbrace{0.3989\cdot0.1630}_{(\partial h/\partial\ell)\dot\ell}={-0.0609}.\end{gathered}

第四步,整个循环。80 个面(48 个内部对偶面与 32 个边界半面)都这样走一遍,R˙=Aφk+(∂R/∂X)𝒆k\dot R=A\varphi_k+(\partial R/\partial X)\boldsymbol e_k 在每个节点都是零(最大 8.3×10−17,右端项本身最大 0.191);目标的切线

Step 4, the whole loop. Running all 80 faces (48 interior dual faces and 32 boundary half-faces) this way, R˙=Aφk+(∂R/∂X)𝒆k\dot R=A\varphi_k+(\partial R/\partial X)\boldsymbol e_k is zero in every node (at most 8.3×10−17, against a right-hand side of size 0.191); the tangent of the objective,

L˙=gTφk+∂L∂Xk=0.469675479367\dot L=g^T\varphi_k+\frac{\partial L}{\partial X_k}=0.469675479367

就是梯度的第 kk 个分量 dL/dXkdL/dX_k:第 12 节的伴随梯度给出同一个数(相对差 2.4×10−16),第 14 节的复数步长也是(相对差 1.2×10−16)。本节末尾的推导(“本页一个面的算例:前向扫描”,图 8.5)把同样的前向扫描写成一般的四条语句,取的是另一个对偶面(面循环里第一个两侧都不在壁面上的内部面)和种子 u˙∞=1\dot u_\infty=1,不是这里的坐标种子。

is the kk-th component dL/dXkdL/dX_k of the gradient: the adjoint gradient of Section 12 gives the same number (relative difference 2.4×10−16), and so does the complex step of Section 14 (1.2×10−16). The derivation at the end of this section (“A worked example on this page: the forward sweep of one face”, Figure 8.5) works the same sweep in the general four-statement form, on another dual face (the first interior one whose two sides are off the wall) and for the seed u˙∞=1\dot u_\infty=1 instead of a coordinate.

核对:把逐句写出的前向程序(几何、状态和 u∞u_\infty 三种种子同时给随机值)与本页的 residual_d、gradient_d 与 dRdUinf 之和相比,相对差 1.4×10−16,目标部分 3.5×10−16;逐句算出的每个对偶面几何与本页存的 𝒏\boldsymbol n、ℓ\ell、vnv_n 最大差 0(本页代码在构建时计算)。

Check: the forward program written statement by statement (random seeds on the geometry, the state and u∞u_\infty at once) against the sum of this page’s residual_d, gradient_d and dRdUinf: relative difference 1.4×10−16, objective part 3.5×10−16; the geometry computed statement by statement against the stored 𝒏\boldsymbol n, ℓ\ell and vnv_n of every dual face: 0 (computed by this page’s code at build time).

1/|t|1/|t′|½1−11−1stQst′Qn/ℓβ∂h/∂ℓ∂h/∂vncLcR+1−1xq, q∈txq, q∈t′xi, xjxc,txc,t′mdtdt′nℓ = |n|vn = β·nuLuRhRiRjx˙qx˙qx˙i,jm˙x˙c,tx˙c,t′d˙​td˙​t′n˙ℓ˙v˙nu˙Lu˙Rh˙​R˙​iR˙​j前向:沿箭头把点(切线)从输入推到输出forward: the dots (tangents) travel with the arrows, inputs to outputs
12 / 12
1x_d[q] ← X_d[q] (q in t, t′)X_d[7] = (1, 0); X_d[q] = 0, q ≠ 7
2m_d ← (x_d[i] + x_d[j]) / 2(0.0000, 0.0000)
3xc_d[t] ← mean of x_d[q], q in tt: (0.0000, 0.0000); t′: (0.2500, 0.0000)
4d_d[t] ← xc_d[t] − m_dt: (0.0000, 0.0000); t′: (0.2500, 0.0000)
5n_d ← sum over t of s_t·(d_d[t].y, −d_d[t].x)(0.0000, −0.2500)
6len_d ← (n·n_d) / len0.1630
7vn_d ← β·n_d−0.1250
8uL_d ← u_d[i]; uR_d ← u_d[j]0.3724, 0.0152
9h_d ← cL·uL_d + cR·uR_d + hv·vn_d + hl·len_d−0.00687 − 0.01166 − 0.10738 + 0.06504 = −0.0609
10r_d[i] += h_d; r_d[j] −= h_dr_d[6] += −0.0609; r_d[14] −= −0.0609
种子:Ẋ = ek,k 是壁面节点 7 的 x 坐标(只有这个节点动,速度 (1, 0));u̇ = φk,第 10 节为这个种子解出的切线解;u̇∞ = 0。对偶面 f13 连接节点 i = 6、j = 14,两段折线分别通向单元 t(节点 5、13、14、6)与 t′(节点 6、14、15、7)的中心。The seeds: Ẋ = ek, k the x coordinate of wall node 7 (only this node moves, with velocity (1, 0)); u̇ = φk, the tangent solution Section 10 solves for this seed; u̇∞ = 0. Dual face f13 joins nodes i = 6 and j = 14; its two segments lead to the centres of cell t (nodes 5, 13, 14, 6) and cell t′ (nodes 6, 14, 15, 7).
第 1 行,收集几何:节点 7 是单元 t′ 的顶点,却不是这条边的端点;只有它动,ẋ[7] = (1, 0)。Line 1, gather the geometry: node 7 is a vertex of cell t′ but not an end of the edge; it is the only node that moves, ẋ[7] = (1, 0).
第 2 行:边中点 ṁ = (ẋi + ẋj)/2 = (0.0000, 0.0000):两端都没动。Line 2: the edge midpoint ṁ = (ẋi + ẋj)/2 = (0.0000, 0.0000): neither end moves.
第 3 行:单元中心是顶点的平均,ẋc,t = (0.0000, 0.0000),ẋc,t′ = ẋ[7]/4 = (0.2500, 0.0000)。Line 3: a cell centre is the average of its vertices, ẋc,t = (0.0000, 0.0000), ẋc,t′ = ẋ[7]/4 = (0.2500, 0.0000).
第 4 行:ḋt = (0.0000, 0.0000),ḋt′ = (0.2500, 0.0000)。Line 4: ḋt = (0.0000, 0.0000), ḋt′ = (0.2500, 0.0000).
第 5 行:每段转 90° 并乘它的方向 st(这里 st = −1,st′ = +1):ṅ = (0.0000, −0.2500)。Line 5: each segment is turned by 90° and multiplied by its orientation st (here st = −1, st′ = +1): ṅ = (0.0000, −0.2500).
第 6 行:ℓ̇ = n·ṅ/ℓ = 0.1630。Line 6: ℓ̇ = n·ṅ/ℓ = 0.1630.
第 7 行:v̇n = β·ṅ = −0.1250。Line 7: v̇n = β·ṅ = −0.1250.
第 8 行,收集状态:u̇L = 0.3724,u̇R = 0.0152(φk 在节点 6、14 的值)。Line 8, gather the state: u̇L = 0.3724, u̇R = 0.0152 (the values of φk at nodes 6 and 14).
第 9 行,通量:ḣ = cLu̇L + cRu̇R + ¼(uL2+uR2)v̇n − ε(uR−uL)ℓ̇ = −0.00687 − 0.01166 − 0.10738 + 0.06504 = −0.0609。Line 9, the flux: ḣ = cLu̇L + cRu̇R + ¼(uL2+uR2)v̇n − ε(uR−uL)ℓ̇ = −0.00687 − 0.01166 − 0.10738 + 0.06504 = −0.0609.
第 10 行,分发:Ṙ[6] += −0.0609,Ṙ[14] −= −0.0609,与 residual 的 h 同一套符号。Line 10, the scatter: Ṙ[6] += −0.0609, Ṙ[14] −= −0.0609, with the signs of residual's h.
整个循环:80 个面都走完后,每个节点的 Ṙ = Aφk + (∂R/∂X)ek 都为零(最大 8.3×10⁻¹⁷):φk 正是切线方程的解。目标的切线 L̇ = gTφk + (∂L/∂X)ek = 0.469675479366645,就是 dL/dXk。The whole loop: after all 80 faces, Ṙ = Aφk + (∂R/∂X)ek is zero in every node (at most 8.3×10⁻¹⁷): φk solves the tangent equation. The tangent of the objective, L̇ = gTφk + (∂L/∂X)ek = 0.469675479366645, is dL/dXk.
图 8.4 · 一个对偶面的程序正向播放:对偶面 f13 的每一句和它的切线。上面是计算图:方框是程序变量,箭头旁是局部偏导(先由顶点、单元中心、边中点和线段得到法向,法向 n 再给出长度 ℓ = |n| 与法向速度 vn,它们与两个状态一起进入通量 h,h 以相反的符号分发);当前语句的方框为紫色,这一句用到的箭头为绿色,绿色的点号标出已经算出切线的变量。下面的列表是这些语句(代码名:x_d 即 ẋ),右侧数值由本页代码按第 8 节的种子算出:只动壁面节点 7 的 x,状态的切线取这个种子的切线解。Figure 8.4 · The program of one dual face, played forward: every statement of dual face f13 and its tangent. Above, the computational graph: the boxes are program variables and the labels on the arrows the local partial derivatives (the vertices, cell centres, edge midpoint and segments give the normal, and the normal n gives the length ℓ = |n| and the normal speed vn, which enter the flux h with the two states; h is scattered with opposite signs). The box of the current statement is violet, the arrows it uses are green, and a green dotted symbol marks each variable whose tangent is known. Below, the statements (code names: x_d is ẋ) with the numbers this page’s code computes for the seed of Section 8: only the x of wall node 7 moves, and the state tangent is the tangent solution of that seed.

局部导数怎样组成全局 Jacobian

How local derivatives form the global Jacobian

固定网格和当前可微分支,把一个面的所有输入记为 zfz_f;它包括面两侧的状态(新增模型中是重构状态)、面的几何与边界数据。令 TfT_f、KfK_f 分别是 zfz_f 对存储状态和设计变量的导数,Hz,fH_{z,f} 是局部通量 Jacobian。于是每个残差行的导数按同一个有向面关联矩阵 ℬ\mathcal B 装配,这里的求和包括相应的边界通量映射。本页没有被替换的行,边界条件全部在边界面的通量里。前向模式按计算顺序传播切向量,状态种子是 v≡𝐮˙v\equiv\dot{\mathbf u},设计种子是 D˙\dot D:

At fixed mesh and differentiable branch, let zfz_f collect all face inputs: the states of both sides (reconstructed states in the added model), the face geometry and the boundary data. Let TfT_f and KfK_f be the derivatives of zfz_f with respect to the stored states and the design variables, and Hz,fH_{z,f} the local flux Jacobian. Assemble each residual derivative with the same oriented face incidence ℬ\mathcal B, including the boundary flux maps. This page replaces no rows: every boundary condition sits in a boundary-face flux. Forward mode carries the tangents in the order of the computation, with the state seed v≡𝐮˙v\equiv\dot{\mathbf u} and the design seed D˙\dot D:

z˙f=Tfv+KfD˙,H˙f=Hz,fz˙f,\dot z_f=T_fv+K_f\dot D,\quad\dot H_f=H_{z,f}\dot z_f,
R˙i=∑fℬifH˙f−S˙i,\dot R_i=\sum_f\mathcal B_{if}\dot H_f-\dot S_i,
(A)ij=∑fℬifHz,f(Tf)j−∂Si∂uj,(A)_{ij}=\sum_f\mathcal B_{if}H_{z,f}(T_f)_j-\frac{\partial S_i}{\partial u_j},
(RD)ik=∑fℬifHz,f(Kf)k−∂Si∂Dk.(R_D)_{ik}=\sum_f\mathcal B_{if}H_{z,f}(K_f)_k-\frac{\partial S_i}{\partial D_k}.

无体源项时 Si=0S_i=0。面长度和法向量随节点坐标变化,它们的设计依赖属于 zfz_f,不能只对状态求导。本页的 KfK_f 就是这些几何语句(𝒏f\boldsymbol n_f、ℓf\ell_f 与 vn,f=𝜷⋅𝒏fv_{n,f}=\boldsymbol\beta\cdot\boldsymbol n_f,都是 XX 的函数)的导数,再加上远场入流面的来流槽位 uR=u∞u_R=u_\infty。主线格式是一阶,TfT_f 只连接面两侧的值;新增模型的二阶重构使 TfT_f 连接更多邻居;matrix-free 仍只需依次执行这些局部乘积,不必存储全局矩阵。

Set Si=0S_i=0 when there is no volume source. Face lengths and normals move with the node coordinates; that design dependence belongs in zfz_f, so differentiating the states alone is insufficient. On this page KfK_f is exactly the derivative of these geometric statements (𝒏f\boldsymbol n_f, ℓf\ell_f and vn,f=𝜷⋅𝒏fv_{n,f}=\boldsymbol\beta\cdot\boldsymbol n_f, all functions of XX) together with the inflow slot uR=u∞u_R=u_\infty of the far-field inflow faces. The main-line scheme is first order, so TfT_f connects only the two values of a face; the second-order reconstruction of the added model connects it to more neighbours. Matrix-free evaluation performs these local products without storing the global matrix.

从程序到切向量:面循环的四条语句逐条线性化

From program to tangent: linearise the four statements of the face loop one by one

上面的 z˙f\dot z_f、H˙f\dot H_f 与 R˙i\dot R_i 来自同一段短程序:残差代码对每个面依次执行四条语句。设 𝒮f\mathcal S_f 是面 ff 读取的存储位置(两侧的 LL、RR,以及重构需要时它们的邻居),把这些位置上的状态排成 𝐮f\mathbf u_f:

The z˙f\dot z_f, H˙f\dot H_f and R˙i\dot R_i above come from one short program: the residual code runs four statements on every face. Let 𝒮f\mathcal S_f be the stored locations that face ff reads (its two sides LL and RR, and their neighbours when the reconstruction needs them), and stack the states stored there as 𝐮f\mathbf u_f:

(1)𝐮f=𝒢f 𝐮,(1)\quad \mathbf u_f=\mathcal G_f\,\mathbf u,
(2)zf=zf(D,𝐮f),(2)\quad z_f=z_f(D,\mathbf u_f),
(3)Hf=H(zf),(3)\quad H_f=H(z_f),
(4)R+=ℬ:,f Hf.(4)\quad R\mathrel{\text{+=}}\mathcal B_{:,f}\,H_f.

(1)取值:𝒢f\mathcal G_f 的每一行只有一个 1,从全局数组里挑出 𝒮f\mathcal S_f 上的状态;(2)重构:由这些状态和设计变量算出面的全部输入 zfz_f,在本页是取出的两个状态 (uL,uR)(u_L,u_R),加上面积向量 𝒏f\boldsymbol n_f 与面长 ℓf\ell_f(一阶时对状态 Zf=IZ_f=I;面积向量与面长由节点坐标 XX 算出);(3)通量:Hf=H(zf)H_f=H(z_f)(本页把面通量 HfH_f 记作 hh);(4)分发:ℬ:,f\mathcal B_{:,f} 是有向关联矩阵 ℬ\mathcal B 的第 ff 列,在第 LL 行为 +1+1、第 RR 行为 −1-1,即 RL+=HfR_L\mathrel{\text{+=}}H_f、RR-=HfR_R\mathrel{\text{-=}}H_f(边界面只写一行)。这四条语句是残差代码的骨架;本页代码里的每一条语句(含边界面与源项)都在本节里逐句写出,并附各自的切向量语句。

(1) Gather: each row of 𝒢f\mathcal G_f holds a single 1 and picks one state of 𝒮f\mathcal S_f out of the global array. (2) Reconstruct: the states and the design give all inputs zfz_f of the face, on this page the two gathered states (uL,uR)(u_L,u_R), plus the area vector 𝒏f\boldsymbol n_f and the face length ℓf\ell_f (at first order Zf=IZ_f=I on the states; the area vector and the length are functions of the node coordinates XX). (3) Flux: Hf=H(zf)H_f=H(z_f) (this page writes the face flux HfH_f as hh). (4) Scatter: ℬ:,f\mathcal B_{:,f} is column ff of the oriented incidence ℬ\mathcal B, +1+1 in row LL and −1-1 in row RR, that is RL+=HfR_L\mathrel{\text{+=}}H_f and RR-=HfR_R\mathrel{\text{-=}}H_f (a boundary face writes one row). These four statements are the skeleton of the residual code; every statement of this page's code, boundary faces and sources included, is written out with its tangent statement in this section.

前向模式对每条语句用一次链式法则:每条语句输出的切向量是它输入的切向量的线性组合,每个中间变量都有自己的切向量:

Forward mode applies the chain rule once per statement: the tangent of each output is a linear combination of the tangents of the statement's inputs, and every intermediate variable gets a tangent of its own:

(1)𝐮˙f=𝒢f 𝐮˙,(1)\quad \dot{\mathbf u}_f=\mathcal G_f\,\dot{\mathbf u},
(2)z˙f=Zf 𝐮˙f+Kf D˙,(2)\quad \dot z_f=Z_f\,\dot{\mathbf u}_f+K_f\,\dot D,
(3)H˙f=Hz,f z˙f,(3)\quad \dot H_f=H_{z,f}\,\dot z_f,
(4)R˙+=ℬ:,f H˙f,(4)\quad \dot R\mathrel{\text{+=}}\mathcal B_{:,f}\,\dot H_f,
Zf=∂zf∂𝐮f,Kf=∂zf∂D,Hz,f=∂H∂zf.Z_f=\frac{\partial z_f}{\partial \mathbf u_f},\qquad K_f=\frac{\partial z_f}{\partial D},\qquad H_{z,f}=\frac{\partial H}{\partial z_f}.

取值与分发都是线性的,它们的切向量就是同样的取值与分发作用在切向量上;只有重构与通量两条语句带来依赖当前状态的局部导数 ZfZ_f、KfK_f 与 Hz,fH_{z,f}。把(1)代入(2),(2)代入(3),(3)代入(4),对所有面求和,再减去体源项的切向量 S˙\dot S,就逐条语句得到 R˙=A𝐮˙+RDD˙\dot R=A\dot{\mathbf u}+R_D\dot D:

Gather and scatter are linear: their tangents are the same gather and scatter applied to the tangents. Only the reconstruct and flux statements bring local derivatives that depend on the current state: ZfZ_f, KfK_f and Hz,fH_{z,f}. Substituting (1) into (2), (2) into (3) and (3) into (4), summing over all faces and subtracting the tangent S˙\dot S of the volume source assembles R˙=A𝐮˙+RDD˙\dot R=A\dot{\mathbf u}+R_D\dot D statement by statement:

R˙=∑fℬ:,fHz,f(Zf𝒢f 𝐮˙+KfD˙)−S˙\dot R=\sum_f\mathcal B_{:,f}H_{z,f}\big(Z_f\mathcal G_f\,\dot{\mathbf u}+K_f\dot D\big)-\dot S
=(∑fℬ:,fHz,fZf𝒢f−∂S∂𝐮⏟A)𝐮˙=\Big(\underbrace{\sum_f\mathcal B_{:,f}H_{z,f}Z_f\mathcal G_f-\frac{\partial S}{\partial \mathbf u}}_{A}\Big)\dot{\mathbf u}
+(∑fℬ:,fHz,fKf−∂S∂D⏟RD)D˙.+\Big(\underbrace{\sum_f\mathcal B_{:,f}H_{z,f}K_f-\frac{\partial S}{\partial D}}_{R_D}\Big)\dot D.

上面的 TfT_f 就是 Zf𝒢fZ_f\mathcal G_f;一阶重构不改变取出的状态,ZfZ_f 在状态上是单位阵,TfT_f 就是取值。所以一个面对 AA 的贡献是四个初等 Jacobian 的乘积

The TfT_f above is Zf𝒢fZ_f\mathcal G_f; first-order reconstruction leaves the gathered states unchanged, so ZfZ_f is the identity on the states and TfT_f is the gather. One face therefore contributes to AA the product of four elementary Jacobians,

Af=ℬ:,f Hz,f Zf 𝒢f,A_f=\mathcal B_{:,f}\,H_{z,f}\,Z_f\,\mathcal G_f,
Af𝐮˙=ℬ:,f(Hz,f(Zf(𝒢f 𝐮˙))).A_f\dot{\mathbf u}=\mathcal B_{:,f}\Big(H_{z,f}\Big(Z_f\big(\mathcal G_f\,\dot{\mathbf u}\big)\Big)\Big).

它从右往左作用在 𝐮˙\dot{\mathbf u} 上,与程序执行的顺序相同:先取值,再重构、通量,最后分发。𝒮f\mathcal S_f 只有几个位置,所以 AfA_f 只有一小块非零,matrix-free 乘积就是按面执行这几次局部运算,不存储任何全局矩阵。图 8.5 按这四条语句逐条播放本页一个面的数值。

It acts on 𝐮˙\dot{\mathbf u} from right to left, in the order in which the program runs: gather first, then reconstruct and flux, scatter last. 𝒮f\mathcal S_f holds only a few locations, so AfA_f has a small nonzero block, and the matrix-free product runs these local operations face by face without storing any global matrix. Figure 8.5 plays the four statements with the numbers of one face of this page.

本页一个面的算例:前向扫描

A worked example on this page: the forward sweep of one face

取面循环里第一个两侧都不在壁面上的内部面 f=17f=17(节点 9 在左、10 在右);一阶格式只读两侧的状态,𝒮f={9,10}\mathcal S_f=\{9,10\}。设计种子取 D˙=ek\dot D=e_k,DkD_k 是来流值 u∞u_\infty(本页前向求解的方向);状态种子取本页前向求解对同一方向给出的解 𝐮˙=v\dot{\mathbf u}=v,于是下面每个切向量都是对 DkD_k 的导数。表中的数由本页代码在构建时算出:基准状态、前向求解与局部导数都用本页自己的程序。

Take the first interior face of the face loop whose two sides are off the wall, f=17f=17 (node 9 on the left, 10 on the right); the first-order scheme reads only its two sides, 𝒮f={9,10}\mathcal S_f=\{9,10\}. Seed the design with D˙=ek\dot D=e_k, where DkD_k is the free-stream value u∞u_\infty (the direction of this page's tangent solve), and the states with this page's tangent solution for the same direction, 𝐮˙=v\dot{\mathbf u}=v; every tangent below is then a derivative with respect to DkD_k. The numbers are computed by this page's code at build time: its baseline state, its tangent solve and its local derivatives.

语句Statement量Quantity原始值Primal value切向量Tangent
(1)取值(1) gatheru9u_{9}0.9170.9071
u10u_{10}0.94560.9737
设计种子design seedDk=u∞D_k=u_\infty11
(2)重构(2) reconstruct𝒏f\boldsymbol n_f−0.4099
0.2802
0
0
ℓf\ell_f0.49650
(3)通量(3) fluxhh−0.1284−0.2628
(4)分发(4) scatterR9+=hR_{9}\mathrel{\text{+=}}h−0.1284−0.2628
R10-=hR_{10}\mathrel{\text{-=}}h0.12840.2628

DkD_k 不直接进入这个面:Kfek=0K_fe_k=0,h˙\dot h 全部来自状态的切向量。u∞u_\infty 只作为远场入流面的右状态进入通量,所以 RD ekR_D\,e_k 只在 5 个节点上非零;几何量也是 zfz_f 的一部分,但这个种子不移动任何节点,所以它们的切向量都是零。

DkD_k does not enter this face directly: Kfek=0K_fe_k=0, and h˙\dot h comes entirely from the state tangents. u∞u_\infty enters only as the right state of the far-field inflow faces, so RD ekR_D\,e_k is nonzero in 5 nodes only; the geometry is part of zfz_f too, but this seed moves no node, so its tangents are zero.

所有面与源项的这些贡献加起来是 Av+RD ekAv+R_D\,e_k,在收敛解上为零:这正是下面前向求解的方程。

Summed over all faces and the source terms, these contributions give Av+RD ekAv+R_D\,e_k, which vanishes at the converged solution: the tangent equation solved further on.

图 8.5Figure 8.5
一个面的前向扫描
The forward sweep of one face

按程序顺序执行四条语句,箭头上是各自的初等 Jacobian;下方是它们的乘积与形状(行 × 列,本页这个面的尺寸)。

The four statements in program order, each arrow labelled with its elementary Jacobian; below, their product with the shapes (rows × columns, this face of this page).

全部存储状态all stored statesuu˙​面读取的状态states the face readsuf​u˙​f​面输入:重构值等face inputszf​z˙​f​面通量face fluxHf​H˙​f​两侧残差行rows L and RRL, RR​R˙​L, R˙​R​+=设计输入design inputDD˙​𝒢f​Zf​Hz,f​ℬ:,f​Kf​Af=ℬ:,f​24×1Hz,f​1×5Zf​5×2𝒢f​2×24u˙​R˙​从右往左作用:先取值(最右),最后分发(最左)applied right to left: the gather (rightmost) first, the scatter (leftmost) last全部状态all statesuu˙​模板上的状态stenciluf​u˙​f​面输入face inputszf​z˙​f​面通量face fluxHf​H˙​f​L、R 两行rows L, RRL, RR​R˙​L, R˙​R​+=设计输入design inputDD˙​𝒢f​Zf​Hz,f​ℬ:,f​Kf​Af=ℬ:,f​24×1Hz,f​1×5Zf​5×2𝒢f​2×24u˙​R˙​从右往左作用:applied right to left:先取值,最后分发the gather first, the scatter last
  1. 种子:D˙=ek\dot D=e_k(DkD_k 是来流值 u∞u_\infty),𝐮˙=v\dot{\mathbf u}=v。在 𝒮f\mathcal S_f 上:u˙9=\dot u_{9}= 0.9071,u˙10=\dot u_{10}= 0.9737。

    Seeds: D˙=ek\dot D=e_k (DkD_k is the free-stream value u∞u_\infty) and 𝐮˙=v\dot{\mathbf u}=v. On 𝒮f\mathcal S_f: u˙9=\dot u_{9}= 0.9071, u˙10=\dot u_{10}= 0.9737.

  2. (1)取值:𝐮˙f=𝒢f𝐮˙\dot{\mathbf u}_f=\mathcal G_f\dot{\mathbf u},只挑出 𝒮f\mathcal S_f 上的 2 个状态的切向量(上一步列出的数)。

    (1) Gather: 𝐮˙f=𝒢f𝐮˙\dot{\mathbf u}_f=\mathcal G_f\dot{\mathbf u} picks the tangents of the 2 states of 𝒮f\mathcal S_f (the numbers of the previous step).

  3. (2)重构:z˙f=Zf𝐮˙f+KfD˙\dot z_f=Z_f\dot{\mathbf u}_f+K_f\dot D。一阶格式,面输入中的状态就是取出的状态。𝒏˙f=\dot{\boldsymbol n}_f= (0, 0),ℓ˙f=\dot\ell_f= 0(这个种子不移动任何节点)。Kfek=0K_fe_k=0:DkD_k 不进入这个面。

    (2) Reconstruct: z˙f=Zf𝐮˙f+KfD˙\dot z_f=Z_f\dot{\mathbf u}_f+K_f\dot D. First order: the states among the face inputs are the gathered states. 𝒏˙f=\dot{\boldsymbol n}_f= (0, 0), ℓ˙f=\dot\ell_f= 0 (this seed moves no node). Kfek=0K_fe_k=0: DkD_k does not enter this face.

  4. (3)通量:h˙=Hz,fz˙f=\dot h=H_{z,f}\dot z_f= −0.2628。

    (3) Flux: h˙=Hz,fz˙f=\dot h=H_{z,f}\dot z_f= −0.2628.

  5. (4)分发:R˙9+=h˙=\dot R_{9}\mathrel{\text{+=}}\dot h= −0.2628,R˙10-=h˙\dot R_{10}\mathrel{\text{-=}}\dot h,即加上 0.2628。

    (4) Scatter: R˙9+=h˙=\dot R_{9}\mathrel{\text{+=}}\dot h= −0.2628 and R˙10-=h˙\dot R_{10}\mathrel{\text{-=}}\dot h, that is 0.2628 added.

新增(选读):光滑特征边界模型
Added (optional): smooth characteristic-boundary model

前向:通量之前还有重构导数

Tangent: differentiate reconstruction before flux

给状态种子 u˙=v\dot u=v 与几何/入流参数的种子 D˙\dot D,逐个标量操作应用链式法则;点表示前向导数(主线第 8 节的记号)。把最小二乘梯度记作 γi=∇hui\gamma_i=\nabla_hu_i(代码里的 grad[i]),它的切线是 γ˙i\dot\gamma_i。固定网格时,最小二乘梯度对 u 是线性的;几何变化时,M 本身也会改变。漏掉 M˙\dot M 项会使状态点积检验仍然通过,却使形状梯度错误。

Given the state seed u˙=v\dot u=v and the geometry/inflow seed D˙\dot D, apply the chain rule to every scalar operation; the dot is the forward derivative (the notation of Section 8 of the main line). Call the least-squares gradient γi=∇hui\gamma_i=\nabla_hu_i (grad[i] in the code); its tangent is γ˙i\dot\gamma_i. The least-squares gradient is linear in u for fixed geometry, but M changes under geometry perturbations. Omitting M˙\dot M may still pass a state transpose test while corrupting shape gradients.

γ˙i=Mi−1(b˙i−M˙iγi),u˙f=u˙i+γ˙i⋅(xf−xi)+γi⋅(x˙f−x˙i).\dot\gamma_i=M_i^{-1}(\dot b_i-\dot M_i\gamma_i),\qquad\dot u_f=\dot u_i+\dot\gamma_i\cdot(x_f-x_i)+\gamma_i\cdot(\dot x_f-\dot x_i).
cL=12(𝜷⋅𝒏)u−+εℓ,cR=12(𝜷⋅𝒏)u+−εℓ,H˙=cLu˙−+cRu˙++Hn⋅n˙.c_L=\tfrac12(\boldsymbol\beta\cdot\boldsymbol n)u^-+\varepsilon\ell,\quad c_R=\tfrac12(\boldsymbol\beta\cdot\boldsymbol n)u^+-\varepsilon\ell,\quad\dot H=c_L\dot u^-+c_R\dot u^++H_n\cdot\dot n.
differentiate coordinates, centres, normals, lengths
differentiate M and b; grad_d = solve(M, b_d - M_d*grad)
differentiate reconstructed traces and inflow u_in(xq)
H_d = chain_rule(flux)
R_d[L] += H_d; R_d[R] -= H_d
w˙ij=−2dij⋅d˙ij(dij⋅dij)2,M˙i=∑j[w˙ijdijdijT+wij(d˙ijdijT+dijd˙ijT)].\dot w_{ij}=-2\frac{d_{ij}\cdot\dot d_{ij}}{(d_{ij}\cdot d_{ij})^2},\quad\dot M_i=\sum_j\left[\dot w_{ij}d_{ij}d_{ij}^T+w_{ij}(\dot d_{ij}d_{ij}^T+d_{ij}\dot d_{ij}^T)\right].
b˙i=∑j[(w˙ijdij+wijd˙ij)(uj−ui)+wijdij(u˙j−u˙i)].\dot b_i=\sum_j\left[(\dot w_{ij}d_{ij}+w_{ij}\dot d_{ij})(u_j-u_i)+w_{ij}d_{ij}(\dot u_j-\dot u_i)\right].

先解出梯度 ∇_hu_i=M_i⁻¹b_i,再对 Miγi=biM_i\gamma_i=b_i(γi=∇hui\gamma_i=\nabla_hu_i)求导:Miγ˙i=b˙i−M˙iγiM_i\dot\gamma_i=\dot b_i-\dot M_i\gamma_i。这样不用显式推导逆矩阵的每个元素,也清楚地看到几何项来自哪里。固定网格令 d˙=w˙=M˙=0\dot d=\dot w=\dot M=0;几何方向则不能省略这些项。面板用固定示例种子 u˙i=vi=sin⁡(i+1)\dot u_i=v_i=\sin(i+1) 展示逐面累加,下一节求解面板再计算真正的参数敏感度。

First solve ∇_hu_i=M_i⁻¹b_i, then differentiate Miγi=biM_i\gamma_i=b_i (γi=∇hui\gamma_i=\nabla_hu_i) to obtain Miγ˙i=b˙i−M˙iγiM_i\dot\gamma_i=\dot b_i-\dot M_i\gamma_i. This exposes the geometry terms without separately differentiating every inverse entry. Fixed-grid directions set d˙=w˙=M˙=0\dot d=\dot w=\dot M=0; shape directions cannot. The face player uses the sample seed u˙i=vi=sin⁡(i+1)\dot u_i=v_i=\sin(i+1); the tangent-solve panel later computes actual parameter sensitivities.

图 8.6Figure 8.6
第 9 节Section 9

matrix-free 伴随:计算 ATw

Matrix-free adjoint mode: computing ATw

把一维通量反过来

Reversing the one-dimensional flux

前向的一维通量是“两个输入、一个输出”:(u̇L,u̇R)↦ḣ=cLu̇L+cRu̇R。它的转置是“一个输入、两个输出”:给面上的权重 h̄,返回

The forward one-dimensional flux is "two inputs, one output": (u̇L,u̇R)↦ḣ=cLu̇L+cRu̇R. Its transpose is "one input, two outputs": given a weight h̄ on the face, it returns

(ūL,ūR)=(cLh̄,cRh̄).

整个边循环也按同样的规则倒过来:原来写 r[i] 的地方现在读 w[i],原来读 u[i] 的地方现在写 y[i]。注意符号:原来是 r[i]+=h、r[j]−=h,转置后就是 h̄=w[i]−w[j];两个分发则都是加。按第 8 节的记号,种子是 R̄=w,结果是 ū=y=ATw。

The whole edge loop reverses by the same rule: where it used to write r[i] it now reads w[i], and where it used to read u[i] it now writes y[i]. Mind the signs: what was r[i]+=h and r[j]−=h becomes h̄=w[i]−w[j] after transposing, while both scatters are additions. In the notation of Section 8, the seed is R̄=w and the result is ū=y=ATw.

在图 9.1 里对比第 8 节:底部数组的箭头方向反过来了——输入权重 w 在下面(残差一侧),结果 y 在上面(状态一侧)。这就是“转置”在数据流上的样子。无粘壁面半面上 cL=0,所以它在伴随里同样什么都不贡献。

Compare Figure 9.1 with Section 8: the arrows on the bottom arrays have reversed — the input weights w are below (the residual side) and the result y above (the state side). That is what "transpose" looks like as data flow. On an inviscid wall half-face cL=0, so it contributes nothing in the adjoint either.

图 9.1Figure 9.1

三个函数逐行对照

The three functions, line by line

步骤Stepresidual(原始)residual (primal)residual_d(前向)residual_d (forward)residual_b(伴随)residual_b (adjoint)
收集GatheruL ← u[i],uR ← u[j]uL ← u[i], uR ← u[j]vL ← v[i],vR ← v[j]vL ← v[i], vR ← v[j]h_b ← w[i] − w[j]
面上计算Computation on the faceh ← flux(uL, uR)h_d ← cL·vL + cR·vR(uL_b, uR_b) ← (cL·h_b, cR·h_b)
分发Scatterr[i] += h,r[j] −= hr[i] += h, r[j] −= hr_d[i] += h_d,r_d[j] −= h_dr_d[i] += h_d, r_d[j] −= h_dy[i] += uL_b,y[j] += uR_by[i] += uL_b, y[j] += uR_b
边界半面Boundary half-faceuR 由边界条件给出uR given by the boundary conditionvR:入流 0,出流 vL;壁面半面 h_d = 0vR: inflow 0, outflow vL; wall half-face h_d = 0不减 w[j];出流把 uR_b 折回 y[i],入流丢弃w[j] not subtracted; outflow folds uR_b into y[i], inflow drops it
数据方向Data direction状态 → 残差state → residual状态 → 残差state → residual残差 → 状态residual → state

前向与伴随的镜像。图 9.2 把第 8 节的前向边循环和本节的伴随边循环放在一起走:每处理一个面,左边在 A 里写下它的 2×2 块(边界面只写一个对角元,壁面什么也不写),右边在 AT 里写下同样的四个数,位置是转置后的。走完所有的面,两个矩阵互为转置,而且左边正是第 6 节显式装配的 Jacobian,面板的最后一步核对这两点。

The forward and the adjoint loop, mirrored. Figure 9.2 runs the forward edge loop of Section 8 and the adjoint edge loop of this section together: at each face the left grid receives the face's 2×2 block of A (a boundary face just one diagonal entry, a wall face nothing) and the right grid the same four numbers at the transposed positions of AT. Once every face is done, the two matrices are transposes of each other and the left one is the Jacobian assembled explicitly in Section 6; the last step of the panel checks both.

图 9.2Figure 9.2
交互:前向与伴随的镜像
Interactive: the forward and the adjoint loop, mirrored
每个面在 A 里写下的块(左)与在 AT 里写下的同一组数(右);当前面的块在两边同时点亮
The block each face writes into A (left) and the same numbers written into AT (right); the current face is lit on both sides

点积测试

The dot-product test

怎样确认 residual_b 写对了?利用对偶关系 R̄TṘ=ūTu̇,也就是 ⟨w,Av⟩=⟨ATw,v⟩:任取两个向量,分别调用前向和伴随函数,比较两个内积。只要有一个系数或符号写错,随机向量几乎必然让两者不相等(图 9.3)。

How do you confirm that residual_b is written correctly? Use the duality R̄TṘ=ūTu̇, that is ⟨w,Av⟩=⟨ATw,v⟩: take any two vectors, call the forward and the adjoint function, and compare the two inner products. If even one coefficient or sign is wrong, random vectors will almost certainly make the two disagree (Figure 9.3).

图 9.3Figure 9.3
交互:点积测试
Interactive: the dot-product test
每次点击生成两组新的随机向量
Each click generates two fresh random vectors

逐步推导:把面循环逐句转置

Step by step: transposing the face loop statement by statement

第 8 节的前向循环里,每个对偶面做三件线性的事。把它们写成三个小矩阵相乘,转置就成了机械的一步:顺序倒过来,每个矩阵各自转置。图 9.4 把三种循环并排画出。

In the tangent loop of Section 8 every dual face does three linear things. Written as a product of three small matrices, transposing becomes mechanical: reverse the order and transpose each factor. Figure 9.4 draws the three loops side by side.

第一步,对偶面 ff 的三行是三个线性映射(记号与上面“局部导数怎样组成全局 Jacobian”一段相同;这里只对状态求导,网格不动,面的几何 𝒏f\boldsymbol n_f、ℓf\ell_f 随设计变化,归 KfK_f,第 12 节讲它):收集 TfT_f 从 vv 取出两个状态槽位,z˙f\dot z_f 里随状态变的两项是 (u˙L,u˙R)T=Tfv(\dot u_L,\dot u_R)^T=T_fv,内部对偶面的 TfT_f 两行是 𝒆LT\boldsymbol e_L^T、𝒆RT\boldsymbol e_R^T;通量 Hz,fH_{z,f} 对这两个槽位的两项是 (cL, cR)(c_L,\ c_R),它们把两个槽位变成一个数 h˙=(cL, cR) (u˙L,u˙R)T\dot h=(c_L,\ c_R)\,(\dot u_L,\dot u_R)^T;分发 ℬ:,f=𝒆L−𝒆R\mathcal B_{:,f}=\boldsymbol e_L-\boldsymbol e_R 把 h˙\dot h 加到 LL、从 RR 减去。所以

Step 1. The three lines of dual face ff are three linear maps (the notation of the block “How local derivatives form the global Jacobian” above; only the state is differentiated here, the mesh does not move, and the face geometry 𝒏f\boldsymbol n_f, ℓf\ell_f changes with the design, so it belongs to KfK_f, which Section 12 is about): the gather TfT_f picks the two state slots out of vv, the two entries of z˙f\dot z_f that move with the state are (u˙L,u˙R)T=Tfv(\dot u_L,\dot u_R)^T=T_fv, and for an interior dual face its two rows are 𝒆LT\boldsymbol e_L^T and 𝒆RT\boldsymbol e_R^T; the two entries of Hz,fH_{z,f} for these slots are (cL, cR)(c_L,\ c_R), and they turn the two slots into one number h˙=(cL, cR) (u˙L,u˙R)T\dot h=(c_L,\ c_R)\,(\dot u_L,\dot u_R)^T; the scatter ℬ:,f=𝒆L−𝒆R\mathcal B_{:,f}=\boldsymbol e_L-\boldsymbol e_R adds h˙\dot h to LL and subtracts it from RR. Hence

Av=∑fℬ:,f Hz,f Tf v,A=∑fℬ:,f Hz,f Tf.Av=\sum_f\mathcal B_{:,f}\,H_{z,f}\,T_f\,v,\qquad A=\sum_f\mathcal B_{:,f}\,H_{z,f}\,T_f.

第二步,乘积的转置要反序,(ℬ:,fHz,fTf)T=TfTHz,fTℬ:,fT(\mathcal B_{:,f}H_{z,f}T_f)^T=T_f^TH_{z,f}^T\mathcal B_{:,f}^T,和则逐项转置:

Step 2. The transpose of a product reverses the order, (ℬ:,fHz,fTf)T=TfTHz,fTℬ:,fT(\mathcal B_{:,f}H_{z,f}T_f)^T=T_f^TH_{z,f}^T\mathcal B_{:,f}^T, and a sum transposes term by term:

ATw=∑fTfT Hz,fT ℬ:,fT w.A^Tw=\sum_fT_f^T\,H_{z,f}^T\,\mathcal B_{:,f}^T\,w.

第三步,从右往左读,每个因子就是伴随循环的一行:

Step 3. Read from right to left, each factor is one line of the adjoint loop:

ℬ:,fTw=wL−wR=hˉ,Hz,fThˉ=(cLhˉcRhˉ)=(uˉLuˉR),TfT(uˉLuˉR)=uˉL 𝒆L+uˉR 𝒆R.\begin{gathered}\mathcal B_{:,f}^Tw=w_L-w_R=\bar h,\qquad H_{z,f}^T\bar h=\begin{pmatrix}c_L\bar h\\c_R\bar h\end{pmatrix}=\begin{pmatrix}\bar u_L\\\bar u_R\end{pmatrix},\\ T_f^T\begin{pmatrix}\bar u_L\\\bar u_R\end{pmatrix}=\bar u_L\,\boldsymbol e_L+\bar u_R\,\boldsymbol e_R.\end{gathered}

这就是上面表格 residual_b 一列的三个步骤行(收集、面上计算、分发;种子 w=Rˉw=\bar R,结果 y=uˉy=\bar u,记号见第 8 节)。分发为什么转置成收集、取值为什么转置成带累加的分发,见本节末尾“从切向量到伴随:转置把语句顺序反过来”一段。这些数落在 AA 与 ATA^T 的哪些位置,上面的镜像面板(图 9.2)逐面演示。

These are the three step rows (gather, face computation, scatter) of the residual_b column of the table above (seed w=Rˉw=\bar R, result y=uˉy=\bar u, the notation of Section 8). Why the scatter transposes into a gather, and the gather into a scatter with accumulation, is explained in the block “From tangent to adjoint: the transpose runs the statements backwards” at the end of this section. Where these numbers land in AA and in ATA^T, face by face, is what the mirror panel above (Figure 9.2) plays.

第四步,边界半面只改 TfT_f 或 Hz,fH_{z,f},而且都没有右节点,ℬ:,f=𝒆L\mathcal B_{:,f}=\boldsymbol e_L:

Step 4. Boundary half-faces change only TfT_f or Hz,fH_{z,f}, and have no right node, ℬ:,f=𝒆L\mathcal B_{:,f}=\boldsymbol e_L:

Tfin=(𝒆LT𝟎T),Tfout=(𝒆LT𝒆LT),Hz,fwall=(0, 0).T_f^{\mathrm{in}}=\begin{pmatrix}\boldsymbol e_L^T\\\boldsymbol 0^T\end{pmatrix},\qquad T_f^{\mathrm{out}}=\begin{pmatrix}\boldsymbol e_L^T\\\boldsymbol e_L^T\end{pmatrix},\qquad H_{z,f}^{\mathrm{wall}}=(0,\ 0).

转置之后:入流 (Tfin)T(uˉL,uˉR)T=uˉL 𝒆L(T_f^{\mathrm{in}})^T(\bar u_L,\bar u_R)^T=\bar u_L\,\boldsymbol e_L,第二个槽位的 uˉR\bar u_R 被丢掉;出流 (Tfout)T(uˉL,uˉR)T=(uˉL+uˉR) 𝒆L(T_f^{\mathrm{out}})^T(\bar u_L,\bar u_R)^T=(\bar u_L+\bar u_R)\,\boldsymbol e_L,uˉR\bar u_R 折回左节点;壁面什么也不做;边界上 hˉ=wL\bar h=w_L,没有 wRw_R 可减。这正是上面表格“边界半面”一行的规则。格点页的边界半面同样是 LL=节点、R=0R=0,规则一字不改。

Transposed: inflow gives (Tfin)T(uˉL,uˉR)T=uˉL 𝒆L(T_f^{\mathrm{in}})^T(\bar u_L,\bar u_R)^T=\bar u_L\,\boldsymbol e_L and the second slot’s uˉR\bar u_R is dropped; outflow gives (Tfout)T(uˉL,uˉR)T=(uˉL+uˉR) 𝒆L(T_f^{\mathrm{out}})^T(\bar u_L,\bar u_R)^T=(\bar u_L+\bar u_R)\,\boldsymbol e_L and uˉR\bar u_R folds back into the left node; the wall does nothing; on the boundary hˉ=wL\bar h=w_L, with no wRw_R to subtract. These are exactly the rules of the “Boundary half-face” row of the table above. The boundary half-faces of this page also have LL = the node and R=0R=0, and the rules apply word for word.

第五步,各对偶面的项相加时次序可以任意,所以伴随循环不必倒着走对偶面:需要倒序的只是每个对偶面内部的三行。本页的 residual_b 就按与 residual 相同的对偶面次序循环。

Step 5. The dual face terms can be added in any order, so the adjoint loop need not visit the dual faces backwards: only the three lines inside each dual face are reversed. This page’s residual_b loops over the dual faces in the same order as residual.

第六步,一个立刻能做的例子:w≡1w\equiv1。每个内部对偶面上 hˉ=1−1=0\bar h=1-1=0,什么也不分发;只有远场半面留下 cLc_L(壁面 cL=0c_L=0)。所以 AT𝟏A^T\mathbf 1 只在远场节点上非零。这正是第 5 节“内部通量两两抵消”的转置读法:𝟏TA\mathbf 1^TA 是 ∑iRi\sum_iR_i 对 uu 的导数,而 ∑iRi\sum_iR_i 只含远场通量。

Step 6. An example to try at once: w≡1w\equiv1. On every interior dual face hˉ=1−1=0\bar h=1-1=0 and nothing is scattered; only the far-field half-faces leave cLc_L (the wall has cL=0c_L=0). So AT𝟏A^T\mathbf 1 is non-zero only in far-field nodes. This is the cancellation of Section 5 read in transpose: 𝟏TA\mathbf 1^TA is the derivative of ∑iRi\sum_iR_i with respect to uu, and ∑iRi\sum_iR_i contains only far-field fluxes.

核对:两个测试向量的点积测试 ∣⟨w,Av⟩−⟨ATw,v⟩∣/∣⟨w,Av⟩∣=3.1×10−16|\langle w,Av\rangle-\langle A^Tw,v\rangle|/|\langle w,Av\rangle|=3.1\times10^{-16};residual_b(u∗,𝒆i)(u^*,\boldsymbol e_i) 与显式 AA 的第 ii 行逐元比较,24 行的最大差为 0;AT𝟏A^T\mathbf 1 在远场以外的节点上最大为 0(本页代码在构建时计算)。

Check: the dot-product test on two test vectors gives ∣⟨w,Av⟩−⟨ATw,v⟩∣/∣⟨w,Av⟩∣=3.1×10−16|\langle w,Av\rangle-\langle A^Tw,v\rangle|/|\langle w,Av\rangle|=3.1\times10^{-16}; residual_b(u∗,𝒆i)(u^*,\boldsymbol e_i) against row ii of the explicit AA, entry by entry, differs by at most 0 over all 24 rows; AT𝟏A^T\mathbf 1 is at most 0 away from the far field (computed by this page’s code at build time).

收集gather面上计算compute on the face分发scatteru状态stateuL ← u[L]uR ← u[R]h = H(uL, uR)r[L] += hr[R] −= hr残差residualresidual(u):原始residual(u): primalv扰动perturbationvL ← v[L]vR ← v[R]hd = cL vL + cR vRrd[L] += hdrd[R] −= hdAv= rdresidual_d(u, v):前向,同一方向residual_d(u, v): tangent, same directionAT​w= yy[L] += uL,by[R] += uR,b(uL,b, uR,b) = (cL, cR) hbhb ← w[L]hb −= w[R]w残差权重weightsresidual_b(u, w):伴随,反向执行(从右往左读)residual_b(u, w): adjoint, run in reverse (read right to left)原来的收集 → 相加的分发old gather → adding scatter原来的分发 → 收集old scatter → gather边界面:入流丢弃 uR,b,出流把 uR,b 折回 y[L],壁面 cL = cR = 0。Boundary faces: inflow drops uR,b, outflow folds uR,b into y[L], the wall has cL = cR = 0.本页的内部面 f3(L = 1,R = 9):cL = 0.785,cR = −0.236。Interior face f3 of this page (L = 1, R = 9): cL = 0.785, cR = −0.236.
图 9.4 · 同一个面循环的三种写法。方框是存储的数组,箭头是数据流,颜色区分原始(蓝)、前向(绿)与伴随(红)。前两行从左往右:先收集两侧的值,再在面上计算,最后按 +、− 分发;前向只把通量换成它的线性化 ḣ=cLu̇L+cRu̇R(代码写作 h_d,种子 u̇=v)。伴随一行从右往左画:输入 w 在残差一侧,结果 y=ATw 在状态一侧;原来的分发 r[L]+=h、r[R]−=h 转置成收集 h̄=w[L]−w[R](代码写作 h_b;种子 R̄=w,结果 ū=y),原来的收集转置成两次相加的分发。最后两行是边界面的三种变体,以及本页一个真实内部面的两个局部导数。Figure 9.4 · One face loop written three ways. Boxes are stored arrays, arrows are data flow, and the colour tells primal (blue), tangent (green) and adjoint (red) apart. The first two rows run left to right: gather the two sides, compute on the face, scatter with + and −; the tangent only replaces the flux by its linearisation ḣ=cLu̇L+cRu̇R (h_d in the code, with the seed u̇=v). The adjoint row is drawn right to left: its input w sits on the residual side and its result y = ATw on the state side; the old scatter r[L] += h, r[R] −= h transposes into the gather h̄=w[L]−w[R] (h_b in the code; the seed is R̄=w and the result ū=y), and the old gather into a scatter that adds twice. The last two lines give the three boundary variants and the two local derivatives of one real interior face of this page.

逐步推导:每一句倒过来,一个面的伴随程序

Step by step: every statement reversed, the adjoint program of one face

上面把面循环的三行转置了。第 8 节的表把一个面写成了完整的程序(含几何与目标),它的伴随也可以逐句写出。规则只有一条:前向的一句 y˙=∑i(∂y/∂xi)x˙i\dot y=\sum_i(\partial y/\partial x_i)\dot x_i,在反向程序里变成对它的每个输入各一句 xˉi+=(∂y/∂xi)yˉ\bar x_i\mathrel{\text{+=}}(\partial y/\partial x_i)\bar y;各句按相反的顺序执行;所有横线从 0 开始,只累加。

Above, the three lines of the face loop were transposed. The table of Section 8 writes one face as a complete program (geometry and objective included), and its adjoint can be written statement by statement too. There is a single rule: a forward statement y˙=∑i(∂y/∂xi)x˙i\dot y=\sum_i(\partial y/\partial x_i)\dot x_i becomes, in the reverse program, one statement xˉi+=(∂y/∂xi)yˉ\bar x_i\mathrel{\text{+=}}(\partial y/\partial x_i)\bar y for each of its inputs; the statements run in the opposite order; every bar starts at 0 and only accumulates.

y˙=∑i∂y∂xi x˙i⟹xˉi+=∂y∂xi yˉ(∀ i),yˉ y˙=∑ixˉi x˙i.\begin{gathered}\dot y=\sum_i\frac{\partial y}{\partial x_i}\,\dot x_i\quad\Longrightarrow\quad\bar x_i\mathrel{\text{+=}}\frac{\partial y}{\partial x_i}\,\bar y\quad(\forall\,i),\\ \bar y\,\dot y=\sum_i\bar x_i\,\dot x_i.\end{gathered}

最后一式说:每一句的伴随就是它的转置。为什么反向程序要把各句倒过来执行、为什么每个横线量都从 0 开始并用 += 累加,以及这些关系怎样接成整个对偶面、整个循环的对偶关系(本节的点积测试检查的正是它),本节末尾的推导在“从切向量到伴随:转置把语句顺序反过来”与“对偶恒等式的逐步证明,以及它的数值形式:点积测试”两段里,对一个对偶面的四条语句讲清楚了。本页被几句同时读到的变量有:n 被 ℓ\ell 与 vnv_n 读到(壁面半面上没有这两句,n 只被目标那一句读到),出流半面上 uLu_L 被两个槽位读到,每个节点被以它为端点的所有面读到,也被含有它的单元的所有其他对偶面读到(经单元中心)。

The last relation says that the adjoint of a statement is its transpose. Why the reverse program runs the statements backwards, why every bar starts at 0 and accumulates with +=, and how these relations chain into the duality of the whole dual face and the whole loop (the identity that the dot-product test of this section checks) is explained for the four statements of a dual face in the derivation at the end of this section, under “From tangent to adjoint: the transpose runs the statements backwards” and “The duality identity step by step, and its numerical form: the dot-product test”. On this page the variables read by several statements are: n, by ℓ\ell and by vnv_n (a wall half-face has neither statement, and there n is read only by the objective); uLu_L, by both slots on an outflow half-face; each node, by every face that ends at it and by every other dual face of each cell that contains it (through the cell centre).

##切线语句tangent statement伴随语句(倒序;x_b 即 x̄)adjoint statement (reverse order; x_b is x̄)
11(壁面)11 (wall)L_d += uL·(n·e)·uL_d + uL²·(e·n_d)/2u_b[i] += uL·(n·e)·L_b; n_b += uL²·e·L_b/2
10r_d[i] += h_d; r_d[j] −= h_dh_b ← r_b[i] − r_b[j]
9h_d ← cL·uL_d + cR·uR_d + hv·vn_d + hl·len_duL_b += cL·h_b; uR_b += cR·h_b; vn_b += hv·h_b; len_b += hl·h_b
8uL_d ← u_d[i]; uR_d ← u_d[j] (入流 uinf_d,出流 uL_d)uL_d ← u_d[i]; uR_d ← u_d[j] (inflow uinf_d, outflow uL_d)u_b[i] += uL_b; u_b[j] += uR_b (入流 uinf_b += uR_b,出流 u_b[i] += uR_b)u_b[i] += uL_b; u_b[j] += uR_b (inflow uinf_b += uR_b, outflow u_b[i] += uR_b)
7vn_d ← β·n_dn_b += β·vn_b
6len_d ← (n·n_d) / lenn_b += (n / len)·len_b
5n_d ← sum over t of s_t·(d_d[t].y, −d_d[t].x)d_b[t] += s_t·(−nb.y, nb.x)
4d_d[t] ← xc_d[t] − m_dxc_b[t] += d_b[t]; m_b −= d_b[t]
3xc_d[t] ← mean of x_d[q], q in tx_b[q] += xc_b[t] / |t| (q in t)
2m_d ← (x_d[i] + x_d[j]) / 2x_b[i] += m_b / 2; x_b[j] += m_b / 2
1x_d[q] ← X_d[q]X_b[q] += x_b[q]

读这张表:第 10–8 行是本节的 residual_b(第 9 行里给 vˉn\bar v_n、ℓˉ\bar\ell 的两句除外,它们和第 7–1 行一起是第 12 节的 gradient);壁面那一行的伴随给出第 7 节的 gg 与显式的 ∂L/∂X\partial L/\partial X;入流行里 residual_b 丢掉的 uˉR\bar u_R(因为 uR=u∞u_R=u_\infty 不是状态),在设计的伴随里并没有丢:它就是 uˉ∞\bar u_\infty(第 11 节)。

How to read the table: rows 10–8 are residual_b of this section (except the two statements of row 9 that give vˉn\bar v_n and ℓˉ\bar\ell, which belong, with rows 7–1, to the gradient of Section 12); the adjoint of the wall row gives the gg of Section 7 and the explicit ∂L/∂X\partial L/\partial X; and the uˉR\bar u_R that residual_b drops in the inflow row (because uR=u∞u_R=u_\infty is not a state) is not lost in the design adjoint: it is uˉ∞\bar u_\infty (Section 11).

同一个例子倒过来(图 9.5)。种子 Rˉ=ψ\bar R=\psi(第 11 节的伴随解)、Lˉ=1\bar L=1:hˉ=ψ6−ψ14=−0.8379\bar h=\psi_{6}-\psi_{14}={-0.8379},uˉL=cLhˉ=0.0154\bar u_L=c_L\bar h=0.0154,uˉR=cRhˉ=0.6428\bar u_R=c_R\bar h=0.6428,vˉn=−0.7198\bar v_n={-0.7198},ℓˉ=−0.3343\bar\ell={-0.3343},两条路在 𝒏ˉ=𝜷vˉn+(𝒏/ℓ)ℓˉ=(−0.4664, −0.1419)\bar{\boldsymbol n}=\boldsymbol\beta\bar v_n+(\boldsymbol n/\ell)\bar\ell=({-0.4664},\,{-0.1419}) 汇合,再转回节点。这个对偶面上的对偶关系:

The same example reversed (Figure 9.5). With the seeds Rˉ=ψ\bar R=\psi (Section 11's adjoint solution) and Lˉ=1\bar L=1: hˉ=ψ6−ψ14=−0.8379\bar h=\psi_{6}-\psi_{14}={-0.8379}, uˉL=cLhˉ=0.0154\bar u_L=c_L\bar h=0.0154, uˉR=cRhˉ=0.6428\bar u_R=c_R\bar h=0.6428, vˉn=−0.7198\bar v_n={-0.7198}, ℓˉ=−0.3343\bar\ell={-0.3343}; the two paths meet in 𝒏ˉ=𝜷vˉn+(𝒏/ℓ)ℓˉ=(−0.4664, −0.1419)\bar{\boldsymbol n}=\boldsymbol\beta\bar v_n+(\boldsymbol n/\ell)\bar\ell=({-0.4664},\,{-0.1419}), which is then turned back to the nodes. The duality on this dual face:

hˉ h˙=5.099768766×10−2=uˉLu˙L+uˉRu˙R+∑q𝒙ˉq⋅𝒙˙q.\bar h\,\dot h=5.099768766\times10^{-2}=\bar u_L\dot u_L+\bar u_R\dot u_R+\sum_q\bar{\boldsymbol x}_q\cdot\dot{\boldsymbol x}_q.

右边逐项相加得 0.0509976876594891,两边相差 0。本节末尾的“同一个面的反向扫描”(图 9.7)在另一个对偶面(面循环里第一个两侧都不在壁面上的内部面)上逐级核对同样的对偶关系,反向种子同样是 Rˉ=ψ\bar R=\psi,前向种子则是 u˙∞=1\dot u_\infty=1,不是这里的坐标种子。

The terms on the right add up to 0.0509976876594891; the two sides differ by 0. “The reverse sweep of the same face” at the end of this section (Figure 9.7) checks the same duality stage by stage on another dual face (the first interior one whose two sides are off the wall), with the same reverse seed Rˉ=ψ\bar R=\psi but the forward seed u˙∞=1\dot u_\infty=1 instead of a coordinate.

点积测试检查的是转置本身,所以它能抓住伴随里的错,而抓不住前向与伴随共有的错。图 9.6 用随机向量对本页的代码和几个故意写错的伴随各试 200 次。

The dot-product test checks the transpose itself: it catches a mistake in the adjoint, but not one that the forward and the adjoint code share. Figure 9.6 tries this page’s code and a few deliberately wrong adjoints on 200 random pairs each.

核对:把逐句写出的伴随程序(Rˉ\bar R 随机、Lˉ=1\bar L=1)与本页的 residual_b 加 gg 相比,相对差 1.4×10−16;坐标的横线与 gradient 相比 2.3×10−16;uˉ∞\bar u_\infty 与 RˉTf\bar R^Tf 相比 2.6×10−16;整个程序的对偶关系 RˉTR˙+LˉL˙=uˉTu˙+XˉTX˙+uˉ∞u˙∞\bar R^T\dot R+\bar L\dot L=\bar u^T\dot u+\bar X^T\dot X+\bar u_\infty\dot u_\infty 两边的相对差 0(本页代码在构建时计算)。

Check: the adjoint program written statement by statement (random Rˉ\bar R, Lˉ=1\bar L=1) against this page’s residual_b plus gg: relative difference 1.4×10−16; the coordinate bars against gradient: 2.3×10−16; uˉ∞\bar u_\infty against RˉTf\bar R^Tf: 2.6×10−16; the duality of the whole program, RˉTR˙+LˉL˙=uˉTu˙+XˉTX˙+uˉ∞u˙∞\bar R^T\dot R+\bar L\dot L=\bar u^T\dot u+\bar X^T\dot X+\bar u_\infty\dot u_\infty, relative difference of its two sides 0 (computed by this page’s code at build time).

1/|t|1/|t′|½1−11−1stQst′Qn/ℓβ∂h/∂ℓ∂h/∂vncLcR+1−1xq, q∈txq, q∈t′xi, xjxc,txc,t′mdtdt′nℓ = |n|vn = β·nuLuRhRiRjR¯​iR¯​jh¯​u¯Lu¯Rv¯nℓ¯n¯d¯​td¯​t′x¯c,tx¯c,t′m¯x¯qx¯qx¯i,j反向:逆着计算顺序把横线(伴随)从输出累加回输入reverse: the bars (adjoints) accumulate back, outputs to inputs, against the order of the computation
12 / 12
1h_b ← R_b[i] − R_b[j]ψ[6] − ψ[14] = −0.8379
2uL_b ← cL·h_b; uR_b ← cR·h_b0.0154, 0.6428
3vn_b ← hv·h_b; len_b ← hl·h_b−0.7198, −0.3343
4u_b[i] += uL_b; u_b[j] += uR_bu_b[6] += 0.0154; u_b[14] += 0.6428
5n_b ← β·vn_b + (n / len)·len_b(−0.4664, −0.1419)
6d_b[t] ← s_t·(−nb.y, nb.x)t: (−0.1419, 0.4664); t′: (0.1419, −0.4664)
7xc_b[t] ← d_b[t]; m_b ← −(d_b[t] + d_b[t′])m_b = (0.0000, 0.0000)
8x_b[q] += xc_b[t] / |t| (q in t)t: (−0.0355, 0.1166); t′: (0.0355, −0.1166)
9x_b[i] += m_b / 2; x_b[j] += m_b / 2(0, 0)
10X_b[q] += x_b[q]X_b[5, 13], X_b[15, 7]; X_b[6], X_b[14] += 0
种子:R̄ = ψ(第 11 节的伴随解),L̄ = 1。这个对偶面读到 R̄[6] = ψ6 = −1.61073,R̄[14] = ψ14 = −0.77286。伴随语句按与前向相反的顺序执行,每个横线都从 0 开始累加(+=)。The seeds: R̄ = ψ (the adjoint solution of Section 11), L̄ = 1. This dual face reads R̄[6] = ψ6 = −1.61073 and R̄[14] = ψ14 = −0.77286. The adjoint statements run in the opposite order of the forward ones, and every bar starts at 0 and accumulates (+=).
第 1 行:分发 R[6] += h、R[14] −= h 倒过来成了收集,h̄ = R̄[6] − R̄[14] = −0.8379。Line 1: the scatter R[6] += h, R[14] −= h reversed is a gather, h̄ = R̄[6] − R̄[14] = −0.8379.
第 2 行:通量对两个状态槽位的偏导把 h̄ 送回,ūL = cLh̄ = 0.0154,ūR = cRh̄ = 0.6428。Line 2: the partial derivatives of the flux in its two state slots send h̄ back, ūL = cLh̄ = 0.0154, ūR = cRh̄ = 0.6428.
第 3 行:另外两个输入,v̄n = ¼(uL2+uR2)h̄ = −0.7198,ℓ̄ = −ε(uR−uL)h̄ = −0.3343。Line 3: the other two inputs, v̄n = ¼(uL2+uR2)h̄ = −0.7198, ℓ̄ = −ε(uR−uL)h̄ = −0.3343.
第 4 行:收集倒过来成了分发,ū[6] += 0.0154,ū[14] += 0.6428(这是 ATψ 里这个对偶面的一份)。Line 4: the gather reversed is a scatter, ū[6] += 0.0154, ū[14] += 0.6428 (this dual face's share of ATψ).
第 5 行:vn 与 ℓ 都读了 n,两条路在 n̄ 汇合:n̄ = βv̄n + (n/ℓ)ℓ̄ = (−0.7198, −0.3599) + (0.2534, 0.2180) = (−0.4664, −0.1419)。Line 5: both vn and ℓ read n, so the two paths meet in n̄: n̄ = βv̄n + (n/ℓ)ℓ̄ = (−0.7198, −0.3599) + (0.2534, 0.2180) = (−0.4664, −0.1419).
第 6 行:每段的转置,d̄t = stQTn̄ = (−0.1419, 0.4664),d̄t′ = (0.1419, −0.4664):两段方向相反,两个横线也相反。Line 6: the transpose of each segment, d̄t = stQTn̄ = (−0.1419, 0.4664), d̄t′ = (0.1419, −0.4664): the two segments point opposite ways, and so do their bars.
第 7 行:dt = xc,t − m,所以 x̄c,t += d̄t,而 m̄ −= d̄t + d̄t′ = (0.0000, 0.0000):边中点那条路正好抵消。Line 7: dt = xc,t − m, so x̄c,t += d̄t while m̄ −= d̄t + d̄t′ = (0.0000, 0.0000): the midpoint path cancels exactly.
第 8 行:中心是顶点的平均,每个顶点分到 1/|t|:t 的顶点各 += (−0.0355, 0.1166),t′ 的顶点各 += (0.0355, −0.1166)。节点 6、14 同时是两个单元的顶点,两份正好抵消(两个单元的顶点数相同)。Line 8: a centre is the average of its vertices, so each vertex gets 1/|t|: every vertex of t += (−0.0355, 0.1166), every vertex of t′ += (0.0355, −0.1166). Nodes 6 and 14 are vertices of both cells, and their two shares cancel (the two cells have the same number of vertices).
第 9 行:x̄i、x̄j 各 += m̄/2 = (0, 0)。Line 9: x̄i and x̄j each += m̄/2 = (0, 0).
第 10 行:累加进 X̄:这个对偶面最后只动了节点 5、13、15、7,两个端点收到的总量为零(两个单元的顶点数相同时正是如此:两个四边形,或两个三角形)。Line 10: accumulate into X̄: in the end this dual face moves only nodes 5, 13, 15, 7; its two ends receive nothing in total (as happens whenever the two neighbouring cells have the same number of vertices: two quadrilaterals or two triangles).
这个对偶面上的对偶关系:h̄ḣ = (−0.83787) × (−0.06087) = 0.05100;不取整时 h̄ḣ = 5.099769×10−2,而 ūLu̇L + ūRu̇R + Σ x̄q·ẋq = 5.099769×10−2(差 0):每一句都是前向那一句的转置,所以整个面也是。The duality on this dual face: h̄ḣ = (−0.83787) × (−0.06087) = 0.05100; unrounded, h̄ḣ = 5.099769×10−2, and ūLu̇L + ūRu̇R + Σ x̄q·ẋq = 5.099769×10−2 (they differ by 0): every statement is the transpose of its forward one, so the whole face is too.
图 9.5 · 同一个对偶面的程序反向播放:每一句的伴随按相反的顺序执行。计算图与上一节的相同,箭头反过来:红色的横线号标出已经累加好的伴随,红色箭头是这一句把横线送回的方向。列表是伴随语句(代码名:x_b 即 x̄),右侧数值由本页代码以 R̄=ψ、L̄=1 算出。最后一步核对这个对偶面上的对偶关系:h̄ḣ 等于各输入的横线与点之积的和。Figure 9.5 · The program of the same dual face, played backward: the adjoint of every statement, in the opposite order. The graph is the one of Section 8 with the arrows reversed: a red barred symbol marks each variable whose adjoint has been accumulated, and the red arrows show where the current statement sends the bars. The listing holds the adjoint statements (code names: x_b is x̄), with the numbers this page’s code computes for R̄ = ψ and L̄ = 1. The last step checks the duality on this dual face: h̄ḣ equals the sum, over its inputs, of bar times dot.
10⁻¹⁸10⁻¹⁵10⁻¹²10⁻⁹10⁻⁶10⁻³10⁰ϵ本页的this page’sresidual_b≤ 1.9×10⁻¹⁶错误 1:出流wrong 1: outflow不折回fold dropped≤ 6.7×10⁻²错误 2:wrong 2:wL + wR≤ 6.8×10⁻¹错误 3:wrong 3:cL、cR 对调cL, cR swapped≤ 1.1×10⁰(a) 状态算子 A(a) the state operator Aϵ本页的this page’sgradient≤ 3.7×10⁻¹⁶错误 4:wrong 4:Q 当 QT​Q for QT​≤ 1.1×10⁰错误 5:丢了wrong 5: length长度那条路path dropped≤ 2.5×10⁻¹(b) 几何算子 ∂R/∂X(b) the geometric operator ∂R/∂X
图 9.6 · 点积测试,每列 200 对随机向量(图中画出前 50 对,纵轴对数),纵轴是 |⟨w, Av⟩ − ⟨ATw, v⟩| / (‖w‖ ‖Av‖):分母是 |⟨w, Av⟩| 的上界,所以不会有哪一对只因内积碰巧接近零而显得很差;恰好为零的画在底线上。(a) 状态算子 A:比较 ⟨w, Av⟩ 与 ⟨ATw, v⟩。本页的 residual_b 全部落在机器精度 ϵ 附近(最大 1.9×10⁻¹⁶)。三个故意写错的版本只改一处:出流半面上第二个槽位的横线丢掉而不折回左节点;h̄ 写成 wL+wR;内部对偶面上 cL、cR 对调。(b) 几何算子 ∂R/∂X:比较 ⟨w, (∂R/∂X)Ẋ⟩ 与 ⟨(∂R/∂X)Tw, Ẋ⟩(分母取 ‖w‖ ‖(∂R/∂X)Ẋ‖),前向是 gradient_d,伴随是 gradient 减去显式项。错误 4 在把 n̄ 转回节点时用了 Q 而不是 QT;错误 5 丢了耗散项经 |n| 的那条路。五个错误版本的最小偏差也有 7.0×10⁻⁵,远在 ϵ 之上。错误 4、5 的整体平移检查照样通过(分量之和最大 2.1×10⁻¹⁶):平移不变性只检查每个面送出的量之和,点积测试检查的是转置本身。点积测试的盲区是前向与伴随共用的错误,例如 local() 里写错的 cL:两边一起错,内积仍然相等;这要靠第 13、14 节与差分、复数步长比较。Figure 9.6 · The dot-product test, 200 random pairs per column (the first 50 drawn, logarithmic axis), plotting |⟨w, Av⟩ − ⟨ATw, v⟩| / (‖w‖ ‖Av‖): the denominator bounds |⟨w, Av⟩| from above, so no pair looks bad merely because its product happens to be near zero; exact zeros sit on the bottom line. (a) The state operator A: ⟨w, Av⟩ against ⟨ATw, v⟩. This page’s residual_b stays at the machine precision ϵ for every pair (at most 1.9×10⁻¹⁶). The three deliberately wrong versions each change one thing: on an outflow half-face the second slot’s bar is dropped instead of folded back into the left node; h̄ is written wL + wR; on interior dual faces cL and cR are swapped. (b) The geometric operator ∂R/∂X: ⟨w, (∂R/∂X)Ẋ⟩ against ⟨(∂R/∂X)Tw, Ẋ⟩ (divided by ‖w‖ ‖(∂R/∂X)Ẋ‖), forward by gradient_d and reverse by gradient minus its explicit term. Wrong version 4 turns n̄ back to the nodes with Q instead of QT; wrong version 5 drops the path of the dissipation through |n|. Even the smallest deviation of the five wrong versions is 7.0×10⁻⁵, far above ϵ. Wrong versions 4 and 5 still pass the translation check (component sums at most 2.1×10⁻¹⁶): translational invariance only checks what each face sends out in total, the dot-product test checks the transpose itself. What the test cannot see is a mistake that the forward and the reverse code share, such as a wrong cL in local(): both sides are wrong together and the products still agree; that is what the comparisons with differences and the complex step in Sections 13 and 14 are for.

为什么反向累加给出矩阵转置

Why reverse accumulation gives the matrix transpose

把所有残差和状态分量展平后,下面只是交换两个有限求和的次序。残差种子 Rˉ=w\bar R=w 是任意给定的,尚未求解伴随方程;只有选择满足第 11 节线性方程的 w=ψw=\psi,才得到该目标对应的离散伴随解。取 D˙=0\dot D=0、𝐮˙=v\dot{\mathbf u}=v,前向得到 R˙=Av\dot R=Av;以 Rˉ=w\bar R=w 为种子反向扫描,得到 𝐮ˉ=ATw\bar{\mathbf u}=A^Tw。下式就是 AD 的对偶(点积)恒等式:

After flattening all residual and state components, the following simply exchanges two finite sums. The residual seed Rˉ=w\bar R=w is arbitrary and has not solved an adjoint equation; only w=ψw=\psi satisfying Section 11 gives the discrete adjoint solution for the chosen objective. With D˙=0\dot D=0 and 𝐮˙=v\dot{\mathbf u}=v the forward sweep gives R˙=Av\dot R=Av; the reverse sweep seeded with Rˉ=w\bar R=w gives 𝐮ˉ=ATw\bar{\mathbf u}=A^Tw. The identity below is the duality (dot-product) identity of AD:

RˉTR˙=wT(Av)=∑r=1nu∑j=1nuwr∂Rr∂ujvj\bar R^T\dot R=w^T(Av)=\sum_{r=1}^{n_u}\sum_{j=1}^{n_u}w_r\frac{\partial R_r}{\partial u_j}v_j
=∑j=1nuvj(∑r=1nu∂Rr∂ujwr)=vT(ATw)=𝐮˙T𝐮ˉ.=\sum_{j=1}^{n_u}v_j\left(\sum_{r=1}^{n_u}\frac{\partial R_r}{\partial u_j}w_r\right)=v^T(A^Tw)=\dot{\mathbf u}^T\bar{\mathbf u}.

内部面通量向左加、向右减,所以先把这两条路径合并成通量的伴随 Hˉf\bar H_f;之后按通量、重构、原始状态的相反依赖顺序传播。反向语句中的 += 很重要:同一状态会被多个面和多个求积点读取,这些贡献必须累加。这里没有分部积分,也没有另行猜测连续伴随边界条件。

An interior flux is added on the left and subtracted on the right, so combine those two paths into its adjoint Hˉf\bar H_f before reversing fluxes, reconstruction and stored states. The += is essential: multiple faces and quadrature points read the same state, and their contributions must accumulate. There is no integration by parts and no separately guessed continuous adjoint boundary condition.

RL+=Hf, RR-=HfR_L\mathrel{\text{+=}}H_f,\ R_R\mathrel{\text{-=}}H_f
⟹Hˉf=RˉL−RˉR=wL−wR,\Longrightarrow\quad\bar H_f=\bar R_L-\bar R_R=w_L-w_R,
z˙f=∑j∈𝒮f(Tf)j u˙j+KfD˙\dot z_f=\sum_{j\in\mathcal S_f}(T_f)_j\,\dot u_j+K_f\dot D
⟹uˉj+=(Tf)jT zˉf.\Longrightarrow\quad\bar u_j\mathrel{\text{+=}}(T_f)_j^T\,\bar z_f.

第 8 节局部链里的每个切向量都有对应的伴随。反向语句按与前向相反的顺序执行,每个输入的横线量都用 += 累加:

Every tangent of the local chain of Section 8 has its adjoint. The reverse statements run in the opposite order of the forward ones, and the bar of every input accumulates with +=:

𝐮ˉ←0, Dˉ←0;Hˉf=∑iℬifRˉi,\bar{\mathbf u}\leftarrow0,\ \bar D\leftarrow0;\quad \bar H_f=\sum_i\mathcal B_{if}\bar R_i,
Sˉi=−Rˉi,zˉf=Hz,fTHˉf,\bar S_i=-\bar R_i,\quad \bar z_f=H_{z,f}^T\bar H_f,
𝐮ˉ+=TfTzˉf+(∂S∂𝐮)TSˉ,\bar{\mathbf u}\mathrel{\text{+=}}T_f^T\bar z_f+\Big(\frac{\partial S}{\partial \mathbf u}\Big)^T\bar S,
Dˉ+=KfTzˉf+(∂S∂D)TSˉ.\bar D\mathrel{\text{+=}}K_f^T\bar z_f+\Big(\frac{\partial S}{\partial D}\Big)^T\bar S.

本页没有被替换的残差行:边界条件都在边界面的通量里,所以上面的面循环转置就是完整的 ATA^T。

This page replaces no residual rows: the boundary conditions all sit in boundary-face fluxes, so the transposed face loop above is the complete ATA^T.

从切向量到伴随:转置把语句顺序反过来

From tangent to adjoint: the transpose runs the statements backwards

一个面的切向量是四个初等 Jacobian 的乘积。乘积的转置等于各个因子的转置按相反的顺序相乘:

The tangent of one face is the product of four elementary Jacobians. The transpose of a product is the product of the transposes in the reverse order:

AfT=(ℬ:,f Hz,f Zf 𝒢f)T=𝒢fT ZfT Hz,fT ℬ:,fT,A_f^T=\big(\mathcal B_{:,f}\,H_{z,f}\,Z_f\,\mathcal G_f\big)^T=\mathcal G_f^T\,Z_f^T\,H_{z,f}^T\,\mathcal B_{:,f}^T,
(ℬ:,f Hz,f Kf)T=KfT Hz,fT ℬ:,fT.\big(\mathcal B_{:,f}\,H_{z,f}\,K_f\big)^T=K_f^T\,H_{z,f}^T\,\mathcal B_{:,f}^T.

仍从右往左读,这就是反向扫描的语句顺序:程序最后执行的分发最先处理,最先执行的取值最后处理。先把所有伴随置零,再以 Rˉ=w\bar R=w 为种子逐条执行:

Read again 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 Rˉ=w\bar R=w, and run:

(0)𝐮ˉ←0,  Dˉ←0,  Rˉ=w,(0)\quad \bar{\mathbf u}\leftarrow0,\ \ \bar D\leftarrow0,\ \ \bar R=w,
(4′)Hˉf=ℬ:,fTRˉ=RˉL−RˉR,(4')\quad \bar H_f=\mathcal B_{:,f}^T\bar R=\bar R_L-\bar R_R,
(3′)zˉf=Hz,fT Hˉf,(3')\quad \bar z_f=H_{z,f}^T\,\bar H_f,
(2′)𝐮ˉf=ZfT zˉf,Dˉ+=KfT zˉf,(2')\quad \bar{\mathbf u}_f=Z_f^T\,\bar z_f,\quad \bar D\mathrel{\text{+=}}K_f^T\,\bar z_f,
(1′)𝐮ˉ+=𝒢fT 𝐮ˉf.(1')\quad \bar{\mathbf u}\mathrel{\text{+=}}\mathcal G_f^T\,\bar{\mathbf u}_f.

面循环结束后,源项的一对也按同样的规则反过来:Sˉi=−Rˉi\bar S_i=-\bar R_i,𝐮ˉ+=(∂S/∂𝐮)TSˉ\bar{\mathbf u}\mathrel{\text{+=}}(\partial S/\partial \mathbf u)^T\bar S,Dˉ+=(∂S/∂D)TSˉ\bar D\mathrel{\text{+=}}(\partial S/\partial D)^T\bar S。本页代码里每一条语句的伴随语句(顺序与前向相反)也都在本节里逐句写出。

After the face loop the source is reversed by the same rule: Sˉi=−Rˉi\bar S_i=-\bar R_i, 𝐮ˉ+=(∂S/∂𝐮)TSˉ\bar{\mathbf u}\mathrel{\text{+=}}(\partial S/\partial \mathbf u)^T\bar S, Dˉ+=(∂S/∂D)TSˉ\bar D\mathrel{\text{+=}}(\partial S/\partial D)^T\bar S. The adjoint statement of every statement of this page's code, in the reverse order, is written out in this section as well.

边界面执行同样的语句。远场入流面上 zfz_f 的右槽位不是存储状态,而是 uR=u∞u_R=u_\infty,所以 ZfZ_f 的这一行为零:(2′)中 ZfTZ_f^T 不把它的伴随 uˉR\bar u_R 传给 𝐮ˉf\bar{\mathbf u}_f,KfTK_f^T 则把它加进 Dˉ\bar D 的 u∞u_\infty 分量。

A boundary face runs the same statements. On a far-field inflow face the right slot of zfz_f is not a stored state but uR=u∞u_R=u_\infty, so its row of ZfZ_f is zero: in (2′), ZfTZ_f^T passes none of its adjoint uˉR\bar u_R to 𝐮ˉf\bar{\mathbf u}_f, and KfTK_f^T adds it to the u∞u_\infty component of Dˉ\bar D.

分发与取值互为转置。分发 R+=ℬ:,fHfR\mathrel{\text{+=}}\mathcal B_{:,f}H_f 把一个面通量写进两行;它的转置 Hˉf=ℬ:,fTRˉ\bar H_f=\mathcal B_{:,f}^T\bar R 从这两行读回一个数,是一次取值。取值 𝐮f=𝒢f𝐮\mathbf u_f=\mathcal G_f\mathbf u 从全局数组读出几个状态;它的转置 𝒢fT\mathcal G_f^T 把它们的伴随加回原来的位置,是一次带累加的分发。

Scatter and gather are transposes of each other. The scatter R+=ℬ:,fHfR\mathrel{\text{+=}}\mathcal B_{:,f}H_f writes one face flux into two rows; its transpose Hˉf=ℬ:,fTRˉ\bar H_f=\mathcal B_{:,f}^T\bar R reads those two rows back into one number: a gather. The gather 𝐮f=𝒢f𝐮\mathbf u_f=\mathcal G_f\mathbf u reads a few states out of the global array; its transpose 𝒢fT\mathcal G_f^T adds their adjoints back into their places: a scatter with accumulation.

为什么要先置零、再用 += 累加:程序里的一次扇出(一个值被多处读取)在反向扫描里变成扇入。一个存储状态属于它每个相邻面的 𝒮f\mathcal S_f,设计变量也进入许多面与源项;链式法则对变量的每一次使用各给出一项,xˉ\bar x 是所有这些项的和:

Why zero first and then +=: a fan-out of the program (one value read in several places) becomes a fan-in of the reverse sweep. A stored state belongs to the 𝒮f\mathcal S_f of every neighbouring face, and the design enters many faces and the sources; the chain rule gives one term per use of a variable, and xˉ\bar x is the sum of all these terms:

uˉj=∑f: j∈𝒮f(𝒢fT 𝐮ˉf)j+(∂S∂uj)TSˉ.\bar u_j=\sum_{f:\ j\in\mathcal S_f}\big(\mathcal G_f^T\,\bar{\mathbf u}_f\big)_j+\Big(\frac{\partial S}{\partial u_j}\Big)^T\bar S.

xˉ←0\bar x\leftarrow0 让这个和从零开始,每个 += 加上一次使用的贡献。反过来,程序里的扇入(RLR_L 收集它各个面的通量)在反向扫描里变成扇出:RˉL\bar R_L 只被读取,不需要累加。

xˉ←0\bar x\leftarrow0 starts this sum at zero, and each += adds the term of one use. Conversely, a fan-in of the program (RLR_L collecting the fluxes of all its faces) becomes a fan-out of the reverse sweep: RˉL\bar R_L is only read and needs no accumulation.

对偶恒等式的逐步证明,以及它的数值形式:点积测试

The duality identity step by step, and its numerical form: the dot-product test

把同一个面的前向语句与反向语句配对。每一步只用一次转置的定义 ⟨xˉ,ℰx˙⟩=⟨ℰTxˉ,x˙⟩\langle\bar x,\mathcal E\dot x\rangle=\langle\mathcal E^T\bar x,\dot x\rangle,把一个初等 Jacobian 从内积的右边移到左边,所以内积在每一级都相同:

Pair the forward and reverse statements of the same face. Each step uses the definition of the transpose once, ⟨xˉ,ℰx˙⟩=⟨ℰTxˉ,x˙⟩\langle\bar x,\mathcal E\dot x\rangle=\langle\mathcal E^T\bar x,\dot x\rangle, to move one elementary Jacobian from the right of the inner product to the left, so the inner product is the same at every stage:

RˉT(ℬ:,f H˙f)=(ℬ:,fTRˉ)TH˙f=HˉfTH˙f,\bar R^T\big(\mathcal B_{:,f}\,\dot H_f\big)=\big(\mathcal B_{:,f}^T\bar R\big)^T\dot H_f=\bar H_f^T\dot H_f,
HˉfTH˙f=HˉfTHz,f z˙f=(Hz,fTHˉf)Tz˙f=zˉfTz˙f,\bar H_f^T\dot H_f=\bar H_f^TH_{z,f}\,\dot z_f=\big(H_{z,f}^T\bar H_f\big)^T\dot z_f=\bar z_f^T\dot z_f,
zˉfTz˙f=zˉfT(Zf𝐮˙f+KfD˙)\bar z_f^T\dot z_f=\bar z_f^T\big(Z_f\dot{\mathbf u}_f+K_f\dot D\big)
=𝐮ˉfT𝐮˙f+(KfTzˉf)TD˙,=\bar{\mathbf u}_f^T\dot{\mathbf u}_f+\big(K_f^T\bar z_f\big)^T\dot D,
𝐮ˉfT𝐮˙f=𝐮ˉfT𝒢f 𝐮˙=(𝒢fT𝐮ˉf)T𝐮˙.\bar{\mathbf u}_f^T\dot{\mathbf u}_f=\bar{\mathbf u}_f^T\mathcal G_f\,\dot{\mathbf u}=\big(\mathcal G_f^T\bar{\mathbf u}_f\big)^T\dot{\mathbf u}.

对所有面求和,再加上源项的一对 SˉTS˙\bar S^T\dot S(Sˉ=−Rˉ\bar S=-\bar R),右边的两项正好累加成反向扫描的输出 𝐮ˉ\bar{\mathbf u} 与 Dˉ\bar D:

Summed over all faces, with the pair of the source added (SˉTS˙\bar S^T\dot S with Sˉ=−Rˉ\bar S=-\bar R), the two terms on the right accumulate exactly into the outputs 𝐮ˉ\bar{\mathbf u} and Dˉ\bar D of the reverse sweep:

RˉTR˙=𝐮ˉT𝐮˙+DˉTD˙.\bar R^T\dot R=\bar{\mathbf u}^T\dot{\mathbf u}+\bar D^T\dot D.

这就是 AD 的点积测试:任取种子 (𝐮˙,D˙)(\dot{\mathbf u},\dot D) 与 Rˉ\bar R,前向扫描算出 R˙\dot R,反向扫描算出 (𝐮ˉ,Dˉ)(\bar{\mathbf u},\bar D),两边之差应在舍入误差量级;只要有一条反向语句的转置写错,差就是 O(1)O(1) 的。本页在构建时用它自己的 matrix-free 前向与反向乘积(nu=24n_u=24 个分量的确定性种子,设计方向取下面算例的 DkD_k)做了这个测试:RˉTR˙=2.21133343664\bar R^T\dot R=2.21133343664,𝐮ˉT𝐮˙+DˉTD˙=2.21133343664\bar{\mathbf u}^T\dot{\mathbf u}+\bar D^T\dot D=2.21133343664,相对差 4×10−164\times10^{-16}。

This is the dot-product test of AD: take any seeds (𝐮˙,D˙)(\dot{\mathbf u},\dot D) and Rˉ\bar R, run the forward sweep for R˙\dot R and the reverse sweep for (𝐮ˉ,Dˉ)(\bar{\mathbf u},\bar D); the two sides must agree to round-off, and a single wrong transpose in the reverse statements gives an O(1)O(1) difference. This page ran the test on its own matrix-free forward and reverse products at build time (deterministic seeds with nu=24n_u=24 components, the design direction the DkD_k of the example below): RˉTR˙=2.21133343664\bar R^T\dot R=2.21133343664 and 𝐮ˉT𝐮˙+DˉTD˙=2.21133343664\bar{\mathbf u}^T\dot{\mathbf u}+\bar D^T\dot D=2.21133343664, a relative difference of 4×10−164\times10^{-16}.

同一个面的反向扫描

The reverse sweep of the same face

反向种子取本页伴随求解的解 Rˉ=ψ\bar R=\psi,于是 𝐮ˉf\bar{\mathbf u}_f 与 Dˉk\bar D_k 是这个面对 ATψA^T\psi 与 RDTψR_D^T\psi 的贡献。下表按反向语句的顺序列出伴随值;最后一列是每一级的内积:先是 Rˉ9TR˙9+Rˉ10TR˙10\bar R_{9}^T\dot R_{9}+\bar R_{10}^T\dot R_{10},然后是 hˉTh˙\bar h^T\dot h,最后是 𝐮ˉfT𝐮˙f+DˉTD˙\bar{\mathbf u}_f^T\dot{\mathbf u}_f+\bar D^T\dot D,切向量取第 8 节“本页一个面的算例:前向扫描”里同一个面的前向算例的值。

Seed the reverse sweep with this page's adjoint solution, Rˉ=ψ\bar R=\psi, so that 𝐮ˉf\bar{\mathbf u}_f and Dˉk\bar D_k are this face's contributions to ATψA^T\psi and RDTψR_D^T\psi. The table lists the adjoints in the order of the reverse statements; the last column is the inner product at each stage: first Rˉ9TR˙9+Rˉ10TR˙10\bar R_{9}^T\dot R_{9}+\bar R_{10}^T\dot R_{10}, then hˉTh˙\bar h^T\dot h, and last 𝐮ˉfT𝐮˙f+DˉTD˙\bar{\mathbf u}_f^T\dot{\mathbf u}_f+\bar D^T\dot D, with the tangents of the forward example of the same face, the one under “A worked example on this page: the forward sweep of one face” in Section 8.

语句Statement量Quantity伴随Adjoint内积Inner product
(0)置零(0) zero𝐮ˉ←0, Dˉ←0\bar{\mathbf u}\leftarrow0,\ \bar D\leftarrow00
(4′)种子(4′) seedRˉ9=ψ9\bar R_{9}=\psi_90.0674−2.531408882×10⁻⁴
Rˉ10=ψ10\bar R_{10}=\psi_{10}0.06643
(4′)取值(4′) gatherhˉ=Rˉ9−Rˉ10\bar h=\bar R_{9}-\bar R_{10}9.632×10⁻⁴−2.531408882×10⁻⁴
(3′)至(1′)加回(3′)–(1′) add backuˉ9+=\bar u_{9}\mathrel{\text{+=}}2.635×10⁻⁴−2.531408882×10⁻⁴
uˉ10+=\bar u_{10}\mathrel{\text{+=}}−5.054×10⁻⁴
Dˉk+=\bar D_k\mathrel{\text{+=}}0

3 级内积相同,最大相对差 9×10−159\times10^{-15}:对偶恒等式在这些数上成立;图 9.7 按反向语句逐条播放这些数。这个面不直接依赖 DkD_k,所以 Dˉk=0\bar D_k=0;DkD_k 的伴随来自它真正进入的那些行。

The 3 inner products agree, with a largest relative difference of 9×10−159\times10^{-15}: the duality identity holds in these numbers. Figure 9.7 plays them statement by statement in reverse. This face does not depend on DkD_k directly, so Dˉk=0\bar D_k=0; the adjoint of DkD_k comes from the rows it does enter.

图 9.7Figure 9.7
同一个面的反向扫描
The reverse sweep of the same face

箭头全部反向,每个初等 Jacobian 转置,顺序相反;存储状态与设计变量处用 += 累加。下方:转置的乘积与形状。

Every arrow reversed, every elementary Jacobian transposed, in the reverse order; the stored states and the design accumulate with +=. Below: the transposed product and its shapes.

全部存储状态all stored statesuu¯​+=面读取的状态states the face readsuf​u¯​f​面输入:重构值等face inputszf​z¯​f​面通量face fluxHf​H¯​f​两侧残差行rows L and RRL, RR​R¯​L, R¯​R​设计输入design inputDD¯​+=𝒢fT​ZfT​Hz,fT​ℬ:,fT​KfT​AfT=𝒢fT​24×2ZfT​2×5Hz,fT​5×1ℬ:,fT​1×24R¯​u¯​从右往左作用:先转置的分发(最右),最后转置的取值(最左)applied right to left: the transposed scatter (rightmost) first, the transposed gather last全部状态all statesuu¯​+=模板上的状态stenciluf​u¯​f​面输入face inputszf​z¯​f​面通量face fluxHf​H¯​f​L、R 两行rows L, RRL, RR​R¯​L, R¯​R​设计输入design inputDD¯​+=𝒢fT​ZfT​Hz,fT​ℬ:,fT​KfT​AfT=𝒢fT​24×2ZfT​2×5Hz,fT​5×1ℬ:,fT​1×24R¯​u¯​从右往左作用:applied right to left: the先转置的分发,最后转置的取值transposed scatter first, gather last
  1. (0)置零并给种子:𝐮ˉ←0\bar{\mathbf u}\leftarrow0,Dˉ←0\bar D\leftarrow0;Rˉ9=ψ9=\bar R_{9}=\psi_9= 0.0674,Rˉ10=ψ10=\bar R_{10}=\psi_{10}= 0.06643。

    (0) Zero and seed: 𝐮ˉ←0\bar{\mathbf u}\leftarrow0, Dˉ←0\bar D\leftarrow0; Rˉ9=ψ9=\bar R_{9}=\psi_9= 0.0674, Rˉ10=ψ10=\bar R_{10}=\psi_{10}= 0.06643.

  2. (4′)分发的转置是一次取值:hˉ=Rˉ9−Rˉ10=\bar h=\bar R_{9}-\bar R_{10}= 9.632×10⁻⁴。内积 Rˉ9TR˙9+Rˉ10TR˙10=hˉTh˙=\bar R_{9}^T\dot R_{9}+\bar R_{10}^T\dot R_{10}=\bar h^T\dot h= −2.531408882×10⁻⁴。

    (4′) The transposed scatter is a gather: hˉ=Rˉ9−Rˉ10=\bar h=\bar R_{9}-\bar R_{10}= 9.632×10⁻⁴. Inner product Rˉ9TR˙9+Rˉ10TR˙10=hˉTh˙=\bar R_{9}^T\dot R_{9}+\bar R_{10}^T\dot R_{10}=\bar h^T\dot h= −2.531408882×10⁻⁴.

  3. (3′)通量的转置:zˉf=Hz,fThˉ\bar z_f=H_{z,f}^T\bar h;一阶格式的面输入就是两侧状态,zˉf\bar z_f 直接给出它们的伴随,面积向量那一部分交给下一步的 KfTK_f^T。

    (3′) The transposed flux: zˉf=Hz,fThˉ\bar z_f=H_{z,f}^T\bar h; at first order the face inputs are the two states, and zˉf\bar z_f is their adjoint, the area-vector part going on to KfTK_f^T in the next step.

  4. (2′)重构的转置:𝐮ˉf=ZfTzˉf\bar{\mathbf u}_f=Z_f^T\bar z_f,Dˉ+=KfTzˉf\bar D\mathrel{\text{+=}}K_f^T\bar z_f,这个面给出 Dˉk=\bar D_k= 0(Kfek=0K_fe_k=0)。

    (2′) The transposed reconstruction: 𝐮ˉf=ZfTzˉf\bar{\mathbf u}_f=Z_f^T\bar z_f and Dˉ+=KfTzˉf\bar D\mathrel{\text{+=}}K_f^T\bar z_f; this face gives Dˉk=\bar D_k= 0 (Kfek=0K_fe_k=0).

  5. (1′)取值的转置是带累加的分发:𝐮ˉ+=𝒢fT𝐮ˉf\bar{\mathbf u}\mathrel{\text{+=}}\mathcal G_f^T\bar{\mathbf u}_f,uˉ9+=\bar u_{9}\mathrel{\text{+=}} 2.635×10⁻⁴,uˉ10+=\bar u_{10}\mathrel{\text{+=}} −5.054×10⁻⁴。内积 𝐮ˉfT𝐮˙f+DˉTD˙=\bar{\mathbf u}_f^T\dot{\mathbf u}_f+\bar D^T\dot D= −2.531408882×10⁻⁴。

    (1′) The transposed gather is a scatter with accumulation: 𝐮ˉ+=𝒢fT𝐮ˉf\bar{\mathbf u}\mathrel{\text{+=}}\mathcal G_f^T\bar{\mathbf u}_f, uˉ9+=\bar u_{9}\mathrel{\text{+=}} 2.635×10⁻⁴, uˉ10+=\bar u_{10}\mathrel{\text{+=}} −5.054×10⁻⁴. Inner product 𝐮ˉfT𝐮˙f+DˉTD˙=\bar{\mathbf u}_f^T\dot{\mathbf u}_f+\bar D^T\dot D= −2.531408882×10⁻⁴.

  6. 对偶:3 级内积相同(最大相对差 9×10⁻¹⁵),RˉTR˙=𝐮ˉT𝐮˙+DˉTD˙\bar R^T\dot R=\bar{\mathbf u}^T\dot{\mathbf u}+\bar D^T\dot D 在这个面上逐级成立。

    Duality: the 3 inner products agree (largest relative difference 9×10⁻¹⁵); RˉTR˙=𝐮ˉT𝐮˙+DˉTD˙\bar R^T\dot R=\bar{\mathbf u}^T\dot{\mathbf u}+\bar D^T\dot D holds stage by stage on this face.

新增(选读):光滑特征边界模型
Added (optional): smooth characteristic-boundary model

伴随:反向传播并累加到整个重构邻域

Adjoint: reverse and accumulate through the reconstruction stencil

内部面通量的反向种子仍是 Hˉ=wL−wR\bar H=w_L-w_R(残差种子 Rˉ=w\bar R=w,横线表示伴随,主线第 8 节的记号)。新增的是面状态向梯度和中心值反传,再把梯度的伴随 γˉi\bar\gamma_i 传给所有最小二乘邻点。共享节点、中心和几何量上的贡献都要累加。面板逐面分解完整反向贡献,包括该面的重构链;它不是仅把旧通量的两个局部导数调换位置。

An internal face still receives the reverse seed Hˉ=wL−wR\bar H=w_L-w_R (the residual seed is Rˉ=w\bar R=w; the bar is the adjoint, the notation of Section 8 of the main line). Trace adjoints now pass to gradients and centre values, then the gradient adjoint γˉi\bar\gamma_i to all least-squares neighbours. Accumulate every path to shared nodes, centres and geometry. The panel decomposes the complete reverse contribution of each face, including reconstruction; it does not merely swap two old flux derivatives.

Hˉ=wL−wR,uˉi+=uˉf,γˉi+=(xf−xi) uˉf,RˉTR˙=uˉTu˙  (wTAv=vTATw).\bar H=w_L-w_R,\quad\bar u_i\mathrel{\text{+=}}\bar u_f,\quad\bar\gamma_i\mathrel{\text{+=}}(x_f-x_i)\,\bar u_f,\qquad\bar R^T\dot R=\bar u^T\dot u\ \ (w^TAv=v^TA^Tw).
for face in reverse order:
    H_b = w[L] - w[R]
    reverse flux, boundary closure and reconstructed traces
    reverse the least-squares dependency for this contribution
    accumulate into all state and coordinate inputs
# Full operator uses the reverse of the same scalar graph.

反向经过线性重构时,一份面迹种子分别累加到中心状态、梯度和重构偏移。反向经过 γi=Mi−1bi\gamma_i=M_i^{-1}b_i 时,先解 MiTbˉi=γˉiM_i^T\bar b_i=\bar\gamma_i 得到 bˉi\bar b_i(右端是梯度的伴随),再得到 Mˉi=−bˉiγiT\bar M_i=-\bar b_i\gamma_i^T。之后沿 M 和 b 的求和定义反传到每个邻点、权重和坐标。同一个邻点可能被多个面访问,所以必须使用 +=。

Reverse a reconstructed trace by accumulating its seed into the centre state, gradient and offset. Reversing γi=Mi−1bi\gamma_i=M_i^{-1}b_i first solves MiTbˉi=γˉiM_i^T\bar b_i=\bar\gamma_i for bˉi\bar b_i, with the gradient’s adjoint on the right, and then gives Mˉi=−bˉiγiT\bar M_i=-\bar b_i\gamma_i^T. Reverse the sums defining M and b into every neighbour, weight and coordinate. Multiple faces share neighbours, so contributions must use +=.

bˉi=Mi−T γˉi,Mˉi=−bˉiγiT,uˉj+=wijdij⋅bˉi,uˉi−=wijdij⋅bˉi.\bar b_i=M_i^{-T}\,\bar\gamma_i,\qquad\bar M_i=-\bar b_i\gamma_i^T,\qquad\bar u_j\mathrel{\text{+=}}w_{ij}d_{ij}\cdot\bar b_i,\quad\bar u_i\mathrel{\text{−=}}w_{ij}d_{ij}\cdot\bar b_i.

上式最后两项只展示固定几何时 b 的状态路径;完整形状反向还包括 M、权重、偏移和边界函数。计算磁带(tape),用最少的话说。程序算一次残差时,把每一步标量运算(加减乘除、开方、正弦)连同它的输入依次记下来,这份记录就叫磁带。前向模式沿记录的顺序传播切线(点),反向模式按相反的顺序传播伴随(横线),每一步只用这一个运算自己的解析局部导数。它就是主线第 8、9 节手写的 residual_d、residual_b 的自动版本:不保存全局 A 的元素,也不用有限差分构造 ATw。

The last two terms show only the state path through b at fixed geometry. Full shape reversal includes M, weights, offsets and boundary data. The computational tape, in a few words. While the program evaluates the residual once, it records every scalar operation (addition, subtraction, multiplication, division, square root, sine) together with its inputs, in order: that record is the tape. Forward mode propagates tangents (dots) along the record, reverse mode propagates adjoints (bars) back through it in the opposite order, each step using only that one operation’s analytic local derivative. It is the automatic version of residual_d and residual_b, written by hand in Sections 8 and 9 of the main line: it stores no entry of the global A and builds no ATw by finite differences.

图 9.8Figure 9.8
第 10 节Section 10

前向求解:切线方程

The forward solve: the tangent equation

第 8 节只算了乘积 Av。前向模式真正要解的是切线方程:选一个输入 α,对 R(u(α),α)=0 求导,u 随 α 的变化率 v=du/dα 满足

Section 8 only computed the product Av. What forward mode actually has to solve is the tangent equation: pick one input α and differentiate R(u(α),α)=0 with respect to it; the rate v=du/dα at which u follows α satisfies

Av=−f,f=∂R∂α

这里取远场来流值 α=u∞。只有远场入流半面见到它(作为通量的右状态),所以 f=∂R/∂u∞ 就是这些面上通量对右槽位的导数,写进各自的左节点,共 5 个非零分量。

Here take the far-field free-stream value, α=u∞. Only the far-field inflow half-faces see it, as the right state of their flux, so f=∂R/∂u∞ is the derivative of those fluxes with respect to their right slot, written into their left nodes: 5 non-zero entries.

按第 8 节的记号,v 就是种子 α̇=1 下的切线 u̇,f 是这个种子在 u̇=0 时产生的 Ṙ。切线方程说的是收敛残差的切线为零:Ṙ=Au̇+f=0。

In the notation of Section 8, v is the tangent u̇ for the seed α̇=1, and f is the Ṙ that this seed produces with u̇=0. The tangent equation says that the tangent of the converged residual vanishes: Ṙ=Au̇+f=0.

与 primal 同一个 Jacobi 迭代

The Jacobi iteration of the primal

切线方程是线性的,用第 6 节那个 Jacobi 迭代、同一个 diag(A) 求解:

The tangent equation is linear, and it is solved by the Jacobi iteration of Section 6, with the same diag(A):

v←v−diag(A)−1(Av+f)

每一轮只调用一次与矩阵有关的函数:第 8 节的 residual_d,算出 Av。diag(A) 是第 6 节的 diagonal 在 u* 处的值。除此之外,矩阵既不形成,也不分解:这就是 matrix-free 的前向求解。

Each sweep calls just one matrix-related function: residual_d of Section 8, which computes Av. diag(A) is what diagonal of Section 6 returns at u*. Beyond that the matrix is neither formed nor factorised: this is the forward solve, matrix-free.

收敛:与 primal 同一个速率

Convergence: at the primal's rate

在解附近,primal 迭代的误差每一轮乘以 I−diag(A)−1A,切线迭代的误差乘的正是同一个矩阵。所以 primal 一过它非线性的起始阶段,两条收敛曲线就以同一个速率下降,这个速率就是谱半径 0.797:实测 k=60 到 100 之间,两者每轮都缩小 0.7971 倍。起点不同:primal 从 u≡1 出发,切线从 v=0 出发,初始残差就是 f。图 10.1 逐轮播放切线迭代:上面一行按面播放本轮内部的 residual_d 边循环(80 个面:48 个对偶面 + 32 个边界半面,最后一步是 Jacobi 更新),下面一行按轮播放,看切线解 v 如何从远场入流边界向下游铺开。切线迭代在 k=133 时降到判据 10−13 以下,从 k=165 起落在舍入误差平台上。

Near the solution the primal iteration multiplies its error every sweep by I−diag(A)−1A, and the tangent iteration multiplies its error by exactly the same matrix. So once the primal is past its nonlinear start, the two convergence curves fall at the same rate, the spectral radius 0.797: measured between k=60 and 100, both shrink by 0.7971 per sweep. They start from different places: the primal from u≡1, the tangent from v=0, its first residual being f. Figure 10.1 plays the tangent iteration: the upper row runs face by face through the residual_d edge loop inside one sweep (80 faces: 48 dual faces plus 32 boundary half-faces, the last step being the Jacobi update), while the lower row runs sweep by sweep, showing how the tangent solution v spreads downstream from the far-field inflow boundary. The tangent iteration drops below the 10−13 criterion at k=133 and is on its round-off floor from k=165 on.

图 10.1Figure 10.1
演示:切线方程的 Jacobi 迭代,与 primal 同速收敛
Demo: the Jacobi iteration of the tangent equation, converging at the primal's pace
远场入流半面(f 所在)the far-field inflow half-faces (where f lives)
本轮内的边循环:0 = 开始,1–80 = 逐个面(48 个对偶面 + 32 个边界半面),81 = Jacobi 更新
The edge loop within this sweep: 0 = start, 1–80 = face by face (48 dual faces + 32 boundary half-faces), 81 = the Jacobi update
Jacobi 轮次 k
Jacobi sweep k

结果:dL/du∞

The result: dL/du∞

L 不直接依赖 u∞,所以

L does not depend on u∞ directly, so

dLdu∞=L̇=gTv,g=(∂L∂u)T

本算例是 1.95162064261909;在 u∞±10−6 各重解一次 primal 的中心差分与它的相对差是 1.8×10−10。一次切线求解只回答一个输入:要得到对第 12 节 48 个坐标的导数,前向那一路要解 48 次。下一节从另一头走,一次伴随求解就得到全部。

Here it comes to 1.95162064261909; a central difference that re-solves the primal at u∞±10−6 differs from it by 1.8×10−10 (relative). One tangent solve answers one input: for the derivatives with respect to the 48 coordinates of Section 12, the forward route has to solve 48 times. The next section goes from the other end, where one adjoint solve gives them all.

逐步推导:右端项 ff,以及 vv 为什么处处为正

Step by step: the right-hand side ff, and why vv is positive everywhere

第一步,u∞u_\infty 在代码里只出现在一处:入流半面的右状态 uR=u∞u_R=u_\infty(local() 的入流分支)。所以只有入流半面的通量对它有导数,就是通量对右槽位的偏导,在 uR=u∞u_R=u_\infty 处取值:

Step 1. u∞u_\infty appears in one place in the code: the right state uR=u∞u_R=u_\infty of the inflow half-faces (the inflow branch of local()). Only their fluxes depend on it, through the partial derivative with respect to the right slot, taken at uR=u∞u_R=u_\infty:

∂hf∂u∞=∂H∂uR(uL,u∞)=12vn,f u∞−ε∣𝒏f∣(vn,f<0).\frac{\partial h_f}{\partial u_\infty}=\frac{\partial H}{\partial u_R}(u_L,u_\infty)=\tfrac12v_{n,f}\,u_\infty-\varepsilon|\boldsymbol n_f|\qquad(v_{n,f}<0).

第二步,按 hh 的符号分发;入流半面只有左节点:

Step 2. Scatter with the signs of hh; an inflow half-face has only a left node:

fi=∂Ri∂u∞=∑f:  L(f)=i, vn,f<0(12vn,f u∞−ε∣𝒏f∣).f_i=\frac{\partial R_i}{\partial u_\infty}=\sum_{f:\;L(f)=i,\ v_{n,f}<0}\Big(\tfrac12v_{n,f}\,u_\infty-\varepsilon|\boldsymbol n_f|\Big).

用第 5 节记号块的话说,ff 就是 RDR_D 中属于 u∞u_\infty 的那一列。第三步,入流半面上 vn<0v_n<0,两项都是负的,所以 f≤0f\le0。

In the notation block of Section 5, ff is the column of RDR_D that belongs to u∞u_\infty. Step 3: on an inflow half-face vn<0v_n<0, both terms are negative, and so f≤0f\le0.

第四步,v=−A−1fv={-A^{-1}f} 的符号由 A−1A^{-1} 决定。第 6 节的 Jacobi 迭代从 v0=0v^0=0 出发:

Step 4. The sign of v=−A−1fv={-A^{-1}f} is decided by A−1A^{-1}. The Jacobi iteration of Section 6 starts from v0=0v^0=0:

vk+1=vk−diag⁡(A)−1(Avk+f)=(I−diag⁡(A)−1A) vk−diag⁡(A)−1f,vk=−∑j=0k−1(I−diag⁡(A)−1A)j diag⁡(A)−1f →  k→∞   −A−1f.\begin{gathered}v^{k+1}=v^k-\operatorname{diag}(A)^{-1}(Av^k+f)=(I-\operatorname{diag}(A)^{-1}A)\,v^k-\operatorname{diag}(A)^{-1}f,\\ v^k={-\sum_{j=0}^{k-1}(I-\operatorname{diag}(A)^{-1}A)^j\,\operatorname{diag}(A)^{-1}f}\ \xrightarrow{\;k\to\infty\;}\ {-A^{-1}f}.\end{gathered}

每个迭代值都是 Neumann 级数 A−1=∑j≥0(I−diag⁡(A)−1A)j diag⁡(A)−1A^{-1}=\sum_{j\ge0}(I-\operatorname{diag}(A)^{-1}A)^j\,\operatorname{diag}(A)^{-1} 的部分和;谱半径 ρ<1\rho<1 保证它收敛。只要 diag⁡(A)>0\operatorname{diag}(A)>0 且 I−diag⁡(A)−1AI-\operatorname{diag}(A)^{-1}A 的每个元都 ≥0\ge0,级数的每一项都是非负矩阵,于是 A−1≥0A^{-1}\ge0,而 −f≥0-f\ge0,所以每个迭代值和极限都 ≥0\ge0。I−diag⁡(A)−1AI-\operatorname{diag}(A)^{-1}A 的非对角元是 −Aij/Aii-A_{ij}/A_{ii}:它们非负,就是 AA 的非对角元非正,也就是每个内部面上 cL≥0c_L\ge0、cR≤0c_R\le0,即第 3 节的单调性条件。

Every iterate is a partial sum of the Neumann series A−1=∑j≥0(I−diag⁡(A)−1A)j diag⁡(A)−1A^{-1}=\sum_{j\ge0}(I-\operatorname{diag}(A)^{-1}A)^j\,\operatorname{diag}(A)^{-1}, which converges because the spectral radius is ρ<1\rho<1. If diag⁡(A)>0\operatorname{diag}(A)>0 and every entry of I−diag⁡(A)−1AI-\operatorname{diag}(A)^{-1}A is ≥0\ge0, every term of the series is a non-negative matrix, so A−1≥0A^{-1}\ge0; with −f≥0-f\ge0, every iterate and the limit are ≥0\ge0. The off-diagonal entries of I−diag⁡(A)−1AI-\operatorname{diag}(A)^{-1}A are −Aij/Aii-A_{ij}/A_{ii}: they are non-negative exactly when the off-diagonal entries of AA are non-positive, that is when cL≥0c_L\ge0 and cR≤0c_R\le0 on every interior face, the monotonicity of Section 3.

第五步,本页的情况:diag⁡(A)\operatorname{diag}(A) 的最小元是 0.648,ρ=0.7971\rho=0.7971,但 AA 有 1 个正的非对角元(I−diag⁡(A)−1AI-\operatorname{diag}(A)^{-1}A 的最小元是 −0.0079),所以这个充分条件在格点页不成立。用本页的 Jacobi 逐列解出的 A−1A^{-1} 仍然没有负元,vv 也处处为正(图 10.2):条件只是充分的。

Step 5, this page: the smallest entry of diag⁡(A)\operatorname{diag}(A) is 0.648 and ρ=0.7971\rho=0.7971, but AA has 1 positive off-diagonal entry (the smallest entry of I−diag⁡(A)−1AI-\operatorname{diag}(A)^{-1}A is −0.0079), so the sufficient condition fails on the node page. A−1A^{-1}, solved column by column with this page’s Jacobi iteration, still has no negative entry, and vv is positive everywhere (Figure 10.2): the condition is only sufficient.

核对:ff 有 5 个非零元,都是负的,从 −1.876 到 −0.610,与改变 u∞u_\infty 的中心差分(步长 10−610^{-6})之差 1.8×10−11;逐列用 Jacobi 解出的 A−1A^{-1} 最小元 2.2×10−4;vv 最小 0.728(本页代码在构建时计算)。

Check: ff has 5 non-zeros, all negative, from −1.876 to −0.610, and differs from central differences in u∞u_\infty (step 10−610^{-6}) by 1.8×10−11; the smallest entry of A−1A^{-1}, solved column by column with Jacobi, is 2.2×10−4; the smallest vv is 0.728 (computed by this page’s code at build time).

0.00−1.88(a) 右端项 f = ∂R/∂u∞(a) right-hand side f = ∂R/∂u∞1.950.00(b) 切线解 v = du/du∞(b) tangent solution v = du/du∞β
图 10.2 · 前向问题的源与解,画在本页的中位对偶控制体上。(a) f=∂R/∂u∞ 只在 5 个带远场入流半面的节点上非零(琥珀色是入流半面),全部为负:−1.876≤f≤0;颜色越深 |f| 越大,色标给出数值。(b) Jacobi 迭代收敛后的切线解 v=du/du∞,每个节点都为正,范围 0.728 到 1.949:A−1 没有负元(最小元 2.2×10⁻⁴),而 f≤0,所以 v=−A−1f≥0,推导见正文。最大值在壁面节点 7,位于物体的上游一侧。底部的箭头是 β。Figure 10.2 · The source and the solution of the tangent problem, drawn on this page’s median-dual control volumes. (a) f = ∂R/∂u∞ is non-zero only in the 5 nodes that own a far-field inflow half-face (amber), and negative in all of them: −1.876 ≤ f ≤ 0; darker means larger |f|, and the colour bar gives the values. (b) The tangent solution v = du/du∞ after the Jacobi iteration has converged: positive in every node, from 0.728 to 1.949. A−1 has no negative entry (its smallest is 2.2×10⁻⁴) and f ≤ 0, so v = −A−1f ≥ 0; the text derives this. The largest value is in the wall node 7, on the upstream side of the body. The arrow at the bottom is β.

逐步推导:一个坐标种子的切线求解

Step by step: the tangent solve for one coordinate seed

上面的种子是 u˙∞=1\dot u_\infty=1。同一个 Jacobi 迭代对设计的任何种子都适用。取第 8 节例子里的种子 D˙=𝒆k\dot D=\boldsymbol e_k:kk 是壁面节点 7 的 xx 坐标,u˙∞=0\dot u_\infty=0(图 10.3)。

The seed above is u˙∞=1\dot u_\infty=1. The same Jacobi iteration serves any seed of the design. Take the seed of Section 8’s example, D˙=𝒆k\dot D=\boldsymbol e_k with kk the xx coordinate of wall node 7 and u˙∞=0\dot u_\infty=0 (Figure 10.3).

第一步,源项:让第 8 节的前向程序在 u˙=0\dot u=0 时走一遍,得到 R˙=(∂R/∂X)𝒆k\dot R=(\partial R/\partial X)\boldsymbol e_k(即 gradient_d),切线方程的右端项是它的相反数。它只在节点 6、7、8、14、15、16 上非零:只有法向随这个节点变、又真正跑通量的对偶面才写进它;这个节点上的壁面半面没有通量,只把目标改变 ∂L/∂Xk=0.1196\partial L/\partial X_k=0.1196。

Step 1, the source term: run the forward program of Section 8 with u˙=0\dot u=0 and obtain R˙=(∂R/∂X)𝒆k\dot R=(\partial R/\partial X)\boldsymbol e_k (gradient_d); the right-hand side of the tangent equation is its negative. It is non-zero only in nodes 6, 7, 8, 14, 15, 16: only dual faces whose normal moves with this node and that run the flux write into it; the wall half-faces at this node run no flux and change only the objective, by ∂L/∂Xk=0.1196\partial L/\partial X_k=0.1196.

第二步,用第 6 节的 Jacobi 迭代与同一个 diag⁡(A)\operatorname{diag}(A) 解切线方程:

Step 2, solve the tangent equation by the Jacobi iteration of Section 6, with the same diag⁡(A)\operatorname{diag}(A):

Aφk=−∂R∂X𝒆k,φk←φk−diag⁡(A)−1(Aφk+∂R∂X𝒆k),A\varphi_k=-\frac{\partial R}{\partial X}\boldsymbol e_k,\qquad\varphi_k\leftarrow\varphi_k-\operatorname{diag}(A)^{-1}\Big(A\varphi_k+\frac{\partial R}{\partial X}\boldsymbol e_k\Big),

第 117 轮满足 10−1310^{-13} 的判据。第三步,目标的切线:

which meets the 10−1310^{-13} criterion at sweep 117. Step 3, the tangent of the objective:

L˙=gTφk⏟0.3501+∂L∂Xk⏟0.1196=0.469675=dLdXk.\dot L=\underbrace{g^T\varphi_k}_{0.3501}+\underbrace{\frac{\partial L}{\partial X_k}}_{0.1196}=0.469675=\frac{dL}{dX_k}.

这是梯度的一个分量。D=(u∞,X)D=(u_\infty,X) 有 49 个分量,前向路线就要这样解 49 次;第 12 节的图 12.7 把它和一次伴随求解的代价放在一起。

This is one component of the gradient. D=(u∞,X)D=(u_\infty,X) has 49 components, so the forward route solves 49 times like this; Figure 12.7 in Section 12 sets that against the cost of one adjoint solve.

核对:跑满 300 轮后,把 φk\varphi_k 代回整个前向程序,R˙\dot R 最大 8.3×10−17;L˙\dot L 与第 12 节伴随梯度的第 kk 个分量相对差 2.4×10−16,与复数步长相对差 1.2×10−16(本页代码在构建时计算)。

Check: after all 300 sweeps, φk\varphi_k put back through the whole forward program leaves R˙\dot R at most 8.3×10−17; L˙\dot L differs from the kk-th component of Section 12’s adjoint gradient by 2.4×10−16 and from the complex step by 1.2×10−16 (relative) (computed by this page’s code at build time).

0.191−0.1910.000(a) 源项 (∂R/∂X)ek(a) source term (∂R/∂X)ekX˙​0.372−0.3720.000(b) 切线解 φk = du/dXk(b) tangent solution φk = du/dXkX˙​
图 10.3 · 一个坐标种子的切线求解,种子是壁面节点 7 的 x 坐标(紫色箭头)。(a) 源项 (∂R/∂X)ek(切线方程的右端项是它的相反数),即 gradient_d 在 u̇=0 时的 Ṙ:只有法向随这个节点变化、又真正跑通量的对偶面才写进它,所以它只在节点 6、7、8、14、15、16 上非零(壁面半面的 h≡0,它们只改变 L)。(b) Jacobi 迭代解出的 φk(Aφk=−(∂R/∂X)ek,第 117 轮满足 10−13 判据),范围 −0.1367 到 0.3724:源只在几个节点里,解却沿 β 向下游铺开。L̇=gTφk+(∂L/∂X)ek=0.469675,是 dL/dXk 这一个分量;48 个坐标各要这样解一次。Figure 10.3 · The tangent solve for one coordinate seed, the x coordinate of wall node 7 (violet arrow). (a) The source term (∂R/∂X)ek (the right-hand side of the tangent equation is its negative), which is the Ṙ of gradient_d with u̇ = 0: only dual faces whose normal moves with this node and that run the flux write into it, so it is non-zero only in nodes 6, 7, 8, 14, 15, 16 (the wall half-faces have h ≡ 0 and change only L). (b) The solution φk of Aφk = −(∂R/∂X)ek by the Jacobi iteration (below the 10−13 criterion at sweep 117), from −0.1367 to 0.3724: the source sits in a few nodes, the solution spreads downstream along β. L̇ = gTφk + (∂L/∂X)ek = 0.469675 is the one component dL/dXk; each of the 48 coordinates needs a solve like this.

把隐函数求导展开成可求解的系统

Expand implicit differentiation into a solvable system

设原始残差已经收敛,且当前光滑分支上的 AA 非奇异。取设计种子(方向)D˙\dot D,令 D(t)=D+t D˙D(t)=D+t\,\dot D;前向求解得到的状态切向量是 v≡𝐮˙=ddt𝐮(D+t D˙)∣t=0v\equiv\dot{\mathbf u}=\frac{d}{dt}\mathbf u(D+t\,\dot D)\big|_{t=0}。对每一个残差分量逐项求导,零残差的切向量仍为零:

Assume the primal residual is converged and AA is nonsingular on the current smooth branch. Choose a design seed (direction) D˙\dot D and set D(t)=D+t D˙D(t)=D+t\,\dot D; the state tangent returned by the tangent solve is v≡𝐮˙=ddt𝐮(D+t D˙)∣t=0v\equiv\dot{\mathbf u}=\frac{d}{dt}\mathbf u(D+t\,\dot D)\big|_{t=0}. Differentiate each residual component: the tangent of a zero residual is zero.

0=R˙r=∑j∂Rr∂ujvj0=\dot R_r=\sum_j\frac{\partial R_r}{\partial u_j}v_j
+∑k∂Rr∂DkD˙k(1≤r≤nu)+\sum_k\frac{\partial R_r}{\partial D_k}\dot D_k\quad(1\le r\le n_u)
⟹Av=−RD D˙.\Longrightarrow\quad Av=-R_D\,\dot D.
L^(D)=L(𝐮(D),D),\widehat{L}(D)=L(\mathbf u(D),D),
L˙=ddtL^(D+t D˙)∣t=0=gTv+(∇DL)T D˙.\dot L=\frac{d}{dt}\widehat{L}(D+t\,\dot D)\Big|_{t=0}=g^Tv+(\nabla_DL)^T\,\dot D.

实现分三步:状态种子 𝐮˙=0\dot{\mathbf u}=0、设计种子 D˙\dot D,前向求值残差得到 R˙=RD D˙\dot R=R_D\,\dot D;用精确 AA 的乘积求解 vv;再以 𝐮˙=v\dot{\mathbf u}=v 和 D˙\dot D 为种子对目标前向求导,得到 L˙\dot L。一次 AvAv 只是算子调用(一次前向扫描),不是求解 vv。稳态导数的对象是收敛残差方程,不是 Jacobi 的迭代次数。在不可微点(如 limiter 的切换或分支判别处)以上普通导数需要另外讨论,不能无条件套用。

Implementation has three steps: seed the state with 𝐮˙=0\dot{\mathbf u}=0 and the design with D˙\dot D and evaluate the residual tangent R˙=RD D˙\dot R=R_D\,\dot D; solve for vv with exact AA products; then differentiate the objective forward with the seeds 𝐮˙=v\dot{\mathbf u}=v and D˙\dot D to obtain L˙\dot L. One AvAv is an operator application (one forward sweep), not the solution vv. The steady derivative concerns the converged residual equation, not the Jacobi iteration count. At nondifferentiable points (a limiter switch or a branch test) these ordinary derivatives need separate treatment.

用面循环的语句来看:右端 RDD˙R_D\dot D 是 𝐮˙=0\dot{\mathbf u}=0 时的一次前向扫描,只有 KfD˙K_f\dot D 与源项两条路径起作用;迭代中的每个 AvAv 是 D˙=0\dot D=0 时的一次前向扫描。一次前向求解的全部工作就是这段面循环以不同的种子反复执行。对 nan_a 个设计分量逐个求导,要做 nan_a 次这样的求解。

In terms of the face loop's statements: the right-hand side RDD˙R_D\dot D is one forward sweep with 𝐮˙=0\dot{\mathbf u}=0, in which only the paths KfD˙K_f\dot D and the source are active, and every AvAv of the iteration is one forward sweep with D˙=0\dot D=0. All the work of a tangent solve is this face loop run again and again with different seeds, and the derivatives with respect to all nan_a design components take nan_a such solves.

新增(选读):光滑特征边界模型
Added (optional): smooth characteristic-boundary model

前向方程:一个设计方向一次求解

Tangent equation: one solve per design direction

对收敛方程 R(u(D),D)=0 求导,得到 Au˙=−RDD˙A\dot u=-R_D\dot D:设计种子 D˙\dot D 的切线 u˙\dot u 使收敛残差的切线为零。这里不是给一个任意 v 做一次算子乘积,而是求出整个敏感度场。求解后用 L˙=Luu˙+LDD˙\dot L=L_u\dot u+L_D\dot D 与伴随梯度的同一分量比较。

Differentiating R(u(D),D)=0 gives Au˙=−RDD˙A\dot u=-R_D\dot D: for the design seed D˙\dot D the tangent u˙\dot u keeps the converged residual's tangent at zero. This solves a sensitivity field rather than applying the tangent once to an arbitrary v. Compare L˙=Luu˙+LDD˙\dot L=L_u\dot u+L_D\dot D with the corresponding adjoint gradient component.

Au˙=−RDD˙,L˙=Luu˙+LDD˙.A\dot u=-R_D\dot D,\qquad\dot L=L_u\dot u+L_D\dot D.
图 10.4Figure 10.4
第 11 节Section 11

伴随求解:伴随方程

The adjoint solve: the adjoint equation

伴随方程 ATψ=−g 的右端项 g=(∂L/∂u)T 只在壁面节点(1–8 号)上非零。它同样用第 6 节的 Jacobi 迭代、同一个 diag(A) 求解(AT 与 A 的对角元相同):

The right-hand side of the adjoint equation ATψ=−g, with g=(∂L/∂u)T, is non-zero only on the wall nodes (1–8). It too is solved by the Jacobi iteration of Section 6, with the same diag(A) (AT has the diagonal of A):

ψ←ψ−diag(A)−1(ATψ+g)

与矩阵有关的唯一操作是一次第 9 节的 residual_b 调用:种子取 R̄=ψ,它返回 ū=ATψ,伴随方程要求的就是 ū=−g。对设计变量再反传一步,设计伴随 D̄=RDTψ+∇DL 就是梯度 dL/dD(第 12 节对 D=X 写出)。

The only matrix-related operation is one call to residual_b of Section 9: with the seed R̄=ψ it returns ū=ATψ, and the adjoint equation asks for ū=−g. One more reverse step into the design variables gives the design adjoint D̄=RDTψ+∇DL, which is the gradient dL/dD (Section 12 writes it out for D=X).

同一个导数,从另一头算

The same derivative, from the other end

伴随解同样给出第 10 节的那个导数,不用再解任何方程:

The adjoint solution gives the derivative of Section 10 as well, with no further solve:

dLdu∞=ψTf=gTv

本算例两者的相对差是 3.4×10−16。一次伴随求解回答一个输出对所有输入的导数:同一个 ψ 也给出第 12 节对 48 个坐标的导数,那里前向那一路要解 48 次。

Here the two agree to 3.4×10−16 (relative). One adjoint solve answers the derivatives of one output with respect to every input: the same ψ also gives the derivatives with respect to the 48 coordinates of Section 12, where the forward route solves 48 times.

收敛:三条曲线同一个速率

Convergence: one rate for all three

伴随迭代的误差每一轮乘以 I−diag(A)−1AT,它是 I−Adiag(A)−1 的转置,而

The adjoint iteration multiplies its error every sweep by I−diag(A)−1AT, the transpose of I−Adiag(A)−1, and

Adiag(A)−1=diag(A)(diag(A)−1A)diag(A)−1

与 primal、切线的 diag(A)−1A 相似。转置不改变特征值,所以三个迭代矩阵的谱完全相同:进入渐近阶段以后,primal、切线、伴随三条收敛曲线以同一个速率下降,这个速率就是谱半径 0.797。实测 k=60 到 100 之间,三条曲线每轮都缩小 0.7971 倍,四位有效数字相同。图 11.1 把三条曲线画在一起,并逐轮播放伴随迭代:上面一行按面播放本轮内部的 residual_b 边循环(80 个面:48 个对偶面 + 32 个边界半面,最后一步是 Jacobi 更新),下面一行按轮播放,看伴随解 ψ 如何从物面向上游铺开,与第 10 节里切线解的走向正好相反。

is similar to the primal's and the tangent's diag(A)−1A. Transposition leaves eigenvalues unchanged, so the three iteration matrices have exactly the same spectrum: once in the asymptotic phase the primal, tangent and adjoint convergence curves fall at the same rate, the spectral radius 0.797. Measured between k=60 and 100, all three shrink by 0.7971 per sweep, the same to four digits. Figure 11.1 draws the three curves together and plays the adjoint iteration: the upper row runs face by face through the residual_b edge loop inside one sweep (80 faces: 48 dual faces plus 32 boundary half-faces, the last step being the Jacobi update), while the lower row runs sweep by sweep, showing how the adjoint solution ψ spreads upstream from the body surface, the opposite way to the tangent solution of Section 10.

diag(A) 不再是常数。若网格均匀、边界右状态都取常数,闭合性会使 Aii=εΣ|n| 对所有内部控制体相同。这里不是这样:壁面半面完全不贡献,远场出流面的导数是 vnuL,再加上混合网格本身的不均匀性,所以 Aii 逐节点不同——面板副标题里给出了最小值与最大值。

diag(A) is no longer constant. On a uniform mesh with a constant right state on every boundary, closure would make Aii=εΣ|n| the same for every interior control volume. That is not the case here: wall half-faces contribute nothing at all, the derivative on a far-field outflow face is vnuL, and the hybrid mesh is itself non-uniform, so Aii differs from node to node — the panel subtitle gives the smallest and the largest value.

图 11.1Figure 11.1
演示:伴随方程的 Jacobi 迭代,三条收敛曲线
Demo: the Jacobi iteration of the adjoint equation, and the three convergence curves
壁面(g 所在)the wall (where g lives)
本轮内的边循环:0 = 开始,1–80 = 逐个面(48 个对偶面 + 32 个边界半面),81 = Jacobi 更新
The edge loop within this sweep: 0 = start, 1–80 = face by face (48 dual faces + 32 boundary half-faces), 81 = the Jacobi update
Jacobi 轮次 k
Jacobi sweep k

收敛到机器精度是什么样子。三个迭代都没有停在判据处,而是跑满 300 轮。primal、切线、伴随分别在 k=122、133、108 时降到判据 10−13 以下,但这时仍以每轮 0.797 倍的比例下降:判据只是约定的停止点,并不说明迭代已经走到头。它们按同一比例一直降到 10−16 附近,然后分别从 k=153、165、148 起落在舍入误差平台上:此后再也不超过这次运行最后三分之一里的最大值,三个平台都在 1.3×10−16 与 2.1×10−16 之间。曲线变平,才说明迭代已经收敛到机器精度。停在判据处的 ψ 与平台上的 ψ 相差 4.6×10−13。

What convergence to machine precision looks like. None of the three iterations stops at the criterion; each runs the full 300 sweeps. The primal, the tangent and the adjoint drop below the 10−13 criterion at k=122, 133 and 108, but there they are still falling by a factor of 0.797 per sweep: a criterion is an agreed place to stop, not a sign that the iteration has run its course. They keep falling at that rate down to about 10−16 and then settle onto their round-off floors from k=153, 165 and 148 on: from there they never again rise above the largest value of the run's last third, and the three floors all lie between 1.3×10−16 and 2.1×10−16. Only this flat tail shows that an iteration has converged to machine precision. The ψ at the criterion is 4.6×10−13 away from the ψ on the floor.

−ψi 表示“在节点 i 的控制体中加一个单位源项时 L 的变化率”。g 只在物面非零,ψ 却遍布上游节点:它们通过一串面通量影响物面上的载荷。

−ψi is "the rate of change of L when a unit source is added in the control volume of node i". g is non-zero only on the body surface, yet ψ spreads over the upstream nodes: they influence the load on the body through a chain of face fluxes.

伴随解本身画在图 11.2 里:右端项 gg 只在壁面节点上,ψ\psi 却铺满了物体上游。

The adjoint solution itself is drawn in Figure 11.2: the right-hand side gg lives at the wall nodes only, yet ψ\psi covers the region upstream of the body.

0.51−0.510.00(a) 右端项 g = (∂L/∂u)T​(a) right-hand side g = (∂L/∂u)T​1.61−1.610.00(b) 伴随解 ψ(b) adjoint solution ψβ
图 11.2 · 伴随问题的源与解。(a) g=(∂L/∂u)T 只在 8 个壁面节点上非零(琥珀色是壁面),有正有负:红色为正,蓝色为负,颜色深浅按绝对值,色标给出数值。(b) 收敛后的伴随解 ψ,范围 −1.611 到 0.445。|ψ| 最大的是壁面节点 6,在物体的上游一侧;把节点按位置在物体中心的上游还是下游((xi−xbody)·β 的符号)分成两半,上游一半的平均 |ψ| 是下游一半的 3.91 倍:伴随的信息逆着 β(底部箭头)从物面向上游传,与前向解的方向相反。−ψi 是在节点 i 的控制体里加单位源项时 L 的变化率。Figure 11.2 · The source and the solution of the adjoint problem. (a) g = (∂L/∂u)T is non-zero only in the 8 wall nodes (the wall is amber) and takes both signs: red is positive, blue negative, shaded by magnitude, with the values on the colour bar. (b) The converged adjoint solution ψ, from −1.611 to 0.445. |ψ| is largest in the wall node 6, on the upstream side of the body; splitting the nodes by whether they lie upstream or downstream of the body centre (the sign of (xi − xbody)·β), the mean |ψ| of the upstream half is 3.91 times that of the downstream half: adjoint information travels from the body upstream, against β (the arrow at the bottom), the opposite way to the tangent. −ψi is the rate of change of L when a unit source is added in the control volume of node i.

逐步推导:同一个数的两条路线

Step by step: two routes to one number

上面说 dL/du∞=ψTf=gTvdL/du_\infty=\psi^Tf=g^Tv。这是三行代数:

Above, dL/du∞=ψTf=gTvdL/du_\infty=\psi^Tf=g^Tv. It is three lines of algebra:

gTv  =ATψ=−g  −(ATψ)Tv  =  −ψT(Av)  =Av=−f  ψTf.g^Tv\;\overset{A^T\psi={-g}}{=}\;{-(A^T\psi)^Tv}\;=\;{-\psi^T(Av)}\;\overset{Av={-f}}{=}\;\psi^Tf.

第一个等号用伴随方程,第二个是转置的定义 ⟨ATψ,v⟩=⟨ψ,Av⟩\langle A^T\psi,v\rangle=\langle\psi,Av\rangle,第三个用切线方程。写成对节点的求和,两边加的是完全不同位置上的数(图 11.3):gTv=∑igivig^Tv=\sum_ig_iv_i 只有 8 个壁面节点的项,ψTf=∑iψifi\psi^Tf=\sum_i\psi_if_i 只有 5 个入流节点的项。

The first equality uses the adjoint equation, the second is the definition of the transpose, ⟨ATψ,v⟩=⟨ψ,Av⟩\langle A^T\psi,v\rangle=\langle\psi,Av\rangle, the third the tangent equation. Written as sums over nodes, the two sides add numbers from entirely different places (Figure 11.3): gTv=∑igivig^Tv=\sum_ig_iv_i has terms only in the 8 wall nodes, ψTf=∑iψifi\psi^Tf=\sum_i\psi_if_i only in the 5 inflow nodes.

核对:gTv=1.95162064261909g^Tv=1.95162064261909,ψTf=1.95162064261909\psi^Tf=1.95162064261909,相对差 3.4×10−16(本页代码在构建时计算)。

Check: gTv=1.95162064261909g^Tv=1.95162064261909 and ψTf=1.95162064261909\psi^Tf=1.95162064261909, a relative difference of 3.4×10−16 (computed by this page’s code at build time).

−1.00−0.500.000.501.001.502.002.50n1n2n3n4n5n6n7n8(a) 前向路线:Σ gi vi(壁面节点)(a) tangent route: Σ gi vi (wall nodes)−1.00−0.500.000.501.001.502.002.50n20n21n22n23n24(b) 伴随路线:Σ ψi fi(入流节点)(b) adjoint route: Σ ψi fi (inflow nodes)dL/du∞gT​v = 1.95162064261909ψT​f = 1.95162064261909
图 11.3 · 同一个导数 dL/du∞ 的两条路线。柱是每个非零项,折线是累加和,紫色虚线是两条路线共同的终点。(a) 前向路线 gTv:只在 8 个壁面节点上有项(g 在那里非零),用的是切线解 v。(b) 伴随路线 ψTf:只在 5 个入流节点上有项(f 在那里非零),用的是伴随解 ψ。两条路线加的是完全不同位置上的数,终点却相同:本页代码算得 gTv=1.95162064261909,ψTf=1.95162064261909,相对差 3.4×10⁻¹⁶。Figure 11.3 · Two routes to the same derivative dL/du∞. Bars are the non-zero terms, the line is the running sum, and the violet dashed line is the end point both routes share. (a) The tangent route gTv: terms only in the 8 wall nodes, where g is non-zero, using the tangent solution v. (b) The adjoint route ψTf: terms only in the 5 inflow nodes, where f is non-zero, using the adjoint solution ψ. The two routes add numbers from entirely different places and still end at the same value: this page’s code gives gTv = 1.95162064261909 and ψTf = 1.95162064261909, a relative difference of 3.4×10⁻¹⁶.

逐步推导:三个迭代为什么同速

Step by step: why the three iterations share one rate

第一步,切线迭代 vk+1=vk−diag⁡(A)−1(Avk+f)v^{k+1}=v^k-\operatorname{diag}(A)^{-1}(Av^k+f),而精确解满足 Av+f=0Av+f=0。两式相减,误差每轮乘以同一个矩阵:

Step 1. The tangent iteration is vk+1=vk−diag⁡(A)−1(Avk+f)v^{k+1}=v^k-\operatorname{diag}(A)^{-1}(Av^k+f), and the exact solution satisfies Av+f=0Av+f=0. Subtracting, the error is multiplied by one matrix every sweep:

vk+1−v=(I−diag⁡(A)−1A) (vk−v).v^{k+1}-v=(I-\operatorname{diag}(A)^{-1}A)\,(v^k-v).

第二步,伴随迭代同样:

Step 2. The adjoint iteration likewise:

ψk+1−ψ=(I−diag⁡(A)−1AT) (ψk−ψ).\psi^{k+1}-\psi=(I-\operatorname{diag}(A)^{-1}A^T)\,(\psi^k-\psi).

第三步,改写伴随的迭代矩阵。diag⁡(A)\operatorname{diag}(A) 是对角阵,diag⁡(A)T=diag⁡(A)\operatorname{diag}(A)^T=\operatorname{diag}(A),(diag⁡(A)−1AT)T=A diag⁡(A)−1(\operatorname{diag}(A)^{-1}A^T)^T=A\,\operatorname{diag}(A)^{-1}:

Step 3. Rewrite the adjoint iteration matrix. diag⁡(A)\operatorname{diag}(A) is diagonal, diag⁡(A)T=diag⁡(A)\operatorname{diag}(A)^T=\operatorname{diag}(A) and (diag⁡(A)−1AT)T=A diag⁡(A)−1(\operatorname{diag}(A)^{-1}A^T)^T=A\,\operatorname{diag}(A)^{-1}:

I−diag⁡(A)−1AT=(I−A diag⁡(A)−1)T=(diag⁡(A) (I−diag⁡(A)−1A) diag⁡(A)−1)T=diag⁡(A)−1 (I−diag⁡(A)−1A)T diag⁡(A).\begin{aligned}I-\operatorname{diag}(A)^{-1}A^T&=\big(I-A\,\operatorname{diag}(A)^{-1}\big)^T\\&=\big(\operatorname{diag}(A)\,(I-\operatorname{diag}(A)^{-1}A)\,\operatorname{diag}(A)^{-1}\big)^T\\&=\operatorname{diag}(A)^{-1}\,(I-\operatorname{diag}(A)^{-1}A)^T\,\operatorname{diag}(A).\end{aligned}

第四步,相似变换 diag⁡(A)−1(⋅) diag⁡(A)\operatorname{diag}(A)^{-1}(\cdot)\,\operatorname{diag}(A) 不改变特征值;一个矩阵与它的转置有同一个特征多项式:

Step 4. The similarity diag⁡(A)−1(⋅) diag⁡(A)\operatorname{diag}(A)^{-1}(\cdot)\,\operatorname{diag}(A) leaves eigenvalues unchanged, and a matrix and its transpose share their characteristic polynomial:

det⁡ ⁣((I−diag⁡(A)−1A)T−μI)=det⁡ ⁣((I−diag⁡(A)−1A−μI)T)=det⁡ ⁣(I−diag⁡(A)−1A−μI).\begin{aligned}\det\!\big((I-\operatorname{diag}(A)^{-1}A)^T-\mu I\big)&=\det\!\big((I-\operatorname{diag}(A)^{-1}A-\mu I)^T\big)\\&=\det\!\big(I-\operatorname{diag}(A)^{-1}A-\mu I\big).\end{aligned}

所以两个迭代矩阵的特征值完全相同,谱半径 ρ\rho 也相同。第五步,把误差按特征向量展开,每一轮每个分量乘以自己的特征值;轮数一多,模最大的分量占上风,误差与 ρk\rho^k 成比例地下降(特征向量不完备时结论不变:∥(I−diag⁡(A)−1A)k∥1/k→ρ\|(I-\operatorname{diag}(A)^{-1}A)^k\|^{1/k}\to\rho)。

So the two iteration matrices have exactly the same eigenvalues and the same spectral radius ρ\rho. Step 5. Expanding the error in eigenvectors, every sweep multiplies each component by its eigenvalue; after enough sweeps the component of largest modulus dominates and the error falls in proportion to ρk\rho^k (without a complete set of eigenvectors the conclusion stands: ∥(I−diag⁡(A)−1A)k∥1/k→ρ\|(I-\operatorname{diag}(A)^{-1}A)^k\|^{1/k}\to\rho).

第六步,原始迭代 uk+1=uk−diag⁡(A(uk))−1R(uk)u^{k+1}=u^k-\operatorname{diag}(A(u^k))^{-1}R(u^k) 是非线性的。在 u∗u^* 附近展开:

Step 6. The primal iteration uk+1=uk−diag⁡(A(uk))−1R(uk)u^{k+1}=u^k-\operatorname{diag}(A(u^k))^{-1}R(u^k) is nonlinear. Expand it about u∗u^*:

R(uk)=A (uk−u∗)+O(∣uk−u∗∣2),diag⁡(A(uk))−1=diag⁡(A)−1+O(∣uk−u∗∣).\begin{gathered}R(u^k)=A\,(u^k-u^*)+O(|u^k-u^*|^2),\\ \operatorname{diag}(A(u^k))^{-1}=\operatorname{diag}(A)^{-1}+O(|u^k-u^*|).\end{gathered}

因为 R(u∗)=0R(u^*)=0,diag⁡(A)\operatorname{diag}(A) 的变化只乘在已经是一阶小量的 RR 上,是二阶项:

Because R(u∗)=0R(u^*)=0, the change of diag⁡(A)\operatorname{diag}(A) multiplies an RR that is already first order, which makes it second order:

uk+1−u∗=(I−diag⁡(A)−1A) (uk−u∗)+O(∣uk−u∗∣2).u^{k+1}-u^*=(I-\operatorname{diag}(A)^{-1}A)\,(u^k-u^*)+O(|u^k-u^*|^2).

进入渐近段后,原始迭代的误差也乘以同一个矩阵:三条曲线同速(图 11.4)。

Once in the asymptotic phase the primal error is multiplied by the same matrix: the three curves fall at the same rate (Figure 11.4).

核对:本页代码分别求出 I−diag⁡(A)−1AI-\operatorname{diag}(A)^{-1}A 与 I−diag⁡(A)−1ATI-\operatorname{diag}(A)^{-1}A^T 的全部 24 个特征值,谱半径都是 0.7971(两者之差 2.9×10−15),逐个配对后最大差 3.7×10−15;第二大的模是 0.6955;k=60k=60 到 100 之间实测每轮缩小 0.7971、0.7971、0.7971,与 ρ\rho 之差不超过 7.3×10−6(本页代码在构建时计算)。

Check: this page’s code computes all 24 eigenvalues of I−diag⁡(A)−1AI-\operatorname{diag}(A)^{-1}A and of I−diag⁡(A)−1ATI-\operatorname{diag}(A)^{-1}A^T separately: both spectral radii are 0.7971 (they differ by 2.9×10−15) and the eigenvalues, paired off, differ by at most 3.7×10−15; the second largest modulus is 0.6955; between k=60k=60 and 100 the measured factors per sweep are 0.7971, 0.7971 and 0.7971, within 7.3×10−6 of ρ\rho (computed by this page’s code at build time).

−1.0−0.50.51.0ReImρ = 0.7971(a) 迭代矩阵的特征值(a) eigenvalues of the iteration matricesI − diag(A)⁻¹A(原始、切线)I − diag(A)⁻¹A (primal, tangent)I − diag(A)⁻¹AT​(伴随)I − diag(A)⁻¹AT​ (adjoint)10⁻¹⁸10⁻¹⁵10⁻¹²10⁻⁹10⁻⁶10⁻³10⁰0100200300Jacobi 轮次 kJacobi sweep k残差的均方根rms of the residual原始 R(u)primal R(u)切线 Av + ftangent Av + f伴随 AT​ψ + gadjoint AT​ψ + g∝ ρk(b) 三个迭代的收敛历程(b) the three convergence histories
图 11.4 · 三个 Jacobi 迭代为什么同速。(a) 复平面上,绿圈是切线(也是原始迭代在解附近)的迭代矩阵 I−diag(A)−1A 的 24 个特征值,红叉是伴随迭代矩阵 I−diag(A)−1AT 的特征值,两组由本页的 A 分别算出,逐个重合(最大差 3.7×10⁻¹⁵)。虚线是单位圆,紫色圆的半径是谱半径 ρ=0.7971,它由一个正的实特征值决定。(b) 三条收敛曲线(残差的均方根,对数坐标)跑满 300 轮直到舍入误差的底;紫色虚线与 ρk 成比例,三条曲线在渐近段都与它平行,k=60–100 之间实测每轮缩小 0.7971、0.7971、0.7971。Figure 11.4 · Why the three Jacobi iterations share one rate. (a) In the complex plane, the green circles are the 24 eigenvalues of the tangent iteration matrix I − diag(A)−1A (also the primal iteration’s near the solution), the red crosses those of the adjoint iteration matrix I − diag(A)−1AT; the two sets are computed separately from this page’s A and coincide one by one (largest difference 3.7×10⁻¹⁵). The dashed circle is the unit circle; the violet disc has the spectral radius ρ = 0.7971 as its radius, set by one positive real eigenvalue. (b) The three convergence curves (rms of the residual, logarithmic) over all 300 sweeps, down to the round-off floor; the violet dashed line is proportional to ρk, and all three curves run parallel to it in the asymptotic phase: between k = 60 and 100 they shrink by 0.7971, 0.7971 and 0.7971 per sweep.

逐步推导:伴随解处的反向程序:uˉ=0\bar u=0,Dˉ\bar D 就是梯度

Step by step: the reverse program at the adjoint solution: uˉ=0\bar u=0, and Dˉ\bar D is the gradient

把第 9 节的反向程序对所有对偶面走一遍,种子取 Rˉ=ψ\bar R=\psi、Lˉ=1\bar L=1。写横线的语句有三类:

Run the reverse program of Section 9 over all dual faces with the seeds Rˉ=ψ\bar R=\psi and Lˉ=1\bar L=1. Three kinds of statement write bars:

  • 通量的各句:uˉ+=ATψ\bar u\mathrel{\text{+=}}A^T\psi,逐面累加,正是 residual_b(u∗,ψ)(u^*,\psi);
  • the flux statements: uˉ+=ATψ\bar u\mathrel{\text{+=}}A^T\psi, face by face, which is residual_b(u∗,ψ)(u^*,\psi);
  • 壁面半面上目标那一句:uˉ[L]+=uL(𝒏⋅𝒆)\bar u[L]\mathrel{\text{+=}}u_L(\boldsymbol n\cdot\boldsymbol e),合起来就是第 7 节的 gg;同时 𝒏ˉ+=12uL2𝒆\bar{\boldsymbol n}\mathrel{\text{+=}}\tfrac12u_L^2\boldsymbol e,转回节点成为 ∂L/∂X\partial L/\partial X;
  • the objective's statement on the wall half-faces: uˉ[L]+=uL(𝒏⋅𝒆)\bar u[L]\mathrel{\text{+=}}u_L(\boldsymbol n\cdot\boldsymbol e), which adds up to the gg of Section 7, and 𝒏ˉ+=12uL2𝒆\bar{\boldsymbol n}\mathrel{\text{+=}}\tfrac12u_L^2\boldsymbol e, which becomes ∂L/∂X\partial L/\partial X once turned back to the nodes;
  • 入流的收集那一句:uˉ∞+=cRhˉ=cRψL\bar u_\infty\mathrel{\text{+=}}c_R\bar h=c_R\psi_L。residual_b 把这个横线丢掉,因为 uR=u∞u_R=u_\infty 不是状态;对设计来说它正是 u∞u_\infty 的伴随。
  • the gather of the inflow slot: uˉ∞+=cRhˉ=cRψL\bar u_\infty\mathrel{\text{+=}}c_R\bar h=c_R\psi_L. residual_b drops this bar, since uR=u∞u_R=u_\infty is not a state; for the design it is exactly the adjoint of u∞u_\infty.
uˉ=ATψ+g=0,uˉ∞=∑f: uR=u∞cR,f ψL(f)=ψTf,Xˉ=∂L∂X+ψT∂R∂X.\begin{gathered}\bar u=A^T\psi+g=0,\qquad\bar u_\infty=\sum_{f:\,u_R=u_\infty}c_{R,f}\,\psi_{L(f)}=\psi^Tf,\\ \bar X=\frac{\partial L}{\partial X}+\psi^T\frac{\partial R}{\partial X}.\end{gathered}

第一式说,伴随方程恰好就是“状态的横线为零”。本页的例子:入流半面 f72 的左节点是 20,cR=−0.8480c_R={-0.8480}(就是 f20f_{20}),ψ20=−0.0617\psi_{20}={-0.0617},这一句贡献 uˉ∞+=0.0523\bar u_\infty\mathrel{\text{+=}}0.0523;8 个入流半面加起来 uˉ∞=1.95162064261909\bar u_\infty=1.95162064261909,与第 10 节的 dL/du∞dL/du_\infty 相同。于是 Dˉ=(uˉ∞,Xˉ)\bar D=(\bar u_\infty,\bar X) 就是 dL/dDdL/dD。

The first relation says that the adjoint equation is exactly “the bar of the state is zero”. On this page: the inflow half-face f72 has left node 20, cR=−0.8480c_R={-0.8480} (which is f20f_{20}) and ψ20=−0.0617\psi_{20}={-0.0617}, so this statement contributes uˉ∞+=0.0523\bar u_\infty\mathrel{\text{+=}}0.0523; over the 8 inflow half-faces, uˉ∞=1.95162064261909\bar u_\infty=1.95162064261909, the dL/du∞dL/du_\infty of Section 10. So Dˉ=(uˉ∞,Xˉ)\bar D=(\bar u_\infty,\bar X) is dL/dDdL/dD.

为什么,用第 8 节的语言说:整个程序的对偶关系对任何种子都成立。前向种子取某个设计方向 D˙\dot D 与它的切线解 u˙\dot u,则 R˙=0\dot R=0(切线方程);反向种子取 Rˉ=ψ\bar R=\psi、Lˉ=1\bar L=1,则 uˉ=0\bar u=0(伴随方程)。剩下的就是

Why, in the language of Section 8: the duality of the whole program holds for any seeds. For the forward seeds take a design direction D˙\dot D and its tangent solution u˙\dot u; then R˙=0\dot R=0 (the tangent equation). For the reverse seeds take Rˉ=ψ\bar R=\psi and Lˉ=1\bar L=1; then uˉ=0\bar u=0 (the adjoint equation). What is left is

RˉTR˙⏟0+Lˉ L˙=uˉTu˙⏟0+DˉTD˙⟹L˙=DˉTD˙∀ D˙,\underbrace{\bar R^T\dot R}_{0}+\bar L\,\dot L=\underbrace{\bar u^T\dot u}_{0}+\bar D^T\dot D\quad\Longrightarrow\quad\dot L=\bar D^T\dot D\quad\forall\,\dot D,

所以 Dˉ\bar D 就是梯度。这与本节后面“从有限维 Lagrangian 逐步消去状态敏感度”一段是同一个论证,只是写成了点和横线。产生 ψ\psi 的 Jacobi 迭代,图 11.1 在网格上逐轮播放。

so Dˉ\bar D is the gradient. It is the argument of the block “Eliminate state sensitivity from a finite-dimensional Lagrangian” later in this section, written in dots and bars. The Jacobi iteration that produces ψ\psi is played on the mesh, sweep by sweep, in Figure 11.1.

核对:在 Rˉ=ψ\bar R=\psi、Lˉ=1\bar L=1 处,逐句写出的反向程序给出的 uˉ\bar u 最大 4.2×10−16;uˉ∞\bar u_\infty 与 ψTf\psi^Tf 之差 4.4×10−16;Xˉ\bar X 与本页 gradient(u,ψ)(u,\psi) 之差 2.2×10−16(本页代码在构建时计算)。

Check: at Rˉ=ψ\bar R=\psi, Lˉ=1\bar L=1, the reverse program written statement by statement gives uˉ\bar u of at most 4.2×10−16; uˉ∞\bar u_\infty differs from ψTf\psi^Tf by 4.4×10−16; Xˉ\bar X differs from this page’s gradient(u,ψ)(u,\psi) by 2.2×10−16 (computed by this page’s code at build time).

从有限维 Lagrangian 逐步消去状态敏感度

Eliminate state sensitivity from a finite-dimensional Lagrangian

ℒ(𝐮,D,ψ)=L(𝐮,D)+ψTR(𝐮,D).\mathcal L(\mathbf u,D,\psi)=L(\mathbf u,D)+\psi^TR(\mathbf u,D).

在满足 R=0R=0 的解上 ℒ=L\mathcal L=L。先把 ψ\psi 固定,沿切向量 𝐮˙\dot{\mathbf u}、D˙\dot D 对 ℒ\mathcal L 前向求导,再按 𝐮˙\dot{\mathbf u} 和 D˙\dot D 收集系数。即使 ψ\psi 随设计改变,额外的 ψ˙TR\dot\psi^TR 项也在收敛解上为零。

At a feasible state R=0R=0, ℒ=L\mathcal L=L. Hold ψ\psi fixed, differentiate ℒ\mathcal L forward along the tangents 𝐮˙\dot{\mathbf u} and D˙\dot D, then collect the coefficients of 𝐮˙\dot{\mathbf u} and D˙\dot D. Even if ψ\psi varies with the design, the extra ψ˙TR\dot\psi^TR term vanishes at the converged state.

ℒ˙=gT𝐮˙+(∇DL)TD˙+ψT(A𝐮˙+RDD˙)\dot{\mathcal L}=g^T\dot{\mathbf u}+(\nabla_DL)^T\dot D+\psi^T(A\dot{\mathbf u}+R_D\dot D)
=(g+ATψ)T𝐮˙+(∇DL+RDTψ)TD˙.=(g+A^T\psi)^T\dot{\mathbf u}+(\nabla_DL+R_D^T\psi)^T\dot D.

选择 ψ\psi 使未知的状态敏感度前面的系数为零。于是一个目标只需求解一次伴随线性系统,再进行设计反传;总设计梯度是列向量 ∇DL^\nabla_D\widehat L。注意符号来自这里的加号约定,并不是因为“伴随一定带负号”。

Choose ψ\psi to cancel the coefficient of the unknown state sensitivity. One objective then requires one adjoint solve followed by design reverse accumulation; the total design gradient ∇DL^\nabla_D\widehat L is a column. Its sign follows from the plus convention in this Lagrangian, not from a universal negative sign for adjoints.

ATψ=−g,∇DL^=∇DL+RDTψ,\boxed{A^T\psi=-g},\qquad\boxed{\nabla_D\widehat L=\nabla_DL+R_D^T\psi},
dL^dDk=∂L∂Dk+∑rψr∂Rr∂Dk.\frac{d\widehat L}{dD_k}=\frac{\partial L}{\partial D_k}+\sum_r\psi_r\frac{\partial R_r}{\partial D_k}.

用反向模式的横线记号:以 Rˉ=ψ\bar R=\psi 为种子运行残差的反向扫描,得到 𝐮ˉ=ATψ=−g\bar{\mathbf u}=A^T\psi=-g,这就是伴随方程。再把目标自己的反向语句(种子 Lˉ=1\bar L=1)累加进同一组横线量:它们给 𝐮ˉ\bar{\mathbf u} 加上 gg,给 Dˉ\bar D 加上 ∇DL\nabla_DL,于是 𝐮ˉ=g+ATψ=0\bar{\mathbf u}=g+A^T\psi=0。伴随方程说的正是状态的横线量总和为零:状态不再把敏感度往下传,同一次扫描的设计伴随 Dˉ=∇DL+RDTψ\bar D=\nabla_DL+R_D^T\psi 就是梯度 ∇DL^\nabla_D\widehat L。也可以直接检查与 tangent 的等价性:利用 Av=−RD D˙Av=-R_D\,\dot D,gTv=−ψTAv=ψTRD D˙g^Tv=-\psi^TAv=\psi^TR_D\,\dot D,所以两条路线给出同一个 L˙=DˉTD˙\dot L=\bar D^T\dot D。更换为 ℒ=L−λTR\mathcal L=L-\lambda^TR 会得到 λ=−ψ\lambda=-\psi,但这只是两个离散乘子的符号约定,不能据此认定连续场 λ(x)\lambda(x) 与离散向量逐点相等。

In the bar notation of reverse mode: the residual's reverse sweep seeded with Rˉ=ψ\bar R=\psi returns 𝐮ˉ=ATψ=−g\bar{\mathbf u}=A^T\psi=-g, which is the adjoint equation. Accumulate the objective's own reverse statements (seeded with Lˉ=1\bar L=1) into the same bars: they add gg to 𝐮ˉ\bar{\mathbf u} and ∇DL\nabla_DL to Dˉ\bar D, so 𝐮ˉ=g+ATψ=0\bar{\mathbf u}=g+A^T\psi=0. The adjoint equation says exactly that the state's total bar vanishes: the state passes no sensitivity on, and the design adjoint of the same sweep, Dˉ=∇DL+RDTψ\bar D=\nabla_DL+R_D^T\psi, is the gradient ∇DL^\nabla_D\widehat L. Check equivalence with the tangent: Av=−RD D˙Av=-R_D\,\dot D gives gTv=−ψTAv=ψTRD D˙g^Tv=-\psi^TAv=\psi^TR_D\,\dot D, so both routes give the same L˙=DˉTD˙\dot L=\bar D^T\dot D. Changing to ℒ=L−λTR\mathcal L=L-\lambda^TR yields λ=−ψ\lambda=-\psi, but that relates two discrete sign conventions; it does not prove pointwise equality with a continuous field λ(x)\lambda(x).

精确算子决定求解哪个方程;Jacobi 迭代决定如何求解。较便宜的近似 Jacobian(例如一阶或冻结部分系数的 Jacobian)可以用于迭代修正或预处理,但不能替代精确转置。应监控真残差 rψ=ATψ+gr_\psi=A^T\psi+g。若其不为零,当前伴随与精确 tangent 的方向导数差为 −rψTv-r_\psi^Tv,因此只看迭代次数或预处理残差不够。

The exact operator determines the equation; the Jacobi iteration determines how it is solved. A cheaper approximate Jacobian (first order, or with some coefficients frozen) may supply corrections or preconditioning, but cannot replace the exact transpose. Monitor the true residual rψ=ATψ+gr_\psi=A^T\psi+g. If it is nonzero, the current adjoint directional derivative minus the exact tangent derivative is −rψTv-r_\psi^Tv; an iteration count or a preconditioned residual alone is insufficient.

为什么一次伴随求解能代替 nan_a 次前向求解

Why one adjoint solve replaces nan_a tangent solves

同样的结论也可以不经过 Lagrangian,直接由前向求解的两个关系得到:

The same result follows without a Lagrangian, directly from the two relations of the tangent solve:

L˙=gT𝐮˙+(∇DL)TD˙,𝐮˙=−A−1RDD˙\dot L=g^T\dot{\mathbf u}+(\nabla_DL)^T\dot D,\qquad \dot{\mathbf u}=-A^{-1}R_D\dot D
⟹L˙=(∇DL)TD˙−gT A−1 RDD˙.\Longrightarrow\qquad \dot L=(\nabla_DL)^T\dot D-g^T\,A^{-1}\,R_D\dot D.

最后一项是三个因子 gTg^T、A−1A^{-1}、RDD˙R_D\dot D 的乘积,括号可以从两边加。前向路线先算 A−1(RDD˙)A^{-1}(R_D\dot D):它依赖方向 D˙\dot D,每个设计分量都要解一次以 AA 为系数的方程,全部分量要解 nan_a 次。伴随路线先算行向量 gTA−1g^TA^{-1}:它与 D˙\dot D 无关,只要解一次。把这个行向量记作 −ψT-\psi^T:

The last term is the product of three factors, gTg^T, A−1A^{-1} and RDD˙R_D\dot D, which can be bracketed from either side. The tangent route computes A−1(RDD˙)A^{-1}(R_D\dot D) first: it depends on the direction D˙\dot D, so every design component needs one solve with AA, nan_a solves for all of them. The adjoint route computes the row gTA−1g^TA^{-1} first: it does not depend on D˙\dot D, so it is solved once. Call this row −ψT-\psi^T:

ψT=−gT A−1⟺ATψ=−g,\psi^T=-g^T\,A^{-1}\quad\Longleftrightarrow\quad A^T\psi=-g,
L˙=(∇DL+RDTψ)TD˙=DˉTD˙.\dot L=\big(\nabla_DL+R_D^T\psi\big)^T\dot D=\bar D^T\dot D.

这对每个方向 D˙\dot D 都成立,所以梯度就是上面方框里的 ∇DL^=Dˉ\nabla_D\widehat L=\bar D:一次伴随求解之后,再以 Rˉ=ψ\bar R=\psi 为种子做一次反向扫描,就得到全部分量。两条路线的计算量(本页的 nan_a 与 nun_u 由本页代码数出,与页首路线图相同):

This holds for every direction D˙\dot D, so the gradient is the boxed ∇DL^=Dˉ\nabla_D\widehat L=\bar D above: after one adjoint solve, one reverse sweep seeded with Rˉ=ψ\bar R=\psi gives every component. The cost of the two routes (nan_a and nun_u of this page, counted by its code, as in the page roadmap at the top):

前向(tangent)路线Tangent route伴随(adjoint)路线Adjoint route
每个目标的线性求解Linear solves per objectivena=49n_a=49 次,系数 AAna=49n_a=49, with AA1 次,系数 ATA^T1, with ATA^T
右端Right-hand sides−RD ek,-R_D\,e_k, k=0,…,na−1k=0,\ldots,n_a-1−g-g
求解之后After the solvesnan_a 次前向扫描,各得一个 L˙\dot Lnan_a forward sweeps, one L˙\dot L each1 次反向扫描,得到整个 Dˉ\bar D1 reverse sweep, the whole Dˉ\bar D
一次求解给出One solve gives一个分量 dL^/dDkd\widehat{L}/dD_kone component dL^/dDkd\widehat{L}/dD_k全部 nan_a 个分量all nan_a components
nJn_J 个目标时With nJn_J objectives仍是 nan_a 次(vv 与目标无关)still nan_a (vv does not depend on the objective)nJn_J 次nJn_J

本页 na=49n_a=49、nu=24n_u=24:前向路线要解 49 个 24 阶的线性方程组,伴随路线只解 1 个。设计变量多而目标少时(形状优化通常如此)伴随路线便宜得多;设计变量少而目标多时,前向路线更合适。

This page has na=49n_a=49 and nu=24n_u=24: the tangent route solves 49 linear systems of order 24, the adjoint route one. With many design variables and few objectives (the usual case in shape optimisation) the adjoint route is far cheaper; with few design variables and many objectives the tangent route is the better choice.

新增(选读):光滑特征边界模型
Added (optional): smooth characteristic-boundary model

伴随方程:一次求解得到全部设计导数

Adjoint equation: one solve for every design derivative

与主线第 8 节一样,把乘积链先从左边乘起:解一次 ATψ=−L_uT,就得到 (dL/dD)T=L_DT+R_DTψ,也就是以 Rˉ=ψ\bar R=\psi 为种子反传到设计变量得到的 Dˉ\bar D。右端只取决于目标函数;一次求解加一次反向参数累加,就给出全部 48 个节点坐标及入流水平 u∞ 的导数。一阶用原来的 Jacobi 转置系统迭代;二阶用精确 AT 与转置 GS 预处理的 GMRES。

As in Section 8 of the main line, multiply the product chain from the left: one solve of ATψ=−L_uT gives (dL/dD)T=L_DT+R_DTψ, the design adjoint Dˉ\bar D of the reverse sweep seeded with Rˉ=ψ\bar R=\psi. The right-hand side depends only on the objective; one solve and one reverse parameter accumulation give the derivatives with respect to all 48 node coordinates and the inflow level u∞. First order retains Jacobi iteration of the transposed system; second order uses exact AT and transposed-GS-preconditioned GMRES.

ATψ=−LuT,(dLdD)T=LDT+RDTψ=Dˉ.A^T\psi=-L_u^T,\qquad\left(\frac{dL}{dD}\right)^T=L_D^T+R_D^T\psi=\bar D.

预处理的转置,用最少的话说。正向方程用 B 作预处理,伴随方程 ATψ=… 就要用 BT:GMRES 处理的是 ATBT=(BA)T。B 是“从 z=0 出发、按单元顺序做三次 GS 扫描”这一整个线性映射,它的转置就是把同样三次扫描按相反的单元顺序、用转置后的系数执行一遍。只转置一个局部系数不够,必须转置整个映射;两者是否互为转置,可以用点积测试 wT(Bv)=(BTw)Tv 检查(check.js)。停止条件检查原系统的真残差。没有把近似 Jacobian 的转置当作精确伴随算子。

The transposed preconditioner, in a few words. If the forward equation is preconditioned with B, the adjoint equation ATψ=… is preconditioned with BT: GMRES then works with ATBT=(BA)T. B is the whole linear map “three GS sweeps in cell order, starting from z=0”; its transpose runs the same three sweeps in reverse cell order with the transposed coefficients. Transposing one local coefficient is not enough; the whole map must be transposed, and the dot-product test wT(Bv)=(BTw)Tv checks that the two are transposes (check.js). Stopping tests use the true system residual. The approximate Jacobian transpose is not substituted for the exact adjoint operator.

一阶 Jacobi 解转置系统时,对角仍是 diag(A),但非对角乘积必须调用 ATψ。二阶用上面说的 BT 预处理 AT。面板显示每次迭代的 ψ 与真残差,收敛后的梯度由一次反向参数累加得到。

For first-order transposed Jacobi, diag(A) stays the same but the product must be ATψ. Second order preconditions AT with the BT described above. The player displays each ψ iterate and true residual; a single reverse parameter accumulation then gives all gradients.

图 11.5Figure 11.5
第 12 节Section 12

几何导数:dR/dX 与 dL/dX

Geometric derivatives: dR/dX and dL/dX

到这里为止,状态以外的输入只出现过远场来流 u∞(第 10、11 节);第 8 节只把网格坐标当作动机提了一句。真正的外形优化要的是对网格坐标 X 的导数。本节把这条链路从头到尾推一遍,前向与伴随各走一次,并说明几何项究竟藏在哪里。

So far the only input besides the state has been the free-stream value u∞ (Sections 10 and 11); Section 8 named the mesh coordinates only as motivation. Real shape optimisation needs the derivative with respect to the mesh coordinates X. This section works that chain through from end to end, once forward and once adjoint, and shows where the geometric terms actually hide.

两条链路

The two routes

设计变量取全部节点坐标 X(算例 24 个节点、48 个分量)。状态由 R(u,X)=0 隐式决定,目标写成 L(u,X)——两者都显式依赖坐标:残差通过面法向,目标通过物面法向。全导数是

The design variables are all the node coordinates X (24 nodes in the test case, 48 components). The state is fixed implicitly by R(u,X)=0 and the objective is written L(u,X) — and both depend explicitly on the coordinates as well: the residual through the face normals, the objective through the wall normals. The total derivative is

dLdX=∂L∂X+∂L∂ududX,

其中 du/dX 不能随便取:把 R(u(X),X)=0 对 X 求导,得到约束

where du/dX is not free to choose: differentiating R(u(X),X)=0 with respect to X gives the constraint

AdudX+∂R∂X=0,A=∂R∂u.

消去 du/dX 有两条路,区别只在先解哪一个:

There are two routes to eliminating du/dX, differing only in which one is solved first:

  • 前向(tangent):对每一个设计变量 Xk 解一次 Aφk=−∂R/∂Xk,再取 dL/dXk=∂L/∂Xk+gTφk(g=(∂L/∂u)T)。这里的 φk 就是种子 Ẋ=ek 下的切线 u̇。代价正比于设计变量个数。
  • Forward (tangent): for every design variable Xk, solve Aφk=−∂R/∂Xk once, then take dL/dXk=∂L/∂Xk+gTφk (with g=(∂L/∂u)T). Here φk is the tangent u̇ for the seed Ẋ=ek. The cost is proportional to the number of design variables.
  • 伴随(adjoint):只解一次 ATψ=−g,然后所有分量一起出来。
  • Adjoint: solve ATψ=−g just once, and every component comes out together.
dLdX=∂L∂X+ψT∂R∂X.

算例里前向要解 48 次线性系统,伴随只解 1 次,两条路的结果相差 4.4e−16:前向那一路的 ∂R/∂X 由 gradient_d 解析地逐列算出(见下文“转置关系照样成立”),两条路都是精确的,差别只剩舍入误差。各自与“逐分量重新求解 primal”的中心差分对照是 3.7e−10 与 3.7e−10(量级 1.032),这个量级由差分步长决定。这就是伴随方法的全部理由:代价与设计变量个数无关。

In the test case the forward route solves 48 linear systems and the adjoint only 1, and the two results differ by 4.4e−16: the ∂R/∂X of the forward route is computed analytically, column by column, by gradient_d (see “The transpose relation holds here too” below), so both routes are exact and only round-off separates them. Each agrees with central differences that re-solve the primal for every component to 3.7e−10 and 3.7e−10 respectively (on a magnitude of 1.032), a level set by the difference step. This is the whole reason for the adjoint method: its cost does not depend on the number of design variables.

这条链路和它的几何依赖范围,可以直接对照 图 12.1 来看。

This chain, and how far its geometric dependence reaches, can be read off directly from Figure 12.1.

(a) 依赖链路:前向顺着走,伴随反着走∂L/∂X(显式项,绕过状态)设计变量 X残差 R状态 u目标 L∂R/∂XψT​A⁻¹A−T​∂L/∂ug前向:对每个设计变量解一次 A φk = −∂R/∂Xk —— 共 48 次伴随:只解一次 AT​ψ = −g,再走一遍几何边循环 —— 共 1 次(b) 移动一个节点,哪些面的法向会变(实测)X˙1移动节点 n1:共 10 个面的法向改变(紫色,其中 6 个对偶面 + 4 个边界半面),其中 6 个并不以该节点为端点;另有 1 条以它为端点却恰好不变——依赖大多经由单元中心传出,比格心格式宽一圈。(a) The dependence chain: forward runs with it, the adjoint against it∂L/∂X (explicit term, bypassing the state)design variables Xresidual Rstate uobjective L∂R/∂XψT​A⁻¹A−T​∂L/∂ugForward: one solve of A φk = −∂R/∂Xk per design variable —— 48 solvesAdjoint: a single solve of AT​ψ = −g, then one pass of the geometric edge loop —— 1 in all(b) Which face normals change when one node moves (measured)X˙1move node n1: 10 face normals change (6 dual + 4 boundary half), 6 not its endpoints; unchanged endpoint faces: 1 — mostly via the cell centres: one ring wider than cell-centred.
图 12.1 · 上:几何量的依赖链路。前向沿 X→R→u→L 走,每个设计变量都要解一次 Aφk=−∂R/∂Xk;伴随把箭头整体反过来,只解一次 ATψ=−g 就得到全部分量。虚线是绕过状态的显式项 ∂L/∂X。下:移动一个节点,法向会变的对偶面里,有相当一部分并不以该节点为端点——依赖大多通过单元中心传出去,另有两条是相邻壁面节点在共享壁面边上的边界半面;反过来也有以它为端点却恰好不变的面(两个相邻单元的贡献互相抵消)。所以格点格式的几何依赖比格心格式宽一圈,图中条数均为实测。Figure 12.1 · Top: the dependence chain of the geometric quantities. The forward route runs along X→R→u→L and solves Aφk=−∂R/∂Xk once per design variable; the adjoint reverses every arrow and gets all the components from a single solve of ATψ=−g. The dashed line is the explicit term ∂L/∂X, which bypasses the state. Bottom: when one node is moved, a fair share of the dual faces whose normal changes do not have that node as an endpoint — most of that dependence travels outwards through the cell centres, and two are the neighbouring wall nodes’ half-faces on the shared wall edges; conversely, some faces that do have it as an endpoint happen not to change at all (the contributions of the two adjacent cells cancel). So the geometric dependence of a node-centred scheme reaches one ring wider than a cell-centred one, and every count in the figure is measured.

几何量怎么依赖坐标

How the geometric quantities depend on the coordinates

残差对 X 的依赖全部藏在对偶面法向里。第 2 节给过它的构造:一条内部边 (i,j) 的法向由相邻单元各贡献一段,

The dependence of the residual on X hides entirely in the dual-face normals. Section 2 gave the construction: the normal of an interior edge (i,j) is assembled from one segment contributed by each adjacent cell,

nij=Σt∋(i,j)±(dy,−dx),d=xc−m,m=12(xi+xj),

其中 xc 是单元中心(顶点平均)。于是移动一个节点会同时改变它所有相邻单元的中心,进而改变这些单元里几乎每一条对偶面的法向(个别面上两个相邻单元的贡献恰好抵消,见上面图 12.1 的 (b))——格点格式的几何依赖比格心格式宽一圈,这是两种格式在几何导数上唯一的实质差别。

where xc is the cell centre (the average of its vertices). So moving one node changes the centres of all its adjacent cells at once, and with them the normal of almost every dual face in those cells (on a few faces the two adjacent cells' contributions cancel exactly — see (b) of Figure 12.1 above) — the geometric dependence of a node-centred scheme reaches one ring wider than a cell-centred one, and that is the only substantive difference between the two schemes in the geometric derivatives.

通量对法向的导数与格心版逐字相同:

The derivative of the flux with respect to the normal is word for word the same as in the cell-centred version:

∂h∂n=14(uL2+uR2)β−ε(uR−uL)n|n|.

差别只在怎么把 n̄ 送回节点。先把旋转转置掉,d̄=(−sn̄y,sn̄x)(s=±1 是该段的取向),再按 d=xc−m 分给两处:

The difference lies only in how n̄ is sent back to the nodes. First transpose the rotation away, d̄=(−sn̄y,sn̄x) (where s=±1 is the orientation of that segment), then distribute it to two places through d=xc−m:

x̄k+=d̄|t|(k∈t),x̄i,x̄j−=12d̄.
x̄k+=d̄|t|(k∈t),x̄i,x̄j−=12d̄.

前一式对该单元 t 的每个顶点 k 各做一次,它来自 xc 是顶点平均(|t| 是该单元的顶点数,三角形 3、四边形 4),后一式来自 m 是边中点。边界半面简单得多,它就是格心版的一半:nbnd=12(yb−ya,−(xb−xa)),转置回去也只差一个 12。

The first update is made once for every vertex k of that cell t; it comes from xc being the vertex average (|t| is the number of vertices of that cell, 3 for a triangle and 4 for a quadrilateral), the second from m being the edge midpoint. A boundary half-face is far simpler, being exactly half of the cell-centred version: nbnd=12(yb−ya,−(xb−xa)), and transposing it back differs only by that 12.

前向方向同样是解析的。把上面的回传倒过来走,就是 gradient_d(u, X_d):单元中心的位移是它各顶点位移的平均 ẋc,边中点的位移是 ṁ=12(ẋi+ẋj),每一段贡献 ±(ḋy,−ḋx),ḋ=ẋc−ṁ,加起来就是 ṅij;边界半面取所在边界边的一半。再算 ḣ=(∂h/∂n)·ṅ,按 h 的符号分发;壁面半面上 h≡0,只给目标函数加上 12uL2e·ṅ。它与 gradient 的关系,正是第 8、9 节 residual_d 与 residual_b 的关系。

The forward direction is analytic too. Run the transposed steps above the other way and you have gradient_d(u, X_d): a cell centre moves by the average ẋc of its vertices' displacements and an edge midpoint by ṁ=12(ẋi+ẋj), so each segment contributes ±(ḋy,−ḋx) with ḋ=ẋc−ṁ, and their sum is ṅij; a boundary half-face takes half of its boundary edge's. Then ḣ=(∂h/∂n)·ṅ is scattered with the signs of h; on a wall half-face h≡0 and only the objective gains 12uL2e·ṅ. It is to gradient what residual_d of Section 8 is to residual_b of Section 9.

目标函数的显式项

The explicit term of the objective

L=Σ壁面12uL2(n·e) 同样显式依赖坐标,而且只在物面上:

L=Σwall12uL2(n·e) depends explicitly on the coordinates too, and only on the body surface:

∂L∂n=12uL2e.

投影之后它是一个常向量,比通量那一项还简单。无粘壁面上 h≡0,所以壁面对 ψT∂R/∂X 没有贡献,却对 ∂L/∂X 有贡献——两项在同一个边循环里累加,这也是 gradient(u, psi) 一次循环就能给出完整梯度的原因。

After projection it is a constant vector, simpler even than the flux term. On an inviscid wall h≡0, so the wall contributes nothing to ψT∂R/∂X while it does contribute to ∂L/∂X — the two terms accumulate in the same edge loop, which is why gradient(u, psi) delivers the complete gradient in a single pass.

两项可以分开验证。把种子取成 ψ=0:非壁面分支的 h̄ 全为零,只剩目标函数那一项,于是 gradient(u, 0) 就是 ∂L/∂X;再用 gradient(u, psi) 减掉它,剩下的正是 ψT∂R/∂X。两半分别与中心差分(u 冻结)对照:8.4e−11 与 1.0e−10。

The two terms can be verified separately. Take the seed ψ=0: every h̄ outside the wall branch vanishes and only the objective term is left, so gradient(u, 0) is ∂L/∂X; subtract it from gradient(u, psi) and what remains is exactly ψT∂R/∂X. The two halves agree with central differences (with u frozen) to 8.4e−11 and 1.0e−10.

转置关系照样成立

The transpose relation holds here too

几何算子也有前向与伴随两个方向,同样可以用点积测试卡住(对偶关系 R̄TṘ=X̄TẊ,种子 R̄=w):

The geometric operator has a forward and an adjoint direction as well, and a dot-product test pins it down just the same (the duality R̄TṘ=X̄TẊ with the seed R̄=w):

⟨w,(∂R/∂X)Ẋ⟩=⟨(∂R/∂X)Tw,Ẋ⟩.

算例实测偏差 1.9e−16:前向方向是解析的 gradient_d,反向是 gradient,两边都精确,所以这是一个真正的转置检验,偏差只是舍入误差,与第 9 节状态空间的点积测试一样。gradient_d 另外单独与中心差分(u 冻结,h=10−6)对照过:相差 1.5e−10,那是差分步长的量级。注意 w 住在残差空间(24 维)、Ẋ 住在坐标空间(48 维)——两个空间维数不同,转置关系依然成立:这正是“伴随”与“转置”是同一件事的又一次体现。

The measured deviation is 1.9e−16: the forward direction is the analytic gradient_d and the adjoint direction gradient, both exact, so this is a true transpose test and the deviation is round-off, as in the state-space test of Section 9. gradient_d itself is checked separately against central differences (u frozen, h=10−6): they differ by 1.5e−10, the level of the difference step. Note that w lives in the residual space (24 dimensions) and Ẋ in the coordinate space (48 dimensions) — the two spaces have different dimensions and the transpose relation holds regardless: one more sign that "adjoint" and "transpose" are the same thing.

一条必然成立的结构性检查

A structural check that cannot fail

把整个网格刚体平移,所有法向都不变,因此 L 不变。于是梯度必须满足

Translate the whole mesh rigidly and no normal changes, so L does not change either. The gradient must therefore satisfy

ΣkdLdXk=0

(两个坐标分量各自求和为零)。这条与差分无关、与算例无关,纯粹由问题的平移不变性决定,是几何梯度最便宜的自检——不过它只查平移不变性:通量导数的符号即使写错,各分量之和照样为零。算例实测两个分量之和的最大绝对值为 7.4×10−16。

(each of the two coordinate components summing to zero on its own). This owes nothing to finite differences and nothing to this particular test case; it follows purely from the translational invariance of the problem, and it is the cheapest self-check a geometric gradient has, though it only tests translational invariance: even a sign error in the flux derivative still sums to zero. The measured largest absolute value of the two component sums is 7.4×10−16.

全部实测

Everything, measured

检查Check格心Cell-centred格点Node-centred
显式 ∂L/∂X 对中心差分(u 冻结)Explicit ∂L/∂X against central differences (u frozen)5.8e−118.4e−11
ψT∂R/∂X 对中心差分(u 冻结)ψT∂R/∂X against central differences (u frozen)3.5e−111.0e−10
解析的 (∂R/∂X)Ẋ(gradient_d)对中心差分(u 冻结)Analytic (∂R/∂X)Ẋ (gradient_d) against central differences (u frozen)1.7e−101.5e−10
几何算子点积测试(转置;两侧都是解析的)Dot-product test of the geometric operator (transpose; both sides analytic)5.6e−171.9e−16
前向链路对全链路中心差分Forward route against full-chain central differences2.2e−103.7e−10
伴随链路对全链路中心差分Adjoint route against full-chain central differences2.2e−103.7e−10
前向链路对伴随链路(两侧都是解析的)Forward route against adjoint route (both analytic)6.9e−174.4e−16
平移不变性:各分量之和的最大绝对值Translational invariance: largest absolute component sum6.2e−177.4e−16
线性系统求解次数(前向 / 伴随)Linear solves (forward / adjoint)48 / 148 / 1

真实求解器里还多一层。本页把所有节点坐标都当设计变量。真实外形优化的设计变量是物面参数(FFD 控制点、样条系数……),体网格由变形算法从物面推出:Xvol=G(Xsurf)。链式法则再接一段 ∂G/∂Xsurf:伴随方向就是把本节得到的 dL/dXvol 通过网格变形算法的转置送回物面。结构一点没变,只是多接一段——而且那一段同样要用点积测试单独验证。

A real solver has one more layer. This page treats every node coordinate as a design variable. In real shape optimisation the design variables are surface parameters (FFD control points, spline coefficients, …), and the volume mesh is produced from the surface by a deformation algorithm: Xvol=G(Xsurf). The chain rule then gains one more link, ∂G/∂Xsurf: in the adjoint direction the dL/dXvol obtained here is sent back to the surface through the transpose of the mesh-deformation algorithm. The structure does not change at all, there is simply one more link — and that link has to be verified separately, with a dot-product test of its own.

逐步推导:一个对偶面的几何链

Step by step: the geometric chain of one dual face

第一步,对偶面的法向由边中点与相邻单元的中心构成(第 2 节),它们都是节点坐标的线性函数;控制体面积 ViV_i 不进入 RR(第 2 节),所以几何只经由单元中心和边中点进入:

Step 1. A dual-face normal is built from the edge midpoint and the centres of the neighbouring cells (Section 2), all linear in the node coordinates; the control-volume areas ViV_i do not enter RR (Section 2), so the geometry enters only through the cell centres and edge midpoints:

𝒏ij=∑t∋(i,j)st Q(𝒙c,t−𝒎),𝒎=12(𝒙i+𝒙j),𝒙c,t=1∣t∣∑k∈t𝒙k.\begin{gathered}\boldsymbol n_{ij}=\sum_{t\ni(i,j)}s_t\,Q(\boldsymbol x_{c,t}-\boldsymbol m),\\ \boldsymbol m=\tfrac12(\boldsymbol x_i+\boldsymbol x_j),\qquad\boldsymbol x_{c,t}=\frac1{|t|}\sum_{k\in t}\boldsymbol x_k.\end{gathered}

所以对节点 kk 的 Jacobian 是常数矩阵:kk 是单元 tt 的顶点时有一项 stQ/∣t∣s_tQ/|t|,kk 是边的端点 ii 或 jj 时再有一项 −stQ/2-s_tQ/2:

So the Jacobian with respect to node kk is a constant matrix: a term stQ/∣t∣s_tQ/|t| when kk is a vertex of cell tt, and a further −stQ/2-s_tQ/2 when kk is an endpoint ii or jj of the edge:

∂𝒏ij∂𝒙k=∑t∋(i,j)st Q([ k∈t ]∣t∣−[ k∈{i,j} ]2),\frac{\partial\boldsymbol n_{ij}}{\partial\boldsymbol x_k}=\sum_{t\ni(i,j)}s_t\,Q\Big(\frac{[\,k\in t\,]}{|t|}-\frac{[\,k\in\{i,j\}\,]}{2}\Big),

方括号在条件成立时为 1,否则为 0。边界半面 𝒏=12Q(𝒙b−𝒙a)\boldsymbol n=\tfrac12Q(\boldsymbol x_b-\boldsymbol x_a) 与格心页的面一样,只多一个 12\tfrac12。

where a bracket is 1 when its condition holds and 0 otherwise. A boundary half-face, 𝒏=12Q(𝒙b−𝒙a)\boldsymbol n=\tfrac12Q(\boldsymbol x_b-\boldsymbol x_a), is the cell-centred face with an extra 12\tfrac12.

第二步,通量对法向有两条路:vn=𝜷⋅𝒏v_n=\boldsymbol\beta\cdot\boldsymbol n(∂vn/∂𝒏=𝜷\partial v_n/\partial\boldsymbol n=\boldsymbol\beta)与 ∣𝒏∣=(nx2+ny2)1/2|\boldsymbol n|=(n_x^2+n_y^2)^{1/2}(∂∣𝒏∣/∂𝒏=𝒏/∣𝒏∣\partial|\boldsymbol n|/\partial\boldsymbol n=\boldsymbol n/|\boldsymbol n|);uLu_L、uRu_R 是另外的输入,保持不变:

Step 2. The flux reaches the normal by two routes: vn=𝜷⋅𝒏v_n=\boldsymbol\beta\cdot\boldsymbol n (∂vn/∂𝒏=𝜷\partial v_n/\partial\boldsymbol n=\boldsymbol\beta) and ∣𝒏∣=(nx2+ny2)1/2|\boldsymbol n|=(n_x^2+n_y^2)^{1/2} (∂∣𝒏∣/∂𝒏=𝒏/∣𝒏∣\partial|\boldsymbol n|/\partial\boldsymbol n=\boldsymbol n/|\boldsymbol n|); uLu_L and uRu_R are separate inputs and stay fixed:

∂h∂𝒏=∂h∂vn 𝜷+∂h∂∣𝒏∣ 𝒏∣𝒏∣=14(uL2+uR2) 𝜷−ε(uR−uL) 𝒏∣𝒏∣.\begin{aligned}\frac{\partial h}{\partial\boldsymbol n}&=\frac{\partial h}{\partial v_n}\,\boldsymbol\beta+\frac{\partial h}{\partial|\boldsymbol n|}\,\frac{\boldsymbol n}{|\boldsymbol n|}\\&=\tfrac14(u_L^2+u_R^2)\,\boldsymbol\beta-\varepsilon(u_R-u_L)\,\frac{\boldsymbol n}{|\boldsymbol n|}.\end{aligned}

第三步,前向(gradient_d):𝒏˙=∑k(∂𝒏/∂𝒙k) 𝒙˙k\dot{\boldsymbol n}=\sum_k(\partial\boldsymbol n/\partial\boldsymbol x_k)\,\dot{\boldsymbol x}_k,h˙=(∂h/∂𝒏)⋅𝒏˙\dot h=(\partial h/\partial\boldsymbol n)\cdot\dot{\boldsymbol n},再按 hh 的符号分发。第四步,反向(gradient):𝒏ˉ=hˉ ∂h/∂𝒏\bar{\boldsymbol n}=\bar h\,\partial h/\partial\boldsymbol n(壁面 h≡0h\equiv0,只加目标的 12uL2𝒆\tfrac12u_L^2\boldsymbol e),再乘各个 Jacobian 的转置:

Step 3, forward (gradient_d): 𝒏˙=∑k(∂𝒏/∂𝒙k) 𝒙˙k\dot{\boldsymbol n}=\sum_k(\partial\boldsymbol n/\partial\boldsymbol x_k)\,\dot{\boldsymbol x}_k, h˙=(∂h/∂𝒏)⋅𝒏˙\dot h=(\partial h/\partial\boldsymbol n)\cdot\dot{\boldsymbol n}, scattered with the signs of hh. Step 4, reverse (gradient): 𝒏ˉ=hˉ ∂h/∂𝒏\bar{\boldsymbol n}=\bar h\,\partial h/\partial\boldsymbol n (on the wall h≡0h\equiv0 and only the objective’s 12uL2𝒆\tfrac12u_L^2\boldsymbol e is added), then multiply by the transpose of each Jacobian:

𝒙ˉk  +=  (∂𝒏∂𝒙k)T𝒏ˉ:𝒅ˉ=stQT𝒏ˉ,𝒙ˉk  +=  𝒅ˉ∣t∣  (k∈t),𝒙ˉi  −=  12𝒅ˉ,𝒙ˉj  −=  12𝒅ˉ.\begin{gathered}\bar{\boldsymbol x}_k\;\text{+=}\;\left(\frac{\partial\boldsymbol n}{\partial\boldsymbol x_k}\right)^T\bar{\boldsymbol n}:\qquad \bar{\boldsymbol d}=s_tQ^T\bar{\boldsymbol n},\\ \bar{\boldsymbol x}_k\;\text{+=}\;\frac{\bar{\boldsymbol d}}{|t|}\ \ (k\in t),\qquad \bar{\boldsymbol x}_i\;\text{−=}\;\tfrac12\bar{\boldsymbol d},\quad\bar{\boldsymbol x}_j\;\text{−=}\;\tfrac12\bar{\boldsymbol d}.\end{gathered}

这正是 core_node.js 里 gradient() 的 db = [−s·nb_y, s·nb_x] 与之后的两次分发。

These are db = [−s·nb_y, s·nb_x] and the two scatters after it in gradient() of core_node.js.

第五步,每个面单独满足平移不变性:对所有节点求和,∑k∂𝒏ij/∂𝒙k=∑tstQ(∣t∣⋅1∣t∣−2⋅12)=0\sum_k\partial\boldsymbol n_{ij}/\partial\boldsymbol x_k=\sum_ts_tQ\big(|t|\cdot\tfrac1{|t|}-2\cdot\tfrac12\big)=0,所以一个面送回各节点的量加起来为零(图 12.2)。上面“一条必然成立的结构性检查”那一小节,就是这些面逐个求和的结果。

Step 5. Every face is translation invariant on its own: summed over all nodes, ∑k∂𝒏ij/∂𝒙k=∑tstQ(∣t∣⋅1∣t∣−2⋅12)=0\sum_k\partial\boldsymbol n_{ij}/\partial\boldsymbol x_k=\sum_ts_tQ\big(|t|\cdot\tfrac1{|t|}-2\cdot\tfrac12\big)=0, so what one face sends back to the nodes adds up to zero (Figure 12.2). The subsection “A structural check that cannot fail” above is the sum of these, face by face.

核对:对偶面 f13 上 ∂h/∂𝒏\partial h/\partial\boldsymbol n 与通量公式对 𝒏\boldsymbol n 的中心差分之差 4.4×10−11;按上面的公式逐对偶面累加,与本页 gradient(u,ψ)(u,\psi) 的最大差 1.1×10−16;各对偶面送出的量之和的最大模 7.8×10−18(本页代码在构建时计算)。

Check: on dual face f13, ∂h/∂𝒏\partial h/\partial\boldsymbol n differs from central differences of the flux formula in 𝒏\boldsymbol n by 4.4×10−11; accumulated dual face by dual face with the formulas above, the result differs from this page’s gradient(u,ψ)(u,\psi) by at most 1.1×10−16; the largest modulus of what one dual face sends out, summed, is 7.8×10−18 (computed by this page’s code at build time).

n6n14n/|n|¼(uL²+uR²) β∂h/∂n(a) 面 f13:∂h/∂n 的两部分(a) face f13: the two parts of ∂h/∂nn6n14n5n13n15n7(b) 反向:h¯​ ∂h/∂n 送回各节点(b) reverse: h¯​ ∂h/∂n sent back to the nodes箭头之和 = 0sum of the arrows = 0
图 12.2 · 一个对偶面的几何导数。(a) 面 f13 连接节点 6、14,由两段折线组成(紫色,从边中点到两侧单元的中心)。在收敛解处 ∂h/∂n(紫色箭头)是两部分之和:沿 β 的 ¼(uL2+uR2)β(蓝色实线)与沿 n/|n| 的 −ε(uR−uL)n/|n|(蓝色虚线);本页数值 ¼(uL2+uR2)=0.8591,−ε(uR−uL)=0.3989。(b) 反向时 n̄=h̄∂h/∂n(h̄=ψ6−ψ14=−0.8379)先转回 90°,再经边中点送给两端节点、经单元中心送给两个单元的每个顶点:这一个面最后只影响 4 个节点(红色箭头,同一比例):两个端点 6、14 经边中点与两个中心收到的量正好抵消(两侧都是四边形时正是如此)。整体平移不改变法向,所以这些箭头之和为零;本页代码算得 0。Figure 12.2 · One dual face’s geometric derivative. (a) Face f13 joins nodes 6 and 14 and consists of two segments (violet, from the edge midpoint to the centres of the two neighbouring cells). At the converged state ∂h/∂n (violet arrow) is the sum of two parts: ¼(uL2+uR2)β along β (solid blue) and −ε(uR−uL)n/|n| along n/|n| (dashed blue); on this page ¼(uL2+uR2) = 0.8591 and −ε(uR−uL) = 0.3989. (b) In reverse, n̄=h̄∂h/∂n (h̄ = ψ6 − ψ14 = −0.8379) is first turned back by 90°, then passed through the edge midpoint to the two endpoints and through the cell centres to every vertex of the two cells: in the end this one face moves only 4 nodes (red arrows, one common scale): what its two ends 6 and 14 receive through the midpoint and the two centres cancels exactly (as it does whenever both neighbouring cells are quadrilaterals). A rigid translation leaves the normal unchanged, so the arrows sum to zero; this page’s code gives 0.

把全部对偶面加起来,得到的梯度正是两项之和(图 12.3):显式项只在壁面节点上,ψT∂R/∂X\psi^T\partial R/\partial X 铺满全场。

Adding up all dual faces gives the gradient as the sum of its two terms (Figure 12.3): the explicit term sits on the wall nodes only, ψT∂R/∂X\psi^T\partial R/\partial X covers the whole field.

(a) 显式项 ∂L/∂X(a) explicit term ∂L/∂X(b) ψT​∂R/∂X(c) 全梯度 dL/dX(c) total dL/dX= 0.5比例:scale:
图 12.3 · 形状梯度拆成两项,画成节点上的箭头(三幅图同一比例,底部箭头给出长度 0.5)。(a) 显式项 ∂L/∂X:L 只通过壁面法向显式依赖坐标,所以只有 8 个壁面节点上有箭头;它就是 gradient(u, 0)。(b) ψT∂R/∂X:每个跑通量的面都把 h̄∂h/∂n 送回它的节点,全场都有箭头。(c) 两者之和 dL/dX=gradient(u, ψ),它指向 L 增长最快的节点移动方向。两项由本页代码分别算出;(c) 的每个坐标分量之和为 7.4×10⁻¹⁶(平移不变性)。Figure 12.3 · The shape gradient split into its two terms, drawn as arrows on the nodes (one scale for all three panels; the arrow at the bottom has length 0.5). (a) The explicit term ∂L/∂X: L depends on the coordinates explicitly only through the wall normals, so only the 8 wall nodes carry arrows; this is gradient(u, 0). (b) ψT∂R/∂X: every face that runs the flux sends h̄∂h/∂n back to its nodes, and arrows appear over the whole field. (c) Their sum dL/dX = gradient(u, ψ), which points in the direction in which moving each node raises L fastest. The two terms are computed separately by this page’s code; each coordinate component of (c) sums to 7.4×10⁻¹⁶ (translational invariance).

逐步推导:什么随坐标而变:法向、长度、面积与中心

Step by step: what moves with the coordinates: normals, lengths, areas and centres

第 8 节表里的第 1–5 句是对偶面法向的构造,本页的残差经由它们依赖坐标:边中点、单元中心(顶点的平均)、线段与法向都是坐标的线性函数,它们的切线与伴随在第 8、9 节的两张表里;接着是长度 ℓ=∣𝒏∣\ell=|\boldsymbol n| 与法向速度。所以在格点格式里单元中心确实进入 RR。单元(以及对偶控制体)的面积随坐标的变化是:

Statements 1–5 of the table in Section 8 build the dual-face normal, and through them this page’s residual depends on the coordinates: the edge midpoint, the cell centres (vertex averages), the segments and the normal are linear in the coordinates, with their tangents and adjoints in the two tables of Sections 8 and 9; then come the length ℓ=∣𝒏∣\ell=|\boldsymbol n| and the normal speed. So on the node-centred page the cell centres do enter RR. The area of a cell (or of a dual control volume) moves with the coordinates as

V=12∑q(xqyq+1−xq+1yq),∂V∂𝒙q=12(𝒏q−1,q+𝒏q,q+1),V˙=∑(a,b)12(𝒙˙a+𝒙˙b)⋅𝒏ab,\begin{gathered}V=\tfrac12\sum_q(x_qy_{q+1}-x_{q+1}y_q),\qquad\frac{\partial V}{\partial\boldsymbol x_q}=\tfrac12\big(\boldsymbol n_{q-1,q}+\boldsymbol n_{q,q+1}\big),\\ \dot V=\sum_{(a,b)}\tfrac12(\dot{\boldsymbol x}_a+\dot{\boldsymbol x}_b)\cdot\boldsymbol n_{ab},\end{gathered}

求和遍及多边形的各条边 (a,b)(a,b),𝒏ab=Q(𝒙b−𝒙a)\boldsymbol n_{ab}=Q(\boldsymbol x_b-\boldsymbol x_a) 是逆时针边的外法向。后一式说,一个多边形的面积以它的边向外扫过的速率增长,每条边以两端速度的平均移动(离散几何守恒律)。

The sum runs over the edges (a,b)(a,b) of the polygon, and 𝒏ab=Q(𝒙b−𝒙a)\boldsymbol n_{ab}=Q(\boldsymbol x_b-\boldsymbol x_a) is the outward normal of a counter-clockwise edge. The last relation says that a polygon grows at the rate its edges sweep outwards, each edge moving with the mean velocity of its ends (the discrete geometric conservation law).

稳态残差里没有面积:对偶控制体的面积 ViV_i 只乘 uiu_i 对时间的导数,只在第 15 节的源项与误差范数里出现。所以 ∂R/∂X\partial R/\partial X 只经过法向的构造、ℓ\ell 与 vnv_n。

The steady residual has no area in it: the dual-volume area ViV_i multiplies only the time derivative of uiu_i and appears only in the source and the error norms of Section 15. So ∂R/∂X\partial R/\partial X runs only through the construction of the normal, ℓ\ell and vnv_n.

第 8 节的例子正好走了中心那条路:种子只动节点 7,它是单元 t′t' 的顶点,不是对偶面 f13 的端点,所以 𝒎˙=0\dot{\boldsymbol m}=0,𝒙˙c,t′=14𝒙˙7\dot{\boldsymbol x}_{c,t'}=\tfrac14\dot{\boldsymbol x}_{7},𝒏˙=st′Q𝒙˙c,t′=(0.00, −0.25)\dot{\boldsymbol n}=s_{t'}Q\dot{\boldsymbol x}_{c,t'}=(0.00,\,{-0.25})。反过来,两段方向相反(st=−st′s_t=-s_{t'}),所以 𝒅ˉt=−𝒅ˉt′\bar{\boldsymbol d}_t=-\bar{\boldsymbol d}_{t'}、𝒎ˉ=0\bar{\boldsymbol m}=0:两个端点 6、14 从中点与两个中心收到的量正好抵消(两个单元的顶点数相同,都是 4 个,两份 1/∣t∣1/|t| 一样大),只剩两个单元的另外 4 个顶点。

Section 8’s example takes exactly the centre path: the seed moves only node 7, a vertex of cell t′t' but not an end of dual face f13, so 𝒎˙=0\dot{\boldsymbol m}=0, 𝒙˙c,t′=14𝒙˙7\dot{\boldsymbol x}_{c,t'}=\tfrac14\dot{\boldsymbol x}_{7} and 𝒏˙=st′Q𝒙˙c,t′=(0.00, −0.25)\dot{\boldsymbol n}=s_{t'}Q\dot{\boldsymbol x}_{c,t'}=(0.00,\,{-0.25}). In reverse, the two segments point opposite ways (st=−st′s_t=-s_{t'}), so 𝒅ˉt=−𝒅ˉt′\bar{\boldsymbol d}_t=-\bar{\boldsymbol d}_{t'} and 𝒎ˉ=0\bar{\boldsymbol m}=0: what the two ends 6 and 14 receive through the midpoint and the two centres cancels exactly (the two cells have the same number of vertices, 4 each, so the two shares 1/∣t∣1/|t| are equal), and only the other 4 vertices of the two cells are left.

核对:单元 1(顶点 1、9、10、2)的面积 0.3436,上式的 ∂V/∂𝒙q\partial V/\partial\boldsymbol x_q 与中心差分之差 9.4×10−11;随机顶点速度下 V˙\dot V 的两种写法之差 1.1×10−16(本页代码在构建时计算)。

Check: cell 1 (vertices 1, 9, 10, 2) has area 0.3436; the ∂V/∂𝒙q\partial V/\partial\boldsymbol x_q above differs from central differences by 9.4×10−11, and the two forms of V˙\dot V differ by 1.1×10−16 for random vertex velocities (computed by this page’s code at build time).

逐面看几何导数:前向与反向

The geometric derivative face by face: forward and reverse

图 12.4 播放 gradient_d:种子只动一个节点的 xx,状态冻结(u˙=0\dot u=0),第 8 节的语句逐面执行。只有法向随这个节点变的那几个面有事可做;3 个可选节点分别在壁面、中间一圈与远场,远场的入流、出流半面按第 8 节第二步处理。最后一帧 R˙=(∂R/∂X)𝒆k\dot R=(\partial R/\partial X)\boldsymbol e_k 的相反数就是第 10 节切线方程的右端项。

Figure 12.4 plays gradient_d: the seed moves the xx of one node, the state is frozen (u˙=0\dot u=0), and the statements of Section 8 run face by face. Only the few faces whose normal moves with this node have anything to do; the 3 nodes to choose from sit on the wall, on the middle ring and on the far field, where the inflow and outflow half-faces follow Step 2 of Section 8. The negative of the last frame, R˙=(∂R/∂X)𝒆k\dot R=(\partial R/\partial X)\boldsymbol e_k, is the right-hand side of the tangent equation of Section 10.

f12: h˙​ = −0.1333 → n6+, n7−f13: h˙​ = −0.0423 → n6+, n14−f14: h˙​ = −0.1273 → n7+, n8−f16: h˙​ = 0.0637 → n8+, n16−f33: h˙​ = −0.0705 → n14+, n15−f36: h˙​ = −0.0751 → n15+, n16−f60: h ≡ 0, L˙​ += 0.2638f61: h ≡ 0, L˙​ += 0.2384f62: h ≡ 0, L˙​ += −0.2384f63: h ≡ 0, L˙​ += −0.1442X˙​Σ: R˙​ ≠ 0 in n6, n7, n8, n14, n15, n16f12: h˙​ = 0.1333 → n6+, n7−f13: h˙​ = −0.0423 → n6+, n14−f14: h˙​ = 0.1273 → n7+, n8−f16: h˙​ = 0.0637 → n8+, n16−f33: h˙​ = 0.0235 → n14+, n15−f35: h˙​ = −0.0822 → n14+, n23−f36: h˙​ = 0.0250 → n15+, n16−f40: h˙​ = 0.0860 → n16+, n24−f48: h˙​ = −0.0836 → n23+, n24−X˙​Σ: R˙​ ≠ 0 in n6, n7, n8, n14, n15, n16, n23, n24f24: h˙​ = 0.0976 → n11+, n12−f25: h˙​ = −0.1043 → n11+, n19−f29: h˙​ = 0.0869 → n12+, n21−f44: h˙​ = 0.0446 → n19+, n20−f45: h˙​ = 0.0405 → n20+, n21−f70: h˙​ = −0.1331 → n19+f71: h˙​ = −0.1335 → n20+f72: h˙​ = 0.1344 → n20+f73: h˙​ = 0.1281 → n21+X˙​Σ: R˙​ ≠ 0 in n11, n12, n19, n20, n21(a) 网格:种子、当前面与它的 n˙,以及累加的 R˙​(a) the mesh: the seed, the current face with its n˙, and the accumulated R˙​(b) 每个面送出的 h˙​(或 L˙​)(b) what each face sends out: h˙​ (or L˙​)
12 / 12
f12 · ṅ = (0.0000, −0.2500) · v̇n = −0.1250 · ℓ̇ = 0.1163 · ḣ = ¼(uL2+uR2)v̇n − ε(uR−uL)ℓ̇ = −0.1404 + 0.0071 = −0.1333 → n6 +, n7 −
f13 · ṅ = (0.0000, −0.2500) · v̇n = −0.1250 · ℓ̇ = 0.1630 · ḣ = ¼(uL2+uR2)v̇n − ε(uR−uL)ℓ̇ = −0.10738 + 0.06504 = −0.0423 → n6 +, n14 −
f14 · ṅ = (0.0000, −0.2500) · v̇n = −0.1250 · ℓ̇ = −0.0784 · ḣ = ¼(uL2+uR2)v̇n − ε(uR−uL)ℓ̇ = −0.1070 − 0.0203 = −0.1273 → n7 +, n8 −
f16 · ṅ = (0.0000, 0.2500) · v̇n = 0.1250 · ℓ̇ = −0.1879 · ḣ = ¼(uL2+uR2)v̇n − ε(uR−uL)ℓ̇ = 0.0753 − 0.0116 = 0.0637 → n8 +, n16 −
f33 · ṅ = (0.0000, −0.2500) · v̇n = −0.1250 · ℓ̇ = 0.0081 · ḣ = ¼(uL2+uR2)v̇n − ε(uR−uL)ℓ̇ = −0.07025 − 0.00029 = −0.0705 → n14 +, n15 −
f36 · ṅ = (0.0000, −0.2500) · v̇n = −0.1250 · ℓ̇ = −0.1808 · ḣ = ¼(uL2+uR2)v̇n − ε(uR−uL)ℓ̇ = −0.0716 − 0.0035 = −0.0751 → n15 +, n16 −
f60 · ṅ = (0.0000, 0.5000) · L̇ += ½uL2e·ṅ = 0.2638(壁面:h≡0,没有通量,只执行几何和目标那一句) (wall: h ≡ 0, no flux, only the geometry and the objective’s statement)
f61 · ṅ = (0.0000, 0.5000) · L̇ += ½uL2e·ṅ = 0.2384(壁面:h≡0,没有通量,只执行几何和目标那一句) (wall: h ≡ 0, no flux, only the geometry and the objective’s statement)
f62 · ṅ = (0.0000, −0.5000) · L̇ += ½uL2e·ṅ = −0.2384(壁面:h≡0,没有通量,只执行几何和目标那一句) (wall: h ≡ 0, no flux, only the geometry and the objective’s statement)
f63 · ṅ = (0.0000, −0.5000) · L̇ += ½uL2e·ṅ = −0.1442(壁面:h≡0,没有通量,只执行几何和目标那一句) (wall: h ≡ 0, no flux, only the geometry and the objective’s statement)
种子 Ẋ = ek 只动节点 7 的 x,状态冻结(u̇=0):这就是第 12 节的 gradient_d。它走遍全部 80 个面(48 个内部对偶面与 32 个边界半面),但只有 10 个面(6 个内部对偶面、4 个壁面半面)的法向随这个节点变化(ṅ ≠ 0)(节点 7 是 f15 的端点,它们的法向却不变:两侧单元的贡献正好抵消);播放器只停在这些面上。The seed Ẋ = ek moves only the x of node 7, with the state frozen (u̇ = 0): this is gradient_d of Section 12. It visits all 80 faces (48 interior dual faces and 32 boundary half-faces), but only 10 of them (6 interior dual faces and 4 wall half-faces) have a normal that moves with this node (ṅ ≠ 0) (node 7 is an end of f15, whose normals nevertheless do not change: the contributions of the two neighbouring cells cancel); the player stops only at those.
走完全部面:Ṙ = (∂R/∂X)ek 只在节点 6、7、8、14、15、16 上非零,目标的显式部分 L̇ = ∂L/∂Xk = 0.1196。这个 Ṙ 的相反数 −Ṙ 就是第 10 节切线方程 Aφk = −(∂R/∂X)ek 的右端项。All faces done: Ṙ = (∂R/∂X)ek is non-zero only in nodes 6, 7, 8, 14, 15, 16, and the explicit part of the objective is L̇ = ∂L/∂Xk = 0.1196. The negative of this Ṙ, −Ṙ, is the right-hand side of the tangent equation Aφk = −(∂R/∂X)ek of Section 10.
f12 · ṅ = (0.0000, 0.2500) · v̇n = 0.1250 · ℓ̇ = −0.1163 · ḣ = ¼(uL2+uR2)v̇n − ε(uR−uL)ℓ̇ = 0.1404 − 0.0071 = 0.1333 → n6 +, n7 −
f13 · ṅ = (0.0000, −0.2500) · v̇n = −0.1250 · ℓ̇ = 0.1630 · ḣ = ¼(uL2+uR2)v̇n − ε(uR−uL)ℓ̇ = −0.10738 + 0.06504 = −0.0423 → n6 +, n14 −
f14 · ṅ = (0.0000, 0.2500) · v̇n = 0.1250 · ℓ̇ = 0.0784 · ḣ = ¼(uL2+uR2)v̇n − ε(uR−uL)ℓ̇ = 0.1070 + 0.0203 = 0.1273 → n7 +, n8 −
f16 · ṅ = (0.0000, 0.2500) · v̇n = 0.1250 · ℓ̇ = −0.1879 · ḣ = ¼(uL2+uR2)v̇n − ε(uR−uL)ℓ̇ = 0.0753 − 0.0116 = 0.0637 → n8 +, n16 −
f33 · ṅ = (0.0000, 0.0833) · v̇n = 0.0417 · ℓ̇ = −0.0027 · ḣ = ¼(uL2+uR2)v̇n − ε(uR−uL)ℓ̇ = 0.0234 + 0.0001 = 0.0235 → n14 +, n15 −
f35 · ṅ = (0.0000, −0.3333) · v̇n = −0.1667 · ℓ̇ = 0.3311 · ḣ = ¼(uL2+uR2)v̇n − ε(uR−uL)ℓ̇ = −0.0879 + 0.0057 = −0.0822 → n14 +, n23 −
f36 · ṅ = (0.0000, 0.0833) · v̇n = 0.0417 · ℓ̇ = 0.0603 · ḣ = ¼(uL2+uR2)v̇n − ε(uR−uL)ℓ̇ = 0.02387 + 0.00116 = 0.0250 → n15 +, n16 −
f40 · ṅ = (0.0000, 0.3333) · v̇n = 0.1667 · ℓ̇ = −0.2808 · ḣ = ¼(uL2+uR2)v̇n − ε(uR−uL)ℓ̇ = 0.0913 − 0.0053 = 0.0860 → n16 +, n24 −
f48 · ṅ = (0.0000, −0.3333) · v̇n = −0.1667 · ℓ̇ = −0.2709 · ḣ = ¼(uL2+uR2)v̇n − ε(uR−uL)ℓ̇ = −0.08763 + 0.00405 = −0.0836 → n23 +, n24 −
种子 Ẋ = ek 只动节点 15 的 x,状态冻结(u̇=0):这就是第 12 节的 gradient_d。它走遍全部 80 个面(48 个内部对偶面与 32 个边界半面),但只有 9 个面(都是内部对偶面)的法向随这个节点变化(ṅ ≠ 0)(节点 15 是 f15、f37、f38 的端点,它们的法向却不变:两侧单元的贡献正好抵消);播放器只停在这些面上。The seed Ẋ = ek moves only the x of node 15, with the state frozen (u̇ = 0): this is gradient_d of Section 12. It visits all 80 faces (48 interior dual faces and 32 boundary half-faces), but only 9 of them (all interior dual faces) have a normal that moves with this node (ṅ ≠ 0) (node 15 is an end of f15, f37, f38, whose normals nevertheless do not change: the contributions of the two neighbouring cells cancel); the player stops only at those.
走完全部面:Ṙ = (∂R/∂X)ek 只在节点 6、7、8、14、15、16、23、24 上非零,L 不直接依赖这个节点(它不在壁面上)。这个 Ṙ 的相反数 −Ṙ 就是第 10 节切线方程 Aφk = −(∂R/∂X)ek 的右端项。All faces done: Ṙ = (∂R/∂X)ek is non-zero only in nodes 6, 7, 8, 14, 15, 16, 23, 24; L does not depend on this node directly (it is not on the wall). The negative of this Ṙ, −Ṙ, is the right-hand side of the tangent equation Aφk = −(∂R/∂X)ek of Section 10.
f24 · ṅ = (0.0000, 0.3333) · v̇n = 0.1667 · ℓ̇ = −0.2521 · ḣ = ¼(uL2+uR2)v̇n − ε(uR−uL)ℓ̇ = 0.0969 + 0.0007 = 0.0976 → n11 +, n12 −
f25 · ṅ = (0.0000, −0.3333) · v̇n = −0.1667 · ℓ̇ = −0.3262 · ḣ = ¼(uL2+uR2)v̇n − ε(uR−uL)ℓ̇ = −0.0926 − 0.0117 = −0.1043 → n11 +, n19 −
f29 · ṅ = (0.0000, 0.3333) · v̇n = 0.1667 · ℓ̇ = −0.0773 · ḣ = ¼(uL2+uR2)v̇n − ε(uR−uL)ℓ̇ = 0.0912 − 0.0043 = 0.0869 → n12 +, n21 −
f44 · ṅ = (0.0000, 0.1667) · v̇n = 0.0833 · ℓ̇ = −0.1385 · ḣ = ¼(uL2+uR2)v̇n − ε(uR−uL)ℓ̇ = 0.0444 + 0.0002 = 0.0446 → n19 +, n20 −
f45 · ṅ = (0.0000, 0.1667) · v̇n = 0.0833 · ℓ̇ = −0.1664 · ḣ = ¼(uL2+uR2)v̇n − ε(uR−uL)ℓ̇ = 0.04355 − 0.00301 = 0.0405 → n20 +, n21 −
f70 · ṅ = (0.0000, −0.5000) · v̇n = −0.2500 · ℓ̇ = −0.4619 · ḣ = ¼(uL2+uR2)v̇n − ε(uR−uL)ℓ̇ = −0.1331 + 0.0000 = −0.1331 → n19 +(出流:uR=uL,耗散那一项为零) (outflow: uR = uL, the dissipation term is zero)
f71 · ṅ = (0.0000, −0.5000) · v̇n = −0.2500 · ℓ̇ = −0.4619 · ḣ = ¼(uL2+uR2)v̇n − ε(uR−uL)ℓ̇ = −0.1335 + 0.0000 = −0.1335 → n20 +(出流:uR=uL,耗散那一项为零) (outflow: uR = uL, the dissipation term is zero)
f72 · ṅ = (0.0000, 0.5000) · v̇n = 0.2500 · ℓ̇ = 0.1913 · ḣ = ¼(uL2+uR2)v̇n − ε(uR−uL)ℓ̇ = 0.1293 + 0.0051 = 0.1344 → n20 +(入流:uR=u∞ 固定,几何两项照样有) (inflow: uR = u∞ is fixed, both geometric terms remain)
f73 · ṅ = (0.0000, 0.5000) · v̇n = 0.2500 · ℓ̇ = 0.1913 · ḣ = ¼(uL2+uR2)v̇n − ε(uR−uL)ℓ̇ = 0.1264 + 0.0017 = 0.1281 → n21 +(入流:uR=u∞ 固定,几何两项照样有) (inflow: uR = u∞ is fixed, both geometric terms remain)
种子 Ẋ = ek 只动节点 20 的 x,状态冻结(u̇=0):这就是第 12 节的 gradient_d。它走遍全部 80 个面(48 个内部对偶面与 32 个边界半面),但只有 9 个面(5 个内部对偶面、4 个远场半面)的法向随这个节点变化(ṅ ≠ 0)(节点 20 是 f26、f28 的端点,它们的法向却不变:两侧单元的贡献正好抵消);播放器只停在这些面上。The seed Ẋ = ek moves only the x of node 20, with the state frozen (u̇ = 0): this is gradient_d of Section 12. It visits all 80 faces (48 interior dual faces and 32 boundary half-faces), but only 9 of them (5 interior dual faces and 4 far-field half-faces) have a normal that moves with this node (ṅ ≠ 0) (node 20 is an end of f26, f28, whose normals nevertheless do not change: the contributions of the two neighbouring cells cancel); the player stops only at those.
走完全部面:Ṙ = (∂R/∂X)ek 只在节点 11、12、19、20、21 上非零,L 不直接依赖这个节点(它不在壁面上)。这个 Ṙ 的相反数 −Ṙ 就是第 10 节切线方程 Aφk = −(∂R/∂X)ek 的右端项。All faces done: Ṙ = (∂R/∂X)ek is non-zero only in nodes 11, 12, 19, 20, 21; L does not depend on this node directly (it is not on the wall). The negative of this Ṙ, −Ṙ, is the right-hand side of the tangent equation Aφk = −(∂R/∂X)ek of Section 10.
图 12.4 · gradient_d 逐面播放:一个坐标种子怎样经过法向进入残差。种子只动一个节点的 x(紫色箭头,可选三个节点),状态冻结。每一步是一个法向在变的面:它的 ṅ(绿色箭头,按本图最大值缩放)、ḣ,以及按 h 的符号累加到两侧节点的 Ṙ(绿正蓝负);壁面半面没有通量,只改变 L。右栏逐面记下送出的量。最后一帧是 (∂R/∂X)ek,它的相反数是第 10 节切线方程的右端项。Figure 12.4 · gradient_d played face by face: how one coordinate seed enters the residual through the normals. The seed moves the x of one node (violet arrow; three nodes to choose from), with the state frozen. Each step is one face whose normal moves: its ṅ (green arrow, scaled to the largest in the figure), its ḣ, and the Ṙ accumulated with the signs of h into the nodes on either side (green positive, blue negative); a wall half-face runs no flux and changes only L. The column on the right keeps what each face sent out. The last frame is (∂R/∂X)ek; its negative is the right-hand side of the tangent equation of Section 10.

图 12.5 反过来播放 gradient(u,ψ)(u,\psi):种子 Rˉ=ψ\bar R=\psi、Lˉ=1\bar L=1,每个面执行第 9 节的伴随语句,把 𝒏ˉ\bar{\boldsymbol n} 转回它的节点,节点上的 Xˉ\bar X 用 += 累加。走完全部 80 个面(48 个内部对偶面与 32 个边界半面),Xˉ\bar X 就是 dL/dXdL/dX(最后一帧与图 12.3 (c) 相同),入流槽位累加出 uˉ∞=ψTf\bar u_\infty=\psi^Tf:一次反向扫描得到全部 49 个分量。

Figure 12.5 plays gradient(u,ψ)(u,\psi) backwards: with the seeds Rˉ=ψ\bar R=\psi and Lˉ=1\bar L=1 each face runs the adjoint statements of Section 9, turns its 𝒏ˉ\bar{\boldsymbol n} back to its nodes, and the nodes accumulate Xˉ\bar X with +=. After all 80 faces (48 interior dual faces and 32 boundary half-faces), Xˉ\bar X is dL/dXdL/dX (the last frame is Figure 12.3 (c)) and the inflow slots have accumulated uˉ∞=ψTf\bar u_\infty=\psi^Tf: one reverse sweep gives all 49 components.

1.61−1.610.00ψ1/802/803/804/805/806/807/808/809/8010/8011/8012/8013/8014/8015/8016/8017/8018/8019/8020/8021/8022/8023/8024/8025/8026/8027/8028/8029/8030/8031/8032/8033/8034/8035/8036/8037/8038/8039/8040/8041/8042/8043/8044/8045/8046/8047/8048/8049/8050/8051/8052/8053/8054/8055/8056/8057/8058/8059/8060/8061/8062/8063/8064/8065/8066/8067/8068/8069/8070/8071/8072/8073/8074/8075/8076/8077/8078/8079/8080/80X¯​ = dL/dX伴随解 ψ(色)上逐面累加 X¯​(紫色)X¯​ (violet) accumulated face by face over the adjoint solution ψ (colour)红:当前面与 n¯red: this face, its n¯
82 / 82
种子 R̄ = ψ、L̄ = 1,所有节点的 X̄ 从 0 开始。每一步是一个面的伴随语句:h̄ 从两侧的 ψ 收集,n̄ 转回 90° 后加到它的节点上(下面依次列出面号、h̄、n̄ 与收到的节点)。The seeds are R̄ = ψ and L̄ = 1, and every node's X̄ starts at 0. Each step runs the adjoint statements of one face: h̄ is gathered from ψ on its two sides, and n̄ is turned back by 90° and added to its nodes (listed below: the face, h̄, n̄ and the nodes that receive it).
f1 · h̄ = ψ1 − ψ2 = −0.0062 · n̄ = (−0.0020, −0.0009) → 1, 2, 9, 10
f2 · h̄ = ψ1 − ψ8 = 0.1926 · n̄ = (0.1136, 0.0953) → 1, 8, 16, 9
f3 · h̄ = ψ1 − ψ9 = 0.3710 · n̄ = (0.0965, 0.0698) → 10, 2, 8, 16
f4 · h̄ = ψ2 − ψ3 = 0.2011 · n̄ = (0.1565, 0.0345) → 2, 3, 10, 11
f5 · h̄ = ψ2 − ψ10 = 0.3782 · n̄ = (0.1166, 0.0399) → 1, 9, 11, 3
f6 · h̄ = ψ3 − ψ4 = 0.5105 · n̄ = (0.5158, 0.2588) → 3, 4, 11, 12
f7 · h̄ = ψ3 − ψ11 = 0.1932 · n̄ = (0.1193, 0.0740) → 2, 10, 12, 4
f8 · h̄ = ψ4 − ψ5 = 0.9235 · n̄ = (0.9767, 0.5221) → 4, 5, 12, 13
f9 · h̄ = ψ4 − ψ12 = −0.1039 · n̄ = (−0.0611, −0.0588) → 3, 11, 13, 5
f10 · h̄ = ψ5 − ψ6 = 0.4203 · n̄ = (0.4687, 0.2583) → 5, 6, 13, 14
f11 · h̄ = ψ5 − ψ13 = −0.5397 · n̄ = (−0.2559, −0.2321) → 4, 12, 14, 6
f12 · h̄ = ψ6 − ψ7 = −1.0889 · n̄ = (−1.2814, −0.5807) → 6, 7, 14, 15
f13 · h̄ = ψ6 − ψ14 = −0.8379 · n̄ = (−0.4664, −0.1419) → 5, 13, 15, 7
f14 · h̄ = ψ7 − ψ8 = −0.7676 · n̄ = (−0.8460, −0.3909) → 7, 8, 15, 16
f15 · h̄ = ψ7 − ψ15 = −0.2999 · n̄ = (−0.2442, −0.0333) → 6, 14, 16, 8
f16 · h̄ = ψ8 − ψ16 = 0.2007 · n̄ = (0.1292, 0.0511) → 7, 15, 9, 1
f17 · h̄ = ψ9 − ψ10 = 0.0010 · n̄ = (0.0004, 0.0002) → 9, 10, 1, 2, 18
f18 · h̄ = ψ9 − ψ16 = 0.0224 · n̄ = (0.0107, 0.0080) → 9, 16, 8, 1, 17
f19 · h̄ = ψ9 − ψ17 = 0.0562 · n̄ = (0.0226, 0.0132) → 18, 16
f20 · h̄ = ψ9 − ψ18 = 0.0541 · n̄ = (0.0226, 0.0106) → 17, 10
f21 · h̄ = ψ10 − ψ11 = 0.0161 · n̄ = (0.0099, 0.0044) → 10, 11, 2, 3, 19
f22 · h̄ = ψ10 − ψ18 = 0.0532 · n̄ = (0.0237, 0.0118) → 9, 19
f23 · h̄ = ψ10 − ψ19 = 0.0466 · n̄ = (0.0233, 0.0082) → 18, 11
f24 · h̄ = ψ11 − ψ12 = 0.2134 · n̄ = (0.1244, 0.0625) → 11, 12, 3, 4, 20
f25 · h̄ = ψ11 − ψ19 = 0.0306 · n̄ = (0.0172, 0.0096) → 10, 20
f26 · h̄ = ψ11 − ψ20 = 0.1121 · n̄ = (0.0591, 0.0332) → 19, 12
f27 · h̄ = ψ12 − ψ13 = 0.4877 · n̄ = (0.2752, 0.1242) → 12, 13, 4, 5, 21
f28 · h̄ = ψ12 − ψ20 = −0.1013 · n̄ = (−0.0547, −0.0315) → 11, 21
f29 · h̄ = ψ12 − ψ21 = 0.1598 · n̄ = (0.0788, 0.0417) → 20, 13
f30 · h̄ = ψ13 − ψ14 = 0.1221 · n̄ = (0.0667, 0.0325) → 13, 14, 5, 6, 22
f31 · h̄ = ψ13 − ψ21 = −0.3280 · n̄ = (−0.1648, −0.0894) → 12, 22
f32 · h̄ = ψ13 − ψ22 = −0.1476 · n̄ = (−0.0751, −0.0351) → 21, 14
f33 · h̄ = ψ14 − ψ15 = −0.5509 · n̄ = (−0.2898, −0.1555) → 14, 15, 6, 7, 23
f34 · h̄ = ψ14 − ψ22 = −0.2697 · n̄ = (−0.1350, −0.0673) → 13, 23
f35 · h̄ = ψ14 − ψ23 = −0.5395 · n̄ = (−0.2855, −0.1330) → 22, 15
f36 · h̄ = ψ15 − ψ16 = −0.2670 · n̄ = (−0.1565, −0.0802) → 15, 16, 7, 8, 24
f37 · h̄ = ψ15 − ψ23 = 0.0114 · n̄ = (0.0061, 0.0025) → 14, 24
f38 · h̄ = ψ15 − ψ24 = −0.2001 · n̄ = (−0.1180, −0.0511) → 23, 16
f39 · h̄ = ψ16 − ψ17 = 0.0338 · n̄ = (0.0197, 0.0088) → 24, 9
f40 · h̄ = ψ16 − ψ24 = 0.0669 · n̄ = (0.0373, 0.0173) → 15, 17
f41 · h̄ = ψ17 − ψ18 = −0.0021 · n̄ = (−0.0009, −0.0005) → 17, 18, 9
f42 · h̄ = ψ17 − ψ24 = 0.0331 · n̄ = (0.0164, 0.0099) → 17, 24, 16
f43 · h̄ = ψ18 − ψ19 = −0.0065 · n̄ = (−0.0036, −0.0018) → 18, 19, 10
f44 · h̄ = ψ19 − ψ20 = 0.0815 · n̄ = (0.0435, 0.0218) → 19, 20, 11
f45 · h̄ = ψ20 − ψ21 = 0.2611 · n̄ = (0.1367, 0.0635) → 20, 21, 12
f46 · h̄ = ψ21 − ψ22 = 0.1804 · n̄ = (0.0922, 0.0455) → 21, 22, 13
f47 · h̄ = ψ22 − ψ23 = −0.2698 · n̄ = (−0.1360, −0.0688) → 22, 23, 14
f48 · h̄ = ψ23 − ψ24 = −0.2114 · n̄ = (−0.1093, −0.0530) → 23, 24, 15
f49 · n̄ = (0.2763, 0.1381) → 2, 1(壁面:目标那一句) (wall: the objective’s statement)
f50 · n̄ = (0.2763, 0.1381) → 1, 8(壁面:目标那一句) (wall: the objective’s statement)
f51 · n̄ = (0.2875, 0.1437) → 2, 1(壁面:目标那一句) (wall: the objective’s statement)
f52 · n̄ = (0.2875, 0.1437) → 3, 2(壁面:目标那一句) (wall: the objective’s statement)
f53 · n̄ = (0.6058, 0.3029) → 3, 2(壁面:目标那一句) (wall: the objective’s statement)
f54 · n̄ = (0.6058, 0.3029) → 4, 3(壁面:目标那一句) (wall: the objective’s statement)
f55 · n̄ = (0.8885, 0.4443) → 4, 3(壁面:目标那一句) (wall: the objective’s statement)
f56 · n̄ = (0.8885, 0.4443) → 5, 4(壁面:目标那一句) (wall: the objective’s statement)
f57 · n̄ = (0.9689, 0.4845) → 5, 4(壁面:目标那一句) (wall: the objective’s statement)
f58 · n̄ = (0.9689, 0.4845) → 6, 5(壁面:目标那一句) (wall: the objective’s statement)
f59 · n̄ = (1.0554, 0.5277) → 6, 5(壁面:目标那一句) (wall: the objective’s statement)
f60 · n̄ = (1.0554, 0.5277) → 7, 6(壁面:目标那一句) (wall: the objective’s statement)
f61 · n̄ = (0.9537, 0.4768) → 7, 6(壁面:目标那一句) (wall: the objective’s statement)
f62 · n̄ = (0.9537, 0.4768) → 8, 7(壁面:目标那一句) (wall: the objective’s statement)
f63 · n̄ = (0.5769, 0.2885) → 8, 7(壁面:目标那一句) (wall: the objective’s statement)
f64 · n̄ = (0.5769, 0.2885) → 1, 8(壁面:目标那一句) (wall: the objective’s statement)
f65 · h̄ = ψ17 = 0.0112 · n̄ = (0.0053, 0.0027) → 17, 18(出流) (outflow)
f66 · h̄ = ψ17 = 0.0112 · n̄ = (0.0053, 0.0027) → 24, 17(出流) (outflow)
f67 · h̄ = ψ18 = 0.0133 · n̄ = (0.0060, 0.0030) → 17, 18(出流) (outflow)
f68 · h̄ = ψ18 = 0.0133 · n̄ = (0.0060, 0.0030) → 18, 19(出流) (outflow)
f69 · h̄ = ψ19 = 0.0198 · n̄ = (0.0105, 0.0053) → 18, 19(出流) (outflow)
f70 · h̄ = ψ19 = 0.0198 · n̄ = (0.0105, 0.0053) → 19, 20(出流) (outflow)
f71 · h̄ = ψ20 = −0.0617 · n̄ = (−0.0330, −0.0165) → 19, 20(出流) (outflow)
f72 · h̄ = ψ20 = −0.0617 · n̄ = (−0.0304, −0.0166) → 20, 21(入流:ū∞ += 0.0523) (inflow: ū∞ += 0.0523)
f73 · h̄ = ψ21 = −0.3228 · n̄ = (−0.1605, −0.0827) → 20, 21(入流:ū∞ += 0.2737) (inflow: ū∞ += 0.2737)
f74 · h̄ = ψ21 = −0.3228 · n̄ = (−0.1605, −0.0805) → 21, 22(入流:ū∞ += 0.3186) (inflow: ū∞ += 0.3186)
f75 · h̄ = ψ22 = −0.5031 · n̄ = (−0.2508, −0.1256) → 21, 22(入流:ū∞ += 0.4967) (inflow: ū∞ += 0.4967)
f76 · h̄ = ψ22 = −0.5031 · n̄ = (−0.2522, −0.1242) → 22, 23(入流:ū∞ += 0.4472) (inflow: ū∞ += 0.4472)
f77 · h̄ = ψ23 = −0.2333 · n̄ = (−0.1174, −0.0565) → 22, 23(入流:ū∞ += 0.2074) (inflow: ū∞ += 0.2074)
f78 · h̄ = ψ23 = −0.2333 · n̄ = (−0.1197, −0.0565) → 23, 24(入流:ū∞ += 0.1424) (inflow: ū∞ += 0.1424)
f79 · h̄ = ψ24 = −0.0219 · n̄ = (−0.0116, −0.0051) → 23, 24(入流:ū∞ += 0.0134) (inflow: ū∞ += 0.0134)
f80 · h̄ = ψ24 = −0.0219 · n̄ = (−0.0117, −0.0059) → 24, 17(出流) (outflow)
全部 80 个面(48 个内部对偶面与 32 个边界半面)走完:累加出的 X̄ 就是 dL/dX(与本页 gradient(u, ψ) 之差 2.2×10⁻¹⁶),入流槽位累加出的 ū∞ = 1.95162064261909 就是 dL/du∞ = ψTf。一次反向扫描给出全部 49 个分量。All 80 faces (48 interior dual faces and 32 boundary half-faces) done: the accumulated X̄ is dL/dX (it differs from this page’s gradient(u, ψ) by 2.2×10⁻¹⁶), and the bar accumulated in the inflow slots, ū∞ = 1.95162064261909, is dL/du∞ = ψTf. One reverse sweep gives all 49 components.
图 12.5 · gradient(u, ψ) 逐面播放:伴随种子怎样累加成形状梯度。底色是伴随解 ψ(红正蓝负)。每一步处理一个面(红色),它的 n̄(红色箭头)转回节点,收到非零量的节点画红框;紫色箭头是各节点到这一步为止累加的 X̄(所有帧同一比例,最后一帧最长的箭头 50 像素;中间的部分和可以更长:有的面先加上、后面的面再抵消)。一个节点被几个面读到,就收到几份,全部用 += 累加;格点页的内部对偶面还经单元中心送到两个单元的顶点。最后一帧与图 12.3 (c) 相同,就是 dL/dX:这一页的形状灵敏度。Figure 12.5 · gradient(u, ψ) played face by face: how the adjoint seeds accumulate into the shape gradient. The colours are the adjoint solution ψ (red positive, blue negative). Each step handles one face (red): its n̄ (red arrow) is turned back to its nodes, and the nodes that receive something non-zero are boxed in red; the violet arrows are each node's X̄ accumulated so far (one scale for all frames, the longest final arrow 50 px; a partial sum can be longer, when one face adds what a later face cancels). A node read by several faces receives one share from each, all accumulated with +=; on the node page an interior dual face also reaches the vertices of its two cells through the cell centres. The last frame equals Figure 12.3 (c): it is dL/dX, the shape sensitivity of this page.

梯度预言得对吗:Taylor 检验

Does the gradient predict correctly? The Taylor test

形状梯度的用处是预言:沿方向 𝒅\boldsymbol d 移动网格 η\eta,LL 应当变化 η dL/dX⋅𝒅\eta\,dL/dX\cdot\boldsymbol d,误差是 O(η2)O(\eta^2)。取只动 8 个壁面节点、使 LL 下降最快的方向(壁面节点上的 −dL/dX-dL/dX,归一化),在移动后的网格上重解 primal(图 12.6):

What a shape gradient is for is prediction: moving the mesh by η\eta along a direction 𝒅\boldsymbol d should change LL by η dL/dX⋅𝒅\eta\,dL/dX\cdot\boldsymbol d, up to O(η2)O(\eta^2). Take the direction in which LL falls fastest when only the 8 wall nodes move (−dL/dX-dL/dX on the wall nodes, normalised) and re-solve the primal on the moved mesh (Figure 12.6):

r1(η)=∣L(X+η𝒅)−L(X)−η dLdX⋅𝒅∣=O(η2),dLdX⋅𝒅=−1.9152.r_1(\eta)=\Big|L(X+\eta\boldsymbol d)-L(X)-\eta\,\frac{dL}{dX}\cdot\boldsymbol d\Big|=O(\eta^2),\qquad \frac{dL}{dX}\cdot\boldsymbol d={-1.9152}.

余项的阶是梯度对错的独立判据:梯度对,r1r_1 按 η2\eta^2 下降(本页最小两步之间实测 2.000);梯度错,r1r_1 只按 η\eta 下降。比如只用显式项 ∂L/∂X\partial L/\partial X(它沿 𝒅\boldsymbol d 只给出 −0.5830),丢掉 ψT∂R/∂X\psi^T\partial R/\partial X,实测阶是 1.000。这个检验不需要在截断与舍入之间挑步长(第 13 节),也不需要每个分量各解一次。

The order of the remainder is an independent test of the gradient: with the right gradient r1r_1 falls like η2\eta^2 (measured between the two smallest steps on this page: 2.000); with a wrong one, only like η\eta. Keeping, for instance, only the explicit term ∂L/∂X\partial L/\partial X (which gives just −0.5830 along 𝒅\boldsymbol d) and dropping ψT∂R/∂X\psi^T\partial R/\partial X, the measured order is 1.000. The test needs no step chosen between truncation and round-off (Section 13), and no solve per component.

1.61−1.610.00(a) ψ(色)与 dL/dX(紫色箭头)(a) ψ (colour) and dL/dX (violet arrows)10⁻¹⁰10⁻⁸10⁻⁶10⁻⁴10⁻²10⁰10⁻⁵10⁻⁴10⁻³10⁻²10⁻¹步长 ηstep η|L(X+ηd) − L|只用 ∂L/∂Xwith ∂L/∂X only用 dL/dXwith dL/dX(b) Taylor 检验:余项随 η 的阶(b) Taylor test: the order of the remainder in η斜率 2slope 2
图 12.6 · 形状灵敏度,以及它预言得对不对。(a) 伴随解 ψ(红正蓝负)与形状梯度 dL/dX(紫色箭头,同一比例):沿箭头移动节点,L 增长最快。琥珀色虚线是只动 8 个壁面节点、使 L 下降最快的方向 d(壁面节点上的 −dL/dX,归一化)。(b) 沿 d 把网格移动 η(本页代码在移动后的网格上重解 primal),比较 L(X+ηd) 与线性预测:减去 η dL/dX·d 后余项按 η2 下降(最小两步之间实测阶 2.000),不减时按 η 下降(1.000);若只用显式项 ∂L/∂X、丢掉 ψT∂R/∂X,余项也只按 η 下降(1.000)。沿 d 的方向导数 dL/dX·d = −1.9152,其中显式项只占 −0.5830。余项的阶是梯度正确与否的独立判据:它不需要差分步长的折中。Figure 12.6 · The shape sensitivity, and whether its prediction holds. (a) The adjoint solution ψ (red positive, blue negative) and the shape gradient dL/dX (violet arrows, one scale): moving the nodes along the arrows raises L fastest. The dashed amber arrows are the direction d that lowers L fastest when only the 8 wall nodes move (−dL/dX on the wall nodes, normalised). (b) Move the mesh by η along d (this page’s code re-solves the primal on the moved mesh) and compare L(X + ηd) with the linear prediction: after subtracting η dL/dX·d the remainder falls like η2 (measured order between the two smallest steps 2.000), without it like η (1.000); with the explicit term ∂L/∂X alone, without ψT∂R/∂X, the remainder falls only like η (1.000). The directional derivative along d is dL/dX·d = −1.9152, of which the explicit term is only −0.5830. The order of the remainder is an independent test of the gradient: it needs no compromise on a difference step.

两条路线的代价

The cost of the two routes

图 12.7 把两条路线放在同一条时间轴上,以面循环次数计。前向路线对 DD 的 49 个分量各解一次切线方程,共 5336 次面循环;伴随路线解一次 ATψ=−gA^T\psi=-g(108 次),再加一次反向扫描,就得到同样的 49 个数。伴随路线的代价为什么不随设计变量的个数增长、什么时候前向路线反而更合适,见第 11 节“为什么一次伴随求解能代替 nan_a 次前向求解”一段;有成千上万个形状参数时,增长的只有前向路线。

Figure 12.7 puts the two routes on one time axis, counted in face loops. The tangent route solves one tangent equation for each of the 49 components of DD, 5336 face loops in all; the adjoint route solves ATψ=−gA^T\psi=-g once (108) and adds one reverse sweep, and has the same 49 numbers. Why the cost of the adjoint route does not grow with the number of design variables, and when the tangent route is nevertheless the better one, is explained in the block “Why one adjoint solve replaces nan_a tangent solves” in Section 11; with thousands of shape parameters only the tangent route grows.

核对:图 12.4 每个种子的逐面累加与 gradient_d 相同(最大差不超过 10−1410^{-14} 量级);图 12.5 累加出的 Xˉ\bar X 与 gradient(u,ψ)(u,\psi) 之差 2.2×10−16;Taylor 检验的实测阶 1.000、2.000、1.000;两条路线的 49 个分量逐个相差至多 1.9×10−12(都停在判据 10−1310^{-13} 处)(本页代码在构建时计算)。

Check: in Figure 12.4 every seed’s face-by-face accumulation equals gradient_d (to the order of 10−1410^{-14} at most); the Xˉ\bar X accumulated in Figure 12.5 differs from gradient(u,ψ)(u,\psi) by 2.2×10−16; the measured orders of the Taylor test are 1.000, 2.000 and 1.000; the 49 components of the two routes differ by at most 1.9×10−12 (both stopped at the criterion 10−1310^{-13}) (computed by this page’s code at build time).

前向路线tangent route49 次切线求解49 tangent solves伴随路线adjoint route1 次伴随求解1 adjoint solve+ 1 次反向扫描+ 1 reverse sweep0 · 0/490178 · 1/49109356 · 3/49109534 · 4/49109712 · 6/49109890 · 7/491091068 · 9/491091246 · 10/491091424 · 12/491091602 · 13/491091780 · 15/491091958 · 16/491092136 · 18/491092314 · 20/491092492 · 22/491092670 · 23/491092848 · 25/491093026 · 26/491093204 · 28/491093382 · 30/491093560 · 31/491093738 · 33/491093916 · 35/491094094 · 36/491094272 · 38/491094450 · 40/491094628 · 42/491094806 · 43/491094984 · 45/491095162 · 47/491095336 · 49/49109面循环次数 · 已完成的求解face loops · solves done面循环次数face loopsdL/dD:u∞(紫)与 48 个坐标dL/dD: u∞ (violet) and the 48 coordinates同样的 49 个分量,一次得到the same 49 components, all at once
31 / 31
图 12.7 · 两条路线的代价:得到 dL/dD 的全部 49 个分量(u∞ 与 48 个坐标)。时间轴以面循环次数计(一次 residual_d 或 residual_b 就是一次面循环),两条轴同一比例,每一帧前进 178 次面循环。前向路线每个设计分量解一次切线方程,第 6 节的 Jacobi 迭代各需 86 到 133 轮才满足 10−13 判据(轴上的细线是每次求解的终点),共 5336 次;每解完一次,下面多一根柱(这个分量的 L̇)。伴随路线只解一次 ATψ=−g(108 轮),再做一次反向扫描(gradient 与 ψTf),49 根柱同时出现:大约是前向路线的 1/49。两组柱逐个相差至多 1.9×10⁻¹²(两边都停在判据 10−13 处;都跑到舍入误差平台时,第 12 节给出的差是 10−16 量级)。Figure 12.7 · The cost of the two routes to all 49 components of dL/dD (u∞ and the 48 coordinates). Time is counted in face loops (one residual_d or residual_b is one face loop), on the same scale for both routes; each frame advances 178 face loops. The tangent route solves one tangent equation per design component: the Jacobi iteration of Section 6 needs 86 to 133 sweeps to meet the 10−13 criterion (the thin lines on the axis end each solve), 5336 in all, and each finished solve adds one bar below (the L̇ of that component). The adjoint route solves ATψ = −g once (108 sweeps) and runs one reverse sweep (gradient and ψTf), and all 49 bars appear together: about 1/49 of the tangent route. The two sets of bars differ by at most 1.9×10⁻¹² (both stop at the criterion 10−13; run to the round-off floor, Section 12 finds differences of order 10−16).
新增(选读):光滑特征边界模型
Added (optional): smooth characteristic-boundary model

几何导数:法向、长度、梯度与边界位置

Geometry derivatives: normals, lengths, gradients and boundary locations

节点位置变化同时改变边法向、面长、积分点、单元中心及最小二乘权重。二阶还有重构偏移与目标边界值的路径。入流数据是固定的空间函数 u_in(x,y),因此移动入流面时不能冻结 u_in 的数值。所有这些依赖均在同一个计算图内解析求导,没有用差分近似形状梯度。

Moving nodes changes normals, lengths, quadrature locations, centres and least-squares weights. Second order adds reconstruction-offset and objective-trace paths. The inflow is a fixed spatial function u_in(x,y), so its sampled value changes when an inflow face moves. All paths are differentiated analytically in the same graph, without finite-difference shape derivatives.

n˙=(y˙b−y˙a,−x˙b+x˙a),ℓ˙=n⋅n˙/ℓ,u˙in=0.2πcos⁡[π(y−0.5x)](y˙−0.5x˙)+u˙∞.\dot n=(\dot y_b-\dot y_a,-\dot x_b+\dot x_a),\quad\dot\ell=n\cdot\dot n/\ell,\quad\dot u_{\rm in}=0.2\pi\cos[\pi(y-0.5x)](\dot y-0.5\dot x)+\dot u_\infty.

导数只在当前连接关系、有效网格和入/出流符号不改变的邻域内解释。跨过 β·n=0 时边界分支会改变,不能把当前线性化外推到另一分支。

Derivatives apply locally while connectivity, mesh validity and inflow/outflow signs remain unchanged. Crossing β·n=0 changes a branch and requires a fresh linearisation.

图 12.8Figure 12.8
第 13 节Section 13

验证:dL/dX 对有限差分

Verification: dL/dX against finite differences

上一节把 dL/dX 推了出来:伴随只解一次线性系统,就拿到全部分量。这一节只做一件事——验证它是对的。

The previous section derived dL/dX: the adjoint solves one linear system and gets every component. This section does one thing only — check that it is right.

判据是最朴素的那个,逐个坐标分量做中心差分:

The criterion is the plainest one there is, a central difference taken coordinate by coordinate:

dLdXk≈L(Xk+h)−L(Xk−h)2h.

关键在于 L(Xk±h) 是完整的目标函数:每次都要把网格改形、重新解一次非线性 primal、再算目标。所以这里验证的是全导数——状态对几何的响应也算在内——而不是冻结状态的偏导数。代价也正因如此:中心差分每个分量两次扰动,一共 2M 次非线性求解,而伴随只用了一次线性求解。

What matters is that L(Xk±h) is the complete objective: every evaluation deforms the mesh, solves the nonlinear primal again, and only then computes the objective. So what is verified here is the total derivative — the response of the state to the geometry included — and not a partial derivative at a frozen state. That is also what it costs: two perturbations per component, 2M nonlinear solves in all, against the single linear solve of the adjoint.

还有省一半的做法——前向差分,只往一侧扰动:

There is also a way to halve that — a forward difference, perturbing to one side only:

dLdXk≈L(Xk+h)−L(X)h.

基准值 L(X) 全局只算一次,于是总共约 M 次非线性求解而不是 2M 次。代价是精度低一个档次:截断误差是 O(h) 而不是 O(h2)。两者的误差随步长怎么走,图 13.1 底部可以逐个变量扫描出来。

The baseline L(X) is computed once for all, so the total is about M nonlinear solves rather than 2M. The price is one order of accuracy: the truncation error is O(h) instead of O(h2). The sweep at the foot of Figure 13.1 plots how the two errors run with the step size, one variable at a time.

图 13.1 里可以自己改差分步长 h,再按“用差分验证”重算。

In Figure 13.1 the step h can be set by hand and recomputed with "Verify by differences".

图 13.1Figure 13.1
交互:形状梯度、差分验证与步长扫描
Interactive: the shape gradient, its difference check, and a step-size sweep
L = ;箭头是移动各节点时 L 增加最快的方向(壁面节点上的箭头就是外形设计方向); the arrows show the direction in which L grows fastest as each node moves (on the wall nodes they are the shape design directions)
横轴是步长 h,纵轴是相对误差——差分结果与伴随结果之差,再除以该分量伴随梯度的绝对值,纵横都是对数。左半段由截断误差主导:h 每缩小十倍,前向降 10 倍(O(h))、中心降 100 倍(O(h2))。右半段由舍入误差主导,两者相同(都约 10 倍地涨),所以两条曲线并拢。两个圈是各自的最优步长:典型情况下前向落在 ϵ1/2 附近、中心落在 ϵ1/3 附近,中心能达到的最好精度通常高出两到三个数量级(中位数约 350 倍);具体数值随分量而变。之所以取相对误差,是因为 48 个分量的 dL/dX 相差三个多数量级(实测约 6.5e−4 到 1.0),只看绝对误差时,梯度小的分量会显得更准,而这与步长好坏无关。曲线是在浏览器里实跑出来的——换一个变量,最优步长往往跟着移动,这正是拿同一个 h 走遍所有分量靠不住的原因。
The horizontal axis is the step h and the vertical axis the relative error — the difference between the finite-difference and the adjoint result, divided by the magnitude of that component's own adjoint gradient — both logarithmic. The left half is dominated by truncation: for every tenfold reduction of h the forward scheme falls by 10 (O(h)) and the central scheme by 100 (O(h2)). The right half is dominated by round-off, identical for both (each rising about tenfold), so the two curves close together. The two rings mark the optimal step of each: typically the forward one lies near ϵ1/2 and the central one near ϵ1/3, and the best accuracy of the central scheme is usually two to three orders of magnitude better (a median of about 350 times); the exact figures vary from component to component. The error is relative because dL/dX varies by more than three orders of magnitude across the 48 components (measured: about 6.5e−4 to 1.0): on an absolute axis a component with a small gradient looks more accurate for reasons that have nothing to do with the step. The curves are swept live in the browser — change the variable and the optimum often moves with it, which is why one single h for every component cannot be trusted.

步长为什么要能调。中心差分的误差有两段:截断误差约 O(h2),随 h 变小而减小;舍入误差约 O(ϵ/h)(ϵ 为机器精度),随 h 变小反而增大。两者相抵,最佳步长大致在 h∼ϵ1/3,也就是 10−5 到 10−6 量级。把 h 调到 10−3 会看到截断误差主导,调到 10−10 会看到舍入噪声主导——两头都会让“验证”看起来像是失败了。这正是差分校验最容易骗人的地方,也是点积测试(第 9 节)比它更可靠的原因:点积测试是精确恒等式,根本不含步长。

Why the step has to be adjustable. The error of a central difference has two parts: truncation, about O(h2), which shrinks as h shrinks; and round-off, about O(ϵ/h) (with ϵ the machine precision), which grows as h shrinks. Between them the best step lies roughly at h∼ϵ1/3, of order 10−5 to 10−6. Set h to 10−3 and truncation dominates; set it to 10−10 and round-off noise does — at either end the "verification" looks like a failure. This is exactly where a difference check is most deceptive, and why the dot-product test (Section 9) is the more reliable of the two: it is an exact identity, with no step size in it at all.

那么哪一侧更糟?要看离最优点多远,因为两侧的斜率根本不同:h 每放大一个数量级,截断误差涨约 100 倍(二次);h 每缩小一个数量级,舍入误差只涨约 10 倍(线性)。实测扫描里截断一侧的斜率很干净,舍入一侧则起伏不定(舍入误差本身近乎随机);中心差分的最小值多数落在 h 约 10−4 到 10−6 之间,与 ϵ1/3 的量级一致。于是离最优点越远,偏大的那一侧通常越吃亏,但对个别分量两侧可能相差不到一个数量级。实用结论是——宁可把 h 取偏小,也不要取偏大。

So which side is worse? It depends how far from the optimum you are, because the two slopes are quite different: enlarging h by one order multiplies the truncation error by about 100 (quadratic), while shrinking h by one order multiplies the round-off error by only about 10 (linear). In the measured sweep the truncation slope is clean while the round-off side is ragged (round-off is close to random); the central minimum mostly lies between h≈10−4 and 10−6, consistent with the ϵ1/3 estimate. So the further from the optimum, the more the too-large side usually costs, though for a few components the two sides differ by less than an order of magnitude. The practical conclusion — better to take h a little too small than a little too large.

差分的步长两难,在第 14 节的复数步长那里不存在:那里没有相减,步长取 10−30,48 个分量与伴随梯度都相符到舍入误差。

The step dilemma of differences does not arise for the complex step of Section 14: nothing is subtracted there, the step is 10−30, and all 48 components agree with the adjoint gradient to round-off.

逐步推导:最优差分步长从哪里来

Step by step: where the best difference step comes from

上面说中心差分的最优步长大致是 h∼ϵ1/3h\sim\epsilon^{1/3}。它来自两个误差的平衡。第一步,截断:沿坐标 XkX_k 作 Taylor 展开(撇号表示对 XkX_k 的导数):

Above, the best central step is roughly h∼ϵ1/3h\sim\epsilon^{1/3}. It comes from balancing two errors. Step 1, truncation: expand along the coordinate XkX_k (primes are derivatives with respect to XkX_k):

L(X±h𝒆k)=L±hL′+h22L′′±h36L′′′+O(h4),L(X\pm h\boldsymbol e_k)=L\pm hL'+\tfrac{h^2}2L''\pm\tfrac{h^3}6L'''+O(h^4),
L(X+h𝒆k)−L(X−h𝒆k)2h=L′+h26L′′′+O(h4),L(X+h𝒆k)−L(X)h=L′+h2L′′+O(h2).\begin{gathered}\frac{L(X+h\boldsymbol e_k)-L(X-h\boldsymbol e_k)}{2h}=L'+\tfrac{h^2}6L'''+O(h^4),\\ \frac{L(X+h\boldsymbol e_k)-L(X)}{h}=L'+\tfrac h2L''+O(h^2).\end{gathered}

第二步,舍入:每次算出的 LL 都带有约 ϵL=ϵ∣L∣\epsilon_L=\epsilon|L| 的舍入误差(ϵ\epsilon 是机器精度)。两个几乎相等的数相减,这个误差不会跟着变小,再除以 2h2h(或 hh),就放大成约 ϵL/h\epsilon_L/h(前向差分约 2ϵL/h2\epsilon_L/h)。第三步,两部分相加,对 hh 求导令其为零:

Step 2, round-off: every evaluation of LL carries a rounding error of about ϵL=ϵ∣L∣\epsilon_L=\epsilon|L| (ϵ\epsilon the machine precision). Subtracting two nearly equal numbers does not shrink it, and dividing by 2h2h (or hh) amplifies it to about ϵL/h\epsilon_L/h (about 2ϵL/h2\epsilon_L/h for the forward difference). Step 3: add the two parts, differentiate with respect to hh and set the result to zero:

Ec(h)=h26∣L′′′∣+ϵLh,Ec′(hc∗)=0 ⇒ hc∗=(3ϵL∣L′′′∣)1/3,Ef(h)=h2∣L′′∣+2ϵLh,Ef′(hf∗)=0 ⇒ hf∗=2(ϵL∣L′′∣)1/2.\begin{gathered}E_c(h)=\frac{h^2}6|L'''|+\frac{\epsilon_L}h,\quad E_c'(h_c^*)=0\ \Rightarrow\ h_c^*=\left(\frac{3\epsilon_L}{|L'''|}\right)^{1/3},\\ E_f(h)=\frac h2|L''|+\frac{2\epsilon_L}h,\quad E_f'(h_f^*)=0\ \Rightarrow\ h_f^*=2\left(\frac{\epsilon_L}{|L''|}\right)^{1/2}.\end{gathered}

所以中心差分的最优步长随 ϵ1/3\epsilon^{1/3} 缩放,前向差分随 ϵ1/2\epsilon^{1/2}。最优点上两部分同量级;步长偏大时截断误差按 h2h^2(或 hh)长,偏小时舍入误差按 1/h1/h 长。

So the best central step scales like ϵ1/3\epsilon^{1/3} and the best forward step like ϵ1/2\epsilon^{1/2}. At the optimum the two parts are of the same size; too large a step grows the truncation like h2h^2 (or hh), too small a step grows the round-off like 1/h1/h.

第四步,代入本页的数。取第 14 节面板的默认坐标(节点 7 的 y),用 h=10−2h=10^{-2} 的宽差分估出 L′′=2.804L''=2.804、L′′′=−7.57L'''=-7.57,而 ϵL=ϵ∣L∣=1.5×10−16\epsilon_L=\epsilon|L|=1.5\times10^{-16}:公式给出 hc∗=3.9×10−6h_c^*=3.9\times10^{-6},hf∗=1.5×10−8h_f^*=1.5\times10^{-8}。第 14 节的步长扫描(图 14.2)按十进位取步长,中心差分实测误差最小的步长是 10−610^{-6},前向差分是 10−810^{-8}。

Step 4, this page’s numbers. For the default coordinate of the Section 14 panel (the y of node 7), wide differences with h=10−2h=10^{-2} estimate L′′=2.804L''=2.804 and L′′′=−7.57L'''=-7.57, and ϵL=ϵ∣L∣=1.5×10−16\epsilon_L=\epsilon|L|=1.5\times10^{-16}: the formulas give hc∗=3.9×10−6h_c^*=3.9\times10^{-6} and hf∗=1.5×10−8h_f^*=1.5\times10^{-8}. In the step sweep of Section 14 (Figure 14.2), which steps by decades, the smallest measured errors are at 10−610^{-6} for the central and 10−810^{-8} for the forward difference.

核对:预测与实测最优步长相差 0.60 与 0.17 个数量级;实测最小相对误差:中心差分 2.1×10−11,前向差分 5.1×10−8(本页代码在构建时计算)。

Check: predicted and measured best steps differ by 0.60 and 0.17 decades; the smallest measured relative errors are 2.1×10−11 (central) and 5.1×10−8 (forward) (computed by this page’s code at build time).

全部分量在默认步长下

All components at the default step

第二步的舍入项 ϵL/h\epsilon_L/h 与分量 L′L' 本身的大小无关。于是在同一个步长下,所有分量的绝对误差差不多大,小分量的相对误差就大(图 13.2):本页 48 个分量的大小从 6.5×10−4 到 1.0×100,h=10−6h=10^{-6} 的中心差分的绝对误差都在 2.9×10−12 到 3.7×10−10 之间,相对误差最大 3.3×10−7。这就是上面的步长扫描按各分量自己的大小取相对误差的原因。

The round-off term ϵL/h\epsilon_L/h of Step 2 does not depend on the size of the component L′L' itself. At one step, then, all components carry absolute errors of about the same size, and small components carry large relative errors (Figure 13.2): this page’s 48 components range from 6.5×10−4 to 1.0×100 in size, the absolute errors of central differences with h=10−6h=10^{-6} all lie between 2.9×10−12 and 3.7×10−10, and the largest relative error is 3.3×10−7. This is why the step sweep above measures each component’s error relative to its own size.

10⁻¹⁸10⁻¹⁶10⁻¹⁴10⁻¹²10⁻¹⁰10⁻⁸10⁻⁴10⁻³10⁻²10⁻¹10⁰10¹梯度分量的大小 |dL/dXk|(对数)size of the gradient component |dL/dXk| (logarithmic)与伴随梯度之差的绝对值absolute difference from the adjoint gradient中心差分 h = 10−6central difference h = 10−6复数步长 h = 10−30complex step h = 10−30相对误差 10−6relative error 10−6相对误差 ϵrelative error ϵ48 个坐标分量48 coordinate components底部虚线:误差为 0(8 个)bottom line: error 0 (8 of them)
图 13.2 · 全部 48 个坐标分量的检验,横轴是分量本身的大小 |dL/dXk|,纵轴是与伴随梯度之差的绝对值,都取对数;每个点都由本页代码重新求解(差分用第 13 节面板的默认步长 10−6,复数步长用第 14 节的 10−30)。梯度分量的大小跨了 3.2 个数量级。中心差分(绿圈)的误差几乎不随分量大小变化,都在 2.9×10⁻¹² 到 3.7×10⁻¹⁰ 之间:它由步长和 L 的舍入决定,而不由分量本身决定,所以分量越小,相对误差越大,最大到 3.3×10⁻⁷(两条灰色虚线是相对误差 10−6 与机器精度 ϵ)。这就是第 13 节的步长扫描画相对误差的原因。复数步长(红菱形)的误差最大 2.2×10⁻¹⁶,停在梯度本身的舍入水平上,其中 8 个分量与伴随梯度逐位相同。Figure 13.2 · The check of all 48 coordinate components: along the axis the size of the component itself, |dL/dXk|, up the axis its absolute difference from the adjoint gradient, both logarithmic; every point is a fresh solve by this page’s code (the differences at the Section 13 panel’s default step 10−6, the complex step at Section 14’s 10−30). The components span 3.2 decades in size. The error of the central difference (green circles) hardly depends on the size of the component, lying between 2.9×10⁻¹² and 3.7×10⁻¹⁰: it is set by the step and by the rounding of L, not by the component, so the smaller the component, the larger its relative error, up to 3.3×10⁻⁷ (the grey dashed lines are relative errors of 10−6 and of the machine precision ϵ). This is why the step sweep of Section 13 plots relative errors. The complex step (red diamonds) is off by at most 2.2×10⁻¹⁶, at the round-off level of the gradient itself, and 8 components agree with the adjoint gradient to the last bit.
新增(选读):光滑特征边界模型
Added (optional): smooth characteristic-boundary model

有限差分:完整重求解与步长扫描

Finite differences: reconverged solves and step-size sweeps

选择任意坐标或入流水平 u∞;每个扰动都重新计算几何、重构和边界值,再把原始方程收敛。支持中心、前向、后向差分以及 10⁻¹ 到 10⁻¹² 的步长。大步长可能改变边界分支或失去有效网格,出现这种情况应标记为不适合当前局部导数验证。

Select any coordinate or the inflow level u∞. Each perturbation rebuilds geometry, reconstruction and boundary values and reconverges the primal. Central, forward and backward differences span steps from 10⁻¹ to 10⁻¹². Large perturbations can change boundary branches or invalidate the mesh and must be flagged as unsuitable for the current local derivative check.

(dLdDk)CD ⁣(h)=L(u(D+hek),D+hek)−L(u(D−hek),D−hek)2h.\left(\frac{dL}{dD_k}\right)^{\rm CD}\!(h)=\frac{L(u(D+h e_k),D+h e_k)-L(u(D-h e_k),D-h e_k)}{2h}.
图 13.3Figure 13.3
第 14 节Section 14

复数步长

The complex step

第 13 节的差分受步长两头夹击:截断误差要 h 小,舍入误差要 h 大。复数步长把这个两难拿掉:把一个节点坐标挪一个虚数步长 ih,整个 primal 求解用复数算,L 的虚部除以 h 就是导数:

The differences of Section 13 are squeezed from both sides: truncation wants a small h, round-off a large one. The complex step removes the dilemma: move one node coordinate by an imaginary step ih, run the whole primal solve in complex arithmetic, and the imaginary part of L, divided by h, is the derivative:

dLdXm=ImL(X+ihem)h+O(h2)

原因是 Taylor 展开:

The reason is the Taylor expansion:

L(X+ihem)=L(X)+ihdLdXm−h22d2LdXm2+O(h3)

实部与虚部各自分开:虚部的第一项就是 hdL/dXm,下一项已是 h3。这里没有两个相近的数相减,也就没有相消,h 可以取 10−30:O(h2) 的截断误差远在舍入误差之下。

Real and imaginary parts separate: the imaginary part starts with hdL/dXm, and its next term is already h3. No two nearly equal numbers are subtracted, so nothing cancels, and h can be 10−30: the O(h2) truncation error lies far below round-off.

具体实现

How to implement it

把残差里的每一个实数换成复数:每个未知量有实部与虚部两个数。被挪动的是一个节点坐标 Xm,复数从面法向进入。第 12 节的对偶面法向 nij=Σt∋(i,j)±(dy,−dx) 与边界半面的法向 12(yb−ya,−(xb−xa)) 都对坐标是线性的,所以复数法向正好是

Replace every real number in the residual by a complex one: every unknown carries a real and an imaginary part. What moves is one node coordinate Xm, and the complex numbers come in through the face normals. The dual-face normals of Section 12, nij=Σt∋(i,j)±(dy,−dx), and the boundary half-face normals 12(yb−ya,−(xb−xa)) are all linear in the coordinates, so the complex normal is exactly

n(X+ihem)=n(X)+ihn(em)

其中 n(em) 是同一个法向公式作用在第 m 个分量为 1、其余分量为 0 的坐标场上:它只落在被挪动节点所在单元里的对偶面上,以及以它为端点的边界边的两个半面上。vn=β·n 随之是复数。|n| 要当心:它得写成复数开方 (nx2+ny2)1/2,不能用复数的模,那是一个实数,会把虚部整个丢掉。h 这么小,这个开方就是

where n(em) is the same normal formula applied to the coordinate field that is 1 in component m and 0 in every other: it lives only on the dual faces inside the cells around the moved node and on the two half-faces of each boundary edge that ends at it. vn=β·n becomes complex with it. |n| needs care: it has to be the complex root (nx2+ny2)1/2, not the modulus of a complex number, which is real and would drop the imaginary part altogether. With h this small the root is

|n|=|Ren|+iRen·Imn|Ren|

其余照旧。通量 14vn(uL2+uR2)−ε|n|(uR−uL) 里只有加、减、乘;Jacobi 更新里有一次复数除法,用 Smith 算法:先用分母里绝对值较大的那个分量去除,免得平方溢出。远场按 Revn 的符号分入流与出流,与实数求解完全一样:分支只看实部,导数沿选中的分支照常传下去。目标 L=Σ壁面12uL2(n·e) 也用复数的 u 与 n 来算。边循环与 Jacobi 迭代都不变,只是每个数多带一个虚部;判据要连虚部一起看:ImR/h 的均方根也要低于 10−13。从 u* 出发时实部一开始就满足判据,只看它,迭代在第 0 轮就会停下,漏掉状态的响应。伪代码在图 14.1 的最上面。

Everything else stays. The flux 14vn(uL2+uR2)−ε|n|(uR−uL) needs only addition, subtraction and multiplication; the Jacobi update has one complex division, done by Smith's algorithm, which divides by the larger of the denominator's two parts first so that no square can overflow. The far field splits into inflow and outflow by the sign of Revn, exactly as in the real solve: a branch looks at the real part only, and the derivative passes through the branch taken as usual. The objective L=Σwall12uL2(n·e) is computed from the complex u and n too. The edge loop and the Jacobi iteration stay as they are; every number just carries an imaginary part. The criterion must watch the imaginary part as well: the rms of ImR/h must fall below 10−13 too. Started at u*, the real part meets the criterion from the start, and a test on it alone would stop the iteration at sweep 0 and miss the state's response. The pseudocode is at the top of Figure 14.1.

从收敛流场出发

Starting from the converged flow

复数迭代从哪里出发并非无关紧要。从收敛解 u* 出发(虚部为零),实部停在不动点上(到舍入误差为止)。把残差在 u* 处展开,R(u*)=0:

Where the complex iteration starts matters. Started at the converged solution u* (imaginary part zero), the real part stays at the fixed point, to round-off. Expand the residual about u*, where R(u*)=0:

R(u*+ihv;X+ihem)=ih(Av+f)+O(h2),f=∂R∂Xm

于是 Jacobi 更新 u←u−diag(A)−1R(u) 的虚部除以 h 是

so the imaginary part of the Jacobi update u←u−diag(A)−1R(u), divided by h, is

v←v−diag(A)−1(Av+f),v=Imu/h

这里 diag(A) 也是复数(它由同一个复数边循环收集),所以更新里那个商的虚部完整写出来是

Here diag(A) is complex as well (the same complex edge loop collects it), so the imaginary part of the quotient in the update reads in full

Im(diag(A)−1R)=Re(diag(A)−1)ImR+Im(diag(A)−1)ReR

从 u* 出发,ReR 停在舍入误差上,第二项也只有舍入误差的量级,剩下的正是第 10 节的前向 Jacobi 迭代,只是右端从第 10 节的 f=∂R/∂u∞ 换成了这里的 f=∂R/∂Xm:v0=0,同一个 diag(A)。所以每一轮的 Imuk/h 就是前向迭代的 vk,不只是收敛后一致:48 个坐标、每一轮(共 300 轮),最大差(除以 max|v|)是 1.1e−14;实部与实数的 Jacobi 迭代逐比特相同(复数除法用 Smith 算法:虚部小到 h 的量级时,商的实部就是实数的那一次除法)。从初始流场 u≡1 出发就不同了:ReR≠0,第二项留下来,迭代把 diag(A) 也求了导,虚部是 primal 迭代值对坐标的导数:另一个序列,只在收敛时才到同一个极限。前几轮的差可达 max|v| 的 1.18 倍,而且每个分量都要把 primal 重解一遍。节点 7 的 y 从初始流场出发要 122 轮才满足判据,从 u* 出发与前向迭代一样是 120 轮。轮数相差不多,因为 primal 与前向迭代以同一个速率收敛(第 10 节);差别在每一轮算出来的是什么。

Started at u*, ReR sits at round-off, so the second term is down at round-off too, and what is left is the forward Jacobi iteration of Section 10 with this f=∂R/∂Xm in place of Section 10’s f=∂R/∂u∞: v0=0, the same diag(A). So Imuk/h is the forward iterate vk at every sweep, not only after convergence: over the 48 coordinates and every sweep (300 of them) the largest difference (over max|v|) is 1.1e−14, and the real part is the real Jacobi iterate to the last bit (the complex division is Smith's: with an imaginary part of the order of h, the real part of the quotient is the real division itself). Started at the initial field u≡1 it is different: ReR≠0, the second term stays, the iteration differentiates diag(A) as well, and the imaginary part is the derivative of the primal iterate with respect to the coordinate: another sequence, which reaches the same limit only at convergence. In the first sweeps the two differ by up to 1.18 times max|v|, and every component re-solves the primal. The y of node 7 needs 122 sweeps to meet the criterion from the initial field, and 120 from u*, the forward iteration's count. The counts are close because the primal and the forward iteration converge at the same rate (Section 10); what differs is what each sweep computes.

用复数步长验证梯度

The gradient, checked by the complex step

每个坐标一次复数求解,得到梯度的一个分量。48 个分量与伴随梯度之差的最大值(除以梯度的最大分量)是 2.2e−16,第 13 节的中心差分(h=10−6)是 3.6e−10:那里的差全是步长造成的。复数步长是精确的前向模式,所以它与伴随相符到舍入误差,是一个与步长无关的梯度检验。它的代价也是前向模式的代价:48 个分量要 48 次复数求解,伴随只要一次线性求解。所以它用来检验伴随,不用来代替伴随。在图 14.1 里选一个节点坐标和起点,看两条迭代逐轮重合或分开;图下的表把复数步长、伴随与中心差分的值并排列出。

One complex solve per coordinate gives one component of the gradient. Over the 48 components its largest difference from the adjoint gradient (over the gradient's largest component) is 2.2e−16, against 3.6e−10 for the central differences of Section 13 (h=10−6): that difference was all step size. The complex step is an exact forward mode, so it agrees with the adjoint to round-off, a gradient check whose result does not depend on the step size. Its cost is the forward mode's too: 48 components take 48 complex solves, where the adjoint takes one linear solve. So it checks the adjoint; it does not replace it. In Figure 14.1 pick a node coordinate and a start, and watch the two iterations coincide sweep by sweep, or part; the table under the plots lists the complex step, the adjoint and the central difference side by side.

图 14.1Figure 14.1

逐步推导:复数步长为什么没有相减

Step by step: why the complex step subtracts nothing

第一步,残差和目标函数由加、减、乘、除和平方根组成(远场开关在分支内固定),都是实解析的;AA 非奇异,由隐函数定理,收敛解 u∗u^* 也解析地依赖坐标。所以这个离散的 LL 对 XmX_m 是实解析的,可以沿虚方向展开;i2=−1i^2={-1},i3=−ii^3={-i}:

Step 1. The residual and the objective are built from additions, subtractions, multiplications, divisions and square roots (the far-field switch fixed within its branch), so both are real analytic; AA is non-singular, so by the implicit function theorem the converged state u∗u^* depends analytically on the coordinates too. This discrete LL is therefore real analytic in XmX_m and can be expanded in an imaginary direction; i2=−1i^2={-1} and i3=−ii^3={-i}:

L(X+ih𝒆m)=L+ihL′−h22L′′−i h36L′′′+O(h4).L(X+ih\boldsymbol e_m)=L+ihL'-\tfrac{h^2}2L''-i\,\tfrac{h^3}6L'''+O(h^4).

第二步,实部与虚部分开:

Step 2. Separate the real and imaginary parts:

Re L(X+ih𝒆m)=L−h22L′′+O(h4),Im L(X+ih𝒆m)h=L′−h26L′′′+O(h4).\begin{gathered}\mathrm{Re}\,L(X+ih\boldsymbol e_m)=L-\tfrac{h^2}2L''+O(h^4),\\ \frac{\mathrm{Im}\,L(X+ih\boldsymbol e_m)}h=L'-\tfrac{h^2}6L'''+O(h^4).\end{gathered}

第三步,对精确的复数延拓,截断误差是 h26∣L′′′∣\tfrac{h^2}6|L'''|,与中心差分的截断误差一样大(第五步线性化的 ∣𝒏∣|\boldsymbol n| 另带一个 O(h2)O(h^2) 项):步长扫描里两条曲线在大步长处几乎重合(图 14.2)。第四步,区别在舍入:虚部是直接算出来的,不是两个相近的数之差,它的舍入误差约为 ϵ∣Im L∣≈ϵh∣L′∣\epsilon|\mathrm{Im}\,L|\approx\epsilon h|L'|,除以 hh 后是 ϵ∣L′∣\epsilon|L'|,与 hh 无关。所以 hh 可以一直取小:本页取 h=10−30h=10^{-30},截断项 h26∣L′′′∣=1.3×10−60\tfrac{h^2}6|L'''|=1.3\times10^{-60},远在舍入水平 ϵ∣L′∣=2.3×10−16\epsilon|L'|=2.3\times10^{-16} 之下;10−3010^{-30} 又远大于双精度能表示的最小正数(约 10−30810^{-308}),不会下溢。

Step 3. For the exact complex extension the truncation error is h26∣L′′′∣\tfrac{h^2}6|L'''|, as large as that of the central difference (the linearised ∣𝒏∣|\boldsymbol n| of Step 5 adds a further O(h2)O(h^2) term): the two curves of the step sweep nearly coincide at large steps (Figure 14.2). Step 4. The difference is round-off. The imaginary part is computed directly, not as the difference of two nearly equal numbers, so its rounding error is about ϵ∣Im L∣≈ϵh∣L′∣\epsilon|\mathrm{Im}\,L|\approx\epsilon h|L'|, and after dividing by hh it is ϵ∣L′∣\epsilon|L'|, independent of hh. The step can therefore be made as small as we like: this page takes h=10−30h=10^{-30}, where the truncation term h26∣L′′′∣=1.3×10−60\tfrac{h^2}6|L'''|=1.3\times10^{-60} lies far below the round-off level ϵ∣L′∣=2.3×10−16\epsilon|L'|=2.3\times10^{-16}; and 10−3010^{-30} is still far above the smallest positive double (about 10−30810^{-308}), so nothing underflows.

第五步,法向长度要用复数平方根,不能用复数的模。设 𝒏=𝒏0+ih𝒏1\boldsymbol n=\boldsymbol n_0+ih\boldsymbol n_1(𝒏0=Re 𝒏\boldsymbol n_0=\mathrm{Re}\,\boldsymbol n,𝒏1=𝒏(𝒆m)\boldsymbol n_1=\boldsymbol n(\boldsymbol e_m)),用 (1+z)1/2=1+z2+O(z2)(1+z)^{1/2}=1+\tfrac z2+O(z^2):

Step 5. The length of the normal must be the complex square root, not the modulus of a complex number. With 𝒏=𝒏0+ih𝒏1\boldsymbol n=\boldsymbol n_0+ih\boldsymbol n_1 (𝒏0=Re 𝒏\boldsymbol n_0=\mathrm{Re}\,\boldsymbol n, 𝒏1=𝒏(𝒆m)\boldsymbol n_1=\boldsymbol n(\boldsymbol e_m)) and (1+z)1/2=1+z2+O(z2)(1+z)^{1/2}=1+\tfrac z2+O(z^2):

𝒏⋅𝒏=∣𝒏0∣2−h2∣𝒏1∣2+2ih 𝒏0⋅𝒏1,𝒏⋅𝒏=∣𝒏0∣(1+2ih 𝒏0⋅𝒏1−h2∣𝒏1∣2∣𝒏0∣2)1/2=∣𝒏0∣+ih 𝒏0⋅𝒏1∣𝒏0∣+O(h2).\begin{gathered}\boldsymbol n\cdot\boldsymbol n=|\boldsymbol n_0|^2-h^2|\boldsymbol n_1|^2+2ih\,\boldsymbol n_0\cdot\boldsymbol n_1,\\ \sqrt{\boldsymbol n\cdot\boldsymbol n}=|\boldsymbol n_0|\left(1+\frac{2ih\,\boldsymbol n_0\cdot\boldsymbol n_1-h^2|\boldsymbol n_1|^2}{|\boldsymbol n_0|^2}\right)^{1/2}=|\boldsymbol n_0|+ih\,\frac{\boldsymbol n_0\cdot\boldsymbol n_1}{|\boldsymbol n_0|}+O(h^2).\end{gathered}

这就是上面的公式 ∣𝒏∣=∣Re 𝒏∣+i Re 𝒏⋅Im 𝒏/∣Re 𝒏∣|\boldsymbol n|=|\mathrm{Re}\,\boldsymbol n|+i\,\mathrm{Re}\,\boldsymbol n\cdot\mathrm{Im}\,\boldsymbol n/|\mathrm{Re}\,\boldsymbol n|。丢掉的项在实部是 O(h2)O(h^2)、在虚部是 O(h3)O(h^3),除以 hh 后只影响导数到 O(h2)O(h^2),与第三步同阶。复数的模 (∣𝒏0∣2+h2∣𝒏1∣2)1/2(|\boldsymbol n_0|^2+h^2|\boldsymbol n_1|^2)^{1/2} 却是实数,虚部为零,∣𝒏∣|\boldsymbol n| 的导数就整个丢了。

This is the formula ∣𝒏∣=∣Re 𝒏∣+i Re 𝒏⋅Im 𝒏/∣Re 𝒏∣|\boldsymbol n|=|\mathrm{Re}\,\boldsymbol n|+i\,\mathrm{Re}\,\boldsymbol n\cdot\mathrm{Im}\,\boldsymbol n/|\mathrm{Re}\,\boldsymbol n| above. The dropped terms are O(h2)O(h^2) in the real part and O(h3)O(h^3) in the imaginary part, so after dividing by hh they touch the derivative only at O(h2)O(h^2), the order of Step 3. The modulus (∣𝒏0∣2+h2∣𝒏1∣2)1/2(|\boldsymbol n_0|^2+h^2|\boldsymbol n_1|^2)^{1/2}, in contrast, is real: its imaginary part is zero and the derivative of ∣𝒏∣|\boldsymbol n| is lost altogether.

核对:在面 f12 上把精确的复数平方根与上面的线性化公式相比:h=10−2h=10^{-2} 时实部差 2.4×10−6、虚部差除以 hh 为 1.8×10−6,h=10−4h=10^{-4} 时是 2.4×10−10 与 1.8×10−10(每缩小 100 倍降 10410^4 倍,二阶),h=10−8h=10^{-8} 时 0 与 0;h=10−30h=10^{-30} 的复数步长与伴随梯度的相对差 2.2×10−16(本页代码在构建时计算)。

Check: on face f12, the exact complex square root against the linearised formula: at h=10−2h=10^{-2} the real parts differ by 2.4×10−6 and the imaginary parts, divided by hh, by 1.8×10−6; at h=10−4h=10^{-4} by 2.4×10−10 and 1.8×10−10 (a factor 10410^4 for every factor 100 in hh: second order); at h=10−8h=10^{-8} by 0 and 0; the complex step with h=10−30h=10^{-30} differs from the adjoint gradient by 2.2×10−16 relative (computed by this page’s code at build time).

10⁻¹⁶10⁻¹⁴10⁻¹²10⁻¹⁰10⁻⁸10⁻⁶10⁻⁴10⁻²10⁰10⁻³⁰10⁻²⁵10⁻²⁰10⁻¹⁵10⁻¹⁰10⁻⁵10⁰步长 h(对数)step h (logarithmic)相对误差(对伴随梯度)relative error (against the adjoint gradient)前向差分forward difference中心差分central difference复数步长complex step12−1底部 1.0×10⁻¹⁷ 处的点points on the 1.0×10⁻¹⁷ line表示误差为 0(全部位相同)are exact to the last bit (error 0)坐标:节点 7 的 ycoordinate: y of node 7dL/dX = −1.031550
图 14.2 · 三种检验的误差随步长的变化,坐标取节点 7 的 y(第 14 节面板的默认坐标,dL/dX=−1.031550),纵轴是与伴随梯度的相对差,两轴都取对数;每个点都由本页代码重新求解一次(差分 12 个步长,复数步长 30 个)。灰色虚线标出斜率 1、2 与 −1。前向差分在截断一侧斜率为 1,中心差分为 2;步长再小,两者都被舍入误差推高(斜率 −1),最好的点:前向差分在 h=10−8,中心差分在 10−6。复数步长在大步长处与中心差分的截断误差几乎重合(两者的截断误差都是 O(h2),主项都是 h2L‴/6),但步长变小后一直停在舍入误差的水平,直到 h=10−30:它没有相减,也就没有抵消。Figure 14.2 · The error of the three checks against the step, for the y coordinate of node 7 (the default of the Section 14 panel, dL/dX = −1.031550); the vertical axis is the relative difference from the adjoint gradient, both axes logarithmic, and every point is a fresh solve by this page’s code (12 steps for the differences, 30 for the complex step). The grey dashed lines mark slopes 1, 2 and −1. On the truncation side the forward difference falls with slope 1 and the central difference with slope 2; at smaller steps round-off pushes both back up (slope −1), and their best points are at h = 10−8 (forward) and 10−6 (central). At large steps the complex step lies almost on the central difference (both truncate at O(h2), with the leading term h2L‴/6), but as the step shrinks it stays at round-off level all the way to h = 10−30: it subtracts nothing, so nothing cancels.
新增(选读):光滑特征边界模型
Added (optional): smooth characteristic-boundary model

复数步长:独立原始代数的校验

Complex step: an independent primal-algebra check

独立复数运算执行同一残差、边界函数与目标,h=10⁻²⁵。基准实部决定边界分支;线性校正使 Im(R)/h 收敛,然后从 Im(L)/h 取总导数。输出不是直接返回前向算子结果。最小二乘的坐标差、矩阵求逆、长度开方和正弦边界都保留复数虚部。

Independent complex arithmetic executes the same residual, boundary function and objective with h=10⁻²⁵. Baseline real values select boundary branches. Correct the imaginary residual until Im(R)/h converges, then read Im(L)/h. This is not a returned tangent derivative. Complex parts propagate through coordinate differences, least-squares inversion, lengths and the sine boundary.

图 14.3Figure 14.3
第 15 节Section 15

精度阶:构造解检验

Order of accuracy: a manufactured solution

伴随给出的是离散格式的精确导数,与格式本身是几阶精度无关;格式的精度阶决定的是离散解,以及由它算出的 L 与 dL/dX,离连续问题有多远。这一节用构造解把它测出来:先选一个光滑函数当精确解,由它反推出源项与边界数据,再在一族逐次一致加密的网格上求解,看误差随网格尺寸按几次方减小。

The adjoint gives the exact derivative of the discrete scheme, whatever that scheme's order of accuracy; the order decides how far the discrete solution, and the L and dL/dX computed from it, are from the continuous problem. This section measures it with a manufactured solution: pick a smooth function as the exact solution, derive the source term and the boundary data from it, solve on a family of uniformly refined meshes, and see with what power of the mesh size the error falls.

构造解

The manufactured solution

ue(x,y)=12+14sin(0.9x+0.3)cos(0.7y−0.2)

与二维对流扩散页面用的是同一个函数。它处处介于 0.25 与 0.75 之间,与本页算例的解同号(u>0),所以对流速度 uβ·n 的符号与原问题相同,远场上入流与出流的划分也不变。它的一阶导数:

It is the same function as on the advection–diffusion pages. It lies between 0.25 and 0.75 everywhere, of the same sign as this page's solution (u>0), so the convective speed uβ·n has the same signs as in the original problem, and the far field splits into the same inflow and outflow parts. Its first derivatives:

∂ue∂x=0.225cos(0.9x+0.3)cos(0.7y−0.2)
∂ue∂y=−0.175sin(0.9x+0.3)sin(0.7y−0.2)

源项

The source term

在方程右端加一个源项 s,让 ue 恰好是它的解:

Put a source term s on the right-hand side and make ue its exact solution:

∇·(12u2β)=s
s=ueβ·∇ue=ue(∂ue∂x+0.5∂ue∂y)

这里用到 β=(1,0.5) 是常数:∇·(12u2β)=uβ·∇u。离散时每个控制体只取源项的一个值(单点积分),从残差里减去:

This uses β=(1,0.5) being constant: ∇·(12u2β)=uβ·∇u. The discrete source takes one value per control volume (a one-point quadrature) and is subtracted from the residual:

Ri(u)←Ri(u)−s(xi)|Ωi|

其中 |Ωi| 是节点 i 的中位对偶控制体的面积,源项就在节点 xi 上求值。

where |Ωi| is the area of the median-dual control volume of node i, and the source is evaluated at the node xi itself.

边界数据

The boundary data

远场照原问题按特征方向划分:入流半面的外侧状态取 ue,在半面的中点 xf 求值;出流半面照旧外推:

The far field splits by the characteristic direction as in the original problem: the outer state of an inflow half-face is ue at its midpoint xf, and an outflow half-face extrapolates as before:

uR=ue(xf)(vn<0)
uR=uL(vn≥0)

物面在这一节里也按同一条规则处理:流体从物面流进计算域的地方(vn<0,特征进入计算域)给 ue;流体撞向物面的迎风一侧(vn>0,特征离开计算域)外推。于是整条边界上只在特征进入的地方给数据,构造解问题是适定的。本页算例的壁面在每个壁面半面上规定通量;为什么不能把它照搬过来,见本节后面“为什么不能照搬本页的壁面”一小节。

In this section the body follows the same rule: where the flow leaves the body into the domain (vn<0, characteristics enter the domain) the outer state is ue, and on the windward side, where the flow runs into the body (vn>0, characteristics leave the domain), the state is extrapolated. Data are then given only where characteristics enter, along the whole boundary, and the manufactured problem is well posed. The wall of this page's test case prescribes the flux on every one of its wall half-faces; why that cannot simply be carried over is the subject of the subsection “Why this page's wall cannot be carried over” below.

网格族

The mesh family

从 24 个单元的基础网格出发,每加密一层:每个三角形用三条边的中点分成 4 个三角形,每个四边形用四条边的中点与顶点平均点分成 4 个四边形,边界边在中点处一分为二。物面与远场都是直边八边形,所以每一层的计算域完全相同,网格尺寸逐层减半,hℓ=h0/2ℓ。7 层网格有 24、96、384、1536、6144、24576、98304 个单元,24、80、288、1088、4224、16640、66048 个节点。这就是二维对流扩散页面用的那一族网格,只是多加密了一层(到 98304 个单元、66048 个节点),原因见下文。

Starting from the 24-cell base mesh, each refinement splits every triangle into 4 triangles through its edge midpoints and every quadrilateral into 4 quadrilaterals through its edge midpoints and its vertex average, and every boundary edge in two at its midpoint. The body and the far field are both straight-edged octagons, so the domain is exactly the same on every level and the mesh size halves from one level to the next, hℓ=h0/2ℓ. The 7 meshes have 24, 96, 384, 1536, 6144, 24576, 98304 cells and 24, 80, 288, 1088, 4224, 16640, 66048 nodes. It is the family of the advection–diffusion pages, refined one level further (to 98304 cells and 66048 nodes), for a reason that comes below.

误差与观测阶

The error and the observed order

误差在节点上量,与 ue 在节点的值比较,用对偶控制体面积加权的 L2 范数与最大范数:

The error is measured at the nodes, against ue there, in the L2 norm weighted by the dual control-volume areas and in the max norm:

‖e‖2=(Σi|Ωi|(ui−ue(xi))2Σi|Ωi|)1/2
‖e‖∞=maxi|ui−ue(xi)|

相邻两层的网格尺寸之比正好是 2,所以观测阶是误差之比以 2 为底的对数:

The mesh sizes of two neighbouring levels differ by exactly a factor of 2, so the observed order is the base-2 logarithm of the error ratio:

pℓ=log2‖e‖ℓ−1‖e‖ℓ
‖e‖ℓ≈Chℓp

每一层都用第 6 节的 Jacobi 迭代求解:先迭代到判据满足(残差的均方根小于 10−13),比如在第 k 轮,再多跑 k 轮,这时已经落在舍入误差平台上。网格越密,Jacobi 越慢:最细一层要 3633 轮才满足判据。离散解与求解器无关,误差表不受影响。本页格式的结果:

Every level is solved by the Jacobi iteration of Section 6: iterated until the criterion is met (the rms residual below 10−13), at some sweep k, and then k sweeps more, by which time it is on its round-off floor. The finer the mesh, the slower Jacobi: the finest level takes 3633 sweeps to meet the criterion. The discrete solution does not depend on the solver, and the error table is not affected. This page's scheme:

层 ℓlevel ℓ节点数nodes‖e‖2p‖e‖∞p满足判据的轮数sweeps to criterion
0246.18e−2—1.05e−1—141
1804.46e−20.479.09e−20.21273
22883.06e−20.546.87e−20.40496
310882.05e−20.585.09e−20.43830
442241.31e−20.653.61e−20.491350
5166407.88e−30.732.41e−20.582220
6660484.48e−30.821.49e−20.703633

图 15.1 把两种格式的 L2 与最大范数误差画在同一张双对数图上。

Figure 15.1 puts the L2 and the max-norm errors of both schemes on one logarithmic plot.

2e−35e−31e−22e−25e−21e−12496384153661442457698304单元数(每层 ×4;格点格式的节点数 24、80、288、1088、4224、16640、66048)误差斜率 1格点,‖e‖2格心,‖e‖2格点,‖e‖∞格心,‖e‖∞2e−35e−31e−22e−25e−21e−12496384153661442457698304cells (×4 per level; the node-centred scheme has 24, 80, 288, 1088, 4224, 16640, 66048 nodes)errorslope 1node, ‖e‖2cell, ‖e‖2node, ‖e‖∞cell, ‖e‖∞
图 15.1 · 构造解的误差随网格加密的变化(24、96、384、1536、6144、24576、98304 个单元;格点格式为 24、80、288、1088、4224、16640、66048 个节点),双对数坐标。实线是 L2 范数,虚线是最大范数;灰色点线是斜率 1 的参考线。实线(L2 范数)的斜率逐层变陡,向 1 靠拢;虚线(最大范数)不那么整齐:格心格式那条的斜率先变缓(阶 0.46、0.26)再变陡。Figure 15.1 · The manufactured-solution error under refinement (24, 96, 384, 1536, 6144, 24576, 98304 cells; 24, 80, 288, 1088, 4224, 16640, 66048 nodes for the node-centred scheme), on logarithmic axes. Solid: the L2 norm; dashed: the max norm; the grey dotted line has slope 1. The solid lines (L2) steepen level by level towards 1; the dashed ones (max norm) are less orderly: the slope of the cell-centred one first flattens (orders 0.46, 0.26), then steepens.

一阶,但来得很慢

First order, but slowly

对流通量里的耗散项可以写成扩散两点通量的样子。设 d 是从左侧节点指向右侧节点的边向量,且与 n 平行:

The dissipation in the convective flux can be written as a two-point diffusive flux. Let d be the edge vector from the left node to the right one, and let it be parallel to n:

−ε|n|(uR−uL)=−νnum|n|uR−uL|d|
νnum=ε|d|=O(h)

离散格式实际上是在解多了一个扩散项、扩散系数为 O(h) 的方程,误差应当是 O(h):格式的设计精度是一阶。实测的 L2 阶是从下面一步步往 1 爬的:本页 0.47、0.54、0.58、0.65、0.73、0.82,格心页面 0.42、0.48、0.60、0.72、0.81、0.87。本页最大范数的阶 0.21、0.40、0.43、0.49、0.58、0.70 也在爬;格心页面的最大范数阶不那么整齐,先降后升。

The discrete scheme is in effect solving the equation with an added diffusion of coefficient O(h), so its error should be O(h): the design order is one. The measured L2 orders climb towards 1 from below, one level at a time: 0.47, 0.54, 0.58, 0.65, 0.73, 0.82 on this page, 0.42, 0.48, 0.60, 0.72, 0.81, 0.87 on the cell-centred page. This page's max-norm orders, 0.21, 0.40, 0.43, 0.49, 0.58, 0.70, climb too; the cell-centred page's are less orderly and fall before they rise.

为什么这么慢?把 ue 代进离散方程,每单位面积剩下的残差就是截断误差:

Why so slowly? Put ue into the discrete equations; what remains of the residual per unit area is the truncation error:

τi=Ri(ue)|Ωi|

在规则网格上,一阶格式的截断误差是 O(h)。在这张不规则网格上并非如此:在本页,它的 L2 范数逐层是 1.6e−1、1.3e−1、8.9e−2、6.2e−2、4.3e−2、3.0e−2、2.1e−2,每加密一次的阶只有 0.38、0.50、0.52、0.52、0.51、0.51,大约是 h1/2;在格心页面上,它几乎不变。不规则的控制体各个面上的 Taylor 展开项在控制体内并不逐点抵消。可是解的误差照样在减小:相邻控制体的局部误差在解里互相抵消,误差仍然是一阶的(这叫超收敛)。这种抵消是逐层慢慢显现出来的,耗散越强显现得越慢:把 ε 从 0.8 换成 0.3,第 4 层(4224 个节点)上的 L2 阶就从 0.65 升到 0.82(格心页面第 4 层从 0.72 升到 0.86)。图 15.2 把截断误差与解的误差画在一起。

On a regular mesh the truncation error of a first-order scheme is O(h). On this irregular mesh it is not: here its L2 norm is 1.6e−1, 1.3e−1, 8.9e−2, 6.2e−2, 4.3e−2, 3.0e−2, 2.1e−2 level by level, the orders only 0.38, 0.50, 0.52, 0.52, 0.51, 0.51, about h1/2; on the cell-centred page it hardly changes. The Taylor terms of the faces of an irregular control volume do not cancel pointwise inside it. The solution error falls all the same: the local errors of neighbouring control volumes cancel in the solution, and the error is still first order (supraconvergence). That cancellation shows itself only gradually, level by level, and the more slowly the stronger the dissipation: with ε=0.3 instead of 0.8 the L2 order on level 4 (4224 nodes) rises from 0.65 to 0.82 (the cell-centred page, level 4: from 0.72 to 0.86). Figure 15.2 draws the truncation error beside the solution error.

2e−35e−31e−22e−25e−21e−12496384153661442457698304单元数(每层 ×4;格点格式的节点数 24、80、288、1088、4224、16640、66048)L2 范数斜率 1格心,截断误差 τ格点,截断误差 τ格心,解的误差格点,解的误差2e−35e−31e−22e−25e−21e−12496384153661442457698304cells (×4 per level; the node-centred scheme has 24, 80, 288, 1088, 4224, 16640, 66048 nodes)L2 normslope 1cell, truncation τnode, truncation τcell, solution errornode, solution error
图 15.2 · 截断误差 τ(点线)与解的误差(实线),都取 L2 范数。格心格式的截断误差几乎不随网格变化,格点格式的大约按 h1/2 减小;两种格式解的误差都在向斜率 1 靠拢。Figure 15.2 · The truncation error τ (dotted) and the solution error (solid), both in the L2 norm. The cell-centred truncation error hardly changes with the mesh, the node-centred one falls about like h1/2; the solution errors of both schemes approach slope 1.

为什么不能照搬本页的壁面

Why this page's wall cannot be carried over

本页算例的壁面在每个壁面半面上规定通量(h=0)。照搬到构造解问题上,就是在每个壁面半面上规定精确解的法向通量 12ue(xf)2vn。在流体撞向物面的迎风一侧(vn>0),特征离开计算域;这里通量 12u2vn 一旦给定,u 本身也就给定了,问题多了一个条件。这与第 4 节解释本页为什么没有 Dirichlet 壁面的,是同一个理由。

The wall of this page's test case prescribes the flux on every one of its wall half-faces (h=0). Carried over to the manufactured problem, that means prescribing the exact solution's normal flux 12ue(xf)2vn on every one of them. On the windward side, where the flow runs into the body (vn>0), characteristics leave the domain; there a given flux 12u2vn gives u itself, and the problem has one condition too many. It is the same reason Section 4 gives for this page having no Dirichlet wall.

实测:在每个壁面半面上都规定通量时,迎风侧壁面控制体上的最大误差逐层是 2.3e−2、2.7e−2、3.1e−2、5.3e−2、6.8e−2、8.1e−2,并不减小;其余地方的最大误差是 1.1e−1、9.1e−2、6.9e−2、5.1e−2、4.1e−2、4.3e−2。L2 误差仍在减小(阶 0.52、0.55、0.57、0.63、0.71),因为出问题的只是贴着迎风侧壁面的一层,这一层随网格加密越来越薄。图 15.3 把两者画在一起。

Measured: with the flux prescribed on every one of the wall half-faces, the max error on the windward wall control volumes is 2.3e−2, 2.7e−2, 3.1e−2, 5.3e−2, 6.8e−2, 8.1e−2 level by level, and does not fall; everywhere else it is 1.1e−1, 9.1e−2, 6.9e−2, 5.1e−2, 4.1e−2, 4.3e−2. The L2 error still falls (orders 0.52, 0.55, 0.57, 0.63, 0.71), because the trouble is confined to one layer against the windward wall, and that layer gets thinner with every refinement. Figure 15.3 draws the two side by side.

2e−25e−21e−124963841536614424576单元数(格点格式的节点数 24、80、288、1088、4224、16640)最大误差格心,迎风侧壁面格点,迎风侧壁面格心,其余格点,其余2e−25e−21e−124963841536614424576cells (the node-centred scheme has 24, 80, 288, 1088, 4224, 16640 nodes)max errorcell, windward wallnode, windward wallcell, everywhere elsenode, everywhere else
图 15.3 · 在每个壁面半面上都规定通量(本页壁面的做法)时的最大误差:迎风侧壁面控制体上(实线)不随网格减小;其余地方(虚线)总体上随网格减小。Figure 15.3 · The max error with the flux prescribed on every one of the wall half-faces, the way this page's wall does it: on the windward wall control volumes (solid) it does not fall with the mesh; everywhere else (dashed) it falls overall.

本页自己的问题:加密网格上没有极限

This page's own problem under refinement: no limit

上面的构造解都换掉了本页的壁面条件,所以回答不了一个问题:本页自己的离散问题(每个壁面半面上 h=0,远场 u∞=1,没有源项)在网格加密时会怎样?把它放到同一族网格上,每一层都用第 6 节的 Jacobi 迭代从 u0≡1 出发求解(满足判据后再跑同样多轮),结果如下:

The manufactured problems above all replace this page's wall condition, so they cannot answer one question: what does this page's own discrete problem (h=0 on every wall half-face, u∞=1 at the far field, no source) do under refinement? On the same mesh family, every level solved by the Jacobi iteration of Section 6 from u0≡1 (and as many sweeps again after the criterion), it gives:

层 ℓlevel ℓ节点数nodesLh与上一层之比ratio to previous壁面最大值largest wall value满足判据的轮数sweeps to criterion每轮缩小factor per sweep符号不对的局部导数derivatives of the wrong sign
0240.693—1.541220.7971
1801.5242.202.051650.8448
2288从 u0≡1 出发的 Jacobi 不收敛Jacobi from u0≡1 does not converge

Lh 从第 0 层到第 1 层增大到 2.20 倍,迎风一侧的壁面值也一层比一层大(1.54、2.05)。原因是守恒:在 h=0 的壁面上,上游沿特征线流进迎风壁面控制体的通量没有出口,只能沿着壁面、从侧面的面上被运走;网格越密,贴壁这一层越薄,侧面也越短,那里的 u 就得越大。壁面值越过第 3 节的单调性界 1.43 以后,符号不对的局部导数一层比一层多(1、8),Jacobi 每一轮的缩小倍数也向 1 靠拢(0.797、0.844)。到第 2 层(288 个节点),从 u0≡1 出发的 Jacobi 不再收敛:残差均方根在 k=5 降到最小值 7.7×10−3,随后增大,k=19 时迭代值溢出。diag(A) 直到 k=11 才出现非正元素,所以先失效的是每一轮迭代的收缩,而不是除以 diag(A)。格心页面的 Lh 是 0.412、0.804、1.557,第 3 层起 Jacobi 同样不再收敛。

Lh more than doubles from level 0 to level 1 (ratio 2.20), and the windward wall values grow level by level (1.54, 2.05). The cause is conservation: on a wall with h=0, the flux that the characteristics carry into the windward wall control volumes has no exit through the wall and can only be carried away sideways, along the wall; the finer the mesh, the thinner that layer and the shorter its side faces, so the larger u has to be there. Once the wall values are past the monotonicity bound 1.43 of Section 3, local derivatives of the wrong sign multiply level by level (1, 8), and the Jacobi factor per sweep creeps towards 1 (0.797, 0.844). On level 2 (288 nodes) the Jacobi iteration from u0≡1 no longer converges: the rms residual reaches its smallest value, 7.7×10−3, at k=5, then grows, and the iterate overflows at k=19. diag(A) has no non-positive entry until k=11, so what fails first is the contraction of each sweep, not the division by diag(A). On the cell-centred page Lh is 0.412, 0.804, 1.557, and Jacobi stops converging from level 3 on.

上一小节“在每个壁面半面上规定通量”的构造解看不到这种积累,因为它规定的是精确解的非零通量 12ue2vn:流进迎风壁面控制体的通量正好从壁面流出去,没有东西积累,那里的误差不随加密减小,但也只停在上面实测的大小。本页的 h=0 却把这份通量整个挡在了壁面上。

The manufactured problem of the previous subsection, with the flux prescribed on every wall half-face, cannot show this build-up, because the flux it prescribes is the exact solution's non-zero 12ue2vn: what flows into the windward wall control volumes leaves through the wall, nothing accumulates, and the error there does not fall with refinement, but stays at the size measured above. This page's h=0 holds that flux back at the wall entirely.

这对伴随意味着什么

What this means for the adjoint

第 11–14 节验证的是:在每一张给定的网格上,伴随与几何梯度对这个离散的 Lh 精确到舍入误差,这与格式几阶无关,也与离散问题有没有连续极限无关。上面的加密表说明,本页的主线问题没有这样的极限:Lh 随加密增长而不收敛,离散解也不逼近任何连续解。原因在壁面条件(第 4 节),不在格式的阶:提高格式的阶也好,改动伴随也好,都改变不了这一点。所以本页的伴随回答的是“这张网格上的这个离散问题”的灵敏度。要让加密有意义,得换一个有连续极限的内边界:各节末尾的“新增”扩展(选读)把内边界也按特征方向处理,它的加密研究在新增的第 15、16 节和附录 B。

What Sections 11 to 14 verified is that on each given mesh the adjoint and the geometric gradient are exact, to round-off, for this discrete Lh, whatever the scheme's order and whether or not the discrete problem has a continuum limit. The refinement table above shows that the main-line problem has no such limit: Lh grows without converging, and the discrete solutions approach no continuous solution. The cause is the wall condition (Section 4), not the order of the scheme: neither raising the order nor changing the adjoint can alter that. So the page's adjoint gives the sensitivity of this discrete problem on a given mesh. For refinement to mean something, the inner boundary has to be one with a continuum limit: the optional “Added” extension at the end of each section treats the inner boundary by characteristics too, and its refinement study is in the added Sections 15 and 16 and Appendix B.

新增(选读):光滑特征边界模型
Added (optional): smooth characteristic-boundary model

精度阶:同一光滑、无源问题上的加密

Accuracy: refinement of the same smooth source-free problem

一阶、二阶使用完全相同的解析解与特征边界,没有切换成不同物理问题。每次把三角形或四边形细分为四块,保留原多边形内外边界。

Both orders use exactly the same analytic solution and characteristic boundaries; no different physical problem is substituted. Each refinement splits triangles or quadrilaterals into four while preserving polygonal boundaries.

Eh=∑iVi(ui−uiref)2∑iVi,q=log⁡2(Eh/Eh/2).E_h=\sqrt{\frac{\sum_iV_i(u_i-u_i^{ref})^2}{\sum_iV_i}},\qquad q=\log_2(E_h/E_{h/2}).

二阶一直加密到第 4 层(4224 个节点),最后两层的实测阶是 1.97、2.37,达到设计阶 2。一阶的耗散很强,渐近区来得很晚:一直加密到第 7 层(263168 个节点),最后三层的实测阶是 0.63、0.77、0.86,逐层往 1 爬,还没有到 1;不能把尚未达到 1 的实测阶写成 1。一阶的细网格用 fast1.js 求解:它逐位复现本页一阶模型的残差、对角元与 Jacobi 迭代(check.js 核查),每一层的解都再代回本页模型的残差检验。

Second order is refined to level 4 (4224 nodes); its last two measured orders are 1.97, 2.37, reaching the design order 2. First order is strongly dissipative and reaches its asymptotic range late: refined to level 7 (263168 nodes), its last three measured orders are 0.63, 0.77, 0.86, climbing level by level but not yet 1; a measured order below one must not be relabelled one. The fine first-order levels are solved by fast1.js, which reproduces this page’s first-order residual, diagonal and Jacobi iteration bit for bit (checked in check.js); every level’s solution is put back into this page’s own residual as a check.

格点比较解析节点值;格心比较解析场的控制体平均值,采用独立体积分求积。图表显示实际数值,不用理论斜率替换测量曲线。

Compare analytic nodal values on the node grid and independently integrated control-volume averages on the cell grid. Plots show measured errors, not replacement curves with prescribed slopes.

图 15.4Figure 15.4
第 16 节Section 16

伴随一致性:结论与阅读范围

Adjoint consistency: conclusions and scope

原算例的零数值通量内壁不提供所需的光滑、正值连续参考。附录另设特征边界对照;它的加密结果不能用来证明原零通量算例的连续一致性。原算例仍需通过自己的离散转置与梯度检验。

The original zero numerical wall flux does not supply the required smooth positive continuous reference. The appendix uses a separate characteristic-boundary contrast; its refinement cannot establish continuous consistency of the original zero-flux case. The original case retains its own discrete transpose and gradient checks.

点积测试检查离散转置,差分和复数步长检查同一离散目标的梯度。独立连续参考回答另一问题:加密后是否趋向相同的连续灵敏度。出现差异时,先核对边界、目标、乘子含义、正则性和参考误差,再讨论适定性。

Dot tests check the discrete transpose; finite differences and complex steps check the gradient of the same discrete objective. An independent continuous reference asks a different question: whether refinement approaches the same continuous sensitivity. A discrepancy calls for checking boundaries, objectives, multiplier interpretation, regularity and reference error before diagnosing well-posedness.

附录 A集中给出完整推导、参考实现、图与交互;可先继续阅读离散伴随主线。

Appendix A contains the full derivation, reference implementation, figures and interaction. You can continue the discrete-adjoint lesson first.

离散乘子与连续场为什么不能直接混用

Why a discrete multiplier is not automatically a continuous field

正文使用普通数组点积定义 ATA^T。若另选带权内积,算子的伴随就会改变:状态权矩阵为 WUW_U、残差权矩阵为 WRW_R 时,带权伴随是 WU−1ATWRW_U^{-1}A^TW_R。代码的普通转置不能在没有换元说明的情况下被写成该带权算子。

The main derivation defines ATA^T using ordinary array dot products. A weighted inner product changes the adjoint: with state and residual weights WUW_U and WRW_R it is WU−1ATWRW_U^{-1}A^TW_R. The coded ordinary transpose cannot be relabelled as that weighted operator without a change of variables.

⟨Av,w⟩WR=vTATWRw=⟨v,A†w⟩WU,\langle Av,w\rangle_{W_R}=v^TA^TW_Rw=\langle v,A^\dagger w\rangle_{W_U},
A†=WU−1ATWR.A^\dagger=W_U^{-1}A^TW_R.

残差的归一化也会改变乘子的数值。若改为 R𝒩=𝒩−1RR_{\mathcal N}={\mathcal N}^{-1}R,𝒩{\mathcal N} 为可逆的行尺度矩阵(例如各控制体的体积),则在收敛解上 A𝒩=𝒩−1AA_{\mathcal N}={\mathcal N}^{-1}A,ψ𝒩=𝒩Tψ\psi_{\mathcal N}={\mathcal N}^T\psi;目标总梯度保持不变。即使 𝒩{\mathcal N} 随设计改变,由 𝒩˙\dot {\mathcal N} 产生的项也乘以 RR,在精确收敛时为零。把控制体积分残差改成体积平均残差后,不能仍直接比较原来的乘子曲线。

Residual normalization also changes multiplier values. For R𝒩=𝒩−1RR_{\mathcal N}={\mathcal N}^{-1}R with an invertible row-scaling matrix 𝒩{\mathcal N} (for example the control-volume sizes), at convergence A𝒩=𝒩−1AA_{\mathcal N}={\mathcal N}^{-1}A and ψ𝒩=𝒩Tψ\psi_{\mathcal N}={\mathcal N}^T\psi, while the total objective gradient is unchanged. Even when 𝒩{\mathcal N} depends on the design, the terms from 𝒩˙\dot {\mathcal N} multiply RR and vanish at exact convergence. Multipliers of integrated and volume-normalized residuals cannot be compared without rescaling.

连续附录从微分方程、积分目标和分部积分出发;正文从实际离散残差及其有限维链式法则出发。两者可以比较同一设计变量的目标总导数与加密极限,但还须匹配符号、变量、积分权重和边界约束。

The continuous appendix starts from differential equations, integral objectives and integration by parts; the main text starts from actual discrete residuals and finite-dimensional chain rules. Compare total derivatives of the same objective and design variable and their refinement limits, after matching signs, variables, weights and boundary constraints.

新增(选读):光滑特征边界模型
Added (optional): smooth characteristic-boundary model

连续伴随:边界条件随目标一起改变

Continuous adjoint: boundary conditions change with the objective

新的原始边界有明确的光滑正值参考,主线零通量问题的“不存在光滑参考”结论在这里不再适用。但光滑原始解不保证伴随处处光滑:本目标只作用于内边界,特征线终止在内边界或外边界时得到不同的伴随末端值,两类特征线的分界上伴随有跳变。附录 B 给出推导及射线追踪参考。

The new primal boundary admits a smooth positive reference; the main-line zero-flux problem’s lack of such a reference does not apply here. A smooth primal does not ensure a globally smooth adjoint: this objective acts only on the inner boundary, giving different terminal data to characteristics ending on inner versus outer boundaries, and the adjoint jumps across the interface between the two families. Appendix B derives and traces the reference.

离散点积与梯度验证检查的是当前程序,连续比较检查的是网格极限;附录 B 的表逐层给出后者。ψ_h 与射线参考之差的 L2 范数随加密减小,但减得慢:一阶第 0–4 层是 0.250、0.224、0.194、0.163、0.138,二阶是 0.236、0.163、0.116、0.099、0.078。主要的差来自两类特征线的分界:参考解在那里跳变,离散伴随把跳变抹开。这两个证据不能互相替代;尤其不能用原始解的二阶误差自动宣布伴随跨特征分界的最大范数也二阶。

Discrete dot products and gradient tests validate the program; continuous comparisons study its mesh limit, which the table in Appendix B measures level by level. The L2 norm of ψ_h minus the ray reference falls under refinement, but slowly: 0.250, 0.224, 0.194, 0.163, 0.138 at first order and 0.236, 0.163, 0.116, 0.099, 0.078 at second order, levels 0–4. Most of the difference sits at the interface between the two families of characteristics, where the reference jumps and the discrete adjoint smears the jump. Neither kind of evidence replaces the other, and second-order primal errors do not imply second-order adjoint maximum-norm convergence across characteristic interfaces.

打开附录 B 的射线追踪、逐点比较与加密表

Open ray tracing, pointwise comparison and the refinement table in Appendix B

第 17 节Section 17

总结

Summary

原始Primal切线(前向)Tangent (forward)伴随(反向)Adjoint (reverse)
函数Functionresidual(u)residual_d(u, v)residual_b(u, w)
种子Seed—u̇=vR̄=w
结果ResultR(u)Ṙ=Avū=ATw
面上计算Computation on the faceh=H(uL,uR)ḣ=cLu̇L+cRu̇R(ūL,ūR)=(cL,cR)h̄
边循环Edge loop收集 → 计算 → 分发gather → compute → scatter同左,数据换成扰动as on the left, with perturbations in place of states顺序倒转,收集与分发互换order reversed, gather and scatter exchanged
Jacobi 求解Jacobi solveu←u−diag(A)−1R(u)v←v−diag(A)−1(Av+f)ψ←ψ−diag(A)−1(ATψ+g)
种子 ei 的结果Result for the seed ei—A 的第 i 列column i of AA 的第 i 行row i of A
能否用差分近似Can differences approximate it?—能yes只能逐列:N 次扰动才得到 ATw;实用上反向求导only column by column: N perturbations give ATw; in practice, reverse differentiation

与格心格式的对照

Compared with the cell-centred scheme

单元中心Cell-centred节点中心(本页)Node-centred (this page)
未知量在哪Where the unknowns live单元中心cell centres网格节点mesh nodes
控制体Control volume单元本身(四边形或三角形)the cell itself (quadrilateral or triangle)中位对偶体(中点—中心连线围成)the median dual, bounded by the midpoint-to-centre lines
未知量个数Number of unknowns24(8 四边形 + 16 三角形)24 (8 quadrilaterals + 16 triangles)24(节点)24 (nodes)
面Faces48 条边48 edges48 个对偶面 + 32 个边界半面 = 8048 dual faces + 32 boundary half-faces = 80
每个控制体的面数Faces per control volume3 或 4(看单元类型)3 or 4, depending on the cell type内部节点等于相邻节点数(本算例都是 5);边界节点是相邻节点数(壁面 3、远场 4)再加两个边界半面(算例中每个控制体 5 或 6 个面)for an interior node, the number of neighbouring nodes (5 everywhere in this mesh); a boundary node has its neighbours (3 on the wall, 4 on the far field) plus its two boundary half-faces (5 or 6 faces per control volume here)
对角元 diag(A)Diagonal diag(A)都逐个不同(壁面不贡献、出流面导数不同)different entry by entry in both (the wall contributes nothing, and an outflow face has a different derivative)
边循环结构Structure of the loop完全相同:收集 → 一维通量 → 分发identical: gather → one-dimensional flux → scatter
前向 / 伴随Forward / adjoint完全相同:只依赖 cL,cR 与分发符号identical: they depend only on cL,cR and the scatter signs
  • 对偶控制体是造出来的:在每个单元内把边中点与单元中心相连,拼出每个节点的控制体;三角形恰好三等分,四边形不是四等分,所以体积要逐块累加(本页只有第 15 节用到它,见第 2 节)。
  • The dual control volume has to be constructed: inside each cell join the edge midpoints to the cell centre, and assemble the control volume of every node from the pieces; a triangle divides into exactly three equal parts, but a quadrilateral does not divide into four equal parts, so the volume has to be accumulated piece by piece (on this page only Section 15 uses it; see Section 2).
  • 边界条件只住在 flux 里面:无粘壁面取 h=0,远场按特征方向选入流/出流(壁面的 h=0 是离散闭合,第 4 节);边循环一行都不用改。
  • Boundary conditions live inside flux alone: an inviscid wall takes h=0, and the far field picks inflow or outflow by the characteristic direction (the wall's h=0 is a discrete closure, Section 4); not one line of the edge loop changes.
  • 一维通量是核心:控制体造好之后,残差就是“对每个对偶面调用一次一维通量,再收集与分发”。
  • The one-dimensional flux is the core: once the control volumes are built, the residual is simply "call the one-dimensional flux once per dual face, then gather and scatter".
  • matrix-free 前向:对一维通量求局部导数 cL,cR,边循环不变。
  • Matrix-free forward: take the local derivatives cL,cR of the one-dimensional flux; the edge loop is unchanged.
  • matrix-free 伴随:每个面的贡献转置——一维通量从“两个输入、一个输出”变为“一个输入、两个输出”,边循环中收集与分发互换、数据方向反转。
  • Matrix-free adjoint: transpose the contribution of each face — the one-dimensional flux goes from "two inputs, one output" to "one input, two outputs", and in the edge loop gather and scatter swap while the data direction reverses.
  • 求解:primal、切线、伴随三个方程都用同一个 Jacobi 迭代,只用到对角元 diag(A) 与 matrix-free 的乘积;进入渐近阶段以后,三者以同一个速率收敛(谱半径 0.797)。
  • Solving: the primal, tangent and adjoint equations are all solved by the same Jacobi iteration, which needs only diag(A) and the matrix-free products; once asymptotic, all three converge at the same rate (spectral radius 0.797).
  • 验证:点积测试检查转置,差分与复数步长检查梯度。复数步长从收敛解出发,每一轮都与前向迭代一致,与伴随梯度相符到舍入误差。
  • Verification: the dot-product test checks the transpose, finite differences and the complex step check the gradient. Started from the converged solution, the complex step matches the forward iteration at every sweep, and the adjoint gradient to round-off.
  • 精度阶:构造解检验(第 15 节)表明格式是一阶的,耗散项相当于一个系数为 O(h) 的扩散。在这张不规则网格上一阶来得很慢:L2 阶逐层往 1 爬,最细一层是 0.82(格心页面 0.87)。伴随对离散的 L 始终是精确的。但本页自己的零通量问题没有连续极限:加密时 Lh 大约翻倍(0.693、1.524),第 2 层起 Jacobi 不再收敛;构造解能测出格式的阶,正是因为它换掉了壁面条件。
  • Order of accuracy: a manufactured solution (Section 15) shows the scheme to be first order, the dissipation acting as a diffusion of coefficient O(h). On this irregular mesh first order comes slowly: the L2 orders climb towards 1 level by level, 0.82 on the finest level (0.87 on the cell-centred page). The adjoint is exact for the discrete L either way. But this page's own zero-flux problem has no continuum limit: under refinement Lh roughly doubles (0.693, 1.524), and from level 2 on Jacobi stops converging; the manufactured solution can measure the order only because it replaces the wall condition.

本页交互面板里的数字(收敛解、Jacobian、前向与伴随结果、梯度、差分与复数步长)都由页面中的脚本在加载时实时计算;正文、表格与静态图中引用的数值由同一份模型代码预先算出。

The numbers in the interactive panels — the converged solution, the Jacobian, the forward and adjoint results, the gradients, the differences and the complex step — are computed live by the page’s own scripts as it loads; the values quoted in the text, the tables and the static figures were computed in advance from the same model code.

新增(选读):光滑特征边界模型
Added (optional): smooth characteristic-boundary model

总结:保留面循环,补全重构与边界求导

Summary: retain face loops, complete reconstruction and boundary differentiation

保留原网格、内部通量、守恒分发与内边界积分目标的形式;必要的改动是内外边界统一特征处理、目标在入流面上用给定的 u_in,以及二阶梯度、分段积分和完整导数链。一阶保留 Jacobi;二阶采用一阶 GS 预处理的精确算子求解。全部状态和几何导数均对应当前选定的离散。

Keep the original mesh, interior flux, conservative scatter and inner-boundary functional form. The necessary changes are characteristic treatment on both boundaries, the prescribed u_in in the objective on inflow faces, second-order gradients, segment integration and complete derivative paths. First order retains Jacobi; second order solves with exact operators and first-order GS preconditioning. All state and geometry derivatives match the selected discretisation.

这里没有激波、限制器或湍流。连续目标为零,离散目标和梯度就是网格误差,加密时实测总体在减小(一阶减得慢;格点页从第 0 层到第 1 层还先增大);离散伴随也实测趋向连续参考,只是在两类特征线的分界附近收敛得慢。页面区分这种教学验证与实际工程优化目标。接下来走向更复杂方程时,应逐项增加新物理与离散算子,并保留可独立核查的导数验证。

There are no shocks, limiters or turbulence here. The continuous objective is zero, so the discrete objective and gradient are mesh error, and under refinement they are measured decreasing overall (slowly at first order; on the node page they first grow from level 0 to level 1); the discrete adjoint is measured approaching the continuous reference, slowly near the interface between the two families of characteristics. This teaching test is distinguished from an engineering optimisation objective. More complex equations should add physics and discrete operators together with independently checkable derivative tests.

完整数值实现Complete numerical implementation
(function(root){'use strict';
const gamma=1.4,alpha=2,zeros=n=>Array(n).fill(0),dot=(a,b)=>a.reduce((s,x,i)=>s+x*b[i],0),norm=a=>Math.sqrt(dot(a,a));
const real={c:x=>x,add:(a,b)=>a+b,sub:(a,b)=>a-b,mul:(a,b)=>a*b,div:(a,b)=>a/b,pow:(a,p)=>a**p,sin:Math.sin,cos:Math.cos};
function tape(){const val=[],pa=[],pb=[],da=[],db=[],inputs=[];function node(v,a=-1,b=-1,x=0,y=0){const i=val.length;val.push(v);pa.push(a);pb.push(b);da.push(x);db.push(y);return i;}const op={c:x=>node(x),input:x=>{const i=node(x);inputs.push(i);return i;},add:(a,b)=>node(val[a]+val[b],a,b,1,1),sub:(a,b)=>node(val[a]-val[b],a,b,1,-1),mul:(a,b)=>node(val[a]*val[b],a,b,val[b],val[a]),div:(a,b)=>node(val[a]/val[b],a,b,1/val[b],-val[a]/val[b]**2),pow:(a,p)=>node(val[a]**p,a,-1,p*val[a]**(p-1)),sin:a=>node(Math.sin(val[a]),a,-1,Math.cos(val[a])),cos:a=>node(Math.cos(val[a]),a,-1,-Math.sin(val[a]))};
function forward(seed){const d=new Float64Array(val.length);inputs.forEach((i,k)=>d[i]=seed[k]||0);for(let i=0;i<val.length;i++){if(pa[i]>=0)d[i]+=da[i]*d[pa[i]];if(pb[i]>=0)d[i]+=db[i]*d[pb[i]];}return d;}
function reverse(outputs,seeds){const w=new Float64Array(val.length);outputs.forEach((i,k)=>w[i]+=seeds[k]);for(let i=val.length-1;i>=0;i--){if(pa[i]>=0)w[pa[i]]+=da[i]*w[i];if(pb[i]>=0)w[pb[i]]+=db[i]*w[i];}return inputs.map(i=>w[i]);}
return {op,val,forward,reverse};}
const complex={c:x=>[x,0],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]**2+b[1]**2;return [(a[0]*b[0]+a[1]*b[1])/d,(a[1]*b[0]-a[0]*b[1])/d];},pow:(a,p)=>{const r=Math.hypot(...a)**p,t=p*Math.atan2(a[1],a[0]);return [r*Math.cos(t),r*Math.sin(t)];},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])]};
function gmres(A,b,P=x=>x.slice(),tol=1e-11,max=b.length){const n=b.length,beta=norm(b);if(beta<=tol)return {x:zeros(n),residual:beta,history:[beta],states:[zeros(n)],converged:true};const V=[b.map(x=>x/beta)],Z=[],H=Array.from({length:max+1},()=>zeros(max)),cs=zeros(max),sn=zeros(max),g=zeros(max+1),history=[beta],estimates=[beta],states=[zeros(n)];g[0]=beta;let m=0;
 for(let j=0;j<max;j++){Z.push(P(V[j]));let w=A(Z[j]);for(let pass=0;pass<2;pass++)for(let i=0;i<=j;i++){const h=dot(V[i],w);H[i][j]+=h;w=w.map((x,k)=>x-h*V[i][k]);}H[j+1][j]=norm(w);const nextnorm=H[j+1][j];if(nextnorm>1e-16)V.push(w.map(x=>x/nextnorm));for(let i=0;i<j;i++){const a=H[i][j],b=H[i+1][j];H[i][j]=cs[i]*a+sn[i]*b;H[i+1][j]=-sn[i]*a+cs[i]*b;}const a=H[j][j],bb=H[j+1][j],r=Math.hypot(a,bb);cs[j]=r?a/r:1;sn[j]=r?bb/r:0;H[j][j]=r;H[j+1][j]=0;g[j+1]=-sn[j]*g[j];g[j]*=cs[j];m=j+1;estimates.push(Math.abs(g[j+1]));const yy=zeros(m);for(let ii=m-1;ii>=0;ii--){let a=g[ii];for(let kk=ii+1;kk<m;kk++)a-=H[ii][kk]*yy[kk];yy[ii]=a/H[ii][ii];}const xx=zeros(n);for(let ii=0;ii<m;ii++)for(let kk=0;kk<n;kk++)xx[kk]+=Z[ii][kk]*yy[ii];states.push(xx);history.push(norm(A(xx).map((v,i)=>v-b[i])));if(history.at(-1)<tol||nextnorm<=1e-16)break;}
 const y=zeros(m);for(let i=m-1;i>=0;i--){let a=g[i];for(let j=i+1;j<m;j++)a-=H[i][j]*y[j];y[i]=a/H[i][i];}const x=zeros(n);for(let i=0;i<m;i++)for(let j=0;j<n;j++)x[j]+=Z[i][j]*y[i];const residual=norm(A(x).map((z,i)=>z-b[i]));return {x,residual,history,estimates,states,converged:residual<=Math.max(tol*5,1e-13)};}
const api={real,complex,tape,gmres,dot,norm};if(typeof module!=='undefined')module.exports=api;else root.QMath=api;
})(typeof window!=='undefined'?window:globalThis);

/* The mesh of the advection-diffusion pages: the advection pages'
   asymmetric hybrid O-mesh, node for node and cell for cell, and its
   uniform subdivisions (the family the order study runs on). Shared by the
   cell-centred and the node-centred core. */
(function () {
  "use strict";
  const key = (a, b) => Math.min(a, b) + "-" + Math.max(a, b);

  /* ---- the base mesh: the advection pages' asymmetric hybrid O-mesh,
     node for node and cell for cell ---- */
  function baseMesh() {
    const NR = 3, NT = 8;
    const BODY = [0.10, -0.04], ROUT = 1.9, LAYER = 0.45;
    const rBody = th => 0.55 * (1 + 0.28 * Math.cos(th) + 0.12 * Math.sin(2 * th));
    const X = [], inner = [], outer = [];
    for (let k = 0; k < NT; k++) {
      const th = 2 * Math.PI * k / NT;
      inner.push([BODY[0] + rBody(th) * Math.cos(th), BODY[1] + rBody(th) * Math.sin(th)]);
      outer.push([ROUT * Math.cos(th), ROUT * Math.sin(th)]);
    }
    const mid = inner.map((p, k) => [(1 - LAYER) * p[0] + LAYER * outer[k][0],
      (1 - LAYER) * p[1] + LAYER * outer[k][1]]);
    inner.forEach(p => X.push(p));
    mid.forEach(p => X.push(p));
    outer.forEach(p => X.push(p));
    const nid = (r, k) => r * NT + ((k % NT) + NT) % NT + 1;
    const C2N = [];
    for (let r = 0; r < NR - 1; r++) {
      for (let k = 0; k < NT; k++) {
        const a = nid(r, k), b = nid(r, k + 1), c = nid(r + 1, k + 1), d = nid(r + 1, k);
        if (r === 0) C2N.push([a, d, c, b]);            // quadrilateral, counter-clockwise
        else { C2N.push([a, d, c]); C2N.push([a, c, b]); }
      }
    }
    const bnd = {};
    for (let k = 0; k < NT; k++) {
      bnd[key(nid(0, k), nid(0, k + 1))] = "wall";
      bnd[key(nid(NR - 1, k), nid(NR - 1, k + 1))] = "far";
    }
    return { X, C2N, bnd, BODY };
  }

  /* ---- uniform subdivision: every triangle into four by its edge
     midpoints, every quadrilateral into four through its centre. The
     boundary is polygonal, so the domain is the same at every level and the
     meshes form one self-similar family. ---- */
  function subdivide(mesh) {
    const X = mesh.X.map(p => p.slice());
    const midOf = {};
    const mp = (a, b) => {
      const k = key(a, b);
      if (!midOf[k]) {
        X.push([(X[a - 1][0] + X[b - 1][0]) / 2, (X[a - 1][1] + X[b - 1][1]) / 2]);
        midOf[k] = X.length;
      }
      return midOf[k];
    };
    const C2N = [];
    mesh.C2N.forEach(v => {
      if (v.length === 3) {
        const [a, b, c] = v, ab = mp(a, b), bc = mp(b, c), ca = mp(c, a);
        C2N.push([a, ab, ca], [ab, b, bc], [ca, bc, c], [ab, bc, ca]);
      } else {
        const [a, b, c, d] = v, ab = mp(a, b), bc = mp(b, c), cd = mp(c, d), da = mp(d, a);
        X.push([0, 1].map(t => (X[a - 1][t] + X[b - 1][t] + X[c - 1][t] + X[d - 1][t]) / 4));
        const m = X.length;
        C2N.push([a, ab, m, da], [ab, b, bc, m], [m, bc, c, cd], [da, m, cd, d]);
      }
    });
    const bnd = {};
    Object.keys(mesh.bnd).forEach(k => {
      const [a, b] = k.split("-").map(Number), m = midOf[k];
      bnd[key(a, m)] = mesh.bnd[k];
      bnd[key(m, b)] = mesh.bnd[k];
    });
    return { X, C2N, bnd };
  }

  window.MESHAD = { baseMesh, subdivide, key };
})();

/* Smooth scalar convection extension. Original first-order cores remain untouched.
   The objective L_h = sum over inner-boundary quadrature points of (w_q/2) u_b^2 e.n uses the BOUNDARY state u_b = u+:
   the prescribed inflow data u_in on inflow faces and the interior trace (u+ = u-) on outflow faces, as the continuous
   derivation of Appendix B does. trace:'interior' keeps the interior trace u- on inflow faces too (the adjoint-inconsistent
   variant, kept only to measure the contrast). */
(function(root){'use strict';const Q=typeof module!=='undefined'?require('../../q1d/reconstruction/math'):root.QMath,{real,complex,tape,norm,dot,gmres}=Q,zeros=n=>Array(n).fill(0);
function model(mesh,{node=false,order=2,trace='boundary'}={}){const cells=mesh.C2N.map(c=>c.map(i=>i-1)),nx=mesh.X.length,n=node?nx:cells.length,params=[...mesh.X.flat(),1],edges=[],emap=new Map();cells.forEach((c,ci)=>c.forEach((a,k)=>{const b=c[(k+1)%c.length],key=[Math.min(a,b)+1,Math.max(a,b)+1].join('-');let e=emap.get(key);if(!e){e={a,b,L:ci,R:-1,bc:mesh.bnd[key]||'interior',parts:[]};emap.set(key,e);edges.push(e);}else e.R=ci;e.parts.push({ci,sign:a===e.a?1:-1});}));const neighbors=Array.from({length:n},()=>new Set());edges.forEach(e=>{const L=node?e.a:e.L,R=node?e.b:e.R;if(R>=0){neighbors[L].add(R);neighbors[R].add(L);}});
function evaluate(o,u,p,space=order){const {c,add,sub,mul,div,pow}=o,sum=a=>a.reduce(add,c(0)),scale=(a,s)=>a.map(v=>mul(v,s)),plus=(a,b)=>a.map((v,i)=>add(v,b[i])),minus=(a,b)=>a.map((v,i)=>sub(v,b[i])),mid=(a,b)=>scale(plus(a,b),c(.5)),cross=(a,b)=>sub(mul(a[0],b[1]),mul(a[1],b[0])),dp=(a,b)=>sum(a.map((v,i)=>mul(v,b[i]))),X=Array.from({length:nx},(_,i)=>p.slice(2*i,2*i+2)),cent=cells.map(cell=>[0,1].map(k=>div(sum(cell.map(i=>X[i][k])),c(cell.length)))),points=node?X:cent;
const exact=x=>add(p[2*nx],mul(c(.2),o.sin(mul(c(Math.PI),sub(x[1],mul(c(.5),x[0]))))));
const gradients=[],lsq=[];for(let i=0;i<n;i++){let a=c(0),b=c(0),d=c(0),rx=c(0),ry=c(0);for(const j of neighbors[i]){const v=minus(points[j],points[i]),w=div(c(1),dp(v,v)),du=sub(u[j],u[i]);a=add(a,mul(w,mul(v[0],v[0])));b=add(b,mul(w,mul(v[0],v[1])));d=add(d,mul(w,mul(v[1],v[1])));rx=add(rx,mul(w,mul(v[0],du)));ry=add(ry,mul(w,mul(v[1],du)));}const det=sub(mul(a,d),mul(b,b));gradients.push(space===1?[c(0),c(0)]:[div(sub(mul(d,rx),mul(b,ry)),det),div(sub(mul(a,ry),mul(b,rx)),det)]);lsq.push([a,b,d,det]);}
const rec=(i,x)=>add(u[i],dp(gradients[i],minus(x,points[i]))),R=Array.from({length:n},()=>c(0)),vol=Array.from({length:n},()=>c(0)),integral=Array.from({length:n},()=>c(0)),sources=Array.from({length:n},()=>c(0)),faces=[];let J=c(0);
function triangle(i,a,b,d){const ar=mul(c(.5),cross(minus(b,a),minus(d,a)));vol[i]=add(vol[i],ar);const bary=[[1/3,1/3,1/3,-27/48],[.6,.2,.2,25/48],[.2,.6,.2,25/48],[.2,.2,.6,25/48]];for(const [r,s,t,w]of bary){const xx=plus(plus(scale(a,c(r)),scale(b,c(s))),scale(d,c(t))),ww=mul(c(w),ar);integral[i]=add(integral[i],mul(ww,exact(xx)));}}
cells.forEach((cell,ci)=>{if(!node){cell.forEach((a,k)=>triangle(ci,cent[ci],X[a],X[cell[(k+1)%cell.length]]));}else cell.forEach((a,k)=>{const b=cell[(k+1)%cell.length],prev=cell[(k+cell.length-1)%cell.length],m1=mid(X[a],X[b]),m0=mid(X[prev],X[a]);triangle(a,X[a],m1,cent[ci]);triangle(a,X[a],cent[ci],m0);});});
function face(L,RR,bc,a,b,sign=1,aggregate=null){const diff=minus(b,a),normal=aggregate||[mul(c(sign),diff[1]),mul(c(-sign),diff[0])],len=pow(dp(normal,normal),.5),vn=add(normal[0],mul(c(.5),normal[1])),xf=mid(a,b);const baseN=normal.map(v=>o===real?v:null);let isIn;if(o===real)isIn=(baseN[0]+.5*baseN[1])<0;else{const val=v=>o===complex?v[0]:activeTape.val[v];isIn=val(vn)<0;} // branch frozen by the real geometry at evaluation
let flux=c(0),obj=c(0),samples=[];const zs=space===1?[0]:[-1/Math.sqrt(3),1/Math.sqrt(3)];for(const z of zs){const xx=plus(xf,scale(diff,c(.5*z))),ul=rec(L,xx),ur=RR>=0?rec(RR,xx):isIn?exact(xx):ul;const h=sub(mul(c(.25),mul(vn,add(mul(ul,ul),mul(ur,ur)))),mul(c(.8),mul(len,sub(ur,ul))));flux=add(flux,mul(c(1/zs.length),h));if(bc==='wall'){const ub=trace==='interior'?ul:ur;obj=add(obj,mul(c(.5/zs.length/Math.sqrt(1.25)),mul(add(normal[0],mul(c(.5),normal[1])),mul(ub,ub))));}samples.push({x:xx,ul,ur,h});}R[L]=add(R[L],flux);if(RR>=0)R[RR]=sub(R[RR],flux);J=add(J,obj);faces.push({L,R:RR,bc,a,b,normal,mid:xf,len,vn,flux,samples});}
for(const e of edges){if(!node)face(e.L,e.R,e.bc,X[e.a],X[e.b]);else{const midp=mid(X[e.a],X[e.b]);if(space===1){let nv=[c(0),c(0)];for(const part of e.parts){const v=minus(cent[part.ci],midp);nv=plus(nv,[mul(c(part.sign),v[1]),mul(c(-part.sign),v[0])]);}face(e.a,e.b,'interior',midp,midp,1,nv);}else for(const part of e.parts)face(e.a,e.b,'interior',midp,cent[part.ci],part.sign);if(e.R<0){face(e.a,-1,e.bc,X[e.a],midp);face(e.b,-1,e.bc,midp,X[e.b]);}}}
for(let i=0;i<n;i++)R[i]=sub(R[i],sources[i]);return {R,J,faces,gradients,lsq,points,vol,sources,truth:node?points.map(exact):integral.map((v,i)=>div(v,vol[i]))};}
let activeTape;function linearize(u,p=params){const t=tape();activeTape=t;const uu=u.map(t.op.input),pp=p.map(t.op.input),out=evaluate(t.op,uu,pp),rv=a=>a.map(i=>t.val[i]);return {R:rv(out.R),J:t.val[out.J],jvp:(v,dp=zeros(p.length))=>{const d=t.forward([...v,...dp]);return {R:out.R.map(i=>d[i]),J:d[out.J],faces:out.faces.map(f=>d[f.flux]),gradients:out.gradients.map(g=>g.map(i=>d[i]))};},vjp:(w,j=0)=>{const b=t.reverse([...out.R,out.J],[...w,j]);return {u:b.slice(0,n),p:b.slice(n)};},faceVjp:(i,w)=>{const b=t.reverse([out.faces[i].flux],[w]);return {u:b.slice(0,n),p:b.slice(n)};}};}
function preconditioner(u,p=params,trans=false){const E=evaluate(real,u,p,1),rows=Array.from({length:n},()=>new Map());const put=(i,j,v)=>rows[i].set(j,(rows[i].get(j)||0)+v);for(const f of E.faces){const ul=u[f.L],ur=f.R>=0?u[f.R]:f.vn<0?f.samples[0].ur:ul,cl=.5*f.vn*ul+.8*f.len,cr=.5*f.vn*ur-.8*f.len,aa=f.R<0&&f.vn>=0?cl+cr:cl;put(f.L,f.L,aa);if(f.R>=0){put(f.L,f.R,cr);put(f.R,f.L,-aa);put(f.R,f.R,-cr);}}const P=trans?Array.from({length:n},()=>new Map()):rows;if(trans)rows.forEach((row,i)=>row.forEach((v,j)=>P[j].set(i,v)));return rhs=>{let x=zeros(n);for(let k=0;k<3;k++)for(let it=0;it<n;it++){const i=trans?n-1-it:it;let v=rhs[i];for(const [j,a]of P[i])if(j!==i)v-=a*x[j];x[i]=v/P[i].get(i);}return x;};}
function diagonal(u,p=params){const e=evaluate(real,u,p,1),d=zeros(n);for(const f of e.faces){const ul=u[f.L],ur=f.samples[0].ur,cl=.5*f.vn*ul+.8*f.len,cr=.5*f.vn*ur-.8*f.len;d[f.L]+=f.R<0&&f.vn>=0?cl+cr:cl;if(f.R>=0)d[f.R]-=cr;}return d;}
function linear(L,rhs,u,p=params,trans=false){const op=v=>trans?L.vjp(v).u:L.jvp(v).R,pre=preconditioner(u,p,trans);if(order===1){const D=diagonal(u,p),x=zeros(n),history=[],states=[];for(let it=0;it<8000;it++){const ax=op(x),r=rhs.map((v,i)=>v-ax[i]);history.push(norm(r));states.push(x.slice());if(history.at(-1)<2e-14)return {x,history,states,residual:history.at(-1)};x.forEach((v,i)=>x[i]=v+r[i]/D[i]);}throw Error('Linear Jacobi failed');}let x=zeros(n),history=[norm(rhs)],states=[x.slice()];for(let cycle=0;cycle<15;cycle++){const ax=op(x),r=rhs.map((v,i)=>v-ax[i]),g=gmres(op,r,pre,2e-14,Math.min(n,60));states.push(...g.states.slice(1).map(z=>z.map((v,i)=>v+x[i])));x=x.map((v,i)=>v+g.x[i]);history.push(...g.history.slice(1));const res=norm(op(x).map((v,i)=>v-rhs[i]));if(res<1e-13)return {x,history,states,residual:res};}throw Error('Linear solve did not converge '+history.at(-1));}
function solve(p=params,start){if(order===1){let u=start?start.slice():Array(n).fill(p[2*nx]),history=[],states=[];for(let it=0;it<10000;it++){const R=evaluate(real,u,p).R;history.push(norm(R));states.push(u.slice());if(history.at(-1)<8e-14)return {u,history,states,residual:history.at(-1)};const D=diagonal(u,p);u=u.map((v,i)=>v-R[i]/D[i]);if(!u.every(v=>v>0&&Number.isFinite(v)))throw Error('Non-positive Jacobi update');}throw Error('Primal Jacobi failed');}let u=start?start.slice():Array(n).fill(p[2*nx]),history=[],states=[];for(let it=0;it<45;it++){const L=linearize(u,p),r=norm(L.R);history.push(r);states.push(u.slice());if(r<2e-13)return {u,history,states,residual:r};const d=linear(L,L.R.map(v=>-v),u,p).x;let step=1,un;while(step>1e-8){un=u.map((v,i)=>v+step*d[i]);const ev=evaluate(real,un,p),valid=un.every(v=>v>0)&&ev.faces.every(f=>f.samples.every(s=>s.ul>0&&s.ur>0));if(valid&&norm(ev.R)<r*(1-1e-4*step))break;step*=.5;}if(step<=1e-8)throw Error('No positive smooth update');u=un;}throw Error('Nonlinear solve failed');}
function sensitivities(sol,p=params){const L=linearize(sol.u,p),g=L.vjp(zeros(n),1),s=linear(L,g.u.map(v=>-v),sol.u,p,true);return {...s,gradient:L.vjp(s.x,1).p,L};}
function cs(sol,k,p=params,h=1e-25){const L=linearize(sol.u,p),u=sol.u.map(v=>[v,0]),pp=p.map((v,i)=>[v,k===i?h:0]);for(let i=0;i<3;i++){const R=evaluate(complex,u,pp).R.map(v=>-v[1]/h);if(norm(R)<1e-13)break;const d=linear(L,R,sol.u,p).x;u.forEach((v,i)=>v[1]+=h*d[i]);}const out=evaluate(complex,u,pp);return {gradient:out.J[1]/h,residual:norm(out.R.map(v=>v[1]/h))};}
return {n,nx,node,order,mesh,params,neighbors,evaluate:(u,p=params)=>evaluate(real,u,p),linearize,solve,sensitivities,linear,cs,preconditioner};}
const api={model,...Q};if(typeof module!=='undefined')module.exports=api;else root.Transport2=api;
})(typeof window!=='undefined'?window:globalThis);
附录 AAppendix A

附录 A:连续伴随在简单算例中的验证与对比

Appendix A: verifying and comparing continuous adjoints on simple cases

本项目以离散伴随为主线:从可逐句检查的小模型,逐步走向工业级非结构网格 RANS 的完整离散伴随实现。连续伴随在简单算例中提供独立验证与对比,不是学习后续章节的前提。

This project follows discrete adjoints, from small models whose operations can be inspected statement by statement toward a complete industrial unstructured-grid RANS discrete adjoint. Continuous adjoints provide independent checks and comparisons for simple cases; they are not a prerequisite for later chapters.

这里的“走向”是项目目标,并不表示当前已实现 RANS。可以为选定的连续 RANS 模型推导形式伴随;但不能据此假定它严格对应工业求解器中的湍流闭合、壁面处理、限幅、边界与网格算法。最终需要验证的是实际离散残差及目标的导数。

RANS is the project destination, not a capability already implemented here. A formal continuous adjoint can be derived for a specified continuous RANS model; this does not establish strict equivalence to an industrial solver’s turbulence closure, wall treatment, limiters, boundary and mesh algorithms. The derivatives to verify are those of the implemented discrete residual and objective.

连续 PDE → 线性化与分部积分 → 连续边界条件 → 独立参考场 λ

Continuous PDE → linearize and integrate by parts → dual boundary conditions → independent λ

离散残差 Rh → 精确 Jacobian → 转置求解 → 守恒行上的 −ψh

Discrete residual Rh → exact Jacobian → transpose solve → −ψh on conservation rows

图 A.1 · 两条独立计算路径,在统一的目标、边界与误差范数下比较Figure A.1 · Two independent paths compared with matched objectives, boundaries and norms

A.1 完整推导与适定性

A.1 Full derivation and well-posedness

1. 符号、积分测度与三类不同的检查

将定常 PDE 写为 R(u,D)=0R(u,D)=0,目标泛函记为 JJ,采用拉格朗日泛函

ℒ=J−∫ΩλTR dΩ.\mathcal L=J-\int_\Omega\lambda^T R\,d\Omega.

页面求解的是 AhTψh=−ghA_h^T\psi_h=-g_h。这里的连续乘子采用相反符号:在守恒方程上有 λh=−ψh\lambda_h=-\psi_h。这些残差已经是控制体积分后的守恒平衡,不需要再除以控制体的体积或面积。被代数边界约束替换的行对应约束乘子,其数值不是连续场的点值。

三类检查分别回答不同的问题:

  1. 转置/点积测试检查 AhTA_h^T 的实现。
  2. 对同一离散问题做有限差分,检查 dJh/dD\mathrm dJ_h/\mathrm dD。
  3. 将独立求得的连续伴随与一系列加密网格上的离散伴随比较,检查指定范数下的一致性。比较时必须匹配 PDE、边界条件、目标泛函、计算区域与目标数据。

有限组网格上误差下降是数值证据,不是收敛定理。即使积分范数下的误差下降,边界层或特征跳跃仍可能阻止最大范数下的收敛。

2. 适定性与伴随一致性不是同一件事

出现差异提供了诊断机会,但差异本身不能证明问题不适定。应先明确状态空间与边界迹空间、允许的数据、目标泛函以及范数。Hadamard 意义下的适定性要求解存在、唯一且连续依赖于数据。正确推导的连续伴随可以是适定的,而离散边界闭合却不一致;一个边界数据与目标泛函不匹配的适定方程,仍然是错误的伴随。约束乘子、有限正则性和参考解误差也会造成表观差异。

线性化、零空间与稳定性

把原始边界条件的齐次线性化约束纳入算子 𝒜:X→Y𝒜:X\to Y 的定义域 XX。如果该算子是适当空间之间的有界同构,则其 Banach 伴随也是相应对偶空间之间的有界同构。这给出一种充分的局部分析框架,并不是写出形式微分表达式就能得到的结论。对于无界 PDE 算子,必须先明确其定义域及图范数,或者采用适当的弱形式。对于非线性原始问题,仅有存在唯一性还不能保证解映射可微;需要在所选解支上检查局部光滑性及线性化可逆性。

对于 𝒜∗λ=g𝒜^*\lambda=g,原始算子的零模态给出必要的兼容性条件:

𝒜v=0⟹g[v]=⟨λ,𝒜v⟩=0.𝒜v=0\quad\Longrightarrow\quad g[v]=\langle\lambda,𝒜v\rangle=0.

若 ker⁡ 𝒜∗\ker\,𝒜^* 含有非零元素,则伴随一旦有解就不唯一。仅有与原始零空间正交的条件,并不能构成一般的无穷维存在性定理:还需要考虑值域闭性和稳定性。对齐次伴随边界数据,所需估计具有以下形式:

∥λ∥Y∗≤C∥g∥X∗\|\lambda\|_{Y^*}\le C\|g\|_{X^*}

如果边界数据非齐次,还应加上相应的边界迹范数。每个网格尺度下逆算子有界也不够:在正确加权的离散范数下,稳定界必须与网格尺度无关。不考虑积分残差和控制体权重的原始欧氏条件数,本身不能作为连续问题的诊断结论。

一个可显式求解的对流例子

取 c>0c>0,固定原始入口值,目标变分为 ∫01g v dx+b v(1)\int_0^1 g\,v\,\mathrm dx+b\,v(1)。对原始线性化 c v′c\,v' 使用 Green 恒等式,得到

−cλ′=g,cλ(1)=b,λ(x)=bc+1c∫x1g(s) ds.-c\lambda'=g,\qquad c\lambda(1)=b,\qquad \lambda(x)=\frac{b}{c}+\frac1c\int_x^1g(s)\,ds.
  • 缺少出口数据: 解可以加上任意常数,因此不唯一。
  • 额外照搬原始入口条件 λ(0)=0\lambda(0)=0: 存在性要求 b+∫g=0b+\int g=0。当 g=1, b=0g=1,\ b=0 时,正确解是 (1−x)/c(1-x)/c;额外入口条件使问题无解。
  • 失去一致稳定性: 有 ∥λ∥∞≤(∣b∣+∥g∥1)/c\|\lambda\|_\infty\le(|b|+\|g\|_1)/c。取 g=1, b=0g=1,\ b=0 就达到 1/c1/c 的放大倍数。每个固定的 c>0c>0 都可以对应适定问题,但对于这类数据,不能得到在 c→0c\to 0 时仍统一有效的界。速度为零时,原始算子退化。

在原二维物体算例中,法向指向流体计算域的外部,所以迎风物面是流体域的原始出流边界。一条已经携带 u=1u=1 的特征线,不可能在这里同时满足零物理通量。首先失败的是所假设的光滑正值原始问题,而不只是某个已经建立的连续伴随的离散逼近。在假定的壁面状态 u=0u=0 处,

δ(12u2β⋅n)=u(β⋅n)v=0\delta(\tfrac12u^2\beta\cdot n)=u(\beta\cdot n)v=0

对任意变分 vv 都成立:通量约束丢失了一阶约束信息,特征速度也同时退化。把壁面条件照抄到形式伴随上,不能恢复有效的光滑解支。弱解/熵解表述需要另行分析边界迹与可微性。

对于特征边界对照,每条内部射线都会在有限路程内到达出流边界。边界数据几乎处处确定内部场;切线或角点引出的跳跃可以在允许非光滑解的空间中处理,本身不意味着不适定。特征边界附近的比值 bu/(a⋅n)b_u/(a\cdot n) 需要兼容性分析:在这里的物面目标中,分子与分母具有可相消的法向因子,不能只看分母就断言伴随发散。

其余算例说明了什么

对流扩散。 正扩散系数提供椭圆主部,但这不是任意低阶项与 Robin 边界系数下的普遍可逆性证明。仍需检查齐次零空间,以及所用能量估计或最大值原理的假设。独立计算支持当前参数组,没有证明对所有参数都成立的定理。把目标所要求的壁面伴随值 1 改成 0,可能得到另一个适定问题,但会给出错误的目标灵敏度。在远场同时规定状态值和法向导数,一般会过约束。把被替换方程行的乘子当成壁面场值,则是另一类代数含义错误。

拟一维 Euler。 对所述正向亚声速分支,特征速度 u−c, u, u+cu-c,\ u,\ u+c 解释了原始入口两个条件、出口一个条件,以及伴随的相反条件数。条件数目正确是必要条件,不是充分条件:特征传播与边界约束共同组成的边值算子还必须可逆。声速点处 BB 奇异,当前使用 (BT)−1(B^T)^{-1} 的表示失效,需要喉部兼容性/正则性分析。这不意味着所有跨声速伴随都不存在。激波还需要考虑激波位置变分及相应内部条件。这些现象超出当前数值证据的范围,可参考 Giles 与 Pierce 的连续喷管伴随分析。强、弱边界处理对数值伴随的不同影响,见 Duivesteijn 等人的研究。

因此,教学顺序应当是:先明确连续原始问题及所选解支;推导伴随,包括其边界定义域;分析存在性、唯一性和稳定性;随后才检查对该问题的一致、稳定逼近。反例应明确保留其失败的前提,不能悄悄替换模型后宣布验证成功。

3. 非线性标量对流:只有形式伴随还不够

实际通量是非线性的:

R(u)=∇⋅(12u2β),β=(1,12),a=uβ.R(u)=\nabla\cdot\bigl(\tfrac12u^2\beta\bigr),\qquad \beta=(1,\tfrac12),\quad a=u\beta.

对状态变分 v=δuv=\delta u,有

Ruv=∇⋅(av),∫ΩλRuv=−∫Ω(a⋅∇λ)v+∫∂Ωλ(a⋅n)v.R_u v=\nabla\cdot(av),\qquad \int_\Omega\lambda R_uv =-\int_\Omega(a\cdot\nabla\lambda)v +\int_{\partial\Omega}\lambda(a\cdot n)v.

对于 J=∫Ωj(u)+∫∂Ωb(u)J=\int_\Omega j(u)+\int_{\partial\Omega}b(u),驻值条件在原始边界迹可以自由变化的部分给出

−a⋅∇λ=ju,bu−λ(a⋅n)=0-a\cdot\nabla\lambda=j_u,\qquad b_u-\lambda(a\cdot n)=0

在给定的入流边界迹上,v=0v=0,无需再给伴随数据。因此,伴随在原始出流边界接收数据;离开特征边界点时,有 λ=bu/(a⋅n)\lambda=b_u/(a\cdot n)。

原闭合零通量物面并不是光滑对流问题的壁面

原二维页面直接令物面数值通量为零,与内侧状态无关。但所述 PDE 的光滑边界迹具有物理通量

12u2β⋅n.\tfrac12u^2\beta\cdot n.

固定向量 β\beta 不可能沿闭合多边形处处相切。在所有 β⋅n≠0\beta\cdot n\ne 0 的位置,零物理通量要求 u=0u=0。这会在物面的原始出流部分额外规定零值。在正值、无源分支中,uu 沿每条直线特征保持不变。从远场以 u=1u=1 进入并最终到达迎风物面的特征,不可能在那里同时满足 u=0u=0。在 u=0u=0 处,特征速度退化,因此不能靠把边界条件除以 uu 来修复推导。

所以,当前零通量数值闭合不能提供上述连续伴随比较所需的光滑正值经典边值问题。这并不证明任何弱解或测度值解表述都不可能存在;这些表述需要明确的熵/边界迹规范和相应灵敏度分析,而当前页面没有建立这些理论。有限维残差、转置和离散梯度仍然有明确的定义。

明确区分的可计算对照:特征边界

仅在新增对照中,对内、外边界都按特征方向处理:在 β⋅n<0\beta\cdot n<0 处规定 u=1u=1,在 β⋅n>0\beta\cdot n>0 处采用内侧物理通量。区域、内部数值通量及其 ε=0.8\varepsilon=0.8 保持与原页面相同。精确原始解为 u=1u=1。

保留类似阻力的目标泛函

J=∫Γw12u2(e⋅n) ds,e=β/∣β∣,J=\int_{\Gamma_w}\tfrac12u^2(e\cdot n)\,ds, \qquad e=\beta/|\beta|,

但在给定入流处,目标使用已给定的边界迹,而不是一个自由的内侧值。这种目标取值方式的改变属于对照的一部分,不会暗中应用到原算例。连续伴随为

−β⋅∇λ=0,λ=1/∣β∣ (物面出流处),λ=0 (外边界出流处).-\beta\cdot\nabla\lambda=0,\quad \lambda=1/|\beta|\ \text{(物面出流处)},\quad \lambda=0\ \text{(外边界出流处)}.

从任一内部点沿 +β+\beta 方向追踪射线,直到第一个边界交点:若碰到物面则取 1/∣β∣1/|\beta|,若碰到外边界则取零。这是精确的特征线计算,不是离散转置求解。物面切线或角点引出的射线上存在跳跃。比较采用体积加权 L2 误差;格点范数排除边界节点。这里不宣称光滑意义下的最大范数收敛,也不以这个对照证明原零通量壁面的连续一致性。

1. Signs, measures and three distinct checks

Write the steady PDE as R(u,D)=0R(u,D)=0, the functional as JJ, and use

ℒ=J−∫ΩλTR dΩ.\mathcal L=J-\int_\Omega\lambda^T R\,d\Omega.

The pages solve AhTψh=−ghA_h^T\psi_h=-g_h. Our continuous multiplier has the opposite sign: λh=−ψh\lambda_h=-\psi_h on conservation equations. Those residuals are integrated balances, so there is no additional division by control-volume measure. Rows replaced by algebraic boundary constraints carry constraint multipliers; their values are not point samples of the continuous field.

Three tests answer different questions:

  1. A transpose/dot test checks the implementation of AhTA_h^T.
  2. Finite differences of the same discrete problem check dJh/dD\mathrm dJ_h/\mathrm dD.
  3. Comparing an independently solved continuous dual with a sequence of refined discrete duals tests consistency in a stated norm. It requires matching PDE, boundary conditions, functional, domain, and target data.

A decreasing error on finitely many meshes is numerical evidence, not a theorem of convergence. Boundary layers or characteristic jumps can prevent maximum-norm convergence even when an integral norm decreases.

2. Well-posedness is not the same as adjoint consistency

A discrepancy is a diagnostic opportunity, not by itself proof of ill-posedness. Specify the state and trace spaces, admissible data, functional, and norms first. Hadamard well-posedness requires existence, uniqueness and continuous dependence. A correctly derived continuous dual can be well posed while a discrete boundary closure is inconsistent; a well-posed equation with the wrong objective boundary data is simply the wrong dual. Constraint multipliers, limited regularity and finite reference error are other sources of apparent disagreement.

Linearization, kernels and stability

Include the homogeneous linearized primal boundary constraints in the domain XX of the operator 𝒜:X→Y𝒜:X\to Y. If this is a bounded isomorphism between suitable spaces, its Banach adjoint is also a bounded isomorphism between the dual spaces. This is a sufficient local framework, not a conclusion obtained merely by writing a formal differential expression. An unbounded PDE operator must first be given a domain/graph norm or a suitable weak realization. For a nonlinear primal, existence and uniqueness alone do not guarantee a differentiable solution map; the local smoothness and invertibility hypotheses must be checked on the chosen branch.

For 𝒜∗λ=g𝒜^*\lambda=g, a primal null mode implies the necessary compatibility condition

𝒜v=0⟹g[v]=⟨λ,𝒜v⟩=0.𝒜v=0\quad\Longrightarrow\quad g[v]=\langle\lambda,𝒜v\rangle=0.

A nonzero element of ker⁡ 𝒜∗\ker\,𝒜^* makes a solvable dual nonunique. Orthogonality to the primal kernel alone is not a general infinite-dimensional existence theorem: closed range and stability still matter. The required estimate has the form

∥λ∥Y∗≤C∥g∥X∗\|\lambda\|_{Y^*}\le C\|g\|_{X^*}

for homogeneous dual boundary data; inhomogeneous boundary data contribute their appropriate trace norm. A bounded inverse at every mesh size is not enough either: the stability bound must be uniform in the properly weighted discrete norms. Raw Euclidean condition numbers without accounting for integrated residuals and control-volume weights are not themselves a continuum diagnosis.

An explicit transport example

For c>0c>0, fixed primal inflow and objective variation ∫01g v dx+b v(1)\int_0^1 g\,v\,\mathrm dx+b\,v(1), the Green identity for the primal linearization c v′c\,v' gives

−cλ′=g,cλ(1)=b,λ(x)=bc+1c∫x1g(s) ds.-c\lambda'=g,\qquad c\lambda(1)=b,\qquad \lambda(x)=\frac{b}{c}+\frac1c\int_x^1g(s)\,ds.
  • Missing outlet data: an arbitrary additive constant remains, so uniqueness fails.
  • An extra copied primal inlet datum λ(0)=0\lambda(0)=0: existence requires b+∫g=0b+\int g=0. With g=1, b=0g=1,\ b=0, the correct solution is (1−x)/c(1-x)/c; the extra inlet condition makes the problem unsolvable.
  • Loss of uniform stability: ∥λ∥∞≤(∣b∣+∥g∥1)/c\|\lambda\|_\infty\le(|b|+\|g\|_1)/c. Taking g=1, b=0g=1,\ b=0 attains the 1/c1/c amplification. Each fixed c>0c>0 is well posed, but there is no bound uniform as c→0c\to 0 for this data class. At zero speed the original operator degenerates.

In the original 2-D body example, normals point out of the fluid domain: the upwind body is therefore primal outflow. A characteristic already carrying u=1u=1 cannot satisfy zero physical flux there. This is first a failure of the assumed smooth positive primal problem, not merely a poor discrete approximation to an otherwise established dual. At a putative wall state u=0u=0,

δ(12u2β⋅n)=u(β⋅n)v=0\delta(\tfrac12u^2\beta\cdot n)=u(\beta\cdot n)v=0

for every variation vv: the flux constraint loses its first-order information while the characteristic speed degenerates. Copying the wall condition onto a formal dual cannot restore a valid smooth solution branch. Weak/entropy formulations would need separate trace and differentiability analysis.

For the characteristic-boundary contrast, every interior ray reaches outflow in finite distance. Boundary data determine the field almost everywhere; tangency/corner jumps are compatible with a nonsmooth solution space and do not by themselves imply ill-posedness. The ratio bu/(a⋅n)b_u/(a\cdot n) requires compatibility at characteristic boundaries: in this particular body objective its numerator and denominator contain a cancelling normal factor. One must not infer blow-up from the denominator alone.

What the other cases demonstrate

Advection–diffusion. Positive diffusion provides an elliptic principal part, not a universal invertibility proof for arbitrary lower-order and Robin coefficients. Homogeneous kernels and the assumptions of applicable energy or maximum-principle estimates still need attention. The independent solves support this parameter set; they do not prove a theorem for all parameters. Setting the wall dual to zero instead of the objective-required one can define a different well-posed problem, but yields the wrong functional sensitivity. Imposing both state and normal derivative at the far field is generally an overconstraint. Treating a replaced-row multiplier as a wall field sample is yet another, algebraic interpretation error.

Quasi-1-D Euler. The speeds u−c, u, u+cu-c,\ u,\ u+c explain the two inlet/one outlet primal conditions and reversed dual counts for the stated positive subsonic branch. Counts are necessary, not sufficient: propagation and the boundary constraints together must yield an invertible boundary-value operator. At a sonic point BB is singular, so the current representation using (BT)−1(B^T)^{-1} fails; throat compatibility/regularity analysis is required. This does not imply that all transonic adjoints fail to exist. Shocks require shock-position variations and internal conditions. These phenomena are outside the current numerical evidence; see the continuous nozzle analysis of Giles and Pierce. The distinct effects of strong/weak boundary treatment on numerical duals are discussed by Duivesteijn et al..

The teaching sequence is therefore: identify the continuous primal and chosen branch; derive the dual including its boundary domain; examine existence, uniqueness and stability; only then test consistent, stable approximation of that problem. A counterexample should retain its failed premise visibly, rather than silently substituting another model and declaring success.

3. Nonlinear scalar transport: a formal adjoint is not enough

The actual flux is nonlinear:

R(u)=∇⋅(12u2β),β=(1,12),a=uβ.R(u)=\nabla\cdot\bigl(\tfrac12u^2\beta\bigr),\qquad \beta=(1,\tfrac12),\quad a=u\beta.

For a state variation v=δuv=\delta u,

Ruv=∇⋅(av),∫ΩλRuv=−∫Ω(a⋅∇λ)v+∫∂Ωλ(a⋅n)v.R_u v=\nabla\cdot(av),\qquad \int_\Omega\lambda R_uv =-\int_\Omega(a\cdot\nabla\lambda)v +\int_{\partial\Omega}\lambda(a\cdot n)v.

For J=∫Ωj(u)+∫∂Ωb(u)J=\int_\Omega j(u)+\int_{\partial\Omega}b(u), stationarity gives

−a⋅∇λ=ju,bu−λ(a⋅n)=0-a\cdot\nabla\lambda=j_u,\qquad b_u-\lambda(a\cdot n)=0

on portions where the primal trace is free. On a prescribed inflow trace v=0v=0, no adjoint datum is prescribed. Thus the adjoint receives its data at primal outflow, with λ=bu/(a⋅n)\lambda=b_u/(a\cdot n), away from characteristic points.

The original closed zero-flux body is not a smooth transport wall

The original two-dimensional pages set the numerical body flux to zero independently of the interior state. But a smooth trace of the stated PDE has physical flux

12u2β⋅n.\tfrac12u^2\beta\cdot n.

The fixed vector β\beta is not tangent to the closed polygon. Wherever β⋅n≠0\beta\cdot n\ne 0, zero physical flux requires u=0u=0. This would impose an additional zero value at body outflow. In the positive, source-free branch, uu is constant along each straight characteristic. A characteristic entering from the far field with u=1u=1 and ending on the upwind side of the body cannot also have u=0u=0 there. At u=0u=0 the characteristic speed degenerates, so one cannot repair the derivation by dividing the boundary condition by uu.

Consequently the present zero-flux numerical closure does not provide the smooth, positive classical boundary-value problem needed for the above continuous-dual comparison. This is not a proof that no weak or measure-valued formulation could ever be defined. Such a formulation would require an entropy/boundary-trace specification and an appropriate sensitivity analysis, absent from these pages. The finite-dimensional residual, transpose, and discrete gradients remain well defined.

A clearly separated, computable characteristic-boundary contrast

For the new comparison only, treat both outer and inner boundaries by characteristics: prescribe u=1u=1 where β⋅n<0\beta\cdot n<0, and use the physical interior flux where β⋅n>0\beta\cdot n>0. The domain, interior numerical flux and its ε=0.8\varepsilon=0.8 remain the pages' own. The exact primal is u=1u=1.

Keep the drag-like functional

J=∫Γw12u2(e⋅n) ds,e=β/∣β∣,J=\int_{\Gamma_w}\tfrac12u^2(e\cdot n)\,ds, \qquad e=\beta/|\beta|,

but at prescribed inflow use the prescribed trace in the functional, not a free interior value. This readout change is part of the contrast and is not silently applied to the original example. The continuous dual is

−β⋅∇λ=0,λ=1/∣β∣ on body outflow,λ=0 on outer outflow.-\beta\cdot\nabla\lambda=0,\quad \lambda=1/|\beta|\text{ on body outflow},\quad \lambda=0\text{ on outer outflow}.

At an interior point, follow the ray in direction +β+\beta to its first boundary intersection. The value is 1/∣β∣1/|\beta| if that intersection is the body, and zero otherwise. This is an exact characteristic calculation, not a transpose solve. It has jumps along rays from body tangencies/corners. The comparison uses volume-weighted L2 error; boundary nodes are excluded from the node-centred norm. No smooth maximum-norm convergence is claimed. This contrast does not validate continuous consistency of the original zero-flux wall.

A.2 从方程到可运行参考实现

A.2 From equations to a runnable reference

读取边界线段 → 沿 +β 发射射线 → 选最近正向交点 → 物面取 1/|β|、远场取 0 → 体积加权比较

Read boundary segments → trace along +β → select first forward hit → body: 1/|β|, outer: 0 → volume-weighted comparison

下方代码可以完整复制到本地文件运行。它独立计算连续参考,不调用本页有限体积 Jacobian 的转置。二维代码内嵌两次细化的多边形网格;这里输出参考场,后面的多网格交互保留已计算的加密结果。

Copy the complete code below into a local file and run it. It computes the continuous reference independently of the page’s finite-volume Jacobian transpose. The 2-D scripts include the polygonal mesh refined twice; they output reference fields, while the refinement viewer below retains the precomputed multi-grid comparisons.

python3 -m pip install numpy scipy
python3 reference_transport.py
展开完整参考代码Show complete reference code
import numpy as np
import json
mesh=json.loads('{"X":[[0.804,-0.04],[0.6125777772109133,0.4725777772109134],[0.10000000000000003,0.51],[-0.165239682094289,0.22523968209428905],[-0.29600000000000004,-0.03999999999999995],[-0.25857777721091335,-0.3985777772109132],[0.09999999999999991,-0.5900000000000001],[0.519239682094289,-0.45923968209428906],[1.2972000000000001,-0.022000000000000002],[0.9414940753805006,0.8644940753805004],[0.05500000000000008,1.1355],[-0.695458123066357,0.7284581230663572],[-1.0178,-0.02199999999999987],[-0.7467940753805007,-0.8237940753805003],[0.05499999999999979,-1.1795],[0.8901581230663569,-0.8571581230663573],[1.9,0],[1.3435028842544403,1.34350288425444],[1.1634144591899854e-16,1.9],[-1.34350288425444,1.3435028842544403],[-1.9,2.326828918379971e-16],[-1.3435028842544405,-1.34350288425444],[-3.490243377569956e-16,-1.9],[1.3435028842544399,-1.3435028842544405],[1.0506000000000002,-0.031],[1.1193470376902503,0.4212470376902502],[0.777035926295707,0.6685359262957069],[0.7082888886054567,0.2162888886054567],[0.9138179631478536,0.31876796314785344],[0.4982470376902503,0.9999970376902502],[0.07750000000000005,0.82275],[0.3562888886054567,0.4912888886054567],[0.42726796314785354,0.7456429631478534],[-0.3202290615331785,0.9319790615331786],[-0.430348902580323,0.4768489025803231],[-0.03261984104714448,0.3676198410471445],[-0.17642445129016146,0.6497994512901616],[-0.8566290615331785,0.3532290615331786],[-0.6569,-0.03099999999999991],[-0.2306198410471445,0.09261984104714455],[-0.5436244512901616,0.22292445129016158],[-0.8822970376902504,-0.4228970376902501],[-0.5026859262957071,-0.6111859262957068],[-0.2772888886054567,-0.2192888886054566],[-0.5797929631478536,-0.32109296314785335],[-0.34589703769025043,-1.0016470376902502],[0.07749999999999985,-0.88475],[-0.07928888860545671,-0.4942888886054566],[-0.21259296314785361,-0.7479679631478533],[0.47257906153317836,-1.0183290615331786],[0.704698902580323,-0.6581989025803232],[0.3096198410471444,-0.5246198410471445],[0.3910994512901613,-0.7714744512901617],[1.0936790615331784,-0.43957906153317866],[0.6616198410471446,-0.24961984104714452],[0.8776494512901616,-0.3445994512901616],[1.5986,-0.011000000000000001],[1.62175144212722,0.67175144212722],[1.3203514421272202,0.66075144212722],[1.1424984798174704,1.1039984798174702],[0.6717514421272203,1.6217514421272199],[0.47074703769025034,1.38224703769025],[0.027500000000000097,1.51775],[-0.6717514421272199,1.62175144212722],[-0.64425144212722,1.2395014421272201],[-1.0194805036603984,1.0359805036603986],[-1.6217514421272199,0.6717514421272203],[-1.2977290615331785,0.3642290615331787],[-1.4588999999999999,-0.010999999999999819],[-1.6217514421272203,-0.6717514421272199],[-1.1806514421272203,-0.6827514421272199],[-1.0451484798174706,-1.0836484798174701],[-0.6717514421272205,-1.6217514421272199],[-0.3733970376902505,-1.3618970376902502],[0.027499999999999723,-1.53975],[0.6717514421272197,-1.6217514421272203],[0.6992514421272198,-1.2615014421272202],[1.1168305036603985,-1.100330503660399],[1.6217514421272199,-0.6717514421272203],[1.3950790615331785,-0.42857906153317865],[0.9273000000000001,-0.035500000000000004],[0.9822089815739269,0.14388398157392673],[0.8110534258766551,0.26752842587665504],[0.7561444443027283,0.08814444430272834],[0.8691767129383277,0.11601421293832753],[1.1739000000000002,-0.026500000000000003],[1.2082735188451252,0.19962351884512508],[1.016582500419052,0.37000750041905184],[1.0952412502095261,0.1717537502095259],[1.0304205565353755,0.6428705565353753],[0.8592650008381038,0.7665150008381036],[0.8454269447217803,0.4936519447217802],[0.9379237506285779,0.5682612506285778],[0.6948068517533101,0.5705568517533102],[0.6604333329081851,0.344433332908185],[0.7529301388149826,0.4190426388149826],[0.6021519447217802,0.7070894447217801],[0.3917784258766551,0.618465925876655],[0.484433332908185,0.4819333329081851],[0.5432926388149826,0.5945113888149827],[0.7198705565353755,0.9322455565353753],[0.46275750041905195,0.8728200004190518],[0.6610112506285778,0.8196675006285776],[0.2766235188451252,1.067748518845125],[0.06625000000000006,0.979125],[0.2523839815739268,0.7841964815739266],[0.264503750209526,0.9259725002095259],[0.08875000000000005,0.6663749999999999],[0.22814444430272837,0.5006444443027284],[0.24026421293832756,0.6424204629383274],[-0.0494622256450807,0.7362747256450808],[-0.10452214616865296,0.5087096461686531],[0.03369007947642778,0.43880992052357226],[-0.00788607308432646,0.5875423230843264],[-0.1326145307665892,1.0337395307665893],[-0.24832675641166996,0.7908892564116701],[-0.09103837820583496,0.885007128205835],[-0.5078435922997677,0.8302185922997678],[-0.56290351282334,0.6026535128233401],[-0.30338667693524224,0.5633241769352424],[-0.405615134617505,0.696771384617505],[-0.297794292337306,0.35104429233730605],[-0.09892976157071673,0.29642976157071677],[-0.20115821925297947,0.4298769692529796],[-0.4869866769352423,0.34988667693524234],[-0.38712214616865304,0.15777214616865307],[-0.19792976157071673,0.1589297615707168],[-0.34245821925297953,0.2544082192529796],[-0.7760435922997677,0.5408435922997679],[-0.70012675641167,0.2880767564116701],[-0.631515134617505,0.4453651346175051],[-0.9372145307665893,0.16561453076658939],[-0.83735,-0.02649999999999989],[-0.6002622256450808,0.09596222564508083],[-0.768738378205835,0.13078837820583508],[-0.47645000000000004,-0.035499999999999934],[-0.26330992052357227,0.026309920523572297],[-0.43178607308432654,0.061136073084326566],[-0.6183464815739268,-0.17604648157392663],[-0.42854092587665515,-0.27019092587665494],[-0.2866444443027284,-0.12964444430272826],[-0.4524954629383276,-0.15284546293832746],[-0.9500485188451252,-0.22244851884512498],[-0.731045000419052,-0.37199500041905176],[-0.784197500209526,-0.1992475002095258],[-0.8145455565353755,-0.6233455565353752],[-0.6247400008381039,-0.7174900008381035],[-0.5412394447217803,-0.46613944472178004],[-0.6778925006285779,-0.5447425006285777],[-0.3806318517533102,-0.50488185175331],[-0.267933332908185,-0.3089333329081849],[-0.40458638881498266,-0.3875363888149825],[-0.35763944472178033,-0.6795769447217801],[-0.14594092587665516,-0.621128425876655],[-0.16893333290818502,-0.4464333329081849],[-0.2632863888149827,-0.5630051388149825],[-0.5463455565353755,-0.9127205565353753],[-0.27924500041905204,-0.8748075004190518],[-0.45199250062857793,-0.7961487506285776],[-0.14544851884512533,-1.090573518845125],[0.06624999999999982,-1.032125],[-0.06754648157392688,-0.8163589815739267],[-0.1064975002095261,-0.9534662502095258],[0.08874999999999988,-0.7373750000000001],[0.010355555697271597,-0.5421444443027283],[-0.028595462938327643,-0.6792517129383275],[0.2342997256450806,-0.8281122256450808],[0.3503596461686529,-0.6480471461686531],[0.20480992052357216,-0.5573099205235723],[0.2195548230843264,-0.6927110730843267],[0.26378953076658906,-1.0989145307665893],[0.43183925641166987,-0.8949017564116701],[0.24904462820583484,-0.963513378205835],[0.6813685922997677,-0.9377435922997679],[0.7974285128233399,-0.7576785128233403],[0.5478991769352421,-0.7148366769352424],[0.6146338846175049,-0.8262901346175051],[0.6119692923373059,-0.5587192923373061],[0.41442976157071665,-0.4919297615707168],[0.4811644692529794,-0.6033832192529797],[0.7911741769352423,-0.5013991769352424],[0.7696346461686531,-0.2971096461686531],[0.5904297615707168,-0.3544297615707168],[0.6908019692529794,-0.4279144692529796],[0.9919185922997676,-0.648368592299768],[0.98566425641167,-0.39208925641167014],[0.8915463846175049,-0.5748838846175053],[1.1954395307665893,-0.23078953076658934],[0.9641247256450809,-0.1877997256450808],[1.079782128205835,-0.20929462820583508],[0.7328099205235723,-0.14480992052357225],[0.8484673230843267,-0.16630482308432656],[1.4479000000000002,-0.0165],[1.45947572106361,0.32487572106361],[1.3087757210636102,0.31937572106361],[1.7492999999999999,-0.0055000000000000005],[1.7608757210636101,0.33587572106361],[1.61017572106361,0.33037572106361],[1.4710514421272203,0.6662514421272201],[1.48262716319083,1.00762716319083],[1.3319271631908303,1.00212716319083],[1.2198492399087353,0.5409992399087351],[1.2430006820359554,1.2237506820359552],[1.2314249609723453,0.8823749609723451],[1.1309227587538604,0.7626227587538602],[1.0419962775989855,0.9842462775989853],[0.8066227587538604,1.2431227587538602],[0.7061205565353754,1.123370556535375],[1.0076271631908302,1.48262716319083],[0.9071249609723453,1.362874960972345],[0.5712492399087353,1.501999239908735],[0.3358757210636102,1.76087572106361],[0.23537351884512522,1.641123518845125],[0.4844970376902503,1.19112203769025],[0.013750000000000108,1.708875],[0.2491235188451252,1.449998518845125],[0.2628735188451252,1.258873518845125],[0.041250000000000085,1.326625],[-0.30837572106360994,1.3786257210636101],[-0.29462572106360996,1.1875007210636102],[-0.3358757210636099,1.7608757210636101],[-0.3221257210636099,1.5697507210636101],[-0.65800144212722,1.4306264421272201],[-1.00762716319083,1.48262716319083],[-0.9938771631908301,1.29150216319083],[-0.48224025183019925,1.0857402518301993],[-1.1814916939574194,1.1897416939574195],[-0.8318659728938091,1.1377409728938095],[-0.6698547825967884,0.9839797825967886],[-0.8574693133633777,0.8822193133633779],[-1.1586047825967885,0.7001047825967887],[-0.9965935922997677,0.5463435922997679],[-1.48262716319083,1.0076271631908302],[-1.3206159728938092,0.8538659728938094],[-1.4597402518301992,0.5179902518301995],[-1.76087572106361,0.33587572106361024],[-1.5988645307665892,0.18211453076658946],[-1.0771790615331784,0.35872906153317863],[-1.6794499999999999,-0.005499999999999793],[-1.3783145307665892,0.17661453076658942],[-1.1577645307665891,0.1711145307665894],[-1.23835,-0.016499999999999845],[-1.31977572106361,-0.34687572106360987],[-1.0992257210636103,-0.3523757210636099],[-1.7608757210636101,-0.33587572106360986],[-1.54032572106361,-0.34137572106360986],[-1.4012014421272203,-0.67725144212722],[-1.4826271631908305,-1.00762716319083],[-1.2620771631908303,-1.01312716319083],[-1.0314742399087353,-0.5528242399087351],[-1.1943256820359556,-1.2135756820359551],[-1.1128999609723453,-0.8831999609723451],[-0.9637227587538605,-0.7532727587538601],[-0.8959712775989856,-0.9537212775989852],[-0.7092727587538605,-1.2227727587538602],[-0.5600955565353756,-1.0928455565353752],[-1.0076271631908305,-1.48262716319083],[-0.8584499609723455,-1.3526999609723451],[-0.5225742399087355,-1.491824239908735],[-0.3358757210636104,-1.76087572106361],[-0.18669851884512542,-1.6309485188451252],[-0.35964703769025047,-1.18177203769025],[0.013749999999999686,-1.719875],[-0.17294851884512538,-1.450823518845125],[-0.15919851884512534,-1.270698518845125],[0.04124999999999976,-1.3596249999999999],[0.36337572106360977,-1.40062572106361],[0.3771257210636098,-1.22050072106361],[0.3358757210636097,-1.7608757210636101],[0.34962572106360973,-1.5807507210636103],[0.6855014421272198,-1.4416264421272202],[1.0076271631908298,-1.4826271631908305],[1.02137716319083,-1.3025021631908302],[0.5859152518301991,-1.1399152518301994],[1.2301666939574192,-1.2219166939574198],[0.9080409728938091,-1.1809159728938097],[0.7947047825967885,-1.0593297825967887],[1.0034943133633778,-0.9787443133633782],[1.2559547825967885,-0.7644547825967888],[1.1426185922997676,-0.642868592299768],[1.4826271631908299,-1.0076271631908305],[1.3692909728938092,-0.8860409728938097],[1.5084152518301992,-0.5501652518301995],[1.76087572106361,-0.33587572106361013],[1.6475395307665892,-0.21428953076658933],[1.2443790615331785,-0.43407906153317866],[1.4968395307665894,-0.21978953076658933],[1.3461395307665893,-0.22528953076658934]],"C2N":[[1,81,85,84],[81,25,82,85],[85,82,29,83],[84,85,83,28],[25,86,89,82],[86,9,87,89],[89,87,26,88],[82,89,88,29],[29,88,93,92],[88,26,90,93],[93,90,10,91],[92,93,91,27],[28,83,96,95],[83,29,92,96],[96,92,27,94],[95,96,94,2],[2,94,100,99],[94,27,97,100],[100,97,33,98],[99,100,98,32],[27,91,103,97],[91,10,101,103],[103,101,30,102],[97,103,102,33],[33,102,107,106],[102,30,104,107],[107,104,11,105],[106,107,105,31],[32,98,110,109],[98,33,106,110],[110,106,31,108],[109,110,108,3],[3,108,114,113],[108,31,111,114],[114,111,37,112],[113,114,112,36],[31,105,117,111],[105,11,115,117],[117,115,34,116],[111,117,116,37],[37,116,121,120],[116,34,118,121],[121,118,12,119],[120,121,119,35],[36,112,124,123],[112,37,120,124],[124,120,35,122],[123,124,122,4],[4,122,128,127],[122,35,125,128],[128,125,41,126],[127,128,126,40],[35,119,131,125],[119,12,129,131],[131,129,38,130],[125,131,130,41],[41,130,135,134],[130,38,132,135],[135,132,13,133],[134,135,133,39],[40,126,138,137],[126,41,134,138],[138,134,39,136],[137,138,136,5],[5,136,142,141],[136,39,139,142],[142,139,45,140],[141,142,140,44],[39,133,145,139],[133,13,143,145],[145,143,42,144],[139,145,144,45],[45,144,149,148],[144,42,146,149],[149,146,14,147],[148,149,147,43],[44,140,152,151],[140,45,148,152],[152,148,43,150],[151,152,150,6],[6,150,156,155],[150,43,153,156],[156,153,49,154],[155,156,154,48],[43,147,159,153],[147,14,157,159],[159,157,46,158],[153,159,158,49],[49,158,163,162],[158,46,160,163],[163,160,15,161],[162,163,161,47],[48,154,166,165],[154,49,162,166],[166,162,47,164],[165,166,164,7],[7,164,170,169],[164,47,167,170],[170,167,53,168],[169,170,168,52],[47,161,173,167],[161,15,171,173],[173,171,50,172],[167,173,172,53],[53,172,177,176],[172,50,174,177],[177,174,16,175],[176,177,175,51],[52,168,180,179],[168,53,176,180],[180,176,51,178],[179,180,178,8],[8,178,184,183],[178,51,181,184],[184,181,56,182],[183,184,182,55],[51,175,187,181],[175,16,185,187],[187,185,54,186],[181,187,186,56],[56,186,190,189],[186,54,188,190],[190,188,9,86],[189,190,86,25],[55,182,192,191],[182,56,189,192],[192,189,25,81],[191,192,81,1],[9,193,195],[193,57,194],[195,194,59],[193,194,195],[57,196,198],[196,17,197],[198,197,58],[196,197,198],[59,199,201],[199,58,200],[201,200,18],[199,200,201],[57,198,194],[198,58,199],[194,199,59],[198,199,194],[9,195,87],[195,59,202],[87,202,26],[195,202,87],[59,201,204],[201,18,203],[204,203,60],[201,203,204],[26,205,90],[205,60,206],[90,206,10],[205,206,90],[59,204,202],[204,60,205],[202,205,26],[204,205,202],[10,206,208],[206,60,207],[208,207,62],[206,207,208],[60,203,210],[203,18,209],[210,209,61],[203,209,210],[62,211,213],[211,61,212],[213,212,19],[211,212,213],[60,210,207],[210,61,211],[207,211,62],[210,211,207],[10,208,101],[208,62,214],[101,214,30],[208,214,101],[62,213,216],[213,19,215],[216,215,63],[213,215,216],[30,217,104],[217,63,218],[104,218,11],[217,218,104],[62,216,214],[216,63,217],[214,217,30],[216,217,214],[11,218,220],[218,63,219],[220,219,65],[218,219,220],[63,215,222],[215,19,221],[222,221,64],[215,221,222],[65,223,225],[223,64,224],[225,224,20],[223,224,225],[63,222,219],[222,64,223],[219,223,65],[222,223,219],[11,220,115],[220,65,226],[115,226,34],[220,226,115],[65,225,228],[225,20,227],[228,227,66],[225,227,228],[34,229,118],[229,66,230],[118,230,12],[229,230,118],[65,228,226],[228,66,229],[226,229,34],[228,229,226],[12,230,232],[230,66,231],[232,231,68],[230,231,232],[66,227,234],[227,20,233],[234,233,67],[227,233,234],[68,235,237],[235,67,236],[237,236,21],[235,236,237],[66,234,231],[234,67,235],[231,235,68],[234,235,231],[12,232,129],[232,68,238],[129,238,38],[232,238,129],[68,237,240],[237,21,239],[240,239,69],[237,239,240],[38,241,132],[241,69,242],[132,242,13],[241,242,132],[68,240,238],[240,69,241],[238,241,38],[240,241,238],[13,242,244],[242,69,243],[244,243,71],[242,243,244],[69,239,246],[239,21,245],[246,245,70],[239,245,246],[71,247,249],[247,70,248],[249,248,22],[247,248,249],[69,246,243],[246,70,247],[243,247,71],[246,247,243],[13,244,143],[244,71,250],[143,250,42],[244,250,143],[71,249,252],[249,22,251],[252,251,72],[249,251,252],[42,253,146],[253,72,254],[146,254,14],[253,254,146],[71,252,250],[252,72,253],[250,253,42],[252,253,250],[14,254,256],[254,72,255],[256,255,74],[254,255,256],[72,251,258],[251,22,257],[258,257,73],[251,257,258],[74,259,261],[259,73,260],[261,260,23],[259,260,261],[72,258,255],[258,73,259],[255,259,74],[258,259,255],[14,256,157],[256,74,262],[157,262,46],[256,262,157],[74,261,264],[261,23,263],[264,263,75],[261,263,264],[46,265,160],[265,75,266],[160,266,15],[265,266,160],[74,264,262],[264,75,265],[262,265,46],[264,265,262],[15,266,268],[266,75,267],[268,267,77],[266,267,268],[75,263,270],[263,23,269],[270,269,76],[263,269,270],[77,271,273],[271,76,272],[273,272,24],[271,272,273],[75,270,267],[270,76,271],[267,271,77],[270,271,267],[15,268,171],[268,77,274],[171,274,50],[268,274,171],[77,273,276],[273,24,275],[276,275,78],[273,275,276],[50,277,174],[277,78,278],[174,278,16],[277,278,174],[77,276,274],[276,78,277],[274,277,50],[276,277,274],[16,278,280],[278,78,279],[280,279,80],[278,279,280],[78,275,282],[275,24,281],[282,281,79],[275,281,282],[80,283,285],[283,79,284],[285,284,17],[283,284,285],[78,282,279],[282,79,283],[279,283,80],[282,283,279],[16,280,185],[280,80,286],[185,286,54],[280,286,185],[80,285,287],[285,17,196],[287,196,57],[285,196,287],[54,288,188],[288,57,193],[188,193,9],[288,193,188],[80,287,286],[287,57,288],[286,288,54],[287,288,286]],"bnd":{"1-84":"wall","28-84":"wall","2-95":"wall","28-95":"wall","17-197":"far","58-197":"far","18-200":"far","58-200":"far","2-99":"wall","32-99":"wall","3-109":"wall","32-109":"wall","18-209":"far","61-209":"far","19-212":"far","61-212":"far","3-113":"wall","36-113":"wall","4-123":"wall","36-123":"wall","19-221":"far","64-221":"far","20-224":"far","64-224":"far","4-127":"wall","40-127":"wall","5-137":"wall","40-137":"wall","20-233":"far","67-233":"far","21-236":"far","67-236":"far","5-141":"wall","44-141":"wall","6-151":"wall","44-151":"wall","21-245":"far","70-245":"far","22-248":"far","70-248":"far","6-155":"wall","48-155":"wall","7-165":"wall","48-165":"wall","22-257":"far","73-257":"far","23-260":"far","73-260":"far","7-169":"wall","52-169":"wall","8-179":"wall","52-179":"wall","23-269":"far","76-269":"far","24-272":"far","76-272":"far","1-191":"wall","55-191":"wall","8-183":"wall","55-183":"wall","17-284":"far","79-284":"far","24-281":"far","79-281":"far"}}')
beta=np.array([1.,.5]);X=np.array(mesh['X']);segments=[]
for key,bc in mesh['bnd'].items():
 a,b=np.array(key.split('-'),int)-1
 segments.append((X[a],X[b],bc))
def cross(a,b): return a[0]*b[1]-a[1]*b[0]
def ray(p):
 hits=[]
 for a,b,bc in segments:
  d=b-a;den=cross(beta,d)
  if abs(den)<1e-14: continue
  t=cross(a-p,d)/den;z=cross(a-p,beta)/den
  if t>1e-9 and -1e-9<=z<=1+1e-9: hits.append((t,bc))
 if not hits: raise ValueError('No forward boundary intersection')
 return 1/np.linalg.norm(beta) if min(hits)[1]=='wall' else 0.
if __name__ == '__main__':
 points=np.array([X[np.array(c)-1].mean(axis=0) for c in mesh['C2N']])
 lam=np.array([ray(p) for p in points])
 np.savetxt('continuous_transport.csv',np.column_stack([points,lam]),delimiter=',',header='x,y,lambda')
 print('Characteristic-boundary contrast only:',len(lam),'samples; range',lam.min(),lam.max())
附录 A(续)Appendix A (continued)

连续伴随与伴随一致性

Continuous adjoints and adjoint consistency

点积测试查转置,差分与复数步长查同一离散目标的梯度;它们都不能证明离散伴随逼近了连续伴随。本节先推导连续问题,再用独立参考解与网格加密比较。

A dot test checks the transpose; finite differences and the complex step check the gradient of the same discrete objective. None proves approximation of the continuous dual. Here we derive the continuous problem first, then compare with an independent reference under refinement.

Laug=J−∫ΩλTRdΩ,λh=−ψh

本页用 AhTψh=−gh,因此连续源项敏感度的符号是 λh=−ψh。守恒残差已做控制体积分,不再除以体积。

This page solves AhTψh=−gh, hence the source-sensitivity sign λh=−ψh. Conservation residuals are already integrated: do not divide by volume.

先做分部积分

First integrate by parts

Ruv=∇·(uβv),a=uβ
∫ΩλRuv=−∫Ω(a·∇λ)v+∫∂Ωλ(a·n)v

若 J=∫Ωj(u)+∫∂Ωb(u),域内得到下式。原始入流的状态固定,v=0,不在那里给伴随条件;原始出流的 v 自由,由边界项给伴随条件。

For J=∫Ωj(u)+∫∂Ωb(u) the volume equation is below. At primal inflow the state is prescribed, v=0, and no dual datum is imposed there. At primal outflow v is free, so the boundary term supplies the dual datum.

−a·∇λ=ju,λ=bua·n(原始出流边界上)
−a·∇λ=ju,λ=bua·non primal outflow

原零通量内壁:连续问题的兼容性限制

The original zero-flux body: a compatibility obstruction

本页的壁面数值通量直接为零,与内侧状态无关。但原 PDE 的物理通量是 12u2β·n;固定的 β=(1,0.5) 不可能沿闭合物面处处相切。β·n≠0 处零物理通量要求 u=0。对从远场 u=1 进入、最后撞到迎风面的特征线,这与沿特征保持 u=1 矛盾;u=0 还使特征速度退化。

The original numerical wall flux is zero independently of the interior state. The PDE physical flux is 12u2β·n, but the fixed β=(1,0.5) cannot be tangent everywhere to a closed body. Where β·n≠0, zero physical flux requires u=0. A characteristic entering from the far field at u=1 and ending on the upwind body cannot satisfy that: its state stays one. At u=0 the characteristic speed also degenerates.

因此不能宣称原零通量算例具有这里所需的光滑、正值连续伴随参考。这不否定已有离散梯度,也不是排除所有弱解理论;它说明必须先另行定义弱解、边界迹及其灵敏度,不能把二维标量的“壁面”直接当作 Euler 滑移壁面。

The original zero-flux example therefore does not supply the smooth positive continuous reference required here. Its discrete gradients remain valid. This does not exclude every weak-solution theory; such a theory would need its own boundary traces and sensitivities. A scalar “wall” cannot simply be treated as an Euler slip wall.

可计算的对照:特征边界与解析连续伴随

A computable contrast: characteristic boundaries and an exact dual

仅在本节对照中,把内、外边界都改为:入流给 u=1,出流外推。目标仍是壁面 12u2(e·n) 的积分,但入流段使用已给定的边界迹,不对内侧未知量求导。原页面第 1–17 节的主线算例不变。此时精确原始解 u=1,e=β/|β|,域内 −β·∇λ=0。

For this contrast only, prescribe u=1 at inflow on both boundaries and extrapolate at outflow. The objective is still the wall integral of 12u2(e·n), but uses the prescribed trace on inflow rather than differentiating an interior unknown there. The main line, Sections 1–17, is unchanged. Now the exact primal is u=1, e=β/|β|, and −β·∇λ=0 inside.

λ=1|β|(物面出流处),λ=0(外边界出流处)
λ=1|β|on body outflow,λ=0on outer outflow

从任一内点沿 +β 追踪射线,到第一个边界交点:遇到物面取 1/|β|,遇到远场取 0。这就是连续伴随的独立计算,不解离散转置。它沿物面切线/角点发出的特征有跳跃;所以检查体积加权 L2,不要求最大范数收敛。

Trace a ray from an interior point along +β to its first boundary intersection: assign 1/|β| at the body, zero at the outer boundary. This independently computes the continuous dual without a discrete transpose. Jumps issue from body tangencies/corners; use volume-weighted L2 and do not demand maximum-norm convergence.

层LevelDOFL2max
0242.497e-015.816e-01
1802.237e-015.588e-01
22881.935e-015.270e-01
310881.630e-015.723e-01

表中是特征边界对照,不是原零通量边界的验证。格点范数排除边界节点;离散耗散把连续伴随的跳跃抹宽,L2 误差下降较慢。下图可切换网格、连续场、离散场与差值。

These are results for the characteristic-boundary contrast, not validation of the original zero-flux boundary. The node norm excludes boundary nodes. Numerical dissipation broadens the dual jumps, so L2 decreases slowly. The viewer switches grid, continuous field, discrete field and difference.

不一致时先检查什么:存在、唯一与稳定

What to check when results disagree: existence, uniqueness and stability

伴随不一致与伴随问题不适定不是同一件事。适定性要求在明确的函数空间、边界迹与数据范数下:解存在、唯一,并连续依赖于数据。比较之前先固定同一个原始问题、目标泛函、边界条件和乘子含义;边界层、有限参考误差、错误的目标边界项,都可能造成差异,不能仅凭一张误差图判定不适定。

Adjoint inconsistency and an ill-posed adjoint problem are different diagnoses. Well-posedness requires existence, uniqueness and continuous dependence on data in specified function spaces, trace spaces and norms. First match the primal problem, functional, boundary conditions and multiplier interpretation. Boundary layers, finite reference error, an incorrect objective boundary term can all cause discrepancies; an error plot alone cannot diagnose ill-posedness.

在线性化状态及边界约束都已确定后,记原始线性算子为 𝒜,伴随方程为 𝒜*λ=g。若 𝒜 是适当空间之间的有界同构,正确配对的对偶问题也有有界逆。若存在非零零模态 v,则可解性至少要求 g[v]=0;伴随自身有非零齐次解时,解就不唯一。无穷维情形还必须检查值域闭性/稳定估计,不能只数边界条件。

After fixing the linearized state and boundary constraints, denote the primal operator by 𝒜 and the dual equation by 𝒜*λ=g. If 𝒜 is a bounded isomorphism between suitable spaces, its correctly paired dual also has a bounded inverse. A nonzero primal null mode v requires at least g[v]=0 for solvability; a nonzero homogeneous dual mode makes the dual nonunique. In infinite dimensions one must also check closed range/stability estimates, not merely count boundary conditions.

𝒜v=0⇒g[v]=⟨λ,𝒜v⟩=0

非线性原始解存在唯一,也不自动保证其导数存在:还需要所选解支的局部可微性和线性化可逆性。下面讨论的都是已说明的光滑解支;不把有限网格上能解出的转置系统当作连续理论的替代。

Even existence and uniqueness of a nonlinear primal do not automatically ensure differentiability: local smoothness of the chosen solution branch and invertibility of its linearization are additional requirements. The discussion concerns the stated smooth branch; a solvable finite-grid transpose system is not a substitute for continuous analysis.

一条特征线就能看出:少条件、多条件与失稳

One characteristic exposes missing data, excess data and instability

取区间 0<x<1、常数速度 c>0,原始入口固定,目标含体积分及出口项 bu(1)。分部积分给出下式,唯一解从原始出口向上游积分:

On 0<x<1, take constant speed c>0, fixed primal inflow and an objective with a volume term and outlet term bu(1). Integration by parts gives the following unique solution, integrated upstream from primal outflow:

−cλ′=g,cλ(1)=b,λ(x)=bc+1c∫x1g(s)ds

少条件:不规定出口伴随值时可加任意常数,解不唯一。多条件:若还照搬原始入口而规定 λ(0)=0,则必须满足 b+∫01gdx=0;例如 g=1,b=0 时正确解是 (1−x)/c,额外入口条件使问题无解。失去一致稳定性:上述数据到解的放大含 1/c,因此不能在 c→0 时声称有与 c 无关的稳定界。每个固定 c>0 仍然可以适定。

Missing data: without the outlet dual value, an arbitrary additive constant remains, so the solution is nonunique. Excess data: also copying the primal inflow condition as λ(0)=0 requires b+∫01gdx=0. For g=1,b=0, the correct solution is (1−x)/c and the extra inlet condition makes it impossible. Loss of uniform stability: data amplification contains 1/c, so no bound independent of c is justified as c→0. Each fixed c>0 can still define a well-posed problem.

把诊断用到原零通量内壁

Apply the diagnosis to the original zero-flux body

法向始终取计算域的外法向,内边界的法向指向物体。因此迎风物面是流体域的原始出流:特征已从远场携带 u=1 到达这里,再要求零物理通量就是不相容的额外限制。这里首先缺少的是原始光滑正值解,故没有合法的该分支线性化状态;不能跳过这一步,只给形式伴随补几个边界值。

The normal always points out of the fluid domain, into the body on its inner boundary. The upwind body is therefore primal outflow from the fluid: a characteristic already carries u=1 from the far field, and requiring zero physical flux adds an incompatible restriction. The first obstruction is the absence of the stated smooth positive primal, hence of a valid linearization about that branch; appending boundary values to a formal dual cannot repair this.

δ(12u2β·n)=u(β·n)v

若退到壁面 u=0,上式对任意 v 都为零:一阶线性化丢失了原非线性通量条件的约束信息,特征速度也同时退化。这个退化不能用“把零通量条件照抄给伴随”来修复。要讨论允许停滞、间断或弱解的新问题,必须另建解空间、熵/边界迹条件及可微性理论。

At a wall state u=0, this first variation vanishes for every v: linearization loses information contained in the nonlinear flux constraint while the characteristic speed degenerates. Copying a zero-flux condition onto the adjoint does not repair it. A new problem allowing stagnation, discontinuities or weak solutions needs its own solution space, entropy/boundary traces and differentiability theory.

对于本节相容的特征边界对照,每条内部特征沿 +β 都能在有限路程内到达出流边界,因此边界数据确定内部场(除去零测集上的取值约定)。切线或角点发出的跳跃不等于不适定;应在允许跳跃的空间中讨论。一般边界公式中的 bu/(a·n) 在特征边界附近需要兼容性分析,本对照的物面分子与分母同时含法向因子并相消,不能仅看分母趋零就宣称伴随发散。

For the compatible contrast, every interior characteristic followed along +β reaches outflow in finite distance, determining the interior field up to values on sets of measure zero. Jumps from tangencies or corners do not imply ill-posedness; use a space allowing them. The general ratio bu/(a·n) requires compatibility analysis near characteristic boundaries. In this contrast its body numerator and denominator share a cancelling normal factor, so a vanishing denominator alone does not establish adjoint blow-up.

矩阵能求逆为什么还不够

Why an invertible matrix is not enough

即使每个有限网格上的 AhT 都可逆,也不保证在网格相关的正确加权范数中,其逆有与 h 无关的界。人工耗散或边界闭合可能让每个有限系统可解,却不能给出目标连续问题的稳定极限。要判断一致性,应先建立连续问题,再检查残差与目标的逼近、边界乘子的对应关系以及离散稳定性;若这些前提不成立,就把它保留为反例并准确指出失败环节。

Even if every finite-grid AhT is invertible, its inverse need not be bounded independently of h in the appropriate mesh-weighted norms. Artificial dissipation or boundary closure may make every finite system solvable without providing a stable limit for the intended continuous problem. Establish the continuous problem first, then check residual/functional approximation, boundary-multiplier interpretation and discrete stability. If a premise fails, retain the example and identify the failed premise explicitly.

怎样理解这些比较

How to interpret these comparisons

下图展示已离线计算的连续参考与离散场,可切换网格;它不是在浏览器里重新求解连续 PDE。图 A.2 给出场与误差。完整的符号、边界项和证据范围见连续伴随推导(新增模型的几何变分见附录 B)。

The viewer displays offline-computed continuous references and discrete fields at selectable resolutions; it does not rerun a continuous PDE solve in the browser. Figure A.2 shows fields and errors. See the continuous-adjoint derivation for signs, boundary terms and evidence limits (geometry variations of the added model: Appendix B).

图 A.2Figure A.2
交互:连续与离散伴随的网格加密比较
Interactive: continuous and discrete adjoints under refinement
独立连续参考;离线结果浏览;不代替原问题的梯度验证
Independent continuous reference; stored results; not a replacement for the original gradient checks

返回正文结论Return to the main conclusions

附录 BAppendix B

附录 B:新增光滑模型的连续伴随

Appendix B: continuous adjoint of the added smooth model

令 F=u²β/2,L=∫Γinner u²(e·n)/2 ds。给 L 加上 ∫Ω ψ∇·F dΩ(原始方程成立时它为零),符号与正文的离散约定一致。固定几何时 δF=uβδu。对残差变分积分一次,得到内部方程和边界项。

Let F=u²β/2 and L=∫Γinner u²(e·n)/2 ds. Add ∫Ω ψ∇·F dΩ to L (it is zero when the primal equation holds), with the sign of the discrete convention in the main text. For fixed geometry, δF=uβδu. Integrating by parts gives the volume equation and boundary term.

δ(L+∫Ωψ∇⋅F dΩ)=−∫Ωu𝜷⋅∇ψ δu dΩ+∫∂Ω[ψu𝜷⋅n+χinneru𝒆⋅n]δu ds.\delta\Big(L+\int_\Omega\psi\nabla\cdot F\,d\Omega\Big)=-\int_\Omega u\boldsymbol\beta\cdot\nabla\psi\,\delta u\,d\Omega+\int_{\partial\Omega}\left[\psi u\boldsymbol\beta\cdot n+\chi_{inner}u\boldsymbol e\cdot n\right]\delta u\,ds.
𝜷⋅∇ψ=0,ψ∣Γ+={−1/∣𝜷∣,Γ+∩Γinner,0,Γ+∩Γouter.\boldsymbol\beta\cdot\nabla\psi=0,\qquad \psi|_{\Gamma_+}=\begin{cases}-1/|\boldsymbol\beta|,&\Gamma_+\cap\Gamma_{inner},\\0,&\Gamma_+\cap\Gamma_{outer}.\end{cases}

Γ₊ 是边界上 β·n>0 的部分(原始出流),Γ₋ 是 β·n<0 的部分(原始入流),|β|=√1.25。固定入流数据时 δu=0,所以不再给原始入流边界指定额外伴随值。离散目标因此在入流面上用给定的 u_in(第 7 节),不用内侧迹。原始出流处 δu 自由,必须消去方括号中的系数。即使 β·n 在某点接近零,内边界 e 与 β 平行使比值保持 −1/|β|;恰好相切的射线及角点仍需按几乎处处的输运解理解。

Γ₊ is the part of the boundary where β·n>0 (primal outflow), Γ₋ where β·n<0 (primal inflow); |β|=√1.25. For fixed inflow data δu=0, so no additional adjoint value is prescribed on primal inflow; the discrete objective accordingly uses the prescribed u_in on inflow faces (Section 7), not the interior trace. At primal outflow δu is free, requiring the bracketed coefficient to vanish. Because e is parallel to β, the inner-boundary ratio stays −1/|β| near grazing; exactly tangent rays and corners still require an almost-everywhere transport interpretation.

从每个内部点沿 +β 追踪,首次遇到内边界则 ψ=−1/|β|,首次遇到外边界则 ψ=0。这是独立几何射线计算,不调用离散矩阵。两类射线的分界可能产生伴随跳变,它由终端目标数据造成,不是原始流场激波。若把乘子的符号取反(加上 −∫λ∇·F),则 λ=−ψ。

Trace +β from each interior point. Set ψ=−1/|β| if the first intersection is the inner boundary and zero if it is outer. This independent geometric ray calculation does not call a discrete matrix. Interfaces between the two ray families can have adjoint jumps caused by terminal objective data, not primal shocks. With the opposite sign of the multiplier (adding −∫λ∇·F), λ=−ψ.

for point x in fluid domain:
    hits = positive_ray_intersections(x, beta, all_boundary_edges)
    first = hit_with_smallest_positive_distance(hits)
    psi_ref[x] = -1/norm(beta) if first.is_inner else 0

改变入流水平 u∞ 时,入流边界剩余项为 ∫Γ₋ u(ψβ·n+χinner e·n)δu∞ ds。改变几何时还必须包含移动边界和 u_in 的位置导数,不能只保留法向项。在本例全局解析场下,L 对所有有效边界形变及 u∞ 恒为零,可由孔洞内散度定理独立证明;因此完整连续总导数也为零。粗网格离散梯度不必为零,它应通过重算差分与复数残差来检验;加密时它是否趋于零,见下面的表。

Changing u∞ leaves the inflow term ∫Γ₋ u(ψβ·n+χinner e·n)δu∞ ds. Geometry variations additionally require moving-boundary and position-dependent u_in terms, not merely normal derivatives. For this global analytic field, L is identically zero for valid boundary shapes and all u∞, independently by the divergence theorem inside the hole. Its complete continuous total derivatives are zero. Coarse discrete gradients need not vanish and are checked by reconverged differences and complex residuals; whether they vanish under refinement is the table below.

图 B.1Figure B.1

下表是存储的加密研究(refine.js),与上方的精度开关联动:每一层的 L_h、dL/du∞、48 个基准网格坐标上的 max|dL/dX|(细网格的形状梯度经细分映射的转置送回基准网格),以及离散伴随与射线参考之差(格点页的 L2 差与附录 B 一样不计边界节点)。最后两列比较内边界入流面旁的伴随误差(格点页只取两侧都是入流半面的节点:入流与出流交界处的特征角点上,参考值取出流一侧的末端值,不作为最大范数的比较点):目标用 u⁺ 时它很小且不随加密增长,用内侧迹 u⁻ 时它随加密增大。

The table below is the stored refinement study (refine.js), following the order selector above: per level, L_h, dL/du∞, max|dL/dX| over the 48 coordinates of the base mesh (the fine-mesh shape gradient sent back through the transpose of the subdivision map), and the difference between the discrete adjoint and the ray reference (on the node page the L2 difference leaves out the boundary nodes, as in Appendix B). The last two columns compare the adjoint error next to inner inflow faces (on the node page only nodes whose half-faces are all inflow: at a characteristic corner between inflow and outflow the reference takes the outflow terminal value and is not a max-norm datum): small and not growing with u⁺ in the objective, growing with refinement with the interior trace u⁻.

图 B.2Figure B.2
射线参考完整代码Complete ray-reference code
/* Continuous reference from boundary intersections alone; no discrete Jacobian. */
(function(root){function reference(mesh,p,node=-1){const beta=[1,.5],hits=[];for(const cell of mesh.C2N)for(let j=0;j<cell.length;j++){const ia=cell[j],ib=cell[(j+1)%cell.length],key=[Math.min(ia,ib),Math.max(ia,ib)].join('-'),bc=mesh.bnd[key];if(!bc)continue;const A=mesh.X[ia-1],B=mesh.X[ib-1],d=[B[0]-A[0],B[1]-A[1]],q=[A[0]-p[0],A[1]-p[1]],vn=d[1]-.5*d[0];if(Math.abs(vn)<1e-12)continue;const t=(q[0]*d[1]-q[1]*d[0])/vn,z=(q[0]*beta[1]-q[1]*beta[0])/vn;if(node>=0&&(ia-1===node||ib-1===node)&&vn>0)hits.push({t:0,bc,hit:p.slice()});else if(t>1e-9&&z>=-1e-10&&z<=1+1e-10)hits.push({t,bc,hit:[p[0]+t,p[1]+.5*t]});}if(!hits.length)throw Error('No characteristic terminal intersection');hits.sort((a,b)=>a.t-b.t);return {...hits[0],psi:hits[0].bc==='wall'?-1/Math.sqrt(1.25):0};}if(typeof module!=='undefined')module.exports=reference;else root.TransportRay=reference;})(typeof window!=='undefined'?window:globalThis);