方程:二维定常可压缩 Euler 方程组
The equations: steady two-dimensional compressible Euler
从守恒律到 Euler 方程组
From conservation laws to the Euler equations
在流体中任取一块固定的区域。区域里的质量、两个方向的动量和总能量,只能通过它的边界改变:流体穿过边界时把这些量带进带出,边界上的压力推动流体并对它做功。不计黏性和热传导时,这四条守恒律就是二维可压缩 Euler 方程组。
Take any fixed region of the fluid. The mass, the momentum in two directions and the total energy inside it can change only through its boundary: the fluid carries these quantities in and out as it crosses the boundary, and the pressure on the boundary pushes the fluid and does work on it. Without viscosity and heat conduction, these four conservation laws are the two-dimensional compressible Euler equations.
拟一维 Euler 的三分量状态现在多了一个横向动量。二维没有变截面积源项:几何直接进入每个面的面积向量(第 2 节)。采用理想气体 , 是单位体积总能量,包含两个方向的动能;压力计算漏掉横向动能,就会破坏原始方程及所有导数。
The quasi-one-dimensional Euler state gains a transverse momentum component. There is no variable-area source: geometry enters through each face area vector (Section 2). Use an ideal gas with . The total energy per unit volume includes kinetic energy in both directions; omitting the transverse kinetic energy corrupts both the primal equations and their derivatives.
第一行是质量守恒;第二、三行是两个方向的动量守恒,压力 像通量一样出现在其中;第四行是能量守恒,能量通量 包括流体携带的总能量和压力所做的功。四个方程里有 五个量,状态方程 把它们闭合;声速 决定下面的特征速度。
The first row is conservation of mass; the second and third are conservation of momentum in the two directions, where the pressure appears like a flux; the fourth is conservation of energy, whose flux holds the total energy carried by the fluid and the work done by the pressure. The four equations contain five quantities ; the equation of state closes them, and the speed of sound sets the characteristic speeds below.
对任意控制体 积分,并用散度定理把通量散度的体积分变成边界积分,就得到积分形式。记 为 边界的单位外法向, 为法向速度, 是穿过边界的法向通量:
Integrating over any control volume , and turning the volume integral of the flux divergence into a boundary integral with the divergence theorem, gives the integral form. With the outward unit normal of the boundary of and the normal velocity, is the normal flux through the boundary:
本页只研究定常解。令 ,要离散的就是定常的积分形式:对计算域 中的每个控制体,穿过它边界的净通量为零。第 2 节把控制体取成网格的单元(格心)或中位对偶体(格点),第 3 节把边界积分写成各个面上的通量之和。
This page studies steady solutions only. With , the equation to be discretised is the steady integral form: for every control volume in the domain , the net flux through its boundary vanishes. Section 2 takes the control volumes to be the cells (cell-centred) or the median-dual volumes (node-centred) of a mesh, and Section 3 writes the boundary integral as a sum of fluxes over faces.
本页的来流马赫数低,绕流光滑、没有激波:第 3 节的一阶通量不需要限制器,第 14 节的复数步长也适用于整条计算链。激波及其伴随不在本页讨论。
The free-stream Mach number of this page is low and the flow past the body is smooth, without shocks: the first-order flux of Section 3 needs no limiter, and the complex step of Section 14 applies to the whole chain of computations. Shocks and their adjoints are not treated on this page.
图 1.1 把这个问题画在一张图上(基准状态 、,本页代码的收敛解,单元平均值;第 2 节起讲怎样算)。左边是计算域:物面八边形(琥珀色)与远场八边形(蓝色)之间的区域;每个控制体按当地马赫数 着色,短箭头是当地速度,远场的实线面是入流面,虚线面是出流面。来流从左下方流入:流体在物体上游一侧减速,在物体上下两侧加速;当地马赫数在 0.176 到 0.236 之间,最高值在物体下侧紧贴物面的控制体里,处处远小于 1。右边是物面压力:迎风一侧高于来流压力 ,背风一侧低于它,流体在物体后面没有恢复压力。第 7 节说明这主要是本页固定数值耗散造成的离散误差,这里的阻力不为零也是因为它。
Figure 1.1 draws the problem in one picture (base state , ; the converged solution of this page’s code, cell averages; Section 2 onwards shows how it is computed). On the left is the domain, the region between the body octagon (amber) and the far-field octagon (blue); every control volume is coloured by its local Mach number , the short arrows are the local velocity, and the solid far-field faces are inflow faces, the dashed ones outflow faces. The free stream comes from the lower left: the fluid slows down on the upstream side of the body and speeds up above and below it; the local Mach number lies between 0.176 and 0.236, highest in a volume next to the wall on the lower side of the body, and far below 1 everywhere. On the right is the wall pressure: above the free-stream pressure on the upstream side and below it on the downstream side, where the flow does not recover its pressure behind the body. Section 7 shows that this is mostly discretisation error caused by the page’s fixed numerical dissipation, which is also why the drag here is not zero.
特征速度:一个边界要给几个条件
Characteristic speeds: how many conditions a boundary takes
把法向通量 对 求导,得到 的 Jacobian 。它有四个实特征值:
Differentiating the normal flux with respect to gives the Jacobian . It has four real eigenvalues:
它们是小扰动沿 传播的速度:两个声波以 传播,另外两个量(熵与切向速度)以流体的法向速度 随流输运。第 4 节由 Riemann 不变量得到同样的四个速度;第 8 节在代码的通量 Jacobian 上核对它们的和与积。
They are the speeds at which small disturbances travel along : two sound waves move at , and two more quantities (the entropy and the tangential velocity) are carried with the normal velocity of the fluid. Section 4 finds the same four speeds from the Riemann invariants; Section 8 checks their sum and product on the flux Jacobian of the code.
在边界上取 指向域外。速度为负的波从外部进入计算域,它携带的信息只能由边界提供;速度为正的波离开计算域,它的值由域内决定,若在边界上再指定它,问题就过定了。所以边界条件的个数等于负特征值的个数:
At a boundary take pointing out of the domain. A wave with a negative speed enters the domain from outside, and the information it carries can only come from the boundary; a wave with a positive speed leaves the domain, its value is decided inside, and prescribing it at the boundary as well would over-determine the problem. The number of boundary conditions is therefore the number of negative eigenvalues:
- 超声速入流():四个特征值都为负,给 4 个条件,即整个状态;
- 亚声速入流():,给 3 个条件;
- 亚声速出流():只有 ,给 1 个条件;
- 超声速出流():没有负特征值,不给条件;
- 滑移壁面():只有 ,给 1 个条件,就是无穿透条件 本身; 的两个量沿壁面输运。
- supersonic inflow (): all four eigenvalues are negative; 4 conditions, the whole state;
- subsonic inflow (): ; 3 conditions;
- subsonic outflow (): only ; 1 condition;
- supersonic outflow (): no negative eigenvalue; no condition;
- slip wall (): only ; 1 condition, the no-penetration condition itself; the two quantities with travel along the wall.
图 1.2 在本页的收敛解上画出这些速度。上半部分是三个边界面:远场入流面 的四个速度中有 3 个为负,出流面 只有 为负,物面 上 ,只有 为负。下半部分把条件个数画成法向马赫数 的函数:本页全部 8 个远场面的 都不超过 0.18,远在亚声速带内。所以远场入流面从来流取 3 个条件,出流面取 1 个,物面只要 。第 4 节用 Riemann 不变量 、熵函数 与切向速度 实现远场的这种取舍,并把 写成只含压力的壁面通量。
Figure 1.2 draws these speeds on this page’s converged solution. The upper part shows three boundary faces: on the far-field inflow face , 3 of the 4 speeds are negative; on the outflow face , only is; and on the wall face , and only is negative. The lower part draws the number of conditions as a function of the normal Mach number : all 8 far-field faces of this page have at most 0.18, far inside the subsonic band. A far-field inflow face therefore takes 3 conditions from the free stream, an outflow face 1, and the wall only . Section 4 implements this choice at the far field with the Riemann invariants , the entropy function and the tangential velocity , and writes as a wall flux that contains only the pressure.
本页的问题:绕物体的亚声速流动
The problem on this page: subsonic flow past a body
计算域是一个闭合物体与外边界之间的区域(图 1.1 左)。物体是一条光滑的闭曲线,外边界是半径 1.9 的圆;计算实际使用两条曲线上各 8 个网格节点连成的八边形,第 2 节给出物面曲线并构造网格。物面是滑移壁面;外边界是远场,在那里流动应当回到均匀的来流。
The domain is the region between a closed body and an outer boundary (left of Figure 1.1). The body is a smooth closed curve and the outer boundary a circle of radius 1.9; the computation uses the octagons through the 8 mesh nodes on each of the two curves, and Section 2 gives the body curve and builds the mesh. The body is a slip wall; the outer boundary is the far field, where the flow should return to the uniform free stream.
来流固定为 、,速度大小为 ,方向为攻角 。于是 , 正好是来流马赫数。每次原始求解都从所有控制体都等于 的均匀流出发。第 7 节的目标值,以及第 10–14 节对 、 的导数,都以这个无量纲化为准。
The free stream is fixed at and , with speed in the direction of the incidence . Hence and is exactly the free-stream Mach number. Every primal solve starts from the uniform state in all volumes. The objective values of Section 7 and the derivatives with respect to and in Sections 10–14 refer to this scaling.
设计变量是 :两个来流参数,加上全部 24 个网格节点的坐标 (48 个数),共 50 个分量。按伴随优化文献的通常写法,设计变量记作 。移动物面节点改变物体的形状,移动远场节点改变外边界,移动其余节点只改变网格;它们都会改变离散解和目标。第 10 节给出这些分量的排列,第 12 节求对全部坐标的导数。
The design variables are : the two free-stream parameters and the coordinates of all 24 mesh nodes (48 numbers), 50 components in all. As is usual in the adjoint-optimization literature, the design variables are written . Moving a body node changes the shape of the body, moving a far-field node changes the outer boundary, and moving any other node changes only the mesh; each of them changes the discrete solution and the objective. Section 10 lists the components, and Section 12 differentiates with respect to all coordinates.
第 7 节给出设计的目的:目标 是流体作用在物体上的压力合力的一个分量,默认是 分量 ,页首可以改成 分量 。它由物面压力(图 1.1 右)逐面求和得到;第 7 节还讨论在这些粗网格上,这个力有多少是物理的。
Section 7 states the purpose of the design: the objective is one component of the pressure force that the fluid exerts on the body, by default the component ; the header can switch it to the component . It is summed face by face from the wall pressures (right of Figure 1.1); Section 7 also discusses how much of this force is physics on these coarse meshes.
这组方程的作用
What these equations are for
在流体力学中,Euler 方程组描述不计黏性和热传导的可压缩流动。黏性只在物面附近的薄边界层和尾迹里起作用时,例如高雷诺数下的流线型物体,它给出其余区域的速度和压力;它包含声波,流速更高时还包含激波(本页不讨论);它也是 Navier–Stokes 方程组去掉黏性应力和热传导后剩下的部分。无黏流中物体受的力全部来自物面压力;另一方面,稳态、亚声速、无激波的无黏绕流阻力为零(d'Alembert 佯谬,第 7 节),这提醒我们它没有描述什么。气动外形设计常先用 Euler 方程组计算压力载荷,再用伴随求载荷对大量外形参数的梯度,这正是本页的内容。
In fluid dynamics the Euler equations describe compressible flow without viscosity and heat conduction. Where viscosity matters only in thin boundary layers next to walls and in wakes, as for streamlined bodies at high Reynolds numbers, they give the velocity and the pressure everywhere else; they contain sound waves and, at higher speeds, shock waves (not treated here); and they are what remains of the Navier–Stokes equations without viscous stresses and heat conduction. In inviscid flow the force on a body comes entirely from the wall pressure; on the other hand, steady, subsonic, shock-free inviscid flow past a body has zero drag (d’Alembert’s paradox, Section 7), a reminder of what the equations leave out. Aerodynamic shape design often computes pressure loads with the Euler equations first and then uses an adjoint for the gradient of a load with respect to many shape parameters, which is the subject of this page.
在本课程中,这组方程把前面两类页面接在一起:
In this course the equations join the two earlier kinds of page:
- 同一张网格:二维标量页的混合网格与面积向量(第 2 节)、面循环(第 5 节)原样沿用,每个控制体的未知量从 1 个变成 4 个;
- 同样的热力学与特征边界:状态方程、声速与 Riemann 不变量来自拟一维 Euler,二维的新内容是在远场先把速度分解到面的法向与切向(第 4 节);
- 一个方程组:Jacobian 的每个非零元变成一个 块(第 6 节),前向与伴随按块传播;
- 几何也是设计变量:节点坐标进入面积向量、面长与单位法向,第 12 节对它们求导;
- 一个光滑的小例子:96 个未知量,没有激波,每个导数都能用有限差分与复数步长核对(第 13、14 节)。
- The same mesh. The hybrid mesh and the area vectors (Section 2) and the face loop (Section 5) of the 2-D scalar pages carry over unchanged; each control volume now holds 4 unknowns instead of 1.
- The same thermodynamics and characteristic boundaries. The equation of state, the sound speed and the Riemann invariants come from quasi-1-D Euler; what is new in two dimensions is that the far field first splits the velocity into the normal and tangential components of a face (Section 4).
- A system. Every nonzero entry of the Jacobian becomes a block (Section 6), and the tangent and the adjoint propagate blocks.
- Geometry as design variables. The node coordinates enter the area vectors, face lengths and unit normals, and Section 12 differentiates through them.
- A small, smooth example. 96 unknowns and no shocks, so every derivative can be checked with finite differences and complex steps (Sections 13 and 14).
图 1.3 把本页放在整个网站的路线上,每个方框都链接到那一页的第 1 节。每往右一步主要增加一类新内容:一维 Burgers 只有一个非线性对流方程;拟一维 Euler 换成质量、动量、能量三个方程,并引入压力和变截面积;本页增加第二个空间方向,面有了法向,网格坐标也成为设计变量;二维层流 N–S 再加入黏性应力和热传导,面上要用到梯度;二维 RANS–SA 再加一个湍流模型方程。每点的未知量从 1 个增加到 5 个,但每一页的残差仍由面循环组装,前向与伴随也沿同一个循环求导。另有两页与本页相邻:二维标量对流页用的是同一张网格(见它的第 2 节),一维层流 N–S 页在一维的 Euler 方程上加入黏性和热传导。
Figure 1.3 places this page on the route through the site; every box links to Section 1 of its page. Each step to the right mainly adds one kind of ingredient: 1-D Burgers has a single nonlinear convection equation; quasi-1-D Euler replaces it by three equations (mass, momentum and energy) and brings in the pressure and a varying cross-section; this page adds a second space direction, so faces have normals and the mesh coordinates become design variables; 2-D laminar N–S adds viscous stresses and heat conduction, which need gradients at the faces; 2-D RANS–SA adds one turbulence-model equation. The unknowns per point grow from 1 to 5, yet every page still assembles its residual with face loops, and the tangent and the adjoint differentiate the same loops. Two more pages sit next to this one: the 2-D scalar advection page uses the same mesh (see its Section 2), and the 1-D laminar N–S page adds viscosity and heat conduction to the Euler equations in one dimension.
本页要回答的问题
The question this page answers
本页假设读者熟悉微积分、矩阵运算、Newton 法和 Krylov 迭代(GMRES),最好读过二维标量对流页(同一张网格与面循环)和拟一维 Euler 页(同样的热力学与特征边界);本页用到的内容都会重新给出。
This page assumes calculus, matrix algebra, Newton’s method and a Krylov solver (GMRES), and it helps to have read the 2-D scalar advection page (the same mesh and face loop) and the quasi-1-D Euler page (the same thermodynamics and characteristic boundaries); the parts this page uses are restated here.
本页只回答一个问题:设计变量 改变一点,压力合力 改变多少? 有 50 个分量。前向(tangent)路线每个分量解一次线性方程,共 50 次;伴随路线只解一次转置方程,就得到全部分量(第 11 节)。页首的全页路线图把后面各节串成一条线。按页首默认设置实际计数,50 次前向求解共用 2400 次 GMRES 迭代,1 次伴随求解只用 52 次。
This page answers one question: how much does the pressure force change when the design changes a little? has 50 components. The tangent (forward) route solves one linear system per component, 50 in all; the adjoint route solves a single transposed system and obtains every component (Section 11). The page roadmap at the top strings the later sections together. Counted with the default settings at the top of the page, the 50 tangent solves take 2400 GMRES iterations in total and the single adjoint solve only 52.
要在计算机上回答这个问题,先要把连续的方程组变成有限个未知量的代数方程。第 2 节沿用二维标量的网格,决定四个守恒量存放在哪里;第 3 节再把定常的积分形式写成各个面上的通量之和。
To answer it on a computer, the continuous equations must first become algebraic equations in finitely many unknowns. Section 2 reuses the mesh of the 2-D scalar pages and decides where the four conserved variables live; Section 3 then writes the steady integral form as a sum of fluxes over faces.
网格:沿用二维标量的混合 O 型网格
Mesh: the scalar example’s hybrid O-grid
第 1 节的方程组对计算域中的每一点都成立,计算机却只能存放有限个数。本节沿用二维标量页的网格,决定四个守恒量存放在哪里,以及每个面的面积向量怎样计算。
The equations of Section 1 hold at every point of the domain, but a computer stores finitely many numbers. This section reuses the mesh of the 2-D scalar pages and decides where the four conserved variables live and how the area vector of every face is computed.
基础网格的 24 个节点坐标、8 个四边形和 16 个三角形,逐点、逐单元与二维标量一致。节点分布在三圈上,每圈取 8 个角度 :内圈取在下面的物面曲线上,外圈取在半径 1.9 的圆上,中圈按 0.45 的比例把两者线性混合。数组从 0 开始编号;边界的 表示没有右控制体,绝不能拿它访问数组。
The 24 base nodes, 8 quadrilaterals and 16 triangles match the scalar example point for point and cell for cell. The nodes lie on three rings at the 8 angles : the inner ring on the body curve below, the outer ring on the circle of radius 1.9, and the middle ring blends the two with weight 0.45. Arrays are zero-based. marks an absent exterior control volume and must never be used as an array index.
图 2.1 把上式与三圈节点画在一起:虚线是曲线本身,实线是计算真正使用的多边形。
Figure 2.1 draws this curve together with the three rings of nodes: the dashed lines are the curves themselves, the solid lines the polygons the computation actually uses.
相邻节点用直线相连,所以计算中的物面是内接于这条曲线的八边形,远场是半径 1.9 的正八边形;本页所有导数都针对这两条多边形边界。第 15 节的加密网格保持这两个八边形不变。
Neighbouring nodes are joined by straight edges, so the computational body is an octagon inscribed in this curve and the far field is a regular octagon of radius 1.9. Every derivative on this page refers to these polygonal boundaries. The refined meshes of Section 15 keep both octagons unchanged.
每个原始单元保存四个守恒分量,单元平均值显示在顶点平均中心。每条边就是一个计算面;逆时针绕单元走时,面积向量指向流出该单元的方向。这里共有 24 个控制体、48 个面、96 个状态自由度。
Each original cell stores four conserved averages, displayed at its vertex-average centre. An edge is a computational face. Walking counterclockwise around a cell gives an outward area vector. There are 24 volumes, 48 faces and 96 state degrees of freedom.
图 2.2 左边给出这 24 个控制体的编号,右边给出它们在状态向量里的位置:控制体 的四个分量占第 到 个位置。残差 和伴随 按同样的顺序存放,所以三个向量的第 个分量指同一个控制体、同一条守恒方程( 依次是质量、 动量、 动量、能量)。
The left of Figure 2.2 numbers the 24 control volumes and the right shows where they sit in the state vector: the four components of volume occupy positions to . The residual and the adjoint are stored in the same order, so component of all three vectors refers to the same volume and the same conservation equation (: mass, momentum, momentum, energy).
这里 与 是面的起点和终点(格点页的对偶面按各段相加)。面积向量 已经包含面长;单位法向 只用于第 4 节的特征分解,两者不能混用。
Here and are the start and end points of the face (a dual face on the node page sums its segments). The area vector already includes the face length; the unit normal is used only for the characteristic split of Section 4, and the two must not be mixed.
图 2.3 以一个控制体为例画出上面的约定。
Figure 2.3 illustrates this convention for one control volume.
逐步推导:每个控制体的面积向量之和为零
Step by step: the area vectors of every control volume sum to zero
第一步,单元是一个多边形。沿逆时针方向依次取边界折线的顶点 ,绕一圈后回到 ;第 段从 到 ,它的外法向面积向量是把边向量顺时针转 90°:
Step 1, a cell is a polygon. Take the vertices of its boundary counterclockwise, , returning to after one loop; segment runs from to , and its outward area vector is the edge vector turned clockwise by 90°:
第二步, 是线性运算;绕一圈的边向量首尾相消,和为零:
Step 2, is linear, and the edge vectors of one closed loop telescope to zero:
第三步,代码按面存储 (从 指向 ),控制体 的外法向是 ,符号与第 5 节的 相同。于是 。物理通量对面积向量是线性的,所以均匀状态 在每个不含边界面的控制体上给出 ,耗散项也因 而为零:均匀状态在所有内部控制体上的残差都是零。第 6 节的对角块和第 7 节的压力合力都用到同一个事实。
Step 3, the code stores per face (pointing from to ); the outward vector of volume is , with the sign of Section 5. Hence . The physical flux is linear in the area vector, so in every volume without boundary faces a uniform state gives , and the dissipation vanishes because : a uniform state has zero residual in every interior volume. The diagonal blocks of Section 6 and the pressure force of Section 7 use the same fact.
核对:全部 24 个控制体上 的最大值为 2×10⁻¹⁶,而最长的面长为 1.454(本页代码在构建时计算)。
Check: the largest over all 24 volumes is 2×10⁻¹⁶, while the longest face has length 1.454 (computed by this page’s code at build time).
点击下面面板中的控制体或面,可查看编号、法向和守恒量;上方可切换着色的物理量,色条给出数值范围,图例说明物面(琥珀色)、远场(蓝色)与内部面的画法。
Click a volume or a face in the panel below to inspect its index, normal and state. The selector changes the plotted quantity, the colour bar gives its range, and the legend explains how body faces (amber), far-field faces (blue) and interior faces are drawn.
控制体、面和面积向量都已确定,状态也按控制体排好了顺序。第 3 节把第 1 节的法向通量写到每个面上,得到四分量的数值通量。
The control volumes, the faces and their area vectors are now fixed, and the state is stored volume by volume. Section 3 writes the normal flux of Section 1 on every face and obtains the four-component numerical flux.
面上的四分量通量
The four-component flux on a face
第 1 节的积分形式要求每个控制体边界上的净通量为零。控制体的边界由面组成;第 2 节中面 的面积向量 已经包含面长,所以面上的物理通量就是第 1 节的 乘以面长:
The integral form of Section 1 asks for zero net flux through the boundary of every control volume. That boundary consists of faces, and the Section 2 area vector of face already includes its length, so the physical flux through a face is the of Section 1 times the face length:
首版使用一阶分片常值重构与固定耗散系数 的 Rusanov 形式通量。固定 是本例明确选定的数值方法,因此它对状态的导数为零;不是对一个本应依赖状态的最大波速偷偷冻结导数。页面检查 。代价是耗散很强: 时 是来流速度的 8.5 倍。第 7 节和第 15 节量化这对压力和载荷意味着什么。
This first version uses piecewise-constant reconstruction and a Rusanov-form flux with the fixed dissipation coefficient . A constant is part of the specified method, so its state derivative is zero; it is not an omitted derivative of a state-dependent maximum wave speed. The page checks . The price is strong dissipation: at , is 8.5 times the free-stream speed. Sections 7 and 15 quantify what this does to pressures and loads.
图 3.1 把上式拆成代码里的几步:两侧各自从守恒量算出原始变量和物理通量,再与只依赖 的耗散项组合。图 3.2 在一个具体的面上给出两部分的数值。
Figure 3.1 splits this formula into the steps of the code: each side computes its primitive variables and physical flux from the conserved variables, which are then combined with a dissipation term that depends only on . Figure 3.2 gives the two parts on one particular face.
逐步推导:压力、声速与它们对 的梯度
Step by step: pressure, sound speed and their gradients with respect to
第一步,把速度写成守恒量之比 、,动能就是 。代入第 1 节的状态方程,压力是四个守恒量的显式函数(代码 prim() 算出的 p):
Step 1, write the velocity as ratios of conserved variables, and ; the kinetic energy is . Substituted into the equation of state of Section 1, the pressure is an explicit function of the four conserved variables (the p computed by prim() in the code):
第二步,对每个分量求偏导,其余三个固定。只有动能项依赖 、、:
Step 2, differentiate with respect to each component, holding the other three fixed. Only the kinetic energy depends on , and :
这正是第 8 节 公式中各项的系数。第三步,声速 。对 两边求前向导数,,再用 :
These are exactly the coefficients of the formula of Section 8. Step 3, the sound speed is . The forward derivative of is , and with :
第 4 节的壁面通量、第 7 节的目标函数、第 8 节的通量 Jacobian 和远场的 Riemann 不变量 都要用这两个梯度。
The wall flux of Section 4, the objective of Section 7, the flux Jacobian of Section 8 and the far-field Riemann invariants all use these two gradients.
核对:第 4 节 riemann() 的计算图与残差共用同一个 prim();由它给出的 与 按 、 换算出 与 ,与上面两式在全部 24 个控制体的收敛状态上最大相差 4×10⁻¹⁶(本页代码在构建时计算)。
Check: the computation graph of riemann() in Section 4 shares prim() with the residual; its and , converted with and , give and , which differ from the two formulas above by at most 4×10⁻¹⁶ over the converged states of all 24 volumes (computed by this page’s code at build time).
# U = [rho, rho*u, rho*v, rho*E]; n = area vector (length l)
un_l = (U[1]*nx + U[2]*ny)/U[0] # l*u_n
p = (gamma-1)*(U[3]-(U[1]**2+U[2]**2)/(2*U[0]))
Fn = [U[0]*un_l, U[1]*un_l+p*nx, U[2]*un_l+p*ny, (U[3]+p)*un_l]
H = (Fn(UL,n)+Fn(UR,n))/2 - alpha*length(n)*(UR-UL)/2内部面两侧都有控制体, 与 都是未知量。边界面只有一侧:物面外面是物体,远场外面没有网格。第 4 节给这两类面补上通量:物面只传递压力,远场按第 1 节的特征计数,从来流和域内各取一部分信息,重建外侧状态 。
An interior face has a control volume on each side, and both and are unknowns. A boundary face has only one: beyond the wall lies the body, and beyond the far field there is no mesh. Section 4 completes the flux on both kinds of face: the wall passes only the pressure, and the far field rebuilds an exterior state from the free stream and the interior, taking from each as many pieces of information as the characteristic count of Section 1 allows.
边界:滑移壁面与亚声速特征远场
Boundaries: slip walls and subsonic characteristic far field
第 1 节按特征速度的符号数过条件:物面只要一个条件 ,远场入流面要三个,出流面要一个。本节把它们写成边界面上的通量。
Section 1 counted the conditions by the signs of the characteristic speeds: the wall needs a single condition, , a far-field inflow face three and an outflow face one. This section turns them into fluxes on the boundary faces.
这里与二维标量最重要的区别在物面。无穿透只令质量和能量通量为零;压力仍传递两个方向的动量。壁面通量采用内侧压力 ,不需要把四个守恒量强行赋值。格心和格点都在边界面上弱施加这条通量规则;格点位于物面并不意味着可以把四行全换成 Dirichlet 约束。
The body boundary is the key difference from the scalar example. No penetration removes the mass and energy fluxes, but pressure still transfers momentum. The wall flux uses the interior pressure without assigning all four conserved values. Both schemes impose this flux weakly; placing a node on the wall does not justify replacing all four equations by Dirichlet constraints.
上面的点表示前向(tangent)导数: 是程序变量 沿给定输入方向的导数,这里 来自状态,、 来自几何。反向(伴随)量写作 。第 8 节完整说明这套自动微分记号。
The dot marks a forward (tangent) derivative: is the derivative of the program variable along a given input direction; here comes from the state and , from the geometry. Reverse (adjoint) quantities are written . Section 8 sets out this algorithmic-differentiation notation in full.
图 4.1 在一个物面面上对比这条规则与内部面的公式。
Figure 4.1 compares this rule with the interior-face formula on one wall face.
逐步推导:壁面通量的 Jacobian 与它的转置
Step by step: the Jacobian of the wall flux and its transpose
第一步,壁面通量只通过压力依赖内侧状态,另外依赖面积向量(代码 flux() 中 bc 为 wall 的分支):
Step 1, the wall flux depends on the interior state only through the pressure, and otherwise on the area vector (the wall branch of flux() in the code):
第二步,用第 3 节的 做链式法则。第 0 行与第 3 行恒为零,第 1、2 行是同一个行向量乘以 、,所以这个 块的秩是 1:
Step 2, apply the chain rule with from Section 3. Rows 0 and 3 are identically zero and rows 1 and 2 are the same row vector times and , so this block has rank one:
第三步,转置。反向扫描把面通量的伴随 (四个分量)乘以这个块的转置;两行非零的块转置后只剩一个组合:
Step 3, transpose. The reverse sweep multiplies the adjoint of the face flux (four components) by the transposed block; with two nonzero rows, only one combination survives:
质量和能量两个分量 、 根本不经过物面传回;经过物面的只有动量分量的伴随在法向上的投影 ,也就是壁面压力的伴随,它再按 分给四个守恒量。第 16 节的连续物面条件约束的正是同一个组合 。第四步,几何导数:状态固定时 ,即上面的 公式中的 、 两项。
The mass and energy components and do not return through the wall at all; only the normal projection of the momentum adjoints, , passes: it is the adjoint of the wall pressure, and distributes it over the four conserved variables. The continuous wall condition of Section 16 constrains exactly the same combination . Step 4, the geometry derivative: at fixed state , which are the terms and of the formula above.
核对:在全部 8 个物面面上,计算图对单位种子的前向结果与上面的矩阵最大相差 7×10⁻¹⁸;以伴随解 作为 时,计算图的逆序结果与第三步的公式最大相差 4×10⁻¹⁹(本页代码在构建时计算)。
Check: on all 8 wall faces the forward results of the computation graph for unit seeds differ from the matrix above by at most 7×10⁻¹⁸; with the adjoint as , the reversed graph differs from the formula of Step 3 by at most 4×10⁻¹⁹ (computed by this page’s code at build time).
远场:先旋转到法向与切向
Far field: rotate to normal and tangential components
设 为指向域外的单位法向, 为切向。速度分解为 与 ,反过来 。
Let be the unit normal pointing out of the domain and the tangent. Split the velocity into and ; conversely .
Riemann 不变量从哪里来
Where the Riemann invariants come from
在面附近只保留沿 的变化(坐标记为 ),得到一维 Euler 方程。对等熵流,,而 给出 ,所以 。把连续方程乘 后与动量方程相加或相减,得到两个沿特征输运的量;熵函数 与切向速度则以速度 随流输运。
Near the face keep only variations along (coordinate ); this gives the one-dimensional Euler equations. For isentropic flow , and gives , so . Adding or subtracting times the continuity equation to the momentum equation gives two quantities transported along characteristics; the entropy function and the tangential velocity move with the flow at speed .
亚声速时 , 总从域内来;, 总从域外来。 与 的速度是 :入流面()从来流取,出流面()从域内取。所以入流面从外部取三项信息、出流面只取一项,不能像标量那样把整个状态一起复制。这个闭合只在局部一维、等熵的意义下是特征关系,不宣称是精确无反射边界。
For subsonic flow , so always comes from inside; , so always comes from outside. and move at : they come from the free stream at an inflow face () and from inside at an outflow face (). An inflow face therefore takes three pieces of outside information and an outflow face only one; copying the whole state as for the scalar is inappropriate. The closure is characteristic only in the local one-dimensional, isentropic sense; it is not claimed to be exactly non-reflecting.
重建边界状态
Rebuilding the boundary state
的公式来自 。远场面的通量仍是第 3 节的 。下表用本页代码逐步算出一个入流面和一个出流面(、,与页首选择无关; 时 、、)。交互面板可以对任意远场面重复这些步骤。
The formula follows from . A far-field face still uses the flux of Section 3. The table applies these steps with this page’s code to one inflow and one outflow face (, , independent of the header; with , , and ). The interactive panel repeats the steps for any far-field face.
| 量quantity | 入流面inflow face | 出流面outflow face |
|---|---|---|
| 面 face | 42 | 40 |
| 分支(冻结)branch (frozen) | 入流inflow | 出流outflow |
| (−0.38268, 0.92388) | (0.92388, 0.38268) | |
| 内侧 interior | 1.0063, 1.011 | 0.95654, 0.94891 |
| 内侧 interior | −0.039928, −0.2427, 1.186 | 0.21195, −0.049215, 1.1785 |
| 来流 free stream | −0.051219, −0.23103, 1.1832 | 0.23103, −0.051219, 1.1832 |
| 5.89 | 6.1044 | |
| −5.9673 | −5.685 | |
| −0.038646 | 0.20966 | |
| 1.1857 | 1.1789 | |
| (取自) (taken from) | 1 (∞) | 1.0098 (L) |
| (取自) (taken from) | −0.23103 (∞) | −0.049215 (L) |
| 1.0107 | 0.9584 | |
| 1.015 | 0.95149 | |
| 0.22824, 0.052709 | 0.21254, 0.034765 | |
| (1.0107, 0.23067, 0.053271, 2.5652) | (0.9584, 0.20369, 0.033319, 2.4009) |
为什么入/出流分支冻结在基准来流
Why the inflow/outflow branch is frozen at the base free stream
入流还是出流是一个判断语句,在 处不连续。如果每次计算残差都重新判断, 就不再是 与设计变量的光滑函数,Newton、tangent、adjoint 和差分都可能跨过这个跳跃。本页在求解之前,用基准几何的法向和基准来流方向判断每个远场面( 为入流),然后在整个求解和全部求导中保持不变。这样 在基准点附近是一个固定的光滑函数,对 求导也不会切换分支。页面再检查收敛后的 与冻结的分支同号。若改变 、 或几何使某个面接近掠入射,分支可能改变,就必须重新判断并重新求解,而不能外推当前的导数。下面的 滑块演示这一点。
Inflow versus outflow is an if-statement, discontinuous at . Re-deciding it at every residual evaluation would make a non-smooth function of and the design variables, and Newton, tangent, adjoint and finite differences could straddle the jump. Before the solve, this page classifies each far-field face with the base geometry normal and the base free-stream direction ( means inflow) and keeps that choice for the whole solve and every derivative. is then one fixed smooth function near the base point, and differentiating with respect to never switches a branch. The page checks that the converged has the frozen sign. If , or the geometry move a face towards grazing flow, the branch may change; then reclassify and re-solve instead of extrapolating the derivative. The slider below shows this.
伴随怎样穿过边界状态
How the adjoint passes through the boundary state
远场面通量同时经由 与 依赖内侧状态。记 为 Riemann 坐标,边界闭合就是 :对角矩阵 在出域特征(从域内取的量)处为 1、其余为 0,其余的量取自来流。于是:
A far-field flux depends on the interior state both through and through . With the Riemann coordinates , the closure is : the diagonal matrix is 1 on the outgoing characteristics (taken from inside) and 0 elsewhere; the rest comes from the free stream. Hence:
这里 是 的局部通量 Jacobian, 是该面通量 的伴随(反向扫描交给这个面的种子,第 9 节)。反向时 先乘 ,得到边界状态的伴随 ,再经过 加到 :因为 把来流分量置零,只有出域特征的分量能回到内侧状态。代码不手写这个矩阵,而是逆序执行 far() 的每个标量运算;附录 A 实测两者一致到舍入误差。
Here is the local flux Jacobian and the adjoint of the face flux (the seed the reverse sweep hands to this face, Section 9). In reverse, is first multiplied by , giving the adjoint of the boundary state, which then passes through into ; because zeroes the free-stream components, only outgoing characteristic components return to the interior state. The code never writes this matrix by hand: it reverses every scalar operation of far(), and Appendix A measures agreement to round-off.
交互面板: 滑块(与页首无关)用新的来流方向重新判断远场面并重新求解。特征图画出所选入流面与出流面上的四个特征速度 、、、,箭头说明每个量来自域内还是域外。表中壁面行没有边界状态,所以不显示边界声速。下方的逐步表和 针对所选远场面现场计算,并把当前伴随解 作为 展示两条反传路径。
Interactive panel: the slider (independent of the header) reclassifies the far-field faces for the new free-stream direction and re-solves. The characteristic diagram draws the four speeds , , , at the selected inflow and outflow faces; arrows show whether each quantity comes from inside or outside. Wall rows have no boundary state, so they show no boundary sound speed. The step table and below are computed live for the selected far-field face, with the current adjoint as to show the two reverse paths.
残差:同一个面,两个相反贡献
Residual: one face, two opposite contributions
先固定离散问题与符号
Fix the discrete problem and its notation
本页每个单元存储 4 个未知量。以下用 表示把所有位置、所有分量按代码顺序排成的列向量。它是一个有限维向量,不是连续函数。物理方程中的空间导数只用于说明模型;求离散伴随时,求导对象是本页实际装配的有限维函数。
Each cell stores 4 unknowns. Below, stacks all locations and components in code order. 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.
是本页全部设计变量排成的向量:来流马赫数 、攻角 与全部节点坐标 。第 11 节的 与 就是下面方框里的两式(行、列只差一次转置)。局部通量 Jacobian(如 )与全局残差 Jacobian 是不同的对象。每个位置的局部状态 是一个小列向量,全局的 才是 Jacobian 的输入。
stacks all design variables of this page: the free-stream Mach number , the angle and all node coordinates . The and of Section 11 are the two boxed formulas below (row and column forms differ by one transpose). A local flux Jacobian such as is a different object from the global residual Jacobian . A local state is a small column vector; the global is the input of the Jacobian.
这里 、 都是列向量,它们的转置才是偏导数行。求导采用自动微分(AD)的记号:程序变量 上加点的 是前向(tangent)模式沿给定种子方向的导数,加横线的 是反向(adjoint)模式累加到 上的权重,例如 与 。两者都是对本页有限维代码求导;变分符号 只用于第 16 节与附录 A 的连续伴随里连续函数的变分。
Both and are columns; their transposes are the derivative rows. Derivatives use the notation of automatic differentiation (AD): a dot on a program variable is its forward (tangent) derivative along the chosen seed direction, and a bar is the weight accumulated into in reverse (adjoint) mode, for example and . Both differentiate the finite-dimensional code of this page; the variation symbol is kept for variations of continuous functions in the continuous adjoint of Section 16 and Appendix A.
一个控制体现在对应四条积分平衡方程。 是四分量流出通量之和,稳态要求全部分量为零。内部面只计算一次 ,并向左加、向右减。这使全域求和时内部贡献精确抵消,是几何导数和伴随也必须保留的计算结构。
Each volume now has four integrated balance equations. sums its outward fluxes; steady state requires every component to vanish. An internal face computes once, adds it to the left and subtracts it from the right. Internal contributions cancel globally, a structure also preserved by geometry derivatives and adjoints.
是全部设计变量(来流 、 与节点坐标)排成的向量,第 10 节给出它的内容。这里没有除以控制体体积。若写非定常方程,则是 。当前 Newton 法求解的对象是积分残差,其 Jacobian、收敛阈值和伴随乘子都沿用这一约定。播放器逐面显示读取、通量、左累加、右累加,四个分量可以分别查看;“播放”按钮会自动逐步前进。
stacks all design variables (the free-stream , and the node coordinates); Section 10 lists them. Residuals are not divided by volume. An unsteady equation would be . Newton solves the integrated residual, so its Jacobian, tolerances and adjoint multipliers use that convention. The player exposes gather, flux, left scatter and right scatter for each face, with all four components selectable; the Play button steps automatically.
图 5.1 把 整个画出来,列的顺序就是面循环的顺序;图 5.2 放大其中一列:一个面的通量怎样进入 96 维的残差向量。
Figure 5.1 draws all of , with its columns in the order of the face loop; Figure 5.2 zooms into one column: how the flux of one face enters the 96-component residual vector.
逐步推导:内部贡献在全域求和中相消
Step by step: interior contributions cancel in the global sum
第一步,把所有控制体的第 个分量相加,并交换两个有限求和的次序:
Step 1, add component over all volumes and exchange the two finite sums:
第二步,内部面的一列恰有 和 各一个,列和为零;边界面的一列只有一个 。第三步,于是全域残差之和只剩边界通量:
Step 2, an interior column holds one and one and sums to zero; a boundary column holds a single . Step 3, so the sum of all residuals keeps only the boundary fluxes:
这就是离散守恒:内部面的通量只在控制体之间搬运守恒量,不产生也不消灭。它依赖“每个面只算一次 ”:如果左右两侧各算一次通量(例如用了不同的边界状态),抵消就不再精确。稳态时两边都为零,所以核对取 Newton 的第一个迭代 ,那里残差不为零。
This is discrete conservation: interior fluxes only move conserved quantities between volumes, creating or destroying nothing. It relies on computing once per face: if each side computed its own flux (for example with different boundary states), the cancellation would no longer be exact. At the steady state both sides vanish, so the check uses the first Newton iterate , where the residual is not zero.
核对:在 上 ,四个分量的全域和为 (−7.3×10⁻⁵, −2.9×10⁻⁴, −7.8×10⁻⁴, 1.0×10⁻³),与边界通量之和最大相差 3×10⁻¹⁶(本页代码在构建时计算)。
Check: at , ; the global sums of the four components are (−7.3×10⁻⁵, −2.9×10⁻⁴, −7.8×10⁻⁴, 1.0×10⁻³) and differ from the sums of the boundary fluxes by at most 3×10⁻¹⁶ (computed by this page’s code at build time).
R[:] = 0
for face in faces:
UL = U[L]; UR = U[R] if R >= 0 else boundary(UL)
H = flux_or_wall(UL, UR, n)
R[L,:] += H
if R >= 0: R[R,:] -= HJacobian、Newton 与 GS 预处理 GMRES
Jacobian, Newton and GS-preconditioned GMRES
标量 Jacobian 的一个非零数,现在变成一个 状态块。内部通量把相邻两个控制体耦合;边界状态又引入内侧状态的链式导数。我们保持精确算子 matrix-free,用 Newton 解非线性残差,再由 GMRES 解每次线性修正。
A scalar Jacobian entry becomes a state block. Interior faces couple neighbouring volumes, while boundary states add chain-rule terms. The exact operator stays matrix-free. Newton solves the nonlinear residual and GMRES solves each linear correction.
GS 预处理只取固定数值耗散形成的图 Laplacian 近似:内部面给两侧对角加 、相邻块加 ;边界只增加对角近似。取它的对角部分 与严格下三角部分 ,,做一次前向 GS 三角求解;伴随用 的反向扫描。这个 省略了压力和特征边界导数,仅负责加速,最终线性残差仍用完整的 或 检查。
The GS preconditioner approximates only the numerical dissipation: internal faces add to both diagonals and to neighbouring blocks; boundaries add a diagonal approximation. With its diagonal part and strictly lower part , one forward GS solve applies the inverse of . The adjoint uses a backward solve with . Pressure and characteristic-boundary derivatives are omitted only from , not from the exact operator used to check convergence.
图 6.1 画出本页收敛解上 的块结构与 的块结构。
Figure 6.1 shows the block structure of at this page’s converged solution next to that of .
逐步推导:内部控制体的对角块正好是
Step by step: the diagonal block of an interior volume is exactly
第一步,控制体 的对角块收集它的每个面对 的导数,符号就是 : 是 时取 ,是 时取 。
Step 1, the diagonal block of volume collects the derivative of each of its faces with respect to , with the sign : where is , and where is .
第二步,代入第 8 节推导的线性化 、。两种情况都在 上求值,并且都可以用外法向 写成同一个形式( 对 是线性的,):
Step 2, insert the linearisation derived in Section 8, and . Both are evaluated at , and both take the same form with the outward vector ( is linear in , so ):
第三步,第 2 节证明了 ,第一项消失;对不含边界面的控制体 ,剩下的正是 GS 预处理的对角元:
Step 3, Section 2 showed ; the first term vanishes, and for a volume without boundary faces what remains is exactly the diagonal of the GS preconditioner:
第四步,边界控制体不同:它的物面面用第 4 节的 代替 ,远场面多出 ,第二步的抵消不再成立, 是满的 块。第五步,非对角块 : 只保留其中的耗散部分 ,并且只放在下三角(编号较大的一行)。所以 在内部控制体的对角上是精确的,非对角块只近似了耗散部分。
Step 4, boundary volumes differ: a wall face contributes of Section 4 instead of , and a far-field face adds ; the cancellation of Step 2 fails and is a full block. Step 5, an off-diagonal block is : keeps only its dissipation part , and only in the lower triangle (the row with the larger index). So is exact on the diagonal of interior volumes and approximates the off-diagonal blocks by their dissipation part.
核对:8 个内部控制体上 ;其余 16 个边界控制体中差别最小的一个也有 0.477(本页代码在构建时计算)。
Check: over the 8 interior volumes ; among the other 16 boundary volumes even the smallest difference is 0.477 (computed by this page’s code at build time).
逐步推导:GS 三角求解、它的转置与伴随中的
Step by step: the GS triangular solve, its transpose, and in the adjoint
第一步, 是块下三角矩阵(每块都是标量乘 )。GMRES 每一步都要把 作用在一个向量 上,也就是解 :按控制体编号从小到大做前向替换,这就是代码 precondition() 中不转置的分支:
Step 1, is block lower triangular (every block a scalar times ). Every GMRES step applies to a vector , i.e. solves by forward substitution in increasing volume order, the non-transposed branch of precondition() in the code:
第二步, 是块上三角矩阵, 要按编号从大到小做后向替换。代码的转置分支先乘以 ,再把 从所有 的分量中减去,是同一个后向替换的“按列”写法:
Step 2, is block upper triangular, so needs backward substitution in decreasing order. The transposed branch of the code first applies and then subtracts from every component , the column-oriented form of the same backward substitution:
第三步,右预处理的 GMRES 求解上面的 、。伴随方程的矩阵是 ;既然 ,就有 ,所以伴随用 。 与 都不对称,拿 去预处理 就是用错了矩阵。第四步,GS 定常迭代 的误差每步乘以 ,只有它的谱半径小于 1 才收敛,而且收敛速度就由谱半径决定;GMRES 用同样的矩阵乘积,却在已生成的 Krylov 子空间里使残差最小。
Step 3, right-preconditioned GMRES solves the , above. The adjoint matrix is ; since , also , so the adjoint uses . Neither nor is symmetric, and preconditioning with would use the wrong matrix. Step 4, the stationary GS iteration multiplies its error by in every step; it converges only if the spectral radius of that matrix is below one, and the radius sets its speed. GMRES uses the same matrix products but minimises the residual over the Krylov space built so far.
核对:对两个固定的向量 、, 与 的相对差为 6×10⁻¹⁶,所以代码的反向扫描正是 的转置;定常迭代的残差每步约乘以 0.936(Newton 方程)与 0.941(伴随方程),也就是每下降一个数量级约需 35 与 38 步(本页代码在构建时计算)。
Check: for two fixed vectors and , and differ by 6×10⁻¹⁶ relatively, so the backward sweep of the code is exactly the transpose of ; the stationary iteration multiplies the residual by about 0.936 per step (Newton system) and 0.941 (adjoint system), about 35 and 38 steps per decade (computed by this page’s code at build time).
图 6.2 把三种解法放在一起,都迭代到舍入误差的底部。
Figure 6.2 puts the three solvers side by side, each run to the round-off floor.
修正若使密度或压力失去正值,或离开本例设定的亚声速范围,就减小步长 ;同时要求残差下降。这个线搜索只用于找到收敛解。后面的稳态 tangent/adjoint 对 求导,不是对接受了几次回退这一离散逻辑求导。下图的播放器逐次显示 Newton 迭代的流场:从均匀来流 出发,每一步都同时修正整个流场。
A line search reduces the step if density or pressure loses positivity, the state leaves the permitted subsonic range, or the residual fails to decrease. It only locates the converged solution. The steady tangent/adjoint differentiates , not the discrete history of accepted line-search decisions. The player below shows the Newton iterates as fields: starting from the uniform , each step corrects the whole field at once.
目标函数:物面压力合力
Objective: integrated pressure force on the body
从拟一维的推力目标推广到二维物面压力合力。 指向流体域外、也就是物体内部,所以流体施加给物体的压力合力为 。页面可选固定坐标方向的 x 分量 或 y 分量 (单位向量 取 或 );这里没有除以动压或参考长度,也不是随攻角旋转的气动升阻力系数。
Extend the quasi-one-dimensional thrust objective to the pressure force on the body. points out of the fluid domain, into the body, so the pressure force of the fluid on the body is . Choose its fixed Cartesian x component or y component (unit vector equal to or ). These are raw force integrals: they are not divided by the dynamic pressure or a reference length, and they are not lift or drag coefficients in axes rotated with the incidence.
第一项给状态偏导数 ,第二项给固定状态下的直接几何偏导数 。遗漏第二项,会导致形状梯度即使求解器收敛也不正确。格点版本在每个边界半面用相应端点状态的压力,因此它和格心格式是两个明确不同的离散目标。若需要随来流旋转的阻力与升力,就把两个分量旋转到来流方向:
The first term gives the state derivative , the second the direct geometry derivative at fixed state. Omitting the latter gives an incorrect shape gradient even with fully converged solvers. The node scheme uses the associated endpoint pressure on each boundary half-face, so the two schemes define distinct discrete objectives. Drag and lift in the free-stream axes are the two components rotated into the free-stream direction:
是沿来流方向的阻力, 是垂直来流的升力;它们仍是每单位展长的力,而不是系数。
is the drag along the free stream and the lift normal to it; both are still forces per unit span, not coefficients.
图 7.1 画出 的每一项;图 7.2 按物面位置把这些项累加起来。
Figure 7.1 draws every term of ; Figure 7.2 adds the terms up along the wall.
逐步推导:目标函数的偏导数 与
Step by step: the partial derivatives and of the objective
第一步,状态只通过物面单元的压力进入 。对控制体 的四个分量求偏导,用第 3 节的 :
Step 1, the state enters only through the pressure of the wall cells. Differentiate with respect to the four components of volume , using of Section 3:
所以 只在 8 个物面控制体的行上非零,并且每一行都与 成比例。第 11 节的伴随右端 就是它。第二步,几何。面积向量 (、 是面的起点和终点),所以 、。状态固定时:
So is nonzero only in the rows of the 8 wall volumes, and every such row is proportional to ; the adjoint right-hand side of Section 11 is exactly this. Step 2, the geometry. The area vector is (, the start and end points of the face), so and . At fixed state:
第三步,每个物面节点恰好是一个物面面的终点、下一个物面面的起点,把两项加起来:对 ,节点 的直接形状导数 就是它两侧物面面压力之差,。图 12.3 中紫色的“直接部分”就是这个量。第四步, 不显式依赖 和 ,所以 :这两个导数完全来自状态的响应(第 10 节)。
Step 3, every wall node is the end point of one wall face and the start point of the next, so the two terms add up: for , the direct shape derivative of node is the difference of the pressures on its two wall faces, and . The violet “direct part” in Figure 12.3 is this quantity. Step 4, has no explicit dependence on and , so : these two derivatives come entirely from the state response (Section 10).
核对:计算图对 反向一次(种子 、)得到的 与第一步的公式最大相差 7×10⁻¹⁸,对全部节点坐标的 与第二、三步的公式完全相同,、 为 0(本页代码在构建时计算)。
Check: one reverse sweep of the graph for (seeds , ) gives a that differs from the formula of Step 1 by at most 7×10⁻¹⁸, and a over all node coordinates that is identical to Steps 2 and 3; and are 0 (computed by this page’s code at build time).
这个力有多少是物理的
How much of this force is physics
来流动压 ,来流总压 ; 时分别为 0.028 与 1.0283。无损失(等熵)流中压力不可能超过总压,所以压力系数有上界:
The free-stream dynamic pressure is and the total pressure ; at they are 0.028 and 1.0283. In loss-free (isentropic) flow the pressure can never exceed the total pressure, so the pressure coefficient has an upper bound:
本页在 、 的收敛解里,物面最大压力为 1.094,即 ,高于总压。控制体 0 的速度 0.2366 与 几乎相同,按等熵关系压力应为 1.000,实际却只有 0.869:总压损失了 13%。按 d'Alembert 佯谬,无激波、无粘性的稳态亚声速绕流阻力为零;这里的阻力却是 。
In this page’s converged solution at and , the largest wall pressure is 1.094, i.e. , above the total pressure. Also, volume 0 moves at 0.2366, almost exactly ; isentropic flow would give it the pressure 1.000, yet it has only 0.869: it has lost 13% of its total pressure. By d’Alembert’s paradox the steady, shock-free, inviscid subsonic flow around the body has zero drag, yet here the drag is .
这些偏差来自固定耗散 :它比来流速度大近一个数量级,相当于很大的数值粘性(第 15 节在加密网格上量化)。因此在本页这些网格上,力的数值主要是离散误差,不能当作物理载荷。但求导的对象就是这个离散目标 本身:第 10–14 节的 tangent、adjoint、差分与复数步长给出的,是离散 的精确导数,这正是本页要教的内容。离散 离物理有多远,是另一个由网格加密回答的问题。
These deviations come from the fixed dissipation : it is almost an order of magnitude larger than the flow speed and acts like a large numerical viscosity (Section 15 quantifies it on refined meshes). On these meshes the forces are therefore mostly discretisation error and must not be read as physical loads. The derivatives, however, are taken of exactly this discrete objective : the tangent, adjoint, finite differences and complex step of Sections 10–14 give the exact derivatives of the discrete , which is what this page teaches. How far the discrete is from physics is a separate question, answered by refinement.
从离散目标得到伴随右端
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:
本页的压力合力 是这种加权和:,;面积向量 随节点坐标变化,所以 同时依赖状态与设计变量。
The pressure force of this page is such a weighted sum: and ; the area vector moves with the node coordinates, so depends on both the state and the design variables.
求 时固定所有设计变量;求 时固定全部离散状态槽位。固定解析目标函数并不一定意味着移动网格上的目标采样值不变:如果采样点随设计移动,还要保留该位置依赖;固定网格及固定采样值时,相应项才为零。目标中的体积权重已经进入 ,不应在伴随右端再乘一次体积。
Hold every design variable fixed for , and every stored state entry fixed for . 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 and must not be multiplied into the adjoint right-hand side again.
伴随右端本身也可以由一次反向扫描得到:以 为种子,把上面的求积和逐项反过来执行,
The adjoint right-hand side is itself obtained by a reverse sweep: seed and run the terms of the quadrature sum above backwards,
从 、 开始,扫完全部求积点后 、:一个状态被多个求积点读取,所以要先置零、再累加。前向的 与反向的这两个结果满足同一个对偶关系 。
Starting from and , after all quadrature points and : a state is read by several quadrature points, so its adjoint is zeroed first and then accumulated. The forward and these two reverse results satisfy one duality, .
前向:从热力学到 matrix-free 乘积
Tangent: from thermodynamics to the matrix-free product
先对压力和速度求导,再对法向速度、物理通量和耗散项求导。给定任意状态切向量 (前向模式的种子),不必逐列构造 ;每个面只读取左右两侧的四分量切向量,按同一计算图传播,然后依原残差符号分发,得到 。 的四个分量写作 。
Differentiate pressure and velocity first, then the normal velocity, the physical flux and the dissipation. For an arbitrary state tangent (the forward-mode seed), no Jacobian columns need be assembled: gather the two four-component tangents, propagate them through the face computation and scatter with the residual signs, which gives . The four components of are written .
记号:本页的前向(tangent)与伴随推导采用自动微分的写法。程序变量 的前向导数(切向量)记为 ,伴随记为 ;反向扫描用 累加、用 清零,语句按与原始计算相反的次序执行。代码与播放器沿用 Tapenade 的命名:x_d 即 ,x_b 即 (例如 residual_d、residual_b)。变分符号 只出现在第 16 节与附录 A 的连续伴随里,表示连续函数的变分;点和横线是对离散代码的前向与反向求导。Newton 的修正量写作 。
Notation: the tangent and adjoint derivations of this page use the notation of algorithmic differentiation. The forward derivative (tangent) of a program variable is and its adjoint is ; the reverse sweep accumulates with , clears with , and runs its statements in the reverse order of the primal computation. Code listings and players use Tapenade’s names: x_d is and x_b is (residual_d, residual_b). The variation symbol appears only in the continuous adjoint of Section 16 and Appendix A, where it is the variation of a continuous function; the dot and the bar are forward- and reverse-mode derivatives of the discrete code. Newton corrections are written .
上式暂时固定几何(),所以面积向量形式的法向速度 的切向量就是 。远场的 就是第 4 节的 ,不能置零;壁面使用第 4 节的压力导数。当前实现把标量加、减、乘、除、幂和三角函数的局部偏导保存成计算图,再沿图向前传播;这是解析链式法则,不是有限差分,也不存全局 Jacobian。播放器可自动播放每个面的四个步骤。
These formulas hold geometry fixed (), so the tangent of the normal velocity in area-vector form, , is . At the far field is the of Section 4, not zero; walls use the pressure derivative of Section 4. The implementation records local derivatives of scalar arithmetic, powers and trigonometric operations and propagates along that graph. This is analytic chain-rule differentiation, not finite differencing, and it stores no global Jacobian. The player can step through the four stages of every face automatically.
图 8.1 把这些公式放回第 3 节的计算图。
Figure 8.1 puts these formulas back into the computation graph of Section 3.
逐步推导:通量 Jacobian 的完整 矩阵
Step by step: the full flux Jacobian
第一步,原始变量 对守恒量的 Jacobian(代码 prim() 中的除法与第 3 节的 ):
Step 1, the Jacobian of the primitive variables with respect to the conserved ones (the divisions in prim() and of Section 3):
第二步,用原始变量写物理通量。面积向量形式的法向速度记作 ,并利用 :
Step 2, write the physical flux in primitive variables, with the normal velocity in area-vector form and :
每一列是对一个原始变量求偏导;例如能量通量对 的导数,来自 中的 与 本身对 的导数 乘以 。第三步,链式法则 ,逐项相乘并化简( 是单位质量的总焓):
Each column is the partial derivative with respect to one primitive variable; for example, the derivative of the energy flux with respect to combines from with the derivative of itself times . Step 3, the chain rule , multiplied out and simplified ( is the total enthalpy per unit mass):
第四步,两个自检。每个元素都对 线性,所以 对面积向量是线性的(第 6 节用到);Euler 通量是 的一次齐次函数:对任意标量 ,,对 求导得 。
Step 4, two self-checks. Every entry is linear in , so is linear in the area vector (used in Section 6); and the Euler flux is homogeneous of degree one in : for any scalar , , whose derivative with respect to gives .
核对:在全部 48 个面的两侧状态上,上面的矩阵与代码 fluxJacobian()(对 physical() 的计算图逐列求前向导数)最大相差 9×10⁻¹⁶(矩阵元素最大为 4.97),与 的乘积最大相差 2×10⁻¹⁵,齐次关系 最大为 4×10⁻¹⁶;sympy 另外做了符号核对(本页代码在构建时计算)。
Check: on both side states of all 48 faces, the matrix above differs from the code's fluxJacobian() (column-wise forward derivatives of the graph of physical()) by at most 9×10⁻¹⁶ (largest entry 4.97), from the product by at most 2×10⁻¹⁵, and the homogeneity relation is at most 4×10⁻¹⁶; sympy checks the same symbolically (computed by this page’s code at build time).
逐步推导:数值通量的线性化与它的特征值
Step by step: linearising the numerical flux, and its eigenvalues
第一步,固定几何,对 分别对 、 求导:
Step 1, at fixed geometry, differentiate with respect to and :
是格式中的常数,所以没有 项,也没有需要冻结的开关。若改用随状态变化的 ,就要对 max 在哪一侧取得作分支选择,那个分支在两侧相等处不可微。第二步, 的特征值是法向的四个特征速度乘以面长:
is a constant of the scheme, so there is no term and no switch to freeze. A state-dependent would require choosing on which side the max is attained, a branch that is not differentiable where both sides are equal. Step 2, the eigenvalues of are the four characteristic speeds in the normal direction times the face length:
第三步,加上 只把每个特征值平移同一个量。于是 的特征值是 、, 的是 、。页面检查的 恰好保证前者全为正、后者全为负:增大左侧状态使流出 的通量增大,增大右侧状态使它减小,这是迎风思想的离散形式,也是第 6 节中耗散部分能主导对角块的原因。
Step 3, adding shifts every eigenvalue by the same amount. The eigenvalues of are and , those of are and . The page’s check guarantees exactly that the former are all positive and the latter all negative: raising the left state increases the flux out of , raising the right state decreases it. This is the discrete form of upwinding, and the reason why the dissipation can dominate the diagonal blocks in Section 6.
核对:在全部 48 个面的两侧状态上,迹的相对偏差最大 3×10⁻¹⁶,行列式的相对偏差最大 8×10⁻¹⁶;,面积向量方向上的所有特征速度都在 以内;两个块与计算图对单位种子的前向结果最大相差 4×10⁻¹⁶(本页代码在构建时计算)。
Check: on both side states of all 48 faces the trace deviates by at most 3×10⁻¹⁶ relatively and the determinant by at most 8×10⁻¹⁶; , so every characteristic speed normal to a face stays below ; the two blocks differ from the forward results of the graph for unit seeds by at most 4×10⁻¹⁶ (computed by this page’s code at build time).
图 8.2 在第 2 节的紫色面上给出这两个块的数值,以及它们在 中的位置。
Figure 8.2 gives the values of the two blocks on the violet face of Section 2, and their places in .
逐步推导:通量 Jacobian 的特征向量与 Riemann 坐标
Step by step: the eigenvectors of the flux Jacobian, and the Riemann coordinates
第一步,右特征向量。取单位法向 (:特征向量相同,特征值乘以 ),记 为第 4 节的切向速度。四个特征速度的右特征向量排成矩阵 的四列:
Step 1, the right eigenvectors. Take the unit normal (: the same eigenvectors, the eigenvalues times ) and write for the tangential velocity of Section 4. The right eigenvectors of the four characteristic speeds are the four columns of :
每一列都可以用本节开头的 、、 与 公式验证:把这一列当作 代入。第三列(剪切波)给出 、、,于是 、,,正是 乘这一列。第一、四列(两道声波)给出 、;第二列(熵波)给出 、,只改变密度。
Each column can be checked with the , , and formulas at the start of this section: insert the column as . The third column (shear wave) gives , , , hence , and , which is times the column. The first and fourth columns (the two sound waves) give and ; the second (entropy wave) gives and , a change of density only.
第二步,左特征向量。 的四行把任意切向量 分解成四个波各自携带的量(、、 都由 得到):
Step 2, the left eigenvectors. The four rows of split any tangent into the amounts the four waves carry (, and all follow from ):
把第一步的四列逐一代入,这四个组合依次给出单位向量:例如沿第一列 、,第一个组合为 ,其余三个为 0。所以 ,并且 。第三步,数值通量的两个块与 共用这组特征向量:
Inserting the four columns of Step 1 one by one, the four combinations give the unit vectors in turn: along the first column, for instance, and , so the first combination is and the other three vanish. Hence and . Step 3, the two blocks of the numerical flux share these eigenvectors with :
其中 在 上求值; 相同,只是 换成 、在 上求值。上一段推导的特征值就是这里的对角元;固定的 只平移特征值,不改变特征向量。第四步,Riemann 坐标。第 4 节的远场用 。由 得 ,再用第 3 节的 , 的切向量是:
with evaluated at ; is the same with for , evaluated at . The eigenvalues of the previous derivation are the diagonal entries here; the fixed shifts the eigenvalues and leaves the eigenvectors alone. Step 4, the Riemann coordinates. The far field of Section 4 uses . From , ; with the of Section 3, the tangents of are:
正是第二步中那道声波的组合,而 只含熵波: 除了自己的声波,还带着一部分熵波。 把每个波乘以它自己的速度,所以 的 分量是
is exactly the combination of that sound wave in Step 2, while holds the entropy wave only: besides its own sound wave, carries part of the entropy wave. multiplies every wave by its own speed, so the component of is
分量同理。在 坐标下:
and likewise for . In the coordinates :
第五步,这对远场意味着什么。第 3 列的两个非零元说明:只有当 均匀(等熵流)时, 才是纯粹的特征变量,第 4 节的闭合才严格按特征取舍。本页收敛解上 在 1.0012 到 1.0103 之间(来流为 1),第 3 列的元素在 −2.56 到 −2.39 之间:数值耗散在物体附近产生熵,随流带到下游(图 8.3 下),所以这个闭合只是近似的特征闭合。这正是第 4 节说它“只在局部一维、等熵的意义下是特征关系”的量化。近似的是边界条件本身,不是它的导数:代码对这个闭合求出的 与它的转置是精确的(附录 A 实测)。
Step 5, what this means at the far field. The two nonzero entries of column 3 say that only when is uniform (isentropic flow) are pure characteristic variables and does the closure of Section 4 select exactly by characteristics. On this page’s converged solution lies between 1.0012 and 1.0103 (1 in the free stream) and the entries of column 3 between −2.56 and −2.39: the numerical dissipation makes entropy near the body and the flow carries it downstream (bottom of Figure 8.3), so the closure is characteristic only approximately. This quantifies Section 4’s “characteristic only in the local one-dimensional, isentropic sense”. What is approximate is the boundary condition itself, not its derivative: the the code computes for this closure, and its transpose, are exact (measured in Appendix A).
核对:在全部 48 个面的两侧状态(共 88 个)上, 最大为 2×10⁻¹⁵( 的元素最大为 3.62), 最大为 6×10⁻¹⁶;由 riemann() 与 fluxJacobian() 两个计算图算出的 与上面的上三角矩阵最大相差 5×10⁻¹⁵;sympy(sym_ta_euler.py)另外做了符号核对(本页代码在构建时计算)。
Check: on both side states of all 48 faces (88 states), is at most 2×10⁻¹⁵ (largest entry of : 3.62) and at most 6×10⁻¹⁶; the of the two computation graphs riemann() and fluxJacobian() differs from the upper triangular matrix above by at most 5×10⁻¹⁵; sympy (sym_ta_euler.py) checks the same symbolically (computed by this page’s code at build time).
图 8.3 在第 2 节的紫色面上给出这三个矩阵的数值,以及收敛解上熵的分布。
Figure 8.3 gives the three matrices on the violet face of Section 2, and the entropy of the converged solution.
逐步推导:面循环的每条语句与它的前向语句
Step by step: every statement of the face loop, and its tangent statement
第一步,前向模式按原始代码的顺序执行,每条赋值语句 换成 。种子是状态切向量 与设计方向 : 经第 12 节的公式给出每个面的 , 经第 10 节的公式给出 。第二步,内部面(右侧的语句与左侧相同;作用在四分量向量上的写成 块):
Step 1, the tangent mode runs in the order of the primal code, and every assignment becomes . The seeds are the state tangent and the design direction : gives every face its through the formulas of Section 12, and give through those of Section 10. Step 2, an interior face (the right side repeats the statements of the left side; statements on four-component vectors are written as blocks):
| 步骤step | 原始语句primal statement | 前向语句tangent statement |
|---|---|---|
| 读取两侧gather | ||
| 原始变量(prim(),4×4 块)primitives (prim(), 4×4 block) | ||
| 物理通量(physical(),4×4 块)physical flux (physical(), 4×4 block) | ||
| 面长face length | ||
| 数值通量numerical flux | ||
| 分发scatter |
与 就是本节前面推导的两个块; 是几何的贡献(第 12 节)。第三步,物面面连同目标函数(目标在物面面上逐项累加):
and are the two blocks derived earlier in this section; is the geometry’s part (Section 12). Step 3, a wall face together with the objective, which accumulates term by term on the wall faces:
| 步骤step | 原始语句primal statement | 前向语句tangent statement |
|---|---|---|
| 读取gather | ||
| 压力pressure | ||
| 壁面通量wall flux | ||
| 分发scatter | ||
| 目标objective |
第四步,远场面:代码 far() 先重建边界状态 ,再按内部面的公式算通量()。 在入流面是来流 ,在出流面是内侧 ;, 时 :
Step 4, a far-field face: far() in the code first rebuilds the boundary state and then evaluates the interior-face flux with . is the free stream at an inflow face and the interior at an outflow face; , and with , :
| 步骤step | 原始语句primal statement | 前向语句tangent statement |
|---|---|---|
| 单位法向unit normal | ||
| 来流free stream | ||
| 两侧原始变量与声速primitives and sound speed, both sides | ||
| 法向与切向速度normal and tangential velocity | ||
| Riemann 不变量Riemann invariants | ||
| 边界法向速度与声速boundary normal velocity and sound speed | ||
| 迎风侧的熵与切向速度entropy and tangential velocity from the upwind side | ||
| 密度与压力density and pressure | ||
| 速度velocity | ||
| 守恒量(4×4 块)conserved variables (4×4 block) | ||
| 数值通量numerical flux | 同内部面,as for an interior face, |
第五步,把 对 的依赖单独拿出来,就是第 4 节的 ;把对 与 的依赖拿出来,就是远场对几何与来流的导数(第 12、10 节)。整张表是同一个程序的前向,不需要为边界单独推一套公式。用本节末尾推导的字母来说,这个闭合属于它的重构语句:边界状态是远场面的一个输入,所以那里的 对存储状态不是单位阵,而是带着这个 ;对单位法向与来流的导数则在 里。
Step 5, the part of that depends on is the of Section 4; the parts that depend on and are the far field’s derivatives with respect to the geometry and the free stream (Sections 12 and 10). The whole table is the tangent of one program; the boundary needs no separate set of formulas. In the letters of the derivation at the end of this section, the closure is part of its reconstruct statement: the boundary state is one of the face inputs there, so on a far-field face is not the identity on the stored state but carries this , and carries the derivatives with respect to the unit normal and the free stream.
核对:把三张表的语句直接写成一段独立的程序(不经过计算图),用一组固定的种子 ( 的全部分量都不为零)在全部 48 个面上执行:共 1560 个中间量的切向量与对原始语句做复数步长(,独立的复数运算)的结果最大相对差为 3×10⁻¹⁵;每个面的 与计算图的 jvp 最大相差 4×10⁻¹⁵,目标的 相差 0(完全相同)(本页代码在构建时计算)。
Check: the statements of the three tables, written as an independent program (not through the computation graph) and run on all 48 faces with one fixed pair of seeds (every component of nonzero): the tangents of all 1560 intermediate quantities differ from complex steps of the primal statements (, independent complex arithmetic) by at most 3×10⁻¹⁵ relatively; every face’s differs from the graph’s jvp by at most 4×10⁻¹⁵, and the objective’s by 0 (identical) (computed by this page’s code at build time).
逐步推导:一个内部面与一个物面面的前向数值
Step by step: the tangent of one interior face and one wall face, in numbers
本节末尾的推导(图 8.5)把同样的语句用在另一个内部面上,即面循环里第一个两侧都不在物面上的内部面 (控制体 8 与 9 之间),种子取节点 17 的 ;那里的数值属于那个面和那个种子,不是下面跟踪的紫色面与物面面的数值。
The derivation at the end of this section (Figure 8.5) runs the same statements through another interior face, the first one of the face loop whose two sides are off the wall, (between volumes 8 and 9), with the seed of node 17; its numbers belong to that face and that seed, not to the violet face and the wall face followed below.
第一步,取设计方向 , 是物面节点 1 的横坐标 。移动这个节点同时改变第 2 节的紫色面 (控制体 0 与 1 之间)与物面面 (控制体 0)的面积向量,所以两个面的 都不为零。状态切向量取第 10 节 tangent() 的解 (GMRES 47 次)。第二步,按上面的表逐条计算内部面(四个分量依次是 ;):
Step 1, take the design direction with the coordinate of wall node 1. Moving this node changes the area vectors of both the violet face of Section 2, (between volumes 0 and 1), and the wall face (volume 0), so both faces have a nonzero . The state tangent is the solution of tangent() in Section 10, (47 GMRES iterations). Step 2, the statements of the tables above on the interior face (the four components are ; ):
| 量quantity | 符号symbol | 原始值primal value | 前向值tangent value |
|---|---|---|---|
| 左侧状态left state | (0.898, 0.211, 0.02494, 2.198) | (−0.009284, −0.01828, 0.05492, −0.03284) | |
| 右侧状态right state | (0.9682, 0.2635, 0.03173, 2.442) | (0.006529, 0.006966, 0.04868, 0.01985) | |
| 面积向量、面长area vector, length | (−0.3919, 0.3289, 0.5116) | (0, −1, −0.6429) | |
| 左侧原始变量left primitives | (0.898, 0.235, 0.02777, 0.8691) | (−0.009284, −0.01793, 0.06145, −0.01213) | |
| 右侧原始变量right primitives | (0.9682, 0.2721, 0.03277, 0.9624) | (0.006529, 0.005359, 0.05006, 0.00664) | |
| 左侧物理通量left physical flux | (−0.07451, −0.3581, 0.2838, −0.2544) | (2.909×10⁻⁴, 0.006159, −0.8776, 0.002094) | |
| 右侧物理通量right physical flux | (−0.09282, −0.4024, 0.3135, −0.3264) | (−0.01845, −0.00812, −0.9654, −0.06521) | |
| 数值通量numerical flux | (−0.1196, −0.4071, 0.2952, −0.4155) | (0.02797, 0.01981, −0.914, 0.09868) |
第三步,物面面(目标 在这个面上的一项是 ):
Step 3, the wall face (the term of the objective on this face is ):
| 量quantity | 符号symbol | 原始值primal value | 前向值tangent value |
|---|---|---|---|
| 状态state | (0.898, 0.211, 0.02494, 2.198) | (−0.009284, −0.01828, 0.05492, −0.03284) | |
| 压力pressure | 0.8691 | −0.01213 | |
| 面积向量area vector | (−0.5126, −0.1914) | (0, 1) | |
| 壁面通量wall flux | (0, −0.4455, −0.1664, 0) | (0, 0.006219, 0.8714, 0) | |
| 目标的这一项this term of the objective | −0.1664 | 0.8714 |
内部面的 同时含三部分:状态经 、 传来的部分,面积向量经 传来的部分,以及面长经耗散传来的 。物面面的 的第 0、3 个分量恒为零; 的切向量 0.8714 中, 与 分别为 0.002322 与 0.8691。
The interior face’s has three parts: the state’s through and , the area vector’s through , and the face length’s through the dissipation, . Components 0 and 3 of the wall face’s vanish identically; of the tangent 0.8714 of , contributes 0.002322 and 0.8691.
核对:两个面的 与计算图 jvp 在同一种子下给出的面切向量分别相差 1×10⁻¹⁶ 与 0(完全相同);全部面的 之和为 −0.0994786461,与一次伴随给出的 相差 3×10⁻¹³(本页代码在构建时计算)。
Check: the of the two faces differ from the face tangents of the graph’s jvp with the same seeds by at most 1×10⁻¹⁶ and 0 (identical); the summed over all faces is −0.0994786461, and the single adjoint solve gives , a difference of 3×10⁻¹³ (computed by this page’s code at build time).
forward multiplication: z = a*b
z_d = b*a_d + a*b_d
forward division: z = a/b
z_d = a_d/b - a*b_d/(b*b)
# Seed: state_d = the chosen state tangent; D_d = 0 on every design input.局部导数怎样组成全局 Jacobian
How local derivatives form the global Jacobian
固定网格和当前可微分支,把一个面的所有输入记为 ;它包括两侧的状态、面的几何(面积向量与面长)与边界数据。令 、 分别是 对存储状态和设计变量的导数, 是局部通量 Jacobian。于是每个残差行的导数按同一个有向面关联矩阵 装配,这里的求和包括相应的边界通量映射。本页没有被替换的行,边界条件全部在边界面的通量里。前向模式按计算顺序传播切向量,状态种子是 ,设计种子是 :
At fixed mesh and differentiable branch, let collect all face inputs: the two states, the face geometry (the area vector and the face length) and the boundary data. Let and be the derivatives of with respect to the stored states and the design variables, and the local flux Jacobian. Assemble each residual derivative with the same oriented face incidence , 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 and the design seed :
无体源项时 。面长度和法向量随节点坐标变化,它们的设计依赖属于 ,不能只对状态求导。本页是一阶格式, 只连接面两侧的状态;matrix-free 仍只需依次执行这些局部乘积,不必存储全局矩阵。
Set when there is no volume source. Face lengths and normals move with the node coordinates; that design dependence belongs in , so differentiating the states alone is insufficient. This page is first order, so connects only the two states of a face. 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
上面的 、 与 来自同一段短程序:残差代码对每个面依次执行四条语句。设 是面 读取的存储位置(两侧的 、,以及重构需要时它们的邻居),把这些位置上的状态排成 :
The , and above come from one short program: the residual code runs four statements on every face. Let be the stored locations that face reads (its two sides and , and their neighbours when the reconstruction needs them), and stack the states stored there as :
(1)取值: 的每一行只有一个 1,从全局数组里挑出 上的状态;(2)重构:由这些状态和设计变量算出面的全部输入 ,在本页是两侧状态 、,加上面积向量 与面长 (一阶时对状态 );(3)通量:;(4)分发: 是有向关联矩阵 的第 列,在第 行为 、第 行为 ,即 、(边界面只写一行);本页每个位置有 4 个分量, 代表 。这四条语句是残差代码的骨架;本页代码里的每一条语句(含边界面与源项)都在本节里逐句写出,并附各自的切向量语句。
(1) Gather: each row of holds a single 1 and picks one state of out of the global array. (2) Reconstruct: the states and the design give all inputs of the face, on this page the two states , , plus the area vector and the face length (at first order on the states). (3) Flux: . (4) Scatter: is column of the oriented incidence , in row and in row , that is and (a boundary face writes one row); with 4 components per location, stands for . 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)代入(2),(2)代入(3),(3)代入(4),对所有面求和,再减去体源项的切向量 ,就逐条语句得到 :
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: , and . Substituting (1) into (2), (2) into (3) and (3) into (4), summing over all faces and subtracting the tangent of the volume source assembles statement by statement:
上面的 就是 ;一阶重构不改变取出的状态, 在状态上是单位阵, 就是取值。所以一个面对 的贡献是四个初等 Jacobian 的乘积
The above is ; first-order reconstruction leaves the gathered states unchanged, so is the identity on the states and is the gather. One face therefore contributes to the product of four elementary Jacobians,
它从右往左作用在 上,与程序执行的顺序相同:先取值,再重构、通量,最后分发。 只有几个位置,所以 只有一小块非零,matrix-free 乘积就是按面执行这几次局部运算,不存储任何全局矩阵。图 8.5 按这四条语句逐条播放本页一个面的数值。
It acts on from right to left, in the order in which the program runs: gather first, then reconstruct and flux, scatter last. holds only a few locations, so 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
取面循环里第一个两侧都不在壁面上的内部面 (控制体 8 在左、9 在右);一阶格式只读两侧的状态,。设计种子取 , 是节点 17 的 坐标 ;状态种子取本页前向求解对同一方向给出的解 ( 是状态对设计变量的敏感度,, 是它的第 列),于是下面每个切向量都是对 的导数。表中的数由本页代码在构建时算出:基准状态、前向求解与局部导数都用本页自己的程序。
Take the first interior face of the face loop whose two sides are off the wall, (control volume 8 on the left, 9 on the right); the first-order scheme reads only its two sides, . Seed the design with , where is the -coordinate of node 17, and the states with this page's tangent solution for the same direction, ( is the sensitivity of the states to the design, , and is its column ); every tangent below is then a derivative with respect to . 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) gather | 0.9565 0.2053 0.03409 2.395 | 0.006583 0.006444 8.034×10⁻⁵ 0.02249 | |
| 0.9468 0.2093 0.0228 2.362 | 0.007669 0.00581 −0.004944 0.02579 | ||
| 设计种子design seed | 1.344 | 1 | |
| (2)重构(2) reconstruct | −1.366 0.0463 | 0 1 | |
| 1.366 | 0.03389 | ||
| (3)通量(3) flux | −0.2685 −1.353 0.05065 −0.9375 | 0.01881 −0.00887 0.9512 0.06381 | |
| −0.009962 −0.01491 0.00783 −0.0366 | |||
| 0.02878 0.006044 0.9434 0.1004 | |||
| (4)分发(4) scatter | −0.2685 −1.353 0.05065 −0.9375 | 0.01881 −0.00887 0.9512 0.06381 | |
| 0.2685 1.353 −0.05065 0.9375 | −0.01881 0.00887 −0.9512 −0.06381 |
移动节点 17 会改变这个面的面积向量:,,所以通量的切向量分成两部分:经过状态的 与直接来自设计的 ,表中两行相加就是 。
Moving node 17 changes the area vector of this face: , , so the flux tangent has two parts: through the states and straight from the design; the two rows of the table add up to .
所有面与源项的这些贡献加起来是 ,在收敛解上为零:这正是下面前向求解的方程。
Summed over all faces and the source terms, these contributions give , which vanishes at the converged solution: the tangent equation solved further on.
按程序顺序执行四条语句,箭头上是各自的初等 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).
种子:( 是节点 17 的 坐标 ),。在 上: (0.006583, 0.006444, 8.034×10⁻⁵, 0.02249), (0.007669, 0.00581, −0.004944, 0.02579)。
Seeds: ( is the -coordinate of node 17) and . On : (0.006583, 0.006444, 8.034×10⁻⁵, 0.02249), (0.007669, 0.00581, −0.004944, 0.02579).
(1)取值:,只挑出 上的 2 个状态的切向量(上一步列出的数)。
(1) Gather: picks the tangents of the 2 states of (the numbers of the previous step).
(2)重构:。一阶格式,面输入中的状态就是取出的状态。移动节点 17 会改变这个面的面积向量:,。
(2) Reconstruct: . First order: the states among the face inputs are the gathered states. Moving node 17 changes the area vector of this face: , .
(3)通量: (0.01881, −0.00887, 0.9512, 0.06381),其中经过状态的部分 (−0.009962, −0.01491, 0.00783, −0.0366),直接来自设计的部分 (0.02878, 0.006044, 0.9434, 0.1004)。
(3) Flux: (0.01881, −0.00887, 0.9512, 0.06381), of which (−0.009962, −0.01491, 0.00783, −0.0366) through the states and (0.02878, 0.006044, 0.9434, 0.1004) straight from the design.
(4)分发: (0.01881, −0.00887, 0.9512, 0.06381),,即加上 (−0.01881, 0.00887, −0.9512, −0.06381)。
(4) Scatter: (0.01881, −0.00887, 0.9512, 0.06381) and , that is (−0.01881, 0.00887, −0.9512, −0.06381) added.
伴随:转置同一个计算图
Adjoint: transpose the same computation graph
给残差一个四分量的伴随种子 (反向模式的种子,可以任意给定),内部面通量的伴随就是 。再分别乘左右通量导数的转置,累加到状态的伴随 。标量页里的一次乘法变成块转置,但收集、计算、分发的顺序没有变,只是整体反过来执行。
Give the residual a four-component adjoint seed (the reverse-mode seed, arbitrary); an internal face flux then has the adjoint . Apply the transposed left and right flux derivatives and accumulate into the state adjoint . Scalar multiplications become block transposes, while the gather/compute/scatter structure remains the same, only run in reverse.
为什么语句要倒序执行,为什么伴随量要先清零、每一项贡献又都用“+=”累加,见本节末尾用面循环四条语句的转置所作的推导;本页里,对边界面(),式中的 是第 4 节该面的完整导数 :物面为 ,远场为 ,外侧状态的伴随 经过 回到内侧;几何和来流参数的反传也不能丢掉。保存运算图后逆序访问每个算子,能自动完成这些汇合。
Why the statements run in reverse, and why the adjoints are cleared first and every contribution accumulates with +=, is explained with the transpose of the four face statements in the derivation at the end of this section; on this page, for a boundary face (), the of the formula is that face’s complete derivative of Section 4: at a wall and at the far field, where the adjoint of the exterior state returns through to the interior. Geometry and free-stream paths also remain. Reversing the recorded scalar operations handles these merges directly.
图 9.1 是同一张计算图的反向扫描。
Figure 9.1 is the reverse sweep of the same computation graph.
逐步推导:逆序转置一个内部面
Step by step: transposing one interior face in reverse order
第一步,一个内部面对 的贡献分三步:取出两侧的切向量 、,乘局部导数 、,再按 分发(行 加、行 减)。转置时,行与列交换,求和的次序也交换:
Step 1, one interior face contributes to in three steps: take the two tangents and , multiply by the local derivatives and , and scatter with (add in row , subtract in row ). Transposition swaps rows and columns, and with them the order of the sums:
第二步,转置把三步的次序倒过来:先按 收集面通量的伴随 ,再乘两个转置块,最后把结果加回两侧(+=)。第三步,块内部也要倒序: 的转置是 。平均运算把 交给一侧的物理通量,先乘 得到原始变量的伴随 ,再乘 (即第 8 节 的转置,逐项写出):
Step 2, transposition reverses the three steps: first collect the adjoint of the face flux, , with , then multiply by the two transposed blocks, and finally add the results back into the two sides (+=). Step 3, inside a block the order is reversed too: the transpose of is . The average hands to the physical flux of one side; comes first and gives the primitive adjoints , followed by (the transpose of from Section 8, written out):
第四步,加上耗散项的转置: 再加 , 再减 ( 的转置还是 )。第五步, 是所有面贡献之和:先 ,再逐面 、;面的访问次序不影响求和;为什么每个伴随量都要先清零、再用 += 累加,见本节末尾的推导。物面面的转置已在第 4 节给出,远场面的转置在第 4 节的 部分。
Step 4, add the transpose of the dissipation: gains and loses (the transpose of is ). Step 5, is the sum of all face contributions: first , then and face by face; the order of the faces does not change the sum; why every bar is cleared first and accumulates with += is explained in the derivation at the end of this section. The transpose of a wall face is given in Section 4, that of a far-field face in the part of Section 4.
核对:以 的伴随解为种子 ,即 ,在全部 32 个内部面上,第三、四步的两段公式与计算图逆序(faceVjp)的结果最大相差 6×10⁻¹⁷(本页代码在构建时计算)。
Check: with the seed of the adjoint for , i.e. , the two-stage formulas of Steps 3 and 4 differ from the reversed graph (faceVjp) by at most 6×10⁻¹⁷ on all 32 interior faces (computed by this page’s code at build time).
图 9.2 在同一个面上给出转置块的位置和一次反向计算的数值。
Figure 9.2 shows where the transposed blocks sit and the numbers of one reverse evaluation on the same face.
逐步推导:面循环的每条语句与它的伴随语句(逆序,+=)
Step by step: every statement of the face loop, and its adjoint statement (reverse order, +=)
第一步,反向模式按与第 8 节的表相反的顺序执行:语句 换成 、, 块换成它的转置。开始前 、、、、;种子是残差的伴随 ,以及目标的 。第二步,内部面(表从下往上是原始语句的顺序;右侧与左侧相同):
Step 1, the reverse mode runs the tables of Section 8 in the opposite order: a statement becomes and , and a block becomes its transpose. Before the sweep, , , , and ; the seeds are the residual adjoint and the objective’s . Step 2, an interior face (read upwards, the table is in the primal order; the right side repeats the left side):
| 步骤step | 原始语句primal statement | 伴随语句(逆序)adjoint statement (reverse order) |
|---|---|---|
| 分发scatter | ||
| 数值通量numerical flux | ||
| 面长face length | ||
| 物理通量(4×4 转置)physical flux (4×4 transpose) | ||
| 原始变量(4×4 转置)primitives (4×4 transpose) | ||
| 读取gather |
与 就是本节前面逐项写出的两段; 收集到的几何伴随留给第 12 节。第三步,物面面连同目标函数:
and are the two stages written out earlier in this section; the geometry adjoint collected in is left for Section 12. Step 3, a wall face together with the objective:
| 步骤step | 原始语句primal statement | 伴随语句(逆序)adjoint statement (reverse order) |
|---|---|---|
| 目标objective | ||
| 分发scatter | ||
| 壁面通量wall flux | ||
| 压力pressure | ||
| 读取gather |
第四步,远场面,far() 的语句倒过来执行( 同第 8 节):
Step 4, a far-field face, the statements of far() run backwards ( as in Section 8):
| 步骤step | 原始语句primal statement | 伴随语句(逆序)adjoint statement (reverse order) |
|---|---|---|
| 数值通量numerical flux | 同内部面,右侧为 as for an interior face, with on the right | |
| 守恒量(4×4 转置)conserved variables (4×4 transpose) | ||
| 速度velocity | ||
| 压力pressure | ||
| 密度density | ||
| 迎风侧的熵与切向速度entropy and tangential velocity from the upwind side | ||
| 边界法向速度与声速boundary normal velocity and sound speed | ||
| Riemann 不变量Riemann invariants | ||
| 法向与切向速度(两侧)normal and tangential velocity (both sides) | ||
| 原始变量与声速(两侧)primitives and sound speed (both sides) | ||
| 来流free stream | ||
| 单位法向unit normal |
第五步,读法。远场的 有两条来源:通量直接给的 ,以及经 一路倒回 (出流面还有 、)的那一条,它合起来就是第 4 节的 。入流面上 与 流向来流 ,再变成 、; 永远流向来流。每一行都只是把前向那一行的系数转置,顺序倒过来,并且用 +=。用本节末尾推导的字母来说,经 的那条路径属于远场面的 ,通向设计变量的路径属于 。
Step 5, how to read it. The far field’s has two sources: the flux’s direct , and the path back through to (and at an outflow face to and ), which together is the of Section 4. At an inflow face and go to the free stream and on to and ; always goes to the free stream. Every row only transposes the coefficients of the corresponding tangent row, in reverse order and with +=. In the letters of the derivation at the end of this section, the path through belongs to the of a far-field face and the paths to the design to .
配上第 8 节表的前向种子,一个面的前向语句与逆序语句给出同一个内积(这个面没有的项为零:物面面没有右侧状态与来流,远场面没有右侧状态,只有物面面的 不为零):
Paired with the tangent seeds of the tables of Section 8, the forward and the reverse statements of one face give the same inner product (a term the face does not have is zero: a wall face has no right state and no free stream, a far-field face no right state, and only a wall face has ):
核对:把三张表的逆序语句写成独立的程序,以 ( 的伴随解)、物面上 在全部 48 个面上执行:状态伴随与计算图 faceVjp 最大相差 3×10⁻¹⁶,设计伴随(、 与经第 12 节的 )最大相差 6×10⁻¹⁷;每个面都满足上面的对偶恒等式,最大相对差 4×10⁻¹⁶(本页代码在构建时计算)。
Check: the reverse statements of the three tables, written as an independent program and run on all 48 faces with (the adjoint for ) and on the wall: the state adjoints differ from the graph’s faceVjp by at most 3×10⁻¹⁶, the design adjoints (, and through Section 12) by at most 6×10⁻¹⁷; every face satisfies the duality identity above to a relative difference of at most 4×10⁻¹⁶ (computed by this page’s code at build time).
逐步推导:同两个面的伴随数值与一个面上的对偶
Step by step: the adjoint of the same two faces in numbers, and the duality on one face
本节末尾的推导(图 9.5)用同一个种子 反向执行另一个面,即它自己的前向例子里的内部面 (控制体 8 与 9 之间);那里的数值属于那个面,不是这里跟踪的两个面。
The derivation at the end of this section (Figure 9.5) reverses another face with the same seed , the interior face (between volumes 8 and 9) of its own forward example; its numbers belong to that face, not to the two faces followed here.
第一步,种子取 ( 的伴随解),物面面还有目标的 。按上面的表逆序计算第 8 节例子中的紫色面 :
Step 1, the seeds are (the adjoint for ) and, on the wall face, the objective’s . The reverse statements of the tables above on the violet face of the example in Section 8:
| 量quantity | 伴随语句adjoint statement | 值value |
|---|---|---|
| 面通量的伴随flux adjoint | (−0.1605, −0.01275, 0.1773, −0.04249) | |
| 两侧物理通量的伴随adjoints of the two physical fluxes | (−0.08024, −0.006373, 0.08867, −0.02124) | |
| 面长的伴随length adjoint | 0.02112 | |
| 左侧原始变量的伴随left primitive adjoints | (0.006626, 0.05428, −0.05141, 0.03783) | |
| 右侧原始变量的伴随right primitive adjoints | (0.007656, 0.05948, −0.05714, 0.03879) | |
| 面积向量的伴随area-vector adjoint | (−0.1004, 0.1673) | |
| 左侧状态的伴随left state adjoint | (−0.08767, 0.05037, 0.03307, −0.006607) | |
| 右侧状态的伴随right state adjoint | (0.07556, 0.06373, −0.1503, 0.03726) |
第二步,同一个物面面 :
Step 2, the same wall face :
| 量quantity | 伴随语句adjoint statement | 值value |
|---|---|---|
| 目标的种子objective seed | 1 | |
| 面通量的伴随flux adjoint | (0.05729, −0.1031, 0.102, 0.03902) | |
| 压力的伴随pressure adjoint | −0.1581 | |
| 面积向量的伴随area-vector adjoint | (−0.08959, 0.9577) | |
| 状态的伴随state adjoint | (−0.001771, 0.01486, 0.001756, −0.06324) |
第三步,一个面上的对偶。把第 8 节这两个面的前向值与这里的伴随值配对,每个面单独满足对偶恒等式:
Step 3, the duality on one face. Pairing the tangent values of the two faces in Section 8 with the adjoint values here, each face satisfies the duality identity by itself:
第四步,对全部面求和。左边相加是 : 满足前向方程,(数值上 ,),所以左边就是 。右边相加是 : 满足伴随方程,(最大 6×10⁻¹³),所以右边就是 。前向与伴随给出同一个导数,因为它们是同一批乘积按两种次序求和。
Step 4, sum over all faces. The left sides add up to : solves the tangent equation, so (numerically and ), and the left side is . The right sides add up to : solves the adjoint equation, so (at most 6×10⁻¹³), and the right side is . The tangent and the adjoint give the same derivative because they sum the same products in two orders.
核对:两个面的伴随与计算图 faceVjp 分别相差 1×10⁻¹⁷ 与 1×10⁻¹⁹;两个面的对偶恒等式两边分别相差 3×10⁻¹⁷ 与 0(完全相同); 与 相差 3×10⁻¹³(本页代码在构建时计算)。
Check: the adjoints of the two faces differ from the graph’s faceVjp by at most 1×10⁻¹⁷ and at most 1×10⁻¹⁹; the two sides of the face dualities differ by at most 3×10⁻¹⁷ and 0 (identical); and differ by 3×10⁻¹³ (computed by this page’s code at build time).
reverse multiplication: z = a*b
a_b += b*z_b
b_b += a*z_b
reverse division: z = a/b
a_b += z_b/b
b_b -= a*z_b/(b*b)
# Seed: residual_b = the chosen adjoint seed; every other _b starts at 0;
# then visit the recorded operations backwards.前向与反向由对偶(点积)恒等式联系在一起:对任意种子 、 与 ,
The forward and reverse modes are tied together by the duality (dot-product) identity: for any seeds , and ,
它的状态部分与设计部分就是两个点积测试:
Its state and design parts are the two dot tests:
点积测试检查转置实现是否相互一致,但不能单独证明 是所需残差的导数:两边若犯同一个错误,测试仍会通过。因此还要做残差方向差分、独立复数运算和全链路目标差分。下面的面板现场给出三个检查,都使用同一组确定的种子 、、:状态块与参数/几何块的点积测试(给出两个内积本身及相对误差),以及残差方向中心差分的相对误差随 的变化:
A dot test checks that the two implementations are transposes of each other, but it cannot alone establish that differentiates the intended residual: the same mistake on both sides would still pass. Residual directional differences, independent complex arithmetic and full objective reruns provide the other checks. The panel below computes three checks live, all with one fixed set of seeds , , : the dot tests of the state block and of the parameter/geometry block (both inner products and their relative difference), and the relative error of the residual directional central difference as a function of :
先随 下降(截断误差),再因舍入误差回升;中间存在一段很小的平台,才说明 与 确实是这个残差的导数。播放器可查看某个面的反传如何累加到各控制体。
first falls like (truncation) and then rises again with round-off; a small plateau in between shows that and really are the derivatives of this residual. The player shows how a selected face accumulates reverse contributions into its volumes.
图 9.4 用第 8、9 节的逐条语句构造了两种错误的伴随,把上面说的道理画出来:点积测试不能单独证明 是所需残差的导数。漏掉一条反传路径的伴随立刻被点积测试抓住;前向和伴随犯同一个错误(都把远场状态当作常数),点积测试就检查不出来,只有差分能发现。
Figure 9.4 builds two wrong adjoints from the statements of Sections 8 and 9 and draws the point made above, that a dot test alone cannot prove that differentiates the intended residual. An adjoint that loses one return path is caught by the dot test at once; a tangent and an adjoint that make the same mistake (both treat the far-field state as a constant) pass it, and only the difference check finds the error.
为什么反向累加给出矩阵转置
Why reverse accumulation gives the matrix transpose
把所有残差和状态分量展平后,下面只是交换两个有限求和的次序。残差种子 是任意给定的,尚未求解伴随方程;只有选择满足第 11 节线性方程的 ,才得到该目标对应的离散伴随解。取 ,前向得到 ;以 为种子反向扫描,得到 。下式就是 AD 的对偶(点积)恒等式:
After flattening all residual and state components, the following simply exchanges two finite sums. The residual seed is arbitrary and has not solved an adjoint equation; only satisfying Section 11 gives the discrete adjoint solution for the chosen objective. With the forward sweep gives ; the reverse sweep seeded with gives . The identity below is the duality (dot-product) identity of AD:
内部面通量向左加、向右减,所以先把这两条路径合并成通量的伴随 ;之后按通量、重构、原始状态的相反依赖顺序传播。反向语句中的 += 很重要:同一状态会被多个面和多个求积点读取,这些贡献必须累加。这里没有分部积分,也没有另行猜测连续伴随边界条件。
An interior flux is added on the left and subtracted on the right, so combine those two paths into its adjoint 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.
第 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 +=:
本页没有被替换的残差行:边界条件都在边界面的通量里,所以上面的面循环转置就是完整的 。
This page replaces no residual rows: the boundary conditions all sit in boundary-face fluxes, so the transposed face loop above is the complete .
从切向量到伴随:转置把语句顺序反过来
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:
仍从右往左读,这就是反向扫描的语句顺序:程序最后执行的分发最先处理,最先执行的取值最后处理。先把所有伴随置零,再以 为种子逐条执行:
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 , and run:
面循环结束后,源项的一对也按同样的规则反过来:,,。本页代码里每一条语句的伴随语句(顺序与前向相反)也都在本节里逐句写出。
After the face loop the source is reversed by the same rule: , , . The adjoint statement of every statement of this page's code, in the reverse order, is written out in this section as well.
分发与取值互为转置。分发 把一个面通量写进两行;它的转置 从这两行读回一个数,是一次取值。取值 从全局数组读出几个状态;它的转置 把它们的伴随加回原来的位置,是一次带累加的分发。
Scatter and gather are transposes of each other. The scatter writes one face flux into two rows; its transpose reads those two rows back into one number: a gather. The gather reads a few states out of the global array; its transpose adds their adjoints back into their places: a scatter with accumulation.
为什么要先置零、再用 += 累加:程序里的一次扇出(一个值被多处读取)在反向扫描里变成扇入。一个存储状态属于它每个相邻面的 ,设计变量也进入许多面与源项;链式法则对变量的每一次使用各给出一项, 是所有这些项的和:
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 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 is the sum of all these terms:
让这个和从零开始,每个 += 加上一次使用的贡献。反过来,程序里的扇入( 收集它各个面的通量)在反向扫描里变成扇出: 只被读取,不需要累加。
starts this sum at zero, and each += adds the term of one use. Conversely, a fan-in of the program ( collecting the fluxes of all its faces) becomes a fan-out of the reverse sweep: is only read and needs no accumulation.
对偶恒等式的逐步证明,以及它的数值形式:点积测试
The duality identity step by step, and its numerical form: the dot-product test
把同一个面的前向语句与反向语句配对。每一步只用一次转置的定义 ,把一个初等 Jacobian 从内积的右边移到左边,所以内积在每一级都相同:
Pair the forward and reverse statements of the same face. Each step uses the definition of the transpose once, , 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:
对所有面求和,再加上源项的一对 (),右边的两项正好累加成反向扫描的输出 与 :
Summed over all faces, with the pair of the source added ( with ), the two terms on the right accumulate exactly into the outputs and of the reverse sweep:
这就是 AD 的点积测试:任取种子 与 ,前向扫描算出 ,反向扫描算出 ,两边之差应在舍入误差量级;只要有一条反向语句的转置写错,差就是 的。本页在构建时用它自己的 matrix-free 前向与反向乘积( 个分量的确定性种子,设计方向取下面算例的 )做了这个测试:,,相对差 。
This is the dot-product test of AD: take any seeds and , run the forward sweep for and the reverse sweep for ; the two sides must agree to round-off, and a single wrong transpose in the reverse statements gives an difference. This page ran the test on its own matrix-free forward and reverse products at build time (deterministic seeds with components, the design direction the of the example below): and , a relative difference of .
同一个面的反向扫描
The reverse sweep of the same face
反向种子取本页伴随求解的解 ,于是 与 是这个面对 与 的贡献。下表按反向语句的顺序列出伴随值;最后一列是每一级的内积:先是 ,然后是 ,最后是 ,切向量取第 8 节“本页一个面的算例:前向扫描”里同一个面的前向算例的值。
Seed the reverse sweep with this page's adjoint solution, , so that and are this face's contributions to and . The table lists the adjoints in the order of the reverse statements; the last column is the inner product at each stage: first , then , and last , 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 | ||
| (4′)种子(4′) seed | 0.05906 −0.04941 0.06067 −0.00319 | 0.0100473996 | |
| 0.1628 −0.09469 0.04838 −0.01428 | |||
| (4′)取值(4′) gather | −0.1037 0.04528 0.01229 0.01109 | 0.0100473996 | |
| (3′)至(1′)加回(3′)–(1′) add back | −0.1349 0.09547 0.0142 6.33×10⁻⁴ | 0.0100473996 | |
| 0.1488 −0.02838 −0.0196 −0.02973 | |||
| 0.009999 |
3 级内积相同,最大相对差 :对偶恒等式在这些数上成立;图 9.5 按反向语句逐条播放这些数。 是这个面对梯度分量 的贡献,经过 这条直接路径得到。
The 3 inner products agree, with a largest relative difference of : the duality identity holds in these numbers. Figure 9.5 plays them statement by statement in reverse. is this face's contribution to the gradient component , along the direct path .
箭头全部反向,每个初等 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.
(0)置零并给种子:,; (0.05906, −0.04941, 0.06067, −0.00319), (0.1628, −0.09469, 0.04838, −0.01428)。
(0) Zero and seed: , ; (0.05906, −0.04941, 0.06067, −0.00319), (0.1628, −0.09469, 0.04838, −0.01428).
(4′)分发的转置是一次取值: (−0.1037, 0.04528, 0.01229, 0.01109)。内积 0.0100473996。
(4′) The transposed scatter is a gather: (−0.1037, 0.04528, 0.01229, 0.01109). Inner product 0.0100473996.
(3′)通量的转置:;一阶格式的面输入就是两侧状态, 直接给出它们的伴随,面积向量那一部分交给下一步的 。
(3′) The transposed flux: ; at first order the face inputs are the two states, and is their adjoint, the area-vector part going on to in the next step.
(2′)重构的转置:,,这个面给出 0.009999。
(2′) The transposed reconstruction: and ; this face gives 0.009999.
(1′)取值的转置是带累加的分发:, (−0.1349, 0.09547, 0.0142, 6.33×10⁻⁴), (0.1488, −0.02838, −0.0196, −0.02973)。内积 0.0100473996。
(1′) The transposed gather is a scatter with accumulation: , (−0.1349, 0.09547, 0.0142, 6.33×10⁻⁴), (0.1488, −0.02838, −0.0196, −0.02973). Inner product 0.0100473996.
对偶:3 级内积相同(最大相对差 5×10⁻¹⁶), 在这个面上逐级成立。
Duality: the 3 inner products agree (largest relative difference 5×10⁻¹⁶); holds stage by stage on this face.
前向求解:输入改变后状态如何响应
Tangent solve: state response to a design input
设计变量向量是 , 是全部节点坐标按节点编号排成的向量。把稳态残差沿 求导,得到 ( 的第 列是对 的状态响应)。右端必须包括远场参数或网格变化对数值残差的直接影响。用与原始 Newton 相同的 GMRES/GS 算法求解,但基准流场保持不动。
The design vector is , where stacks all node coordinates in node order. Differentiating the steady residual with respect to gives (column of is the state response to ). The right-hand side includes the direct effect of free-stream or mesh changes. Solve with the same GMRES/GS as Newton, at the fixed converged base state.
前向模式一次只沿一个设计方向 求导:状态的切向量 满足下面的方程, 是目标的方向导数。取 , 就是 的第 列 ,。前向求解的结果 就是代码 tangent() 返回的解数组 x。
The forward mode differentiates along one design direction at a time: the state tangent solves the equation below, and is the directional derivative of the objective. With , is column of , , and . The result of the tangent solve is the solution array x that tangent() returns in the code.
逐步推导:来流参数的导数
Step by step: derivatives with respect to the free-stream parameters
第一步,第 1 节的来流状态(,速度大小 ,能量 ):
Step 1, the free-stream state of Section 1 (, speed , energy ):
第二步,逐分量求导( 以弧度计):
Step 2, differentiate component by component ( in radians):
第三步, 只通过远场边界状态 进入残差(入/出流分支已冻结),所以前向方程的右端只在远场控制体的行上非零:
Step 3, enters the residual only through the far-field boundary states (with the inflow/outflow branch frozen), so the tangent right-hand side is nonzero only in the rows of the far-field volumes:
第四步,第 7 节已说明 ,所以 :导数完全来自状态的响应。图 10.1 画出右端的非零位置,图 10.2 画出解出的 。
Step 4, Section 7 showed , so : the derivative comes entirely from the state response. Figure 10.1 shows where the right-hand side is nonzero and Figure 10.2 the solution .
核对:第二步的两个公式与对代码 free() 的四阶中心差分(步长 )最大相差 1×10⁻¹³ 与 1×10⁻¹³; 的非零行恰好是 8 个远场控制体;,前向求解给出 0.0298097877,伴随给出 0.0298097877(本页代码在构建时计算)。
Check: the two formulas of Step 2 differ from fourth-order central differences of the code's free() (step ) by at most 1×10⁻¹³ and 1×10⁻¹³; the nonzero rows of are exactly the 8 far-field volumes; , the tangent solve gives 0.0298097877 and the adjoint 0.0298097877 (computed by this page’s code at build time).
前向模式分三步:以 、 为种子,面循环求残差的切向量 ;用 GMRES 解 ;再求 。图 10.3 逐帧演示这三步(可以播放、暂停、单步或拖动滑块):右端只在少数几个面上不为零,GMRES 却要把它变成整个流场的响应。
The tangent mode has three steps: with the seeds and , the face loop computes the residual tangent ; GMRES solves ; then . Figure 10.3 shows the three steps frame by frame (play, pause, step or drag the slider): the right-hand side is nonzero on only a few faces, yet GMRES turns it into a response of the whole flow.
攻角在所有导数中以弧度为单位;页首用度数显示。几何参数只移动所选节点,其余节点固定,不包含额外的网格弹性变形。下图显示响应场的某个守恒分量,色条给出范围。
Incidence derivatives are per radian, although the header displays degrees. A coordinate parameter moves only that node; other nodes stay fixed, without an additional mesh-deformation model. The plot shows a chosen conserved component of the response, with a colour bar for its range.
把隐函数求导展开成可求解的系统
Expand implicit differentiation into a solvable system
设原始残差已经收敛,且当前光滑分支上的 非奇异。取设计种子(方向),令 ;前向求解得到的状态切向量是 。对每一个残差分量逐项求导,零残差的切向量仍为零:
Assume the primal residual is converged and is nonsingular on the current smooth branch. Choose a design seed (direction) and set ; the state tangent returned by the tangent solve is . Differentiate each residual component: the tangent of a zero residual is zero.
实现分三步:状态种子 、设计种子 ,前向求值残差得到 ;用精确 的乘积求解 ;再以 和 为种子对目标前向求导,得到 。一次 只是算子调用(一次前向扫描),不是求解 。稳态导数的对象是收敛残差方程,不是 Newton/GMRES 的迭代次数。在不可微点(如 limiter 的切换或分支判别处)以上普通导数需要另外讨论,不能无条件套用。
Implementation has three steps: seed the state with and the design with and evaluate the residual tangent ; solve for with exact products; then differentiate the objective forward with the seeds and to obtain . One is an operator application (one forward sweep), not the solution . The steady derivative concerns the converged residual equation, not the Newton/GMRES iteration count. At nondifferentiable points (a limiter switch or a branch test) these ordinary derivatives need separate treatment.
用面循环的语句来看:右端 是 时的一次前向扫描,只有 与源项两条路径起作用;迭代中的每个 是 时的一次前向扫描。一次前向求解的全部工作就是这段面循环以不同的种子反复执行。对 个设计分量逐个求导,要做 次这样的求解。
In terms of the face loop's statements: the right-hand side is one forward sweep with , in which only the paths and the source are active, and every of the iteration is one forward sweep with . 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 design components take such solves.
伴随求解:一次求解得到多个设计导数
Adjoint solve: one solve, many design derivatives
采用与其他页面一致的拉格朗日函数 。选择 后,状态响应 消掉;每个设计分量只需要直接目标导数加残差反传。换一个目标需要重新解一次伴随,但不需要重新求原始流场。
Use the same Lagrangian convention as the other pages: . Choosing eliminates . Each design derivative then combines the direct objective derivative and a residual reverse product. A new objective needs another adjoint solve, but not another primal flow solve.
用反向模式的记号:以 为残差种子做一次反向扫描,状态的伴随是 ;再加上目标自身的种子 ,设计变量的伴随 就是梯度 。代码 sensitivities() 的最后一步正是种子为 的一次反向扫描。
In reverse-mode notation: one reverse sweep with the residual seed gives the state adjoint ; adding the objective’s own seed , the design adjoint is the gradient . The last step of sensitivities() in the code is exactly one reverse sweep with the seeds .
图 11.1 画出 的伴随解。
Figure 11.1 shows the adjoint solution for .
逐步推导:伴随解的含义,以及一次求解得到全部导数
Step by step: what the adjoint solution means, and why one solve gives every derivative
第一步,设想给第 条残差方程加一个常数 :( 是第 个单位向量),收敛解 与目标 都随 变化。在 处对 求导:
Step 1, imagine adding a constant to residual equation : ( the -th unit vector), so that the converged and the objective depend on . Differentiate with respect to at :
第二步,伴随方程 转置后就是 ,代入:
Step 2, the adjoint equation , transposed, reads ; substitute:
所以 是目标对第 条方程“残差扰动”的灵敏度,与设计变量无关。第三步,每个设计变量 都只是以 的形式扰动残差,于是 ,这正是本节末尾 Lagrangian 推导得到的公式;右边对全部 只需要一次残差的反向扫描 。第四步,计算量:前向方法每个设计变量解一次 ,伴随方法无论有多少设计变量都只解一次 。
So is the sensitivity of the objective to a “residual perturbation” of equation , independent of any design variable. Step 3, every design variable perturbs the residual only through , hence , the formula the Lagrangian derivation at the end of this section arrives at; for all at once the right-hand side needs one reverse sweep of the residual, . Step 4, the cost: the tangent method solves once per design variable, the adjoint method solves once, however many design variables there are.
核对:取 最大的分量(控制体 20 的 ρ 方程,),用本页的 forcing 接口取 ()重新求解原始问题,中心差分 ;全部 50 个设计变量逐个做前向求解,与一次伴随给出的梯度最大相差 1×10⁻¹²(梯度元素最大为 0.196)(本页代码在构建时计算)。
Check: take the largest component of (the ρ equation of volume 20, ), set through the page's forcing hook () and re-solve the primal problem: the central difference ; one tangent solve for each of the 50 design variables differs from the gradient of the single adjoint solve by at most 1×10⁻¹² (largest gradient entry 0.196) (computed by this page’s code at build time).
图 11.2 把整页的三条计算链放在一起:前向每个设计方向要一次线性求解,伴随只要一次。
Figure 11.2 puts the three computation chains of the whole page side by side: the tangent needs one linear solve per design direction, the adjoint only one.
上面第一、二步给了 一个具体的含义。图 11.3 换成物理的说法:给一个控制体加上质量源或动量源(只加在质量方程或动量方程的右端,能量方程不变), 的变化率就是 的相应分量,所以伴随的密度分量与动量分量可以直接读成“在哪里、朝哪个方向推流体,目标变化最大”,并用重新求解核对。
Steps 1 and 2 above give a concrete meaning. Figure 11.3 puts it physically: add a mass source or a momentum source to one control volume (a source on the right-hand side of the mass or of the momentum equations only; the energy equation is unchanged), and changes at the rate of the corresponding component of . The density and momentum components of the adjoint can therefore be read as where to push the fluid, and in which direction, to change the objective most, which re-solves confirm.
图 11.4 把第四步所说的代价数出来:前向路线每个设计变量一次线性求解,伴随路线每个目标一次。
Figure 11.4 counts the cost of Step 4: one linear solve per design variable on the tangent route, one per objective on the adjoint route.
右预处理使用 ,即前向 GS 三角算子的精确转置逆。它与完整 不同。停止条件检查未预处理的真实残差 ,避免预处理缩放掩盖误差。图中乘子对应四条积分平衡方程,不是压力或流速。
Right preconditioning uses , the inverse transpose of the forward GS triangular operator. It is distinct from . The stopping test uses the true unpreconditioned residual , so preconditioner scaling cannot hide error. The four multiplier components correspond to integrated conservation equations, not to pressure or velocity.
图 11.5 逐帧演示这个伴随求解:每一帧是 GMRES 做了 步之后的 ,右边同时给出真残差,以及用 算出的梯度的误差。
Figure 11.5 shows this adjoint solve frame by frame: each frame is the of GMRES after steps, with its true residual and the error of the gradient computed from on the right.
解出 之后,梯度只要一次反向扫描。图 11.6 逐个面演示这次扫描:四分量的 怎样一面一面累加、最后在每个控制体都抵消为零,形状梯度 又怎样在节点上长出来。
Once is known, the gradient needs one reverse sweep. Figure 11.6 runs this sweep face by face: how the four-component accumulates face after face and finally cancels to zero in every control volume, and how the shape gradient grows at the nodes.
从有限维 Lagrangian 逐步消去状态敏感度
Eliminate state sensitivity from a finite-dimensional Lagrangian
在满足 的解上 。先把 固定,沿切向量 、 对 前向求导,再按 和 收集系数。即使 随设计改变,额外的 项也在收敛解上为零。
At a feasible state , . Hold fixed, differentiate forward along the tangents and , then collect the coefficients of and . Even if varies with the design, the extra term vanishes at the converged state.
选择 使未知的状态敏感度前面的系数为零。于是一个目标只需求解一次伴随线性系统,再进行设计反传;总设计梯度是列向量 。注意符号来自这里的加号约定,并不是因为“伴随一定带负号”。
Choose 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 is a column. Its sign follows from the plus convention in this Lagrangian, not from a universal negative sign for adjoints.
用反向模式的横线记号:以 为种子运行残差的反向扫描,得到 ,这就是伴随方程。再把目标自己的反向语句(种子 )累加进同一组横线量:它们给 加上 ,给 加上 ,于是 。伴随方程说的正是状态的横线量总和为零:状态不再把敏感度往下传,同一次扫描的设计伴随 就是梯度 。也可以直接检查与 tangent 的等价性:利用 ,,所以两条路线给出同一个 。更换为 会得到 ,但这只是两个离散乘子的符号约定,不能据此认定连续场 与离散向量逐点相等。
In the bar notation of reverse mode: the residual's reverse sweep seeded with returns , which is the adjoint equation. Accumulate the objective's own reverse statements (seeded with ) into the same bars: they add to and to , so . 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, , is the gradient . Check equivalence with the tangent: gives , so both routes give the same . Changing to yields , but that relates two discrete sign conventions; it does not prove pointwise equality with a continuous field .
精确算子决定求解哪个方程;Newton 迭代与 GS 预处理 GMRES 决定如何求解。较便宜的近似 Jacobian(例如一阶或冻结部分系数的 Jacobian)可以用于迭代修正或预处理,但不能替代精确转置。应监控真残差 。若其不为零,当前伴随与精确 tangent 的方向导数差为 ,因此只看迭代次数或预处理残差不够。
The exact operator determines the equation; Newton iterations and GS-preconditioned GMRES determine 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 . If it is nonzero, the current adjoint directional derivative minus the exact tangent derivative is ; an iteration count or a preconditioned residual alone is insufficient.
为什么一次伴随求解能代替 次前向求解
Why one adjoint solve replaces tangent solves
同样的结论也可以不经过 Lagrangian,直接由前向求解的两个关系得到:
The same result follows without a Lagrangian, directly from the two relations of the tangent solve:
最后一项是三个因子 、、 的乘积,括号可以从两边加。前向路线先算 :它依赖方向 ,每个设计分量都要解一次以 为系数的方程,全部分量要解 次。伴随路线先算行向量 :它与 无关,只要解一次。把这个行向量记作 :
The last term is the product of three factors, , and , which can be bracketed from either side. The tangent route computes first: it depends on the direction , so every design component needs one solve with , solves for all of them. The adjoint route computes the row first: it does not depend on , so it is solved once. Call this row :
这对每个方向 都成立,所以梯度就是上面方框里的 :一次伴随求解之后,再以 为种子做一次反向扫描,就得到全部分量。两条路线的计算量(本页的 与 由本页代码数出,与页首路线图相同):
This holds for every direction , so the gradient is the boxed above: after one adjoint solve, one reverse sweep seeded with gives every component. The cost of the two routes ( and of this page, counted by its code, as in the page roadmap at the top):
| 前向(tangent)路线Tangent route | 伴随(adjoint)路线Adjoint route | |
|---|---|---|
| 每个目标的线性求解Linear solves per objective | 次,系数 , with | 1 次,系数 1, with |
| 右端Right-hand sides | ||
| 求解之后After the solves | 次前向扫描,各得一个 forward sweeps, one each | 1 次反向扫描,得到整个 1 reverse sweep, the whole |
| 一次求解给出One solve gives | 一个分量 one component | 全部 个分量all components |
| 个目标时With objectives | 仍是 次( 与目标无关)still ( does not depend on the objective) | 次 |
本页 、:前向路线要解 50 个 96 阶的线性方程组,伴随路线只解 1 个。设计变量多而目标少时(形状优化通常如此)伴随路线便宜得多;设计变量少而目标多时,前向路线更合适。
This page has and : the tangent route solves 50 linear systems of order 96, 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.
几何导数:面法向、面长与直接压力载荷
Geometry derivatives: normals, lengths and direct pressure load
二维标量已经展示了移动节点如何改变法向。Euler 还需要把单位法向的变化传入远场特征旋转,把面长变化传入耗散。格点对偶面由边中点和顶点平均中心构造,因此一个节点还会影响相邻对偶分段。所有这些几何计算都进入同一条求导链。
As in the scalar example, moving a node changes face normals. Euler additionally differentiates unit normals in the characteristic rotations and lengths in the dissipation. Node-centred dual segments depend on edge midpoints and vertex-average cell centres, so one coordinate affects neighbouring segments. All these constructions enter the same derivative graph.
图 12.1 用一个物面面说明上面三个公式的几何意义。
Figure 12.1 shows the geometric meaning of the three formulas above on one wall face.
逐步推导:面积向量、面长与单位法向对坐标的导数
Step by step: derivatives of area vectors, face lengths and unit normals with respect to the coordinates
第一步,面积向量是端点坐标的线性函数,,逐个坐标求导:
Step 1, the area vector is a linear function of the endpoint coordinates, ; differentiate coordinate by coordinate:
第二步,面长 ,链式法则给出沿法向的投影;第三步,单位法向 ,商的求导法则给出垂直部分:
Step 2, the face length ; the chain rule gives the projection on the normal. Step 3, the unit normal ; the quotient rule gives the perpendicular part:
第四步,图 12.2 汇总这些依赖:坐标经面积向量进入每个面的通量与物面目标,经面长进入耗散,经单位法向进入远场的特征旋转;反向时 ,对各面收集到的 求和。
Step 4, Figure 12.2 collects these dependencies: the coordinates enter every face flux and the wall objective through the area vectors, the dissipation through the face lengths and the far-field characteristic rotation through the unit normals; in reverse, , summed over the collected on every face.
核对: 对坐标是线性的,所以单位步长的中心差分没有截断误差:对全部面、全部 48 个坐标,它与上面的公式最大相差 2×10⁻¹⁶; 与 的公式与步长 的中心差分最大相差 4×10⁻¹¹ 与 1×10⁻¹⁰(差分本身的误差)(本页代码在构建时计算)。
Check: is linear in the coordinates, so a central difference with unit step has no truncation error: over all faces and all 48 coordinates it differs from the formulas above by at most 2×10⁻¹⁶; the formulas for and differ from central differences with step by at most 4×10⁻¹¹ and 1×10⁻¹⁰ (the error of the differences themselves) (computed by this page’s code at build time).
物理通量对面积向量是线性的:。所以状态固定时,面积向量的切向量 给出通量的切向量 。于是固定状态下面通量的切向量为:
The physical flux is linear in the area vector: . At fixed state a tangent of the area vector therefore gives the flux tangent . At fixed state the face flux then has the tangent:
最后一项只在远场出现: 依赖单位法向(旋转与 ),所以即使 不变, 也会给出 。几何分支、连通性和边界分类在这些微扰中保持不变;不允许跨过翻转单元或切换入流分类后还声称使用同一个导数。积分稳态残差没有体积除法,因此体积导数不出现在当前 中;若加入时间项,它就必须出现。
The last term applies at the far field only: depends on the unit normal (rotation and ), so gives a even at fixed . Connectivity and boundary classification stay fixed under these perturbations. Crossing an inverted cell or changing an inflow branch is outside this linearisation. Integrated steady residuals have no volume division, so volume derivatives do not appear in ; they would be required for time terms.
逐步推导:几何的伴随语句与一个物面节点的形状导数
Step by step: the adjoint statements of the geometry, and the shape derivative of one wall node
第一步,第 9 节的逆序语句把每个面的几何伴随收集到 :内部面与远场面的物理通量给 ,耗散经 给 ,远场闭合经单位法向给 ,物面通量给 ,目标给 。第二步,面积向量对坐标是线性的,上面“逐步推导:面积向量、面长与单位法向对坐标的导数”的第一步给出四个导数,转置后就是节点坐标的伴随语句:
Step 1, the reverse statements of Section 9 collect every face’s geometry adjoint in : the physical fluxes of interior and far-field faces give , the dissipation gives through , the far-field closure gives through the unit normal, the wall flux gives and the objective . Step 2, the area vector is linear in the coordinates, and the four derivatives that Step 1 of “Step by step: derivatives of area vectors, face lengths and unit normals with respect to the coordinates” above gives, transposed, are the adjoint statements of the node coordinates:
第三步,一个节点的形状导数就是它所在的每个面贡献之和。以第 8、9 节的物面节点 1 为例,下表列出面积向量 依赖这个节点的全部 3 个面: 分成种子 的部分与目标种子 的部分, 写成 ,最后一列是这个面加到 上的量:
Step 3, a node’s shape derivative is the sum of the contributions of the faces it belongs to. For wall node 1 of Sections 8 and 9, the table lists all 3 faces whose area vector depends on this node: is split into the part of the seed and the part of the objective seed , is written , and the last column is what the face adds to :
| 面face | 类型kind | () () | () () | ||
|---|---|---|---|---|---|
| 0 | 内部面interior | (−0.1004, 0.1673) | (0, 0) | (0, −1, 1, 0) | (−0.1673, −0.1004) |
| 32 | 物面面wall face | (−0.08959, 0.08861) | (0, 0.8691) | (0, 1, −1, 0) | (0.9577, 0.08959) |
| 33 | 物面面wall face | (−0.08695, −0.07254) | (0, 0.9624) | (0, −1, 1, 0) | (−0.8898, −0.08695) |
| 合计sum | (−0.09948, −0.09776) |
合计 ,正是一次伴随给出的 。其中只来自 的部分 就是第 7 节的直接导数 : 的 只含端点的 坐标,所以它的 分量为零;其余来自 ,即 。
The sum is exactly the of the single adjoint solve. The part from alone, , is the direct derivative of Section 7: the of contains only the coordinates of the endpoints, so its component is zero; the rest comes from , i.e. .
核对:各面贡献之和与伴随梯度相差 1×10⁻¹⁷, 部分与计算图反向一次(种子 、)给出的 相差 0(完全相同)(本页代码在构建时计算)。
Check: the sum of the face contributions differs from the adjoint gradient by at most 1×10⁻¹⁷, and the part from the of one reverse sweep of the graph (seeds , ) by 0 (identical) (computed by this page’s code at build time).
图 12.3 把最终的形状梯度拆成两部分:第 7 节的直接部分 与经残差传来的 。
Figure 12.3 splits the final shape gradient into its two parts: the direct part of Section 7 and the part that comes through the residual.
有限差分:变量、步长与完整原始重算
Finite differences: variables, steps and full primal reruns
每个差分点都移动同一个设计变量,重新计算几何与边界状态,并把整个 Euler 原始问题重新收敛。不能只在固定 上差分 ,那样只得到直接导数 。表中同时保留正负扰动目标值、原始残差与完整梯度;选择“全部变量”可检查来流与全部物面坐标。
Each difference perturbs the selected parameter, recomputes geometry and boundary states, and reconverges the full Euler problem. Differencing at fixed would recover only the direct derivative . Tables retain both perturbed objectives, the primal residuals and the total gradients. The all-variable option checks the free-stream inputs and every body coordinate.
图 13.1 说明为什么每个差分点都必须重新收敛原始问题:只在固定的 上差分 ,得到的只是直接偏导数 。
Figure 13.1 shows why every difference point must re-converge the primal problem: differencing at the fixed returns only the direct partial derivative .
是第 个设计变量的单位方向, 是差分步长(与第 15 节的网格尺度 无关)。大步长有截断误差;很小的步长放大舍入和原始求解残差。步长扫描按整数量级 均匀取点,横轴就是 。正确证据是存在合适的步长区间,而不是只挑一个好看的数字。误差可切换绝对值与相对分量误差;当真梯度接近零时,优先查看绝对误差。
is the unit direction of design variable and the difference step (unrelated to the mesh size of Section 15). Large steps incur truncation error; tiny steps amplify round-off and primal residuals. The step sweep uses every decade , with on the horizontal axis. Look for a useful step-size range, not one favourable number. Absolute and component-relative errors are selectable; near a zero gradient the absolute error is more informative.
复数步长:独立复数残差核的检查
Complex step: checking an independent complex residual kernel
光滑无激波分支没有限幅器或最大波速运算,适合复数步长检查。复数算术与实数求导图分开实现,几何面长使用 的解析延拓,不能使用取共轭的复数范数。物理公式和固定边界分支共用。
This smooth branch has no limiter or maximum wave-speed operation, which makes complex-step verification useful. Complex arithmetic is implemented separately from the real derivative tape. Geometry uses the analytic continuation of , not a conjugating complex norm. Physical formulas and fixed boundary branches are shared.
逐步推导:方程组的复数步长展开
Step by step: the complex-step expansion for a system
第一步,解析函数在复数步长上的 Taylor 展开,虚部除以 就是导数,误差是 量级,并且没有两个相近数相减:
Step 1, the Taylor expansion of an analytic function at a complex step; the imaginary part divided by is the derivative, with an error of order and no subtraction of nearly equal numbers:
第二步,残差。代码保持状态的实部等于收敛解 ,令 、。展开时实部的零阶项是 :
Step 2, the residual. The code keeps the real part of the state at the converged and sets , . In the expansion the zeroth-order real term is :
第三步,代码用实数的 迭代 ,直到虚部残差收敛;此时 , 就是 的前向解 ,误差 。第四步,目标同样展开:
Step 3, the code iterates with the real until the imaginary residual has converged; then , so is the tangent solution for , up to . Step 4, expand the objective in the same way:
第五步,复数残差的实部等于 乘以二阶项,并不为零;代码不去修正它,因为它只影响 ,在 时远小于舍入误差。复数运算由独立的复数算子集执行,不经过实数计算图,所以它能检出计算图中的错误。
Step 5, the real part of the complex residual equals times second-order terms and is not zero; the code does not correct it, because it affects only , far below round-off at . The complex arithmetic runs on an independent complex operator set, not through the real computation graph, so it can expose errors in that graph.
核对: 时复数步长给出的 与前向解的相对差为 4×10⁻¹²,最终虚部残差 4×10⁻¹³; 与 两处的误差之比为 100.01,正是 项所预言的 100(本页代码在构建时计算)。
Check: at the complex-step differs from the tangent solution by 4×10⁻¹² relatively, with a final imaginary residual of 4×10⁻¹³; the errors at and have the ratio 100.01, the 100 predicted by the term (computed by this page’s code at build time).
图 14.1 把复数步长与第 13 节的差分放在同一张误差–步长图上。
Figure 14.1 puts the complex step and the differences of Section 13 on one error-versus-step plot.
从已收敛实数场出发,直接计算复数残差,用实数 作为虚部修正的迭代算子,直到 收敛。这样独立的复数残差仍能检出错误的 ;必须检查最终虚部残差,不能只做一次修正就宣称成功。它不是对 Newton 的回退逻辑做复数运算。
Starting from the converged real state, evaluate the complex residual and use the real as an iterative correction operator for the imaginary part until converges. The independent complex residual can still expose an incorrect ; its final imaginary residual must be checked. This does not apply complex arithmetic to Newton line-search decisions.
网格加密:固定多边形上的精度阶与载荷
Mesh refinement: order of accuracy on a fixed polygon, and the loads
伴随给出的是离散格式的精确导数,无论格式的精度阶是多少;精度阶决定离散解、以及由它算出的 和 离连续问题有多远。本节先用制造解测量格式的精度阶,再在同一网格族上看物理算例的载荷。
The adjoint gives the exact derivative of the discrete scheme, whatever its order of accuracy; the order decides how far the discrete solution, and the and computed from it, are from the continuous problem. This section first measures the order with a manufactured solution, then looks at the loads of the physical problem on the same mesh family.
网格族:均匀细分,区域不变
The mesh family: uniform subdivision, fixed domain
从第 2 节的 24 单元基础网格出发,每次加密把每个三角形经三条边的中点分成 4 个三角形,把每个四边形经四条边的中点和顶点平均点分成 4 个四边形,每条边界边在中点一分为二。新的边界节点都落在原来的八边形边上,所以物面和远场在每一层都是同一对八边形,区域完全不变,网格尺度逐层减半:。5 层网格分别有 24、96、384、1536、6144 个单元、24、80、288、1088、4224 个节点。只有这样均匀细分出来的网格族才能拟合精度阶。早先的版本在每层按 个角度重新采样物面曲线 ,每一层的区域都不同,载荷序列把几何变化和离散误差混在一起;本节的全部数据都改在这个固定多边形网格族上重新计算。
Starting from the 24-cell base mesh of Section 2, each refinement splits every triangle into 4 triangles through its edge midpoints, every quadrilateral into 4 quadrilaterals through its edge midpoints and its vertex average, and every boundary edge in two at its midpoint. The new boundary nodes lie on the old octagon edges, so the body and the far field are the same pair of octagons on every level, the domain never changes, and the mesh size halves from level to level: . The 5 meshes have 24, 96, 384, 1536, 6144 cells and 24, 80, 288, 1088, 4224 nodes. An order may only be fitted on a family refined uniformly like this. An earlier version re-sampled the body curve at angles on every level, so each level had a different domain and its force sequence mixed geometry changes with discretisation error; every number in this section is recomputed on the fixed-polygon family.
图 15.1 画出这个网格族的前三层。
Figure 15.1 shows the first three levels of this family.
制造解
The manufactured solution
取一个光滑、亚声速、处处为正的场作为精确解(均值沿 ,使远场八边形的每个面都明确是入流或出流):
Take a smooth, subsonic, everywhere-positive field as the exact solution (its mean flow is along , so every face of the far-field octagon is clearly inflow or outflow):
把它代入 Euler 方程得到源项 。源项由 sympy 对上式做符号求导并直接生成代码,不用差分,也不从离散残差反推;另用前向模式对偶数独立核对(随机点上最大相对差 1×10⁻¹⁶)。每个控制体的源项 在本格式实际的控制体(格心:单元;格点:中位对偶体)上用 点折叠 Gauss 公式积分,并用散度定理 独立核对(最大差 2×10⁻¹³)。离散问题是 。
Substituting it into the Euler equations gives the source . The source is differentiated symbolically by sympy and printed directly as code: no differencing, and nothing is reverse-engineered from the discrete residual. Forward-mode dual numbers check it independently (largest relative difference 1×10⁻¹⁶ over random points). Each volume’s source is integrated over the scheme’s actual control volumes (cells for the cell scheme, median-dual volumes for the node scheme) with a -point collapsed Gauss rule, and checked independently against the divergence theorem (largest difference 2×10⁻¹³). The discrete problem is .
边界数据只在这个研究里改动,页面的正常边界不变。制造解会穿过物面,所以物面在这里不能是滑移壁面;八边形物面与远场八边形的每个边界面都用第 4 节的特征闭合,只是把 换成面中点的 ,入/出流分支由它的法向速度决定。这样每个面从外部得到的条件个数正好等于入域特征的个数:入流面三个,出流面一个。反过来,如果在物面上照搬壁面通量 并把精确解的对流通量整体作为数据,那么在制造解从物面流出的面上,外部条件比入域特征多两个,这个边界就被过定了,所以本研究不这样做。离散问题 与连续问题因此只差离散化本身。
Only this study changes the boundary data; the page’s normal boundaries stay as they are. The manufactured field crosses the body, so the body cannot be a slip wall here. Every boundary face, on the body octagon and on the far-field octagon, uses the characteristic closure of Section 4 with at the face midpoint in place of , and the branch set by its normal velocity. Each face then receives exactly as many outside conditions as it has incoming characteristics: three at inflow, one at outflow. If instead the wall flux were kept and the whole convective flux of the exact solution supplied as data, a face where the manufactured field leaves through the body would receive two conditions more than it has incoming characteristics; that boundary would be over-determined, so this study does not do it. The discrete problem therefore differs from the continuous one only by the discretisation.
误差与观测阶
Errors and observed orders
误差在控制体上测量:格心与精确解的单元平均比较,格点与节点处的精确值比较(记为 )。四个守恒分量一起取按体积加权的 范数和最大范数。相邻两层的网格尺度正好差 2 倍,所以观测阶就是误差比的以 2 为底的对数:
Errors are measured per control volume: cell values against the exact cell averages, node values against the exact nodal values (written ). The four conserved components enter together, in the volume-weighted norm and in the max norm. Neighbouring levels differ in mesh size by exactly a factor of 2, so the observed order is the base-2 logarithm of the error ratio:
每一层都用本页的 Newton 求解到残差低于 (离线研究为了速度改用精确 Jacobian 的块 ILU 预处理,它只影响 GMRES 的迭代次数,不改变离散解)。本页格式的 观测阶依次为 0.48、0.61、0.77、0.80,最大范数为 0.34、0.51、0.70、0.78;格点格式的 阶为 0.35、0.52、0.71、0.85,最大范数为 0.33、0.51、0.64、0.69。最细一层的 误差是最粗一层的 0.158 倍。
Every level is solved by this page’s Newton method until the residual is below (for speed the offline study preconditions GMRES with a block ILU of the exact Jacobian; it changes only the number of GMRES iterations, not the discrete solution). The orders of this page’s scheme are 0.48, 0.61, 0.77, 0.80, the max-norm orders 0.34, 0.51, 0.70, 0.78; the node-centred scheme has orders 0.35, 0.52, 0.71, 0.85 and max-norm orders 0.33, 0.51, 0.64, 0.69. The finest-level error is 0.158 times the coarsest.
格式是一阶的:耗散项 相当于数值粘性 ( 为面两侧存储点的距离),随 线性减小,误差也就是 。两种格式、两种范数的观测阶都小于 1; 阶逐层增大(最细一对网格上本页格式为 0.80)。粗网格的单元尺寸与制造解的变化尺度相比还不小,数值粘性仍然很大,误差还没有进入渐近区,所以观测阶从下方逐步接近 1。
The scheme is first order: the dissipation acts like a numerical viscosity ( the distance between the two stored points of a face), which falls linearly with , so the error is . Both schemes have observed orders below 1 in both norms; the order grows level by level (0.80 for this page’s scheme on the finest pair). On coarse meshes the cells are not yet small compared with the length over which the manufactured field varies, the numerical viscosity is still large, the error has not reached the asymptotic range, and the observed order approaches 1 from below.
同一网格族上的物理载荷
The physical loads on the same family
同一网格族、页面的正常边界、,三个来流马赫数。内部面上 的中位数从第 1 层的 0.604 降到第 5 层的 0.0393;以物面尺寸 和来流速度估计的“数值雷诺数” ()只有 0.5、0.9、1.9、3.7、7.4。这只是量级估计,但足以说明:即使在最细一层,离散流动也更像一个强粘性流动,而不是无粘 Euler 流动。
Same family, the page’s normal boundaries, , three free-stream Mach numbers. The median of over interior faces falls from 0.604 on level 1 to 0.0393 on level 5; the “numerical Reynolds number” built with the body size () is only 0.5, 0.9, 1.9, 3.7, 7.4. It is only an order-of-magnitude estimate, but it makes the point: even on the finest level the discrete flow behaves more like a strongly viscous flow than an inviscid Euler flow.
载荷与此一致()。沿来流的阻力 逐层为 0.193、0.254、0.236、0.176、0.124,第 2 层之后逐层下降;最细一层的 ,远不是 d'Alembert 的零。x 方向合力 为 0.195、0.257、0.240、0.180、0.128;y 方向合力 为 0.00701、0.00588、0.000866、−0.00495、−0.0118,数值很小,并且在加密中变号,只有用单独的纵轴才看得清。物面最大 为 3.36、4.36、4.28、3.60、2.88,始终远高于等熵上界 ;最低的总压比 为 0.870、0.809、0.812、0.842、0.863。格心与格点两个离散目标逐渐接近: 的相对差从第 1 层的 23% 降到第 5 层的 9%, 从 39% 降到 6%。
The loads agree (). The drag along the free stream is 0.193, 0.254, 0.236, 0.176, 0.124 level by level, falling after level 2; on the finest level , nowhere near d’Alembert’s zero. The x force is 0.195, 0.257, 0.240, 0.180, 0.128; the y force is 0.00701, 0.00588, 0.000866, −0.00495, −0.0118, small and changing sign under refinement, visible only on its own vertical axis. The largest wall is 3.36, 4.36, 4.28, 3.60, 2.88, always far above the isentropic bound ; the lowest total-pressure ratio is 0.870, 0.809, 0.812, 0.842, 0.863. The cell-centred and node-centred objectives approach each other: their relative difference in falls from 23% on level 1 to 9% on level 5, in from 39% to 6%.
压力偏差怎样随马赫数变化?在第 1 层,三个马赫数下的 只相差 0.8%:物面压力偏差 与 成正比,也就是按 而不是按动压 标度,所以 的偏差约与 成正比,马赫数越低越大。这是迎风型耗散在低马赫数下的已知行为。加密后这一部分减小,最细一层三者相差 23.9%。
How does the pressure error depend on the Mach number? On level 1 the three values of differ by only 0.8%: the wall pressure excess is proportional to , i.e. it scales like rather than the dynamic pressure , so the error is roughly proportional to and grows as the Mach number falls. This is the known low-Mach behaviour of upwind-type dissipation. Refinement shrinks this part; on the finest level the three differ by 23.9%.
所以,在这些网格上载荷主要是离散误差: 既不是物理阻力,也不是激波或粘性阻力。八边形的尖角使连续无粘解在角点附近奇异,这也减慢载荷的收敛;制造解是光滑的,所以它测得的观测阶(从下方接近 1)反映的才是格式本身的精度。所有这些都不影响第 10–14 节的结论:tangent、adjoint、差分与复数步长求的,都是这个离散 的精确导数。
On these meshes the loads are therefore mostly discretisation error: is neither physical drag nor shock or viscous drag. The corners of the octagon make the continuous inviscid solution singular near them, which further slows the convergence of the loads; the manufactured solution is smooth, so the orders it measures (approaching 1 from below) reflect the scheme’s own accuracy. None of this affects Sections 10–14: the tangent, adjoint, finite differences and complex step all compute exact derivatives of this discrete .
连续伴随:边界条件与验证范围
Continuous adjoint: boundary conditions and verification scope
本节与附录 A 回到连续问题, 表示连续解的变分(第 8 节的记号约定),而不是离散代码的前向导数。对光滑稳态 Euler,令 、 为 通量 Jacobian(与全局残差 Jacobian 是不同的对象),线性化为 。乘以连续乘子 并分部积分,域内伴随方程是 ;边界上留下 ,它必须与目标的变分一起处理,不能把原始的四个边界状态直接复制给 。
This section and Appendix A return to the continuous problem: denotes a variation of the continuous solution (the notation of Section 8), not a forward derivative of the discrete code. For smooth steady Euler flow let and be the flux Jacobians (different objects from the global residual Jacobian ). Linearisation gives . Multiplying by a continuous multiplier and integrating by parts gives the interior adjoint equation , and leaves on the boundary. That term must be combined with the variation of the objective; copying the four primal boundary values to is invalid.
采用本页的加号拉格朗日约定。物面上可容许的变分保持 ,物理壁面通量的变分是 ,而 。消去 的系数,在物面 上得到一个条件:
Use this page’s plus-sign Lagrangian. Admissible wall variations keep , the physical wall flux varies by , and . Cancelling the coefficient of gives one condition on the wall :
远场对应第 4 节的特征闭合。可容许的变分只能改变出域的量,也就是 的值域;要求所有这样的 都使边界项为零,得到( 为入流面, 为出流面):
The far field corresponds to the characteristic closure of Section 4. Admissible variations may change only the outgoing quantities, i.e. they lie in the range of ; requiring the boundary term to vanish for every such gives ( at inflow faces, at outflow faces):
入流面一个条件、出流面三个条件:正好等于原始问题出域特征的个数,因为伴随信息沿相反方向传播。代码实际做的是第 4 节的面转置 ;在边界状态满足闭合的极限下,它的 投影正好是上面的连续条件,其余部分是格式对伴随出域分量的数值闭合(附录 A 推导并实测)。
One condition at an inflow face and three at an outflow face: exactly the number of outgoing primal characteristics, because adjoint information travels the opposite way. What the code actually does is the face transpose of Section 4; in the limit where the boundary state satisfies the closure, its projection is exactly the continuous condition above, and the remainder is the scheme’s numerical closure for the adjoint’s outgoing components (derived and measured in Appendix A).
一致性检查(附录 A):在第 15 节的细分网格族上,把离散伴随 代入物面条件,逐面计算缺陷 。积分残差不除体积、目标按面积向量求和,所以 与 同量纲,不需要额外按面或体积缩放。结果分两部分:在每条八边形边中间一半的面上(远离角点的固定区域),对 本页格式的缺陷 范数逐层下降为 0.641、0.499、0.346、0.214、0.105,对 为 0.686、0.487、0.347、0.183、0.080,离散伴随在这里逐步满足连续物面条件;但整个物面上的最大缺陷为 0.92、0.82、0.91、1.28、1.96,没有下降,它出现在角点旁,那里法向跳变,连续条件本身就不连续。远场条件的检查结果不一致(见附录 A)。因此本页只宣称:远离角点时物面条件的一致性在所测网格上得到数值支持;不宣称离散伴随整体收敛到连续伴随。
Consistency check (Appendix A): on the subdivided family of Section 15, insert the discrete adjoint into the wall condition and compute the defect face by face. Residuals are integrated, not divided by volume, and the objective sums area vectors, so has the units of and needs no extra scaling per face or volume. The result has two parts. On the middle half of every octagon edge (a fixed region away from the corners), the norm of the defect for this page’s scheme falls level by level: 0.641, 0.499, 0.346, 0.214, 0.105 for and 0.686, 0.487, 0.347, 0.183, 0.080 for ; there the discrete adjoint approaches the continuous wall condition. Over the whole wall, however, the largest defect is 0.92, 0.82, 0.91, 1.28, 1.96 and does not fall; it sits next to the corners, where the normal jumps and the continuous condition itself is discontinuous. The far-field check is mixed (Appendix A). This page therefore claims only that the wall condition is numerically supported away from the corners on the tested meshes; it does not claim that the discrete adjoint converges to the continuous adjoint as a whole.
本页没有把同一个离散矩阵的转置再包装成“独立连续伴随”。当前验证对象是完整离散 Euler 的状态、几何和来流导数,它们对离散 是精确的;连续伴随只给出上面的方程和边界条件,本页没有独立求解它,也没有证明离散伴随在加密下收敛到它。
This page does not relabel a transpose of the same discrete matrix as an independent continuous adjoint. It verifies the discrete state, geometry and free-stream derivatives, which are exact for the discrete . The continuous adjoint appears only through the equations and boundary conditions above; this page neither solves it independently nor proves that the discrete adjoint converges to it under refinement.
离散乘子与连续场为什么不能直接混用
Why a discrete multiplier is not automatically a continuous field
正文使用普通数组点积定义 。若另选带权内积,算子的伴随就会改变:状态权矩阵为 、残差权矩阵为 时,带权伴随是 。代码的普通转置不能在没有换元说明的情况下被写成该带权算子。
The main derivation defines using ordinary array dot products. A weighted inner product changes the adjoint: with state and residual weights and it is . The coded ordinary transpose cannot be relabelled as that weighted operator without a change of variables.
残差的归一化也会改变乘子的数值。若改为 , 为可逆的行尺度矩阵(例如各控制体的体积),则在收敛解上 ,;目标总梯度保持不变。即使 随设计改变,由 产生的项也乘以 ,在精确收敛时为零。把控制体积分残差改成体积平均残差后,不能仍直接比较原来的乘子曲线。
Residual normalization also changes multiplier values. For with an invertible row-scaling matrix (for example the control-volume sizes), at convergence and , while the total objective gradient is unchanged. Even when depends on the design, the terms from multiply 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.
总结:从标量面循环到 Euler 方程组
Summary: from scalar face loops to Euler systems
几何、面方向、守恒分发和反向累加沿用二维标量;热力学和多特征边界承接拟一维 Euler。新增的难点是四分量耦合、压力壁面通量、特征状态对法向的依赖,以及多条反传路径。核心仍是同一条链:节点 → 面几何 → 边界状态与通量 → 残差 → 收敛解 → 压力目标。
Geometry, face orientation, conservative scatter and reverse accumulation follow the scalar pages. Thermodynamics and characteristic boundaries extend quasi-one-dimensional Euler. The new issues are four-component coupling, pressure wall fluxes, normal-dependent characteristic states and merged reverse paths. The chain remains nodes → face geometry → boundary states and fluxes → residual → converged state → pressure objective.
首版提供一阶、亚声速分支的原始求解、matrix-free tangent/adjoint、来流与几何梯度、全链路差分和复数残差验证;制造解在固定多边形的均匀细分网格族上给出从下方逐步接近一阶的观测阶。固定耗散 使这些网格上的压力与载荷主要是离散误差;离散伴随在远离角点处逐步满足连续物面条件,但没有整体验证与连续伴随一致。这些限制都不影响导数对离散目标的精确性。没有加入激波、限制器、二阶重构或湍流闭合。下一步扩展应逐项增加离散算子,并让验证始终针对实际执行的程序。
This first version provides first-order subsonic primal solves, matrix-free tangent/adjoint operators, free-stream and geometry gradients, full-rerun finite differences and complex-residual checks; a manufactured solution on a uniformly subdivided fixed-polygon family gives observed orders approaching first order from below. The fixed dissipation makes pressures and loads on these meshes mostly discretisation error; the discrete adjoint approaches the continuous wall condition away from the corners, but its consistency with the continuous adjoint as a whole is not verified. Neither limitation affects the exactness of the derivatives of the discrete objective. Shocks, limiters, second-order reconstruction and turbulence closure are not implemented. Each future operator should be added together with verification of the program actually executed.
下面是本页完整的数值核心。它的计算图 tape() 就是前向与反向两种模式:forward() 的数组 d 存放每个运算结果的切向量 ,reverse() 的数组 w 存放伴随 (按与记录相反的次序用 += 累加);jvp、vjp 与 faceVjp 是对应的乘积 、 与单个面的反向扫描。
Below is this page’s complete numerical core. Its computation graph tape() provides both modes: the array d of forward() holds the tangent of every operation, and the array w of reverse() holds the adjoint (accumulated with += in the reverse order of recording); jvp, vjp and faceVjp are the corresponding products , and the reverse sweep of one face.
展开本页完整数值核心Complete numerical core
/* Smooth 2-D Euler: a scalar-operation tape gives exact matrix-free JVP/VJP.
Scalar face algebra also runs with independent complex arithmetic. */
(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 mesh(level=1){ // level 1 is indexed-identical to the scalar pages
// Level 1: angles t_j=2*pi*j/8. Ring 0 samples the body curve, ring 2 the circle of radius 1.9, ring 1 blends them.
// Level l>1 subdivides level l-1 uniformly: every edge is halved at its midpoint, every quadrilateral gains its
// vertex average and every triangle is split through its edge midpoints. The body and the far boundary therefore
// stay the two level-1 octagons, the domain never changes, and the mesh size halves from level to level.
if(!Number.isInteger(level)||level<1||level>6)throw Error('Unsupported mesh level');
let X=[],L=1;const mid=(a,b)=>[.5*(a[0]+b[0]),.5*(a[1]+b[1])];
for(let r=0;r<=2;r++){const k=r,z=k<=1?.45*k:.45+.55*(k-1);for(let j=0;j<8;j++){const t=2*Math.PI*j/8,rad=.55*(1+.28*Math.cos(t)+.12*Math.sin(2*t)),b=[.1+rad*Math.cos(t),-.04+rad*Math.sin(t)],o=[1.9*Math.cos(t),1.9*Math.sin(t)];X.push(b.map((x,d)=>(1-z)*x+z*o[d]));}}
for(let l=1;l<level;l++){const nt=8*L,old=(r,j)=>X[r*nt+(j%nt)],Y=[];
for(let R=0;R<=4*L;R++)for(let J=0;J<2*nt;J++){const r=R>>1,j=J>>1;
Y.push(R%2===0&&J%2===0?old(r,j):R%2===0?mid(old(r,j),old(r,j+1)):J%2===0?mid(old(r,j),old(r+1,j))
:r<L?[0,1].map(d=>.25*(old(r,j)[d]+old(r+1,j)[d]+old(r+1,j+1)[d]+old(r,j+1)[d])):mid(old(r,j),old(r+1,j+1)));}
X=Y;L*=2;}
const nt=8*L,C=[],id=(r,j)=>r*nt+(j%nt);for(let r=0;r<2*L;r++)for(let j=0;j<nt;j++){const a=id(r,j),b=id(r,j+1),c=id(r+1,j+1),d=id(r+1,j);if(r<L)C.push([a,d,c,b]);else C.push([a,d,c],[a,c,b]);}return {X,C,nt,level};}
function topology(mesh,scheme){const edges=[],map=new Map();mesh.C.forEach((cell,c)=>cell.forEach((a,k)=>{const b=cell[(k+1)%cell.length],key=[Math.min(a,b),Math.max(a,b)].join('-');if(map.has(key))edges[map.get(key)].R=c;else{map.set(key,edges.length);edges.push({a,b,L:c,R:-1});}}));for(const e of edges)e.bc=e.R>=0?'interior':e.a<mesh.nt&&e.b<mesh.nt?'wall':'far';
let faces;if(scheme==='cell')faces=edges.map(e=>({...e,terms:[[e.a,e.b,1]],segs:[[e.a,e.b]]}));
else{const off=mesh.X.length;faces=edges.map(e=>{const a=Math.min(e.a,e.b),b=Math.max(e.a,e.b),mid=off+mesh.C.length+edges.indexOf(e),terms=[],segs=[];for(const c of [e.L,e.R].filter(i=>i>=0)){const cell=mesh.C[c],s=cell[(cell.indexOf(a)+1)%cell.length]===b?1:-1;terms.push([mid,off+c,s]);segs.push([mid,off+c]);}return {a,b,L:a,R:b,bc:'interior',terms,segs};});for(const e of edges.filter(e=>e.R<0)){const mid=off+mesh.C.length+edges.indexOf(e);for(const a of [e.a,e.b])faces.push({L:a,R:-1,bc:e.bc,terms:[[e.a,e.b,.5]],segs:[[a,mid]]});}}
const rank={interior:0,wall:1,far:2};faces.sort((a,b)=>rank[a.bc]-rank[b.bc]||a.L-b.L||a.R-b.R);return {edges,faces};}
function model({scheme='cell',level=1,M=.2,angle=10,objective='lift',coords,mms}={}){
if(!['cell','node'].includes(scheme)||!Number.isInteger(level)||level<1||level>6||!Number.isFinite(M)||M<0||M>.3||!Number.isFinite(angle))throw Error('Unsupported Euler model settings');
const grid=mesh(level);if(coords)grid.X=coords.map(p=>p.slice());const topo=topology(grid,scheme),nc=scheme==='cell'?grid.C.length:grid.X.length,nd=4*nc,params=[M,angle*Math.PI/180,...grid.X.flat()];
// Incoming entropy/vorticity branch is tied to the BASE free-stream normal.
const geo=geometry(real,params.slice(2));topo.faces.forEach((f,i)=>f.inflow=geo.faces[i].n[0]*Math.cos(params[1])+geo.faces[i].n[1]*Math.sin(params[1])<0);
// Manufactured-solution mode (order study only): every boundary face listed in data.exterior uses the far-field
// characteristic closure with that constant exterior state in place of U_inf (its own normal velocity fixes the
// branch; listed wall faces use the same closure), and a constant source is subtracted from the residual.
const data=mms?mms({grid,topo,geo,scheme,nc,nd,physics:physics(real)}):null;
if(data)topo.faces.forEach((f,i)=>{if(f.bc!=='interior'&&data.exterior[i]){f.exterior=data.exterior[i];const z=physics(real).prim(f.exterior);f.inflow=z.u*geo.faces[i].n[0]+z.v*geo.faces[i].n[1]<0;}});
function geometry(o,Xflat){const {c,add,sub,mul,pow}=o,scale=(a,s)=>a.map(v=>mul(v,c(s))),avg=pts=>[0,1].map(k=>mul(pts.reduce((s,p)=>add(s,p[k]),c(0)),c(1/pts.length))),X=Array.from({length:grid.X.length},(_,i)=>Xflat.slice(2*i,2*i+2)),cent=grid.C.map(cell=>avg(cell.map(i=>X[i]))),mid=topo.edges.map(e=>avg([X[e.a],X[e.b]])),points=[...X,...cent,...mid];
const area=poly=>mul(poly.reduce((s,p,i)=>{const q=poly[(i+1)%poly.length];return add(s,sub(mul(p[0],q[1]),mul(p[1],q[0])));},c(0)),c(.5)),vol=Array.from({length:nc},()=>c(0)),polys=Array.from({length:nc},()=>[]);
grid.C.forEach((cell,j)=>{if(scheme==='cell'){vol[j]=area(cell.map(i=>X[i]));polys[j].push(cell.map(i=>X[i]));}else cell.forEach((a,k)=>{const next=cell[(k+1)%cell.length],prev=cell[(k-1+cell.length)%cell.length],poly=[X[a],avg([X[a],X[next]]),cent[j],avg([X[prev],X[a]])];vol[a]=add(vol[a],area(poly));polys[a].push(poly);});});
const faces=topo.faces.map(f=>{let n=[c(0),c(0)];for(const [a,b,s] of f.terms){n[0]=add(n[0],mul(sub(points[b][1],points[a][1]),c(s)));n[1]=sub(n[1],mul(sub(points[b][0],points[a][0]),c(s)));}return {n,len:pow(add(mul(n[0],n[0]),mul(n[1],n[1])),.5),segs:f.segs.map(([a,b])=>[points[a],points[b]])};});return {X,cent,points,vol,polys,faces,locations:scheme==='cell'?cent:X};}
function physics(o){const {c,add,sub,mul,div,pow,sin,cos}=o,scale=(v,s)=>v.map(x=>mul(x,s)),sum=(a,b)=>a.map((x,i)=>add(x,b[i]));
function prim(U){const rho=U[0],u=div(U[1],rho),v=div(U[2],rho),p=mul(c(gamma-1),sub(U[3],mul(c(.5),add(mul(U[1],u),mul(U[2],v))))),a=pow(div(mul(c(gamma),p),rho),.5);return {rho,u,v,p,a};}
function cons(r,u,v,p){return [r,mul(r,u),mul(r,v),add(div(p,c(gamma-1)),mul(mul(c(.5),r),add(mul(u,u),mul(v,v))))];}
function physical(U,n){const q=prim(U),un=add(mul(q.u,n[0]),mul(q.v,n[1]));return [mul(q.rho,un),add(mul(U[1],un),mul(q.p,n[0])),add(mul(U[2],un),mul(q.p,n[1])),mul(add(U[3],q.p),un)];}
function far(UL,UI,n,len,inflow,log){const q=prim(UL),z=prim(UI),nx=div(n[0],len),ny=div(n[1],len),normal=q=>add(mul(q.u,nx),mul(q.v,ny)),tan=q=>sub(mul(q.v,nx),mul(q.u,ny)),rp=add(normal(q),mul(c(2/(gamma-1)),q.a)),rm=sub(normal(z),mul(c(2/(gamma-1)),z.a)),un=mul(c(.5),add(rp,rm)),sound=mul(c((gamma-1)/4),sub(rp,rm)),up=inflow?z:q,S=div(up.p,pow(up.rho,gamma)),rho=pow(div(mul(sound,sound),mul(c(gamma),S)),1/(gamma-1)),ut=tan(up),u=sub(mul(un,nx),mul(ut,ny)),v=add(mul(un,ny),mul(ut,nx)),pb=mul(S,pow(rho,gamma));
if(log)Object.assign(log,{nx,ny,unL:normal(q),unInf:normal(z),utL:tan(q),utInf:tan(z),cL:q.a,cInf:z.a,SL:div(q.p,pow(q.rho,gamma)),SInf:div(z.p,pow(z.rho,gamma)),rp,rm,un,c:sound,S,rho,ut,u,v,p:pb});
return cons(rho,u,v,pb);}
function free(p){return cons(c(1),mul(mul(p[0],c(Math.sqrt(gamma))),cos(p[1])),mul(mul(p[0],c(Math.sqrt(gamma))),sin(p[1])),c(1));}
function flux(UL,UR,f,g,UI){const q=prim(UL);if(f.bc==='wall'&&!f.exterior)return {H:[c(0),mul(q.p,g.n[0]),mul(q.p,g.n[1]),c(0)],UR:UL};const B=f.R>=0?UR:far(UL,f.exterior?f.exterior.map(c):UI,g.n,g.len,f.inflow),FL=physical(UL,g.n),FR=physical(B,g.n);return {UR:B,H:FL.map((x,k)=>sub(mul(c(.5),add(x,FR[k])),mul(mul(c(alpha/2),g.len),sub(B[k],UL[k]))))};}
return {prim,cons,physical,far,free,flux};}
function evaluate(o,U,p){const {c,add,sub,mul}=o,g=geometry(o,p.slice(2)),ph=physics(o),UI=ph.free(p),R=Array.from({length:nd},()=>c(0)),local=[];let J=c(0);
topo.faces.forEach((f,j)=>{const L=U.slice(4*f.L,4*f.L+4),RR=f.R>=0?U.slice(4*f.R,4*f.R+4):null,q=ph.flux(L,RR,f,g.faces[j],UI);q.H.forEach((h,k)=>{R[4*f.L+k]=add(R[4*f.L+k],h);if(f.R>=0)R[4*f.R+k]=sub(R[4*f.R+k],h);});if(f.bc==='wall')J=add(J,mul(ph.prim(L).p,g.faces[j].n[objective==='drag'?0:1]));local.push({...q,UL:L});});if(data)for(let i=0;i<nd;i++)R[i]=sub(R[i],c(data.forcing[i]));return {R,J,g,local};}
function linearize(U,p=params){const t=tape(),u=U.map(t.op.input),pp=p.map(t.op.input),out=evaluate(t.op,u,pp),outids=[...out.R,out.J];return {R:out.R.map(i=>t.val[i]),J:t.val[out.J],size:t.val.length,faceVjp:(j,w)=>{const b=t.reverse(out.local[j].H,w);return {U:b.slice(0,nd),p:b.slice(nd)};},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.local.map(q=>q.H.map(i=>d[i]))};},vjp:(w,bJ=0)=>{const b=t.reverse(outids,[...w,bJ]);return {U:b.slice(0,nd),p:b.slice(nd)};}};}
function initial(p=params){return Array.from({length:nc},()=>physics(real).free(p)).flat();}
function valid(U,p=params){const ph=physics(real);return Array.from({length:nc},(_,i)=>ph.prim(U.slice(i*4,i*4+4))).every(q=>q.rho>.05&&q.p>.05&&Math.hypot(q.u,q.v)/q.a<.85);}
function precondition(g,transpose=false){const diag=zeros(nc),lower=Array.from({length:nc},()=>[]);topo.faces.forEach((f,j)=>{const a=alpha*g.faces[j].len/2;diag[f.L]+=a;if(f.R>=0){diag[f.R]+=a;const l=Math.max(f.L,f.R),r=Math.min(f.L,f.R);lower[l].push([r,-a]);}});return rhs=>{const z=rhs.slice();if(transpose){for(let i=nc-1;i>=0;i--)for(let k=0;k<4;k++){z[4*i+k]/=diag[i];for(const [j,c] of lower[i])z[4*j+k]-=c*z[4*i+k];}return z;}for(let i=0;i<nc;i++)for(let k=0;k<4;k++){for(const [j,c] of lower[i])z[4*i+k]-=c*z[4*j+k];z[4*i+k]/=diag[i];}return z;};}
function diagnostics(U,p=params){const out=evaluate(real,U,p),ph=physics(real);let maxSpeed=0,maxMach=0,minRho=Infinity,minP=Infinity,mismatches=0;
out.local.forEach((q,j)=>[q.UL,q.UR].forEach((u,k)=>{const z=ph.prim(u),g=out.g.faces[j],un=(z.u*g.n[0]+z.v*g.n[1])/g.len;maxSpeed=Math.max(maxSpeed,Math.abs(un)+z.a);maxMach=Math.max(maxMach,Math.hypot(z.u,z.v)/z.a);minRho=Math.min(minRho,z.rho);minP=Math.min(minP,z.p);if(k===1&&(topo.faces[j].bc==='far'||topo.faces[j].exterior)&&(un<0)!==topo.faces[j].inflow)mismatches++;}));return {maxSpeed,maxMach,minRho,minP,mismatches,minVolume:Math.min(...out.g.vol)};}
function solve(p=params,start,opts={}){let U=start?start.slice():initial(p);const history=[],states=[],P=precondition(geometry(real,p.slice(2)));for(let it=0;it<35;it++){const A=linearize(U,p),rn=norm(A.R);history.push(rn);states.push(U.slice());if(rn<3e-12){const check=diagnostics(U,p);if(opts.check!==false&&!(check.maxSpeed<alpha&&check.maxMach<.85&&check.minRho>.05&&check.minP>.05&&check.minVolume>0&&check.mismatches===0))throw Error('State leaves supported smooth subsonic boundary branch');return {U,J:A.J,history,states,converged:true,A,diagnostics:check};}const l=gmres(v=>A.jvp(v).R,A.R.map(x=>-x),opts.precondition?opts.precondition(A,false):P,Math.min(1e-12,rn*1e-3),nd);if(!l.converged&&l.residual>rn*.01)throw Error('Euler Newton linear solve failed: '+l.residual);let step=1,next;while(step>1e-7){next=U.map((x,i)=>x+step*l.x[i]);if(valid(next,p)&&norm(evaluate(real,next,p).R)<(1-1e-4*step)*rn)break;step*=.5;}if(step<=1e-7)throw Error('Euler positivity/residual line search failed');U=next;}throw Error('Euler primal did not converge');}
function sensitivities(sol,p=params,opts={}){const A=sol.A||linearize(sol.U,p),P=opts.precondition?opts.precondition(A,true):precondition(geometry(real,p.slice(2)),true),Jg=A.vjp(zeros(nd),1),adj=gmres(v=>A.vjp(v).U,Jg.U.map(x=>-x),P,2e-12,nd);if(!adj.converged)throw Error('Euler adjoint failed');const total=A.vjp(adj.x,1);return {psi:adj.x,gradient:total.p,history:adj.history,residual:adj.residual};}
function tangent(sol,k,p=params){const A=sol.A||linearize(sol.U,p),dp=zeros(p.length);dp[k]=1;const direct=A.jvp(zeros(nd),dp),P=precondition(geometry(real,p.slice(2))),l=gmres(v=>A.jvp(v).R,direct.R.map(x=>-x),P,2e-12,nd);if(!l.converged)throw Error('Euler tangent failed');return {...l,gradient:A.jvp(l.x,dp).J};}
function complexGradient(sol,k,h=1e-25,p=params){const A=sol.A||linearize(sol.U,p),pp=p.map((x,i)=>[x,i===k?h:0]),U=sol.U.map(x=>[x,0]),P=precondition(geometry(real,p.slice(2)));let im=zeros(nd),history=[];for(let it=0;it<4;it++){const out=evaluate(complex,U,pp),rhs=out.R.map(x=>-x[1]/h),rn=norm(rhs);history.push(rn);if(rn<2e-12)break;const l=gmres(v=>A.jvp(v).R,rhs,P,1e-12,nd);im=im.map((x,i)=>x+l.x[i]);U.forEach((x,i)=>x[1]=h*im[i]);}const out=evaluate(complex,U,pp),residual=norm(out.R.map(x=>x[1]/h));if(residual>2e-10)throw Error('complex residual not converged');return {gradient:out.J[1]/h,residual,history};}
function boundary(j,U,p=params){ // one far face: intermediate values, U_b and G_U=dU_b/dU_L (by the same far())
const f=topo.faces[j];if(f.bc!=='far'&&!f.exterior)throw Error('Not a far-field face');const g=geometry(real,p.slice(2)).faces[j],ph=physics(real),UL=U.slice(4*f.L,4*f.L+4),UI=f.exterior||ph.free(p),log={},Ub=ph.far(UL,UI,g.n,g.len,f.inflow,log),t=tape(),x=UL.map(t.op.input),out=physics(t.op).far(x,UI.map(t.op.c),g.n.map(t.op.c),t.op.c(g.len),f.inflow),cols=[0,1,2,3].map(k=>{const d=t.forward([0,1,2,3].map(i=>+(i===k)));return out.map(o=>d[o]);});
return {face:j,inflow:f.inflow,UL,UI,Ub,log,n:g.n,len:g.len,G:[0,1,2,3].map(r=>cols.map(col=>col[r]))};}
function riemann(U,n,len){ // Riemann coordinates W=(r+,r-,S,u_t) of a state at a face and dW/dU (same prim())
const t=tape(),x=U.map(t.op.input),{c,add,sub,mul,div,pow}=t.op,q=physics(t.op).prim(x),nx=c(n[0]/len),ny=c(n[1]/len),un=add(mul(q.u,nx),mul(q.v,ny)),ut=sub(mul(q.v,nx),mul(q.u,ny)),k=c(2/(gamma-1)),out=[add(un,mul(k,q.a)),sub(un,mul(k,q.a)),div(q.p,pow(q.rho,gamma)),ut],cols=[0,1,2,3].map(j=>{const d=t.forward([0,1,2,3].map(i=>+(i===j)));return out.map(o=>d[o]);});
return {W:out.map(o=>t.val[o]),dW:[0,1,2,3].map(r=>cols.map(col=>col[r]))};}
function fluxJacobian(U,n){const t=tape(),x=U.map(t.op.input),out=physics(t.op).physical(x,n.map(t.op.c)),cols=[0,1,2,3].map(k=>{const d=t.forward([0,1,2,3].map(i=>+(i===k)));return out.map(o=>d[o]);});return [0,1,2,3].map(r=>cols.map(col=>col[r]));}
return {scheme,grid,topo,nc,nd,params,data,boundary,riemann,fluxJacobian,diagnostics,preconditioner:transpose=>precondition(geo,transpose),geometry:()=>geometry(real,params.slice(2)),geometryAt:p=>geometry(real,p.slice(2)),physics:physics(real),evaluate:(U,p=params)=>evaluate(real,U,p),linearize,initial,solve,sensitivities,tangent,complexGradient,valid};
}
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],converged:true};const V=[b.map(x=>x/beta)],Z=[],H=[],cs=zeros(max),sn=zeros(max),g=zeros(max+1),history=[beta];g[0]=beta;let m=0;
for(let j=0;j<max;j++){const h=zeros(j+2);H.push(h);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 hij=dot(V[i],w);h[i]+=hij;w=w.map((x,k)=>x-hij*V[i][k]);}h[j+1]=norm(w);const nextnorm=h[j+1];if(nextnorm>1e-16)V.push(w.map(x=>x/nextnorm));for(let i=0;i<j;i++){const a=h[i],b=h[i+1];h[i]=cs[i]*a+sn[i]*b;h[i+1]=-sn[i]*a+cs[i]*b;}const a=h[j],bb=h[j+1],r=Math.hypot(a,bb);cs[j]=r?a/r:1;sn[j]=r?bb/r:0;h[j]=r;h[j+1]=0;g[j+1]=-sn[j]*g[j];g[j]*=cs[j];m=j+1;history.push(Math.abs(g[j+1]));if(Math.abs(g[j+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[j][i]*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,converged:residual<=Math.max(tol*5,1e-13)};}
const api={model,mesh,gamma,alpha,gmres,dot,norm,real,complex,tape};if(typeof module!=='undefined')module.exports=api;else root.Euler2D=api;
})(typeof window!=='undefined'?window:globalThis);
附录 A:连续伴随与一致性检查
Appendix A: continuous adjoint and consistency check
A.1 变分与域内伴随方程
A.1 Variation and the interior adjoint equation
连续问题:在 内 ;物面 上 ;远场 上入域特征量取来流值;目标 ( 指向流体域外)。拉格朗日函数 ,在固定几何与固定来流下变分:
Continuous problem: in ; on the wall ; incoming characteristic quantities take free-stream values on the far field ; objective ( out of the fluid). With , vary at fixed geometry and free stream:
域内项对任意 为零,给出伴随方程 。边界项只对可容许的 要求为零,由此得到物面与远场条件。
The interior term vanishes for every , which gives the adjoint equation . The boundary terms need to vanish only for admissible , which gives the wall and far-field conditions.
A.2 物面条件
A.2 The wall condition
在物面上 ,并且可容许变分保持 。由第 1 节的 ,,于是物面上的边界项与目标变分合并为 。 任意,所以得到第 16 节的物面条件。它只约束 的一个组合,与滑移壁面只有一个入域特征相对应。
On the wall , and admissible variations keep . From of Section 1, , so the wall boundary term and the objective variation combine into . Since is arbitrary, the wall condition of Section 16 follows. It constrains only one combination of , matching the single incoming characteristic of a slip wall.
A.3 远场条件:连续形式与代码中的离散形式
A.3 The far-field condition: continuous form and the discrete form in the code
用 Riemann 坐标 表示,闭合是 ,所以对任意 都有 ()。 满足 ,在满足闭合的状态()上 是投影:,秩等于出域特征个数(入流 1、出流 3)。连续问题的可容许变分正是 的值域,于是远场条件是 ,即第 16 节的 ;由 可逆,它等价于对每个出域坐标 有 。
In the Riemann coordinates the closure reads , so for every (). satisfies , and at a state that satisfies the closure () is a projection: , with rank equal to the number of outgoing characteristics (1 at inflow, 3 at outflow). The admissible variations of the continuous problem are exactly the range of , so the far-field condition is , i.e. the of Section 16; since is invertible, it is equivalent to for every outgoing coordinate .
代码中远场面对其控制体伴随方程的贡献是第 4 节给出的:
In the code a far-field face contributes the following to its volume’s adjoint equation (Section 4):
在 的极限下它等于 ;左乘投影 得到 ,正是连续远场条件的表达式,而 部分是格式自己给伴随出域分量加的迎风型数值闭合。
In the limit this equals . Multiplying by the projection leaves , exactly the expression of the continuous far-field condition, while the part is the scheme’s own upwind-type numerical closure for the adjoint’s outgoing components.
这些结论在本页第 1 层网格的收敛解上实测(、,全部远场面): 与 最大差 6×10⁻¹⁵( 元素最大为 4.28); 最大 1×10⁻¹⁵;闭合状态上 最大 5×10⁻¹⁵,迹在入流面为 1、出流面为 3(偏差小于 2×10⁻¹⁵);上面的面转置公式与计算图逆序结果最大差 2×10⁻¹⁵。
These statements are measured on this page’s level-1 converged solution (, , every far-field face): and differ by at most 6×10⁻¹⁵ (largest entry of : 4.28); is at most 1×10⁻¹⁵; at the closure state is at most 5×10⁻¹⁵ and the trace is 1 on inflow faces and 3 on outflow faces (deviation below 2×10⁻¹⁵); the face transpose formula above and the reversed computation graph differ by at most 2×10⁻¹⁵.
A.4 一致性检查
A.4 The consistency check
在第 15 节的 5 层细分网格上(、,正常边界),对两个目标求离散伴随,并计算:物面缺陷 (第 16 节)的面长加权 范数与最大值;只取每条八边形边中间一半的面(固定区域,远离角点)的 范数;远场的比值:
On the 5 subdivided levels of Section 15 (, , normal boundaries), solve the discrete adjoint for both objectives and compute: the length-weighted norm and the maximum of the wall defect (Section 16); its norm over only the middle half of every octagon edge (a fixed region away from the corners); the far-field ratio:
以及 。若离散伴随收敛到满足这些条件的连续伴随,中间一半的缺陷和 都应随 趋于零, 应保持有界;角点处连续条件本身跳变,所以整个物面上的最大缺陷不必趋于零。
and . If the discrete adjoint converged to a continuous adjoint satisfying these conditions, the middle-half defect and would tend to zero with and would stay bounded; at the corners the continuous condition itself jumps, so the largest defect over the whole wall need not tend to zero.
结果(本页格式)。整个物面上缺陷的 范数对 为 0.64、0.51、0.41、0.41、0.44,对 为 0.69、0.55、0.40、0.30、0.25;最大缺陷对 为 0.92、0.82、0.91、1.28、1.96,出现在角点旁(最细一层在 46.0°,最近的角点在 45°)。只取每条边中间一半的面, 缺陷对 为 0.641、0.499、0.346、0.214、0.105,对 为 0.686、0.487、0.347、0.183、0.080,逐层下降,最细一对网格上的观测阶分别为 1.02 与 1.19;格点格式的对应值()为 0.585、0.354、0.239、0.129、0.055。远场比值 对 为 1.31、0.92、0.58、0.42、0.35,逐层下降;对 为 0.19、0.31、0.38、0.38、0.36,没有逐层下降,最细一层是第 1 层的 1.93 倍。 对 为 1.08、2.08、3.12、3.73、4.10,最细一层出现在 (0.45, −1.70),靠近下方的远场边。
Results (this page’s scheme). Over the whole wall the norm of the defect is 0.64, 0.51, 0.41, 0.41, 0.44 for and 0.69, 0.55, 0.40, 0.30, 0.25 for ; the largest defect for is 0.92, 0.82, 0.91, 1.28, 1.96 and sits next to a corner (at 46.0° on the finest level, nearest corner 45°). Restricted to the middle half of every edge, the defect is 0.641, 0.499, 0.346, 0.214, 0.105 for and 0.686, 0.487, 0.347, 0.183, 0.080 for , falling level by level, with observed orders 1.02 and 1.19 on the finest pair; the node-centred scheme gives 0.585, 0.354, 0.239, 0.129, 0.055 (). The far-field ratio is 1.31, 0.92, 0.58, 0.42, 0.35 for , falling level by level, and 0.19, 0.31, 0.38, 0.38, 0.36 for , not falling level by level; its finest value is 1.93 times the first. is 1.08, 2.08, 3.12, 3.73, 4.10 for ; on the finest level it sits at (0.45, −1.70), next to the lower far-field edge.
结论:远离角点时,离散伴随在所测网格上逐步满足连续物面条件,这是本页能给出的一致性证据。角点旁的缺陷不下降,符合连续条件在角点处不连续、连续伴随在角点附近奇异的预期; 的远场比值没有逐层下降,原因本页没有查明。所以本页不宣称离散伴随整体收敛到连续伴随。
Conclusion: away from the corners the discrete adjoint approaches the continuous wall condition on the tested meshes; this is the consistency evidence this page can offer. The defect next to the corners does not fall, as expected where the continuous condition is discontinuous and the continuous adjoint is singular; the far-field ratio for does not fall level by level, for reasons this page has not established. The page therefore does not claim that the discrete adjoint converges to the continuous adjoint as a whole.
A.5 验证范围
A.5 Scope of the verification
已验证:离散残差、目标与它们的全部导数(状态、来流、几何)彼此一致,tangent 与 adjoint 在舍入误差内相同,完整重算差分与独立复数步长在截断误差与线性求解容差之内与它们一致;远场闭合的 结构与面转置公式;制造解的观测阶从下方逐步接近一阶;远离角点时物面伴随条件的缺陷逐层下降。未验证:离散伴随在角点附近和远场()上与连续伴随一致;离散载荷收敛到物理载荷。要检验这些,可能需要更弱的耗散(例如随状态变化的波速或二阶重构)、光滑物面和更远的远场;它们属于后续工作,而不是本页离散求导的缺陷。
Verified: the discrete residual and objective and all their derivatives (state, free stream, geometry) agree, with the tangent and the adjoint equal to round-off, and the full-rerun differences and the independent complex step agreeing with them within truncation error and the linear-solver tolerance; the structure of and the face transpose of the far-field closure; observed orders of the manufactured solution approaching first order from below; a wall-condition defect that falls level by level away from the corners. Not verified: consistency of the discrete adjoint with the continuous adjoint next to the corners and at the far field (); convergence of the discrete loads to physical loads. Checking these would likely need weaker dissipation (for example a state-dependent wave speed or second-order reconstruction), a smooth body and a more distant far field; they are future work, not defects of this page’s discrete differentiation.