{"docs":[{"audience":[],"chunks":[],"headings":[],"keywords":["home","overview","reading paths"],"level":1,"max_level":1,"path":"index.md","section":"","section_title":"Home","summary":"Gyre is a differentiable, high-order computational fluid dynamics capability: the flow discipline that a multidisciplinary design optimization framework wraps.","text":"","title":"Gyre","url":"index.html"},{"audience":[],"chunks":[{"anchor":"","end":872,"heading":"","level":1,"start":0}],"headings":[],"keywords":["overview"],"level":1,"max_level":1,"path":"overview/index.md","section":"overview","section_title":"Overview","summary":"What Gyre is, where it sits in a design-optimization stack, how it is put together, and what it does not do.","text":"Start here. These pages explain the purpose of the project in plain terms, show how a request travels from an interface to the numerical core and back, and state the limits of the current capability. What Gyre is L1 Gyre is a differentiable high-order flow solver delivered as a service. It gives a design-optimization framework what it needs from an aerodynamics discipline — converged flows, outputs, coupling fields and exact derivatives — and deliberately nothing more. Architecture at a glance L1 A case specification flows through one pipeline — mesh, discretization, discipline — that every interface shares. The service wraps that pipeline in isolated worker processes and exposes it to programs, people and agents. Capabilities and limits L1 What Gyre does, how well each part is verified, and what it does not do yet. Read this before planning work on top of it.","title":"Overview","url":"overview/index.html"},{"audience":["everyone"],"chunks":[{"anchor":"","end":1709,"heading":"","level":1,"start":0},{"anchor":"what-you-can-do-with-it-today","end":2829,"heading":"What you can do with it today","level":2,"start":1710},{"anchor":"a-thirty-second-example","end":3366,"heading":"A thirty-second example","level":2,"start":2830},{"anchor":"how-the-pieces-relate","end":3816,"heading":"How the pieces relate","level":3,"start":3367},{"anchor":"design-principles","end":4470,"heading":"Design principles","level":3,"start":3817}],"headings":["What you can do with it today","A thirty-second example","How the pieces relate","Design principles"],"keywords":["overview","MDO","differentiable","discipline"],"level":1,"max_level":3,"path":"overview/what-is-gyre.md","section":"overview","section_title":"Overview","summary":"Gyre is a differentiable high-order flow solver delivered as a service. It gives a design-optimization framework what it needs from an aerodynamics discipline — converged flows, outputs, coupling fields and exact derivatives — and deliberately nothing more.","text":"High-fidelity multidisciplinary design optimization (MDO) couples a flow solver with structures, propulsion, controls and a geometry engine, and drives the whole with a gradient-based optimizer. The flow solver is usually the most expensive and the least cooperative member: its gradients are hard to obtain, hard to trust and hard to keep in sync with the code. Gyre is a flow discipline designed for that seat. It is built around three commitments. Everything is differentiable. The solver is written in JAX. Jacobian products, adjoints, shape sensitivities and error estimates are produced by automatic differentiation of the same kernels that compute the flow, so they are exact for the discrete problem and can never drift out of sync. High order, with entropy-conservative volume terms. A split-form discontinuous Galerkin spectral element method on curved meshes whose volume terms conserve entropy exactly, so aliasing inside an element cannot feed the instability that limits under-resolved high-order methods. A discrete entropy inequality is proven with the entropy-stable interface fluxes ( es_roe , llf ); with the default classical Roe flux entropy decay is measured, not proven, and positivity is not enforced ( the exact conditions ). A service, not a script. One deployable unit exposes a REST API, warm analysis sessions for design loops, an agent (MCP) interface, declarative pipelines and this documentation — built from the revision that is running. Note What Gyre is not Gyre contains no optimizer, no design-variable management and no problem formulation. Those belong to the MDO framework. Gyre’s contract ends at: given inputs, return converged outputs and exact derivative products .\nSolve steady compressible Euler, laminar Navier–Stokes and Reynolds-averaged (Spalart–Allmaras) flows on curved high-order meshes in two and three dimensions: aerofoils, cylinders, channels, flat plates, spheres and ellipsoids from the built-in generators. Use the built-in mesh generators or bring a mesh of curved quadrilaterals or hexahedra from Gmsh ( mesh files ). Obtain lift, drag, side force, moments, heat flux and consistent nodal surface loads. Obtain total derivatives of any scalar output with respect to Mach number, angle of attack, sideslip, Reynolds number, turbulence-model data and the position of every surface node, by adjoint or direct method. Couple to another discipline through surface displacements (in) and loads (out), with the forward and reverse products a coupled adjoint needs. Estimate the discretization error of an output with one extra linear solve, and let the solver raise the polynomial degree until that estimate meets a tolerance. Run all of this in-process (Python), from the command line, over HTTP from another machine, or from an AI agent through the Model Context Protocol.\ngyre run naca0012_euler --gradients --of cl,cd --wrt alpha,mach --error cd solves inviscid flow over a NACA 0012 at Mach 0.5 and 2°, and prints the force coefficients, their adjoint derivatives with respect to angle of attack and Mach number, and an adjoint-based estimate of how much of the (physically zero) drag is discretization error. The worked example shows the numbers. Mach number, NACA 0012, M = 0.5, α = 2° Part of the domain: window x ∈ [-1, 2.5], y ∈ [-1.5, 1.5] · the 354 mesh elements that reach into it · 8×8 points each\nYou are… You interact with… Start at integrating Gyre into an MDO framework FlowDiscipline / RemoteDiscipline , OpenMDAO adapter MDO integration running analyses CLI, REST jobs Getting started an AI agent or building one MCP tools Agent interface operating a deployment tools/deploy.py , container, metrics Deployment extending the solver gyre.dg , gyre.physics , gyre.implicit Code structure assessing the numerics verification studies Verification\nOne execution path. CLI, service workers, sessions, pipelines and agent tools all call the same run_case / FlowDiscipline code. There is no second implementation to diverge. Geometry is data. Node coordinates are an argument of the differentiated function, not configuration, so shape derivatives need no dedicated code. Matrix-free everywhere. Jacobians are applied, never stored; preconditioner blocks come from AD as well. Evidence travels with the code. Verification numbers shown in these pages are files produced by gyre verify and stamped with commit, device and time. Honest scope. Limits are stated where they apply ( capabilities and limits ).","title":"What Gyre is","url":"overview/what-is-gyre.html"},{"audience":[],"chunks":[{"anchor":"","end":395,"heading":"","level":1,"start":0},{"anchor":"from-request-to-result","end":1594,"heading":"From request to result","level":2,"start":396},{"anchor":"process-model-of-the-service","end":2436,"heading":"Process model of the service","level":3,"start":1595},{"anchor":"deployments-carry-their-own-documentation","end":2791,"heading":"Deployments carry their own documentation","level":3,"start":2437}],"headings":["From request to result","Process model of the service","Deployments carry their own documentation"],"keywords":["architecture","service","discipline","layers"],"level":1,"max_level":3,"path":"overview/architecture.md","section":"overview","section_title":"Overview","summary":"A case specification flows through one pipeline — mesh, discretization, discipline — that every interface shares. The service wraps that pipeline in isolated worker processes and exposes it to programs, people and agents.","text":"Gyre has three layers. The numerical core turns a state and a geometry into residuals and outputs, differentiably. The discipline adds solvers and exposes the contract an MDO framework expects. The service makes the discipline reachable — by HTTP, by pipeline, by agent — and serves the documentation you are reading. Every interface reaches the numerical core through the same discipline layer.\nA case specification ( CaseSpec : mesh generator or mesh file, flow conditions, discretization, solver controls, reference quantities) arrives as a preset name with overrides or as a full document. Its canonical JSON is hashed (for a mesh file, with the file’s contents in place of its name); the digest identifies the analysis in job, session and result records, and the service’s result cache is keyed by that JSON together with the requested gradients, error estimates and order selection, the contents of a mesh file and a fingerprint of the code. Case builds the curved mesh, or reads it from a file, the Discretization (operators, index tables, boundary conditions) and a FlowDiscipline . The discipline converges the flow with the Newton–Krylov solver, evaluates outputs, and on request solves adjoint or direct linear systems for derivatives and error estimates. With an order selection ( adapt ) this step is repeated at rising polynomial degree until the estimated error of one output meets a tolerance. The runner collects everything into one self-describing result record: outputs, convergence history, gradients, surface distributions, timings and provenance (version, commit, device).\nSolver work is isolated in subprocesses; the API process stays responsive and device-free. Jobs are one-shot analyses. Each runs in a fresh subprocess, so a crash or an out-of-memory condition cannot take the service down, the device is chosen per job, and cancelling is a process termination. Progress is appended to an event log and streamed as Server-Sent Events. Sessions are long-lived solver processes for design loops: kernels stay compiled, every solve is warm-started from the last state, and derivative products are available between evaluations. The store is plain files — inspectable, restart-safe, and the source of truth for the result cache. Mesh files that clients upload are kept in the same data directory, in the mesh store ; a case refers to them by name, and no request makes the service open a path of its own choosing.\ntools/deploy.py up refuses a port that is already served, builds this site from the working tree, stamps it with the commit, starts the service on all interfaces, and smoke-tests docs, API, a real solver job (which bypasses the result cache) and the MCP endpoint through the tailnet address. The footer of every page shows which deployment is serving it.","title":"Architecture at a glance","url":"overview/architecture.html"},{"audience":[],"chunks":[{"anchor":"","end":171,"heading":"","level":1,"start":0},{"anchor":"implemented-and-verified","end":8290,"heading":"Implemented and verified","level":1,"start":172},{"anchor":"implemented-less-exercised","end":8291,"heading":"Implemented, less exercised","level":1,"start":8291},{"anchor":"use-with-awareness","end":11776,"heading":"Use with awareness","level":2,"start":8292},{"anchor":"not-included","end":13046,"heading":"Not included","level":1,"start":11777},{"anchor":"numerical-characteristics-to-plan-around","end":13976,"heading":"Numerical characteristics to plan around","level":3,"start":13047}],"headings":["Implemented and verified","Implemented, less exercised","Use with awareness","Not included","Numerical characteristics to plan around"],"keywords":["scope","limitations","roadmap","verified"],"level":1,"max_level":3,"path":"overview/capabilities-and-limits.md","section":"overview","section_title":"Overview","summary":"What Gyre does, how well each part is verified, and what it does not do yet. Read this before planning work on top of it.","text":"A foundation is only useful if its edges are known. This page separates what is implemented and verified from what is implemented but lightly exercised and what is absent.\nCapability Evidence Split-form DGSEM, 2-D and 3-D, curved elements discrete invariants : SBP, free-stream preservation, conservation, entropy conservation to round-off Design-order accuracy (Euler) order of accuracy : density wave, isentropic vortex, manufactured solution Transonic flow with differentiable shock capturing transonic example : converged to round-off, adjoint versus finite differences 3-D flow solves on unstructured hexahedral meshes, with all six force and moment coefficients, sideslip and their adjoint derivatives three-dimensional flows : inviscid sphere against exact properties (no drag, no entropy rise, Janzen–Rayleigh surface pressure) through all eight face orientations, laminar sphere at Re = 100 against accepted drag and wake length, adjoint versus finite differences on an ellipsoid at incidence and sideslip, an extruded case that reproduces the 2-D solution Force, moment and heat-flux coefficients of selected walls and per named component, each with its adjoint and error estimate unit tests: the coefficients of components that partition the walls add up to the total; a component’s derivative equals that of a case restricted to the same walls, which is compared with finite differences of re-converged flows Laminar Navier–Stokes with BR2 symmetric, compact operator; manufactured solutions by regime; cylinder at Re = 40 RANS with the negative Spalart–Allmaras model, wall distance included in shape derivatives turbulence closure : manufactured solution of the coupled system, exact linearisation on both branches of the model, flat-plate skin friction against the NASA Turbulence Modeling Resource, adjoint versus finite differences Entropy-stable interface fluxes ( es_roe , llf ) fluxes : no entropy production for any of the random state pairs tested, where the classical Roe flux produces some; unit tests of the inequality, of the Roe limit and of a stationary contact Newton–Krylov with p-multigrid solvers : residual reduction of ten orders, terminal contraction per step at the forcing rate of the inexact Newton iteration, Krylov iteration count versus block Jacobi; line smoothing against block Jacobi on a wall-resolved turbulent plate and on a sphere in three dimensions (same drag and adjoint derivatives, fewer than half the Krylov iterations in tight linear solves) Exact linearisation (JVP, VJP, blocks) derivatives Adjoint and direct total derivatives: flow parameters and shape adjoint versus finite differences versus direct Coupling products: loads, displacements, forward/reverse totals coupled aero-structural adjoint test; OpenMDAO adapter test Residual-level discipline contract (apply, linearise and solve about the caller’s own point; checkpoints; a stalled linear solve is an error) discipline contract : transposes to round-off, inverses to the tolerance, finite differences, OpenMDAO’s own derivative checks of the implicit component Output error estimation output error : effectivity against the converged p+1 solution External meshes: curved quadrilaterals and hexahedra from Gmsh or NPZ files imported meshes : node ordering against genuine Gmsh files, free stream, conservation and neighbour coupling on unstructured meshes, a solve and an adjoint derivative on an imported mesh Output-driven order selection order selection : the lowest degree whose estimated output error meets the tolerance is selected; an unreachable tolerance is reported as not met Service, sessions, MCP, pipelines end-to-end tests with real worker processes 39 of 39 studies passed Passed Summation-by-parts operators operators 1/1 checks · 0.29 s 39979fcf90 Same code as this build Passed Free-stream preservation on curved meshes free_stream 1/1 checks · 18.67 s 39979fcf90 Same code as this build Passed Entropy conservation and stability entropy_conservation 41/41 checks · 15.77 s 39979fcf90 Same code as this build Passed Structure of the BR2 viscous operator viscous_operator 17/17 checks · 116.9 s 39979fcf90 Same code as this build Passed Independent 1-D model of the BR2 formulation diffusion_1d 17/17 checks · 4.93 s 39979fcf90 Same code as this build Passed Exact linearization by automatic differentiation linearization 40/40 checks · 198.1 s 39979fcf90 Same code as this build Passed Differentiable mesh deformation mesh_deformation 9/9 checks · 2.45 s 39979fcf90 Same code as this build Passed Advected density wave density_wave 4/4 checks · 40.98 s 39979fcf90 Same code as this build Passed Isentropic vortex convection isentropic_vortex 10/10 checks · 65.18 s 39979fcf90 Same code as this build Passed Manufactured solution: Euler mms_euler 6/6 checks · 212.5 s 39979fcf90 Same code as this build Passed Manufactured solutions: Navier–Stokes mms_navier_stokes 14/14 checks · 1225 s 39979fcf90 Same code as this build Passed Nonlinear and linear solver convergence solver_convergence 16/16 checks · 890.1 s 39979fcf90 Same code as this build Passed Line-implicit smoothing on a wall-resolved mesh line_smoother 7/7 checks · 2160 s 39979fcf90 Same code as this build Passed Line-implicit smoothing in three dimensions line_smoother_3d 6/6 checks · 620.4 s 39979fcf90 Same code as this build Passed Single-precision blocks in the preconditioner mixed_precision 6/6 checks · 2649 s 39979fcf90 Same code as this build Passed Adjoint gradients versus finite differences adjoint_gradients 18/18 checks · 1176 s 39979fcf90 Same code as this build Passed Output error estimation output_error 5/5 checks · 367.7 s 39979fcf90 Same code as this build Passed Laminar cylinder at Re = 40 cylinder_re40 7/7 checks · 417.2 s 39979fcf90 Same code as this build Passed Three-dimensional consistency extruded_3d 8/8 checks · 300.8 s 39979fcf90 Same code as this build Passed Inviscid flow past a sphere (3-D) sphere_inviscid 9/9 checks · 424.7 s 39979fcf90 Same code as this build Passed Laminar flow past a sphere, Re = 100 (3-D) sphere_re100 5/5 checks · 187.8 s 39979fcf90 Same code as this build Passed Adjoint derivatives of 3-D outputs adjoint_3d 6/6 checks · 297.7 s 39979fcf90 Same code as this build Passed Largest 3-D solve on one device scale_3d 7/7 checks · 452 s 39979fcf90 Same code as this build Passed Manufactured solutions: RANS with the Spalart-Allmaras model rans_mms 9/9 checks · 455.8 s 39979fcf90 Same code as this build Passed Exact linearization with the Spalart-Allmaras model rans_linearization 20/20 checks · 158.6 s 39979fcf90 Same code as this build Passed Turbulent flat plate (NASA TMR) with the Spalart-Allmaras model rans_flat_plate 8/8 checks · 162.1 s 39979fcf90 Same code as this build Passed Three-dimensional consistency with the Spalart-Allmaras model rans_extruded_3d 10/10 checks · 675.1 s 39979fcf90 Same code as this build Passed Adjoint gradients with the Spalart-Allmaras model rans_adjoint 6/6 checks · 119 s 39979fcf90 Same code as this build Passed Discipline contract for MDO integration discipline_contract 15/15 checks · 249.9 s 39979fcf90 Same code as this build Passed Imported meshes imported_mesh 29/29 checks · 184.5 s 39979fcf90 Same code as this build Passed Output-driven order selection order_selection 13/13 checks · 364.4 s 39979fcf90 Same code as this build Passed Example: inviscid NACA 0012 example_airfoil 4/4 checks · 104.5 s 39979fcf90 Same code as this build Passed Example: laminar cylinder, Re = 40 example_cylinder 4/4 checks · 51.5 s 39979fcf90 Same code as this build Passed Example: turbulent flat plate (Spalart-Allmaras) example_flat_plate 5/5 checks · 119.9 s 39979fcf90 Same code as this build Passed Example: lift polar with adjoint slopes example_polar 5/5 checks · 686.1 s 39979fcf90 Same code as this build Passed Example: coupled aero-structural adjoint example_coupling 2/2 checks · 823 s 39979fcf90 Same code as this build Passed Example: transonic aerofoil with shock capturing example_transonic 9/9 checks · 151.8 s 39979fcf90 Same code as this build Passed Kernel throughput (cpu) performance_cpu 2/2 checks · 123.8 s 39979fcf90 Same code as this build Passed Kernel throughput (NVIDIA GeForce RTX 3060 Ti) performance_gpu 2/2 checks · 103.5 s 39979fcf90 Same code as this build\n\nFlows with shocks. A sensor-based artificial viscosity ( shock capturing ) is part of the differentiated residual and is verified on a transonic aerofoil, including adjoint derivatives through the shock ( example ). It has been exercised at p = 2 on that one configuration with the default roe interface flux; strong shocks, supersonic free streams, higher degrees and the entropy-stable interface fluxes in combination with it are untested or unconverged, and positivity is not enforced. 3-D flows. Verified on a sphere and an ellipsoid (inviscid and laminar, degrees 1 to 4) and on a spanwise extrusion; the only 3-D turbulent case is an extruded flat plate, and there is no wing and no lifting 3-D configuration in the suite. Built-in 3-D geometry is the cubed-sphere shell ( sphere_shell : spheres and ellipsoids), boxes and spanwise extrusions; anything else is read from a mesh file of hexahedra. Problem size is limited by the memory of one device: the block inverses hold n_\\text{var}^2 (p+1)^{2d} numbers per element, in single precision if solver.precond_precision: single is set. (The mesh-deformation operator behind shape derivatives is applied matrix-free at 3-D sizes and is not what limits them.) The largest solve recorded on the development machine, with the derivative of the drag with respect to every surface node, is stated with its memory and times. Turbulent flows. One model (negative Spalart–Allmaras), integrated to the wall, verified on a manufactured solution and on the zero-pressure-gradient flat plate, in two dimensions and on a spanwise extrusion that must reproduce them ( three dimensions ). Separated flow, aerofoils at flight Reynolds number, turbulent flows that are three-dimensional in themselves, transition, rotation and compressibility corrections, wall functions and other models are untested or absent ( what is included ). The line smoother for wall-resolved meshes ( solver.smoother: line ) is measured on that flat plate and on a laminar sphere with thin wall layers in three dimensions, both small: it cuts the Krylov iterations of tight linear solves to less than half and does not make the flow solve itself faster ( record ). External meshes. A case can take its mesh from a file ( mesh.generator: file ): Gmsh MSH 2.2 or 4.1 in ASCII, with complete Lagrange quadrilaterals of degree 1 to 5 or hexahedra of degree 1 to 4, or Gyre’s own NPZ arrays at any geometry degree. Boundary groups are the physical groups of the file, a boundary face without one is refused, and the case must give a boundary condition for every group. Periodic node pairs of the file become periodic topology. Left-handed elements are re-oriented; a mesh with an element whose Jacobian determinant is not positive at every geometry node is refused, not repaired. Flow solves on imported meshes are verified in 2-D. Order selection. Uniform p-enrichment on a fixed mesh, driven by the error estimate of one built-in scalar output, from the case’s degree (at least 1) up to at most degree 8 (default limit 5). It inherits the limits of the estimate: the error is measured against the next degree, not against the exact solution. Internal-flow boundary conditions ( total_inlet , pressure_outlet ): used by the bump channel; simple pressure-extrapolation formulation. Sutherland viscosity : implemented, not part of the verification suite. GPU execution : used for the larger verification studies; small problems are faster on CPU because compilation dominates.\nWarning Out of scope in this release Scale-resolving turbulence simulation. No LES or hybrid model, no wall functions, no transition model; the only closure is the Spalart–Allmaras RANS model. Unsteady analysis through the service. An explicit Runge–Kutta integrator exists for verification; time-accurate jobs, unsteady adjoints and implicit time stepping are not exposed. Element shapes other than quadrilaterals and hexahedra. Mesh files with triangles, tetrahedra, prisms, pyramids or serendipity elements are refused. So are binary MSH files and MSH versions other than 2.2 and 4.1, and meshes with hanging nodes (every interior face must be shared by exactly two elements). Mesh adaptation. The mesh of a case is fixed. The per-element error indicators are computed but not acted upon; order selection raises the polynomial degree uniformly on that mesh. Optimization. Gyre is the analysis discipline. Order selection controls the discretization error of one analysis; it does not change inputs or search a design space. Distributed-memory parallelism. One process, one device (multi-threaded CPU or one GPU). Geometry engine and parameterisation. Shape enters as surface-node displacements; mapping design variables to those displacements is the framework’s job.\nGauss–Lobatto collocation trades some accuracy per degree of freedom for diagonal mass matrices and entropy-conservative volume terms: the design rate p+1 is checked for the advected density wave and for steady manufactured solutions on Cartesian meshes, while for the nonlinear unsteady vortex only the guaranteed rate p is required and the observed rate is reported. In diffusion-dominated regimes only the rate p is required of the state; the observed orders per variable are reported ( details ). Elements are isoparametric, so at p = 1 curved walls are polygons and generate spurious entropy at their kinks; use p \\ge 2 on curved geometry ( evidence ). Sharp trailing edges are geometric singularities: output convergence rates there are limited by the singularity, not by p . First use of each mesh-size/degree combination costs tens of seconds of JIT compilation; sessions and the persistent compilation cache amortise it.","title":"Capabilities and limits","url":"overview/capabilities-and-limits.html"},{"audience":[],"chunks":[{"anchor":"","end":2979,"heading":"","level":2,"start":0}],"headings":[],"keywords":["guide"],"level":2,"max_level":2,"path":"guide/index.md","section":"guide","section_title":"User Guide","summary":"How to install Gyre, describe a case, bring your own mesh, run it from the command line, the service or Python, obtain gradients and error estimates, wire it into an MDO framework or an agent, and deploy it.","text":"Task-oriented pages. Each starts with the shortest path to a result and unfolds options, behaviour and rationale as you raise the depth dial. Getting started L1 Install Gyre, run a preset from the command line, read the result, and start the service. The first solve of the first preset takes about a minute on a four-core CPU, most of it compilation. Cases L2 A case specification is one document describing mesh, flow conditions, discretization, solver controls and reference quantities. Presets cover common configurations; dotted-path overrides adapt them; the same document works in the CLI, the API, pipelines and agent tools. Mesh files L2 A case can take its mesh from a file instead of a built-in generator: Gmsh MSH 2.2 or 4.1 (ASCII) with quadrilaterals or hexahedra, or Gyre's own NPZ arrays. This page shows how to produce such a file, how its boundary groups become boundary conditions, what the reader checks, and how a mesh reaches a service. Service API L2 The REST API offers two ways to compute — batch jobs for one-off analyses and warm sessions for design loops — plus a mesh store for uploaded mesh files, case validation, progress streams, artefacts and metrics. Responses are JSON, except /metrics (Prometheus text), artefact downloads and the ?stream=true event stream; every error carries a type, a message and a hint. Gradients and error estimates L2 Request total derivatives of any scalar output with respect to flow parameters and surface shape from the CLI, a job, a session or Python. One adjoint solve per output; an adjoint-based error estimate costs one more linear solve. Integrating with an MDO framework L2 Gyre presents itself as an implicit discipline — inputs, a state, a residual, outputs — with exact matrix-free derivative products. This page maps that contract onto the concepts MDO frameworks use and shows the three ways to connect. Agent interface (MCP) L2 Gyre exposes its capabilities to AI agents through the Model Context Protocol. An agent can discover cases, upload meshes, run analyses, let the solver choose the polynomial degree for a target accuracy, obtain exact gradients and error estimates, and read this documentation — through tools designed to be self-correcting. Pipelines L2 Chain analyses into reproducible workflows with a small YAML vocabulary. Stages are cached by content, so re-running a pipeline recomputes only what changed. Deployment L2 A deployment is one process tree serving the API, the agent endpoint and the documentation for its own revision, bound to all interfaces so that tailnet machines can reach it. Deploy natively, as a container, or under systemd. Python API L2 Use Gyre as a library — build a case, drive the discipline, access the discretization and its differentiable kernels directly. Troubleshooting L1 What to do when a solve does not converge — every reason the solver can report, what it means and which control to change — and the first-run and service symptoms most often met.","title":"User Guide","url":"guide/index.html"},{"audience":["CFD practitioners","MDO integrators"],"chunks":[{"anchor":"install","end":410,"heading":"Install","level":1,"start":0},{"anchor":"requirements","end":807,"heading":"Requirements","level":2,"start":411},{"anchor":"run-a-case","end":2311,"heading":"Run a case","level":1,"start":808},{"anchor":"what-was-written","end":2578,"heading":"What was written","level":2,"start":2312},{"anchor":"change-the-case","end":2918,"heading":"Change the case","level":2,"start":2579},{"anchor":"gradients-and-error-estimates","end":3475,"heading":"Gradients and error estimates","level":1,"start":2919},{"anchor":"let-the-solver-choose-the-degree","end":4900,"heading":"Let the solver choose the degree","level":2,"start":3476},{"anchor":"your-own-mesh","end":5198,"heading":"Your own mesh","level":1,"start":4901},{"anchor":"start-the-service","end":5561,"heading":"Start the service","level":1,"start":5199},{"anchor":"where-next","end":5995,"heading":"Where next","level":2,"start":5562},{"anchor":"choosing-the-device","end":6326,"heading":"Choosing the device","level":3,"start":5996}],"headings":["Install","Requirements","Run a case","What was written","Change the case","Gradients and error estimates","Let the solver choose the degree","Your own mesh","Start the service","Where next","Choosing the device"],"keywords":["install","quickstart","CLI","first run"],"level":1,"max_level":3,"path":"guide/getting-started.md","section":"guide","section_title":"User Guide","summary":"Install Gyre, run a preset from the command line, read the result, and start the service. The first solve of the first preset takes about a minute on a four-core CPU, most of it compilation.","text":"git clone <repository> gyre && cd gyre make bootstrap # creates .venv with uv The commands on this page call the program by its path, .venv/bin/gyre . After source .venv/bin/activate plain gyre works too. scripts/bootstrap.sh detects two host properties and adapts: CPUs without AVX2 get jax < 0.12 (newer wheels abort at import on such processors), and hosts with an NVIDIA GPU get the CUDA-enabled JAX build.\nPython 3.11 or newer, a C toolchain is not needed. Dependencies: JAX, NumPy, SciPy, pydantic, PyYAML; for the service FastAPI, uvicorn, httpx, sse-starlette and the MCP SDK (2.0 or newer); for the documentation Jinja2, Markdown and Pygments; for the OpenMDAO adapter OpenMDAO (extra mdo ). Everything is declared in pyproject.toml ( pip install -e \".[all]\" works too; all includes the mdo extra).\n.venv/bin/gyre presets .venv/bin/gyre run naca0012_euler -o runs/first case naca0012_euler [e418…]: 256 elements, p = 2, 9216 DOF, levels [2, 1, 0] step 0 |R| = 3.663e-01 CFL = 5.0e+00 krylov = 2 … step 15 |R| = 4.569e-11 CFL = 4.6e+07 krylov = 14 { \"converged\": true, \"outputs\": {\"cd\": 0.000949, \"cl\": 0.278767, \"cm\": -0.002021, …} } The first run of a new mesh size or polynomial degree compiles its kernels, and nothing is printed while that happens : after the case … line the program is silent until step 0 . A persistent cache ( ~/.cache/gyre/jit ) shortens later runs of the same configuration. gyre run naca0012_euler -o DIR Wall time of which timing.solve first run, empty compilation cache 51 s 40 s second run, cache filled 27 s 20 s a later run on the same machine under heavy load (load average 32 to 39 on four cores), cache state not controlled 386 s 351 s Measured once each on a four-core Intel i7-3770K (no AVX2, 16 GB), CPU device, with the working tree that followed revision 5ee1cdc . The load of the machine was not recorded for the first run; the second ran while the machine was shared with other work (load average about 4). The third row shows how much the time depends on what else the machine is doing: the same command took more than six minutes while about nine times as many processes as cores were runnable. Read the first two rows as what a mostly free machine of this class gives, not as a guarantee. timing.solve includes the compilation that happens inside the solve.\nFile Content result.json spec, outputs, convergence history, surface c_p , timings, provenance solution.npz checkpoint of the state with the spec that produced it flow.vtu sub-sampled solution for ParaView/VisIt field.json compact field sample for the browser viewer\nOverrides are dotted paths into the case specification: .venv/bin/gyre run naca0012_euler --set flow.mach = 0 .6 --set flow.alpha_deg = 1 .5 \\ --set discretization.order = 3 --set mesh.params.n_surf = 48 .venv/bin/gyre run my_case.yaml # or a full specification file .venv/bin/gyre schema # every field, with types and defaults See Cases .\n.venv/bin/gyre run naca0012_euler --gradients --of cl,cd --wrt alpha,mach --error cd adds total derivatives from the discrete adjoint and an adjoint-based estimate of the discretization error of cd ( details ). alpha is the angle of attack in radians , so --wrt alpha gives derivatives per radian. --wrt alpha_deg gives the same derivative per degree (smaller by the factor π/180 ≈ 1/57.3) under the name alpha_deg . The case field flow.alpha_deg is always in degrees. What cl , cd , cm and the other outputs mean is defined under Outputs and functionals .\n.venv/bin/gyre run bump_euler --set discretization.order = 1 --target-error cd = 1e-4 --max-order 4 --target-error OUTPUT=TOL raises the polynomial degree, starting at the case’s order, until the estimated error of OUTPUT is at most TOL (absolute) or --max-order (default 5) is reached. One line is printed per degree tried ( -q suppresses them): order P: N DOF, OUTPUT = value, estimated error E (target TOL) order P: N DOF, did not converge (reason) The second form is the line of a degree whose solve did not converge; the selection ends there, with the last degree that converged as its result. The tolerance counts as met only by a finite estimate whose adjoint solve converged. The fields of adaptation and of its level records are described under Service . The JSON summary gains adaptation ( output , tolerance , met , order , value , corrected , error , and per degree order , degrees_of_freedom , converged , error ); outputs, gradients and artefacts are those of the final degree, and result.json (with -o ) has the complete level records. The exit status is 0 if the tolerance was met, 3 if the solve converged but the tolerance was not met at --max-order , 2 if the solve did not converge. A request that cannot work (a tolerance that is not a positive number, --max-order without --target-error , below 1 or below the case’s order, an unknown output) ends with a message and status 1 before any flow is solved.\n.venv/bin/gyre mesh info my_mesh.msh # is the file usable? which boundary groups? .venv/bin/gyre run my_case.yaml # mesh: {generator: file, params: {path: my_mesh.msh}} A case can read a Gmsh mesh of quadrilaterals or hexahedra instead of using a generator; see Mesh files and the worked example .\nmake deploy # builds the docs for this revision, starts the service, smoke-tests it # or, without the deployment record: .venv/bin/gyre serve --host 0 .0.0.0 --port 8600 Then, from any machine on the tailnet: documentation http://HOST:8600/ API explorer http://HOST:8600/api/docs deployment identity http://HOST:8600/api/v1/info agents (MCP) http://HOST:8600/mcp\nTroubleshooting — when a solve does not converge, and first-run problems. Mesh files — geometries beyond the built-in generators. examples/quickstart.py — the same first run from Python, with gradients and an error estimate ( .venv/bin/python examples/quickstart.py ). Service API — jobs and warm sessions over HTTP. MDO integration — the discipline contract. Agent interface — driving Gyre from an AI agent. Examples — worked cases.\nGYRE_DEVICE=cpu|gpu|auto selects the device for a process (default auto : GPU if JAX finds one). For problems below a few tens of thousands of degrees of freedom the CPU is usually faster end-to-end because compilation dominates; the service makes this choice per job. GYRE_JIT_CACHE=off disables the persistent compilation cache.","title":"Getting started","url":"guide/getting-started.html"},{"audience":[],"chunks":[{"anchor":"presets","end":1652,"heading":"Presets","level":2,"start":0},{"anchor":"anatomy-of-a-specification","end":2452,"heading":"Anatomy of a specification","level":2,"start":1653},{"anchor":"overrides","end":2993,"heading":"Overrides","level":2,"start":2453},{"anchor":"mesh-generators","end":8487,"heading":"Mesh generators","level":2,"start":2994},{"anchor":"boundary-conditions","end":10188,"heading":"Boundary conditions","level":3,"start":8488},{"anchor":"flow-model","end":10967,"heading":"Flow model","level":2,"start":10189},{"anchor":"reference-quantities","end":12558,"heading":"Reference quantities","level":2,"start":10968},{"anchor":"discretization-choices","end":13275,"heading":"Discretization choices","level":2,"start":12559},{"anchor":"solver-controls","end":15098,"heading":"Solver controls","level":3,"start":13276},{"anchor":"size-before-you-run","end":15585,"heading":"Size before you run","level":3,"start":15099},{"anchor":"full-schema","end":15586,"heading":"Full schema","level":2,"start":15586},{"anchor":"every-field","end":20547,"heading":"Every field","level":3,"start":15587}],"headings":["Presets","Anatomy of a specification","Overrides","Mesh generators","Boundary conditions","Flow model","Reference quantities","Discretization choices","Solver controls","Size before you run","Full schema","Every field"],"keywords":["CaseSpec","presets","overrides","mesh generators","mesh files","boundary conditions","reference quantities","solver options"],"level":2,"max_level":3,"path":"guide/cases.md","section":"guide","section_title":"User Guide","summary":"A case specification is one document describing mesh, flow conditions, discretization, solver controls and reference quantities. Presets cover common configurations; dotted-path overrides adapt them; the same document works in the CLI, the API, pipelines and agent tools.","text":"preset description elements DOF model naca0012_euler Inviscid subsonic flow over a NACA 0012 aerofoil (M=0.5, alpha=2 deg). 256 9,216 Euler, 2-D naca0012_laminar Laminar flow over a NACA 0012 aerofoil (M=0.5, alpha=1 deg, Re=5000). 480 17,280 Navier–Stokes, 2-D naca2412_euler Inviscid flow over a cambered NACA 2412 aerofoil (M=0.4, alpha=1 deg). 256 9,216 Euler, 2-D naca0012_transonic Transonic inviscid flow over a NACA 0012 aerofoil (M=0.8, alpha=1.25 deg) with sensor-based shock capturing. 1024 36,864 Euler, 2-D naca0012_euler_3d Spanwise-periodic 3-D extrusion of the inviscid NACA 0012 case (hexahedra): exercises the 3-D operators, solver and adjoint; coefficients per unit span. 192 25,920 Euler, 3-D cylinder_re40 Steady separated laminar flow past a circular cylinder (Re_D=40, M=0.2). 240 15,360 Navier–Stokes, 2-D sphere_euler Inviscid subsonic flow past a sphere (M=0.2) on a cubed-sphere mesh of curved hexahedra: a genuinely 3-D, unstructured-topology case whose exact solution has no drag and no entropy rise. 216 29,160 Euler, 3-D sphere_re100 Steady laminar flow past a sphere (Re_D=100, M=0.2): 3-D Navier-Stokes with a closed recirculation bubble; drag referred to the frontal area. 576 77,760 Navier–Stokes, 3-D flat_plate_sa Turbulent flat plate at zero pressure gradient (M=0.2, Re=5e6 per unit length) with the Spalart-Allmaras model: the NASA Turbulence Modeling Resource verification case on a coarse wall-resolved mesh. 150 6,750 Navier–Stokes, 2-D bump_euler Inviscid subsonic channel flow over a smooth Gaussian bump (M=0.5): the exact solution is isentropic, so the entropy error measures accuracy. 48 3,072 Euler, 2-D\nname : my_aerofoil mesh : generator : airfoil_omesh geometry_order : 4 params : { code : \"2412\" , n_surf : 48 , n_radial : 12 , r_far : 50.0 , first_cell : 0.02 } flow : mach : 0.4 alpha_deg : 1.0 reynolds : null # null = inviscid; a number switches on Navier–Stokes discretization : order : 3 # polynomial degree p (accuracy p + 1); viscous cases need p >= 1 surface_flux : roe solver : levels : auto # p-multigrid: p, p//2, …, 0 tol_rel : 1.0e-10 reference : # optional: normalisation of the force and moment coefficients moment_center : [ 0.25 , 0.0 ] The mesh comes from one of the built-in generators or, with generator: file , from a mesh file . Note order: 0 together with a Reynolds number is rejected: for viscous flow p = 0 remains a p-multigrid level but is not a solution degree ( why ).\nAny field can be overridden by its dotted path — on the command line ( --set flow.mach=0.6 ), in a job request ( \"overrides\": {\"flow.mach\": 0.6} ) or in a pipeline stage. Unknown paths are rejected (by the service with a hint); values are validated against the schema. A --set value is read as JSON (numbers, true / false , null , lists); anything else is text. A number or true / false given for a field that holds text stays text, so --set mesh.params.code=2412 is the string \"2412\" ; a quoted JSON string ( '\"2412\"' ) is always a string.\ngenerator parameters (defaults) boundary groups airfoil_omesh code (“0012”) or coordinates (list of [x, y] in Selig order, unit chord), n_surf (48, even), n_radial (16), r_far (30), first_cell (0.02), wall_blend (0.6), nz (0), span (1) wall , farfield annulus n_theta (required), n_r (required), r_inner (0.5), r_outer (20), ratio (1.25), nz (0), span (1) wall , farfield bump_channel nx (required), ny (required), height (0.0625), sigma (25), x_lim ( [-1.5, 1.5] ), y_top (0.8), beta_x (0), ratio_y (1) inflow , outflow , wall , top flat_plate nx (12), ny (10), h_wall (5e-5), nx_up (3), dx_le (0.02), length (2), upstream (0.33), height (1), nz (0), span (1) inflow , outflow , wall , top , symmetry sphere_shell n (4), n_r (4), r_inner (0.5), r_outer (10), ratio (1.4), axes ( [1, 1, 1] ), orientation (“mixed”), bc (none) wall , farfield box nel (required), lo (zeros), hi (ones), periodic ( false ), warp (0), bc (none) xmin … zmax file path (a Gmsh .msh or Gyre .npz file), scale (1) the physical groups of the file file reads an external mesh; mesh.geometry_order then has no effect, because the file carries its own degree. Formats, the Gmsh recipe, the checks and the service’s mesh store are described under Mesh files . coordinates replaces the NACA section by a spline fit of arbitrary aerofoil data (trailing edge → upper surface → leading edge → lower surface → trailing edge). nz > 0 extrudes an aerofoil, cylinder or flat-plate section into a spanwise-periodic 3-D mesh of hexahedra (at least three elements across the span). first_cell is the height of the first cell at the wall in chords, measured along the grid line; wall_blend scales the tangent with which the grid lines leave the wall, in units of the line length. An extruded mesh has the boundary groups of its section and no boundary across the span, and its force coefficients are per unit span (the default reference.area is span ). With nz and span the turbulent preset flat_plate_sa becomes a 3-D case with six conserved variables; the wall distance of the turbulence model is that of the section in every spanwise plane. An extruded case with viscous terms or with shock capturing is not the same discrete problem as its section, though. The default penalty of the viscous scheme, which the artificial viscosity of shock capturing uses as well, is the number of faces of an element, 6 for hexahedra and 4 for quadrilaterals ( penalty parameter ), so the two solutions differ as two discretizations of one flow do, not by round-off. And at any penalty the scheme leaves a viscous extruded solution with a small spanwise velocity that changes sign across the mid-plane of every element. It is driven by the jumps of the in-plane velocity between elements: inside an element the viscous stress is built from the gradient lifted from all faces, on a spanwise face from the local gradient alone. What the parameters without a description above mean: generator parameter meaning annulus n_theta , n_r elements around the circle and in the radial direction (positive integers) r_inner , r_outer radii of the wall circle and of the far-field circle ratio geometric growth of the radial element size from the wall outwards nz , span 0 for a 2-D section, or at least 3 elements across a spanwise-periodic extrusion of width span bump_channel nx , ny elements along and across the channel height , sigma the lower wall is y = \\texttt{height}\\cdot e^{-\\texttt{sigma}\\,x^2} x_lim , y_top the channel extends over x_lim (an increasing pair) and up to y_top beta_x strength of the tanh clustering of the elements in x (0: uniform) ratio_y geometric growth of the element height from the lower wall (1: uniform) flat_plate nx , nx_up , ny elements along the plate, upstream of its leading edge, and across the domain h_wall , dx_le height of the first element at the wall and length of the first element at the leading edge; both grow geometrically away from there length , upstream , height the plate occupies 0 \\le x \\le length of the lower boundary; the domain starts upstream ahead of it (group symmetry ) and is height high nz , span 0 for the 2-D section, or at least 3 elements across a spanwise-periodic extrusion of width span sphere_shell n , n_r each of the six blocks of the cubed sphere has n × n × n_r hexahedra ( 6 n^2 n_r in total) r_inner , r_outer radius of the body and of the spherical far-field boundary ratio geometric growth of the radial element size from the wall outwards axes semi-axes of the body relative to r_inner : [1, 1, 1] is a sphere, anything else an ellipsoid orientation mixed rotates the local axes of the blocks against each other (all eight relative face orientations occur); aligned keeps the radial direction as the third local axis bc renames the two groups, e.g. {wall: body} box nel elements per direction: a list of 2 or 3 positive integers, which also sets the dimension lo , hi opposite corners, one number per direction periodic true / false , or one per direction warp amplitude, as a fraction of the box size, of a smooth interior distortion that leaves the boundary planes in place (curvilinear verification meshes) bc mapping from block sides to boundary-group names, to rename the default groups xmin … zmax params are validated against the generator: an unknown, missing or mistyped parameter is a validation error (422 from the service). pg is not a parameter (set mesh.geometry_order ), code must be a quoted 4-digit string, nz is 0 or at least 3, and span is positive.\nDefaults follow the generator and the flow model: walls are slip_wall for inviscid and noslip_adiabatic for viscous cases; outer boundaries are farfield ; the bump channel and the flat plate use total_inlet / pressure_outlet (the plate also symmetry ahead of its leading edge and farfield on top). A box and a mesh file have no defaults: give a condition for every non-periodic side of the box, and for every boundary group of the file. A missing one is reported when the case is built, not by validation of the specification alone; the service does report it for a mesh file, at POST /cases/validate and on submission, because it reads the stored mesh. A condition for a group the mesh does not have is an error. Override per group: discretization : boundaries : { wall : noslip_isothermal } flow : reynolds : 500 extra : { t_wall : 1.2 } Available: farfield , slip_wall , symmetry , slip_wall_mirror , noslip_adiabatic , noslip_isothermal , pressure_outlet , total_inlet ( formulation ). flow.extra holds boundary data and one model switch, all as plain numbers: t_wall , p_back , pt_in , tt_in (ratios to the free-stream static values) and suth , the non-dimensional Sutherland temperature S/T_\\infty — when present, the viscosity follows Sutherland’s law instead of being constant ( governing equations ). Three conditions read data from flow.extra : noslip_isothermal needs t_wall , pressure_outlet needs p_back , and total_inlet needs pt_in and tt_in . The bump channel and the flat plate supply defaults: the isentropic total conditions of flow.mach for pt_in and tt_in , and p_back = 1. Elsewhere a missing entry is a validation error that names the boundary group, the condition and the key.\nfield default meaning mach 0.5 free-stream Mach number alpha_deg 0 angle of attack in degrees beta_deg 0 sideslip angle in degrees (3-D only); the free stream points along (\\cos\\alpha\\cos\\beta,\\ \\sin\\alpha\\cos\\beta,\\ \\sin\\beta) reynolds none Reynolds number formed with the unit length of the mesh; null or absent means inviscid (Euler) turbulence none sa adds the negative Spalart–Allmaras model ( formulation ); it needs reynolds prandtl , gamma 0.72, 1.4 Prandtl number and ratio of specific heats With turbulence: sa two more entries of flow.extra are read: chi_inf , the free-stream ratio \\tilde\\nu/\\nu (default 3), and prt , the turbulent Prandtl number (default 0.9). Mach number, angles, Reynolds number and these model data are differentiable inputs of the discipline .\nThe reference section sets what the force and moment coefficients are normalised with. The defaults suit the generators (unit chord, quarter-chord moment point); a mesh file usually needs its own. field default meaning area 1 in 2-D; the span of a spanwise-extruded generator mesh in 3-D; the frontal area of the body of a sphere_shell mesh reference area (a length in 2-D) length 1 reference length of the moment coefficient moment_center [0.25, 0.0, 0.0] point about which the moment is taken (two or three coordinates) groups every wall group of the mesh wall boundary groups over which the force, moment and heat-flux coefficients are integrated: the loads of one component, e.g. groups: [wing] on a mesh whose walls are wing and fuselage . Their derivatives and error estimates follow. The nodal loads f_surface and the displacement x_surface always cover all walls components none named components of the walls, each a list of wall groups: components: {wing: [wing], tail: [htail, vtail]} . Every force, moment and heat-flux coefficient is then also an output per component, cd_wing , cl_tail , …, from the same solve. Each is an output like any other: it can be named in gradients.of , error_estimate and adapt.output . Names consist of letters and digits A wall group is a boundary group with a wall condition ( slip_wall , symmetry , slip_wall_mirror , noslip_adiabatic , noslip_isothermal ). Without groups the coefficients are integrated over all of them; groups restricts them to the named ones. The coefficients of a component cover the groups of that component in either case.\nfield default choices and when to change them order 2 polynomial degree p , 0 to 8 (design accuracy p+1 ); viscous cases need p \\ge 1 two_point_flux ranocha volume flux. ranocha and chandrashekar are entropy conservative (keep one of them for production); kennedy_gruber preserves kinetic energy only; central is the unsplit form, for comparisons surface_flux roe interface flux. roe and hllc are the classical solvers; es_roe has the same wave-by-wave dissipation as roe and, like the more dissipative llf , a proven entropy inequality when the volume flux is entropy conservative shock_capturing false sensor-based artificial viscosity; switch on for flows with shocks av_coef 1 scale of that viscosity, in (0, 10]\nfield default range meaning levels auto list of degrees, auto , single p-multigrid degrees fine→coarse; auto is p, \\lfloor p/2\\rfloor, \\dots, 0 ; single for block Jacobi only tol_rel 1e-10 (0, 1) residual reduction, relative to the residual of the free stream, at which the solve counts as converged max_steps 150 1 to 5000 limit on nonlinear (pseudo-time) steps cfl0 5 > 0 pseudo-time CFL number of the first step of a cold start cfl_growth 10 \\ge 1 largest factor by which the CFL number may grow after an accepted step cfl_shrink 1 (0, 1] factor applied to the CFL number after an accepted step that the line search shortened to a quarter or less (1: the CFL number is held) lin_tol 1e-2 (0, 1) relative tolerance of each linear solve (the forcing term of the inexact Newton iteration) krylov_dim 40 5 to 400 size of the GMRES basis between restarts; memory grows with it restarts 2 1 to 50 GMRES cycles per linear solve, so at most krylov_dim × restarts iterations smoother jacobi jacobi , gmres , line multigrid smoother: damped block Jacobi, block-Jacobi-preconditioned GMRES, or line Jacobi (exact solves along lines of strongly coupled elements, for wall-resolved meshes; block Jacobi where the mesh has no such lines) precond_precision double double , single precision in which the blocks of the smoother are assembled, stored and applied. single halves their memory, which is what bounds a 3-D problem on one device; the residual, its Jacobian products, the Krylov vectors and every inverse stay in double precision, so the answers do not change ( measured ) precond_lag 1 1 to 50 assemble the blocks of the smoother every this many nonlinear steps and reuse them in between; the linear systems stay exact, only their preconditioner ages Which of these to change when a solve does not converge: Troubleshooting .\nPOST /api/v1/cases/validate (or the agent tool validate_case ) resolves a case without solving it and returns the element count, degrees of freedom, block size and the device the service would choose. The digest in the response identifies the exact specification; a job with the same specification and the same requests is served from the result cache as long as the code is unchanged. For a case on a mesh file the digest and the cache key cover the contents of the file, not its name.\n\nCaseSpec — Complete, serialisable description of a steady flow analysis. field type default description name string case (max length 80, pattern ^[A-Za-z0-9_.-]+$) description string mesh MeshSpec *required* flow FlowSpec discretization DiscretizationSpec solver SolverSpec reference ReferenceSpec MeshSpec — Built-in mesh generator and its parameters, or a mesh file ( generator: file ). field type default description generator airfoil_omesh | annulus | bump_channel | flat_plate | sphere_shell | box | file *required* Name of the built-in mesh generator, or ‘file’ for an external mesh (params: path, optional scale; Gmsh .msh 2.2/4.1 ASCII or Gyre .npz). params mapping Keyword arguments of the generator (e.g. n_surf, n_radial, r_far, code); unknown, missing or mistyped arguments are validation errors. geometry_order integer 4 Polynomial degree of the curved elements (generators only: a mesh file carries its own degree). (≥ 1, ≤ 8) FlowSpec — Free-stream / reference flow conditions (all differentiable inputs). field type default description mach number 0.5 Free-stream Mach number. (> 0.0, < 5.0) alpha_deg number 0.0 Angle of attack in degrees. (≥ -90.0, ≤ 90.0) beta_deg number 0.0 Sideslip angle in degrees (3-D only; ignored in 2-D). Free-stream direction: (cos a cos b, sin a cos b, sin b). (≥ -90.0, ≤ 90.0) reynolds number or null None Reynolds number (unit reference length); null means inviscid. prandtl number 0.72 (> 0.0) gamma number 1.4 (> 1.0) turbulence none | sa none Turbulence closure: ‘none’ (laminar or inviscid) or ‘sa’ (negative Spalart-Allmaras RANS model; requires reynolds). extra mapping Additional boundary data: p_back, pt_in, tt_in, t_wall, suth; with turbulence ‘sa’ also chi_inf (free-stream nu_tilde/nu, default 3) and prt (turbulent Prandtl number, default 0.9). DiscretizationSpec — Spatial discretization. field type default description order integer 2 Solution polynomial degree p (accuracy p+1); viscous cases need p >= 1. (≥ 0, ≤ 8) two_point_flux ranocha | chandrashekar | kennedy_gruber | central ranocha surface_flux roe | es_roe | hllc | llf roe Interface flux: ‘roe’ and ‘hllc’ are the classical solvers; ‘es_roe’ (matrix dissipation in entropy variables) and ‘llf’ (scalar dissipation) are entropy stable with an entropy-conservative two_point_flux. shock_capturing boolean False Sensor-based artificial viscosity for flows with shocks. av_coef number 1.0 Artificial-viscosity scale (multiples of h/p * (|u|+c)). (> 0.0, ≤ 10.0) boundaries mapping Boundary group -> condition; defaults are chosen per generator. SolverSpec — Nonlinear and linear solver controls. field type default description levels list of integer or auto | single auto p-multigrid degrees fine->coarse, ‘auto’ (p, p//2, …, 0) or ‘single’. tol_rel number 1e-10 (> 0.0, < 1.0) max_steps integer 150 (≥ 1, ≤ 5000) cfl0 number 5.0 (> 0.0) cfl_growth number 10.0 (≥ 1.0) cfl_shrink number 1.0 Factor applied to the CFL number after a step that the line search shortened to a quarter or less (1: hold the CFL). (> 0.0, ≤ 1.0) lin_tol number 0.01 (> 0.0, < 1.0) krylov_dim integer 40 (≥ 5, ≤ 400) restarts integer 2 (≥ 1, ≤ 50) smoother jacobi | gmres | line jacobi Smoother of the p-multigrid levels: damped block Jacobi, block-Jacobi GMRES, or line Jacobi (block-tridiagonal solves along lines of strongly coupled elements of an anisotropic mesh; block Jacobi where the mesh has none). precond_lag integer 1 Assemble the blocks of the smoother every this many nonlinear steps (1: every step). In between, the last blocks are reused. (≥ 1, ≤ 50) precond_precision double | single double Precision in which the blocks of the smoother are assembled, stored and applied. ‘single’ halves their memory, which is what bounds the size of a 3-D problem on one device; the residual, its Jacobian products, the Krylov vectors and the inverses stay in double precision, so the solution is unchanged and only the iteration count can differ. ReferenceSpec — Reference quantities of the force and moment coefficients. field type default description area number or null None Reference area (length in 2-D). Default: 1 in 2-D, the span of a spanwise-extruded generator mesh in 3-D, the frontal area of the body of a sphere_shell mesh. length number 1.0 Reference length of the moment coefficient. (> 0.0) moment_center list of number Point about which the moment is taken. groups list of string or null None Wall boundary groups over which the force, moment and heat-flux coefficients are integrated (the loads of one component). Default: every wall group of the mesh. The nodal load field f_surface and the surface displacement x_surface always cover all walls. components mapping or null None Named components of the walls, each a list of wall boundary groups. Every force, moment and heat-flux coefficient is then also an output per component, named _ (cd_wing, cl_tail, …), from the same solve and with its own derivatives. Names are letters and digits.","title":"Cases","url":"guide/cases.html"},{"audience":["CFD practitioners","MDO integrators","agent builders"],"chunks":[{"anchor":"","end":347,"heading":"","level":2,"start":0},{"anchor":"in-short","end":682,"heading":"In short","level":1,"start":348},{"anchor":"a-case-on-a-mesh-file","end":1515,"heading":"A case on a mesh file","level":2,"start":683},{"anchor":"where-the-file-is-looked-for","end":2009,"heading":"Where the file is looked for","level":2,"start":1516},{"anchor":"making-a-mesh-with-gmsh","end":4938,"heading":"Making a mesh with Gmsh","level":2,"start":2010},{"anchor":"what-the-file-may-contain","end":5712,"heading":"What the file may contain","level":2,"start":4939},{"anchor":"boundary-groups-and-conditions","end":7202,"heading":"Boundary groups and conditions","level":2,"start":5713},{"anchor":"reference-quantities","end":7847,"heading":"Reference quantities","level":2,"start":7203},{"anchor":"periodic-meshes","end":8304,"heading":"Periodic meshes","level":2,"start":7848},{"anchor":"reader-checks","end":9301,"heading":"What the reader checks","level":2,"start":8305},{"anchor":"messages-and-remedies","end":12242,"heading":"Messages and remedies","level":2,"start":9302},{"anchor":"command-line-tools","end":13626,"heading":"Command-line tools","level":2,"start":12243},{"anchor":"mesh-lines","end":16669,"heading":"Will the line smoother act on this mesh?","level":2,"start":13627},{"anchor":"where-else-the-lines-appear","end":17069,"heading":"Where else the lines appear","level":2,"start":16670},{"anchor":"meshes-in-a-service","end":19529,"heading":"Meshes in a service","level":2,"start":17070},{"anchor":"rules-of-the-store","end":20882,"heading":"Rules of the store","level":2,"start":19530},{"anchor":"limits","end":22281,"heading":"Limits","level":2,"start":20883},{"anchor":"how-the-service-confines-the-reader","end":23589,"heading":"How the service confines the reader","level":3,"start":22282},{"anchor":"how-a-gmsh-mesh-becomes-a-gyre-mesh","end":23590,"heading":"How a Gmsh mesh becomes a Gyre mesh","level":2,"start":23590},{"anchor":"the-six-steps-of-the-reader","end":26151,"heading":"The six steps of the reader","level":3,"start":23591},{"anchor":"element-orientation","end":26724,"heading":"Element orientation","level":3,"start":26152},{"anchor":"why-interpolation-and-topological-numbering-are-exact","end":27358,"heading":"Why interpolation and topological numbering are exact","level":4,"start":26725},{"anchor":"npz-format","end":27481,"heading":"The NPZ format","level":2,"start":27359},{"anchor":"array-by-array","end":29022,"heading":"Array by array","level":3,"start":27482}],"headings":["In short","A case on a mesh file","Where the file is looked for","Making a mesh with Gmsh","What the file may contain","Boundary groups and conditions","Reference quantities","Periodic meshes","What the reader checks","Messages and remedies","Command-line tools","Will the line smoother act on this mesh?","Where else the lines appear","Meshes in a service","Rules of the store","Limits","How the service confines the reader","How a Gmsh mesh becomes a Gyre mesh","The six steps of the reader","Element orientation","Why interpolation and topological numbering are exact","The NPZ format","Array by array"],"keywords":["mesh","Gmsh","MSH","NPZ","import","physical groups","boundary groups","periodic","mesh store","upload","gyre mesh"],"level":2,"max_level":4,"path":"guide/meshes.md","section":"guide","section_title":"User Guide","summary":"A case can take its mesh from a file instead of a built-in generator: Gmsh MSH 2.2 or 4.1 (ASCII) with quadrilaterals or hexahedra, or Gyre's own NPZ arrays. This page shows how to produce such a file, how its boundary groups become boundary conditions, what the reader checks, and how a mesh reaches a service.","text":"The built-in generators cover aerofoil O-meshes, cylinders, bump channels and boxes ( Cases ). For any other geometry the mesh comes from a file, written by Gmsh or by your own program. A mesh read from a file becomes the same object a generator builds, so everything else — solver, outputs, gradients, error estimates, sessions — works unchanged.\nMesh the domain with quadrilaterals (2-D) or hexahedra (3-D) in Gmsh, give every boundary a named physical group , save as MSH 4.1 or 2.2 ASCII , and write a case with mesh: {generator: file, params: {path: my.msh}} and one boundary condition per group. gyre mesh info my.msh tells you whether the file is usable and lists its groups.\nname : cylinder_channel mesh : generator : file params : { path : examples/meshes/cylinder_channel_p3.msh } # optional: scale: 0.001 flow : { mach : 0.2 , reynolds : 40.0 } discretization : order : 2 boundaries : # one entry per boundary group of the mesh cylinder : noslip_adiabatic inflow : farfield outflow : farfield walls : slip_wall reference : moment_center : [ 0.0 , 0.0 ] # moment about the cylinder axis gyre run cylinder_channel.yaml -o runs/channel mesh.params Meaning path the mesh file; the extension selects the reader: .msh (Gmsh) or .npz (Gyre arrays) scale positive factor applied to all coordinates (default 1), for a mesh drawn in other units than the reference length Any other key in mesh.params is a validation error. mesh.geometry_order has no effect on a mesh file: the file carries its own geometry degree.\nWith the CLI and in Python, path is used as given (a relative path is relative to the working directory). If a relative path does not exist and the environment variable GYRE_MESH_DIR names a directory, the file is looked up there. With GYRE_MESH_RESTRICT=1 only bare file names inside GYRE_MESH_DIR are accepted; a service sets both for itself (see Meshes in a service ). The case digest covers the contents of the file, not its name: editing the file changes the digest, renaming it does not.\nGmsh is not a dependency of Gyre; any installation that writes MSH files will do. The script below uses Gmsh’s Python API ( pip install gmsh ) and is the construction that produced examples/meshes/cylinder_channel_p3.msh (the repository regenerates it with tools/make_mesh_fixtures.py ): a 5 × 3 channel with a circular hole of radius 0.5, meshed with unstructured cubic quadrilaterals. import gmsh gmsh . initialize () gmsh . model . add ( \"plate\" ) # Geometry: rectangle [-2, 3] x [-1.5, 1.5] minus a disk of radius 0.5 at the origin. occ = gmsh . model . occ rect = occ . addRectangle ( - 2.0 , - 1.5 , 0.0 , 5.0 , 3.0 ) disk = occ . addDisk ( 0.0 , 0.0 , 0.0 , 0.5 , 0.5 ) occ . cut ([( 2 , rect )], [( 2 , disk )]) occ . synchronize () # A named physical group on every boundary curve, sorted by position. def side ( box ): x0 , y0 , _ , x1 , y1 , _ = box if abs ( x0 + 2.0 ) < 1e-6 and abs ( x1 + 2.0 ) < 1e-6 : return \"inflow\" if abs ( x0 - 3.0 ) < 1e-6 and abs ( x1 - 3.0 ) < 1e-6 : return \"outflow\" if abs ( y0 - y1 ) < 1e-6 and abs ( abs ( y0 ) - 1.5 ) < 1e-6 : return \"walls\" return \"cylinder\" groups = {} for _ , tag in gmsh . model . getEntities ( 1 ): groups . setdefault ( side ( gmsh . model . getBoundingBox ( 1 , tag )), []) . append ( tag ) for name , tags in sorted ( groups . items ()): gmsh . model . addPhysicalGroup ( 1 , tags , name = name ) gmsh . model . addPhysicalGroup ( 2 , [ t for _ , t in gmsh . model . getEntities ( 2 )], name = \"fluid\" ) # Quadrilaterals only, complete Lagrange elements. gmsh . option . setNumber ( \"Mesh.RecombineAll\" , 1 ) gmsh . option . setNumber ( \"Mesh.Algorithm\" , 8 ) # frontal-Delaunay for quads gmsh . option . setNumber ( \"Mesh.RecombinationAlgorithm\" , 1 ) # blossom gmsh . option . setNumber ( \"Mesh.SubdivisionAlgorithm\" , 1 ) # split every element into quads gmsh . option . setNumber ( \"Mesh.SecondOrderIncomplete\" , 0 ) gmsh . option . setNumber ( \"Mesh.MeshSizeMin\" , 0.6 ) gmsh . option . setNumber ( \"Mesh.MeshSizeMax\" , 0.6 ) gmsh . model . mesh . generate ( 2 ) gmsh . model . mesh . setOrder ( 3 ) # cubic elements # MSH 4.1, ASCII; only elements that belong to a physical group are written. gmsh . option . setNumber ( \"Mesh.SaveAll\" , 0 ) gmsh . option . setNumber ( \"Mesh.MshFileVersion\" , 4.1 ) gmsh . option . setNumber ( \"Mesh.Binary\" , 0 ) gmsh . write ( \"cylinder_channel_p3.msh\" ) gmsh . finalize () In a .geo script the same settings are the options of the same names ( Mesh.RecombineAll = 1; , Mesh.ElementOrder = 3; , Mesh.MshFileVersion = 4.1; ) and the groups are Physical Curve(\"walls\") = {…}; and Physical Surface(\"fluid\") = {…}; . Tip All-quadrilateral meshes Recombination alone can leave a few triangles. Mesh.SubdivisionAlgorithm = 1 splits every element into quadrilaterals and removes them; transfinite surfaces with Recombine give structured quadrilaterals directly. In 3-D use transfinite volumes or extrusions with Recombine .\nSupported Gmsh element types File format MSH 2.2 and 4.1 , ASCII — 2-D elements quadrilaterals of degree 1 to 5 3, 10, 36, 37, 38 3-D elements hexahedra of degree 1 to 4 5, 12, 92, 93 Boundary elements lines of degree 1 to 5 (2-D), quadrilaterals (3-D), in physical groups 1, 8, 26, 27, 28 Points accepted and ignored 15 All elements are complete Lagrange elements ( Mesh.SecondOrderIncomplete = 0 ) and all volume elements of a mesh have the same degree. Triangles, tetrahedra, prisms, pyramids and serendipity elements are refused. A 2-D mesh must lie in a plane z = \\text{const} . Binary MSH files and other format versions are refused; save again with -format msh41 or -format msh22 . The Gyre NPZ format has no upper limit on the geometry degree; see its arrays below.\nEach physical group of boundary curves (2-D) or boundary surfaces (3-D) becomes a boundary group of the mesh, under the group’s name ( group<tag> if the group has no name). The physical group of the elements themselves ( fluid above) is not used. Every boundary face must belong to a physical group. A file with untagged boundary faces is refused. A face in several physical groups gets the one with the smallest tag. Physical groups on interior curves or surfaces are ignored. A mesh file has no default boundary conditions . discretization.boundaries must give a condition for every group, and must not name a group the mesh does not have. The available conditions and the data some of them need in flow.extra are listed under Cases . $ gyre mesh info examples/meshes/cylinder_channel_p3.msh { \"dim\": 2, \"elements\": 284, \"geometry_order\": 3, \"nodes\": 2676, \"boundaries\": { \"cylinder\": 16, \"inflow\": 12, \"outflow\": 12, \"walls\": 40 }, … } The output is shortened here: the quality numbers, the bounding box, sha256 , bytes and lines (the lines of the line smoother on this mesh) follow. Warning Which groups carry the forces The force and moment coefficients are integrated over every group with a wall condition ( slip_wall , symmetry , slip_wall_mirror , noslip_adiabatic , noslip_isothermal ), and the shape input x_surface and the load output f_surface cover the nodes of the same groups. In the channel case above the slip walls therefore contribute to cl and cm next to the cylinder.\nThe generators know their geometry; a mesh file does not say what its reference length is. The reference section of the case sets the quantities the coefficients are normalised with: Field Default Meaning area 1 reference area of the force coefficients (a length in 2-D) length 1 reference length of the moment coefficient moment_center [0.25, 0.0, 0.0] point the moment is taken about (two or three coordinates) The flow conditions are non-dimensional with a unit reference length ( flow.reynolds is the Reynolds number per unit length), so either draw the mesh with the reference length equal to one or bring it there with mesh.params.scale .\nPeriodic node pairs of the file ( $Periodic , written by Gmsh for curves or surfaces declared periodic, for example with gmsh.model.mesh.setPeriodic ) identify the two sides: the faces on them become interior faces, and a physical group on a periodic side no longer appears among the boundary groups, so it needs no boundary condition. periodic_nodes in the summary counts the identified nodes. A periodic direction needs at least three elements across it.\nA file is read and validated in full before it is used; nothing is repaired silently. Each refusal is a message that says what is wrong. gyre mesh info FILE shows it on the command line, the service returns it as the message of a 422 InvalidMesh . The summary reports quality numbers for a mesh that passed: Key Meaning min_scaled_jacobian smallest ratio of the minimum to the maximum Jacobian determinant within one element (1 for a parallelogram; small values mean strongly distorted elements) interior_faces , boundary_faces number of interior face pairs and of boundary faces reoriented_elements left-handed elements of the file that were turned right-handed reoriented_face_pairs interior face pairs whose two elements use different local axes on the face (zero for a structured mesh) periodic_nodes nodes identified with a periodic master bounding_box [[xmin, ymin(, zmin)], [xmax, ymax(, zmax)]] after scaling sha256 , bytes hash and size of the file; the hash is what the case digest uses\nMessage (beginning) Cause Remedy mesh file '…' does not exist wrong path, or the file is not in GYRE_MESH_DIR correct mesh.params.path unknown mesh file type '.xyz' extension is neither .msh nor .npz export as MSH, or convert not a Gmsh MSH file (no $MeshFormat header) the file is something else export from Gmsh as MSH binary MSH files are not supported written with -bin or Mesh.Binary = 1 Mesh.Binary = 0 MSH format version … is not supported for example 4.0 or 1.0 -format msh41 or -format msh22 the mesh contains triangles (tetrahedra, prisms, pyramids, serendipity …) not an all-quad / all-hex mesh of complete elements recombined or transfinite meshing, Mesh.SecondOrderIncomplete = 0 the mesh contains no quadrilaterals or hexahedra only points or lines were saved add a physical group for the surface or volume, or Mesh.SaveAll = 1 elements of several degrees in one mesh mixed element orders one Mesh.ElementOrder for the whole mesh N boundary faces belong to no physical group (the first one is near (x, y)) a boundary without a physical group; for MSH 4.1 also a file without $Entities add Physical Curve / Physical Surface for that boundary; export again two physical groups share a name duplicate names rename one a 2-D mesh must lie in a plane z = const a surface mesh in space mesh in the x-y plane N of K elements have a non-positive Jacobian determinant (element k near …) inverted element or tangled curved edges Mesh.HighOrderOptimize = 2 , or refine near strongly curved boundaries $Nodes holds coordinates that are not finite numbers (MSH), N nodes have coordinates that are not finite numbers (NPZ) NaN or infinity in the file regenerate the mesh a face is shared by more than two elements non-manifold mesh, or fewer than three elements across a periodic direction repair the topology; refine the periodic direction periodic identification maps two nodes of one element onto each other , an element edge joins a vertex to itself fewer than three elements across a periodic direction, or a collapsed element refine; remove degenerate elements unmatched interior faces , a face is both interior and tagged as boundary inconsistent connectivity (usually an NPZ file) correct elem_nodes / face_bc the file ends inside section $… , the file declares N … but holds at most M , $Nodes declares … , malformed MSH … file: … truncated or damaged file export again the group name '…' cannot be written to an MSH file (writing) a name with a double quote or a line break rename the group no boundary condition for groups […] the case omits a group add it to discretization.boundaries discretization.boundaries names boundary groups […] that the mesh does not have misspelt or stale group name use the names gyre mesh info lists A damaged file is never read as a smaller or a non-periodic mesh: counts declared in the file are checked against its contents, and a section that is cut off or has lost its opening line is an error.\ngyre mesh info FILE [ --scale S ] # read, validate, print the summary as JSON gyre mesh convert IN OUT # .msh <-> .npz, chosen by the extension of OUT gyre mesh export CASE OUT [ --set PATH = VALUE ... ] # write the mesh of a preset or case file info prints the summary shown above; its last key, lines , is described below . --scale applies a factor to the coordinates before the mesh is measured. convert reads IN (which validates it) and writes OUT as Gmsh MSH 2.2 ASCII ( .msh ) or as Gyre NPZ ( .npz ). It prints {\"written\": OUT, …} followed by the summary of the written file, read back. An .npz file is smaller than the Gmsh text file. export writes the mesh of a preset or case file, for example to look at a generator mesh in Gmsh: gyre mesh export cylinder_re40 annulus.msh . --set works as for gyre run . It prints the same as convert , so it is also the way to the summary of a generator mesh. Any other extension of OUT is refused before anything is read. A command that cannot use a file or case prints gyre mesh <action>: <message> to standard error, prints nothing to standard output and exits with status 1. The writer produces $Periodic node pairs for periodic meshes, names the element group fluid , and writes degrees for which Gmsh has an element type (quadrilaterals up to 5, hexahedra up to 4); a generator mesh of a higher degree can be exported as .npz only.\nsolver.smoother: line solves exactly along lines of thin cells and is block Jacobi wherever a mesh has none ( how the lines are formed ). The lines depend on the mesh alone, so the three commands report them, under lines , before anything is solved. For the mesh of the preset flat_plate_sa : $ gyre mesh export flat_plate_sa plate.npz { \"written\": \"plate.npz\", \"dim\": 2, \"elements\": 150, … \"lines\": { \"lines\": 18, \"elements_in_lines\": 131, \"elements\": 150, \"longest\": 12, \"colors\": 3, \"slots\": 152, \"threshold\": 4.0, \"anisotropy_max\": 50679888.014390774, \"anisotropy_median\": 463.07670555770085 } } Eighteen lines hold 131 of the 150 elements; the other 19 are smoothed by block Jacobi. For examples/meshes/cylinder_channel_p3.msh , gyre mesh info prints \"lines\": 0 with an anisotropy_max of 2.12 and an anisotropy_median of 1.11: no cell reaches the threshold of 4, and smoother: line would change nothing on that mesh. Both outputs were recorded on the CPU of the development machine with the working tree that followed revision 15e5de9 (the change that added slots ); … marks omitted keys and the two numbers of the channel mesh are rounded. Key of lines Meaning lines number of lines; 0 for a mesh without lines elements_in_lines , elements elements that belong to a line, and all elements of the mesh longest number of elements of the longest line colors colours the smoother needs to assemble the blocks that couple the elements of a line (three for aligned lines); 0 without lines slots line slots the smoother stores: the lines are kept in a few tables by length, each as long as its longest line, so this exceeds elements_in_lines by the padding; 0 without lines threshold anisotropy from which a cell qualifies for a line. It is 4, the value the solver uses: a box-shaped cell at least twice as wide in every other direction as it is thick anisotropy_max , anisotropy_median largest and median anisotropy of the cells The anisotropy of a cell is the number the threshold is compared with: the coupling of the cell with its neighbours in its strongest direction divided by that in its second strongest, where the coupling across a face is the face area divided by the distance of the two cell centres. For a box-shaped cell it is the square of the ratio of its two shortest edges: the square of the aspect ratio in two dimensions, and in three dimensions only when the two longer edges are equal. anisotropy_max below threshold : no cell qualifies, the mesh has no lines. anisotropy_median above threshold : at least half of the cells qualify. A cell that qualifies is in a line only if a neighbour across its strongest direction qualifies through the same face, so elements_in_lines can be smaller than the number of such cells, and anisotropy_max can exceed the threshold on a mesh without lines. null instead of a number: the value is not finite. A cell with neighbours in one direction only (a mesh one element wide) has no second direction; it qualifies whatever its shape. A mesh of a single cell has no neighbours to measure.\nA run with solver.smoother: line records the first five keys as case.lines of its result ( null if the mesh has no lines or another smoother is used). In Python, meshio.inspect_mesh(path, lines=True) returns the summary with lines , and meshio.line_summary(mesh) the lines of a mesh object. The summary a service keeps for a stored mesh, and inspect_mesh without the argument, do not contain lines .\nA service never opens a path from a request. It keeps a mesh store (the directory meshes of its data directory): a client uploads a file under a name and then refers to it by that name in mesh.params.path . Below, API is http://HOST:8600/api/v1 . curl -s -X PUT --data-binary @cylinder_channel_p3.msh \\ -H 'content-type: application/octet-stream' $API /meshes/cylinder_channel_p3.msh # → 201 {\"name\": \"cylinder_channel_p3.msh\", \"bytes\": …, \"sha256\": \"…\", \"dim\": 2, \"elements\": 284, # \"boundaries\": {\"cylinder\": 16, \"inflow\": 12, \"outflow\": 12, \"walls\": 40}, …} curl -s $API /meshes # {\"meshes\": [summary, …]} curl -s $API /meshes/cylinder_channel_p3.msh # one summary curl -s -X DELETE $API /meshes/cylinder_channel_p3.msh curl -s $API /jobs -H 'content-type: application/json' -d '{ \"case\": {\"name\": \"cylinder_channel\", \"mesh\": {\"generator\": \"file\", \"params\": {\"path\": \"cylinder_channel_p3.msh\"}}, \"flow\": {\"mach\": 0.2, \"reynolds\": 40.0}, \"discretization\": {\"order\": 2, \"boundaries\": { \"cylinder\": \"noslip_adiabatic\", \"inflow\": \"farfield\", \"outflow\": \"farfield\", \"walls\": \"slip_wall\"}}, \"reference\": {\"moment_center\": [0.0, 0.0]}}}' from gyre.client import GyreClient gyre = GyreClient ( \"http://HOST:8600\" ) info = gyre . upload_mesh ( \"cylinder_channel_p3.msh\" ) # name defaults to the file name print ( info [ \"boundaries\" ]) # faces per boundary group gyre . meshes () # list of summaries gyre . mesh ( info [ \"name\" ]) # one summary case = { \"name\" : \"cylinder_channel\" , \"mesh\" : { \"generator\" : \"file\" , \"params\" : { \"path\" : info [ \"name\" ]}}, \"flow\" : { \"mach\" : 0.2 , \"reynolds\" : 40.0 }, \"discretization\" : { \"order\" : 2 , \"boundaries\" : { \"cylinder\" : \"noslip_adiabatic\" , \"inflow\" : \"farfield\" , \"outflow\" : \"farfield\" , \"walls\" : \"slip_wall\" }}, \"reference\" : { \"moment_center\" : [ 0.0 , 0.0 ]}} result = gyre . run ( case ) gyre . delete_mesh ( info [ \"name\" ]) → upload_mesh(name=\"cylinder_channel_p3.msh\", content=\"$MeshFormat\\n4.1 0 8\\n…\") ← {\"name\": \"cylinder_channel_p3.msh\", \"elements\": 284, \"geometry_order\": 3, \"boundaries\": {\"cylinder\": 16, \"inflow\": 12, \"outflow\": 12, \"walls\": 40}, …} → validate_case(case={\"name\": \"cylinder_channel\", \"mesh\": {\"generator\": \"file\", \"params\": {\"path\": \"cylinder_channel_p3.msh\"}}, \"flow\": {\"mach\": 0.2, \"reynolds\": 40.0}, \"discretization\": {\"order\": 2, \"boundaries\": {…one per group…}}}) → list_meshes() → inspect_mesh(name) → delete_mesh(name) An .npz file is sent as base64 with encoding=\"base64\" .\nNames, never paths. A name matches ^[A-Za-z0-9][A-Za-z0-9_.-]{0,79}\\.(msh|npz)$ (lower-case extension, no directories). Any other name, in the URL or in mesh.params.path of a case (also when it arrives through overrides ), is answered 422 InvalidMeshName , and nothing is opened or written. Validated on upload. The request body is the file. It is read by the same reader as on the command line before it enters the store; a file the reader refuses is answered 422 InvalidMesh with the reader’s message and is not stored. A mesh already stored under that name stays as it was. A valid upload replaces an existing mesh of the same name. A case names a stored mesh. POST /cases/validate , /jobs and /sessions answer 404 UnknownMesh (the hint names the PUT to make) if the mesh is not in the store, and 422 InvalidCase if discretization.boundaries does not match the boundary groups of the mesh. Replacing a mesh. The result cache of jobs is keyed on the contents of the mesh file, so a job on a replaced mesh is computed anew. A session reads its mesh when it starts and keeps running on it, and reports the digest of that moment, if the mesh is replaced or deleted afterwards. A job that is still queued when its mesh is deleted fails. Access. With GYRE_API_TOKEN set, every mesh route, GET included, requires the bearer token, like the rest of the API.\nLimit Default Variable Answer when exceeded size of one upload 64 MiB GYRE_MESH_MAX_BYTES 413 MeshTooLarge uncompressed size of the arrays of an .npz upload the smaller of 4 × the upload limit and 1 GiB GYRE_MESH_MAX_BYTES , GYRE_MESH_MAX_EXPANDED_BYTES 422 InvalidMesh number of meshes in the store 256 GYRE_MESH_STORE_MAX_FILES 507 MeshStoreFull total size of the store 4 GiB GYRE_MESH_STORE_MAX_BYTES 507 MeshStoreFull The variables belong to the service’s environment and are read at each request. A mesh that replaces one of the same name does not count against the number of meshes. An .npz upload must pass two limits on the uncompressed size of its arrays: four times GYRE_MESH_MAX_BYTES , which the service checks, and GYRE_MESH_MAX_EXPANDED_BYTES (1 GiB by default), which the reader checks in every process, the service included. With an upload limit above 256 MiB the second one is the smaller unless it is raised as well. All four values are positive integers in bytes (a count for GYRE_MESH_STORE_MAX_FILES ). For GYRE_MESH_MAX_BYTES , GYRE_MESH_STORE_MAX_FILES and GYRE_MESH_STORE_MAX_BYTES any other value, zero included, is replaced by the default, so 0 does not switch uploads off. An invalid GYRE_MESH_MAX_EXPANDED_BYTES is an error instead: every read of an .npz file fails with GYRE_MESH_MAX_EXPANDED_BYTES must be a positive integer, got '…' (a 422 InvalidMesh for an upload).\nThe store is <data_dir>/meshes . While the service runs it sets GYRE_MESH_DIR to that directory and GYRE_MESH_RESTRICT=1 in its own environment; job workers, session processes and pipeline runs inherit both, and the previous values are restored at shutdown. With the restriction the reader accepts only a bare file name inside that directory, so the check holds in every process that could read a mesh, not only in the request handler. The request handler checks the name against the pattern before it touches the file system, and error messages name the mesh as the client named it, never a server directory. An upload is received into a temporary file of the store’s directory, with the size limit enforced on the Content-Length header and again while the body arrives. It is validated there, one upload at a time, and moved to its name only if it is accepted. Leftovers of an upload that was interrupted by a crash are removed at the next start. An application object that is used without its start-up phase has not set the two variables; it answers a case that names a stored mesh with 503 MeshStoreInactive instead of resolving it. The name check and the lookup in the store come first, so a name that is not a store name is still a 422 InvalidMeshName and a mesh that is not stored a 404 UnknownMesh .\n\nA Gyre mesh stores, per element, the numbers of its (p_g + 1)^d geometry nodes on a lattice indexed [i, j(, l)] along the reference directions, with the nodes at the Legendre–Gauss–Lobatto (LGL) points of degree p_g and shared between neighbours. Gmsh stores each element as a list of nodes in its own order, at equally spaced points. A cubic quadrilateral. Gmsh numbers the four vertices counter-clockwise, then the interior nodes of the four edges in the same sense, then the interior as a quadrilateral of degree p_g - 2 , recursively. The reader places each node at its lattice position and moves the nodes from equal spacing to Gauss–Lobatto spacing. Ordering. A table maps the Gmsh node order of each element type to lattice positions. For a hexahedron the order is vertices, edge interiors, face interiors (each as a quadrilateral of degree p_g - 2 in the frame of the face’s vertices) and the volume interior, recursively. An element whose first two lattice directions (and the third, in 3-D) form a left-handed frame has its first two directions exchanged ( reoriented_elements ). Node distribution. Along every lattice direction the equally spaced nodes are interpolated to the LGL points of the same degree. Up to degree 2 the two sets coincide and nothing is done. Topological numbering. Lattice nodes are numbered from the mesh topology, not from their coordinates: a vertex by its node in the file; an edge node by the two vertices of its edge and its position counted from the vertex with the smaller number; a face node (3-D) by the four vertices of its face and its position in a frame fixed by the smallest vertex; interior nodes are private to their element. Two elements that share an edge or a face therefore share its nodes exactly, whatever their local axes, and nodes that merely coincide in space — the two sides of a slit, a periodic pair — stay distinct. Boundary faces. A face that belongs to one element only is a boundary face and takes the physical group of the boundary element with the same vertices. An element listed several times (MSH 2.2 repeats it per physical group) is used once. Periodicity. The $Periodic node pairs are treated as an equivalence of nodes, so that a corner node that is the slave of two periodic curves is identified consistently. Each node gets a periodic master; faces are matched through the masters. Validation. All coordinates must be finite and the Jacobian determinant positive at every geometry node of every element; then faces are matched and each interior pair gets its relative orientation.\nIn an unstructured mesh two neighbours generally disagree about the direction, and in 3-D about the order, of the axes on their common face. The connectivity records for each interior face which index symmetry (reversal of either face axis, exchange of the two) maps its nodes onto the neighbour’s; the spatial operator applies it when it reads the neighbour’s trace. reoriented_face_pairs counts the pairs for which that map is not the identity. The verification study on imported meshes tests free-stream preservation, conservation and neighbour coupling on such meshes.\nThe p_g + 1 equally spaced nodes on an edge and the p_g + 1 LGL nodes both determine the same polynomial of degree p_g , so evaluating the Lagrange interpolant of the first set at the second changes the representation and not the curve: the geometry of the file is reproduced exactly, up to round-off. The interpolation acts along lattice lines, and on an edge or face it uses only the nodes of that edge or face. Two neighbours thus compute the same coordinates for a shared node; because the node is stored once, under its topological number, the mesh is watertight by construction, with no tolerance for merging nodes by distance.\nSee the imported-mesh example for the channel case with results, and the REST and MCP references for the routes and tools.\nAn .npz file holds the arrays of a Gyre mesh. gyre mesh convert and gyre.meshio.save_mesh write it; a program that builds meshes can write it directly with numpy.savez . Array Shape Content format scalar string gyre-mesh-1 dim scalar integer 2 or 3 pg scalar integer geometry degree, at least 1 nodes (Nn, dim) float coordinates of the unique geometry nodes elem_nodes (K, pg+1, pg+1[, pg+1]) integer node numbers on each element’s lattice, indexed [k, i, j(, l)] along the reference directions; the nodes of an element are LGL-distributed in its reference element and shared with its neighbours face_bc (K, 2*dim) integer boundary-group index of each local face, −1 for interior faces; local face 2*a + s is normal to reference direction a , at its lower ( s = 0 ) or upper ( s = 1 ) end bc_names (G,) strings names of the boundary groups, indexed by face_bc topo (Nn,) integer, optional periodic master of each node; a master is its own master ( topo[topo] == topo ); absent for a mesh without periodicity The reader checks shapes, integer types, index ranges and unique group names, and then validates the mesh like a Gmsh mesh (finite coordinates, positive Jacobians, face matching). Elements must be right-handed and neighbours must share node numbers: the NPZ reader does not re-orient or renumber. The archive is measured before any array is read: every member must be an .npy array without Python objects whose header describes exactly the bytes stored for it, and the members together must stay within the size limit (see Limits ).","title":"Mesh files","url":"guide/meshes.html"},{"audience":[],"chunks":[{"anchor":"","end":111,"heading":"","level":2,"start":0},{"anchor":"jobs-one-shot-analyses","end":632,"heading":"Jobs: one-shot analyses","level":2,"start":112},{"anchor":"endpoints","end":1175,"heading":"Endpoints","level":2,"start":633},{"anchor":"order-selection-adapt","end":4437,"heading":"Order selection: `adapt`","level":2,"start":1176},{"anchor":"states-and-caching","end":5614,"heading":"States and caching","level":3,"start":4438},{"anchor":"sessions-warm-solvers-for-design-loops","end":6126,"heading":"Sessions: warm solvers for design loops","level":2,"start":5615},{"anchor":"endpoints-2","end":7713,"heading":"Endpoints","level":2,"start":6127},{"anchor":"session-behaviour","end":9192,"heading":"Session behaviour","level":3,"start":7714},{"anchor":"meshes","end":10125,"heading":"Meshes","level":2,"start":9193},{"anchor":"behaviour","end":11165,"heading":"Behaviour","level":2,"start":10126},{"anchor":"pipelines","end":12214,"heading":"Pipelines","level":2,"start":11166},{"anchor":"cases-metadata-metrics","end":12562,"heading":"Cases, metadata, metrics","level":2,"start":12215},{"anchor":"errors","end":12817,"heading":"Errors","level":2,"start":12563},{"anchor":"error-types","end":15947,"heading":"Error types","level":3,"start":12818},{"anchor":"python-client","end":16552,"heading":"Python client","level":3,"start":15948}],"headings":["Jobs: one-shot analyses","Endpoints","Order selection: `adapt`","States and caching","Sessions: warm solvers for design loops","Endpoints","Session behaviour","Meshes","Behaviour","Pipelines","Cases, metadata, metrics","Errors","Error types","Python client"],"keywords":["REST","API","jobs","sessions","meshes","mesh store","order selection","SSE","artefacts","metrics","errors"],"level":2,"max_level":3,"path":"guide/service.md","section":"guide","section_title":"User Guide","summary":"The REST API offers two ways to compute — batch jobs for one-off analyses and warm sessions for design loops — plus a mesh store for uploaded mesh files, case validation, progress streams, artefacts and metrics. Responses are JSON, except /metrics (Prometheus text), artefact downloads and the ?stream=true event stream; every error carries a type, a message and a hint.","text":"Base URL: http://HOST:8600/api/v1 . The interactive explorer at /api/docs lists every endpoint with its schema.\n# submit curl -s $API /jobs -H 'content-type: application/json' -d '{ \"case\": \"naca0012_euler\", \"overrides\": {\"flow.alpha_deg\": 3.0}, \"gradients\": {\"of\": [\"cl\", \"cd\"], \"wrt\": [\"alpha\", \"mach\"]}, \"error_estimate\": [\"cd\"] }' # → 202 {\"id\": \"job-20261110-121500-a1b2c3\", \"state\": \"queued\", \"device\": \"cpu\", \"digest\": \"…\", …} curl -s \" $API /jobs/ $ID ?wait=60\" # long-poll until finished (or 60 s) curl -s $API /jobs/ $ID /result # full result record curl -N \" $API /jobs/ $ID /events?stream=true\" # live Server-Sent Events\nMethod and path Purpose POST /jobs queue an analysis ( case , overrides , gradients , error_estimate , adapt , device , use_cache , label ) GET /jobs recent jobs ( ?state= , ?limit= ) GET /jobs/{id} state, progress, result summary ( ?wait= seconds) GET /jobs/{id}/result outputs, history, gradients, error estimates, surface data, provenance GET /jobs/{id}/events progress events ( ?since=N , ?stream=true ) GET /jobs/{id}/artifacts/{name} result.json , solution.npz , flow.vtu , field.json , events.jsonl , error.txt DELETE /jobs/{id} cancel\nWith adapt the service chooses the polynomial degree: starting at discretization.order it raises the degree, warm-starting each level from the previous one, until the adjoint-based error estimate of one output is within a tolerance. curl -s $API /jobs -H 'content-type: application/json' -d '{ \"case\": \"bump_euler\", \"overrides\": {\"discretization.order\": 1}, \"adapt\": {\"output\": \"cd\", \"tolerance\": 1e-4, \"max_order\": 4} }' adapt output required scalar output whose estimated error drives the selection tolerance required, > 0 absolute tolerance on that estimate max_order 1 to 8, default 5 highest degree to try The result is that of the final degree ( spec , case , outputs , gradients , error_estimate , artefacts) and has an adaptation section: {output, tolerance, met, order, value, corrected, error, levels} , where levels has one record per degree tried. Check met : a job whose tolerance was not reached at max_order is still succeeded , with the result of the highest degree that converged. The job record carries requested.adapt and result_summary.adaptation (without levels ). The event log shows every degree in turn: a case event, the iteration events of its solve (each with order ), and one adapt event (the level record) that closes it; the last case event is that of the result. timing.solve is the wall time of the whole selection and timing.setup is 0. A degree whose solve fails from the interpolated lower-degree solution is tried again before it counts as failed: from the same guess with the full continuation (skipped when the guess is not an admissible state), then from the free stream. Each further attempt is an adapt_retry event with order , reason (why the previous attempt failed) and start ( interpolated or free_stream ). Field of a level record Meaning order polynomial degree of this level degrees_of_freedom size of the discretization at this degree converged whether the flow solve of this degree converged value the output at this degree estimate adjoint-based estimate of its discretization error corrected the output corrected by the estimate error the estimated error that is compared with tolerance (by absolute value) adjoint_converged whether the adjoint solve of the estimate converged newton_steps steps of the flow solve wall_time seconds spent on this degree, estimate included A degree whose flow solve did not converge has order , degrees_of_freedom , converged: false , the solver’s reason and wall_time only. met is true only when the estimate is a finite number, its adjoint solve converged and its absolute value is at most tolerance ; an estimate below the tolerance from an adjoint solve that did not converge does not end the selection. The selection also ends at the first degree whose flow solve does not converge, below max_order too: met is false and order , value , corrected and error are those of the last degree that converged. If the starting degree itself does not converge, these four are null and the result has converged: false . Unknown keys and invalid values are a 422 ValidationError . A selection that cannot start — discretization.order is 0, or max_order is below discretization.order — is a 422 InvalidCase . An output that the case does not have is found by the worker: the job fails.\nqueued → running → succeeded | failed | cancelled , plus interrupted for jobs that were active when the service restarted. A job identical to an earlier successful one is answered immediately from the cache with cached_from naming the original; pass \"use_cache\": false to force a new run. The cache key is the specification, the gradient, error-estimate and adapt requests (an omitted max_order equals 5), the contents of a mesh file the case names, and the code (version, revision and a hash of the package sources): a result computed by different code, for example before an upgrade on the same data directory, is not reused, and replacing a stored mesh under the same name is a cache miss. On a cache hit device is the device of the original run. A job that ran but did not converge is succeeded with \"converged\": false — check the flag. Its outputs are those of the iterate the solve ended at, and it has no gradients or error_estimate : the result and the job’s result_summary carry skipped , which names each requested section that is missing with the reason. Non-finite values (a diverged solve) are reported as null in job records, results, events and session replies.\nSID = $( curl -s $API /sessions -H 'content-type: application/json' \\ -d '{\"case\": \"naca0012_euler\"}' | jq -r .id ) curl -s $API /sessions/ $SID /evaluate -H 'content-type: application/json' \\ -d '{\"inputs\": {\"alpha_deg\": 2.5, \"mach\": 0.52}}' # → {\"converged\": true, \"nonlinear_iterations\": 5, \"outputs\": {\"cl\": 0.36, …}, \"wall_time\": 0.8} curl -s $API /sessions/ $SID /gradients -H 'content-type: application/json' \\ -d '{\"of\": [\"cl\"], \"wrt\": [\"alpha_deg\", \"x_surface\"]}' curl -s -X DELETE $API /sessions/ $SID\nMethod and path Purpose POST /sessions start a solver process for a case; returns its interface (inputs, outputs, shapes) GET /sessions , GET /sessions/{id} list / describe POST /sessions/{id}/evaluate set inputs, converge (warm start), return outputs POST /sessions/{id}/gradients total derivatives at the current state POST /sessions/{id}/adjoint reverse total product for output seeds (coupling) POST /sessions/{id}/error-estimate adjoint-weighted-residual estimates GET /sessions/{id}/surface surface-node coordinates and current displacement GET /sessions/{id}/distributions wall c_p , c_f GET /sessions/{id}/field field sample for visualisation GET /sessions/{id}/state current inputs, solved , converged and residual norm POST /sessions/{id}/tangent forward total product for input perturbations POST /sessions/{id}/residual norms of the residual of the session’s state, at its inputs or at given ones POST /sessions/{id}/revert after an evaluation that did not converge: back to the last converged point POST /sessions/{id}/get-state , …/set-state download / upload the flow state (bytes, application/octet-stream ) POST /sessions/{id}/checkpoint , …/restore save / restore inputs, surface displacement and state as one blob; a checkpoint also rebuilds a session that was closed or reaped ( GyreClient.restore_session ) DELETE /sessions/{id} close The last five rows go through one generic route, POST /sessions/{id}/{command} , which passes any command of the solver process through ( - and _ are the same in the name); GET /sessions/{id} lists the commands a session supports.\nOne command runs at a time per session. The first evaluate compiles kernels; later ones are warm-started and typically need a handful of Newton steps. Idle sessions close after 30 minutes; at most four are open at once by default. alpha_deg is accepted wherever alpha (radians) is: in evaluate inputs and in the wrt of /gradients , /adjoint and job gradients . The derivative is returned under the name asked for, per degree for alpha_deg . With wrt omitted the result has alpha (radians) only, and /state and the session interface list alpha . Non-finite or non-numeric inputs are refused with 422. If an evaluation does not converge, the reply has converged: false with the outputs and residual of the iterate the solve ended at, and the session keeps its last converged state as the warm start of the next evaluation. Until an evaluation converges again, /gradients , /adjoint , /error-estimate , /distributions and /field answer 422 RuntimeError ; the last two do so before the first evaluation as well. converged of /state is true when a state exists and the last evaluation converged. case is a preset name or a CaseSpec object, for jobs, sessions and validation alike; a string is never read as a file path (file specifications are a CLI and Python feature). A case on a mesh file names a mesh of the mesh store . A session reads that mesh once, when it starts: it keeps running on it, and reports the digest of that moment, if the mesh is replaced or deleted afterwards.\nA case with mesh: {generator: file, params: {path: NAME}} refers to a mesh that was uploaded to the service’s mesh store under NAME . Mesh files has the formats, examples for curl, Python and MCP, and the limits. curl -s -X PUT --data-binary @wing.msh -H 'content-type: application/octet-stream' \\ $API /meshes/wing.msh # → 201 {\"name\": \"wing.msh\", \"bytes\": …, \"sha256\": \"…\", \"dim\": 2, \"elements\": …, # \"geometry_order\": …, \"nodes\": …, \"boundaries\": {\"wall\": …, \"farfield\": …}, …} Method and path Purpose GET /meshes {\"meshes\": [summary, …]} , sorted by name PUT /meshes/{name} store the request body (the file as it is on disk) under name ; 201 with the summary; replaces an existing mesh of that name GET /meshes/{name} summary: name , bytes , sha256 , dim , elements , geometry_order , nodes , boundaries (faces per group), bounding_box , the reader’s quality numbers DELETE /meshes/{name} remove; {\"name\": name, \"deleted\": true}\nA name matches ^[A-Za-z0-9][A-Za-z0-9_.-]{0,79}\\.(msh|npz)$ . Anything else, including a name with directories, is a 422 InvalidMeshName on every mesh route and in mesh.params.path of a case; nothing is opened or written. The upload is read and validated in full. A file the reader cannot use is a 422 InvalidMesh with the reader’s message and is not stored; a mesh already stored under the name is unchanged. A case that names a mesh which is not stored is a 404 UnknownMesh , at validation, job submission and session creation; the hint names the PUT to make. A case whose discretization.boundaries does not give a condition for every boundary group of the mesh, or names a group the mesh does not have, is a 422 InvalidCase . An upload above GYRE_MESH_MAX_BYTES (64 MiB by default) is a 413 MeshTooLarge ; a full store is a 507 MeshStoreFull ( limits ). A file that was placed in the store’s directory by hand and that the reader rejects is listed by GET /meshes as {\"name\", \"error\"} and answered 422 InvalidMesh by GET /meshes/{name} .\nMethod and path Purpose GET /pipelines pipeline definitions available to the deployment (stages, parameters); each entry has runnable and, when that is false, a reason POST /pipelines/{name}/runs start a run ( params , force ); unchanged stages come from the cache. String params stay strings. Every stage’s case , after the run’s parameters are substituted, must be a preset name or a CaseSpec object (404 UnknownPreset otherwise, whatever the parameter is called). Runs of one pipeline are executed one after another GET /pipelines/runs , GET /pipelines/runs/{id} list runs / status; record is the per-stage record this run wrote, or null if the run ended before the engine finished Definitions with shell stages are not runnable through the service (403 PipelineNotRunnable ) unless it was started with GYRE_PIPELINES_ALLOW_SHELL=1 . A run leads its own process group: signals to the service’s process group do not reach it, and at service shutdown each run is terminated together with its stage processes. See Pipelines for the YAML vocabulary.\nMethod and path Purpose GET /presets , GET /presets/{name} built-in cases GET /schema/case , GET /schema/job JSON Schemas POST /cases/validate dry run: resolved spec, size estimate, device, warnings GET /info version, commit, process id ( pid ), deployment name, uptime, documentation build GET /health liveness GET /metrics Prometheus text format\nEvery 4xx and 5xx answer under /api/v1 has the same shape, with type , message and hint always present: { \"error\" : { \"type\" : \"UnknownPreset\" , \"message\" : \"unknown preset 'naca0013'\" , \"hint\" : \"available presets: ['bump_euler', 'cylinder_re40', …]\" }}\nStatus type Meaning 401 Unauthorized bearer token required ( GYRE_API_TOKEN is set) 403 PipelineNotRunnable the pipeline has shell stages (or an invalid definition) and may not be started through the service 404 UnknownPreset , UnknownPipeline , NotFound no such preset, pipeline, job, session, artefact, pipeline run or API route; UnknownPreset is also the answer to a pipeline run in which a stage’s case is a string that is not a preset name 404 UnknownMesh no mesh of that name in the mesh store: on the mesh routes, and for a case that names it 405 MethodNotAllowed the path exists but not with this method; for GET, POST, PUT, PATCH and DELETE the Allow header lists the methods of the path (HEAD and OPTIONS are answered 405 on every API path, with an Allow header that is not complete) 409 NotReady no result: the job has not finished, or did not succeed 410 SessionDead the session process has terminated 422 ValidationError the request does not match the route’s schema (body, query, malformed JSON), or the case specification is invalid: mesh parameters the generator does not accept, order 0 for a viscous case, missing boundary data 413 MeshTooLarge the uploaded mesh file exceeds GYRE_MESH_MAX_BYTES 422 BadOverride , InvalidCase unknown override path; an empty case ; a mesh-file case whose discretization.boundaries does not match the boundary groups of the mesh; an adapt request that cannot start 422 InvalidMeshName , InvalidMesh a mesh name that is not a bare store name (in a URL or in mesh.params.path ); a mesh file the reader cannot use, with the reader’s message 422 KeyError , ValueError , RuntimeError a session command failed: unknown input or output name, bad input value, no flow state yet 429 TooManySessions session limit reached 500 InternalError unexpected fault of the service. The message is the service failed while handling this request (incident <8 hex digits>) ; the exception type and text are in the service log (logger gyre.service ) under the same incident id and are not sent to the client 500 SessionStartFailed the session process died while building the case 503 MeshStoreInactive a case that names a stored mesh was sent to an application that was not started with its start-up phase (never the case under gyre serve ); the name check (422 InvalidMeshName ) and the store lookup (404 UnknownMesh ) come first 504 Timeout a session command, or session start-up, exceeded its time limit 507 MeshStoreFull the mesh store holds GYRE_MESH_STORE_MAX_FILES meshes or would exceed GYRE_MESH_STORE_MAX_BYTES Session command errors carry the exception class name as type ; classes other than the three listed are answered with 500. An exception raised while a new session builds its case is a 422 with the class name as type ; its hint, like that of a start-up Timeout , points to POST /cases/validate , since there is no session to inspect. Case errors are reported by POST /cases/validate and, identically, when a job or session is submitted. The error object inside a failed job record is {\"type\", \"message\"} without a hint; the traceback of a worker exception is the error.txt artefact.\ngyre.client.GyreClient wraps the job, session, mesh, preset, validation and info endpoints ( submit , job , wait , run , result , events , artifact , cancel , session , meshes , upload_mesh , mesh , delete_mesh , presets , validate , info ) and raises GyreError carrying the status and the structured error. submit(..., adapt={\"output\": \"cd\", \"tolerance\": 1e-4}) and run(..., adapt=...) send an order selection; upload_mesh(path, name=None) stores a local file under its own name unless name is given. Pipelines, metrics and schemas are reached with plain HTTP. See MDO integration for RemoteDiscipline .","title":"Service API","url":"guide/service.html"},{"audience":[],"chunks":[{"anchor":"asking-for-derivatives","end":662,"heading":"Asking for derivatives","level":2,"start":0},{"anchor":"inputs-and-outputs-you-can-differentiate","end":1189,"heading":"Inputs and outputs you can differentiate","level":2,"start":663},{"anchor":"what-comes-back","end":2154,"heading":"What comes back","level":3,"start":1190},{"anchor":"error-estimates","end":3546,"heading":"Error estimates","level":2,"start":2155},{"anchor":"cost","end":3805,"heading":"Cost","level":3,"start":3547},{"anchor":"letting-the-error-estimate-choose-the-order","end":5767,"heading":"Letting the error estimate choose the order","level":2,"start":3806},{"anchor":"what-comes-back-2","end":7824,"heading":"What comes back","level":2,"start":5768},{"anchor":"how-it-proceeds","end":9325,"heading":"How it proceeds","level":3,"start":7825},{"anchor":"when-it-helps-in-a-design-loop","end":9655,"heading":"When it helps in a design loop","level":3,"start":9326}],"headings":["Asking for derivatives","Inputs and outputs you can differentiate","What comes back","Error estimates","Cost","Letting the error estimate choose the order","What comes back","How it proceeds","When it helps in a design loop"],"keywords":["gradients","adjoint","totals","x_surface","error estimate"],"level":2,"max_level":3,"path":"guide/gradients.md","section":"guide","section_title":"User Guide","summary":"Request total derivatives of any scalar output with respect to flow parameters and surface shape from the CLI, a job, a session or Python. One adjoint solve per output; an adjoint-based error estimate costs one more linear solve.","text":"gyre run naca0012_euler --gradients --of cl,cd --wrt alpha,mach curl -s http://HOST:8600/api/v1/jobs -H 'content-type: application/json' -d '{ \"case\": \"naca0012_euler\", \"gradients\": {\"of\": [\"cl\", \"cd\"], \"wrt\": [\"alpha\", \"mach\", \"x_surface\"]} }' with GyreClient ( \"http://HOST:8600\" ) . session ( \"naca0012_euler\" ) as flow : flow . evaluate ( alpha_deg = 2.0 ) g = flow . gradients ( of = [ \"cl\" , \"cd\" ], wrt = [ \"alpha_deg\" , \"mach\" , \"x_surface\" ]) tot = flow . totals ( of = ( \"cl\" , \"cd\" ), wrt = ( \"alpha\" , \"mach\" , \"x_surface\" )) tot [ \"derivatives\" ][ \"cl\" ][ \"alpha\" ] # float tot [ \"derivatives\" ][ \"cd\" ][ \"x_surface\" ] # array (n_surface_nodes, dim)\nOutputs ( of ) : any scalar functional — cl , cd , cm , cx , cy , heat (viscous), entropy_error , kinetic_energy , total_entropy . The load field f_surface is differentiated through adjoint_product / tangent_product with a seed. Inputs ( wrt ) : mach , alpha (radians), re , gamma , pr , any boundary datum present in the case, and x_surface . The CLI, jobs and sessions also accept alpha_deg and return that derivative per degree. Mode : adjoint (one solve per output), direct (one per scalar input; no x_surface ), or auto .\n{ \"derivatives\" : { \"cl\" : { \"alpha\" : 7.7383 , \"mach\" : 0.23041 }, \"cd\" : { \"alpha\" : 0.011770 , \"mach\" : 7.908e-05 }}, \"info\" : { \"mode\" : \"adjoint\" , \"linear_solves\" : [{ \"output\" : \"cl\" , \"iterations\" : 51 , \"reduction\" : 7.09e-11 , \"converged\" : true }, { \"output\" : \"cd\" , \"iterations\" : 50 , \"reduction\" : 7.18e-11 , \"converged\" : true }], \"wall_time\" : 127.6 , \"residual_norm\" : 7.06e-13 }} This is the gradients section of the result of the unmodified preset naca0012_euler ( --gradients --of cl,cd --wrt alpha,mach ), recorded at revision 5ee1cdc on the CPU and shortened to the digits shown. alpha derivatives are per radian. The wall_time of that record includes compiling the adjoint kernels on a busy machine; it says nothing about the cost of a warm adjoint solve. residual_norm is the nonlinear residual of the state the derivative was taken at: check it when gradients look noisy. The outputs themselves are defined under Outputs and functionals .\ngyre run naca0012_euler --error cd prints, for each requested output, the value, the estimate of its change at the next polynomial degree, the corrected value and the estimated error. The per-element indicators are written to error_estimate in result.json when -o <dir> is given, and are returned in full by jobs, sessions and run_case() . See Output error estimation for the method and its verification . The estimate is defined for the built-in scalar outputs of the case (force and moment coefficients, heat , the volume diagnostics). A user-defined functional or the load field f_surface raises KeyError , because the output has to be rebuilt in the enriched space. For spanwise-extruded cases value , corrected and error are per unit span, like the coefficients themselves. Derivatives and error estimates belong to a converged state. When the solve of a job, of gyre run or of run_case() does not converge, the result has converged: false , the outputs of the iterate the solve ended at, and no gradients or error_estimate . skipped names each requested section that is missing, with the reason: it is in the result of run_case() , in the job result and the job’s result_summary , and for gyre run in the result.json written with -o <dir> . The JSON summary that gyre run prints has no skipped entry; there the gradients and error_estimate sections are absent and the exit status is 2.\nAn error estimate builds the p+1 discretization and solves one transposed linear system there. The first call compiles the enriched hierarchy (the dominant cost on small cases); the linear solve itself is comparable to one adjoint solve at the higher degree.\nInstead of guessing the polynomial degree, state the accuracy an output needs and let the solver raise the degree until its estimated error is at or below that tolerance. The selection starts at the case’s discretization.order (at least 1) and tries degrees up to max_order (default 5, at most 8). The tolerance is absolute and positive. gyre run bump_euler --set discretization.order = 1 --target-error cd = 1e-3 --max-order 4 curl -s http://HOST:8600/api/v1/jobs -H 'content-type: application/json' -d '{ \"case\": \"bump_euler\", \"overrides\": {\"discretization.order\": 1}, \"adapt\": {\"output\": \"cd\", \"tolerance\": 1e-3, \"max_order\": 4} }' res = GyreClient ( \"http://HOST:8600\" ) . run ( \"bump_euler\" , overrides = { \"discretization.order\" : 1 }, adapt = { \"output\" : \"cd\" , \"tolerance\" : 1e-3 , \"max_order\" : 4 }) res [ \"adaptation\" ][ \"met\" ], res [ \"adaptation\" ][ \"order\" ] - id : solve uses : solve with : case : bump_euler overrides : { discretization.order : 1 } adapt : { output : cd , tolerance : 1.0e-3 , max_order : 4 } - id : accurate uses : assert needs : [ solve ] with : checks : - { value : \"${stages.solve.outputs.adaptation.met}\" , equals : true } from gyre.adapt import adaptive_order from gyre.cases import preset from gyre.runner import run_case spec = preset ( \"bump_euler\" , ** { \"discretization.order\" : 1 }) res = adaptive_order ( spec , output = \"cd\" , tolerance = 1e-3 , max_order = 4 ) res [ \"met\" ], res [ \"order\" ], res [ \"value\" ], res [ \"error\" ] for level in res [ \"levels\" ]: # one record per degree tried; no value/error if not converged print ( level [ \"order\" ], level [ \"degrees_of_freedom\" ], level . get ( \"value\" ), level . get ( \"error\" )) flow = res [ \"case\" ] . discipline # the final, solved discipline (gradients, loads, …) # or as part of the complete result record: result = run_case ( spec , adapt = { \"output\" : \"cd\" , \"tolerance\" : 1e-3 , \"max_order\" : 4 }) result [ \"adaptation\" ] # the same entries without \"case\"\nEvery interface reports the selection as adaptation : output , tolerance , met , order , value , corrected , error and levels , one record per degree tried. Everything else in the result — outputs, gradients, error estimates, fields, the case summary — is that of the final degree. gyre run prints one line per degree ( order P: N DOF, cd = …, estimated error … (target …) , or order P: N DOF, did not converge (reason) ; -q suppresses them). Its JSON summary lists per level order , degrees_of_freedom , converged and error ; the complete level records are in result.json with -o <dir> . The exit status is 0 when the solve converged and the tolerance was met, 2 when it did not converge, and 3 when it converged but the tolerance was not met at --max-order . A request that cannot start (the output is not a scalar output of the case, --max-order is below the case’s order or above 8, the case has order 0) ends with a message and exit status 1 before any flow is solved. --max-order without --target-error is an error. Jobs validate adapt when the request arrives: unknown keys, a tolerance that is not positive or a max_order outside 1 to 8 are a 422 ValidationError ; a case of order 0 or a max_order below discretization.order is a 422 InvalidCase . An adapt.output that is not a scalar output of the case is a 422 UnknownName that lists the scalar outputs; nothing is queued. The job’s event log shows every degree in turn (a case event, the iteration events of its solve and one adapt event with the level record), result_summary.adaptation carries the selection without levels , and adapt is part of the result-cache key. Pipeline solve stages put adaptation into the stage outputs. The stage fails when the solve does not converge, but not when the tolerance was not met: gate on adaptation.met as in the example if the pipeline needs the accuracy. adaptive_order also returns case , the solved Case of the final degree. With run_case , adapt cannot be combined with case= , and keys other than output , tolerance and max_order raise ValueError .\nFor each degree, starting at the case’s own: solve, estimate the error of the chosen output relative to the next degree, and stop when the magnitude of the estimate is at or below the tolerance, when max_order is reached, or when a degree does not converge. From the second degree on, the solve starts from the interpolated solution of the previous degree, directly in the Newton regime. If that fails, the same degree is tried again from the interpolated solution with the full pseudo-transient continuation (skipped when the interpolated state is not admissible, for example a negative pressure next to a shock), and then from the free stream; only then does the degree count as failed. met is true only for a finite estimate whose adjoint solve converged. When met is false, order , value , corrected and error are those of the last degree that converged; if the starting degree itself did not converge they are null and the result reports converged: false like any failed solve. An explicit multigrid schedule in solver.levels belongs to the degree the case was written for; at another degree p it becomes p followed by the listed degrees below p . This is uniform enrichment on a fixed mesh. It controls the discretization error of one analysis and nothing else: the per-element indicators of the estimate are not used, the mesh is not adapted, and no input of the case is changed. The cost is that of every degree tried, each with its own compilation and one error estimate at the degree above.\nRun it once at the baseline design to pick the degree the objective actually needs, and again when the design has moved far (for example, when a shock appears). Between those points, keep the degree fixed: changing the discretization changes the discrete objective, and an optimizer should not see that as a change of the design.","title":"Gradients and error estimates","url":"guide/gradients.html"},{"audience":["MDO integrators"],"chunks":[{"anchor":"","end":228,"heading":"","level":2,"start":0},{"anchor":"the-contract","end":2441,"heading":"The contract","level":2,"start":229},{"anchor":"three-ways-to-connect","end":4670,"heading":"Three ways to connect","level":2,"start":2442},{"anchor":"units-and-conventions","end":6553,"heading":"Units and conventions","level":2,"start":4671},{"anchor":"reduced-space-versus-full-space-coupling","end":7508,"heading":"Reduced-space versus full-space coupling","level":3,"start":6554},{"anchor":"recipe-a-coupled-adjoint-in-reduced-space","end":8348,"heading":"Recipe: a coupled adjoint in reduced space","level":3,"start":7509},{"anchor":"geometry-parameterisation-is-yours","end":8956,"heading":"Geometry parameterisation is yours","level":3,"start":8349},{"anchor":"operational-guidance-for-design-loops","end":11785,"heading":"Operational guidance for design loops","level":3,"start":8957},{"anchor":"what-exact-means-here","end":12313,"heading":"What 'exact' means here","level":4,"start":11786}],"headings":["The contract","Three ways to connect","Units and conventions","Reduced-space versus full-space coupling","Recipe: a coupled adjoint in reduced space","Geometry parameterisation is yours","Operational guidance for design loops","What 'exact' means here"],"keywords":["MDO","OpenMDAO","MPhys","discipline","coupled adjoint","loads","displacements","session"],"level":2,"max_level":4,"path":"guide/mdo-integration.md","section":"guide","section_title":"User Guide","summary":"Gyre presents itself as an implicit discipline — inputs, a state, a residual, outputs — with exact matrix-free derivative products. This page maps that contract onto the concepts MDO frameworks use and shows the three ways to connect.","text":"Gyre does not optimize. It supplies what an optimizer’s framework needs from the aerodynamics discipline and leaves problem formulation, design variables, geometry parameterisation and multidisciplinary solvers to the framework.\nFramework concept Gyre inputs flow parameters mach , alpha (rad), beta (rad, 3-D), re , with the turbulence model chi_inf and prt , … and the field x_surface : displacements of the wall geometry nodes, shape (n_surface_nodes, dim) state the DG solution \\mathbf U , shape disc.state_shape . In reduced-space use it stays inside Gyre; at residual level the framework owns it ( set_state , or state= on every call below) residual (apply nonlinear) apply_nonlinear(inputs=None, state=None) : the weak-form residual \\mathbf R_w(\\mathbf U, \\text{inputs}) at the discipline’s point or at the one named outputs outputs(names, state=None) : scalars cl , cd , cm , cx , cy , heat , entropy_error , … (in 3-D also cz , cs , cmx , cmy , cmz ) and the field f_surface : consistent nodal loads on the same surface nodes solve nonlinear solve() , or solve_nonlinear(inputs) to set inputs and solve — warm-started from the last converged state; a solve that fails is reported in the returned history and does not replace that state ( revert_inputs() goes back to the point it belongs to) linearize linearize(state=None, inputs=None) apply linear (fwd / rev) apply_linear(\"fwd\" \\| \"rev\", …) , which is jvp(d_inputs, d_state) / vjp(residual_bar, outputs_bar) : partial products with respect to state and inputs solve linear (fwd / rev) solve_linear(rhs, mode=\"fwd\" \\| \"rev\") ; a solve that misses its tolerance raises LinearSolveError (or is flagged, with on_failure=\"flag\" ) totals of scalar outputs totals(of, wrt, mode=\"auto\" \\| \"adjoint\" \\| \"direct\") reduced products for coupling tangent_product(d_inputs) / adjoint_product(seeds) save and restore a point checkpoint() / restore(ckpt) : inputs and state, as arrays without pickle Every residual-level call takes state= and inputs= : without them it acts at the discipline’s own point, with them at the point the caller names, without changing the discipline. A framework-owned Newton or coupled solver can therefore evaluate, linearise and solve about its own iterate. The operations are checked against each other in the discipline contract study. describe() returns this interface — names, shapes, current values — as data, so adapters can be generated rather than written.\n1. In-process (Python). Construct a case and use its discipline directly: from gyre.cases import Case , preset flow = Case ( preset ( \"naca0012_euler\" )) . discipline flow . set_inputs ( alpha = 0.04 , mach = 0.55 ) flow . solve () out = flow . outputs (( \"cl\" , \"cd\" , \"f_surface\" )) grad = flow . totals ( of = ( \"cl\" , \"cd\" ), wrt = ( \"alpha\" , \"x_surface\" ))[ \"derivatives\" ] 2. Remote session (HTTP). The framework runs on another machine; a warm solver process lives in a Gyre deployment: from gyre.client import GyreClient with GyreClient ( \"http://gyre-host:8600\" ) . session ( \"naca0012_euler\" ) as flow : out = flow . evaluate ( alpha_deg = 2.5 )[ \"outputs\" ] grad = flow . gradients ( of = [ \"cl\" ], wrt = [ \"alpha_deg\" , \"x_surface\" ]) back = flow . adjoint ({ \"f_surface\" : seed }, wrt = [ \"x_surface\" , \"alpha\" ]) The calls a driver makes on such a session — at fixed design points, without an optimizer — are shown by examples/remote_session.py , described under A remote session called at fixed design points . RemoteDiscipline exposes the reduced-space contract under its own names: evaluate(**inputs) (set inputs, solve, outputs), gradients(of, wrt) (the derivatives of totals ), adjoint(seeds, wrt) and tangent(d_inputs, outputs) (the reverse and forward total products), residual(**inputs) (norms of the residual of the session’s state), revert() after a failed evaluation, get_state / set_state , checkpoint() / restore() (a checkpoint also rebuilds a session that was closed or reaped: GyreClient.restore_session ), plus describe , surface_coordinates , error_estimate and distributions . The partial products jvp , vjp and solve_linear are available in-process only. Fields travel as JSON arrays. 3. Framework adapter. gyre.integrations.openmdao wraps a discipline for OpenMDAO in two forms (see the OpenMDAO example ): GyreFlowComp , a matrix-free ExplicitComponent (reduced space: the framework sees inputs and outputs), and GyreFlowGroup , an ImplicitComponent whose output is the flow state plus a functionals component (residual level: the framework sees the state, the residual and its linear solves). Both turn a flow solve that fails and a linear solve that stalls into an OpenMDAO analysis error.\nNon-dimensional free stream: \\rho_\\infty = |\\mathbf u_\\infty| = T_\\infty = 1 ; coefficients are 2F/S_\\text{ref} with S_\\text{ref} = 1 in 2-D. Spanwise-extruded 3-D cases use $S_\\text{ref} = $ span, so their coefficients, and the error estimates of those coefficients, are per unit span. reference.area , reference.length and reference.moment_center of the case set other reference quantities, which a case on a mesh file usually needs. alpha is in radians. Through the service alpha_deg is accepted as a session input (hence by RemoteDiscipline.evaluate ) and in the wrt of session gradients and adjoint and of job gradients ; the derivative is then per degree. The in-process FlowDiscipline knows only alpha . x_surface is a displacement relative to the baseline surface nodes returned by surface_coordinates() ; f_surface is a force per node (not a traction), already integrated, and sums to the total force: 2 * f_surface.sum(0) == (cx, cy) . Loads of components. reference.components of the case names sets of wall groups, for example {wing: [wing], tail: [htail, vtail]} on a mesh with those groups, and the discipline then returns every coefficient per component as well ( cd_wing , cmy_tail , …) from the same solve; each can be an objective or a constraint with its own adjoint. reference.groups instead changes what the plain coefficients cover (default: all walls). f_surface and x_surface always cover every wall, so the identity above holds for the coefficients of all walls only. Derivatives with respect to x_surface have the same shape as x_surface . On a periodic mesh (spanwise-extruded cases) the surface nodes on the two periodic planes are one topological node. Both rows remain inputs, but the pair moves by the mean of the two prescribed displacements, and the derivative with respect to x_surface is shared equally between them. Give both rows the same value.\nTwo styles of multidisciplinary derivative computation exist, and Gyre supports both. Reduced space (the flow state is eliminated inside the discipline). The framework sees an explicit map inputs → outputs with total derivative products. Each reverse product costs one flow-adjoint solve; this is what adjoint_product , tangent_product , the session endpoint /adjoint and the OpenMDAO adapter provide. It is the simplest correct integration and works across a network boundary because the state never leaves the solver. Full space (the framework owns the coupled linear solve). The framework treats the flow as an implicit component and needs the residual, partial products of the residual and outputs, and a preconditioned linear solve, all about a state it owns: apply_nonlinear , apply_linear ( jvp , vjp ) and solve_linear with state= and inputs= . This is the MPhys-style contract; it is available in-process, and GyreFlowGroup is its OpenMDAO form.\nFor a structure \\mathbf d = \\mathbf C\\,\\mathbf f coupled to the flow and an objective J : tot = flow . totals ( of = ( \"cl\" ,), wrt = ( \"alpha\" , \"x_surface\" ))[ \"derivatives\" ][ \"cl\" ] lam = tot [ \"x_surface\" ] . ravel () for _ in range ( max_iter ): # transposed block Gauss–Seidel seed = ( C . T @ lam ) . reshape ( shape ) back = flow . adjoint_product ({ \"f_surface\" : seed }, wrt = ( \"x_surface\" ,))[ \"derivatives\" ] lam = tot [ \"x_surface\" ] . ravel () + back [ \"x_surface\" ] . ravel () cross = flow . adjoint_product ({ \"f_surface\" : ( C . T @ lam ) . reshape ( shape )}, wrt = ( \"alpha\" ,)) dJ_dalpha = tot [ \"alpha\" ] + cross [ \"derivatives\" ][ \"alpha\" ] The aero-structural example runs exactly this and verifies it against finite differences of the re-converged coupled system. The derivation is in Adjoints and sensitivities .\nGyre differentiates with respect to surface-node positions because that is the one interface every geometry engine can target. If your design variables \\boldsymbol\\theta move the surface by \\mathbf d(\\boldsymbol\\theta) — free-form deformation, CAD parameters, Hicks–Henne bumps — then \\frac{\\mathrm dJ}{\\mathrm d\\boldsymbol\\theta} = \\left(\\frac{\\partial \\mathbf d}{\\partial\\boldsymbol\\theta}\\right)^{\\!\\mathsf T}\\frac{\\mathrm dJ}{\\mathrm d\\mathbf d}, one matrix–vector product with the field Gyre returns. Volume-mesh deformation is handled inside Gyre and is already part of \\mathrm dJ/\\mathrm d\\mathbf d .\nUse sessions. The first evaluation of a mesh/order compiles kernels (tens of seconds); later evaluations reuse them and warm-start from the previous flow. Converge tightly when you need gradients. The adjoint is exact for the converged discrete problem; a loosely converged flow gives a gradient of something slightly different. Presets converge ten orders of magnitude by default. One adjoint per output. Requesting of=[\"cl\",\"cd\",\"cm\"] costs three transposed solves; request only what the optimizer uses, or seed a linear combination with adjoint_product . On wall-resolved meshes, precondition the linear solves along the wall-normal lines. Every output of every design point costs one transposed solve to a tight tolerance, and solver.smoother: line is made for such solves ( how , what it gained on the flat plate ). The flow solve itself need not get faster with it, and no study measures how a design loop divides its time between flow solves and transposed solves: that depends on the number of outputs and on the step between design points. The smoother belongs to the case, so a session keeps it for every evaluation. Check the mesh for the objective, not in general. error_estimate tells you whether a change in the objective between two designs exceeds its discretization error. Failures are data. A non-converged evaluation returns converged: false with the reason and the outputs and residual of the iterate it ended at. The failed iterate is not accepted: the state stays the last converged solution and the next evaluation warm-starts from it. In Python, flow.U is unchanged, flow.history describes the failed attempt and flow.failed_state holds its last iterate. Repeating the same evaluation repeats the same warm start, so take a smaller step, or in Python start elsewhere with flow.solve(U0=...) . No derivatives of a failed point. After a solve that did not converge the kept state belongs to the previous inputs, so totals , tangent_product , adjoint_product , jvp , vjp , solve_linear and linearize raise RuntimeError until a solve converges (or the caller declares a state with set_state ). A session answers gradients , adjoint , error_estimate , distributions and the field request with 422 RuntimeError in the same situation, and GET /sessions/{id}/state reports converged: false . outputs() raises as well, because the kept state belongs to the previous inputs: name a state to get numbers anyway ( outputs(state=flow.failed_state) is the failed iterate), or go back to the last converged point with revert_inputs() ( revert() on a session). Choose the degree once, then keep it. adapt on a job, --target-error on the command line or gyre.adapt.adaptive_order select the polynomial degree from an error tolerance on one output ( Gradients and error estimates ). A session keeps the degree of its case.\nAll partial derivatives are obtained by automatic differentiation of the discrete operators, so there is no approximation in the Jacobian, the boundary conditions, the metric terms or the mesh deformation. Total derivatives are exact up to the tolerance of the linear solve (default relative 10^{-10} ) and the convergence of the nonlinear solve. They are derivatives of the discrete outputs — the function the optimizer actually evaluates — and therefore consistent with line searches and convergence checks at any resolution.","title":"Integrating with an MDO framework","url":"guide/mdo-integration.html"},{"audience":["AI agents","agent builders"],"chunks":[{"anchor":"connect","end":364,"heading":"Connect","level":2,"start":0},{"anchor":"options-of-the-stdio-proxy","end":1151,"heading":"Options of the stdio proxy","level":2,"start":365},{"anchor":"tools","end":4737,"heading":"Tools","level":2,"start":1152},{"anchor":"workflows-an-agent-should-follow","end":6096,"heading":"Workflows an agent should follow","level":2,"start":4738},{"anchor":"design-rules-of-the-tool-surface","end":7635,"heading":"Design rules of the tool surface","level":3,"start":6097},{"anchor":"a-session-transcript","end":9145,"heading":"A session transcript","level":3,"start":7636},{"anchor":"resources-and-prompts","end":9384,"heading":"Resources and prompts","level":3,"start":9146},{"anchor":"documentation-for-machines","end":9815,"heading":"Documentation for machines","level":4,"start":9385}],"headings":["Connect","Options of the stdio proxy","Tools","Workflows an agent should follow","Design rules of the tool surface","A session transcript","Resources and prompts","Documentation for machines"],"keywords":["MCP","Model Context Protocol","agents","tools","Claude","upload_mesh","order selection"],"level":2,"max_level":4,"path":"guide/agents.md","section":"guide","section_title":"User Guide","summary":"Gyre exposes its capabilities to AI agents through the Model Context Protocol. An agent can discover cases, upload meshes, run analyses, let the solver choose the polynomial degree for a target accuracy, obtain exact gradients and error estimates, and read this documentation — through tools designed to be self-correcting.","text":"The service mounts an MCP endpoint (streamable HTTP, stateless) at /mcp : claude mcp add --transport http gyre http://HOST:8600/mcp For clients that speak stdio only, a proxy forwards to a deployment: claude mcp add gyre -- python -m gyre.agent --service http://HOST:8600 If the deployment sets GYRE_API_TOKEN , pass it as a bearer token (HTTP) or --token (proxy).\npython -m gyre.agent is a second entry point, separate from the gyre program (it is not in the command-line reference ). Option Default Meaning --service URL the environment variable GYRE_SERVICE_URL , else http://127.0.0.1:8600 the deployment the proxy forwards to --token TOKEN the environment variable GYRE_API_TOKEN , else none bearer token sent with every request to the deployment --transport stdio\\|streamable-http stdio how the proxy itself talks to its MCP client. stdio is what a client that starts the proxy as a subprocess expects; streamable-http makes the proxy an HTTP MCP server of its own, on the MCP SDK’s default address The proxy holds no state and no solver: every tool call becomes a request to the deployment’s REST API, with a timeout of 900 seconds per request.\ntool purpose gyre_info Identify this Gyre deployment: version, source commit, device, endpoints, uptime. list_presets List built-in cases (name, description, size estimate) under presets . Start here. get_case_schema JSON Schema of a case specification (mesh, flow, discretization, solver). validate_case Dry-run a case without solving: resolved spec, element/DOF estimate, chosen device. submit_job Queue a steady flow analysis and return the job record. get_job Job state (queued/running/succeeded/failed/cancelled), progress and result summary. get_job_result Result of a finished job. get_job_events Progress events of a job (nonlinear iterations etc.) starting at sequence since . list_jobs Recent jobs under jobs , newest first; optionally filter by state. cancel_job Cancel a queued or running job. get_job_artifact Read a file a job produced. open_session Open a warm analysis session (a dedicated solver process) for repeated evaluations. session_evaluate Set inputs (e.g. {“alpha_deg”: 3.0, “mach”: 0.55}), converge the flow, return outputs. session_gradients Exact total derivatives d(of)/d(wrt) at the session’s current converged state. session_error_estimate Adjoint-weighted-residual discretization-error estimate for outputs of . session_adjoint Reverse (adjoint) total product at the session’s converged state: the coupled-adjoint building block. session_surface Surface nodes of the session’s case: coordinates (one row per node, the nodes that the input x_surface displaces and on which f_surface acts), the current displacement and nodes . Arrays are summarised unless full_arrays . session_state Current inputs of a session, whether a flow state exists ( solved ), whether the last evaluation converged ( converged ) and the residual norm of the kept state. get_session A session’s case, device, counters and interface (inputs with shapes and current values, outputs); commands lists what session_command accepts when the solver process reports it. session_command Run any command of the session’s solver process (generic pass-through). list_sessions Open sessions (under sessions ) with their case, device, call counts and idle time. close_session Close a session and free its solver process. list_meshes Meshes in this deployment’s mesh store, under meshes : name, size, element count, geometry order, boundary groups (faces per group), bounding box, sha256. upload_mesh Store a mesh file so that cases can use it; returns its summary. inspect_mesh Summary of a stored mesh: bytes, sha256, dim, elements, geometry_order, nodes, boundaries (faces per group), bounding_box and quality numbers. delete_mesh Remove a mesh from the store (jobs still queued on it will fail). list_pipelines Declarative pipelines of this deployment, under pipelines . run_pipeline Start a pipeline run (a DAG of cached stages: solves, sweeps, gates, reports). get_pipeline_run Status and progress of a pipeline run. list_pipeline_runs Recent pipeline runs under runs , newest first (id, pipeline, status, times). cancel_pipeline_run Cancel an active pipeline run (the engine and the stage it is running are terminated); returns the run’s status, cancelled once its processes have ended. Stages that had completed stay in the stage cache. A finished run is returned as is. get_pipeline_artifact Files a stage of a pipeline run produced. search_docs Full-text search of the Gyre documentation served with this deployment. read_doc Read a documentation page as Markdown (math in LaTeX). path comes from search_docs (or use “llms.txt” for the site index). Long pages are paged with offset .\nExplore. list_presets → validate_case (size, device, warnings) → get_case_schema when a field name is needed. One-shot analysis. submit_job with wait_seconds (short jobs finish within the call) → otherwise get_job with wait_seconds until state is succeeded → get_job_result . Design loop or sweep. open_session once → repeat session_evaluate and session_gradients → close_session . Sessions keep compiled kernels and warm-start each solve. Your own geometry. upload_mesh (a Gmsh .msh file of quadrilaterals or hexahedra with a named physical group on every boundary, or a Gyre .npz file) → read boundaries in the returned summary → validate_case with mesh: {generator: \"file\", params: {path: <name>}} and one condition per group in discretization.boundaries → submit_job or open_session . list_meshes , inspect_mesh and delete_mesh manage the store. See Mesh files . Accuracy on demand. submit_job with adapt_output and adapt_tolerance (and optionally adapt_max_order , default 5) raises the polynomial degree from the case’s discretization.order until the estimated error of that output meets the tolerance. get_job_result returns the adaptation section by default; read met before relying on the value. Trust but verify. session_error_estimate for the discretization error of an output; search_docs / read_doc for methods, limits and verification evidence.\nOne code path. Every tool calls the REST API; an agent gets exactly the behaviour a program gets. Structured, stable results. JSON with fixed keys. Arrays longer than a couple of dozen entries are replaced by {\"_array\": true, \"shape\", \"min\", \"max\", \"l2_norm\", \"head\"} unless full_arrays is set — shape sensitivities and histories would otherwise flood the context. Errors that teach. A failure relayed from the REST API returns {\"ok\": false, \"status\", \"error\": {\"type\", \"message\", \"hint\"}} ; the hint names the valid alternatives or the next call to make. The documentation tools report their own failures in the same form without status ( read_doc omits the hint for an invalid path). Refusals before a request. A mesh name that is not a bare file name ending in .msh or .npz returns {\"ok\": false, \"status\": 422, \"error\": {\"type\": \"InvalidMeshName\", …}} without a request to the service. upload_mesh takes content as text ( encoding=\"text\" , the default) or base64 ( encoding=\"base64\" , needed for .npz ); invalid base64 or another encoding returns InvalidEncoding . adapt_output and adapt_tolerance must be given together; one without the other returns a ValidationError . Bounded waits. Long work is asynchronous; waits are capped at 300 seconds per call, so an agent stays responsive and can report progress with get_job_events . Self-description. The server’s instructions summarise conventions (non-dimensional units, radians, input and output names); validate_case and the session interface report sizes, shapes and current values.\n→ open_session(case=\"naca0012_euler\") ← {\"id\": \"ses-…\", \"interface\": {\"inputs\": {\"mach\": …, \"alpha\": …, \"x_surface\": {\"shape\": [128, 2]}}, \"outputs\": {\"cl\": …, \"cd\": …, \"f_surface\": {\"shape\": [128, 2]}}}} → session_evaluate(session_id, inputs={\"alpha_deg\": 2.0}) ← {\"converged\": true, \"reason\": \"residual tolerance reached\", \"nonlinear_iterations\": 16, \"outputs\": {\"cl\": 0.278767, \"cd\": 0.000949, …}, …} → session_gradients(session_id, of=[\"cl\"], wrt=[\"alpha_deg\", \"x_surface\"]) ← {\"derivatives\": {\"cl\": {\"alpha_deg\": 0.135059, \"x_surface\": {\"_array\": true, \"shape\": [128, 2], \"l2_norm\": 61.73, …}}}, \"info\": {\"mode\": \"adjoint\", \"linear_solves\": [{\"output\": \"cl\", \"iterations\": 51, …}], …}} → session_evaluate(session_id, inputs={\"alpha_deg\": 2.5}) # warm start ← {\"converged\": true, \"nonlinear_iterations\": 6, \"outputs\": {\"cl\": 0.345916, \"cd\": 0.001074, …}, …} → close_session(session_id) ← {\"id\": \"ses-…\", \"closed\": true} Recorded from a service running revision 5ee1cdc on the CPU; numbers are shortened and … marks omitted keys. alpha_deg derivatives are per degree: 0.135059 per degree is the 7.7383 per radian that a job with wrt: [\"alpha\"] returns for the same case. The warm-started second evaluation needed 6 nonlinear iterations instead of 16. The recording is not kept in the repository: the coefficients and gradients were reproduced by an independent command-line run of the same case, the iteration counts were not ( what stands behind the replies ). The agent example shows a complete exchange.\nResources: gyre://presets and gyre://schema/case . Prompts: analyze_case (run, check convergence, report outputs with error estimates and provenance) and gradient_check (verify an adjoint gradient against finite differences in a session).\nThe site publishes llms.txt (an index of pages with summaries), the Markdown source of every page next to its HTML, and a search index. search_docs ranks pages by term matches in title, headings, summary and body; read_doc returns Markdown with LaTeX math, paged by character offset. An agent can therefore ground a statement about, say, adjoint consistency in the page that defines it and in the verification study that tests it.","title":"Agent interface (MCP)","url":"guide/agents.html"},{"audience":[],"chunks":[{"anchor":"","end":1149,"heading":"","level":2,"start":0},{"anchor":"runs-through-the-service","end":2303,"heading":"Runs through the service","level":3,"start":1150},{"anchor":"a-complete-example","end":3133,"heading":"A complete example","level":2,"start":2304},{"anchor":"vocabulary","end":5337,"heading":"Vocabulary","level":2,"start":3134},{"anchor":"stage-inputs-the-with-keys-of-every-stage-type","end":8947,"heading":"Stage inputs: the `with:` keys of every stage type","level":2,"start":5338},{"anchor":"what-each-stage-returns","end":10581,"heading":"What each stage returns","level":3,"start":8948},{"anchor":"records","end":11211,"heading":"Records","level":3,"start":10582}],"headings":["Runs through the service","A complete example","Vocabulary","Stage inputs: the `with:` keys of every stage type","What each stage returns","Records"],"keywords":["pipeline","YAML","sweep","gates","cache"],"level":2,"max_level":3,"path":"guide/pipelines.md","section":"guide","section_title":"User Guide","summary":"Chain analyses into reproducible workflows with a small YAML vocabulary. Stages are cached by content, so re-running a pipeline recomputes only what changed.","text":"gyre pipeline run pipelines/airfoil-characterisation.yaml gyre pipeline run pipelines/airfoil-characterisation.yaml --force # ignore the cache gyre pipeline run pipelines/airfoil-characterisation.yaml --param NAME = VALUE --record FILE gyre pipeline run pipelines/airfoil-characterisation.yaml --param-json 'NAME=JSON' --param overrides a pipeline parameter: the value is read as JSON, otherwise as text, and a number or boolean given for a parameter whose default is text stays text. --param-json (also repeatable) takes a value that must be valid JSON and uses it exactly as given, whatever the declared default is; it wins over --param for the same name. --record also writes this run’s record to FILE . Pipelines can also be started through the service ( POST /api/v1/pipelines/{name}/runs ) and by agents ( run_pipeline , get_pipeline_run ). The definitions of a deployment are listed by GET /api/v1/pipelines , each with runnable : a definition with shell stages cannot be started through the service or MCP unless the operator sets GYRE_PIPELINES_ALLOW_SHELL=1 . Runs of the same pipeline in the same work directory execute one after another.\nThe service lists the *.yaml files of the directory named by GYRE_PIPELINES_DIR (default: pipelines in the service’s working directory). The container image ships the analysis definitions in /app/pipelines ; the CI pipeline is not part of the image. A run started through the service takes a stage’s case as a preset name or a CaseSpec object. The check is made on every stage’s case after the run’s parameters are substituted, whatever the parameter is called: a string that is not a preset name, or a case that cannot be determined before the run (a reference to another stage’s output), is refused with 404 UnknownPreset . The stages of such a run also refuse to read a case file ( GYRE_PIPELINE_CASE_FILES=0 in their environment). Paths work with gyre pipeline run on the host. A run leads its own process group and is terminated, with its stage processes, when the service shuts down. A definition that is not runnable answers 403 PipelineNotRunnable , with the reason as the hint . A string parameter stays a string ( \"123\" is not turned into a number). The record of a run is the record of its status document ( GET /api/v1/pipelines/runs/{id} ).\nname : airfoil-characterisation params : case : naca0012_euler stages : - id : baseline uses : solve with : case : \"${params.case}\" gradients : { of : [ cl , cd ], wrt : [ alpha , mach ]} error_estimate : [ cd ] - id : polar uses : sweep with : case : \"${params.case}\" parameter : alpha_deg values : [ 0.0 , 1.0 , 2.0 , 3.0 , 4.0 ] outputs : [ cl , cd , cm ] gradients_wrt : [ alpha ] - id : gates uses : assert with : checks : - { name : lift at 2 deg , value : \"${stages.baseline.outputs.outputs.cl}\" , min : 0.25 , max : 0.32 } - { name : polar converged , value : \"${stages.polar.outputs.all_converged}\" , equals : true } - id : summary uses : report needs : [ gates ] with : title : NACA 0012 characterisation values : cl : \"${stages.baseline.outputs.outputs.cl}\" dcl_dalpha : \"${stages.baseline.outputs.gradients.cl.alpha}\"\nKey Meaning params pipeline-level values, referenced as ${params.name} stages[].id unique name; outputs are referenced as ${stages.<id>.outputs.<path>} stages[].uses solve , sweep , order_study , verify , docs , shell , assert , report stages[].with inputs of the stage stages[].needs explicit dependencies (references add implicit ones) stages[].device auto , cpu or gpu for compute stages stages[].cache false to always run A reference that is the whole value keeps its type (number, boolean, list); inside a longer string it is formatted as text. An assert stage takes a non-empty checks list. A check is {value, name} (the name is optional) with at least one of the criteria min , max , abs_max and equals . Unknown keys, a check without a criterion and a missing or empty checks list are rejected when the pipeline is loaded, before any stage runs. min , max and abs_max compare numbers: a value that is not a number ( null from a diverged solve, a string, a list) or a criterion that is not a number fails the check, and the check result says why in reason . equals compares values of any type. A failed gate ends the stage with PipelineError: gate failed: […] . A solve stage takes case , overrides , gradients , error_estimate , require_converged (default true: the stage fails if the solve does not converge) and adapt : - id : accurate uses : solve with : case : bump_euler overrides : { discretization.order : 1 } adapt : { output : cd , tolerance : 1.0e-4 , max_order : 4 } - id : accuracy-gate uses : assert with : checks : - { name : tolerance met , value : \"${stages.accurate.outputs.adaptation.met}\" , equals : true } adapt selects the polynomial degree by the estimated error of output ( max_order defaults to 5). Unknown keys, a missing output or tolerance and a value that is not a mapping are errors before the solve. The stage does not fail when the solve converged but the tolerance was not met at max_order ; adaptation.met is then false, which is what the gate above tests. met is true only for a finite estimate whose adjoint solve converged, and the selection ends at the first degree that does not converge; the level records in adaptation.levels are described under Service .\nKeys marked required have no default; a stage without them fails when it runs. case is a preset name, the path of a specification file, or an inline specification (through the service: a preset name or an inline specification only). overrides is a mapping of dotted paths, as with gyre run --set . solve — one analysis (the files of gyre run -o are written to the stage directory). Key Type Default Meaning case name, path or mapping required the case to solve overrides mapping {} changes to the case gradients {of: [...], wrt: [...]} none total derivatives of scalar outputs; wrt accepts alpha_deg error_estimate list of output names none adjoint-based error estimates require_converged boolean true fail the stage if the solve does not converge adapt {output, tolerance, max_order} none order selection, see below; max_order defaults to 5 sweep — one warm-started solve per value, in one solver process. Key Type Default Meaning case name, path or mapping required the case overrides mapping {} changes to the case parameter input name required the swept input: a flow input of the discipline ( mach , alpha , …) or alpha_deg values list of numbers required the values, in the order they are solved outputs list of output names [cl, cd, cm] outputs recorded per row; may include f_surface gradients_wrt list of input names none adds d<output>/d<input> columns for the scalar outputs order_study — the same case at several polynomial degrees, each solved from scratch. Key Type Default Meaning case name, path or mapping required the case orders list of integers required polynomial degrees; each replaces discretization.order overrides mapping {} changes to the case (applied before the degree) error_estimate list of output names none adds <output>_corrected and <output>_error_estimate to each row verify — the verification suite. Key Type Default Meaning suite quick or full quick the level of the studies output directory the stage directory where the study records and index.json are written only list of study ids all studies run a subset strict boolean true fail the stage if a study of this invocation fails docs — build the documentation site. Key Type Default Meaning output directory site where the site is written verification directory var/verification the study records the pages render shell — a command on the host (not startable through the service unless the operator sets GYRE_PIPELINES_ALLOW_SHELL=1 ). Key Type Default Meaning run string required the command line, run by the shell; a non-zero exit code fails the stage cwd directory the directory gyre pipeline run was started in working directory of the command timeout number, seconds none the command and everything it started are stopped, and the stage fails, when it runs longer assert — a gate. Key Type Default Meaning checks non-empty list required each {value, name?, min?, max?, abs_max?, equals?} with at least one criterion; rules below report — a Markdown and JSON summary ( report.md , report.json in the stage directory). Key Type Default Meaning title string Pipeline report heading of the report values mapping name → value {} the rows, usually references to outputs of other stages - id : degrees uses : order_study with : { case : bump_euler , orders : [ 1 , 2 , 3 ], error_estimate : [ cd ]} - id : studies uses : verify with : { suite : quick , only : [ free_stream , entropy_conservation ], output : var/verification } - id : site uses : docs needs : [ studies ] with : { output : site , verification : var/verification } - id : lint uses : shell with : { run : \".venv/bin/ruff check src tests\" , timeout : 600 }\nStage Outputs solve converged , outputs , residual_norm , timing , case , optional gradients , error_estimate ; with adapt also adaptation ( output , tolerance , met , order , value , corrected , error , levels ), and case describes the final degree sweep parameter , rows (one per value: parameter, converged , iterations , outputs, optional slopes d<output>/d<input> ), all_converged order_study rows per polynomial degree, with corrected outputs if error estimates were requested verify studies (per-study pass/fail and wall time) and failed , both of this invocation; stale_failed (ids of failing results that earlier invocations left in the output directory) and skipped (studies not run, each {id, reason} ). Pass or fail refers to this invocation only shell return code, duration, log tail assert the evaluated checks (fails the pipeline if any check fails) report path of the Markdown report and the collected values In a sweep , outputs may name the field output f_surface and gradients_wrt the shape input x_surface ; the rows then hold the loads and the shape derivatives ( d<output>/dx_surface ) as lists. Slopes exist for scalar outputs only. Unknown names in outputs or gradients_wrt are rejected before the first solve, as is gradients_wrt together with outputs that contain no scalar output. gradients_wrt accepts alpha_deg (column d<output>/dalpha_deg , the derivative per degree), like the solve stage’s gradients.wrt . A point that does not converge reports the outputs of its failed iterate ( null where they are not finite) and null in every derivative column; derivatives are computed for converged points only.\nEach run writes var/pipelines/<name>/pipeline.json (status of every stage, keys, timings) and, per stage, stage.json , a log (compute and shell stages), and whatever artefacts the stage produces (a solve stage leaves the same files as gyre run -o ). -o selects another directory than var/pipelines . pipeline.json is replaced by the next run of the pipeline; use --record FILE for a per-run copy. A stage whose execution fails writes stage.json with status: failed . var/pipelines/<name>/ also contains a .lock file, which serialises the runs of the pipeline in that directory. See Pipelines and provenance for the caching rules.","title":"Pipelines","url":"guide/pipelines.html"},{"audience":["operators"],"chunks":[{"anchor":"native-deployment","end":784,"heading":"Native deployment","level":2,"start":0},{"anchor":"what-the-smoke-test-covers","end":1612,"heading":"What the smoke test covers","level":2,"start":785},{"anchor":"reaching-it-over-the-tailnet","end":1980,"heading":"Reaching it over the tailnet","level":2,"start":1613},{"anchor":"container","end":2070,"heading":"Container","level":2,"start":1981},{"anchor":"build-arguments-and-environment","end":3818,"heading":"Build arguments and environment","level":3,"start":2071},{"anchor":"systemd","end":3900,"heading":"systemd","level":2,"start":3819},{"anchor":"configuration","end":3901,"heading":"Configuration","level":2,"start":3901},{"anchor":"environment-variables","end":6710,"heading":"Environment variables","level":3,"start":3902},{"anchor":"operations","end":8706,"heading":"Operations","level":3,"start":6711}],"headings":["Native deployment","What the smoke test covers","Reaching it over the tailnet","Container","Build arguments and environment","systemd","Configuration","Environment variables","Operations"],"keywords":["deployment","tailnet","docker","systemd","GPU","token"],"level":2,"max_level":3,"path":"guide/deployment.md","section":"guide","section_title":"User Guide","summary":"A deployment is one process tree serving the API, the agent endpoint and the documentation for its own revision, bound to all interfaces so that tailnet machines can reach it. Deploy natively, as a container, or under systemd.","text":"python3 tools/deploy.py up --name dev --port 8600 # make deploy python3 tools/deploy.py status python3 tools/deploy.py down --name dev up performs, in order: refuse a name that is still running and a port that is already in use ( make serve , the systemd unit and other deployments default to 8600 too) → record the revision (commit, dirty flag) → build the documentation into the deployment directory → start gyre serve --host 0.0.0.0 → wait for health and verify through /api/v1/info that the answer comes from the process just started → run the smoke test against the tailnet address (loopback if the host has none) → write var/deploy/<name>/deployment.json , which records whether the smoke test passed. No record is written if the service has exited by the end of the smoke test.\nHealth; deployment identity; the documentation home page, search index and llms.txt ; presets; a structured error; pipeline definitions (at least one runnable, none unloadable); the MCP tool list; a real solver job with an adjoint gradient, submitted with use_cache: false (an answer from the result cache fails the check); metrics. It uses only the Python standard library and can be run from any tailnet machine: python3 deploy/smoke.py http://HOST:8600 GYRE_API_TOKEN = ... python3 deploy/smoke.py http://HOST:8600 # token-protected deployment A deployment protected by GYRE_API_TOKEN is tested with the same variable set for the script, or with --token . up passes its environment on to the service (without GYRE_DEVICE , which the service sets per worker) and to the smoke test, so setting the variable for up covers both.\nThe service binds 0.0.0.0 , never loopback only. Every URL below works from another tailnet device with the host’s tailnet name or address: URL http://HOST:8600/ documentation built from the deployed revision http://HOST:8600/api/v1/info which revision and deployment is answering http://HOST:8600/api/docs interactive API explorer http://HOST:8600/mcp agent endpoint\nGYRE_COMMIT = $( git rev-parse HEAD ) docker compose -f deploy/compose.yaml up -d --build\nName Purpose GYRE_CONSTRAINTS the tested dependency set to install: constraints/tested-cpu.txt (default; JAX 0.11.2, which also runs on CPUs without AVX2) or constraints/tested-cuda12.txt for NVIDIA GPUs GYRE_EXTRAS extras of the package to install: service,docs (default), service,docs,gpu with the CUDA set GYRE_COMMIT revision stamped into the image and its documentation GYRE_PORT published host port (default 8600, bound on all interfaces) GYRE_API_TOKEN require a bearer token GYRE_SERVICE_GPU auto , on or off (default off in the container) GYRE_PIPELINES_ALLOW_SHELL 1 lets the service start pipelines with shell stages (see Configuration ). The compose file forwards it from the environment docker compose is started in ( GYRE_PIPELINES_ALLOW_SHELL=1 docker compose … up ); nothing needs to be added to the file The image builds the documentation at image-build time from the verification results present in the build context, so a container and its documentation cannot disagree. Job data and the JIT cache live on the /data volume. make image ( deploy/image_smoke.sh ) builds the image and smoke-tests a throw-away container of it on port 8602. On a machine whose Docker build sandbox cannot resolve host names (the package download fails with “Temporary failure in name resolution” although docker run works), set GYRE_BUILD_NETWORK=host for that script, or pass --network host to docker build . The image also ships the analysis pipeline definitions of its revision ( pipelines/ without ci.yaml ) and examples/ under /app ; the OpenMDAO example needs the mdo extra, which the image does not install. To serve your own pipeline definitions, bind-mount a directory over /app/pipelines ( deploy/compose.yaml carries a commented example).\ncp deploy/gyre.service ~/.config/systemd/user/ systemctl --user enable --now gyre\n\nVariable Default Effect GYRE_DEPLOYMENT dev name reported by /api/v1/info and shown in the documentation footer GYRE_API_TOKEN unset bearer token for /api/v1 and /mcp ( /api/v1/health and /api/v1/info stay open) GYRE_SERVICE_GPU auto whether workers may use a GPU GYRE_MAX_SESSIONS 4 concurrent sessions GYRE_SESSION_IDLE_S 1800 idle time before a session is closed GYRE_PIPELINES_DIR pipelines directory of the pipeline definitions the service lists, relative to its working directory ( /app/pipelines in the image) GYRE_MESH_MAX_BYTES 67108864 (64 MiB) largest mesh file PUT /api/v1/meshes/{name} accepts (413 MeshTooLarge above it); the arrays of an .npz upload may take up to four times this value uncompressed and must also stay within GYRE_MESH_MAX_EXPANDED_BYTES (422 InvalidMesh ). Read at each request. A value that is not a positive integer is replaced by the default GYRE_MESH_STORE_MAX_FILES 256 number of meshes the mesh store holds (507 MeshStoreFull ). A value that is not a positive integer is replaced by the default GYRE_MESH_STORE_MAX_BYTES 4294967296 (4 GiB) total size of the mesh store in bytes (507 MeshStoreFull ). A value that is not a positive integer is replaced by the default GYRE_MESH_DIR , GYRE_MESH_RESTRICT set by the service while it runs, the service sets GYRE_MESH_DIR to <data>/meshes (absolute path) and GYRE_MESH_RESTRICT=1 in its own environment, whatever they were before; its job workers, session processes and pipeline runs inherit them and open mesh files only by bare name from that directory. Outside a service, GYRE_MESH_DIR is a directory in which the CLI and Python look for a relative mesh path that does not exist as given GYRE_MESH_MAX_EXPANDED_BYTES 1073741824 (1 GiB) largest uncompressed size of the arrays of an .npz mesh the reader accepts, in any process, the service included (an upload above it is a 422 InvalidMesh ). A value that is not a positive integer is an error: every read of an .npz mesh fails with GYRE_MESH_MAX_EXPANDED_BYTES must be a positive integer, got '…' GYRE_PIPELINES_ALLOW_SHELL unset 1 lets the REST API and the MCP tools start pipelines that contain shell stages (for example ci ). Without it such definitions are listed with runnable: false and a reason , and starting one returns 403 PipelineNotRunnable ; they stay runnable on the host with gyre pipeline run GYRE_DEVICE auto device of a solver process (the service sets it per worker) GYRE_JIT_CACHE ~/.cache/gyre/jit persistent compilation cache directory, or off GYRE_LOG_LEVEL info uvicorn log level GYRE_SERVICE_URL http://127.0.0.1:8600 read by the stdio MCP proxy only ( python -m gyre.agent ): the deployment it forwards to when --service is not given ( agent interface ) gyre serve --workers N sets the number of concurrent job workers (GPU jobs are serialised).\nIdentity. GET /api/v1/info returns version, commit, process id, start time and deployment name; the documentation footer shows the same. up accepts the answer only if it reports the deployment’s name and a start time not earlier than the launch; otherwise it stops the process it started. It passes the name and start time on to the smoke test, which stops at once if another process answers. Monitoring. Scrape GET /api/v1/metrics . An unexpected fault is answered 500 InternalError with an incident id; the service log (logger gyre.service ) has the exception under the same id. Mesh uploads. Without GYRE_API_TOKEN , anyone who reaches the service can upload meshes up to the limits above. Uploads are validated before they are stored and are addressed by name only; a request cannot make the service read or write a path outside <data>/meshes . Data. The data directory ( gyre serve --data , default var/service ) holds jobs/<id>/ (request, status, events, artefacts), cache.json , meshes/ (the mesh store : one file per uploaded mesh) and pipelines/ ( runs/<id>/ per run and work/ , the stage directories and their cache). Deleting a job directory is safe. Mesh files can be copied into meshes/ or removed by hand; a file the reader rejects is listed with an error . The result cache is keyed by the case specification, the requested gradients, error estimates and order selection, the contents of a mesh file the case names, and a fingerprint of the code (version, revision and the package sources). An upgrade can therefore keep its data directory: results computed by other code are not reused, and old job records stay readable but are never served as cache hits. <data>/cache.json is only an index of that cache; the job records are the source of truth. Upgrades. Deploy the new revision under a new name and port, smoke-test it, then stop the old one: tools/deploy.py keeps deployments side by side. up refuses a port that is already in use, so the new revision needs its own --port .","title":"Deployment","url":"guide/deployment.html"},{"audience":[],"chunks":[{"anchor":"cases-and-the-discipline","end":784,"heading":"Cases and the discipline","level":2,"start":0},{"anchor":"flowdiscipline-at-a-glance","end":1772,"heading":"`FlowDiscipline` at a glance","level":2,"start":785},{"anchor":"after-a-solve-that-does-not-converge","end":3057,"heading":"After a solve that does not converge","level":2,"start":1773},{"anchor":"meshes-from-files","end":3855,"heading":"Meshes from files","level":2,"start":3058},{"anchor":"gyre-meshio-and-the-case-fields-for-mesh-files","end":6371,"heading":"`gyre.meshio` and the case fields for mesh files","level":2,"start":3856},{"anchor":"order-selection","end":6637,"heading":"Order selection","level":2,"start":6372},{"anchor":"adaptive-order-and-run-case-adapt","end":7977,"heading":"`adaptive_order` and `run_case(adapt=...)`","level":2,"start":6638},{"anchor":"one-level-down-the-discretization","end":9894,"heading":"One level down: the discretization","level":2,"start":7978},{"anchor":"useful-entry-points","end":10770,"heading":"Useful entry points","level":3,"start":9895},{"anchor":"device-and-precision","end":10940,"heading":"Device and precision","level":3,"start":10771}],"headings":["Cases and the discipline","`FlowDiscipline` at a glance","After a solve that does not converge","Meshes from files","`gyre.meshio` and the case fields for mesh files","Order selection","`adaptive_order` and `run_case(adapt=...)`","One level down: the discretization","Useful entry points","Device and precision"],"keywords":["Python","API","FlowDiscipline","Discretization","library"],"level":2,"max_level":3,"path":"guide/python-api.md","section":"guide","section_title":"User Guide","summary":"Use Gyre as a library — build a case, drive the discipline, access the discretization and its differentiable kernels directly.","text":"from gyre.cases import Case , preset , load_spec case = Case ( preset ( \"naca0012_euler\" , ** { \"flow.mach\" : 0.55 })) # or load_spec(\"case.yaml\") flow = case . discipline # FlowDiscipline hist = flow . solve () # converged? hist.converged, hist.steps out = flow . outputs () # scalars and \"f_surface\" flow . set_inputs ( alpha = 0.05 ) # radians; keeps the state for a warm start flow . solve () tot = flow . totals ( of = ( \"cl\" , \"cd\" ), wrt = ( \"alpha\" , \"mach\" , \"x_surface\" )) examples/quickstart.py is a runnable version of this block that also prints an error estimate ( .venv/bin/python examples/quickstart.py ). The in-process discipline takes the angle of attack as alpha in radians and knows no alpha_deg ; what each output means is defined under Outputs and functionals .\nMethod Returns describe() inputs, outputs, shapes, discretization summary set_inputs(**inputs) — (flow parameters and/or x_surface ) solve(U0=None, callback=None) SolveHistory ; on failure U keeps the last converged state, and history and failed_state describe the failed attempt set_state(U, converged=True) — (restore a saved solution, or declare an initial guess with converged=False ) outputs(names=None, state=None) dict of floats / arrays, at the current state or at state , e.g. failed_state totals(of, wrt, mode) {\"derivatives\": {of: {wrt: value}}, \"info\": …} tangent_product(d_inputs, outputs) forward total product adjoint_product(seeds, wrt) reverse total product jvp(d_inputs, d_state) , vjp(residual_bar, outputs_bar) partial products solve_linear(rhs, mode) forward / transposed linear solve surface_coordinates() baseline surface nodes residual_norm() current nonlinear residual require_solution(what) — (raises RuntimeError unless the state belongs to the current inputs)\nsolve() reports failure in the history ( hist.converged , hist.reason ); it does not raise. A failed solve never replaces a converged state: flow.U remains the last converged solution, which belongs to the inputs it was converged for, and is the warm start of the next solve. flow.failed_state holds the last iterate of the failed attempt ( None after a converged solve). While nothing has converged yet, the iterate also becomes flow.U . Until a solve converges again, linearize , solve_linear , jvp , vjp , tangent_product , adjoint_product and totals raise RuntimeError . Ways on: hist = flow . solve () if not hist . converged : flow . outputs (( \"cl\" ,), state = flow . failed_state ) # where the failed attempt ended flow . solve ( U0 = flow . failed_state ) # continue from there, or flow . set_inputs ( alpha = 0.03 ); flow . solve () # take a smaller step from the kept state A plain repeat of flow.solve() restarts from the same kept state and fails the same way. flow.set_state(flow.failed_state, converged=False) makes the failed iterate the state and lifts the refusal: the caller then takes responsibility for differentiating it. gyre.error.estimate_error reads the state directly and does not make this check; call flow.require_solution(\"the error estimate\") before it.\nfrom gyre import meshio from gyre.cases import Case , CaseSpec info = meshio . inspect_mesh ( \"examples/meshes/cylinder_channel_p3.msh\" ) # reads and validates info [ \"boundaries\" ] # faces per boundary group: cylinder, inflow, outflow, walls spec = CaseSpec . model_validate ({ \"name\" : \"cylinder_channel\" , \"mesh\" : { \"generator\" : \"file\" , \"params\" : { \"path\" : \"examples/meshes/cylinder_channel_p3.msh\" , \"scale\" : 1.0 }}, \"flow\" : { \"mach\" : 0.2 , \"reynolds\" : 40.0 }, \"discretization\" : { \"order\" : 2 , \"boundaries\" : { \"cylinder\" : \"noslip_adiabatic\" , \"inflow\" : \"farfield\" , \"outflow\" : \"farfield\" , \"walls\" : \"slip_wall\" }}, \"reference\" : { \"area\" : 1.0 , \"length\" : 1.0 , \"moment_center\" : [ 0.0 , 0.0 ]}, }) case = Case ( spec ) # raises ValueError if a boundary group has no condition\nObject Purpose meshio.read_mesh(path, scale=1.0) read .msh or .npz (by extension) into a Mesh ; reading validates meshio.read_gmsh(path, scale=1.0) Gmsh MSH 2.2 or 4.1, ASCII: quadrilaterals of degree 1 to 5, hexahedra of degree 1 to 4 meshio.write_gmsh(mesh, path, volume_name=\"fluid\") write MSH 2.2 (ASCII), periodic node pairs included meshio.save_mesh(mesh, path) , meshio.load_mesh(path, scale=1.0) Gyre NPZ format: the arrays of a Mesh , at any geometry degree meshio.inspect_mesh(path, scale=1.0, lines=False) summary dict: dim , elements , geometry_order , nodes , boundaries , quality numbers, bounding_box , sha256 , bytes ; with lines=True also lines , as gyre mesh info prints it meshio.line_summary(mesh) the lines of the line smoother on a mesh and the anisotropy of its cells meshio.validate_mesh(mesh) quality summary; raises for inverted elements and unmatched, untagged faces meshio.file_digest(path) SHA-256 of the file’s contents (hex); the value CaseSpec.digest() uses for a mesh file meshio.resolve_mesh_path(name) the file a case names in mesh.params.path meshio.MeshFormatError a ValueError : the file is not a mesh Gyre can use; the message says why CaseSpec.mesh_file() path of the mesh file of a generator: file case, None for generators CaseSpec.reference area (default 1 in 2-D, the span of an extruded generator mesh in 3-D), length (default 1) and moment_center (default [0.25, 0, 0] ) of the force and moment coefficients; groups , the wall groups they are integrated over (default: all walls), and components , named sets of wall groups whose coefficients are additional outputs cd_<name> , … ( FlowDiscipline(..., groups=(...), components={...}) when a discipline is built by hand) mesh.params of a file case accepts path and scale only. A relative path that does not exist as given is looked up in the directory named by the environment variable GYRE_MESH_DIR ; with GYRE_MESH_RESTRICT=1 , which the service sets for itself and its workers, only bare file names inside that directory are accepted. CaseSpec.digest() hashes the contents of the file, not its name. Reading an .npz file ( load_mesh , read_mesh , inspect_mesh ) raises MeshFormatError when its arrays take more than 1 GiB uncompressed; the environment variable GYRE_MESH_MAX_EXPANDED_BYTES (a positive integer, in bytes) sets another limit, see Meshes . The boundary groups of a mesh file have no default conditions: discretization.boundaries must name a condition for every group and no group the mesh does not have.\nfrom gyre.adapt import adaptive_order from gyre.runner import run_case res = adaptive_order ( spec , output = \"cd\" , tolerance = 1e-3 , max_order = 5 , min_order = None ) result = run_case ( spec , adapt = { \"output\" : \"cd\" , \"tolerance\" : 1e-3 , \"max_order\" : 5 })\nadaptive_order(spec, output, tolerance, max_order=5, min_order=None, callback=None) raises the polynomial degree from spec.discretization.order (or min_order ) until the estimated error of output is at or below tolerance , and returns output , tolerance , met , order , value , corrected , error , levels and case , the solved Case of the final degree. The arguments are checked before any flow is solved: a tolerance that is not a positive finite number, a degree that is not an integer, a starting degree below 1, a max_order below the starting degree or above 8 raise ValueError ; an output that is not a scalar output of the case raises KeyError . Per degree the callback receives a case event, the iteration events of its solve (each with order ), {\"event\": \"adapt_retry\", ...} when the degree is tried again from another start, and one {\"event\": \"adapt\", ...} record that closes it. run_case(spec, adapt={...}) does the same inside the complete result record: the result is that of the final degree and gains adaptation (the entries above without case ); timing[\"solve\"] is the wall time of the whole selection and timing[\"setup\"] is 0. max_order may be omitted (5). Keys other than output , tolerance and max_order , a missing output or tolerance , or adapt together with case= raise ValueError . See Gradients and error estimates .\nimport jax , jax.numpy as jnp from gyre import meshgen , physics as ph from gyre.dg import Discretization mesh = meshgen . airfoil_omesh ( 16 , 4 , pg = 3 ) # 64 curved elements disc = Discretization ( mesh , p = 2 , phys = ph . Compressible ( dim = 2 ), bcs = { \"wall\" : \"slip_wall\" , \"farfield\" : \"farfield\" }) th = ph . flow_params ( mach = 0.5 , alpha_deg = 2.0 ) geom = disc . geometry () # differentiable in the node coordinates U = disc . uniform_state ( th ) Rw = disc . residual ( U , geom , th , weak = True ) # (4, 64, 3, 3) # Derivatives are matrix-free JAX calls on the same kernel. # Forward mode: one Jacobian-vector product for one direction dU. Jv = jax . jvp ( lambda V : disc . residual ( V , geom , th , weak = True ), ( U ,), ( jnp . ones_like ( U ),))[ 1 ] # Reverse mode: the gradient of ONE scalar with respect to ALL mesh nodes in one sweep. dJ_dX = jax . grad ( lambda X : disc . residual ( U , disc . geometry ( X ), th , weak = True ) . sum ())( disc . X0 ) This block is executed by tests/test_docs_content.py . Run on its own on a four-core CPU it took 40 s with a peak memory of 0.7 GB while the machine was heavily loaded (load average above 30); a single earlier run took 15 s, under a load that was not recorded. Most of the time is compilation. Warning Forward or reverse mode: count the inputs Forward mode ( jax.jvp , jax.jacfwd ) costs one tangent per input direction; reverse mode ( jax.grad , jax.vjp ) one sweep per output . A scalar that depends on every mesh node has thousands of inputs and one output: use jax.grad . jax.jacfwd on the same function would carry one tangent per node coordinate through the residual at once — on the 256-element mesh of the naca0012_euler preset that is 8448 tangents, which exhausted the memory of a 16 GB host. Never form a full Jacobian with respect to the state or the mesh; the solver, the adjoint and FlowDiscipline use products only.\nObject Purpose gyre.physics.flow_params build the differentiable parameter dict Discretization.jitted(\"residual_weak\" | \"geometry\" | \"dt_local\") compiled kernels with operator tables as runtime arguments gyre.implicit.SteadySolver , SolverOptions nonlinear solver gyre.implicit.Hierarchy p-multigrid: setup , solve(mode=\"fwd\" | \"adj\") gyre.functionals.SurfaceOps surface forces, loads, distributions gyre.deform.Deformer surface displacement → volume nodes gyre.error.estimate_error adjoint-weighted-residual estimates gyre.adapt.adaptive_order , prolong_state output-driven order selection; interpolation of a state to another degree gyre.meshio mesh files: Gmsh reader and writer, NPZ format, inspection gyre.mms.manufactured_source forcing for manufactured solutions gyre.runner.run_case the end-to-end record used by the service gyre.io.write_vtu , save_checkpoint output\nImport gyre before using JAX: it enables double precision and applies GYRE_DEVICE . To pin a process to the CPU set GYRE_DEVICE=cpu in the environment before the import.","title":"Python API","url":"guide/python-api.html"},{"audience":["CFD practitioners","MDO integrators","operators"],"chunks":[{"anchor":"","end":333,"heading":"","level":1,"start":0},{"anchor":"the-reasons-a-solve-can-end-with","end":1007,"heading":"The reasons a solve can end with","level":1,"start":334},{"anchor":"what-to-change-reason-by-reason","end":5081,"heading":"What to change, reason by reason","level":2,"start":1008},{"anchor":"after-a-failed-solve-what-you-still-have","end":5759,"heading":"After a failed solve: what you still have","level":2,"start":5082},{"anchor":"how-the-iteration-decides","end":6593,"heading":"How the iteration decides","level":3,"start":5760},{"anchor":"first-run-and-installation","end":8083,"heading":"First run and installation","level":1,"start":6594},{"anchor":"service-symptoms","end":9136,"heading":"Service symptoms","level":1,"start":8084}],"headings":["The reasons a solve can end with","What to change, reason by reason","After a failed solve: what you still have","How the iteration decides","First run and installation","Service symptoms"],"keywords":["troubleshooting","convergence","reason","cfl0","max_steps","not finite","stalled","collapsed","AVX2","JIT"],"level":1,"max_level":3,"path":"guide/troubleshooting.md","section":"guide","section_title":"User Guide","summary":"What to do when a solve does not converge — every reason the solver can report, what it means and which control to change — and the first-run and service symptoms most often met.","text":"A solve that does not converge is reported, not raised: the result carries converged: false and a reason . gyre run exits with status 2; its printed summary shows converged: false but not the reason, which is in result.json (run with -o DIR ). A job result, a session evaluation and SolveHistory.reason in Python carry the same text.\nreason Converged? Meaning residual tolerance reached yes the residual fell below tol_rel × the reference residual residual stagnated at round-off floor yes the residual stopped halving, at or below 10^{-8} of the reference residual maximum number of steps reached no max_steps nonlinear steps were used residual stalled above tolerance no the residual stopped halving above 10^{-8} of the reference residual residual is not finite no the residual of the initial state is nan or inf pseudo-time step collapsed (no admissible update) no no step was accepted even at a pseudo-time CFL below 10^{-6} These six strings are the complete list ( gyre.implicit.SteadySolver.solve ).\nThe controls are the fields of solver and discretization in the case ; on the command line --set solver.max_steps=400 . The remedies are listed in the order to try them. They follow from how the iteration works (see the block below); they are not the outcome of a study of failures. maximum number of steps reached . Look at the residual history first ( gyre run prints it; result.json and the job events hold it). The residual is still falling and the CFL still growing: the solve needed more steps. Raise solver.max_steps (default 150). The CFL stays small or keeps being cut: the start is too far from the solution. Start from a converged neighbour instead (a session or flow.solve() after set_inputs warm-starts from the last converged state), approach the target condition in smaller increments, or lower solver.cfl0 (default 5). The Krylov iteration count per step sits at its limit ( krylov_dim × restarts , default 40 × 2) and the residual falls slowly: the linear systems are not being solved. Raise solver.krylov_dim , or use solver.smoother: gmres ; keep solver.levels: auto . On a wall-resolved mesh (cells much longer than thick at the wall) use solver.smoother: line , which solves exactly along the lines of thin cells ( measured on the turbulent flat plate ). residual stalled above tolerance . The residual did not halve in six consecutive steps although the CFL had reached 10^4 . Usual causes: an unsteady or marginally stable flow that has no steady state at this resolution (a wake that wants to shed, a shock that moves between nodes), or linear solves that do not reach lin_tol . Check the last entries of the history: linear_reduction well above lin_tol means the linear solver is the limit — raise solver.krylov_dim or solver.restarts . A flow with a shock needs discretization.shock_capturing: true ; a larger discretization.av_coef (default 1) smooths the shock further at the price of accuracy. A coarser tolerance is a decision about the result, not a fix: with tol_rel above the plateau the solve “converges” to the same state. Compare the plateau with the accuracy you need before doing that. residual is not finite . This reason describes the starting point , not the iteration: the residual of the initial state — the uniform free stream, or the warm-start state evaluated with the new inputs — is nan or inf . The line search never accepts an update with a non-finite residual, so the reason cannot appear after the first step, and no solver control ( cfl0 , cfl_growth , max_steps ) changes it. Check the inputs: the Mach number, gamma , and the boundary data in flow.extra must give positive density and pressure. Check the mesh: gyre mesh info reports inverted or badly scaled elements ( messages and remedies ). Check the start state: a state restored from a checkpoint or set by hand, or the iterate of a failed solve passed on as a start, may itself be inadmissible. pseudo-time step collapsed (no admissible update) . Every update, at every step length, either produced negative density or pressure, gave a non-finite residual, or failed to reduce the residual, and cutting the pseudo-time step did not help. This is how a transient that leaves the admissible range ends (the scheme does not enforce positivity); if the step limit is reached first, the same cause is reported as maximum number of steps reached . Start from a converged neighbouring condition, or lower solver.cfl0 and solver.cfl_growth (default 10) so that the transient is followed more closely. Under-resolved strong gradients: enable discretization.shock_capturing , lower the polynomial degree ( discretization.order ), or refine the mesh where the gradient is. In a design loop it usually means the last step in the design variables was too large — the kept state is still the last converged one, so take a smaller step. Converged, but residual stagnated at round-off floor . Not a failure: the residual reached the conditioning-limited floor before tol_rel . The result is as converged as double precision allows on this mesh; a smaller tol_rel cannot be met.\nThe outputs in the result are those of the iterate the failed attempt ended at; they are not a solution. The discipline keeps the last converged state and warm-starts the next solve from it. Repeating the same request repeats the same failure. Gradients and error estimates are not computed for a failed point: the result record ( result.json , a job result) and the summary printed by gyre run list them under skipped , next to the reason the solve ended with; sessions answer 422, and the Python calls raise RuntimeError ( Python API , MDO integration ). Order selection ( --target-error ) ends at the first degree that does not converge and reports the last degree that did.\nEach step solves a linearised pseudo-time problem to the relative tolerance lin_tol and tries the update with step lengths 1, ½, …, down to 1/32. A step length is accepted if the new state has positive density and pressure, a finite residual, and a smaller pseudo-transient residual. After an accepted full step the CFL grows by the factor by which the residual fell, limited to between 1.5 and cfl_growth ; after a shortened step it stays (a step shortened to a quarter or less multiplies it by solver.cfl_shrink , which is 1 by default); if no step length is accepted the CFL is divided by ten and the step is repeated. Stagnation is declared after six steps without halving the best residual once the CFL is at least 10^4 . The reference residual is that of the uniform free stream, also for warm starts. See the implicit solver .\nSymptom Cause Remedy import jax aborts with “illegal instruction” the CPU has no AVX2 and a newer JAX wheel was installed make bootstrap detects this and pins jax < 0.12 ( constraints/avx-only.txt ); with pip, install with -c constraints/avx-only.txt gyre: command not found the virtual environment is not activated call .venv/bin/gyre , or source .venv/bin/activate once per shell nothing is printed for tens of seconds after the case … line kernels are being compiled; the CLI prints nothing while JAX compiles wait: see the measured times in Getting started . Later runs of the same mesh size and degree reuse the cache in ~/.cache/gyre/jit a small case is slower on the GPU than on the CPU compilation dominates and is slower for the GPU GYRE_DEVICE=cpu (set before the process starts; the choice is per process) a 3-D solve exhausts device memory while the preconditioner is assembled the element blocks of the smoother grow like n_\\text{var}^2 (p+1)^{6} per element set solver.precond_precision: single (half the memory, same answers); lower the degree or the number of elements a derivative call exhausts memory forward mode over many inputs ( jax.jacfwd with respect to all mesh nodes) builds one tangent per input use reverse mode ( jax.grad , flow.totals , adjoint_product ) for one output and many inputs ( Python API ) stale or corrupt compiled kernels are suspected the persistent cache GYRE_JIT_CACHE=off disables it for a process; GYRE_JIT_CACHE=<dir> uses another directory\nAnswer Meaning What to do 429 QueueFull the job queue is at its limit wait for jobs to finish and submit again, or raise GYRE_MAX_QUEUED_JOBS 429 TooManySessions the session limit is reached close sessions that are no longer used ( DELETE /api/v1/sessions/{id} ); the operator can raise GYRE_MAX_SESSIONS 504 Timeout a session command, or the start-up of a session, exceeded its time limit check the size of the case with POST /api/v1/cases/validate ; the first evaluation of a new size compiles kernels 410 SessionDead the solver process of the session has ended and its state with it open a new session 409 NotReady the job has not finished, or did not succeed poll GET /api/v1/jobs/{id} ; read error in the job record if its state is failed 422 with type: RuntimeError on a session command derivatives or an error estimate were requested after an evaluation that did not converge evaluate a point that converges first The complete list of error types is in the service guide ; the limits and their environment variables are in the deployment guide .","title":"Troubleshooting","url":"guide/troubleshooting.html"},{"audience":[],"chunks":[{"anchor":"","end":2796,"heading":"","level":1,"start":0}],"headings":[],"keywords":["examples"],"level":1,"max_level":1,"path":"examples/index.md","section":"examples","section_title":"Examples","summary":"Worked cases with real output: an inviscid aerofoil, a transonic flow with shocks, a separated viscous flow, a turbulent boundary layer, a sphere in three dimensions, a case on a mesh imported from Gmsh, a warm-started polar with adjoint slopes, a coupled aero-structural adjoint, OpenMDAO, and an agent session.","text":"Every figure, table and field on these pages is produced by the example studies of the verification pipeline and carries the revision that computed it. Inviscid aerofoil L1 The complete loop on one case — solve the flow over a NACA 0012, inspect the field and the surface pressure, obtain adjoint derivatives with respect to flow conditions and shape, and estimate the error of the result. Transonic aerofoil L2 A flow with shocks — NACA 0012 at Mach 0.8 — computed with sensor-based shock capturing, converged to machine precision, with adjoint derivatives taken through the shock. Laminar cylinder L1 A viscous case with separation — the steady wake of a circular cylinder at Reynolds number 40 — showing no-slip walls, skin friction and the separation point. Turbulent flat plate L1 A Reynolds-averaged case with the Spalart–Allmaras model — the zero-pressure-gradient flat plate at a Reynolds number of five million — showing how a turbulent case is specified, what its boundary layer looks like in wall units, and that its drag can be differentiated like any other output. Sphere in three dimensions L1 Flow past a sphere on an unstructured mesh of curved hexahedra, inviscid and at Reynolds number 100. The same commands, outputs and derivatives as in two dimensions, with three force and three moment coefficients and a sideslip angle. A mesh from Gmsh L1 Laminar flow past a cylinder in a channel on an unstructured mesh of cubic quadrilaterals written by Gmsh — set up from the mesh's physical groups, solved in Python and through the service, with an adjoint derivative checked against finite differences. Lift polar with adjoint slopes L2 A parameter sweep in one warm solver process, with the lift-curve slope from one adjoint solve at every point. Demonstrates warm starts and the consistency of gradients along a path. Coupled aero-structural adjoint L2 A flow coupled to an elastic skin, with the coupled derivative assembled outside Gyre from loads, displacements and reverse total products — the pattern an MDO framework uses, in sixty lines. Inside OpenMDAO L2 Wrap the flow discipline as a matrix-free OpenMDAO component, in reduced space or at residual level, and let the framework assemble total derivatives in forward or reverse mode. No optimizer involved. An agent at work L2 The Model Context Protocol exchange behind an agent task — \"characterise this aerofoil and tell me how far to trust the drag\" — shown as the raw tool calls and results. A remote session called at fixed design points L2 The calls an optimizer or MDO framework running on another machine would make on a warm session of a deployment — evaluate, gradients, adjoint and tangent products, a finite-difference cross-check, checkpoint and restore — shown at three fixed design points, with no optimizer.","title":"Examples","url":"examples/index.html"},{"audience":[],"chunks":[{"anchor":"","end":831,"heading":"","level":1,"start":0},{"anchor":"run-it","end":1872,"heading":"Run it","level":1,"start":832},{"anchor":"results","end":2243,"heading":"Results","level":1,"start":1873},{"anchor":"reading-the-table","end":3035,"heading":"Reading the table","level":2,"start":2244},{"anchor":"convergence","end":3213,"heading":"Convergence","level":2,"start":3036},{"anchor":"shape-sensitivity","end":3364,"heading":"Shape sensitivity","level":1,"start":3214},{"anchor":"what-this-is","end":3702,"heading":"What this is","level":2,"start":3365},{"anchor":"why-the-sensitivity-is-large-at-the-edges","end":4011,"heading":"Why the sensitivity is large at the edges","level":3,"start":3703}],"headings":["Run it","Results","Reading the table","Convergence","Shape sensitivity","What this is","Why the sensitivity is large at the edges"],"keywords":["NACA 0012","Euler","adjoint","shape sensitivity","error estimate","example"],"level":1,"max_level":3,"path":"examples/naca0012-euler.md","section":"examples","section_title":"Examples","summary":"The complete loop on one case — solve the flow over a NACA 0012, inspect the field and the surface pressure, obtain adjoint derivatives with respect to flow conditions and shape, and estimate the error of the result.","text":"Subsonic inviscid flow over a NACA 0012 at Mach 0.5 and 2° incidence, on a 48 × 12 curved O-mesh with cubic elements. Everything on this page is produced by the example_airfoil study of the verification pipeline. Mach number (48 × 12 elements, p = 3) Part of the domain: window x ∈ [-1, 2.5], y ∈ [-1.5, 1.5] · the 354 mesh elements that reach into it · 8×8 points each Passed Example: inviscid NACA 0012 4 of 4 checks passed · 104.5 s · quick suite NACA 0012 at Mach 0.5 and 2 degrees on a 48 x 12 curved O-mesh, p = 3. Outputs, adjoint derivatives, adjoint-based error estimates and surface distributions. converged yes expected true pass cl near compressible thin-aerofoil value 0.2827 expected 0.228 … 0.317 pass spurious inviscid drag |cd| 1.98e-04 expected < 1e-3 pass lift-curve slope per radian 8.12 expected 6.5 … 9.0 pass\ngyre run naca0012_euler \\ --set mesh.params.n_surf = 48 --set mesh.params.n_radial = 12 \\ --set mesh.params.first_cell = 0 .02 --set discretization.order = 3 \\ --gradients --of cl,cd,cm --wrt alpha,mach,x_surface --error cd,cl -o runs/naca from gyre.cases import Case , preset from gyre.error import estimate_error case = Case ( preset ( \"naca0012_euler\" , ** { \"mesh.params.n_surf\" : 48 , \"mesh.params.n_radial\" : 12 , \"mesh.params.first_cell\" : 0.02 , \"discretization.order\" : 3 })) flow = case . discipline flow . solve () out = flow . outputs (( \"cl\" , \"cd\" , \"cm\" )) grad = flow . totals ( of = ( \"cl\" , \"cd\" , \"cm\" ), wrt = ( \"alpha\" , \"mach\" , \"x_surface\" )) est = estimate_error ( case , ( \"cd\" , \"cl\" )) curl -s $API /jobs -H 'content-type: application/json' -d '{ \"case\": \"naca0012_euler\", \"overrides\": {\"mesh.params.n_surf\": 48, \"mesh.params.n_radial\": 12, \"mesh.params.first_cell\": 0.02, \"discretization.order\": 3}, \"gradients\": {\"of\": [\"cl\", \"cd\", \"cm\"], \"wrt\": [\"alpha\", \"mach\", \"x_surface\"]}, \"error_estimate\": [\"cd\", \"cl\"]}'\nOutputs, adjoint derivatives and estimated discretization error Example: inviscid NACA 0012 output value d/dα [1/rad] d/dMach estimated error cl 0.282687 8.12013 0.229219 -0.00220267 cd 1.9845e-04 0.00534302 1.7935e-04 3.3961e-05 cm -0.00266027 -0.0673649 0.00197556 — Surface pressure coefficient Example: inviscid NACA 0012 Residual history Example: inviscid NACA 0012\nThe lift coefficient is close to the compressible thin-aerofoil estimate 2\\pi\\alpha/\\sqrt{1-M^2} ; thickness adds a few percent. The exact drag of a subsonic inviscid flow is zero, so the computed drag is error. It has three sources: discretization error (which the adjoint-based estimate in the last column measures, relative to the next polynomial degree), the singular trailing edge (which enrichment in p resolves poorly) and the far-field boundary at a finite distance (a modelling error no mesh refinement removes). Comparing the estimate with the drag itself shows how much of it better resolution would recover. The derivative columns come from three adjoint solves (one per output) and include \\partial/\\partial\\alpha , \\partial/\\partial M and the sensitivity to every surface node.\nThe residual falls slowly while the pseudo-time step grows, then at Newton’s rate — the Jacobian is exact — down to round-off. The p-multigrid hierarchy for p = 3 is (3, 1, 0) .\nShape sensitivity of lift: d cl / d y of each surface node Example: inviscid NACA 0012 One adjoint solve yields the sensitivity to every surface node.\nOne adjoint solve returns \\mathrm dc_l/\\mathrm d\\mathbf x for every surface geometry node. The chart shows its vertical component along the chord: where pushing the surface up raises the lift, and by how much. A geometry parameterisation maps design variables to node displacements and contracts them with this field ( MDO integration ).\nLift responds strongly to changes of the effective camber line near the trailing edge (the Kutta condition) and to the nose shape. Individual node sensitivities also scale with the surface area a node represents, so their magnitude follows the node clustering; a smooth parameterisation integrates over this.","title":"Inviscid aerofoil","url":"examples/naca0012-euler.html"},{"audience":[],"chunks":[{"anchor":"","end":1918,"heading":"","level":2,"start":0},{"anchor":"what-to-look-for","end":2259,"heading":"What to look for","level":2,"start":1919},{"anchor":"convergence-with-a-shock","end":2660,"heading":"Convergence with a shock","level":3,"start":2260},{"anchor":"derivatives-through-the-shock","end":2956,"heading":"Derivatives through the shock","level":2,"start":2661},{"anchor":"why-this-is-the-hard-case","end":3288,"heading":"Why this is the hard case","level":3,"start":2957}],"headings":["What to look for","Convergence with a shock","Derivatives through the shock","Why this is the hard case"],"keywords":["transonic","shock capturing","NACA 0012","wave drag","adjoint","example"],"level":2,"max_level":3,"path":"examples/transonic.md","section":"examples","section_title":"Examples","summary":"A flow with shocks — NACA 0012 at Mach 0.8 — computed with sensor-based shock capturing, converged to machine precision, with adjoint derivatives taken through the shock.","text":"Mach number: supersonic pocket and shock on the upper surface Part of the domain: window x ∈ [-1, 2.5], y ∈ [-1.5, 1.5] · the 608 mesh elements that reach into it · 6×6 points each Passed Example: transonic aerofoil with shock capturing 9 of 9 checks passed · 151.8 s · quick suite NACA 0012 at Mach 0.8 and 1.25 degrees (64 x 16 curved elements, p = 2). A modal smoothness sensor switches on a Laplacian artificial viscosity in the few elements that contain the shocks; the term is part of the differentiated residual, so Newton convergence and adjoint derivatives remain exact. Grid-converged Euler values in the literature are about cl = 0.356 and cd = 0.0226 (Vassberg & Jameson 2010). converged ten orders (final / reference residual) 3.91e-12 expected <= 1e-10 pass lift coefficient 0.3505 expected 0.33 … 0.37 pass drag coefficient (wave drag) 0.02266 expected 0.020 … 0.026 pass upper-surface shock position x/c 0.6351 expected 0.55 … 0.70 pass fraction of elements with artificial viscosity 0.01367 expected < 0.05 pass dcl/dalpha: adjoint vs finite difference (rel.) 1.46e-06 expected < 1e-4 pass dcd/dalpha: adjoint vs finite difference (rel.) 9.92e-07 expected < 1e-4 pass dcl/dmach: adjoint vs finite difference (rel.) 9.94e-06 expected < 1e-4 pass dcd/dmach: adjoint vs finite difference (rel.) 3.75e-06 expected < 1e-4 pass gyre run naca0012_transonic --gradients --of cl,cd --wrt alpha,mach Outputs Example: transonic aerofoil with shock capturing cl cd cm shock x/c (upper) elements with viscosity elements Newton steps field 0.350505 0.0226564 -0.0391713 0.635144 14 1024 241 example_transonic/field.json Surface pressure coefficient Example: transonic aerofoil with shock capturing The steepest pressure rise on the upper surface, taken as the shock position, is at x/c = 0.635; artificial viscosity is active in 14 of 1024 elements. Residual history Example: transonic aerofoil with shock capturing\nThe flow accelerates to supersonic speed over the upper surface and returns to subsonic through a shock; a weaker shock sits on the lower surface. The drag is no longer discretization error: it is wave drag, the entropy produced at the shocks. Artificial viscosity is active in a handful of elements only (table), all of them at the shocks.\nThe residual first wanders while the shock finds its position — the line search shortens many steps — then falls step by step to ten orders below its reference, which the study checks. The solver is an inexact Newton iteration with a fixed linear tolerance, so that last phase is linear, not quadratic. It is possible because the sensor and the viscosity are part of the Jacobian ( shock capturing ).\nAdjoint derivatives through the shock Example: transonic aerofoil with shock capturing derivative adjoint finite difference relative difference dcl/dalpha 17.6219 17.6219 1.4558e-06 dcd/dalpha 1.31459 1.31459 9.9160e-07 dcl/dmach 4.46992 4.46988 9.9365e-06 dcd/dmach 0.688727 0.688725 3.7501e-06\nShock position moves with angle of attack and Mach number, and wave drag depends on it strongly; a gradient that ignores the dependence of the shock-capturing term on the solution is wrong exactly where a transonic optimizer needs it. The table compares the adjoint derivatives with central differences of fully re-converged flows.","title":"Transonic aerofoil","url":"examples/transonic.html"},{"audience":[],"chunks":[{"anchor":"","end":1284,"heading":"","level":1,"start":0},{"anchor":"what-to-look-for","end":1574,"heading":"What to look for","level":2,"start":1285},{"anchor":"viscous-specifics","end":2060,"heading":"Viscous specifics","level":3,"start":1575}],"headings":["What to look for","Viscous specifics"],"keywords":["cylinder","laminar","Navier-Stokes","separation","skin friction","example"],"level":1,"max_level":3,"path":"examples/cylinder-re40.md","section":"examples","section_title":"Examples","summary":"A viscous case with separation — the steady wake of a circular cylinder at Reynolds number 40 — showing no-slip walls, skin friction and the separation point.","text":"Streamwise velocity: the closed recirculation bubble behind the cylinder Part of the domain: window x ∈ [-3, 8], y ∈ [-4, 4] · the 146 mesh elements that reach into it · 8×8 points each Passed Example: laminar cylinder, Re = 40 4 of 4 checks passed · 51.5 s · quick suite Steady separated flow past a circular cylinder at Re_D = 40, Mach 0.2 (p = 3, 24 x 10 curved elements). Surface pressure and skin friction over the upper half, from the front stagnation point (0°) to the rear (180°). converged yes expected true pass drag coefficient 1.537 expected 1.45 … 1.65 pass front stagnation cp 1.153 expected 1.0 … 1.25 pass separation angle from the rear stagnation point [deg] 53.75 expected 45 … 60 pass gyre run cylinder_re40 -o runs/cylinder The preset uses a 24 × 10 curved O-mesh reaching 40 diameters, p = 3 , an adiabatic no-slip wall and a far-field boundary at Mach 0.2. Outputs Example: laminar cylinder, Re = 40 cd cl separation from rear [deg] Newton steps DOF field 1.53727 -1.7153e-14 53.7473 16 15360 example_cylinder/field.json Surface pressure coefficient Example: laminar cylinder, Re = 40 Skin-friction coefficient (signed) Example: laminar cylinder, Re = 40 The sign change marks separation; literature places it about 53° from the rear stagnation point at Re = 40.\nPressure recovers only partly on the rear half: the flow has separated. The skin friction changes sign where the flow separates. Its location is reported in the table as the angle from the rear stagnation point. The drag coefficient is compared with the literature in Physical validation .\nThe wall imposes zero velocity through the BR2 lifting and zero heat flux through the viscous boundary flux ( boundary conditions ). The numerical energy flux through the adiabatic wall is zero by construction, for any state. The validation study checks instead that the conduction the converged solution carries at the wall, which vanishes for the exact solution, decreases with the polynomial degree. The Reynolds number is a differentiable input: --gradients --of cd --wrt re,mach .","title":"Laminar cylinder","url":"examples/cylinder-re40.html"},{"audience":[],"chunks":[{"anchor":"","end":2229,"heading":"","level":1,"start":0},{"anchor":"what-to-look-for","end":3033,"heading":"What to look for","level":2,"start":2230},{"anchor":"resolution","end":3526,"heading":"Resolution","level":2,"start":3034},{"anchor":"derivatives","end":3858,"heading":"Derivatives","level":2,"start":3527},{"anchor":"solver-settings-of-the-preset","end":4825,"heading":"Solver settings of the preset","level":3,"start":3859}],"headings":["What to look for","Resolution","Derivatives","Solver settings of the preset"],"keywords":["turbulence","RANS","Spalart-Allmaras","flat plate","law of the wall","skin friction","example"],"level":1,"max_level":3,"path":"examples/turbulent-flat-plate.md","section":"examples","section_title":"Examples","summary":"A Reynolds-averaged case with the Spalart–Allmaras model — the zero-pressure-gradient flat plate at a Reynolds number of five million — showing how a turbulent case is specified, what its boundary layer looks like in wall units, and that its drag can be differentiated like any other output.","text":"Passed Example: turbulent flat plate (Spalart-Allmaras) 5 of 5 checks passed · 119.9 s · quick suite The preset flat_plate_sa: zero-pressure-gradient flat plate at M = 0.2 and Re = 5e6 per unit length with the negative Spalart-Allmaras model, 150 elements of degree 2 (6750 unknowns), solved from the uniform free stream. Skin friction along the plate against the NASA Turbulence Modeling Resource reference (CFL3D and FUN3D, 545 x 385 grid), and the boundary-layer profile at x = 0.97 in wall units with the eddy viscosity across the layer. converged from the free stream yes expected true pass cf at x = 0.97 against the TMR reference (relative difference) 0.001656 expected < 0.02 pass first solution node off the wall, y+ 4.594 expected < 10 pass logarithmic layer, 40 <= y+ <= 200: largest relative difference of u+ to ln(y+) / 0.41 + 5.0 0.0026 expected < 0.08 (a bound observed with margin on this mesh, not a property of the model) pass eddy viscosity over molecular viscosity, largest value on the profile 194.4 expected > 50 (the layer is turbulent) pass gyre run flat_plate_sa -o runs/plate The preset is the flat plate of the NASA Turbulence Modeling Resource at Mach 0.2 and \\mathit{Re} = 5\\times10^6 per unit length, on 150 elements of degree 2 whose first row is 5\\times10^{-5} high. One line of the case switches the model on: flow : mach : 0.2 reynolds : 5.0e6 turbulence : sa Outputs Example: turbulent flat plate (Spalart-Allmaras) cd (one side, reference length 1) cf at x = 0.97 difference to TMR [%] y+ of the first node largest mu_t / mu smallest nu_tilde / nu largest u+ difference in the log layer [%] largest u+ / y+ - 1 for y+ <= 3 [%] Newton steps DOF 0.00575365 0.00270103 -0.165649 4.59367 194.403 0.00109241 0.259964 — 34 6750 Skin friction along the plate Example: turbulent flat plate (Spalart-Allmaras) Velocity profile at x = 0.97 in wall units Example: turbulent flat plate (Spalart-Allmaras) u_tau from the computed skin friction; first node at y+ = 4.59. Eddy viscosity across the layer at x = 0.97 Example: turbulent flat plate (Spalart-Allmaras) nu_tilde / nu below zero at the edge of the layer is the undershoot the negative branch of the model handles; there the eddy viscosity is zero.\nSkin friction. Downstream of the leading-edge region the curve follows the reference of the resource, computed with the same model on a grid of 545 × 385 points. Law of the wall. In wall units the velocity profile leaves the sublayer line u^+ = y^+ at the first nodes and follows the logarithmic law with the model’s von Kármán constant of 0.41 further out. Where two elements meet, the chart shows both nodal values: the small jumps between them are the discontinuous approximation, not an error of the plot. Eddy viscosity. It is zero at the wall, several hundred times the molecular viscosity in the outer layer, and returns to the small free-stream value outside the boundary layer. At that edge \\tilde\\nu undershoots below zero in a few nodes, which is what the negative branch of the model is for.\nThe model is integrated to the wall, so the first element must reach into the viscous sublayer: the table gives y^+ of the first solution node off the wall. With a high-order element the requirement is on the nodes inside the element, not on the element height, which is why 10 elements across the layer are enough here. To refine: gyre run flat_plate_sa --set mesh.params.nx = 20 --set mesh.params.ny = 12 --set mesh.params.h_wall = 3e-5 gyre run flat_plate_sa --set discretization.order = 3\nThe turbulence model is part of the differentiated residual, and so is the wall distance: gyre run flat_plate_sa --gradients --of cd --wrt mach,re,chi_inf returns the total derivatives of the drag from one adjoint solve. They are verified against finite differences, including a displacement of the wall, under Turbulence closure .\nTurbulent cases start from a uniform free stream with a small pseudo-time step and a moderate growth ( cfl0: 1 , cfl_growth: 3 ), and they keep the piecewise-constant multigrid level ( levels: [2, 1, 0] ): on a wall-resolved mesh the linear solves need it ( why ). The line smoother ( solver.smoother: line ) cuts the Krylov iterations of linear solves to a tight tolerance to less than half on this mesh; the flow solve from the free stream does not get faster with it, and a single run with one adjoint solve is not where it pays ( measured ). It is meant for a case that is kept, in a session or a design loop, for many derivative evaluations at one flow state after another: solver : levels : [ 2 , 1 , 0 ] smoother : line precond_lag : 2 # assemble the blocks every second step of a flow solve gyre run prints, under the line of the case, how many lines the smoother found on the mesh, and gyre mesh info reports the same for a mesh file before anything is run.","title":"Turbulent flat plate","url":"examples/turbulent-flat-plate.html"},{"audience":[],"chunks":[{"anchor":"","end":2464,"heading":"","level":1,"start":0},{"anchor":"what-to-look-for","end":2900,"heading":"What to look for","level":2,"start":2465},{"anchor":"the-mesh","end":3565,"heading":"The mesh","level":2,"start":2901},{"anchor":"outputs-and-inputs-in-3-d","end":4075,"heading":"Outputs and inputs in 3-D","level":2,"start":3566},{"anchor":"the-inviscid-case-as-a-check-on-the-3-d-discretization","end":4333,"heading":"The inviscid case as a check on the 3-D discretization","level":3,"start":4076},{"anchor":"size-and-cost","end":4692,"heading":"Size and cost","level":3,"start":4334}],"headings":["What to look for","The mesh","Outputs and inputs in 3-D","The inviscid case as a check on the 3-D discretization","Size and cost"],"keywords":["sphere","3-D","hexahedra","cubed sphere","sideslip","side force","example"],"level":1,"max_level":3,"path":"examples/sphere.md","section":"examples","section_title":"Examples","summary":"Flow past a sphere on an unstructured mesh of curved hexahedra, inviscid and at Reynolds number 100. The same commands, outputs and derivatives as in two dimensions, with three force and three moment coefficients and a sideslip angle.","text":"Nothing in the workflow changes with the third dimension: a preset, a run, outputs, gradients. What changes is the size of the problem and the list of outputs. gyre run sphere_euler # inviscid, 216 curved hexahedra, p = 2 gyre run sphere_re100 -o runs/sphere # laminar, Re = 100, 576 hexahedra, p = 2 Passed Laminar flow past a sphere, Re = 100 (3-D) 5 of 5 checks passed · 187.8 s · quick suite Steady three-dimensional Navier–Stokes solve: sphere of diameter 1, Re = 100 on the diameter, M = 0.2, adiabatic no-slip wall, far field at 10 diameters; cubed-sphere mesh of 576 curved hexahedra (geometry degree 3) with mixed block orientations, BR2 viscous fluxes, Newton–Krylov with p-multigrid. At this Reynolds number the flow is steady and axisymmetric with a closed recirculation bubble. References: drag coefficient 1.09 (standard drag curve of Clift, Grace & Weber 1978; Johnson & Patel, JFM 378, 1999, compute 1.08–1.10) and a bubble length of about 0.87 diameters (Johnson & Patel 1999; Taneda 1956). Both references are for incompressible flow in an unbounded domain; at M = 0.2 with the boundary at 10 diameters a difference of the order of one per cent is expected, which the tolerance on the drag covers. Drag is referred to the frontal area π/4. all solves converged to a steady state yes expected true pass drag coefficient, highest order: |cd − 1.09| / 1.09 0.01409 expected < 0.03 pass wake is axisymmetric: lift, side force and moments (largest |coefficient|) 1.77e-15 expected < 1e-7 pass a closed recirculation bubble exists; length in diameters, highest order 0.9501 expected within 15 % of 0.87 pass adiabatic wall: integrated heat flux 0 expected < 1e-10 pass Run notes The mesh has the symmetry of the cube, so the vanishing lift and side force show that the solver preserves a symmetric solution; they do not test the stability of the axisymmetric wake (which is stable below Re of about 210). The bubble length is read from the solution nodes on the wake axis by linear interpolation between the two nodes where the velocity changes sign. Sphere at Re = 100 Laminar flow past a sphere, Re = 100 (3-D) p elements DOF Newton steps residual reduction cd bubble length / D first cell height / D largest |cl, cs, cm| solve time (s) 2 576 77760 14 1.3330e-11 1.10536 0.950129 0.185684 1.7681e-15 162.798 Streamwise velocity on the wake axis Laminar flow past a sphere, Re = 100 (3-D) Surface pressure coefficient Inviscid flow past a sphere (3-D)\nThe drag coefficient, referred to the frontal area \\pi D^2/4 , against the accepted value of about 1.09 at this Reynolds number. The streamwise velocity on the wake axis is negative behind the sphere and changes sign where the recirculation bubble closes, a little under one diameter downstream. Lift, side force and all three moments vanish to round-off: the flow is axisymmetric and the discretization keeps the symmetry of the mesh.\nsphere_shell builds a cubed sphere: six blocks of n × n × n_r curved hexahedra between the body and a spherical far-field boundary, three blocks meeting at each of the eight corners of the cube. The body can be an ellipsoid ( axes ), and the radial spacing grows geometrically from the wall ( ratio ): gyre run sphere_re100 --set mesh.params.n = 6 --set mesh.params.n_r = 8 # finer gyre run sphere_euler --set 'mesh.params.axes=[1.0, 0.7, 1.2]' \\ --set flow.alpha_deg = 4 --set flow.beta_deg = 3 # ellipsoid at incidence and sideslip The second command gives a case in which none of the six coefficients vanishes; it is the configuration of the 3-D adjoint study .\nOutput Meaning cd , cl , cs drag, lift and side force coefficients in wind axes cmx , cmy , cmz moment coefficients about reference.moment_center heat , entropy_error , … as in two dimensions flow.beta_deg is the sideslip angle; as a differentiable input of the discipline it is beta (radians). The definitions are under Outputs and functionals . gyre run sphere_euler --set 'mesh.params.axes=[1.0, 0.7, 1.2]' --set flow.alpha_deg = 4 \\ --set flow.beta_deg = 3 --gradients --of cl,cs,cmz --wrt alpha,beta,mach\nThe chart shows the surface pressure of the inviscid preset at polynomial degrees one to four against the compressible potential-flow solution. The exact flow has no drag and no entropy rise, so both are error measures; the verification page tabulates them.\nElement blocks in three dimensions are large: 5(p+1)^3 unknowns per element, 135 at p = 2 and 625 at p = 4 , and the preconditioner stores their inverses. The largest solve recorded on the development machine, with its memory and wall times, is under Largest solve on one device . The service sends cases above 60 000 unknowns to the GPU when one is present.","title":"Sphere in three dimensions","url":"examples/sphere.html"},{"audience":["CFD practitioners","MDO integrators"],"chunks":[{"anchor":"","end":8497,"heading":"","level":1,"start":0},{"anchor":"the-case-in-python-and-through-the-service","end":10896,"heading":"The case, in Python and through the service","level":2,"start":8498},{"anchor":"what-to-look-for","end":11938,"heading":"What to look for","level":2,"start":10897},{"anchor":"the-same-solver-on-a-generated-and-on-an-imported-mesh","end":12721,"heading":"The same solver on a generated and on an imported mesh","level":3,"start":11939}],"headings":["The case, in Python and through the service","What to look for","The same solver on a generated and on an imported mesh"],"keywords":["Gmsh","mesh file","imported mesh","unstructured","cylinder","channel","mesh store","example"],"level":1,"max_level":3,"path":"examples/imported-mesh.md","section":"examples","section_title":"Examples","summary":"Laminar flow past a cylinder in a channel on an unstructured mesh of cubic quadrilaterals written by Gmsh — set up from the mesh's physical groups, solved in Python and through the service, with an adjoint derivative checked against finite differences.","text":"The mesh examples/meshes/cylinder_channel_p3.msh is Gmsh output: a channel with a circular cylinder, meshed with unstructured cubic quadrilaterals and saved as MSH 4.1 ASCII. Its four physical groups — cylinder , inflow , outflow , walls — are the boundary groups of the case. Mesh files explains how such a file is made and what the reader requires. gyre mesh info examples/meshes/cylinder_channel_p3.msh # groups, size, quality python examples/imported_mesh.py # solve in this process python examples/imported_mesh.py http://HOST:8600 # upload the mesh, solve as a job The case: Mach 0.2, Reynolds number 40 (based on the unit diameter), polynomial degree 2, an adiabatic no-slip cylinder, slip walls, and far-field conditions at the inflow and outflow sides. Passed Imported meshes 29 of 29 checks passed · 184.5 s · quick suite A mesh read from a file must behave exactly like a generated one, including unstructured topology, where neighbouring elements use different local axes. (i) Every Gmsh fixture of the test suite is read; the files are genuine Gmsh output (tools/make_mesh_fixtures.py), and on the straight-sided ones every high-order node must be the multilinear image of the element's vertices, which tests the node-ordering tables for quadrilaterals of degree 1 to 5 and hexahedra of degree 1 to 4. (ii) On curved unstructured meshes from Gmsh in 2-D and 3-D, and on a 3-D mesh whose elements were randomly re-oriented and passed through a file, a uniform flow must remain steady, the residuals of a perturbed state must sum to zero, and the inviscid residual of a continuous state must not depend on the Riemann solver away from the boundary (the jump across every face is zero only if its nodes are matched with the right ones of the neighbour; the viscous terms cancel in this comparison, so it is made for the Euler equations only). The coupling of the viscous terms across faces (gradient traces of the neighbour, lifted jumps) is tested separately, for both sets of equations: a rough state with varying velocity and temperature and a jump on every face must give the same residual, node by node, when every element of the mesh is stored in other, randomly chosen local axes. (iii) The coarse annulus of the cylinder case, written to a Gmsh file after randomly re-orienting its elements and read back through a case specification, must give the force coefficients of the generated mesh to solver tolerance. (iv) Laminar flow past a cylinder in a channel (Re = 40, M = 0.2, slip walls) is solved on the unstructured example mesh at degree 2, and the adjoint derivative of the drag coefficient with respect to the Mach number is compared with a central finite difference of re-converged flows. usable Gmsh fixtures read 15 of 15 expected all pass unusable Gmsh fixtures (triangles, binary, untagged) refused with a message 3 of 3 expected all pass straight-sided fixtures cover quadrilaterals of degree 1–5 and hexahedra of degree 1–4 9 expected = 9 (dimension, degree) pairs pass node ordering: largest distance of a node of a straight-sided mesh from the multilinear map of its vertices 4.44e-15 expected < 1e-13 (round-off; coordinates of order one) pass straight-sided meshes: relative error of the area / volume 3.06e-16 expected < 1e-12 (the quadrature is exact for multilinear elements) pass every straight-sided fixture is unstructured: fewest face pairs with a non-identity orientation 44 expected > 0 pass curved quadrilateral meshes (circular hole): area error at geometry degree pg + 1 relative to pg, largest 0.1473 expected < 1 (the boundary interpolant converges with the degree) pass MSH 2.2 and MSH 4.1 files of one periodic mesh: largest node difference 0 expected < 1e-12 pass periodic node pairs of the file became topology (slave nodes) 13 expected > 0, and no boundary group on the periodic sides pass hole_quad_p3.msh (Gmsh, curved, 2-D), inviscid: perturbed state is not trivial (max |Rw|) 0.4158 expected > 1e-3 pass hole_quad_p3.msh (Gmsh, curved, 2-D), viscous: perturbed state is not trivial (max |Rw|) 0.4359 expected > 1e-3 pass hole_quad_p3.msh (Gmsh, curved, 2-D): face pairs with a non-identity orientation 48 expected > 0 pass hole_hex_p2.msh (Gmsh, curved, 3-D), inviscid: perturbed state is not trivial (max |Rw|) 0.6203 expected > 1e-3 pass hole_hex_p2.msh (Gmsh, curved, 3-D), viscous: perturbed state is not trivial (max |Rw|) 0.3062 expected > 1e-3 pass hole_hex_p2.msh (Gmsh, curved, 3-D): face pairs with a non-identity orientation 72 expected > 0 pass periodic warped 3x3x3 box, re-oriented, written and read back, inviscid: perturbed state is not trivial (max |Rw|) 0.1151 expected > 1e-3 pass periodic warped 3x3x3 box, re-oriented, written and read back, viscous: perturbed state is not trivial (max |Rw|) 0.1204 expected > 1e-3 pass periodic warped 3x3x3 box, re-oriented, written and read back: face pairs with a non-identity orientation 74 expected > 0 pass re-oriented 3-D box read from a file: relative face orientations present 8 expected = 8 (all) pass free stream on imported meshes: largest max |Rw| 4.44e-15 expected < 1e-12 pass conservation on imported meshes: largest |sum of Rw| of a perturbed state 1.38e-14 expected < 1e-12 (residuals of order 0.1 to 1) pass neighbour matching on imported meshes (inviscid): largest |Rw(Roe) − Rw(LLF)| off the boundary for a continuous state 9.99e-16 expected < 1e-12 (zero jump across every correctly matched face; residuals of order 0.1 to 1) pass neighbour coupling on imported meshes, Euler and Navier–Stokes: largest difference of the residual of a rough state between two descriptions of the mesh (elements re-oriented) 2.48e-14 expected < 1e-12 (round-off; residuals of order 0.1 to 1) pass the rough state exercises the viscous terms: smallest max |Rw(Navier–Stokes) − Rw(Euler)| 0.02271 expected > 1e-3 (velocity and temperature vary; far above the bound of the coupling check) pass generated and imported annulus: both solves converged yes expected true pass imported annulus: face pairs with a non-identity orientation 42 expected > 0 (elements were re-oriented before writing) pass force and moment coefficients, |imported − generated| (largest) 2.13e-14 expected < 1e-10 (both solves reduce the residual by 1e-12) pass cylinder in a channel (examples/meshes/cylinder_channel_p3.msh), Re = 40, p = 2: converged yes expected true pass cylinder in a channel: dcd/dmach, adjoint vs central finite difference (rel.) 1.18e-07 expected < 1e-6 pass Run notes Measured in this run: the largest free-stream residual on the imported meshes was 4.4e-15, the largest conservation defect 1.4e-14 and the largest dependence of the inviscid residual of a continuous state on the Riemann solver 1.0e-15. The residual of a rough state changed by at most 2.5e-14 when the elements were re-oriented, with and without viscous terms; the viscous terms contributed between 2.3e-02 and 1.4e-01 to that residual. Conservation alone would not notice a wrong index map between two straight faces (their normals are constant); the Riemann-solver comparison (trace matching, an inviscid test: the viscous terms cancel in it), the comparison of two descriptions (which includes the gradient traces and lifted jumps of the viscous discretization) and the solves of parts (iii) and (iv) do. The annulus written to a file with re-oriented elements (42 of 84 face pairs with a non-identity orientation) gave force coefficients within 2.1e-14 of those on the generated mesh. On the example mesh the adjoint derivative of the drag coefficient with respect to the Mach number differs from the central difference by a relative 1.2e-07. The Gmsh fixtures contain only two of the eight relative face orientations of hexahedra (see the table); all eight occur in the re-oriented box, which is written by Gyre's own writer, not by Gmsh. The study above covers more than this example (it also reads the Gmsh fixtures of the test suite and tests the discrete invariants on them); the table below is its part on this mesh. The complete study is on the page Imported meshes . Cylinder in a channel on examples/meshes/cylinder_channel_p3.msh Imported meshes quantity value elements 284 geometry degree 3 degrees of freedom 10224 face pairs with a non-identity orientation 104 of 528 Newton steps 14 final / reference residual 7.7014e-13 cd 3.21039 cl 2.2180e-04 cm (about the cylinder axis) -0.00200862 dcd/dmach, adjoint 0.331089 dcd/dmach, central difference (step 0.0001) 0.331089 relative difference 1.1825e-07\nfrom gyre.cases import CaseSpec from gyre.meshio import inspect_mesh from gyre.runner import run_case mesh = \"examples/meshes/cylinder_channel_p3.msh\" print ( inspect_mesh ( mesh )[ \"boundaries\" ]) # faces per boundary group spec = CaseSpec . model_validate ({ \"name\" : \"cylinder_channel\" , \"mesh\" : { \"generator\" : \"file\" , \"params\" : { \"path\" : mesh }}, \"flow\" : { \"mach\" : 0.2 , \"reynolds\" : 40.0 }, \"discretization\" : { \"order\" : 2 , \"boundaries\" : { \"cylinder\" : \"noslip_adiabatic\" , \"inflow\" : \"farfield\" , \"outflow\" : \"farfield\" , \"walls\" : \"slip_wall\" }}, \"reference\" : { \"moment_center\" : [ 0.0 , 0.0 ]}, }) result = run_case ( spec , gradients = { \"of\" : [ \"cd\" ], \"wrt\" : [ \"mach\" ]}) print ( result [ \"converged\" ], result [ \"outputs\" ][ \"cd\" ], result [ \"gradients\" ][ \"derivatives\" ][ \"cd\" ][ \"mach\" ]) from gyre.client import GyreClient gyre = GyreClient ( \"http://HOST:8600\" ) info = gyre . upload_mesh ( \"examples/meshes/cylinder_channel_p3.msh\" ) case = { \"name\" : \"cylinder_channel\" , \"mesh\" : { \"generator\" : \"file\" , \"params\" : { \"path\" : info [ \"name\" ]}}, # the stored name \"flow\" : { \"mach\" : 0.2 , \"reynolds\" : 40.0 }, \"discretization\" : { \"order\" : 2 , \"boundaries\" : { \"cylinder\" : \"noslip_adiabatic\" , \"inflow\" : \"farfield\" , \"outflow\" : \"farfield\" , \"walls\" : \"slip_wall\" }}, \"reference\" : { \"moment_center\" : [ 0.0 , 0.0 ]}, } print ( gyre . validate ( case )[ \"estimate\" ]) # elements, degrees of freedom result = gyre . run ( case , gradients = { \"of\" : [ \"cd\" ], \"wrt\" : [ \"mach\" ]}) API = http://HOST:8600/api/v1 curl -s -X PUT --data-binary @examples/meshes/cylinder_channel_p3.msh \\ -H 'content-type: application/octet-stream' $API /meshes/cylinder_channel_p3.msh curl -s $API /jobs -H 'content-type: application/json' -d '{ \"case\": {\"name\": \"cylinder_channel\", \"mesh\": {\"generator\": \"file\", \"params\": {\"path\": \"cylinder_channel_p3.msh\"}}, \"flow\": {\"mach\": 0.2, \"reynolds\": 40.0}, \"discretization\": {\"order\": 2, \"boundaries\": { \"cylinder\": \"noslip_adiabatic\", \"inflow\": \"farfield\", \"outflow\": \"farfield\", \"walls\": \"slip_wall\"}}, \"reference\": {\"moment_center\": [0.0, 0.0]}}, \"gradients\": {\"of\": [\"cd\"], \"wrt\": [\"mach\"]}}' examples/imported_mesh.py is the runnable form of the first two tabs; --order 1 makes the solve cheaper. In the process that owns the file the case names a path; a service is given the name under which the mesh was uploaded and never a path.\nEvery group needs a condition. A mesh file has no default boundary conditions; leaving out walls , or naming a group the mesh does not have, is a validation error that lists the groups of the mesh. The reference section. moment_center moves the moment reference from the default quarter-chord point to the cylinder axis. The diameter is one, so the default reference area and length apply. Forces include the slip walls. The coefficients are integrated over every group with a wall condition, here the cylinder and the two channel walls. The channel walls are parallel to the flow and carry no shear, so they add nothing to the force in the stream direction; they do contribute to cl and cm . An unstructured mesh. The table reports how many interior face pairs join elements with different local axes; on a generated mesh there are none. The adjoint agrees with finite differences. The derivative of the drag coefficient with respect to the Mach number from one adjoint solve is compared with a central difference of two re-converged flows.\nThe study also writes the coarse annulus of the cylinder preset to a Gmsh file, after re-orienting its elements at random, reads it back through a case specification and solves both. The force coefficients must agree to solver tolerance: Cylinder Re = 40 on the generated annulus (12 x 4, p = 2) and on the same mesh read from a file Imported meshes quantity generated imported difference cd 1.69824 1.69824 2.1316e-14 cl -2.0933e-14 -2.1852e-14 9.1940e-16 cm -2.8159e-15 -6.9159e-15 4.1000e-15 Newton steps 13 13 — final / reference residual 4.0911e-14 3.5832e-14 — Residual histories on generated and imported meshes Imported meshes The histories of the generated and of the imported annulus coincide. gyre mesh export cylinder_re40 annulus.msh writes such a file from any preset.","title":"A mesh from Gmsh","url":"examples/imported-mesh.html"},{"audience":[],"chunks":[{"anchor":"","end":1599,"heading":"","level":2,"start":0},{"anchor":"how-it-was-run","end":2426,"heading":"How it was run","level":2,"start":1600},{"anchor":"warm-starts","end":2667,"heading":"Warm starts","level":2,"start":2427}],"headings":["How it was run","Warm starts"],"keywords":["polar","sweep","warm start","session","lift-curve slope","example"],"level":2,"max_level":2,"path":"examples/lift-polar.md","section":"examples","section_title":"Examples","summary":"A parameter sweep in one warm solver process, with the lift-curve slope from one adjoint solve at every point. Demonstrates warm starts and the consistency of gradients along a path.","text":"Passed Example: lift polar with adjoint slopes 5 of 5 checks passed · 686.1 s · quick suite A warm-started sweep of the angle of attack for a NACA 2412 at Mach 0.4 in one solver process. At every point one adjoint solve gives the lift-curve slope, drawn as a tangent; the checks compare the slopes with a small-step central difference and with differences of neighbouring points. all points converged yes expected true pass positive lift at zero incidence (camber) 0.2821 expected > 0.15 pass adjoint slope vs small-step central difference at 4 deg (rel.) 9.86e-07 expected < 1e-5 pass adjoint slope vs two-degree secants of the polar (worst, rel.) 0.0127 expected < 0.03 pass warm starts: Newton steps after the first point (max) 6 expected < 14 pass Lift polar and adjoint tangents Example: lift polar with adjoint slopes Each point is a converged flow over a NACA 2412 at Mach 0.4; the short segments are tangents whose slope is the adjoint derivative \\mathrm dc_l/\\mathrm d\\alpha at that point. They line up with the curve: the gradient is the gradient of the function being evaluated. Polar Example: lift polar with adjoint slopes α [deg] cl cd cm dcl/dα [1/rad] dcl/dα [1/deg] Newton steps converged -2 0.0185712 0.00100944 -0.0572344 7.43176 0.129709 14 yes -1 0.148671 8.9199e-04 -0.0583837 7.52605 0.131354 6 yes 0 0.282053 9.2904e-04 -0.0601193 7.72338 0.134798 6 yes 1 0.41544 0.00102287 -0.0618284 7.53123 0.131445 6 yes 2 0.544452 0.00117546 -0.0626012 7.22024 0.126017 6 yes 3 0.666028 0.00142276 -0.061664 6.65559 0.116162 6 yes 4 0.773825 0.00180472 -0.0575159 6.47169 0.112952 6 yes\n- id : polar uses : sweep with : case : naca2412_euler parameter : alpha_deg values : [ -2 , -1 , 0 , 1 , 2 , 3 , 4 ] outputs : [ cl , cd , cm ] gradients_wrt : [ alpha ] with GyreClient ( URL ) . session ( \"naca2412_euler\" ) as flow : for a in ( - 2 , - 1 , 0 , 1 , 2 , 3 , 4 ): out = flow . evaluate ( alpha_deg = a )[ \"outputs\" ] slope = flow . gradients ( of = [ \"cl\" ], wrt = [ \"alpha_deg\" ])[ \"cl\" ][ \"alpha_deg\" ] flow = Case ( preset ( \"naca2412_euler\" )) . discipline for a in ( - 2 , - 1 , 0 , 1 , 2 , 3 , 4 ): flow . set_inputs ( alpha = math . radians ( a )) flow . solve () slope = flow . totals ( of = ( \"cl\" ,), wrt = ( \"alpha\" ,))[ \"derivatives\" ][ \"cl\" ][ \"alpha\" ] Nonlinear iterations per point Example: lift polar with adjoint slopes The first point starts from the free stream; the others are warm starts.\nThe first point starts from a uniform free stream and ramps the pseudo-time step. Every later point starts from the previous converged flow directly in the Newton regime and converges to the same absolute residual level in a few iterations.","title":"Lift polar with adjoint slopes","url":"examples/lift-polar.html"},{"audience":["MDO integrators"],"chunks":[{"anchor":"","end":1083,"heading":"","level":2,"start":0},{"anchor":"what-the-numbers-say","end":1420,"heading":"What the numbers say","level":2,"start":1084},{"anchor":"the-coupled-analysis","end":2148,"heading":"The coupled analysis","level":2,"start":1421},{"anchor":"the-coupled-adjoint","end":2747,"heading":"The coupled adjoint","level":2,"start":2149},{"anchor":"what-gyre-contributed","end":3401,"heading":"What Gyre contributed","level":3,"start":2748},{"anchor":"over-the-network","end":3638,"heading":"Over the network","level":3,"start":3402}],"headings":["What the numbers say","The coupled analysis","The coupled adjoint","What Gyre contributed","Over the network"],"keywords":["coupled adjoint","aeroelastic","loads","displacements","MDO","example"],"level":2,"max_level":3,"path":"examples/aerostructural-coupling.md","section":"examples","section_title":"Examples","summary":"A flow coupled to an elastic skin, with the coupled derivative assembled outside Gyre from loads, displacements and reverse total products — the pattern an MDO framework uses, in sixty lines.","text":"This example exists to show an interface , not a structural model. The structure is a toy — a smooth linear compliance mapping surface loads to surface displacements — but the coupling is real: loads deform the surface, the deformed surface changes the loads. Passed Example: coupled aero-structural adjoint 2 of 2 checks passed · 823 s · quick suite The flow is coupled to a linear elastic skin (loads in, displacements out) by a fixed-point iteration written outside Gyre. The coupled derivative of lift with respect to angle of attack is assembled from Gyre's reduced reverse products and compared with a finite difference of the fully re-converged coupled system. coupled adjoint vs finite difference (rel.) 9.63e-08 expected < 1e-5 pass coupling is active: max deflection [chords] 0.005559 expected > 1e-5 pass Coupled derivative Example: coupled aero-structural adjoint cl (flexible) max deflection [c] dcl/dα coupled adjoint dcl/dα finite difference relative difference dcl/dα frozen shape aeroelastic effect [%] 0.279698 0.00555919 7.76502 7.76502 9.6349e-08 7.71574 0.638621\nThe derivative of lift with respect to angle of attack for the coupled system differs from the frozen-shape value by the aeroelastic effect in the last column. The coupled adjoint reproduces a finite difference of the fully re-converged coupled system to the relative difference shown — using only operations any MDO framework can call.\ndef coupled_solve ( flow , C , tol = 1e-9 , relax = 1.0 , max_iter = 40 ): d = np . asarray ( flow . d_surface ) . copy () for it in range ( max_iter ): flow . set_inputs ( x_surface = d ) # displacements in converge ( flow ) # warm-started flow solve; raises if it does not converge f = flow . outputs (( \"f_surface\" ,))[ \"f_surface\" ] # consistent nodal loads out d_new = ( C @ f . reshape ( - 1 )) . reshape ( d . shape ) # the \"structure\" ... converge is a small helper of the example: it calls flow.solve() and raises if the history says the solve did not converge. A failed solve leaves the discipline’s last converged state in place, the flow of the previous shape, so loads read after it would belong to another design.\ntot = flow . totals ( of = ( \"cl\" ,), wrt = ( \"alpha\" , \"x_surface\" ))[ \"derivatives\" ][ \"cl\" ] dJ_dd = tot [ \"x_surface\" ] . reshape ( - 1 ) lam = dJ_dd . copy () for _ in range ( max_iter ): # transposed fixed-point iteration seed = ( C . T @ lam ) . reshape ( shape ) back = flow . adjoint_product ({ \"f_surface\" : seed }, wrt = ( \"x_surface\" ,))[ \"derivatives\" ] lam_new = dJ_dd + back [ \"x_surface\" ] . reshape ( - 1 ) ... cross = flow . adjoint_product ({ \"f_surface\" : ( C . T @ lam ) . reshape ( shape )}, wrt = ( \"alpha\" ,)) dJ_dalpha = tot [ \"alpha\" ] + cross [ \"derivatives\" ][ \"alpha\" ]\nThree things, all part of the discipline contract : f_surface , loads that are conservative and work-conjugate to x_surface ; totals(…, wrt=[\"alpha\", \"x_surface\"]) , the reduced gradient of a scalar; adjoint_product({\"f_surface\": seed}, …) , the reverse product (\\mathrm d\\mathbf f/\\mathrm d\\mathbf x)^{\\mathsf T}\\mathbf{seed} — one flow-adjoint solve regardless of the number of load components. The loop, the structure and the choice of solver for the coupled system live outside. The derivation is in Adjoints and sensitivities ; the full script is examples/aerostructural_coupling.py , and the same logic is exercised by tests/test_integrations.py .\nThe same calls exist on a remote session: flow.adjoint({\"f_surface\": seed}, wrt=[...]) on a RemoteDiscipline , or POST /api/v1/sessions/{id}/adjoint . A structural code on another tailnet machine can therefore own the coupled iteration.","title":"Coupled aero-structural adjoint","url":"examples/aerostructural-coupling.html"},{"audience":["MDO integrators"],"chunks":[{"anchor":"","end":863,"heading":"","level":2,"start":0},{"anchor":"what-the-adapter-does","end":1545,"heading":"What the adapter does","level":2,"start":864},{"anchor":"residual-level-the-flow-as-an-implicit-component","end":3649,"heading":"Residual level: the flow as an implicit component","level":2,"start":1546},{"anchor":"verified-behaviour","end":4649,"heading":"Verified behaviour","level":3,"start":3650},{"anchor":"coupling-in-a-framework","end":4986,"heading":"Coupling in a framework","level":3,"start":4650}],"headings":["What the adapter does","Residual level: the flow as an implicit component","Verified behaviour","Coupling in a framework"],"keywords":["OpenMDAO","adapter","compute_jacvec_product","totals","example"],"level":2,"max_level":3,"path":"examples/openmdao.md","section":"examples","section_title":"Examples","summary":"Wrap the flow discipline as a matrix-free OpenMDAO component, in reduced space or at residual level, and let the framework assemble total derivatives in forward or reverse mode. No optimizer involved.","text":"import numpy as np import openmdao.api as om from gyre.cases import Case , preset from gyre.integrations.openmdao import GyreFlowComp flow = Case ( preset ( \"naca0012_euler\" , ** { \"solver.tol_rel\" : 1e-12 })) . discipline prob = om . Problem () prob . model . add_subsystem ( \"flow\" , GyreFlowComp ( discipline = flow , inputs = ( \"alpha\" , \"mach\" ), outputs = ( \"cl\" , \"cd\" )), promotes = [ \"*\" ]) prob . setup ( mode = \"rev\" ) prob . set_val ( \"alpha\" , np . radians ( 2.0 )) prob . run_model () totals = prob . compute_totals ( of = [ \"cl\" , \"cd\" ], wrt = [ \"alpha\" , \"mach\" , \"x_surface\" ]) examples/openmdao_wrapper.py is a runnable script built on this adapter ( .venv/bin/python examples/openmdao_wrapper.py ). The adapter needs OpenMDAO, which the mdo extra installs ( pip install -e \".[mdo]\" ); the all extra, which make bootstrap installs, includes it.\nOpenMDAO calls Adapter does compute set_inputs , warm-started solve (both skipped when the discipline already holds the converged solution for these inputs), outputs ; raises om.AnalysisError if the flow does not converge or the inputs are outside the range of the flow model; after a failed point the next call always solves again, also for inputs that had converged before compute_jacvec_product(mode=\"rev\") one adjoint_product for all seeded outputs compute_jacvec_product(mode=\"fwd\") one tangent_product for the perturbed inputs Options: inputs (scalar flow parameters), outputs (scalar functionals), shape=True (expose x_surface ), loads=True (expose f_surface ), linear_tol .\nfrom gyre.integrations.openmdao import GyreFlowGroup prob . model . add_subsystem ( \"flow\" , GyreFlowGroup ( discipline = flow , inputs = ( \"alpha\" , \"mach\" ), outputs = ( \"cl\" , \"cd\" ), loads = True ), promotes = [ \"*\" ]) GyreFlowGroup contains two components. The implicit one has the flow state as its output: OpenMDAO asks it for the residual ( apply_nonlinear ), for products with the linearised residual in either mode ( apply_linear ) and for linear solves ( solve_linear ), each about the state and inputs OpenMDAO holds; its solve_nonlinear is the flow solve. The second component evaluates the functionals and the load field from inputs and state. The framework then owns the coupled linear system, which is what a hierarchical or monolithic coupled adjoint needs. OpenMDAO calls Adapter does solve_nonlinear solve_nonlinear(inputs) of the discipline; the state becomes the component’s output apply_nonlinear apply_nonlinear(inputs, state) : the weak-form residual at OpenMDAO’s values apply_linear (fwd / rev) jvp / vjp about that point solve_linear (fwd / rev) solve_linear ; a solve that misses linear_tol raises om.AnalysisError OpenMDAO integration Discipline contract for MDO integration quantity value check_partials flow.solver: 4 (of, wrt) pairs, directional 3.0315e-09 check_partials flow.functionals: 12 (of, wrt) pairs, directional 5.6471e-09 totals through the implicit component (reverse) vs FlowDiscipline.totals 4.1434e-16 coupled dcl/dα, OpenMDAO reverse mode 7.54117 coupled dcl/dα, OpenMDAO forward mode 7.54117 coupled dcl/dα, central difference of re-converged coupled analyses 7.54117 relative difference, reverse mode vs finite difference 1.2307e-07 dcl/dα of the rigid aerofoil 7.51815 largest deflection [chords] 0.00555541 The table is from the discipline contract study: OpenMDAO’s own check_partials of both components, totals through the implicit component against the discipline’s adjoint totals, and the coupled derivative of a flow–structure cycle built on the group ( examples/openmdao_wrapper.py ) against a finite difference of re-converged coupled analyses.\ntests/test_integrations.py::test_openmdao_component_totals checks that the framework’s reverse-mode totals equal Gyre’s own adjoint totals (including the shape sensitivity field), that forward mode agrees with reverse mode, and that a finite difference through the framework’s model evaluation matches. tests/test_integrations.py::test_openmdao_component_solves_again_after_a_failed_point checks that a point that does not converge raises AnalysisError and that a return to the previous, converged inputs solves again instead of reusing the cached point. For the implicit form, test_openmdao_implicit_component_passes_check_partials , test_openmdao_implicit_group_totals_match_the_reduced_space and test_openmdao_coupled_adjoint_through_the_implicit_component check the partial products, the totals mode for mode against the discipline’s own, and the coupled adjoint. The tests skip themselves when OpenMDAO is not installed; make bootstrap and the hosted CI workflow install all , so it runs there.\nWith loads=True and shape=True the component exposes the two coupling fields. Connected to a structural component in an OpenMDAO group with a nonlinear block solver, the framework’s linear solver assembles the coupled adjoint from the same compute_jacvec_product calls — the framework-owned version of the hand-written coupled adjoint .","title":"Inside OpenMDAO","url":"examples/openmdao.html"},{"audience":["AI agents","agent builders"],"chunks":[{"anchor":"","end":1126,"heading":"","level":2,"start":0},{"anchor":"1-orient","end":1836,"heading":"1. Orient","level":2,"start":1127},{"anchor":"2-analyse-with-an-error-estimate","end":3699,"heading":"2. Analyse with an error estimate","level":2,"start":1837},{"anchor":"3-ground-the-claim-in-the-documentation","end":4489,"heading":"3. Ground the claim in the documentation","level":2,"start":3700},{"anchor":"4-a-sweep-in-a-session","end":5354,"heading":"4. A sweep in a session","level":2,"start":4490},{"anchor":"why-the-interface-looks-like-this","end":5512,"heading":"Why the interface looks like this","level":3,"start":5355},{"anchor":"what-is-tested","end":6011,"heading":"What is tested","level":3,"start":5513}],"headings":["1. Orient","2. Analyse with an error estimate","3. Ground the claim in the documentation","4. A sweep in a session","Why the interface looks like this","What is tested"],"keywords":["MCP","agent","tools","transcript","example"],"level":2,"max_level":3,"path":"examples/agent-session.md","section":"examples","section_title":"Examples","summary":"The Model Context Protocol exchange behind an agent task — \"characterise this aerofoil and tell me how far to trust the drag\" — shown as the raw tool calls and results.","text":"The requests below are JSON-RPC messages posted to /mcp ; any MCP client produces them. The replies were recorded from a service running revision 5ee1cdc on the CPU (a scratch instance, not a public deployment); numbers are shortened to the digits shown and … marks omitted keys and values. Nothing on this page was typed in from memory. Note What stands behind the recorded replies The exchange was recorded once and the recording itself is not kept in the repository. The force coefficients, gradients and error estimate it contains were reproduced afterwards by an independent run of the same case through the command line ( gyre run naca0012_euler with the same gradients and error estimate); the lift and drag at 2.5° were reproduced by a cold run at that angle. The parts that belong to the exchange itself — the job still running after the first 120 seconds, the nonlinear iteration counts 16 and 6, the l2_norm of the shape-sensitivity field, the digests, and the number, order and scores of the search hits — have not been reproduced; take them as an illustration of the shape of the replies, not as reference values.\n{ \"jsonrpc\" : \"2.0\" , \"id\" : 1 , \"method\" : \"tools/call\" , \"params\" : { \"name\" : \"validate_case\" , \"arguments\" : { \"case\" : \"naca0012_euler\" , \"overrides\" : { \"discretization.order\" : 3 }}}} { \"valid\" : true , \"digest\" : \"e81f2ad4bcafe5b9\" , \"estimate\" : { \"elements\" : 256 , \"dim\" : 2 , \"block_size\" : 64 , \"degrees_of_freedom\" : 16384 , \"viscous\" : false }, \"device\" : \"cpu\" , \"spec\" : { \"…\" : \"…\" }, \"warnings\" : []} A wrong name is answered with a way forward rather than a dead end: { \"ok\" : false , \"status\" : 404 , \"error\" : { \"type\" : \"UnknownPreset\" , \"message\" : \"unknown preset 'naca0013'\" , \"hint\" : \"available presets: ['bump_euler', 'cylinder_re40', 'naca0012_euler', 'naca0012_euler_3d', …]\" }}\n{ \"name\" : \"submit_job\" , \"arguments\" : { \"case\" : \"naca0012_euler\" , \"gradients_of\" : [ \"cl\" , \"cd\" ], \"gradients_wrt\" : [ \"alpha\" , \"mach\" ], \"error_estimate\" : [ \"cd\" ], \"wait_seconds\" : 120 }} { \"id\" : \"job-…\" , \"state\" : \"running\" , \"device\" : \"cpu\" , \"digest\" : \"e4180d2af8d6dd7e\" , \"estimate\" : { \"elements\" : 256 , \"degrees_of_freedom\" : 9216 , \"…\" : \"…\" }, \"…\" : \"…\" } The job had not finished within the 120 seconds of the call (the kernels of this case had never been compiled on that service, and the machine was busy), so the agent waits again: { \"name\" : \"get_job\" , \"arguments\" : { \"job_id\" : \"job-…\" , \"wait_seconds\" : 120 }} { \"name\" : \"get_job_result\" , \"arguments\" : { \"job_id\" : \"job-…\" }} { \"converged\" : true , \"reason\" : \"residual tolerance reached\" , \"residual_norm\" : 7.06e-13 , \"outputs\" : { \"cl\" : 0.278767 , \"cd\" : 0.000949 , \"cm\" : -0.002021 , \"…\" : \"…\" }, \"gradients\" : { \"derivatives\" : { \"cl\" : { \"alpha\" : 7.7383 , \"mach\" : 0.23041 }, \"cd\" : { \"alpha\" : 0.011770 , \"mach\" : 7.908e-05 }}, \"info\" : { \"mode\" : \"adjoint\" , \"linear_solves\" : [ { \"output\" : \"cl\" , \"iterations\" : 51 , \"reduction\" : 7.09e-11 , \"converged\" : true }, { \"output\" : \"cd\" , \"iterations\" : 50 , \"reduction\" : 7.18e-11 , \"converged\" : true }], \"…\" : \"…\" }}, \"error_estimate\" : { \"cd\" : { \"value\" : 0.000949 , \"estimate\" : -0.000771 , \"corrected\" : 0.000264 , \"error\" : 0.000684 , \"indicators\" : { \"_array\" : true , \"shape\" : [ 256 ], \"max\" : 1.19e-04 , \"…\" : \"…\" }, \"…\" : \"…\" }}, \"provenance\" : { \"version\" : \"0.1.0\" , \"device\" : { \"platform\" : \"cpu\" , \"…\" : \"…\" }, \"…\" : \"…\" }} The agent can now state: lift coefficient 0.279; drag coefficient 0.00095, of which about 0.0007 is estimated to be discretization error on this mesh (the exact inviscid drag is zero) — and cite the revision that produced it. The derivatives with respect to alpha are per radian.\n{ \"name\" : \"search_docs\" , \"arguments\" : { \"query\" : \"adjoint-weighted residual effectivity\" }} { \"query\" : \"adjoint-weighted residual effectivity\" , \"results\" : [{ \"title\" : \"…\" , \"section\" : \"…\" , \"level\" : 2 , \"summary\" : \"…\" , \"path\" : \"….md\" , \"score\" : 32 }, \"…\" ]} Each hit carries title , section , level , summary , path and score , best first. The titles are elided here on purpose: the ranking is computed from the pages of the deployed revision and changes whenever the documentation does. In the recorded exchange six pages were returned and verification/output-error.md , the page that defines the effectivity, was the sixth; an agent should read down the list rather than take the first hit. { \"name\" : \"read_doc\" , \"arguments\" : { \"path\" : \"verification/output-error.md\" }}\n{ \"name\" : \"open_session\" , \"arguments\" : { \"case\" : \"naca0012_euler\" }} { \"name\" : \"session_evaluate\" , \"arguments\" : { \"session_id\" : \"ses-…\" , \"inputs\" : { \"alpha_deg\" : 2.0 }}} { \"name\" : \"session_gradients\" , \"arguments\" : { \"session_id\" : \"ses-…\" , \"of\" : [ \"cl\" ], \"wrt\" : [ \"alpha_deg\" , \"x_surface\" ]}} { \"name\" : \"session_evaluate\" , \"arguments\" : { \"session_id\" : \"ses-…\" , \"inputs\" : { \"alpha_deg\" : 2.5 }}} { \"name\" : \"close_session\" , \"arguments\" : { \"session_id\" : \"ses-…\" }} In the recorded exchange the evaluation at 2° converged in 16 nonlinear iterations to cl = 0.278767, the gradient call returned alpha_deg : 0.135059 (per degree) and a [128, 2] shape-sensitivity field summarised as {\"_array\": true, \"shape\": [128, 2], \"l2_norm\": 61.73, …} , and an evaluation at 2.5° warm-started from the previous flow needed 6 iterations ( cl = 0.345916).\nSee the design rules in the agent guide : one code path with the REST API, structured results with summarised arrays, errors that carry hints, bounded waits.\nThe validate → submit → result path and the hinted error are asserted end-to-end through MCP in tests/test_service.py::test_mcp_agent_workflow ; the session calls are asserted through the REST API (which the MCP tools call) in test_session_evaluate_gradients_and_coupling ; the deployment smoke test checks that the search index and llms.txt , which the documentation tools read, are served. The documentation tools themselves and the session tools through MCP are not covered by an automated test.","title":"An agent at work","url":"examples/agent-session.html"},{"audience":["MDO integrators"],"chunks":[{"anchor":"","end":1143,"heading":"","level":2,"start":0},{"anchor":"what-the-script-does","end":2962,"heading":"What the script does","level":2,"start":1144},{"anchor":"what-to-notice","end":3780,"heading":"What to notice","level":2,"start":2963}],"headings":["What the script does","What to notice"],"keywords":["remote session","GyreClient","RemoteDiscipline","evaluate","gradients","adjoint","tangent","checkpoint","restore_session","shape gradient","example"],"level":2,"max_level":2,"path":"examples/remote-session.md","section":"examples","section_title":"Examples","summary":"The calls an optimizer or MDO framework running on another machine would make on a warm session of a deployment — evaluate, gradients, adjoint and tangent products, a finite-difference cross-check, checkpoint and restore — shown at three fixed design points, with no optimizer.","text":"examples/remote_session.py shows what an external driver calls on a remote Gyre deployment. The script contains no optimizer and no loop that updates a design : it visits three fixed design points and makes, at each, the calls a driver would make. Gyre, somewhere on the network, evaluates the flow and returns exact derivatives; what to do with them is the business of the caller. .venv/bin/python examples/remote_session.py http://HOST:8600 HOST is the machine the deployment runs on (default http://127.0.0.1:8600 ). The client needs the gyre package (for gyre.client ) and NumPy; no flow is solved on the client. Note No recorded output on this page The pages of this section normally show numbers rendered from a verification study. This script has no such study and no output of it is quoted here. The script itself is executed, on a small case and against an in-process service, by tests/test_sessions.py::test_remote_example_calls_the_discipline_and_contains_no_optimizer , which checks that the adjoint and tangent products agree with the gradients, the gradient with the finite difference, and the restored session with the original.\nOpens a warm session on the preset naca0012_euler with GyreClient(url).session(...) and prints the inputs (with units) and outputs the session declares ( flow.describe() ). Reads the surface nodes ( flow.surface_coordinates() ) and builds one smooth camber mode on them: a displacement field of the shape of x_surface , zero at the leading and trailing edge. Visits the design points POINTS — (angle of attack in degrees, mode amplitude in chords) = (2.0, 0), (2.5, 0), (2.5, 2·10⁻³) — and at each calls out = flow . evaluate ( alpha_deg = alpha_deg , x_surface = amplitude * mode ) grad = flow . gradients ( of = [ \"cl\" , \"cm\" ], wrt = [ \"alpha_deg\" , \"x_surface\" ]) It stops with an error if out[\"converged\"] is false, and obtains the derivative with respect to the amplitude by the chain rule of its own parameterisation: the sum of the shape-sensitivity field grad[...][\"x_surface\"] times the mode. 4. At the last point, asks for products instead of whole gradients: flow.adjoint({\"cl\": 1.0, \"cm\": -0.5}, wrt=[\"alpha_deg\", \"x_surface\"]) (one transposed solve for a weighted combination of outputs) and flow.tangent({\"x_surface\": mode}, outputs=[\"cl\", \"cm\"]) (one forward solve along the mode), and prints both next to the values assembled from the gradients of step 3. 5. Cross-checks dcl/dalpha_deg against a central difference of two re-converged evaluations at alpha_deg ± 0.01 . 6. Takes a checkpoint ( flow.checkpoint() , bytes), lets the session close, rebuilds it with GyreClient.restore_session(blob) , asks it for its status ( flow.state() ) and re-evaluates: the restored session has the inputs and the converged state of the original. The forward product and the checkpoint are skipped, with a printed remark, on a deployment that answers 404 or 405 for those routes (one installed before they existed).\nThe cost of a gradient does not grow with the number of shape variables. The derivative with respect to every surface node comes back as one field per output; projecting it on one mode or two hundred costs the client a sum. Units. The session takes and differentiates with respect to alpha_deg (degrees); the in-process discipline would use alpha in radians ( outputs and units ). Warm starts. Each evaluation starts from the previous converged flow. Failures are data. If an evaluation does not converge, evaluate returns converged: false with a reason ; the script raises at that point rather than differentiating a state that is not a solution ( MDO integration , Troubleshooting ). The mode, the design points and the chain rule are client code. Nothing in the service knows what the caller does with the numbers.","title":"A remote session called at fixed design points","url":"examples/remote-session.html"},{"audience":[],"chunks":[{"anchor":"","end":4883,"heading":"","level":1,"start":0},{"anchor":"how-to-read-the-evidence","end":6678,"heading":"How to read the evidence","level":2,"start":4884},{"anchor":"reproduce-it","end":8473,"heading":"Reproduce it","level":2,"start":6679},{"anchor":"what-a-failing-study-looks-like","end":10514,"heading":"What a failing study looks like","level":3,"start":8474},{"anchor":"principles-behind-the-suite","end":11398,"heading":"Principles behind the suite","level":4,"start":10515},{"anchor":"pages-in-this-section","end":14998,"heading":"Pages in this section","level":1,"start":11399}],"headings":["How to read the evidence","Reproduce it","What a failing study looks like","Principles behind the suite","Pages in this section"],"keywords":["verification","validation","evidence","provenance"],"level":1,"max_level":4,"path":"verification/index.md","section":"verification","section_title":"Verification","summary":"Every numerical claim in this documentation is backed by an executable study. The studies run as a pipeline, write machine-readable results with provenance, and the charts and tables on these pages are rendered from those files.","text":"Verification asks whether the equations are solved correctly; validation asks whether they are the right equations. Gyre’s suite is mostly verification — exact properties and convergence rates that leave no room for interpretation — plus a small physical plausibility check. 39 of 39 studies passed Passed Summation-by-parts operators operators 1/1 checks · 0.29 s 39979fcf90 Same code as this build Passed Free-stream preservation on curved meshes free_stream 1/1 checks · 18.67 s 39979fcf90 Same code as this build Passed Entropy conservation and stability entropy_conservation 41/41 checks · 15.77 s 39979fcf90 Same code as this build Passed Structure of the BR2 viscous operator viscous_operator 17/17 checks · 116.9 s 39979fcf90 Same code as this build Passed Independent 1-D model of the BR2 formulation diffusion_1d 17/17 checks · 4.93 s 39979fcf90 Same code as this build Passed Exact linearization by automatic differentiation linearization 40/40 checks · 198.1 s 39979fcf90 Same code as this build Passed Differentiable mesh deformation mesh_deformation 9/9 checks · 2.45 s 39979fcf90 Same code as this build Passed Advected density wave density_wave 4/4 checks · 40.98 s 39979fcf90 Same code as this build Passed Isentropic vortex convection isentropic_vortex 10/10 checks · 65.18 s 39979fcf90 Same code as this build Passed Manufactured solution: Euler mms_euler 6/6 checks · 212.5 s 39979fcf90 Same code as this build Passed Manufactured solutions: Navier–Stokes mms_navier_stokes 14/14 checks · 1225 s 39979fcf90 Same code as this build Passed Nonlinear and linear solver convergence solver_convergence 16/16 checks · 890.1 s 39979fcf90 Same code as this build Passed Line-implicit smoothing on a wall-resolved mesh line_smoother 7/7 checks · 2160 s 39979fcf90 Same code as this build Passed Line-implicit smoothing in three dimensions line_smoother_3d 6/6 checks · 620.4 s 39979fcf90 Same code as this build Passed Single-precision blocks in the preconditioner mixed_precision 6/6 checks · 2649 s 39979fcf90 Same code as this build Passed Adjoint gradients versus finite differences adjoint_gradients 18/18 checks · 1176 s 39979fcf90 Same code as this build Passed Output error estimation output_error 5/5 checks · 367.7 s 39979fcf90 Same code as this build Passed Laminar cylinder at Re = 40 cylinder_re40 7/7 checks · 417.2 s 39979fcf90 Same code as this build Passed Three-dimensional consistency extruded_3d 8/8 checks · 300.8 s 39979fcf90 Same code as this build Passed Inviscid flow past a sphere (3-D) sphere_inviscid 9/9 checks · 424.7 s 39979fcf90 Same code as this build Passed Laminar flow past a sphere, Re = 100 (3-D) sphere_re100 5/5 checks · 187.8 s 39979fcf90 Same code as this build Passed Adjoint derivatives of 3-D outputs adjoint_3d 6/6 checks · 297.7 s 39979fcf90 Same code as this build Passed Largest 3-D solve on one device scale_3d 7/7 checks · 452 s 39979fcf90 Same code as this build Passed Manufactured solutions: RANS with the Spalart-Allmaras model rans_mms 9/9 checks · 455.8 s 39979fcf90 Same code as this build Passed Exact linearization with the Spalart-Allmaras model rans_linearization 20/20 checks · 158.6 s 39979fcf90 Same code as this build Passed Turbulent flat plate (NASA TMR) with the Spalart-Allmaras model rans_flat_plate 8/8 checks · 162.1 s 39979fcf90 Same code as this build Passed Three-dimensional consistency with the Spalart-Allmaras model rans_extruded_3d 10/10 checks · 675.1 s 39979fcf90 Same code as this build Passed Adjoint gradients with the Spalart-Allmaras model rans_adjoint 6/6 checks · 119 s 39979fcf90 Same code as this build Passed Discipline contract for MDO integration discipline_contract 15/15 checks · 249.9 s 39979fcf90 Same code as this build Passed Imported meshes imported_mesh 29/29 checks · 184.5 s 39979fcf90 Same code as this build Passed Output-driven order selection order_selection 13/13 checks · 364.4 s 39979fcf90 Same code as this build Passed Example: inviscid NACA 0012 example_airfoil 4/4 checks · 104.5 s 39979fcf90 Same code as this build Passed Example: laminar cylinder, Re = 40 example_cylinder 4/4 checks · 51.5 s 39979fcf90 Same code as this build Passed Example: turbulent flat plate (Spalart-Allmaras) example_flat_plate 5/5 checks · 119.9 s 39979fcf90 Same code as this build Passed Example: lift polar with adjoint slopes example_polar 5/5 checks · 686.1 s 39979fcf90 Same code as this build Passed Example: coupled aero-structural adjoint example_coupling 2/2 checks · 823 s 39979fcf90 Same code as this build Passed Example: transonic aerofoil with shock capturing example_transonic 9/9 checks · 151.8 s 39979fcf90 Same code as this build Passed Kernel throughput (cpu) performance_cpu 2/2 checks · 123.8 s 39979fcf90 Same code as this build Passed Kernel throughput (NVIDIA GeForce RTX 3060 Ti) performance_gpu 2/2 checks · 103.5 s 39979fcf90 Same code as this build\nEach study produces checks (a measured value, the expectation, pass or fail), tables and charts , and a provenance stamp : the source revision, the compute device and the time of the run. A page built from a different revision than the one deployed says so in its stamp. Nothing on these pages is typed in by hand. Layer Question Studies Discrete invariants Do exact algebraic properties hold to round-off? operators , free_stream , entropy_conservation , viscous_operator Order of accuracy Does the error fall at the designed rate? density_wave , isentropic_vortex , mms_euler , mms_navier_stokes , diffusion_1d Derivatives Are linearisations and gradients exact? linearization , adjoint_gradients , mesh_deformation Solvers Do the iterations converge as theory predicts, and does the answer stay the same when the preconditioner changes? solver_convergence , line_smoother , line_smoother_3d Output error Is the error estimate trustworthy? output_error Order selection Does the estimate select the lowest sufficient degree, and say so when none suffices? order_selection Three-dimensional flows Does a genuinely 3-D flow on an unstructured hexahedral mesh come out right, with its derivatives? sphere_inviscid , sphere_re100 , adjoint_3d , scale_3d , extruded_3d Turbulence closure Is the RANS model discretized at design order, linearised exactly and implemented as published? rans_mms , rans_linearization , rans_flat_plate , rans_adjoint Discipline contract Are the operations an MDO framework calls consistent with each other? discipline_contract Imported meshes Does a mesh from a file behave exactly like a generated one? imported_mesh Physical validation Does a canonical flow come out right? cylinder_re40 Testing strategy What guards every commit? unit, integration, end-to-end tests\ngyre verify --suite quick -o var/verification # all studies gyre verify --only isentropic_vortex,mms_euler # a subset gyre docs build # re-render these pages from the new results The quick suite is not short: its last complete run on the development host (39 studies in three parallel lanes, CPU plus an RTX 3060 Ti) took 104 minutes, and the studies’ own wall times, shown on each study card, add up to 273 minutes. A subset such as --only free_stream,entropy_conservation takes about a minute. --suite full adds finer meshes and higher degrees. Each study runs in its own process on the device suited to its size; results land in var/verification/<study>.json . The timing studies run last, one at a time, and the GPU timing study is skipped on a host without a GPU. index.json lists every result file in the directory, not only those of the last invocation. Each entry carries the level, commit, device and timestamp of the run that produced it, and stale: true if that was an earlier invocation. requested , skipped and summary describe the last invocation. gyre verify ends with the line P/R studies of this invocation passed and exits with status 1 only when a study of this invocation failed; failing results that an earlier invocation left in the directory are named separately ( ; stale failing results not re-run: ... ) and do not change the exit status. On these pages a status block or study card of a result that the last invocation did not re-run carries the marker “Earlier run”; its tooltip names the commit and time of the run that produced the result. Such a block shows the checks, thresholds, tables and notes of that run. The text of a page describes the studies as they are in the code now, so the two can differ until the study is run again with gyre verify --only <id> .\nOrder checks have one of three kinds of threshold, and each names its basis in its expected text: the design rate , for example “observed order at least p + 1 - 0.2 ”, where that rate is established; the guaranteed rate , “at least p ” with a tolerance, where only consistency is guaranteed; an empirical regression floor between the two, where a rate above p was measured but not derived: p + 0.35 for the manufactured Euler solution on coarse curved meshes and p + 0.4 for the velocity in the diffusion-dominated Navier–Stokes regimes. A floor of this kind guards against regressions; it is not a property of the scheme. Bounds on timing ratios and on the ratio of actual to estimated error in the order-selection study are regression bounds of the same kind and say so. When a study fails, its badge on these pages turns red and lists the failing checks, the CI pipeline stops before deployment, and the study’s log is kept under var/verification/logs/ . Failures during development were instructive: The vortex study at p = 4 measured order 4.05 against an expectation of at least 4.4. The first explanation — time-integration error — was tested by shrinking the time step five-fold and was refuted : the error did not change. The expectation was wrong, not the measurement; the study now checks the guaranteed rate, reports the observed one, and keeps the time-step test as a permanent check. The mesh-deformation derivative check compared against a finite difference of the whole domain area, a badly conditioned reference; it now uses a weighted area concentrated near the surface. A finite-difference step sweep started with a step of 0.1 rad, which pushed the flow into a regime with shocks; the failed solve then contaminated every later point. Perturbed solves now restart from the baseline state and must converge. The output-error study included a mesh so coarse that the bump was sampled at a single node; its “errors” were those of a different geometry. In each case the suite did its job: it made a wrong assumption visible.\nPrefer exact properties over convergence rates. Round-off-level identities (conservation, entropy conservation, symmetry, dot-product tests) cannot be passed by accident. Test the negative. Fluxes that should not conserve entropy are checked to violate the condition; otherwise a broken test would pass everything. Independent references. Derivatives are compared against finite differences and against the other differentiation mode; the error estimator is compared against an actually converged finer solution; block preconditioners against a dense Jacobian. Manufactured forcing from the solver’s own physics. Source terms are generated by automatic differentiation of the flux kernels, eliminating transcription errors. Separate regimes. Where behaviour differs by regime (diffusion- versus convection-dominated), the study reports them separately instead of averaging them away.\nDiscrete invariants L2 Summation by parts, free-stream preservation, conservation and entropy conservation are algebraic identities of the scheme. They hold to round-off, in two and three dimensions, on curved meshes. Order of accuracy L2 Convergence studies with exact solutions. The design rate p + 1 is required where it is established (the advected density wave, steady manufactured solutions on Cartesian meshes, heat conduction, convection-dominated flow); elsewhere the guaranteed rate p is required and the observed rate is reported. Linearisation and gradients L2 Jacobian products, preconditioner blocks, adjoint and direct total derivatives are verified against finite differences, against each other and against dense reference Jacobians. Agreement with finite differences is limited by finite-difference accuracy; adjoint and direct mode agree to the tolerance of their linear solves. Solver convergence L2 The pseudo-transient Newton–Krylov solver reduces the residual by ten orders from a free stream; its terminal convergence is linear at the forcing rate of the inexact Newton iteration, and p-multigrid preconditioning cuts the Krylov iteration count compared with block Jacobi alone. What those iterations cost is measured and reported without a threshold: operator applications counted from the cycle, and the wall time of each solve repeated with compiled kernels. Three-dimensional flows L2 Flow past a sphere on an unstructured cubed-sphere mesh of curved hexahedra tests the 3-D path on a genuinely three-dimensional flow, inviscid against exact properties and laminar against accepted data, with adjoint derivatives of all six force and moment coefficients; a spanwise extrusion of the aerofoil case must reproduce the two-dimensional solution. Turbulence closure L2 The Spalart–Allmaras RANS closure is verified in four steps: a manufactured solution for the order of accuracy of the coupled system, exactness of its linearisation including the wall distance, the turbulent flat plate of the NASA Turbulence Modeling Resource for the model itself, and adjoint derivatives against finite differences. Discipline contract L2 The interface a multidisciplinary framework integrates against is itself verified. Forward and reverse products are exact transposes, the linear solves invert them, the products agree with finite differences at the solution and at a point the framework names, a stalled linear solve is never silent, and the OpenMDAO implicit component passes OpenMDAO's own derivative checks. Output error estimation L2 The adjoint-weighted-residual estimate predicts the change of an output between polynomial degree p and p + 1 from a single linear solve. Its effectivity is measured against the actually converged p + 1 solution. Order selection L2 Output-driven order selection raises the polynomial degree until the estimated error of an output meets a tolerance. The study checks the selection rule itself, compares the estimate with solutions two degrees higher, and cross-checks every continued level against an independent solve. Imported meshes L2 A mesh read from a Gmsh or NPZ file must behave exactly like a generated one. The study reads genuine Gmsh files, tests the node-ordering tables, the operator identities on unstructured curved meshes in 2-D and 3-D, and solves a flow with an adjoint derivative on an imported mesh. Physical validation L2 A canonical viscous flow — the steady wake of a circular cylinder at Reynolds number 40 — is compared with the literature as a plausibility check on the physics, boundary conditions and force evaluation.","title":"Verification","url":"verification/index.html"},{"audience":[],"chunks":[{"anchor":"","end":121,"heading":"","level":2,"start":0},{"anchor":"summation-by-parts-operators","end":1049,"heading":"Summation-by-parts operators","level":2,"start":122},{"anchor":"what-is-being-checked","end":1481,"heading":"What is being checked","level":3,"start":1050},{"anchor":"free-stream-preservation","end":2581,"heading":"Free-stream preservation","level":2,"start":1482},{"anchor":"why-the-3-d-rows-matter","end":2957,"heading":"Why the 3-D rows matter","level":3,"start":2582},{"anchor":"conservation-and-entropy","end":8267,"heading":"Conservation and entropy","level":2,"start":2958},{"anchor":"reading-the-chart","end":9982,"heading":"Reading the chart","level":2,"start":8268},{"anchor":"structure-of-the-viscous-operator","end":12428,"heading":"Structure of the viscous operator","level":2,"start":9983},{"anchor":"interpretation","end":12827,"heading":"Interpretation","level":3,"start":12429}],"headings":["Summation-by-parts operators","What is being checked","Free-stream preservation","Why the 3-D rows matter","Conservation and entropy","Reading the chart","Structure of the viscous operator","Interpretation"],"keywords":["SBP","free-stream preservation","conservation","entropy conservation","symmetry"],"level":2,"max_level":3,"path":"verification/discrete-invariants.md","section":"verification","section_title":"Verification","summary":"Summation by parts, free-stream preservation, conservation and entropy conservation are algebraic identities of the scheme. They hold to round-off, in two and three dimensions, on curved meshes.","text":"These studies do not measure convergence; they check identities that are either satisfied to machine precision or broken.\nPassed Summation-by-parts operators 1 of 1 checks passed · 0.29 s · quick suite Gauss–Lobatto collocation operators: the SBP identity M D + (M D)^T = B, quadrature exactness to degree 2p-1, exact differentiation of degree-p polynomials, skew-symmetry of the flux-differencing matrix M S and consistency of the p-multigrid transfers. largest defect over all identities and degrees 6.22e-15 expected < 1e-11 pass Maximum defects per polynomial degree Summation-by-parts operators p SBP quadrature (deg <= 2p-1) differentiation skew(M S) Pi I - 1 1 0 0 0 0 2.2204e-16 2 0 2.2204e-16 0 0 4.9960e-16 3 3.3307e-16 5.5511e-17 8.8818e-16 4.4409e-16 0 4 1.1102e-15 3.3307e-16 1.7764e-15 8.8818e-16 4.4409e-16 5 1.3866e-15 2.2204e-16 2.6645e-15 1.9984e-15 4.4409e-16 6 2.2128e-15 1.9429e-16 4.4409e-15 1.5543e-15 4.4409e-15 7 1.8936e-15 1.1102e-16 3.1086e-15 2.2204e-15 1.8874e-15 8 2.5535e-15 4.4409e-16 6.2172e-15 3.7748e-15 1.1423e-15\nFor every polynomial degree: the SBP identity \\mathbf M\\mathbf D + (\\mathbf M\\mathbf D)^{\\mathsf T} = \\mathbf B ; exactness of LGL quadrature up to degree 2p-1 (and, in the unit tests, inexactness at 2p ); exact differentiation of polynomials up to degree p ; skew-symmetry of the flux-differencing matrix \\mathbf M\\mathbf S ; and that projection after prolongation is the identity on the coarse space of the p-multigrid transfers.\nPassed Free-stream preservation on curved meshes 1 of 1 checks passed · 18.67 s · quick suite A uniform flow must remain an exact steady state on arbitrarily curved elements. This requires discrete metric identities: cross-product metrics in 2-D and the conservative-curl form in 3-D. Meshes are smoothly warped periodic boxes (6 % amplitude); the table reports the largest weak residual of the uniform state and the closure of each element's discrete surface (geometric conservation law). max |Rw| of uniform flow and GCL defect 2.91e-15 expected < 1e-11 pass Uniform-flow residual on warped meshes Free-stream preservation on curved meshes dim elements p geometry degree viscous min J / max J max |Rw| surface closure 2-D 6x5 3 4 no 0.451047 1.3492e-15 3.0531e-16 2-D 6x5 3 4 yes 0.451047 1.3368e-15 3.0531e-16 2-D 4x4 5 4 no 0.453556 2.8560e-15 4.9960e-16 2-D 4x4 5 4 yes 0.453556 2.9091e-15 4.9960e-16 3-D 3x3x3 2 2 no 0.276159 2.6316e-16 3.4694e-17 3-D 3x3x3 2 2 yes 0.276159 2.6316e-16 3.4694e-17 3-D 3x3x3 3 3 no 0.417089 3.2526e-16 6.9823e-17 3-D 3x3x3 3 3 yes 0.417089 3.2823e-16 6.9823e-17\nIn three dimensions the naive cross-product metrics break the discrete metric identities on curved elements; the conservative curl form restores them. The 3-D rows of the table are therefore a direct test of that construction. The last column is the closure of each element’s discrete surface, \\sum_\\text{faces}\\int \\mathbf n\\,\\mathrm dS = 0 , the geometric conservation law.\nPassed Entropy conservation and stability 41 of 41 checks passed · 15.77 s · quick suite The rate of change of the discrete mathematical entropy, -w^T Rw, is evaluated on warped periodic meshes for combinations of two-point volume fluxes and interface fluxes. The state is a periodic field carrying a 3 % node-wise perturbation, so it jumps across every element face, the curved interior faces included. Split-form DGSEM with an entropy-conservative two-point flux satisfies a discrete entropy balance exactly for any nodal state: the checks require the rate to vanish to round-off for the entropy-conservative pairings, to be clearly non-zero for the volume fluxes that are not entropy conservative, and to be negative, well above round-off, when an upwind interface flux adds dissipation. A second table tests the interface fluxes alone on random pairs of states far apart: the entropy-stable ones (llf, es_roe) must produce no entropy for any pair, which is the property the proof rests on; the classical solvers are shown for comparison. 2-D ranocha / ec: |d/dt entropy| 5.98e-15 expected < 1e-11 pass 2-D ranocha / ec: conservation defect 4.46e-14 expected < 1e-11 pass 2-D chandrashekar / ec: |d/dt entropy| 8.10e-15 expected < 1e-11 pass 2-D chandrashekar / ec: conservation defect 4.88e-14 expected < 1e-11 pass 2-D kennedy_gruber / ec: not entropy conservative 5.98e-04 expected > 1e-8 pass 2-D kennedy_gruber / ec: conservation defect 4.24e-14 expected < 1e-11 pass 2-D central / ec: not entropy conservative 4.57e-04 expected > 1e-8 pass 2-D central / ec: conservation defect 4.31e-14 expected < 1e-11 pass 2-D ranocha / llf: entropy dissipated -0.0551 expected < -1e-8 pass 2-D ranocha / llf: conservation defect 4.66e-14 expected < 1e-11 pass 2-D ranocha / es_roe: entropy dissipated -0.01835 expected < -1e-8 pass 2-D ranocha / es_roe: conservation defect 4.46e-14 expected < 1e-11 pass 2-D chandrashekar / es_roe: entropy dissipated -0.01835 expected < -1e-8 pass 2-D chandrashekar / es_roe: conservation defect 4.73e-14 expected < 1e-11 pass 2-D ranocha / roe: entropy dissipated -0.01835 expected < -1e-8 pass 2-D ranocha / roe: conservation defect 4.13e-14 expected < 1e-11 pass 2-D ranocha / hllc: entropy dissipated -0.01834 expected < -1e-8 pass 2-D ranocha / hllc: conservation defect 4.09e-14 expected < 1e-11 pass 3-D ranocha / ec: |d/dt entropy| 3.69e-15 expected < 1e-11 pass 3-D ranocha / ec: conservation defect 1.20e-14 expected < 1e-11 pass 3-D chandrashekar / ec: |d/dt entropy| 3.52e-15 expected < 1e-11 pass 3-D chandrashekar / ec: conservation defect 1.17e-14 expected < 1e-11 pass 3-D kennedy_gruber / ec: not entropy conservative 0.001198 expected > 1e-8 pass 3-D kennedy_gruber / ec: conservation defect 2.69e-15 expected < 1e-11 pass 3-D central / ec: not entropy conservative 0.007444 expected > 1e-8 pass 3-D central / ec: conservation defect 1.82e-15 expected < 1e-11 pass 3-D ranocha / llf: entropy dissipated -0.05432 expected < -1e-8 pass 3-D ranocha / llf: conservation defect 1.18e-14 expected < 1e-11 pass 3-D ranocha / es_roe: entropy dissipated -0.01778 expected < -1e-8 pass 3-D ranocha / es_roe: conservation defect 1.16e-14 expected < 1e-11 pass 3-D chandrashekar / es_roe: entropy dissipated -0.01778 expected < -1e-8 pass 3-D chandrashekar / es_roe: conservation defect 1.23e-14 expected < 1e-11 pass 3-D ranocha / roe: entropy dissipated -0.01783 expected < -1e-8 pass 3-D ranocha / roe: conservation defect 1.36e-15 expected < 1e-11 pass 3-D ranocha / hllc: entropy dissipated -0.01782 expected < -1e-8 pass 3-D ranocha / hllc: conservation defect 8.19e-16 expected < 1e-11 pass 2-D llf: largest two-state entropy production over 5000 random pairs -0.05717 expected < 0 pass 2-D es_roe: largest two-state entropy production over 5000 random pairs -0.03018 expected < 0 pass 3-D llf: largest two-state entropy production over 5000 random pairs -0.2282 expected < 0 pass 3-D es_roe: largest two-state entropy production over 5000 random pairs -0.04117 expected < 0 pass classical solvers (roe, hllc) violate the two-state inequality for some pairs (cases of 4) 2 expected >= 1 (why they carry no proof) pass Entropy production rate by flux pairing Entropy conservation and stability Measured here: with an entropy-conservative volume flux and the dissipation-free interface flux the entropy rate is at most 8.1e-15 in magnitude; with the upwind interface fluxes it lies between -5.5e-02 and -1.8e-02. Entropy rate and conservation defect Entropy conservation and stability dim volume flux interface flux d/dt ∫ η |Σ Rw| 2-D ranocha ec -5.9781e-15 4.4631e-14 2-D chandrashekar ec -8.0969e-15 4.8850e-14 2-D kennedy_gruber ec -5.9810e-04 4.2411e-14 2-D central ec -4.5667e-04 4.3077e-14 2-D ranocha llf -0.0550953 4.6629e-14 2-D ranocha es_roe -0.0183493 4.4631e-14 2-D chandrashekar es_roe -0.0183493 4.7296e-14 2-D ranocha roe -0.0183487 4.1300e-14 2-D ranocha hllc -0.0183448 4.0856e-14 3-D ranocha ec -3.6880e-15 1.1970e-14 3-D chandrashekar ec -3.5232e-15 1.1685e-14 3-D kennedy_gruber ec -0.00119758 2.6854e-15 3-D central ec -0.00744448 1.8180e-15 3-D ranocha llf -0.0543174 1.1796e-14 3-D ranocha es_roe -0.0177799 1.1602e-14 3-D chandrashekar es_roe -0.0177799 1.2261e-14 3-D ranocha roe -0.0178301 1.3600e-15 3-D ranocha hllc -0.0178184 8.1879e-16\nThe state of the study is a smooth periodic field with a node-wise random perturbation of 3 %, so every interface carries a jump and the interface flux is exercised on every face, curved interior faces included. With an entropy-conservative volume flux and a dissipation-free interface flux ( ec ), the discrete entropy changes only at round-off level on a warped mesh — it is conserved. The Kennedy–Gruber and central fluxes, which are not entropy conservative, produce or destroy entropy at a rate many orders of magnitude larger (see the table). With upwind interface fluxes ( llf , es_roe , roe , hllc ) the rate must be negative with a margin: the check requires a rate below -10^{-8} , three orders above the tolerance of the conservative pairings, so that a round-off value of either sign cannot pass. Entropy is dissipated, as the second law requires. For small jumps the matrix dissipation of es_roe removes entropy at the rate of the classical Roe flux, about a third of the scalar dissipation of llf in this study. Entropy production of the interface flux alone, random state pairs (Ranocha two-point flux) Entropy conservation and stability dim interface flux pairs largest production pairs with production > 0 [%] 2-D llf 5000 -0.0571705 0 2-D es_roe 5000 -0.0301779 0 2-D roe 5000 1365.06 18.5 2-D hllc 5000 -0.0156571 0 3-D llf 5000 -0.228196 0 3-D es_roe 5000 -0.0411714 0 3-D roe 5000 812.676 19.18 3-D hllc 5000 -0.0401914 0 The second table tests the interface fluxes alone, on random pairs of states far apart: the entropy-stable fluxes produce no entropy for any pair, the classical Roe flux does for a share of them ( why ). In every case mass, momentum and energy are conserved to round-off.\nPassed Structure of the BR2 viscous operator 17 of 17 checks passed · 116.9 s · quick suite At a quiescent state the energy–energy and momentum–momentum blocks of the weak-form Jacobian are exactly the discrete heat-conduction and viscous-stress operators. Their symmetry (the hallmark of an adjoint-consistent diffusion discretization) and positive semi-definiteness are checked on Cartesian and warped meshes with periodic and Dirichlet boundaries, and the Jacobian sparsity pattern is checked to be compact (face neighbours only), which element-block preconditioning relies on. warp=0.0, periodic, p=2: asymmetry 2.70e-16 expected < 1e-12 pass warp=0.0, periodic, p=2: smallest eigenvalue -5.45e-16 expected >= -1e-12 pass warp=0.0, periodic, p=3: asymmetry 2.54e-16 expected < 1e-12 pass warp=0.0, periodic, p=3: smallest eigenvalue -1.89e-15 expected >= -1e-12 pass warp=0.0, Dirichlet, p=2: asymmetry 1.17e-16 expected < 1e-12 pass warp=0.0, Dirichlet, p=2: smallest eigenvalue 0.02524 expected >= -1e-12 pass warp=0.0, Dirichlet, p=3: asymmetry 1.44e-16 expected < 1e-12 pass warp=0.0, Dirichlet, p=3: smallest eigenvalue 0.0147 expected >= -1e-12 pass warp=0.05, periodic, p=2: asymmetry 2.82e-16 expected < 1e-12 pass warp=0.05, periodic, p=2: smallest eigenvalue 5.54e-17 expected >= -1e-12 pass warp=0.05, periodic, p=3: asymmetry 2.93e-16 expected < 1e-12 pass warp=0.05, periodic, p=3: smallest eigenvalue -5.08e-16 expected >= -1e-12 pass warp=0.05, Dirichlet, p=2: asymmetry 1.60e-16 expected < 1e-12 pass warp=0.05, Dirichlet, p=2: smallest eigenvalue 0.02478 expected >= -1e-12 pass warp=0.05, Dirichlet, p=3: asymmetry 1.64e-16 expected < 1e-12 pass warp=0.05, Dirichlet, p=3: smallest eigenvalue 0.0144 expected >= -1e-12 pass element couplings outside the face-neighbour stencil 0 expected == 0 pass Relative asymmetry and smallest eigenvalue Structure of the BR2 viscous operator warp boundaries p asym. (heat) asym. (stress) min eig (heat) min eig (stress) 0 periodic 2 2.7046e-16 2.0254e-16 -5.4470e-16 6.5560e-16 0 periodic 3 2.2839e-16 2.5421e-16 1.1123e-16 -1.8926e-15 0 Dirichlet 2 7.9058e-17 1.1709e-16 0.0423156 0.0252442 0 Dirichlet 3 6.5254e-17 1.4417e-16 0.0246366 0.0147011 0.05 periodic 2 1.9043e-16 2.8210e-16 1.5375e-15 5.5369e-17 0.05 periodic 3 2.3864e-16 2.9273e-16 1.1192e-15 -5.0847e-16 0.05 Dirichlet 2 5.4250e-17 1.5964e-16 0.0417777 0.0247796 0.05 Dirichlet 3 1.3399e-16 1.6369e-16 0.0242548 0.0143994\nAt a quiescent state the energy–energy block of the Jacobian is the discrete heat-conduction operator and the momentum–momentum block is the discrete viscous-stress operator (including the cross-derivative coupling). Both are symmetric to round-off and positive semi-definite, on warped meshes and with Dirichlet walls. A separate check confirms that the full Jacobian only couples face neighbours.","title":"Discrete invariants","url":"verification/discrete-invariants.html"},{"audience":[],"chunks":[{"anchor":"advected-density-wave","end":1199,"heading":"Advected density wave","level":2,"start":0},{"anchor":"table","end":1526,"heading":"Table","level":3,"start":1200},{"anchor":"isentropic-vortex","end":3913,"heading":"Isentropic vortex","level":2,"start":1527},{"anchor":"what-the-study-requires","end":4602,"heading":"What the study requires","level":2,"start":3914},{"anchor":"table-and-the-time-step-test","end":5761,"heading":"Table, and the time-step test","level":3,"start":4603},{"anchor":"manufactured-solution-euler","end":7594,"heading":"Manufactured solution: Euler","level":2,"start":5762},{"anchor":"set-up","end":9241,"heading":"Set-up","level":3,"start":7595},{"anchor":"manufactured-solutions-navierstokes","end":14135,"heading":"Manufactured solutions: Navier–Stokes","level":2,"start":9242},{"anchor":"regimes-are-checked-differently","end":15007,"heading":"Regimes are checked differently","level":2,"start":14136},{"anchor":"why-pressure-may-lag-when-diffusion-dominates-a-hypothesis","end":17680,"heading":"Why pressure may lag when diffusion dominates (a hypothesis)","level":4,"start":15008}],"headings":["Advected density wave","Table","Isentropic vortex","What the study requires","Table, and the time-step test","Manufactured solution: Euler","Set-up","Manufactured solutions: Navier–Stokes","Regimes are checked differently","Why pressure may lag when diffusion dominates (a hypothesis)"],"keywords":["convergence","manufactured solution","isentropic vortex","order","regime"],"level":2,"max_level":4,"path":"verification/order-of-accuracy.md","section":"verification","section_title":"Verification","summary":"Convergence studies with exact solutions. The design rate p + 1 is required where it is established (the advected density wave, steady manufactured solutions on Cartesian meshes, heat conduction, convection-dominated flow); elsewhere the guaranteed rate p is required and the observed rate is reported.","text":"Passed Advected density wave 4 of 4 checks passed · 40.98 s · quick suite A smooth density wave carried by a uniform flow is an exact solution of the Euler equations. It isolates the convective operator and the explicit time integrator. The density is advected linearly, so the design order p + 1 of the split-form DGSEM with a Roe interface flux is what the checks require. p = 1: observed order (finest pair) 1.926 expected >= 1.80 (design rate p + 1 = 2, tolerance 0.2) pass p = 2: observed order (finest pair) 2.994 expected >= 2.80 (design rate p + 1 = 3, tolerance 0.2) pass p = 3: observed order (finest pair) 3.998 expected >= 3.80 (design rate p + 1 = 4, tolerance 0.2) pass p = 4: observed order (finest pair) 5.011 expected >= 4.80 (design rate p + 1 = 5, tolerance 0.2) pass Run notes Observed order minus the design rate p + 1 on the finest mesh pair, over all degrees: -0.07 to +0.01. Density error after convection Advected density wave A density wave carried by a uniform flow exercises the convective operator, the interface flux and the time integrator in a setting where the exact solution is known. The study requires the design order p+1 (tolerance 0.2) for every degree tested.\nErrors and observed orders Advected density wave p N L2 error order 1 8 0.0244511 — 1 16 0.00716847 1.77016 1 32 0.00188612 1.92624 2 8 0.00125525 — 2 16 1.6201e-04 2.95377 2 32 2.0333e-05 2.99425 3 8 5.2276e-05 — 3 16 3.4487e-06 3.92201 3 32 2.1592e-07 3.99753 4 8 2.0736e-06 — 4 16 6.8806e-08 4.91346 4 32 2.1334e-09 5.01128\nPassed Isentropic vortex convection 10 of 10 checks passed · 65.18 s · quick suite A strong isentropic vortex convected through a periodic domain for one time unit, on Cartesian meshes and on meshes warped by 4 % of the domain size (degree-4 geometry). The order checks require the rate p that consistency guarantees, not the design rate p + 1, which is not established for this strongly nonlinear unsteady problem. One case is repeated with a five-fold smaller time step to test whether the measured error is spatial, and the error on curved meshes is required to stay within a factor 1.5 of the Cartesian one. p = 2, Cartesian: observed order (finest pair) 2.596 expected >= 1.80 (guaranteed rate p = 2, tolerance 0.2; design rate 3) pass p = 3, Cartesian: observed order (finest pair) 3.486 expected >= 2.80 (guaranteed rate p = 3, tolerance 0.2; design rate 4) pass p = 4, Cartesian: observed order (finest pair) 4.051 expected >= 3.80 (guaranteed rate p = 4, tolerance 0.2; design rate 5) pass p = 2, curved: observed order (finest pair) 2.628 expected >= 1.80 (guaranteed rate p = 2, tolerance 0.2; design rate 3) pass p = 2: curved / Cartesian error on the finest mesh 1.006 expected < 1.5 pass p = 3, curved: observed order (finest pair) 3.525 expected >= 2.80 (guaranteed rate p = 3, tolerance 0.2; design rate 4) pass p = 3: curved / Cartesian error on the finest mesh 0.9722 expected < 1.5 pass p = 4, curved: observed order (finest pair) 4.173 expected >= 3.80 (guaranteed rate p = 4, tolerance 0.2; design rate 5) pass p = 4: curved / Cartesian error on the finest mesh 0.996 expected < 1.5 pass time-step independence (p = 4, N = 16): relative change of the error for a 5x smaller step 8.79e-08 expected < 1e-3 pass Run notes Observed orders on the finest mesh pair: p = 2: 2.60 (Cartesian), 2.63 (curved); p = 3: 3.49 (Cartesian), 3.53 (curved); p = 4: 4.05 (Cartesian), 4.17 (curved). All of them lie between the guaranteed rate p and the design rate p + 1. Observed order minus the design rate: -0.95 to -0.37. A five-fold smaller time step changed the error of the p = 4, N = 16 case by a relative 8.8e-08: at that resolution the error is spatial, so time integration does not explain a rate below p + 1. Curved-to-Cartesian error ratio on the finest meshes: 0.97 to 1.01. Density error at t = 1 Isentropic vortex convection Reference lines show the design rate p + 1.\nFor this strongly nonlinear unsteady problem the design rate p+1 is not established, so the order checks require the rate p that consistency guarantees (tolerance 0.2). Reduced rates are a documented characteristic of Gauss–Lobatto collocation schemes for nonlinear problems. Two further checks: the error on curved meshes (4 % warp, degree-4 geometry) must stay within a factor 1.5 of the Cartesian one at each degree, and one case is repeated with a five-fold smaller time step. The orders that were observed, their distance from the design rate and the curved-to-Cartesian error ratios are stated in the run notes under the status above, which the study generates from its own numbers.\nErrors and observed orders Isentropic vortex convection mesh p N L2 error order Cartesian 2 16 0.00272098 — Cartesian 2 32 4.6335e-04 2.55397 Cartesian 2 64 7.6634e-05 2.59602 Cartesian 3 16 4.8464e-04 — Cartesian 3 32 4.3365e-05 3.48233 Cartesian 3 64 3.8697e-06 3.48624 Cartesian 4 8 0.00140628 — Cartesian 4 16 7.4780e-05 4.23308 Cartesian 4 32 4.5114e-06 4.051 curved 2 16 0.00278432 — curved 2 32 4.7664e-04 2.54636 curved 2 64 7.7106e-05 2.62798 curved 3 16 5.0349e-04 — curved 3 32 4.3313e-05 3.5391 curved 3 64 3.7620e-06 3.52522 curved 4 8 0.00151026 — curved 4 16 8.1038e-05 4.22005 curved 4 32 4.4933e-06 4.17275 A fourth-order time integrator at fixed CFL number has an error that falls like h^4 , so a measured order near 4 at p = 4 invites the explanation that time integration dominates. The study tests this directly: a p = 4 case is repeated with a five-fold smaller time step, and the check requires the error to change by less than one part in a thousand. When it passes, the error at that resolution is spatial and time integration does not explain a rate below p + 1 ; the run notes state which conclusion the numbers of the run support.\nPassed Manufactured solution: Euler 6 of 6 checks passed · 212.5 s · quick suite A smooth trigonometric state is forced to be an exact steady solution by a source term generated with automatic differentiation of the physics kernels. Dirichlet data enter weakly through the interface Riemann flux. The nonlinear system is solved with the pseudo-transient Newton–Krylov solver, started from the nodal values of the exact solution, whose residual is the truncation error. The study therefore verifies the spatial operator and the boundary treatment, and that the implicit solver removes that residual; it does not test convergence from a distant initial state (see the solver-convergence study). Curved meshes are warped by 5 % with degree-4 geometry. p = 1, Cartesian: observed order (finest pair) 1.911 expected >= 1.75 (design rate p + 1 = 2, tolerance 0.25) pass p = 2, Cartesian: observed order (finest pair) 2.969 expected >= 2.75 (design rate p + 1 = 3, tolerance 0.25) pass p = 3, Cartesian: observed order (finest pair) 3.979 expected >= 3.75 (design rate p + 1 = 4, tolerance 0.25) pass p = 1, curved: observed order (finest pair) 1.873 expected >= 1.35 (empirical floor p + 0.35 for coarse warped meshes; guaranteed rate 1, design rate 2) pass p = 2, curved: observed order (finest pair) 2.597 expected >= 2.35 (empirical floor p + 0.35 for coarse warped meshes; guaranteed rate 2, design rate 3) pass p = 3, curved: observed order (finest pair) 3.825 expected >= 3.35 (empirical floor p + 0.35 for coarse warped meshes; guaranteed rate 3, design rate 4) pass Error of the conserved state Manufactured solution: Euler Observed order minus the design rate p + 1 on the finest mesh pair: Cartesian -0.09 to -0.02, curved -0.40 to -0.13. On the curved meshes the observed order increased with every refinement for every degree.\nA smooth trigonometric state with \\pm 10\\,\\% variations around a Mach 0.5 mean flow is made an exact steady solution by a source term. The source is generated by forward-mode automatic differentiation of the same flux kernels the solver uses ( gyre.mms.manufactured_source ), so no hand-derived expression can be wrong. Dirichlet data enter through the interface Riemann flux. The Newton iteration starts from the nodal values of the exact solution, whose residual is the truncation error, and is converged to 10^{-11} of the residual of a uniform state; convergence from a distant state is the subject of the solver-convergence study . Cartesian meshes are checked against the design rate p + 1 (tolerance 0.25). On the curved meshes (5 % warp, degree-4 geometry) the check is p + 0.35 : an empirical regression floor between the guaranteed rate p and the design rate, not a bound from theory. The chart note is generated from the run: the observed order minus the design rate, and whether the curved-mesh order rose under refinement. Errors and observed orders Manufactured solution: Euler mesh p N L2 error order Cartesian 1 4 0.0753256 — Cartesian 1 8 0.0209751 1.84446 Cartesian 1 16 0.00557784 1.9109 Cartesian 2 4 0.00448636 — Cartesian 2 8 5.8940e-04 2.92824 Cartesian 2 16 7.5265e-05 2.96919 Cartesian 3 4 1.8968e-04 — Cartesian 3 8 1.2419e-05 3.93293 Cartesian 3 16 7.8738e-07 3.9794 curved 1 4 0.0809866 — curved 1 8 0.0228986 1.82242 curved 1 16 0.00625139 1.87301 curved 2 4 0.00743004 — curved 2 8 0.00126828 2.55049 curved 2 16 2.0961e-04 2.59713 curved 3 4 0.00117036 — curved 3 8 9.6953e-05 3.59353 curved 3 16 6.8413e-06 3.82494\nPassed Manufactured solutions: Navier–Stokes 14 of 14 checks passed · 1225 s · quick suite Viscous manufactured solutions with Dirichlet walls, solved implicitly with p-multigrid from the nodal values of the exact solution (the solve removes the truncation-error residual). 4 regimes are separated: heat conduction (quiescent); diffusion dominated, Re = 10; diffusion dominated, no mean flow, Re = 10; convection dominated, Re = 1000. Orders are reported per primitive variable. Every regime must reach the rate p that consistency guarantees; heat conduction and convection-dominated flow, where the coupling between pressure and velocity is absent or hyperbolic, are also checked against the design rate p + 1. In the diffusion-dominated regimes, where only the rate p is required of the state, the velocity is held to an empirical floor of p + 0.4 between the meshes 8 → 16; the floor was measured on that pair and is not a property of the scheme, so it is applied to the same pair at every level. heat conduction (quiescent), p = 2: state order 3.601 expected >= 1.75 (guaranteed rate p = 2, tolerance 0.25; design rate 3) pass heat conduction (quiescent), p = 2: near-optimal state order 3.601 expected >= 2.60 (design rate p + 1 = 3, tolerance 0.4) pass heat conduction (quiescent), p = 3: state order 3.998 expected >= 2.75 (guaranteed rate p = 3, tolerance 0.25; design rate 4) pass heat conduction (quiescent), p = 3: near-optimal state order 3.998 expected >= 3.60 (design rate p + 1 = 4, tolerance 0.4) pass diffusion dominated, Re = 10, p = 2: state order 2.333 expected >= 1.75 (guaranteed rate p = 2, tolerance 0.25; design rate 3) pass diffusion dominated, Re = 10, p = 2: velocity order between the meshes 8 → 16 2.727 expected >= 2.40 (empirical floor p + 0.4, measured on this mesh pair; guaranteed rate p = 2, design rate 3) pass diffusion dominated, Re = 10, p = 3: state order 3.794 expected >= 2.75 (guaranteed rate p = 3, tolerance 0.25; design rate 4) pass diffusion dominated, Re = 10, p = 3: velocity order between the meshes 8 → 16 4.194 expected >= 3.40 (empirical floor p + 0.4, measured on this mesh pair; guaranteed rate p = 3, design rate 4) pass diffusion dominated, no mean flow, Re = 10, p = 2: state order 1.991 expected >= 1.75 (guaranteed rate p = 2, tolerance 0.25; design rate 3) pass diffusion dominated, no mean flow, Re = 10, p = 2: velocity order between the meshes 8 → 16 2.572 expected >= 2.40 (empirical floor p + 0.4, measured on this mesh pair; guaranteed rate p = 2, design rate 3) pass convection dominated, Re = 1000, p = 2: state order 2.852 expected >= 1.75 (guaranteed rate p = 2, tolerance 0.25; design rate 3) pass convection dominated, Re = 1000, p = 2: near-optimal state order 2.852 expected >= 2.60 (design rate p + 1 = 3, tolerance 0.4) pass convection dominated, Re = 1000, p = 3: state order 3.919 expected >= 2.75 (guaranteed rate p = 3, tolerance 0.25; design rate 4) pass convection dominated, Re = 1000, p = 3: near-optimal state order 3.919 expected >= 3.60 (design rate p + 1 = 4, tolerance 0.4) pass Run notes State orders on the finest mesh pair. Within 0.4 of the design rate p + 1: heat conduction (quiescent) (p = 2: 3.60); heat conduction (quiescent) (p = 3: 4.00); diffusion dominated, Re = 10 (p = 3: 3.79); convection dominated, Re = 1000 (p = 2: 2.85); convection dominated, Re = 1000 (p = 3: 3.92). Further below it: diffusion dominated, Re = 10 (p = 2: 2.33, slowest variable pressure at 2.34); diffusion dominated, no mean flow, Re = 10 (p = 2: 1.99, slowest variable pressure at 1.99). Only the guaranteed rate p is required of the state in the regimes that are not checked against the design rate; in the diffusion-dominated ones the velocity must also reach p + 0.4 between the meshes 8 → 16, the pair on which that empirical floor was measured (the finest pair of this run), and on the finest pair its order minus that of pressure was +0.39 to +0.58 in this run. The per-variable orders are in the table. Symmetry of the viscous operator and the order of the diffusion discretization on its own are the subject of the viscous-operator and 1-D model studies. Error of the conserved state by regime Manufactured solutions: Navier–Stokes Observed orders on the finest mesh pair Manufactured solutions: Navier–Stokes regime p L2 error (finest) state velocity pressure temperature heat conduction (quiescent) 2 3.0899e-05 3.60088 3.6433 3.60046 3.05238 heat conduction (quiescent) 3 9.7099e-07 3.99849 4.94591 3.99795 4.58993 diffusion dominated, Re = 10 2 4.9402e-04 2.33256 2.72687 2.33663 3.1517 diffusion dominated, Re = 10 3 2.7784e-06 3.79396 4.19354 3.79776 4.61291 diffusion dominated, no mean flow, Re = 10 2 0.00135697 1.99132 2.57181 1.99047 2.70647 convection dominated, Re = 1000 2 8.6201e-05 2.852 2.99477 2.84167 2.95746 convection dominated, Re = 1000 3 8.8491e-07 3.91862 3.98075 3.90575 4.11096\nEvery regime must reach the rate p that consistency guarantees (tolerance 0.25). Beyond that: Heat conduction in a quiescent gas — the diffusion operator alone — and convection-dominated flow ( Re = 1000 on the unit box) are also checked against the design rate p+1 (tolerance 0.4). There the coupling between pressure and velocity is absent or hyperbolic. Diffusion-dominated flow ( Re = 10 ), with and without a mean flow: only the rate p is required of the state. The velocity is additionally held to an empirical floor of p + 0.4 between the meshes 8 → 16. That floor was measured on that mesh pair and is not a property of the scheme. The table reports the observed order per primitive variable, and the run notes under the status state, from the numbers of the run, which regimes came within 0.4 of the design rate and which variable was slowest where they did not.\nThe explanation in this block is a conjecture that fits the measurements; no study in the repository isolates the mechanism. When viscous terms dominate the momentum balance, the system behaves like the compressible analogue of the Stokes problem: velocity is determined by an elliptic equation, and pressure (with density and total energy, which contain it) plays the role of the multiplier that enforces the continuity constraint. For equal-order velocity–pressure discretizations of such saddle-point problems the classical estimate bounds the L^2 error of pressure by the H^1 error of velocity — order p , one below the velocity’s L^2 rate. That bound is a guarantee, not a prediction; the observed pressure orders are in the table. When convection dominates, pressure and velocity are instead coupled hyperbolically, and the design rate p+1 is checked. Two pieces of evidence show that the diffusion operator on its own is not the cause; they do not exercise the coupled system and so do not confirm the Stokes-like mechanism: the discrete heat-conduction and viscous-stress operators are symmetric to round-off ( discrete invariants ); an independent one-dimensional implementation of the same BR2/Gauss–Lobatto formulation, including the nonlinear work term u\\,\\tau of the energy equation, converges at p+1 for every degree on uniform and non-uniform grids: Observed orders on the finest pair (η = 2) Independent 1-D model of the BR2 formulation grid p L2 error u order u L2 error T order T uniform 1 6.8751e-05 1.98631 3.6884e-05 1.93671 uniform 2 3.4142e-07 3.01796 1.0618e-07 3.09137 uniform 3 3.5915e-09 3.99268 5.6670e-10 4.20343 uniform 4 6.0971e-12 5.00374 1.3510e-12 5.46181 non-uniform 1 7.8943e-05 2.26723 4.1248e-05 2.173 non-uniform 2 4.2637e-07 3.5915 1.3261e-07 3.75993 non-uniform 3 4.6638e-09 4.55556 7.3161e-10 4.89335 non-uniform 4 9.7043e-12 5.98881 2.1334e-12 6.64666 Against the hypothesis: the manufactured solution runs at Mach 0.5, where pressure is a thermodynamic variable and not a constraint multiplier, and three other candidates in this discretization have not been excluded — Gauss–Lobatto under-integration of the BR2 form, the penalty \\eta = 2d sitting exactly on the coercivity bound, and the non-conservative gradient in (\\mathbf u, T) . The discriminating studies (rate against Mach number, Gauss against Gauss–Lobatto quadrature, a sweep of \\eta ) have not been run; see viscous terms . Practical consequence, which rests on the measured rates and not on the explanation: in boundary layers and wakes resolved at cell Reynolds numbers of order one, plan resolution for pressure-derived quantities with a rate nearer p than p+1 .","title":"Order of accuracy","url":"verification/order-of-accuracy.html"},{"audience":[],"chunks":[{"anchor":"","end":224,"heading":"","level":2,"start":0},{"anchor":"exact-linearisation","end":5558,"heading":"Exact linearisation","level":2,"start":225},{"anchor":"the-four-columns","end":6959,"heading":"The four columns","level":3,"start":5559},{"anchor":"adjoint-gradients","end":10510,"heading":"Adjoint gradients","level":2,"start":6960},{"anchor":"what-the-table-shows","end":11331,"heading":"What the table shows","level":2,"start":10511},{"anchor":"the-v-curve","end":11581,"heading":"The V-curve","level":3,"start":11332},{"anchor":"geometry-chain","end":12941,"heading":"Geometry chain","level":2,"start":11582},{"anchor":"what-is-being-checked","end":13283,"heading":"What is being checked","level":3,"start":12942},{"anchor":"coupled-derivatives","end":13284,"heading":"Coupled derivatives","level":2,"start":13284},{"anchor":"beyond-the-suite-integration-tests","end":13776,"heading":"Beyond the suite: integration tests","level":3,"start":13285}],"headings":["Exact linearisation","The four columns","Adjoint gradients","What the table shows","The V-curve","Geometry chain","What is being checked","Coupled derivatives","Beyond the suite: integration tests"],"keywords":["adjoint","finite differences","dot product test","Jacobian","shape derivative"],"level":2,"max_level":3,"path":"verification/derivatives.md","section":"verification","section_title":"Verification","summary":"Jacobian products, preconditioner blocks, adjoint and direct total derivatives are verified against finite differences, against each other and against dense reference Jacobians. Agreement with finite differences is limited by finite-difference accuracy; adjoint and direct mode agree to the tolerance of their linear solves.","text":"For an MDO foundation the derivative code is as important as the flow solver. It is verified at three levels: products of the residual Jacobian, total derivatives of outputs, and the geometric chain behind shape derivatives.\nPassed Exact linearization by automatic differentiation 40 of 40 checks passed · 198.1 s · quick suite Jacobian-vector products (forward mode) are compared with central differences of the residual; vector-Jacobian products (reverse mode) are checked through the dot-product identity; and the element-block-Jacobi inverses obtained from split-argument JVPs are multiplied with the diagonal blocks of a dense AD Jacobian. Random admissible states on curved meshes. Boundary conditions covered: farfield, noslip_adiabatic, noslip_isothermal, pressure_outlet, slip_wall, slip_wall_mirror, symmetry, total_inlet; interface fluxes: es_roe, hllc, llf, roe. Euler, slip wall: JVP vs central difference 5.37e-10 expected < 1e-6 pass Euler, slip wall: <v,Ju> = <J^T v,u> 7.47e-16 expected < 1e-12 pass Euler, slip wall: block inverse times dense diagonal block 6.75e-14 expected < 1e-8 pass Euler, slip wall: split-argument residual with identical arguments vs ordinary residual 0 expected < 1e-12 pass NS, adiabatic wall: JVP vs central difference 3.71e-10 expected < 1e-6 pass NS, adiabatic wall: <v,Ju> = <J^T v,u> 7.54e-15 expected < 1e-12 pass NS, adiabatic wall: block inverse times dense diagonal block 1.24e-14 expected < 1e-8 pass NS, adiabatic wall: split-argument residual with identical arguments vs ordinary residual 0 expected < 1e-12 pass NS, isothermal wall: JVP vs central difference 3.16e-10 expected < 1e-6 pass NS, isothermal wall: <v,Ju> = <J^T v,u> 1.03e-13 expected < 1e-12 pass NS, isothermal wall: block inverse times dense diagonal block 1.63e-14 expected < 1e-8 pass NS, isothermal wall: split-argument residual with identical arguments vs ordinary residual 0 expected < 1e-12 pass NS, mixed channel: JVP vs central difference 5.26e-10 expected < 1e-6 pass NS, mixed channel: <v,Ju> = <J^T v,u> 4.33e-16 expected < 1e-12 pass NS, mixed channel: block inverse times dense diagonal block 1.49e-14 expected < 1e-8 pass NS, mixed channel: split-argument residual with identical arguments vs ordinary residual 0 expected < 1e-12 pass Euler, mirrored slip wall: JVP vs central difference 1.99e-10 expected < 1e-6 pass Euler, mirrored slip wall: <v,Ju> = <J^T v,u> 1.06e-13 expected < 1e-12 pass Euler, mirrored slip wall: block inverse times dense diagonal block 5.68e-14 expected < 1e-8 pass Euler, mirrored slip wall: split-argument residual with identical arguments vs ordinary residual 0 expected < 1e-12 pass Euler, mirrored slip wall, HLLC flux: JVP vs central difference 5.60e-10 expected < 1e-6 pass Euler, mirrored slip wall, HLLC flux: <v,Ju> = <J^T v,u> 7.57e-14 expected < 1e-12 pass Euler, mirrored slip wall, HLLC flux: block inverse times dense diagonal block 2.49e-14 expected < 1e-8 pass Euler, mirrored slip wall, HLLC flux: split-argument residual with identical arguments vs ordinary residual 0 expected < 1e-12 pass Euler, mirrored slip wall, LLF flux: JVP vs central difference 1.38e-10 expected < 1e-6 pass Euler, mirrored slip wall, LLF flux: <v,Ju> = <J^T v,u> 1.47e-15 expected < 1e-12 pass Euler, mirrored slip wall, LLF flux: block inverse times dense diagonal block 1.17e-14 expected < 1e-8 pass Euler, mirrored slip wall, LLF flux: split-argument residual with identical arguments vs ordinary residual 0 expected < 1e-12 pass Euler, inlet/outlet channel: JVP vs central difference 2.11e-10 expected < 1e-6 pass Euler, inlet/outlet channel: <v,Ju> = <J^T v,u> 1.25e-13 expected < 1e-12 pass Euler, inlet/outlet channel: block inverse times dense diagonal block 5.38e-15 expected < 1e-8 pass Euler, inlet/outlet channel: split-argument residual with identical arguments vs ordinary residual 0 expected < 1e-12 pass Euler, mirrored slip wall, ES-Roe flux: JVP vs central difference 1.88e-10 expected < 1e-6 pass Euler, mirrored slip wall, ES-Roe flux: <v,Ju> = <J^T v,u> 1.39e-14 expected < 1e-12 pass Euler, mirrored slip wall, ES-Roe flux: block inverse times dense diagonal block 3.82e-14 expected < 1e-8 pass Euler, mirrored slip wall, ES-Roe flux: split-argument residual with identical arguments vs ordinary residual 0 expected < 1e-12 pass NS, adiabatic wall, ES-Roe flux: JVP vs central difference 3.00e-10 expected < 1e-6 pass NS, adiabatic wall, ES-Roe flux: <v,Ju> = <J^T v,u> 2.92e-15 expected < 1e-12 pass NS, adiabatic wall, ES-Roe flux: block inverse times dense diagonal block 1.68e-14 expected < 1e-8 pass NS, adiabatic wall, ES-Roe flux: split-argument residual with identical arguments vs ordinary residual 0 expected < 1e-12 pass Linearization defects Exact linearization by automatic differentiation configuration p DOF JVP vs FD (rel.) dot-product (rel.) |B⁻¹ J_ee - I| split residual defect Euler, slip wall 2 432 5.3652e-10 7.4656e-16 6.7502e-14 0 NS, adiabatic wall 2 432 3.7139e-10 7.5418e-15 1.2397e-14 0 NS, isothermal wall 2 432 3.1571e-10 1.0274e-13 1.6348e-14 0 NS, mixed channel 3 576 5.2611e-10 4.3341e-16 1.4949e-14 0 Euler, mirrored slip wall 2 432 1.9949e-10 1.0578e-13 5.6843e-14 0 Euler, mirrored slip wall, HLLC flux 2 432 5.6037e-10 7.5694e-14 2.4869e-14 0 Euler, mirrored slip wall, LLF flux 2 432 1.3770e-10 1.4703e-15 1.1713e-14 0 Euler, inlet/outlet channel 2 324 2.1088e-10 1.2508e-13 5.3846e-15 0 Euler, mirrored slip wall, ES-Roe flux 2 432 1.8848e-10 1.3925e-14 3.8192e-14 0 NS, adiabatic wall, ES-Roe flux 2 432 3.0017e-10 2.9248e-15 1.6784e-14 0\nJVP vs FD — forward-mode product \\mathbf J\\mathbf u against a central difference of the residual; the residual difference is \\mathcal O(10^{-10}) , the accuracy of the finite difference itself. dot-product — \\langle\\mathbf v, \\mathbf J\\mathbf u\\rangle = \\langle\\mathbf J^{\\mathsf T}\\mathbf v, \\mathbf u\\rangle for random vectors: forward and reverse mode agree to round-off, so the adjoint operator is the exact transpose. |\\mathbf B^{-1}\\mathbf J_{ee} - \\mathbf I| — the block-Jacobi inverses from split-argument JVPs multiplied with the diagonal blocks of a dense AD Jacobian. split residual defect — the split-argument residual evaluated with identical arguments must equal the ordinary residual; the check requires a difference below 10^{-12} . Random admissible states are used, on curved meshes, for slip, adiabatic and isothermal walls, for a channel combining far-field, pressure-outlet, no-slip and slip boundaries, for the mirrored slip wall ( slip_wall_mirror ) with the Roe, HLLC, LLF and ES-Roe interface fluxes, for an inlet/outlet channel ( total_inlet , pressure_outlet , symmetry , slip_wall_mirror ), and for a viscous case with the ES-Roe flux. The mirrored slip wall is the only wall that hands two different states to the interface Riemann flux, which is why it is run with each flux; the ES-Roe flux is the one whose dissipation contains logarithmic means and entropy variables.\nPassed Adjoint gradients versus finite differences 18 of 18 checks passed · 1176 s · quick suite Total derivatives of force coefficients with respect to angle of attack, Mach number, Reynolds number and a smooth surface-shape perturbation are computed with one transposed (adjoint) solve per output and compared with central finite differences of fully re-converged flows. For the scalar inputs of the Euler case the derivatives are also computed in direct (forward) mode, an independent linear solve of the same linearization, and compared with the adjoint values. Euler: dcl/dalpha adjoint vs FD (rel.) 9.82e-11 expected < 1e-6 pass Euler: dcl/dalpha direct vs adjoint mode (rel.) 2.73e-09 expected < 1e-4 (limited by the linear-solve tolerance 1e-10) pass Euler: dcd/dalpha adjoint vs FD (rel.) 2.04e-08 expected < 1e-6 pass Euler: dcd/dalpha direct vs adjoint mode (rel.) 2.84e-08 expected < 1e-4 (limited by the linear-solve tolerance 1e-10) pass Euler: dcm/dalpha adjoint vs FD (rel.) 1.13e-08 expected < 1e-6 pass Euler: dcm/dalpha direct vs adjoint mode (rel.) 4.61e-07 expected < 1e-4 (limited by the linear-solve tolerance 1e-10) pass Euler: dcl/dmach adjoint vs FD (rel.) 1.98e-09 expected < 1e-6 pass Euler: dcl/dmach direct vs adjoint mode (rel.) 2.64e-06 expected < 1e-4 (limited by the linear-solve tolerance 1e-10) pass Euler: dcd/dmach adjoint vs FD (rel.) 2.13e-08 expected < 1e-6 pass Euler: dcd/dmach direct vs adjoint mode (rel.) 1.10e-05 expected < 1e-4 (limited by the linear-solve tolerance 1e-10) pass Euler: dcm/dmach adjoint vs FD (rel.) 7.83e-09 expected < 1e-6 pass Euler: dcm/dmach direct vs adjoint mode (rel.) 6.91e-05 expected < 1e-4 (limited by the linear-solve tolerance 1e-10) pass Euler: dcl/dshape (directional) adjoint vs FD (rel.) 6.73e-09 expected < 1e-6 pass Euler: dcd/dshape (directional) adjoint vs FD (rel.) 1.65e-08 expected < 1e-6 pass Euler: dcm/dshape (directional) adjoint vs FD (rel.) 3.84e-08 expected < 1e-6 pass Navier–Stokes: dcd/dre adjoint vs FD (rel.) 4.76e-10 expected < 1e-6 pass Navier–Stokes: dcd/dmach adjoint vs FD (rel.) 1.33e-09 expected < 1e-6 pass step sweep: best FD agreement with adjoint (rel.) 1.19e-10 expected < 1e-8 pass Run notes Largest relative difference in this run: adjoint against finite differences 3.8e-08, adjoint against direct mode 6.9e-05. Adjoint, finite-difference and direct derivatives Adjoint gradients versus finite differences case derivative adjoint finite difference rel. diff. (adjoint, FD) rel. diff. (adjoint, direct) Euler NACA 0012 dcl/dalpha 7.59626 7.59626 9.8180e-11 2.7336e-09 Euler NACA 0012 dcd/dalpha 0.0222355 0.0222355 2.0356e-08 2.8444e-08 Euler NACA 0012 dcm/dalpha 0.0123702 0.0123702 1.1288e-08 4.6115e-07 Euler NACA 0012 dcl/dmach 0.218262 0.218262 1.9822e-09 2.6393e-06 Euler NACA 0012 dcd/dmach 3.8734e-04 3.8734e-04 2.1349e-08 1.1047e-05 Euler NACA 0012 dcm/dmach 0.00128519 0.00128519 7.8285e-09 6.9061e-05 Euler NACA 0012 dcl/dshape (directional) 0.0130491 0.0130491 6.7282e-09 — Euler NACA 0012 dcd/dshape (directional) -2.4501e-05 -2.4501e-05 1.6480e-08 — Euler NACA 0012 dcm/dshape (directional) 3.9401e-04 3.9401e-04 3.8413e-08 — Cylinder Re = 40 dcd/dre -0.0125001 -0.0125001 4.7607e-10 — Cylinder Re = 40 dcd/dmach 0.270411 0.270411 1.3254e-09 — Finite-difference error of dcl/dα versus step size Adjoint gradients versus finite differences Smallest difference in this run: 1.2e-10 at a step of 1e-05. The reference line is the ε² truncation error of a central difference; round-off of the re-converged flows grows as 1/ε.\nTotal derivatives of c_l , c_d and c_m with respect to angle of attack, Mach number and a smooth shape perturbation (Euler, NACA 0012), and of c_d with respect to Reynolds and Mach number (laminar cylinder). The adjoint value is compared with a central finite difference of fully re-converged flows (check: relative difference below 10^{-6} ); that column is limited by finite-difference error. For the scalar inputs of the Euler case the derivative is also computed in direct mode, an independent linear solve of the same linearisation, and must agree with the adjoint value to a relative 10^{-4} . That agreement is limited by the 10^{-10} tolerance of the two Krylov solves times the conditioning of the derivative, which is large for derivatives that are small through cancellation; it is not a round-off comparison.\nSweeping the finite-difference step shows truncation error falling as \\varepsilon^2 on the right and round-off growing as 1/\\varepsilon on the left. The adjoint value sits at the bottom of the V: the best any finite difference can do is approach it.\nPassed Differentiable mesh deformation 9 of 9 checks passed · 2.45 s · quick suite Surface displacements are propagated to all high-order geometry nodes by inverse-distance weighting. The study deforms a NACA 0012 O-mesh with a thickness bump of increasing amplitude and checks that elements stay valid, that wall nodes follow the prescription exactly while far-field nodes stay fixed, and that the derivative of a geometric quantity through deformation and metric evaluation matches finite differences. scale 0.0: mesh valid (min J > 0) 4.33e-05 expected > 0 pass scale 0.0: surface/far-field prescription defect 0 expected < 1e-13 pass scale 0.5: mesh valid (min J > 0) 4.33e-05 expected > 0 pass scale 0.5: surface/far-field prescription defect 0 expected < 1e-13 pass scale 1.0: mesh valid (min J > 0) 4.33e-05 expected > 0 pass scale 1.0: surface/far-field prescription defect 0 expected < 1e-13 pass scale 2.0: mesh valid (min J > 0) 4.33e-05 expected > 0 pass scale 2.0: surface/far-field prescription defect 0 expected < 1e-13 pass d(weighted area)/d(surface displacement): AD vs FD 1.08e-08 expected < 1e-7 pass Mesh validity under deformation Differentiable mesh deformation bump scale min J min scaled Jacobian surface defect far-field motion 0 4.3328e-05 0.125712 0 0 0.5 4.3327e-05 0.125627 0 0 1 4.3327e-05 0.125541 0 0 2 4.3325e-05 0.125371 0 0\nA thickness bump of increasing amplitude is applied to the aerofoil surface. Elements remain valid, wall nodes follow the prescription exactly, far-field nodes do not move, and the derivative of a weighted area through deformation and metric evaluation matches a finite difference. The same chain carries every shape derivative Gyre returns.\n\nTwo tests in the regular test suite exercise the coupling contract: test_coupled_aerostructural_adjoint_matches_finite_differences builds a coupled adjoint for a flow coupled to an elastic skin outside Gyre, using only loads, displacements and reverse total products, and matches a finite difference of the re-converged coupled system; test_openmdao_component_totals wraps the discipline as an OpenMDAO component and compares the framework’s reverse- and forward-mode totals with Gyre’s own.","title":"Linearisation and gradients","url":"verification/derivatives.html"},{"audience":[],"chunks":[{"anchor":"","end":4761,"heading":"","level":2,"start":0},{"anchor":"reading-the-histories","end":5622,"heading":"Reading the histories","level":2,"start":4762},{"anchor":"linear-work","end":6312,"heading":"Linear work","level":3,"start":5623},{"anchor":"what-the-iterations-cost","end":8017,"heading":"What the iterations cost","level":2,"start":6313},{"anchor":"reading-the-cost-table","end":9238,"heading":"Reading the cost table","level":2,"start":8018},{"anchor":"line-smoothing-on-a-wall-resolved-mesh","end":16510,"heading":"Line smoothing on a wall-resolved mesh","level":2,"start":9239},{"anchor":"what-is-compared","end":17579,"heading":"What is compared","level":2,"start":16511},{"anchor":"in-three-dimensions","end":22775,"heading":"In three dimensions","level":2,"start":17580},{"anchor":"the-three-dimensional-case","end":23957,"heading":"The three-dimensional case","level":2,"start":22776},{"anchor":"why-the-derivatives-are-compared-at-a-tighter-tolerance","end":25223,"heading":"Why the derivatives are compared at a tighter tolerance","level":3,"start":23958},{"anchor":"single-precision-blocks","end":28086,"heading":"Single-precision blocks","level":2,"start":25224},{"anchor":"what-is-compared-and-why-nothing-should-change","end":29147,"heading":"What is compared, and why nothing should change","level":2,"start":28087},{"anchor":"warm-starts","end":29572,"heading":"Warm starts","level":3,"start":29148}],"headings":["Reading the histories","Linear work","What the iterations cost","Reading the cost table","Line smoothing on a wall-resolved mesh","What is compared","In three dimensions","The three-dimensional case","Why the derivatives are compared at a tighter tolerance","Single-precision blocks","What is compared, and why nothing should change","Warm starts"],"keywords":["Newton","Krylov","p-multigrid","convergence history","line smoother","wall-resolved mesh"],"level":2,"max_level":3,"path":"verification/solvers.md","section":"verification","section_title":"Verification","summary":"The pseudo-transient Newton–Krylov solver reduces the residual by ten orders from a free stream; its terminal convergence is linear at the forcing rate of the inexact Newton iteration, and p-multigrid preconditioning cuts the Krylov iteration count compared with block Jacobi alone. What those iterations cost is measured and reported without a threshold: operator applications counted from the cycle, and the wall time of each solve repeated with compiled kernels.","text":"Passed Nonlinear and linear solver convergence 16 of 16 checks passed · 890.1 s · quick suite Residual histories of the pseudo-transient Newton–Krylov solver from a uniform free stream, with element-block-Jacobi and with p-multigrid preconditioned FGMRES. The linear systems are solved to a fixed relative tolerance (inexact Newton), so once the pseudo-time step is large the residual is expected to fall by about that factor per step: linear convergence at the forcing rate, not quadratic convergence. The checks require a residual reduction of ten orders, a terminal contraction per step within a factor five of the forcing term (the signature of an exact Jacobian), and fewer than half the Krylov iterations with p-multigrid. What those iterations cost is measured and reported without a threshold: operator applications counted from the definition of the multigrid cycle, and the wall time of each solve repeated with compiled kernels. Euler NACA 0012, p = 2, block Jacobi: converged 10 orders (final / reference residual) 2.86e-12 expected <= 1e-10 pass Euler NACA 0012, p = 2, block Jacobi: terminal residual contraction per step (geometric mean) 0.01092 expected <= 0.05 (5 x forcing term lin_tol = 0.01) pass Euler NACA 0012, p = 2, p-multigrid: converged 10 orders (final / reference residual) 1.93e-12 expected <= 1e-10 pass Euler NACA 0012, p = 2, p-multigrid: terminal residual contraction per step (geometric mean) 0.0102 expected <= 0.05 (5 x forcing term lin_tol = 0.01) pass Euler NACA 0012, p = 2: Krylov iterations, p-MG / block Jacobi 0.1177 expected < 0.5 pass Laminar cylinder Re = 40, p = 2, block Jacobi: converged 10 orders (final / reference residual) 1.52e-12 expected <= 1e-10 pass Laminar cylinder Re = 40, p = 2, block Jacobi: terminal residual contraction per step (geometric mean) 0.009805 expected <= 0.05 (5 x forcing term lin_tol = 0.01) pass Laminar cylinder Re = 40, p = 2, p-multigrid: converged 10 orders (final / reference residual) 8.21e-11 expected <= 1e-10 pass Laminar cylinder Re = 40, p = 2, p-multigrid: terminal residual contraction per step (geometric mean) 0.007633 expected <= 0.05 (5 x forcing term lin_tol = 0.01) pass Laminar cylinder Re = 40, p = 2: Krylov iterations, p-MG / block Jacobi 0.08377 expected < 0.5 pass Euler NACA 0012, p = 3, block Jacobi: converged 10 orders (final / reference residual) 3.30e-12 expected <= 1e-10 pass Euler NACA 0012, p = 3, block Jacobi: terminal residual contraction per step (geometric mean) 0.01179 expected <= 0.05 (5 x forcing term lin_tol = 0.01) pass Euler NACA 0012, p = 3, p-multigrid: converged 10 orders (final / reference residual) 5.90e-11 expected <= 1e-10 pass Euler NACA 0012, p = 3, p-multigrid: terminal residual contraction per step (geometric mean) 0.005823 expected <= 0.05 (5 x forcing term lin_tol = 0.01) pass Euler NACA 0012, p = 3: Krylov iterations, p-MG / block Jacobi 0.1132 expected < 0.5 pass cost of the two preconditioners measured: the repeated, compiled solves converged yes expected true pass Run notes Measured in this run: below 0.0001 of the reference residual the residual fell by a factor 5.8e-03 to 1.2e-02 per step (geometric means; worst single step 1.7e-02) with a forcing term of 0.01. p-multigrid needed 0.08 to 0.12 of the Krylov iterations of block Jacobi. That is a ratio of iteration counts, not of work: one multigrid-preconditioned iteration applies the operator several times (smoothing sweeps on every level). In fine-level operator applications the ratio was 0.63 to 0.89, and in wall time of the repeated solve, with every kernel compiled, 0.88 to 1.38 (below 1: p-multigrid cheaper). The wall times of the first solves in the effort table include one-off JIT compilation. Machine load while the study ran: The timings were taken on a busy machine (other processes used 51 % of the capacity of the 8 CPUs while the study ran). Residual history Nonlinear and linear solver convergence Solver effort Nonlinear and linear solver convergence case preconditioner levels Newton steps Krylov iterations max Krylov / step converged final / reference residual terminal contraction (mean) terminal contraction (worst step) wall time [s] Euler NACA 0012, p = 2 block Jacobi 2 16 1206 182 yes 2.8557e-12 0.0109235 0.0134203 58.5489 Euler NACA 0012, p = 2 p-multigrid 2, 1, 0 16 142 17 yes 1.9270e-12 0.010203 0.0154498 104.262 Laminar cylinder Re = 40, p = 2 block Jacobi 2 15 1707 320 yes 1.5243e-12 0.00980497 0.00996111 48.199 Laminar cylinder Re = 40, p = 2 p-multigrid 2, 1, 0 14 143 19 yes 8.2062e-11 0.0076328 0.00863853 73.4588 Euler NACA 0012, p = 3 block Jacobi 3 16 1360 202 yes 3.3017e-12 0.0117945 0.0171502 74.6616 Euler NACA 0012, p = 3 p-multigrid 3, 1, 0 15 154 23 yes 5.9042e-11 0.00582304 0.00610324 79.4693\nFrom a uniform free stream the residual first falls slowly while the pseudo-time step ramps up, then drops by a roughly constant factor per step. The linear systems are solved to a fixed relative tolerance, lin_tol = 10^{-2} (inexact Newton with a fixed forcing term), so the terminal convergence is linear at the forcing rate, about two orders per step, not quadratic. The study checks three things per configuration: a residual reduction of ten orders relative to the reference residual, a terminal contraction per step (geometric mean) of at most 5 \\times lin_tol , and fewer than half the Krylov iterations with p-multigrid. A contraction near the forcing term is the signature of an exact Jacobian; a wrong Jacobian gives a contraction of order one. Both preconditioners solve the same Newton systems — they differ in how much linear work each step costs.\nBlock Jacobi damps element-local error only, so GMRES needs many iterations (and restarts) once the pseudo-time step is large. The p-multigrid V-cycle adds coarse levels down to a finite-volume discretization at p = 0 and reduces the Krylov iteration count; the check requires less than half. That is a ratio of iteration counts, not of work: one multigrid-preconditioned iteration applies the operator several times (smoothing sweeps on every level). The wall times in the table include one-off JIT compilation, which is larger for the multigrid hierarchy. The table also lists the final residual relative to the reference and the terminal contraction, as the mean and for the worst step.\nWhat the Krylov iterations cost Nonlinear and linear solver convergence case preconditioner Krylov iterations fine-level operator applications per Krylov iteration fine-level operator applications, total block solves per iteration wall time, first solve [s] wall time, repeated solve (compiled) [s] repeated solve converged Krylov iterations, repeated solve Euler NACA 0012, p = 2 block Jacobi 1206 1 1206 1 58.5489 81.5204 yes 1206 Euler NACA 0012, p = 2 p-multigrid 142 7.55556 1072.89 16 104.262 71.3936 yes 142 Euler NACA 0012, p = 2 p-MG / block Jacobi 0.117745 — 0.889626 — — 0.875776 — — Laminar cylinder Re = 40, p = 2 block Jacobi 1707 1 1707 1 48.199 64.3311 yes 1707 Laminar cylinder Re = 40, p = 2 p-multigrid 143 7.55556 1080.44 16 73.4588 60.265 yes 143 Laminar cylinder Re = 40, p = 2 p-MG / block Jacobi 0.0837727 — 0.632949 — — 0.936793 — — Euler NACA 0012, p = 3 block Jacobi 1360 1 1360 1 74.6616 71.866 yes 1360 Euler NACA 0012, p = 3 p-multigrid 154 6.4375 991.375 16 79.4693 99.4675 yes 154 Euler NACA 0012, p = 3 p-MG / block Jacobi 0.113235 — 0.728952 — — 1.38407 — — Warning What these numbers do and do not show No evidence here says p-multigrid is cheaper in general. The ratios are measurements on three small two-dimensional cases at p = 2 and p = 3 , on one machine, whose load during the run is stated in the note of the record; the wall times of small solves carry that load. The first-solve times in the effort table include JIT compilation. Where block Jacobi alone does not converge, the question of cost does not arise: on the wall-resolved mesh of the turbulent flat plate the coarse level is what makes the linear solves reach their tolerance ( turbulence closure ).\nFewer Krylov iterations are not yet less work: one multigrid-preconditioned iteration applies the operator several times. The table therefore gives two measures per solve. Fine-level operator applications. Counted from the definition of the cycle: the Krylov product, the smoothing sweeps and the residual of every level, with coarse levels weighted by their share of the unknowns ( Hierarchy.cycle_work ). With the default sweeps (2 before, 2 after, 8 on the coarsest level) and the levels 2, 1, 0 in two dimensions, one iteration costs about 7.6 applications where block Jacobi costs one, so the hierarchy has to cut the iteration count by more than that factor before it saves operator work. The weighting overstates the coarse levels and leaves out block solves and transfers: it is a unit for comparing the two preconditioners, not a time. Wall time of the repeated solve. Each solve is run a second time from the free stream when every kernel is compiled, which removes the one-off compilation that dominates the first solve and is larger for the hierarchy. The rows “p-MG / block Jacobi” give the ratios; a value below one means p-multigrid was cheaper in that measure. The study reports them without a threshold.\nPassed Line-implicit smoothing on a wall-resolved mesh 7 of 7 checks passed · 2160 s · quick suite The turbulent flat plate of the preset flat_plate_sa (Spalart-Allmaras, first cell 5e-5 high) solved with element-block Jacobi and with line Jacobi as the smoother, each on the p-multigrid levels 2-1-0 and on one level, and once more with line Jacobi whose blocks are assembled every second nonlinear step only. Line Jacobi inverts the block-tridiagonal system of every line of strongly coupled elements; the lines follow the thin direction of the cells and are found from the mesh. At the converged flow one forward and one transposed system with the same right-hand side are solved to a relative tolerance of 1e-10. The checks require that the answer does not depend on the preconditioner (drag coefficient, adjoint derivatives), that line Jacobi needs fewer than half the Krylov iterations of block Jacobi on three levels, and that it reaches the tolerance on one level. What block Jacobi does on one level is reported without a threshold, and so are all wall times. flow solves converged with block Jacobi on three levels, with line Jacobi on three levels and on one, and with line Jacobi whose blocks are assembled every second step yes expected true pass lines found on the wall-resolved mesh (elements in lines) 131 expected > 0 pass drag coefficient does not depend on the preconditioner (largest relative difference) 2.06e-09 expected < 1e-6 pass forward solve to 1e-10, three levels, both smoothers reaching the tolerance: Krylov iterations, line Jacobi / block Jacobi 0.2773 expected < 0.5 pass transposed (adjoint) solve to 1e-10, three levels, both smoothers reaching the tolerance: Krylov iterations, line Jacobi / block Jacobi 0.2834 expected < 0.5 pass one level, line Jacobi: forward and transposed solves reach the tolerance yes expected true pass adjoint derivatives of the drag (Mach, Reynolds, free-stream turbulence) from transposed solves that reached 1e-10: largest relative difference between the two smoothers 3.16e-09 expected < 1e-6 pass Run notes The mesh has 18 lines holding 131 of its 150 elements (the longest 12 elements, 3 colours). On three levels the transposed solve took 70 Krylov iterations with line Jacobi against 247 with block Jacobi (ratio 0.28; wall time of the compiled solve, ratio 0.67); the forward solve 66 against 238. One assembly of the preconditioner on three levels took 4.04 s with line Jacobi against 0.19 s with block Jacobi (the coupling blocks of the line elements are assembled as well, one batch of derivatives per colour). With one level and a tolerance of 1e-10 on the transposed system, block Jacobi reduced the residual by 2.9e-04 in 720 iterations and stopped at the iteration limit; line Jacobi reached the tolerance in 465 iterations. A flow solve assembles the preconditioner at every nonlinear step; the last row of the flow table assembles it every second step only (the repeated solves took 33.1 s (block Jacobi, levels 2-1-0), 233.6 s (line Jacobi, levels 2-1-0), 113.3 s (block Jacobi, one level), 392.5 s (line Jacobi, one level), 147.9 s (line Jacobi, levels 2-1-0, blocks every 2nd step)). Wall times: the column of the linear table is the Krylov solve alone, with a preconditioner that is already assembled; one assembly is timed separately (table 'assembly'). The first flow solve of a configuration includes the one-off compilation. The suite runs its studies in parallel lanes, so other studies were running while these times were taken; they are measurements of small cases on a machine that was not idle, reported without a threshold. Machine load while the study ran: The timings were taken on a busy machine (other processes used 45 % of the capacity of the 8 CPUs while the study ran). Linear solves at the converged flow (tolerance 1e-10) Line-implicit smoothing on a wall-resolved mesh preconditioner system Krylov iterations residual reduction tolerance reached wall time of the Krylov solve, compiled, preconditioner assembled before [s] block Jacobi, levels 2-1-0 forward 238 8.9804e-11 yes 6.18439 block Jacobi, levels 2-1-0 transposed (adjoint) 247 9.4097e-11 yes 10.9862 line Jacobi, levels 2-1-0 forward 66 9.5595e-11 yes 4.98718 line Jacobi, levels 2-1-0 transposed (adjoint) 70 9.4827e-11 yes 7.34169 block Jacobi, one level forward 720 1.1248e-04 no 4.00668 block Jacobi, one level transposed (adjoint) 720 2.8702e-04 no 12.8319 line Jacobi, one level forward 441 9.9505e-11 yes 5.62206 line Jacobi, one level transposed (adjoint) 465 8.7870e-11 yes 5.46822 Lines of the mesh Line-implicit smoothing on a wall-resolved mesh lines elements in lines elements longest line colours stored line slots 18 131 150 12 3 152 Flow solve: residual against accumulated Krylov iterations Line-implicit smoothing on a wall-resolved mesh One assembly of the preconditioner at the converged flow Line-implicit smoothing on a wall-resolved mesh preconditioner wall time, compiled, fastest of five [s] block Jacobi, levels 2-1-0 0.1873 line Jacobi, levels 2-1-0 4.04077 block Jacobi, one level 3.00486 line Jacobi, one level 4.08774 Flow solve from the free stream Line-implicit smoothing on a wall-resolved mesh preconditioner nonlinear steps Krylov iterations converged drag coefficient wall time, first solve [s] wall time, repeated solve (compiled) [s] repeated solve converged block Jacobi, levels 2-1-0 34 540 yes 0.00575365 86.6006 33.1422 yes line Jacobi, levels 2-1-0 34 267 yes 0.00575365 159.257 233.618 yes block Jacobi, one level 40 3498 yes 0.00575365 136.85 113.283 yes line Jacobi, one level 34 1914 yes 0.00575365 245.519 392.492 yes line Jacobi, levels 2-1-0, blocks every 2nd step 34 268 yes 0.00575365 189.424 147.94 yes Warning What line smoothing costs, and what this does not show Fewer Krylov iterations in every solve; less time only where the iterations dominate. Every update of the preconditioner assembles two more blocks per line element, one batch of derivatives per colour, on every level. The wall time of the linear solves in the first table is that of the Krylov iterations alone, with a preconditioner that is already assembled; one assembly is timed in the table above, and what a linear solve gains with its assembly counted follows from the two tables. A flow solve whose Newton steps each ask for two orders only assembles the preconditioner at every step, so its wall time need not fall with its iteration count, and the last row of the flow table shows what assembling the blocks every second step does to it. All wall times are those of small cases on one machine, taken while the other studies of the suite were running (the load is stated in the note of the record), and are reported without a threshold. Two small cases, one in two and one in three dimensions, do not say how the comparison comes out on a large mesh, at higher degree or for a turbulent flow that is three-dimensional in itself; smoother: line is therefore an option and not the default ( decision 13 ). Adjoint derivatives of the drag coefficient, levels 2-1-0 Line-implicit smoothing on a wall-resolved mesh input block Jacobi line Jacobi relative difference mach -0.0498736 -0.0498736 3.1572e-09 re -3.0651e-10 -3.0651e-10 1.8531e-10 chi_inf 3.6050e-06 3.6050e-06 1.6364e-09\nThe turbulent flat plate of the preset flat_plate_sa : 150 elements of degree 2, the first row 5\\times10^{-5} high, so the cells at the wall are between 400 and about 10 600 times longer than they are thick. The flow is converged with block Jacobi and with line Jacobi as the smoother, each on the p-multigrid levels 2, 1, 0 and on one level, and once more with line Jacobi whose blocks are assembled every second step ( solver.precond_lag: 2 ). At the converged flow one forward and one transposed system with the same right-hand side are then solved to a relative tolerance of 10^{-10} , which is what a direct sensitivity and an adjoint ask for. The checks require that the answer does not depend on the preconditioner (drag coefficient and three adjoint derivatives agree to 10^{-6} relative), that line Jacobi needs fewer than half the Krylov iterations of block Jacobi on three levels, and that it reaches the tolerance on one level. What block Jacobi does on one level is reported as it comes out: it is the stalled solve that made a missed tolerance an error .\nPassed Line-implicit smoothing in three dimensions 6 of 6 checks passed · 620.4 s · quick suite Steady laminar flow past a sphere (Re = 100, M = 0.2, degree 2) on a cubed-sphere shell whose layers grow by a factor 2.2 from the wall, so that the first layers are several times wider than they are thick. The flow is converged with element-block Jacobi and with line Jacobi as the smoother of the p-multigrid levels 2-1-0; at the converged flow one forward and one transposed system with the same right-hand side are solved to a relative tolerance of 1e-10. The lines run radially through the anisotropic layers of all six blocks of the mesh. The checks require the same drag coefficient with both smoothers, the same adjoint derivatives when the transposed systems are solved to 1e-12 (their difference at 1e-10 is reported), and fewer than half the Krylov iterations with line Jacobi in both linear solves. Wall times are reported without a threshold. flow solves converged with both smoothers yes expected true pass lines found on the shell with thin wall layers (elements in lines) 216 expected > 0 pass drag coefficient: relative difference between the two smoothers 6.58e-11 expected < 1e-6 pass forward solve to 1e-10, both smoothers reaching the tolerance: Krylov iterations, line Jacobi / block Jacobi 0.3286 expected < 0.5 pass transposed (adjoint) solve to 1e-10, both smoothers reaching the tolerance: Krylov iterations, line Jacobi / block Jacobi 0.3464 expected < 0.5 pass adjoint derivatives of the drag (Mach, Reynolds, angle of attack) from transposed solves that reached 1e-12: largest difference between the two smoothers, relative to the largest derivative 8.64e-08 expected < 1e-6 pass Run notes The mesh has 54 lines holding 216 of its 432 elements (the longest 4 elements, 3 colours); 58320 unknowns. The transposed solve took 62 Krylov iterations with line Jacobi against 179 with block Jacobi (ratio 0.35; compiled wall time, ratio 0.29), the forward solve 69 against 210. The adjoint derivatives of the two smoothers differ by at most 2.4e-06 of the largest derivative when the transposed systems are solved to 1e-10 (in the derivative with respect to 'mach') and by at most 8.6e-08 at 1e-12. Between the two tolerances the block-Jacobi value of that derivative itself moves by 7.5e-06 of the largest derivative (last column of the table): the difference between the smoothers is the linear tolerance, amplified by the conditioning of that derivative. One assembly of the preconditioner took 1.18 s with line Jacobi against 0.39 s with block Jacobi, and a flow solve assembles it at every nonlinear step: the repeated flow solves took 13.9 s (block Jacobi) and 19.5 s (line Jacobi). The mesh is coarse and its layers are thinner than a laminar boundary layer at this Reynolds number needs: the case measures the solver, not the flow. Wall times: the column of the linear table is the Krylov solve alone, with a preconditioner that is already assembled; one assembly is timed separately (table 'assembly'). The first flow solve of a configuration includes the one-off compilation. The suite runs its studies in parallel lanes, so other studies were running while these times were taken; they are measurements of small cases on a machine that was not idle, reported without a threshold. Machine load while the study ran: The timings were taken on a busy machine (other processes used 74 % of the capacity of the 8 CPUs while the study ran; the GPU was 17 % utilized when the study started). Linear solves at the converged flow (tolerance 1e-10) Line-implicit smoothing in three dimensions preconditioner system Krylov iterations residual reduction tolerance reached wall time of the Krylov solve, compiled, preconditioner assembled before [s] block Jacobi forward 210 9.2582e-11 yes 7.68143 block Jacobi transposed (adjoint) 179 9.9328e-11 yes 5.71461 line Jacobi forward 69 7.6571e-11 yes 1.86119 line Jacobi transposed (adjoint) 62 7.3926e-11 yes 1.67616 Lines of the mesh Line-implicit smoothing in three dimensions lines elements in lines elements longest line colours stored line slots 54 216 432 4 3 216 One assembly of the preconditioner at the converged flow Line-implicit smoothing in three dimensions preconditioner wall time, compiled, fastest of five [s] block Jacobi 0.391384 line Jacobi 1.17592 Flow solve from the free stream Line-implicit smoothing in three dimensions preconditioner levels nonlinear steps Krylov iterations converged drag coefficient wall time, first solve [s] wall time, repeated solve (compiled) [s] repeated solve converged block Jacobi 2, 1, 0 13 193 yes 1.13546 141.43 13.8529 yes line Jacobi 2, 1, 0 13 123 yes 1.13546 109.475 19.5358 yes Adjoint derivatives of the drag coefficient Line-implicit smoothing in three dimensions input block Jacobi (solve to 1e-12) line Jacobi (solve to 1e-12) difference / largest derivative, solves to 1e-12 difference / largest derivative, solves to 1e-10 block Jacobi: change from 1e-10 to 1e-12 / largest derivative mach -0.00635239 -0.00635239 8.6433e-08 2.4227e-06 7.4871e-06 re -0.0055249 -0.0055249 6.1495e-11 2.9472e-10 2.6955e-10 alpha 2.2762e-14 -3.4771e-14 9.0568e-12 6.3684e-11 7.2750e-11\nSteady laminar flow past a sphere at a Reynolds number of 100, degree 2, on a cubed-sphere shell whose layers grow by a factor 2.2 from the wall: the first layers are several times wider than they are thick, and the lines run radially through them in all six blocks of the mesh, whose local axes are rotated against each other. Elements further out are close to isotropic and stay with block Jacobi. The comparison is narrower than that of the flat plate: the two smoothers on the p-multigrid levels 2, 1, 0 only, with no run on one level and none with lagged blocks, and with blocks of 135\\times135 instead of 45\\times45 . The checks require the same drag coefficient with both smoothers to 10^{-6} relative, fewer than half the Krylov iterations with line Jacobi in the forward and in the transposed solve to 10^{-10} , and adjoint derivatives with respect to Mach number, Reynolds number and angle of attack that agree to 10^{-6} of the largest derivative when the transposed systems are solved to 10^{-12} . On the flat plate the derivatives come from solves to 10^{-10} and each must agree to 10^{-6} of its own value; why this case is checked differently is explained below .\nWith transposed solves to 10^{-10} the two smoothers give Mach-number derivatives of the drag that differ in the sixth digit, while the Reynolds-number derivatives agree to ten. At a fixed state the drag of this case does not depend on the Mach number (it is the momentum flux through an adiabatic wall), so the derivative is the adjoint-weighted term -\\boldsymbol\\psi^{\\mathsf T}\\,\\partial\\mathbf R_w/\\partial M alone ( adjoint formula ). That term is badly conditioned: the Mach number enters the residual through the far-field state, whose pressure 1/(\\gamma M^2) changes by -2/(\\gamma M^3) per unit Mach number, and at the low Mach number of this case the flow answers mostly with a shift of its pressure level, which exerts no net force on a closed body. The derivative of the drag is the small remainder of that large response. An error of 10^{-10} in the adjoint is amplified accordingly, with either smoother: the last column shows that the block-Jacobi value itself moves by as much when the tolerance is tightened. At 10^{-12} the two agree, which is what the check requires. For a design loop at low Mach number the consequence is the same as everywhere else in Gyre: the tolerance of totals is the accuracy of the derivative, scaled by its conditioning.\nPassed Single-precision blocks in the preconditioner 6 of 6 checks passed · 2649 s · quick suite Each case is solved twice from the free stream, with the blocks of the smoother in double and in single precision (solver.precond_precision), and at the converged flow one forward and one transposed system with the same right-hand side are solved to a relative tolerance of 1e-10. A rounded preconditioner must change neither the answers nor, to speak of, the work: the checks require the same drag coefficient, the same solutions of the linear systems, at most a tenth more Krylov iterations, and half the memory for the stored blocks. The cases cover turbulent flow, whose blocks are the worst conditioned, with both smoothers, and laminar flow in three dimensions. every flow solve and every linear solve converged, in both precisions yes expected true pass drag coefficient: largest relative difference between the precisions 2.25e-14 expected < 1e-08 pass solutions of the linear systems (tolerance 1e-10): largest relative difference between the precisions 4.47e-11 expected < 1e-07 pass Krylov iterations of the flow solve, single / double: largest ratio 1 expected < 1.1 pass Krylov iterations of the linear solves, single / double: largest ratio 1 expected < 1.1 pass memory of the stored blocks, single / double: largest ratio 0.5 expected < 0.51 pass Run notes With solver.precond_precision 'single' the columns of the element blocks (and of the coupling blocks of the line smoother) are cast to float32 as they are computed, the inverses and the factorisation of the line systems are computed in float64 from them and rounded, and the blocks are applied in float32. The residual, its Jacobian products and the Krylov vectors are float64 throughout, so the converged flow and the solutions of the linear systems are those of the same equations: the differences in the table are the tolerances of the solves. The cases are small; what the option is for, a problem that does not fit the device otherwise, is in the study scale_3d. Double against single precision of the smoother's blocks Single-precision blocks in the preconditioner case blocks unknowns nonlinear steps Krylov iterations, flow solve drag coefficient Krylov iterations, forward solve Krylov iterations, transposed solve linear solutions: relative difference to double stored blocks [MB] turbulent flat plate (SA) double 6750 35 540 0.00575365 238 249 — 2.94 turbulent flat plate (SA) single 6750 35 540 0.00575365 238 249 1.7075e-11 1.47 turbulent flat plate (SA), line smoother double 6750 35 267 0.00575365 66 63 — 8.8984 turbulent flat plate (SA), line smoother single 6750 35 267 0.00575365 66 63 4.4650e-11 4.4492 laminar sphere (3-D), Re = 100, degree 1 double 3840 14 79 2.13486 46 43 — 1.248 laminar sphere (3-D), Re = 100, degree 1 single 3840 14 79 2.13486 46 43 4.1034e-14 0.624\nsolver.precond_precision: single keeps the blocks of the smoother (the element-block inverses and the factors of the line systems) in single precision ( how ). The residual, its Jacobian products and the Krylov vectors stay in double precision, so the equations that are solved are the same, and a preconditioner that is rounded in the eighth digit should cost no iterations either. Each case is solved twice from the free stream and then one forward and one transposed system are solved to 10^{-10} at the converged flow. The checks require the same drag coefficient, the same solutions of the linear systems, at most a tenth more Krylov iterations and half the memory of the stored blocks. The turbulent flat plate is the hard case for a rounded block (its turbulence rows are scaled by six orders against the mean-flow rows) and is run with both smoothers; a small laminar sphere adds three dimensions. The cases are small. What the option is for, a problem that does not fit the device with double-precision blocks, is recorded with the largest 3-D solve .\nInside a session each solve restarts from the previous converged state in the Newton regime. The service test-suite asserts that a re-solve after a small change of angle of attack needs fewer nonlinear iterations than the cold start. A solve that does not converge does not replace that state: the next solve warm-starts from the last converged one ( tests/test_discipline.py::test_failed_solve_keeps_last_converged_state ).","title":"Solver convergence","url":"verification/solvers.html"},{"audience":[],"chunks":[{"anchor":"","end":477,"heading":"","level":2,"start":0},{"anchor":"inviscid-sphere-exact-properties","end":3671,"heading":"Inviscid sphere: exact properties","level":2,"start":478},{"anchor":"why-a-sphere-and-what-is-exact-about-it","end":4449,"heading":"Why a sphere, and what is exact about it","level":2,"start":3672},{"anchor":"what-the-convergence-table-does-and-does-not-establish","end":4889,"heading":"What the convergence table does and does not establish","level":3,"start":4450},{"anchor":"laminar-sphere-re-100","end":6990,"heading":"Laminar sphere, Re = 100","level":2,"start":4890},{"anchor":"the-comparison","end":7550,"heading":"The comparison","level":2,"start":6991},{"anchor":"adjoint-derivatives-in-3-d","end":11124,"heading":"Adjoint derivatives in 3-D","level":2,"start":7551},{"anchor":"what-is-differentiated","end":11556,"heading":"What is differentiated","level":2,"start":11125},{"anchor":"largest-solve-on-one-device","end":15731,"heading":"Largest solve on one device","level":2,"start":11557},{"anchor":"reading-these-numbers","end":16964,"heading":"Reading these numbers","level":2,"start":15732},{"anchor":"consistency-with-two-dimensions","end":18586,"heading":"Consistency with two dimensions","level":2,"start":16965},{"anchor":"why-this-is-a-meaningful-test","end":18896,"heading":"Why this is a meaningful test","level":2,"start":18587},{"anchor":"what-the-extrusion-does-not-cover","end":19241,"heading":"What the extrusion does not cover","level":3,"start":18897}],"headings":["Inviscid sphere: exact properties","Why a sphere, and what is exact about it","What the convergence table does and does not establish","Laminar sphere, Re = 100","The comparison","Adjoint derivatives in 3-D","What is differentiated","Largest solve on one device","Reading these numbers","Consistency with two dimensions","Why this is a meaningful test","What the extrusion does not cover"],"keywords":["3-D","hexahedra","sphere","cubed sphere","face orientation","sideslip","moments","extrusion"],"level":2,"max_level":3,"path":"verification/three-dimensional.md","section":"verification","section_title":"Verification","summary":"Flow past a sphere on an unstructured cubed-sphere mesh of curved hexahedra tests the 3-D path on a genuinely three-dimensional flow, inviscid against exact properties and laminar against accepted data, with adjoint derivatives of all six force and moment coefficients; a spanwise extrusion of the aerofoil case must reproduce the two-dimensional solution.","text":"Two- and three-dimensional computations share the physics kernels but differ in everything dimensional: metric terms, neighbour coupling across hexahedral faces in eight possible relative orientations, one more direction of flux differencing, larger element blocks, surface integration over curved quadrilateral faces, three force and three moment components, and the adjoint of all of it. The studies on this page exercise that path on flows that vary in all three directions.\nPassed Inviscid flow past a sphere (3-D) 9 of 9 checks passed · 424.7 s · quick suite Subsonic inviscid flow (M = 0.2) past a sphere of diameter 1 in a spherical far field of radius 10, on a cubed-sphere mesh of curved hexahedra (geometry degree 4): six blocks whose local axes are rotated against each other, so that the block interfaces contain all eight relative face orientations and the flow solve itself exercises the orientation tables. The exact solution is isentropic, has no drag (d'Alembert) and, by symmetry, no lift, side force or moment. Its surface pressure is known to O(M²) from the Janzen–Rayleigh expansion of the compressible potential, whose first correction is derived in closed form in the study module (surface speed 3/2 + 0.3131 M² at the equator) and checked numerically here; the neglected terms are O(M⁴) = 1.6e-03 times a coefficient of order one. Reported per polynomial degree: the L2 entropy error, the drag coefficient (frontal-area reference) and the area-weighted L2 difference of the wall pressure coefficient to that reference. Janzen–Rayleigh correction solves its equation (largest defect) 2.99e-06 expected < 1e-5 (finite-difference check) pass all solves converged yes expected true pass block interfaces show all 8 relative face orientations 8 expected = 8 pass entropy error falls with p (smallest ratio between consecutive orders) 2.494 expected > 2 pass spurious drag |cd| falls with p (smallest ratio) 2.103 expected > 1.5 pass spurious drag |cd| at the highest resolution 0.003294 expected < 0.03 pass surface cp error against Janzen–Rayleigh falls with p (smallest ratio) 3.084 expected > 1.5 pass surface cp, area-weighted L2 difference to Janzen–Rayleigh, highest resolution 0.01099 expected < 0.03 pass lift, side force and moments vanish (largest |coefficient|) 1.11e-13 expected < 1e-8 pass Run notes The far-field boundary at 20 radii imposes the free stream through a Riemann flux; the potential-flow disturbance there is of relative size (a/r)³/2 = 6e-5. The drag and entropy thresholds are bounds observed with margin on this mesh, not asymptotic rates: four polynomial degrees on one mesh do not establish an order of convergence. Surface pressure coefficient Inviscid flow past a sphere (3-D) Convergence with the polynomial degree Inviscid flow past a sphere (3-D) elements p DOF Newton steps residual reduction entropy error cd cp L2 diff. (Janzen–Rayleigh) cp L2 diff. (incompressible speed) cp L2 diff. (incompressible) cp max diff. largest |cl, cs, cm| solve time (s) 216 1 8640 11 4.9310e-11 2.5017e-05 0.123133 0.605749 0.586555 0.593691 0.997252 4.2407e-15 43.7913 216 2 29160 12 6.0815e-12 1.0030e-05 0.0585389 0.196404 0.182213 0.186304 0.751574 1.1084e-13 54.2957 216 3 69120 13 1.4683e-12 2.3377e-06 0.0144415 0.0612559 0.0550651 0.055083 0.320863 1.4419e-14 39.5968 216 4 135000 13 3.5167e-11 5.9278e-07 0.00329403 0.01099 0.0192673 0.0117749 0.0532136 6.3655e-14 111.039 Interior faces by relative orientation (transpose, flip first, flip second) Inviscid flow past a sphere (3-D) orientation code faces 0 924 1 24 2 24 3 24 4 48 5 48 6 48 7 48 Error measures against degrees of freedom Inviscid flow past a sphere (3-D)\nSubsonic inviscid flow past a sphere is isentropic, has no drag (d’Alembert’s paradox) and, by symmetry, no lift, side force or moment. Every one of these is a number the computation should reproduce, and none needs a reference solution. The surface pressure is compared with the Janzen–Rayleigh expansion of the compressible potential flow, which is exact up to terms of order M^4 ; its first correction is derived in closed form in the study and checked numerically there. The mesh is a cubed sphere: six blocks of curved hexahedra, three meeting at each of the eight cube corners, with the local axes of the blocks deliberately rotated against each other. The flow solve therefore passes through all eight relative face orientations, which a structured block never produces.\nEntropy error, spurious drag and the pressure difference fall with every increase of the polynomial degree on a fixed mesh of 216 elements. Four degrees on one mesh show convergence; they do not establish an asymptotic order, and the thresholds of the checks are bounds observed with margin on this mesh. The lift, side force and moments are zero to round-off at every degree, because the discretization preserves the symmetry of the mesh.\nPassed Laminar flow past a sphere, Re = 100 (3-D) 5 of 5 checks passed · 187.8 s · quick suite Steady three-dimensional Navier–Stokes solve: sphere of diameter 1, Re = 100 on the diameter, M = 0.2, adiabatic no-slip wall, far field at 10 diameters; cubed-sphere mesh of 576 curved hexahedra (geometry degree 3) with mixed block orientations, BR2 viscous fluxes, Newton–Krylov with p-multigrid. At this Reynolds number the flow is steady and axisymmetric with a closed recirculation bubble. References: drag coefficient 1.09 (standard drag curve of Clift, Grace & Weber 1978; Johnson & Patel, JFM 378, 1999, compute 1.08–1.10) and a bubble length of about 0.87 diameters (Johnson & Patel 1999; Taneda 1956). Both references are for incompressible flow in an unbounded domain; at M = 0.2 with the boundary at 10 diameters a difference of the order of one per cent is expected, which the tolerance on the drag covers. Drag is referred to the frontal area π/4. all solves converged to a steady state yes expected true pass drag coefficient, highest order: |cd − 1.09| / 1.09 0.01409 expected < 0.03 pass wake is axisymmetric: lift, side force and moments (largest |coefficient|) 1.77e-15 expected < 1e-7 pass a closed recirculation bubble exists; length in diameters, highest order 0.9501 expected within 15 % of 0.87 pass adiabatic wall: integrated heat flux 0 expected < 1e-10 pass Run notes The mesh has the symmetry of the cube, so the vanishing lift and side force show that the solver preserves a symmetric solution; they do not test the stability of the axisymmetric wake (which is stable below Re of about 210). The bubble length is read from the solution nodes on the wake axis by linear interpolation between the two nodes where the velocity changes sign. Sphere at Re = 100 Laminar flow past a sphere, Re = 100 (3-D) p elements DOF Newton steps residual reduction cd bubble length / D first cell height / D largest |cl, cs, cm| solve time (s) 2 576 77760 14 1.3330e-11 1.10536 0.950129 0.185684 1.7681e-15 162.798 Streamwise velocity on the wake axis Laminar flow past a sphere, Re = 100 (3-D)\nAt a Reynolds number of 100 the flow past a sphere is steady and axisymmetric with a closed recirculation bubble. The drag coefficient is compared with the standard drag curve and with the computations and measurements of Johnson and Patel, the length of the bubble with the same sources ( bibliography ). The Mach number of 0.2 leaves a compressibility effect of the order of a per cent in the drag, which is why the tolerance is three per cent and not tighter. The chart shows the streamwise velocity on the wake axis: the bubble ends where it changes sign.\nPassed Adjoint derivatives of 3-D outputs 6 of 6 checks passed · 297.7 s · quick suite An ellipsoid with three different semi-axes (0.5, 0.35, 0.6) at 4 degrees angle of attack and 3 degrees sideslip, M = 0.3, on a mixed-orientation cubed-sphere mesh: nothing is symmetric, so all six wind-axis force and moment coefficients (drag, lift, side force, roll, yaw, pitch about an offset reference point) and all their derivatives are non-zero. Total derivatives from one adjoint solve per output are compared with central finite differences of re-converged solves with respect to the angle of attack, the sideslip angle, the Mach number (and the Reynolds number for the viscous case), and, as a directional derivative, with respect to a smooth three-dimensional displacement of the surface nodes (the shape input x_surface, propagated into the volume by the mesh deformation). The finite differences are accurate to about 1e-7 in absolute terms (step 1e-5, solves converged 12 orders), so errors are relative to the reference value or, for a derivative that is small through cancellation, to 1 % of the largest derivative with respect to the same input (and at least 1e-3). adjoint vs central differences, d/dalpha: largest relative error 3.28e-06 expected < 1e-5 pass adjoint vs central differences, d/dbeta: largest relative error 1.46e-06 expected < 1e-5 pass adjoint vs central differences, d/dmach: largest relative error 2.33e-06 expected < 1e-5 pass adjoint vs central differences, d/dx_surface: largest relative error 1.97e-06 expected < 1e-4 pass adjoint linear solves reached their tolerance (largest reduction) 9.99e-11 expected < 1e-9 pass the flow responds to the sideslip angle (free stream turned by beta) yes expected true pass Adjoint totals against central differences Adjoint derivatives of 3-D outputs case output input adjoint finite difference relative error inviscid ellipsoid, p = 2 cd alpha 0.43361 0.43361 7.3819e-07 inviscid ellipsoid, p = 2 cl alpha 2.33525 2.33525 3.5024e-07 inviscid ellipsoid, p = 2 cs alpha -0.173265 -0.173265 2.2887e-06 inviscid ellipsoid, p = 2 cmx alpha -0.00796038 -0.0079603 3.2793e-06 inviscid ellipsoid, p = 2 cmy alpha 0.0429865 0.0429866 1.4651e-06 inviscid ellipsoid, p = 2 cmz alpha 0.50961 0.50961 4.4519e-08 inviscid ellipsoid, p = 2 cd beta 0.051034 0.051034 1.0742e-06 inviscid ellipsoid, p = 2 cl beta -0.471577 -0.471577 2.2216e-07 inviscid ellipsoid, p = 2 cs beta -0.539389 -0.539389 4.4874e-07 inviscid ellipsoid, p = 2 cmx beta 0.0455927 0.0455927 1.4592e-06 inviscid ellipsoid, p = 2 cmy beta 0.196756 0.196756 1.9583e-07 inviscid ellipsoid, p = 2 cmz beta -0.0172066 -0.0172066 7.9000e-08 inviscid ellipsoid, p = 2 cd mach -0.0167098 -0.0167097 2.3326e-06 inviscid ellipsoid, p = 2 cl mach -0.714292 -0.714292 2.3132e-07 inviscid ellipsoid, p = 2 cs mach -0.26575 -0.26575 2.0726e-08 inviscid ellipsoid, p = 2 cmx mach 0.0190407 0.0190407 2.7440e-07 inviscid ellipsoid, p = 2 cmy mach 0.0335359 0.0335359 8.4426e-08 inviscid ellipsoid, p = 2 cmz mach -0.0145547 -0.0145546 4.4813e-07 inviscid ellipsoid, p = 2 cd x_surface (directional) -0.00211058 -0.00211059 1.9694e-06 inviscid ellipsoid, p = 2 cl x_surface (directional) -0.0617161 -0.0617161 1.4978e-07 inviscid ellipsoid, p = 2 cs x_surface (directional) -0.0438045 -0.0438045 9.6410e-08 inviscid ellipsoid, p = 2 cmx x_surface (directional) 0.00296326 0.00296326 1.0509e-07 inviscid ellipsoid, p = 2 cmy x_surface (directional) 0.0163957 0.0163957 1.5919e-08 inviscid ellipsoid, p = 2 cmz x_surface (directional) -9.4583e-04 -9.4583e-04 3.6248e-08\nAn ellipsoid with three different semi-axes at an angle of attack and a sideslip angle, with the moment reference off the centre, so that none of the six coefficients and none of their derivatives vanishes by symmetry. Adjoint totals of drag, lift, side force and the three moments with respect to angle of attack, sideslip, Mach number and a displacement of the surface are compared with central differences of re-converged flows.\nPassed Largest 3-D solve on one device 7 of 7 checks passed · 452 s · quick suite Inviscid flow (M = 0.3) past a sphere on a cubed-sphere mesh of 7680 curved hexahedra (geometry degree 2, mixed block orientations) at p = 2: 1,036,800 degrees of freedom, solved on the GPU (NVIDIA GeForce RTX 3060 Ti) with the standard pseudo-transient Newton–Krylov solver and p-multigrid [2, 1, 0]: residual, Jacobian products and Krylov vectors in double precision, the blocks of the smoother in single precision (solver.precond_precision). Solved to a residual reduction of 1e-8, followed by one discrete adjoint solve for the drag coefficient (derivatives with respect to angle of attack, sideslip, Mach number and the position of each of the 6,146 surface nodes). Wall-clock times include just-in-time compilation unless stated; they depend on what else the machine was doing, which is recorded below. The configuration depends on level and device: quick/CPU 4x4x4 blocks, quick/GPU and full/GPU 16x16x5 (1,036,800 unknowns, single-precision blocks), full/CPU 8x8x5. On the GPU the same solve is first tried with double-precision blocks in a process of its own; what happened is in the table of sizes and is not checked, because it depends on the memory of the device. steady solve converged yes expected true pass residual reduction (orders of magnitude) 9.375 expected >= 8 pass adjoint linear solve converged (residual reduction) 9.97e-09 expected < 1e-7 pass spurious drag of the inviscid sphere 0.01716 expected < 0.1 pass lift and side force vanish 1.82e-14 expected < 1e-7 pass shape derivative of the drag for every surface node: finite, and its net transverse component vanishes by symmetry (largest |sum|, relative to the largest entry) 1.28e-10 expected < 1e-6 pass degrees of freedom 1036800 expected >= 0 pass Run notes The timings were taken on a busy machine (other processes used 87 % of the capacity of the 8 CPUs while the study ran; the GPU was 15 % utilized when the study started). What limits the size on this device: the element-block inverses of the block-Jacobi smoother are dense (block size squared per element and level, in double precision) and their construction holds several copies at once; the Krylov basis is small in comparison. The mesh-deformation operator behind the shape derivative is a dense (volume nodes x surface nodes) matrix; above 2e7 entries it is applied matrix-free, with its transpose written out chunk by chunk, and is not stored. Problem size and memory Largest 3-D solve on one device quantity value elements 7680 polynomial degree 2 degrees of freedom 1036800 block size 135 geometry nodes 67606 surface nodes (shape inputs: three coordinates each) 6146 mesh deformation applied matrix-free yes a stored deformation matrix would take (GB) 3.32405 precision of the smoother's blocks single element-block inverses, all levels (GB) 0.609792 the same blocks in double precision would take (GB) 1.21958 the same solve with double-precision blocks on this device out of device memory peak device memory after the solve (GB) 3.5992 peak device memory after the adjoint (GB) 3.5992 smallest scaled Jacobian 0.267033 Wall-clock times (s) Largest 3-D solve on one device phase seconds mesh generation, discretization and discipline set-up 15.2393 face connectivity (build_connectivity) alone 0.114721 steady solve, including compilation 194.389 adjoint total derivatives of cd, first call (with compilation) 82.4142 adjoint total derivatives of cd, second call 26.6624 VTU output (file size in MB: 30) 3.74941 surface payload (1536 wall faces) 16.5098 Solver statistics Largest 3-D solve on one device quantity value Newton steps 15 Krylov iterations 256 residual reduction 4.2146e-10 adjoint Krylov iterations 153 adjoint residual reduction 9.9657e-09 cd 0.0171553 entropy error 8.9133e-06 d cd / d alpha -2.4909e-10 d cd / d mach 0.0115206 largest |d cd / d x_surface| over the surface nodes 3.6251e-04 net transverse shape derivative (largest |sum|) 4.6532e-14 Residual history Largest 3-D solve on one device The line smoother for meshes with thin layers at the wall is measured on a sphere as well: line smoothing in three dimensions .\nThe record states the largest steady 3-D solve that was run on the device named in its provenance, with one adjoint solve that returns the derivative of the drag with respect to the flow parameters and to the position of every surface node, and what it cost in memory and wall time. The sphere is symmetric about the flow axis, so the net shape derivative transverse to the flow must vanish: that is the check on the shape derivative at this size (agreement with finite differences is checked on a smaller case above). It is a measurement of this implementation on that machine, with the load of the machine recorded alongside; it is not a scaling study. Problem size in 3-D is bounded by the memory of one device: the block-Jacobi inverses hold n_\\text{var}^2 (p+1)^{6} numbers per element, and their assembly is batched to a fixed memory budget ( performance ). On the GPU the blocks of this solve are kept in single precision ( solver.precond_precision: single , what that changes and what it does not ); the table also states what happened when the same solve was first tried with double-precision blocks, in a process of its own on the same device. That outcome depends on the memory of the device and is reported, not checked.\nPassed Three-dimensional consistency 8 of 8 checks passed · 300.8 s · quick suite The inviscid NACA 0012 case is solved in 2-D and on a spanwise-periodic extrusion of the same section into curved hexahedra. A correct 3-D implementation — metrics in conservative-curl form, neighbour coupling across hexahedral faces, the Newton–Krylov solver with p-multigrid, force integration and the discrete adjoint — must reproduce the 2-D outputs and derivatives per unit span, with no spanwise flow. A structured extrusion only contains faces whose two sides use the same local axes, so this study cannot detect an error in the face-orientation tables; those are exercised by the imported-mesh study and by unit tests on randomly re-oriented meshes. both solves converged yes expected true pass cl: |3-D − 2-D| / |2-D| 3.45e-13 expected < 1e-08 pass cd: |3-D − 2-D| / |2-D| 1.64e-13 expected < 1e-08 pass cm: |3-D − 2-D| / |2-D| 4.18e-12 expected < 1e-08 pass dcl_dalpha: |3-D − 2-D| / |2-D| 1.02e-11 expected < 1e-06 pass dcl_dmach: |3-D − 2-D| / |2-D| 4.42e-09 expected < 1e-06 pass spanwise momentum (max |ρw|) 7.35e-14 expected < 1e-10 pass spanwise variation of the state 6.05e-14 expected < 1e-10 pass 2-D solution versus 3-D extrusion Three-dimensional consistency quantity 2-D 3-D (per unit span) relative difference cl 0.268485 0.268485 3.4549e-13 cd 0.00558541 0.00558541 1.6399e-13 cm -0.00179499 -0.00179499 4.1762e-12 dcl_dalpha 7.51815 7.51815 1.0186e-11 dcl_dmach 0.186709 0.186709 4.4154e-09 Problem sizes Three-dimensional consistency case dim elements DOF Newton steps 2-D 2 64 2304 15 3-D extruded 3 192 25920 24\nA spanwise-periodic extrusion of the aerofoil case into curved hexahedra must reproduce the two-dimensional solution, its forces and its adjoint derivatives. An error in the 3-D metric terms, the face coupling, the larger blocks or the surface integration would break the agreement or produce a spanwise flow.\nThe flow is two-dimensional by construction and a structured extrusion only produces the identity relative face orientation. Both gaps are what the sphere studies above close; the discrete invariants on warped periodic hexahedral meshes are tested separately in Discrete invariants , and unstructured meshes read from files in Imported meshes .","title":"Three-dimensional flows","url":"verification/three-dimensional.html"},{"audience":[],"chunks":[{"anchor":"","end":399,"heading":"","level":2,"start":0},{"anchor":"order-of-accuracy","end":3559,"heading":"Order of accuracy","level":2,"start":400},{"anchor":"what-is-solved","end":4096,"heading":"What is solved","level":2,"start":3560},{"anchor":"reading-the-orders","end":4405,"heading":"Reading the orders","level":3,"start":4097},{"anchor":"exact-linearisation","end":7972,"heading":"Exact linearisation","level":2,"start":4406},{"anchor":"what-the-columns-mean","end":8617,"heading":"What the columns mean","level":2,"start":7973},{"anchor":"why-the-wall-distance-column-matters","end":9065,"heading":"Why the wall-distance column matters","level":3,"start":8618},{"anchor":"turbulent-flat-plate","end":13333,"heading":"Turbulent flat plate","level":2,"start":9066},{"anchor":"the-case-and-the-reference","end":13974,"heading":"The case and the reference","level":2,"start":13334},{"anchor":"what-the-comparison-does-and-does-not-show","end":14637,"heading":"What the comparison does and does not show","level":3,"start":13975},{"anchor":"three-dimensions","end":21428,"heading":"Three dimensions","level":2,"start":14638},{"anchor":"what-the-extrusion-tests","end":22406,"heading":"What the extrusion tests","level":2,"start":21429},{"anchor":"why-the-three-dimensional-solution-is-not-exactly-uniform-along-the-span","end":23341,"heading":"Why the three-dimensional solution is not exactly uniform along the span","level":3,"start":22407},{"anchor":"what-an-extrusion-does-not-cover","end":23749,"heading":"What an extrusion does not cover","level":3,"start":23342},{"anchor":"adjoint-derivatives","end":26042,"heading":"Adjoint derivatives","level":2,"start":23750},{"anchor":"what-is-differentiated","end":26427,"heading":"What is differentiated","level":2,"start":26043},{"anchor":"the-linear-solve-on-a-wall-resolved-mesh","end":26983,"heading":"The linear solve on a wall-resolved mesh","level":3,"start":26428}],"headings":["Order of accuracy","What is solved","Reading the orders","Exact linearisation","What the columns mean","Why the wall-distance column matters","Turbulent flat plate","The case and the reference","What the comparison does and does not show","Three dimensions","What the extrusion tests","Why the three-dimensional solution is not exactly uniform along the span","What an extrusion does not cover","Adjoint derivatives","What is differentiated","The linear solve on a wall-resolved mesh"],"keywords":["RANS","Spalart-Allmaras","flat plate","skin friction","manufactured solution","wall distance","adjoint"],"level":2,"max_level":3,"path":"verification/turbulence.md","section":"verification","section_title":"Verification","summary":"The Spalart–Allmaras RANS closure is verified in four steps: a manufactured solution for the order of accuracy of the coupled system, exactness of its linearisation including the wall distance, the turbulent flat plate of the NASA Turbulence Modeling Resource for the model itself, and adjoint derivatives against finite differences.","text":"A turbulence model adds an equation whose terms are stiff, non-conservative and singular at the wall. Each of the four studies below isolates one way in which that can go wrong: the discretization of the new terms, their derivatives, the model as implemented against two established codes, and the total derivatives an optimizer would consume. The formulation is described under Turbulence closure .\nPassed Manufactured solutions: RANS with the Spalart-Allmaras model 9 of 9 checks passed · 455.8 s · quick suite Steady manufactured solution of the compressible RANS equations coupled to the negative Spalart-Allmaras model on the unit box (M = 0.5, Re = 1000, nu_tilde / nu = 8 (1 + 0.4 sin), a smooth positive manufactured wall distance between 0.1 and 0.8), Dirichlet data from the exact state on all sides. The forcing is generated by automatic differentiation of the same flux and source kernels the solver uses (turbulence.manufactured_source). Errors: L2 norm of the mean-flow conserved variables, and of rho nu_tilde scaled by Re. The solve starts from the nodal values of the exact solution and removes the truncation-error residual with p-multigrid Newton-Krylov. The eddy viscosity of the manufactured state is 4.7 times the molecular one, so the mean flow sees an effective Reynolds number of about 175: between the diffusion- and the convection-dominated regimes of the laminar manufactured solutions, where the state converges at a rate between p and p + 1. The laminar manufactured solution with the same mean state is therefore solved at that Reynolds number on the same meshes, and the checks require the guaranteed rate p of both equations and that the model changes neither the order nor the size of the mean-flow error. p = 1: order of the mean-flow state (finest pair) 1.914 expected >= 0.75 (guaranteed rate p = 1, tolerance 0.25; design rate 2) pass p = 1: order of nu_tilde (finest pair) 2.202 expected >= 0.75 (guaranteed rate p = 1, tolerance 0.25; design rate 2) pass p = 1: mean-flow order with the model minus the laminar order at the effective Reynolds number 0.006866 expected > -0.15 pass p = 1: mean-flow error with the model over the laminar error at the effective Reynolds number (finest mesh) 1.024 expected 0.5 .. 1.5 pass p = 2: order of the mean-flow state (finest pair) 2.477 expected >= 1.75 (guaranteed rate p = 2, tolerance 0.25; design rate 3) pass p = 2: order of nu_tilde (finest pair) 2.612 expected >= 1.75 (guaranteed rate p = 2, tolerance 0.25; design rate 3) pass p = 2: mean-flow order with the model minus the laminar order at the effective Reynolds number -0.07206 expected > -0.15 pass p = 2: mean-flow error with the model over the laminar error at the effective Reynolds number (finest mesh) 1.148 expected 0.5 .. 1.5 pass every model term is active at the domain centre (smallest |term| Re) 0.00169 expected > 1e-3 pass RANS-SA manufactured solution Manufactured solutions: RANS with the Spalart-Allmaras model Errors on the finest mesh and observed orders (finest pair) Manufactured solutions: RANS with the Spalart-Allmaras model p mean-flow error order nu_tilde error x Re order laminar error at the effective Re order 1 0.00592756 1.91436 0.0231004 2.20221 0.00578644 1.90749 2 1.5584e-04 2.47676 6.9494e-04 2.61163 1.3574e-04 2.54882 Model terms of the manufactured state at (0.5, 0.5), times Re Manufactured solutions: RANS with the Spalart-Allmaras model term value production 0.00168964 destruction 2.22251 cb2 0.0839335 density_gradient -0.0667243 eddy viscosity / molecular viscosity 7.20704\nA smooth manufactured state of the coupled system (mean flow and \\tilde\\nu ) on the unit square, with a manufactured, strictly positive wall-distance field so that production, destruction and the gradient terms are all active. The forcing is generated by automatic differentiation of the kernels the solver itself uses, and the steady problem is solved with the implicit solver on three meshes per degree. The second table lists the size of each model term of the manufactured state, to show that none of them is negligible in the test.\nThe mean flow and the turbulence variable are measured separately, the latter scaled by the Reynolds number so that both are of order one. The study requires the rate that the laminar manufactured solutions establish for this discretization ( order of accuracy ); the observed orders are reported next to it.\nPassed Exact linearization with the Spalart-Allmaras model 20 of 20 checks passed · 158.6 s · quick suite Jacobian-vector products of the weak RANS-SA residual from automatic differentiation against central differences (best of three step sizes), the dot-product identity of tangent and adjoint products, the element-diagonal blocks from the split-argument residual against the dense Jacobian (2-D), and the derivative with respect to the geometry nodes, which includes the wall distance. States are random perturbations of the free stream with nu_tilde / nu between -2 and 12, so both branches of the model and its limiters are differentiated; every boundary-condition type appears. 2-D ring, adiabatic wall, far field: JVP vs central difference 4.13e-10 expected < 1e-6 pass 2-D ring, adiabatic wall, far field: JVP vs central difference, turbulence row 3.95e-09 expected < 1e-6 pass 2-D ring, adiabatic wall, far field: <v,Ju> = <J^T v,u> 7.51e-15 expected < 1e-12 pass 2-D ring, adiabatic wall, far field: shape derivative of the residual vs central difference 1.66e-10 expected < 1e-6 pass 2-D ring, adiabatic wall, far field: derivative of the wall distance w.r.t. the nodes 1.45e-09 expected < 1e-6 pass 2-D ring, adiabatic wall, far field: share of nodes on the negative branch 0.1759 expected 0.05 .. 0.5 pass 2-D ring, adiabatic wall, far field: block inverse times dense diagonal block 9.06e-14 expected < 1e-8 pass 2-D channel: isothermal wall, inlet, outlet, slip wall: JVP vs central difference 2.06e-10 expected < 1e-6 pass 2-D channel: isothermal wall, inlet, outlet, slip wall: JVP vs central difference, turbulence row 1.38e-09 expected < 1e-6 pass 2-D channel: isothermal wall, inlet, outlet, slip wall: <v,Ju> = <J^T v,u> 2.33e-14 expected < 1e-12 pass 2-D channel: isothermal wall, inlet, outlet, slip wall: shape derivative of the residual vs central difference 7.91e-11 expected < 1e-6 pass 2-D channel: isothermal wall, inlet, outlet, slip wall: derivative of the wall distance w.r.t. the nodes 3.73e-10 expected < 1e-6 pass 2-D channel: isothermal wall, inlet, outlet, slip wall: share of nodes on the negative branch 0.2099 expected 0.05 .. 0.5 pass 2-D channel: isothermal wall, inlet, outlet, slip wall: block inverse times dense diagonal block 8.23e-13 expected < 1e-8 pass 3-D box: adiabatic wall, far field, symmetry: JVP vs central difference 2.66e-10 expected < 1e-6 pass 3-D box: adiabatic wall, far field, symmetry: JVP vs central difference, turbulence row 6.48e-09 expected < 1e-6 pass 3-D box: adiabatic wall, far field, symmetry: <v,Ju> = <J^T v,u> 1.77e-14 expected < 1e-12 pass 3-D box: adiabatic wall, far field, symmetry: shape derivative of the residual vs central difference 6.76e-11 expected < 1e-6 pass 3-D box: adiabatic wall, far field, symmetry: derivative of the wall distance w.r.t. the nodes 4.21e-10 expected < 1e-6 pass 3-D box: adiabatic wall, far field, symmetry: share of nodes on the negative branch 0.1296 expected 0.05 .. 0.5 pass Relative defects Exact linearization with the Spalart-Allmaras model configuration p DOF negative nu_tilde share JVP - FD JVP - FD (turbulence row) dot product block inverse shape derivative wall distance 2-D ring, adiabatic wall, far field 2 540 0.175926 4.1315e-10 3.9495e-09 7.5077e-15 9.0650e-14 1.6640e-10 1.4456e-09 2-D channel: isothermal wall, inlet, outlet, slip wall 2 405 0.209877 2.0567e-10 1.3754e-09 2.3291e-14 8.2334e-13 7.9132e-11 3.7334e-10 3-D box: adiabatic wall, far field, symmetry 2 1296 0.12963 2.6557e-10 6.4804e-09 1.7685e-14 — 6.7631e-11 4.2069e-10\nRandom states with \\tilde\\nu/\\nu between -2 and 12 put a share of the nodes on the negative branch of the model (column 4), so both branches and the limiters are differentiated. The Jacobian-vector product from automatic differentiation is compared with a central difference of the residual, for all equations and for the turbulence equation alone; the dot-product identity ties the adjoint product to the tangent product; the block inverses of the preconditioner are multiplied with the diagonal blocks of a dense Jacobian (2-D). The last two columns differentiate with respect to the mesh nodes: the residual, and the wall distance by itself.\nThe wall distance is a minimum over the wall, and the solver stores the minimiser as interpolation weights on wall nodes instead of recomputing it when the geometry moves. The check moves the nodes, recomputes the closest points from scratch, and compares the difference quotient with the derivative of the stored representation. Agreement to the accuracy of the finite difference confirms the envelope argument of the theory page on these meshes.\nPassed Turbulent flat plate (NASA TMR) with the Spalart-Allmaras model 8 of 8 checks passed · 162.1 s · quick suite Zero-pressure-gradient turbulent flat plate of the NASA Turbulence Modeling Resource (2D Zero Pressure Gradient Flat Plate Verification Case: M = 0.2, Re = 5e6 per unit length, plate from x = 0 to 2, symmetry upstream to x = -0.33, far field at y = 1, total-pressure inflow, pressure outflow, adiabatic wall, free-stream nu_tilde = 3 nu). Each run starts from the uniform free stream and is converged nine orders by the pseudo-transient Newton-Krylov solver. The skin friction, taken from the numerical wall flux of the residual, is compared with the TMR's SA results on its finest grid (545 x 385 points; file FlatPlate/SA/cf_plate.dat, mean of CFL3D and FUN3D, which differ by less than 0.1 % at the stations used; at x = 0.97008 their grid-converged value is cf = 2.705513e-03). The meshes here are far coarser: the coarse mesh has 15 x 10 elements with a first element height of 5e-05. The comparison is with the reference model implementation (standard SA with the ft2 term; this code solves SA-neg, identical for positive nu_tilde), not with experiment. coarse mesh, p = 1: converged from the free stream (residual drop) 8.21e-10 expected <= 1e-9 pass coarse mesh, p = 1: Newton steps 33 expected <= 100 (cap 150) pass coarse mesh, p = 2: converged from the free stream (residual drop) 2.31e-11 expected <= 1e-9 pass coarse mesh, p = 2: Newton steps 34 expected <= 100 (cap 150) pass p = 2: |cf / cf_TMR - 1| at x = 0.97 0.001656 expected < 0.02 pass p = 2: largest |cf / cf_TMR - 1| over the stations x >= 0.5 0.02153 expected < 0.04 pass p = 1: largest |cf / cf_TMR - 1| over the stations x >= 0.5 0.09989 expected < 0.15 pass raising the degree from 1 to 2 reduces the largest difference (ratio) 0.2155 expected < 0.5 pass Run notes The leading-edge region (x < 0.3) is the least resolved: the first element on the plate is 0.02 long. Negative nu_tilde occurs at the edge of the boundary layer, where the negative branch of the model takes over; it is reported, not clipped. Not covered: a mesh-converged result, the velocity profile in wall units, other Reynolds numbers. Skin friction along the plate Turbulent flat plate (NASA TMR) with the Spalart-Allmaras model Runs Turbulent flat plate (NASA TMR) with the Spalart-Allmaras model run elements unknowns y+ of the first node off the wall (x = 0.97) Newton steps residual drop cf(x = 0.97) difference to TMR at x = 0.97 [%] largest difference, x >= 0.5 [%] cd (one wetted side, reference length 1) max nu_tilde / nu min nu_tilde / nu nodes with nu_tilde < 0 [%] coarse mesh, p = 1 150 3000 9.63577 33 8.2135e-10 0.00297114 9.81794 9.9885 0.00628466 327.141 -3.16499 0.166667 coarse mesh, p = 2 150 6750 4.59367 34 2.3071e-11 0.00270103 -0.165649 2.15281 0.00575365 386.72 -42.5509 1.55556 Skin friction at the TMR stations Turbulent flat plate (NASA TMR) with the Spalart-Allmaras model run x cf cf TMR difference [%] coarse mesh, p = 1 0.100573 0.00417967 0.00379009 10.2788 coarse mesh, p = 1 0.199971 0.0037162 0.0034172 8.75013 coarse mesh, p = 1 0.299841 0.00353274 0.00321396 9.91855 coarse mesh, p = 1 0.50151 0.00320101 0.00297659 7.53952 coarse mesh, p = 1 0.747281 0.00308886 0.00280835 9.9885 coarse mesh, p = 1 0.970084 0.00297114 0.00270551 9.81794 coarse mesh, p = 1 1.24823 0.00283694 0.0026112 8.64499 coarse mesh, p = 1 1.49524 0.00270903 0.00254669 6.37439 coarse mesh, p = 1 1.75299 0.00268374 0.00249222 7.68478 coarse mesh, p = 1 1.90334 0.00266898 0.0024652 8.26632 coarse mesh, p = 2 0.100573 0.00361473 0.00379009 -4.62671 coarse mesh, p = 2 0.199971 0.00339 0.0034172 -0.795826 coarse mesh, p = 2 0.299841 0.00314796 0.00321396 -2.05354 coarse mesh, p = 2 0.50151 0.00298899 0.00297659 0.416487 coarse mesh, p = 2 0.747281 0.0028373 0.00280835 1.03093 coarse mesh, p = 2 0.970084 0.00270103 0.00270551 -0.165649 coarse mesh, p = 2 1.24823 0.00262038 0.0026112 0.351467 coarse mesh, p = 2 1.49524 0.0025715 0.00254669 0.973867 coarse mesh, p = 2 1.75299 0.00254059 0.00249222 1.94095 coarse mesh, p = 2 1.90334 0.00251827 0.0024652 2.15281 Residual history (weighted norm, relative to the free stream) Turbulent flat plate (NASA TMR) with the Spalart-Allmaras model\nZero-pressure-gradient flat plate at M = 0.2 and \\mathit{Re} = 5\\times10^6 per unit length, on the domain of the NASA Turbulence Modeling Resource. The reference is the skin friction that two independent codes, CFL3D and FUN3D, obtain with the same model on the finest grid of that resource (545 × 385 points); their values agree with each other to four digits, so the comparison tests the implementation of the model, not the model. Gyre runs the case on a mesh of a few hundred elements whose first element is thin enough for the polynomial inside it to resolve the sublayer; the table reports y^+ of the first solution node off the wall.\nThe check is made at the station where the resource reports grid convergence ( x = 0.97 ) and over the stations of the downstream three quarters of the plate. Near the leading edge the coarse streamwise mesh and the numerical transition of the model dominate, and the difference is larger; the chart shows the whole plate. The solution contains nodes with negative \\tilde\\nu at the edge of the boundary layer (last columns of the run table): the situation the negative branch of the model exists for. This is one attached boundary layer on one mesh family. It says nothing about separated flow, pressure gradients, curvature or three-dimensional boundary layers.\nPassed Three-dimensional consistency with the Spalart-Allmaras model 10 of 10 checks passed · 675.1 s · quick suite The turbulent flat plate of the preset flat_plate_sa (M = 0.2, Re = 5e6, degree 2, first cell 5e-5 high) is solved on its 2-D section and on a spanwise-periodic extrusion of that section into hexahedra (3 across a span of 3), both from the free stream with line Jacobi as the smoother of the p-multigrid levels 2-1-0. A correct 3-D implementation of the RANS equations (viscous and turbulent fluxes, the source terms of the model, the wall distance, the boundary conditions, the line smoother, force integration and the discrete adjoint) must give the drag of the section per unit span, its skin friction and the adjoint derivative of the drag. Section and extrusion have the same in-plane equations only at the same BR2 penalty, whose default is the number of faces of an element (4 for quadrilaterals, 6 for hexahedra): the section is therefore solved with 6, the default of the extrusion, and once more with its own default, which is reported without a check. Three kinds of statement are checked. Before the 3-D solve, the solution of the section is extruded and put into the 3-D residual: the equations the section has must be satisfied as well as the section satisfies them (below 1e-8 of the free-stream residual; the flow solves stop at a drop of 1e-9), which tests the 3-D operator without the 3-D solver. The spanwise momentum equation is not satisfied by the extruded solution, for the reason given in the notes, so that the 3-D solution has a small spanwise velocity that alternates within each element: it must leave the layers identical and mirror-symmetric and exert no net spanwise force (round-off only: below 1e-8). Being odd across the mid-plane of an element, it cannot change the mean over the span at first order: drag, mean skin friction and the derivative must agree with the section to 1e-6. The size of the spanwise velocity itself is reported, not checked. the three flow solves converged from the free stream yes expected true pass the solution of the section, extruded, in the 3-D discretization: residual of the equations of the section, relative to the residual of the free stream 6.84e-10 expected < 1e-8 pass drag coefficient per unit span: |3-D - 2-D| / |2-D| 5.23e-09 expected < 1e-6 pass skin friction at x = 0.97008, mean over the span: |3-D - 2-D| / |2-D| 3.86e-09 expected < 1e-6 pass transposed solves reached their tolerance of 1e-10 (section and extrusion) yes expected true pass adjoint derivative of the drag with respect to the Mach number: |3-D - 2-D| / |2-D| 2.42e-11 expected < 1e-6 pass the spanwise layers repeat each other: largest difference of the state between a layer and the first 7.69e-12 expected < 1e-8 pass every layer is symmetric about its mid-plane (spanwise momentum odd, the other variables even): largest defect 1.08e-11 expected < 1e-8 pass no net spanwise force: |cs| 3.08e-19 expected < 1e-8 pass lines of the extrusion: those of the section in each spanwise layer (elements in lines) 393 expected = 3 x 131, in 3 x 18 lines pass Run notes The 3-D solution is not exactly uniform along the span: its spanwise momentum reaches 6.9e-07 and the in-plane variables differ by up to 7.4e-07 between spanwise nodes (table 'span'). This is a property of the BR2 scheme, not of the extrusion: the viscous flux inside an element is evaluated with the gradient lifted from all its faces, the flux through a face with the local gradient and the lifting of that face alone. Where the in-plane velocity jumps across in-plane faces, the two differ in the normal stress on the spanwise faces, which leaves a residual in the spanwise momentum equation of every element (table 'span': the extruded solution of the section). It changes sign across the mid-plane of the element, which is why the layers repeat each other and there is no net spanwise force; how little the mean over the span is affected is what the comparison with the section shows. With the default penalty of quadrilaterals (4) the section gives cd = 5.753647e-03 and cf = 2.701031e-03 at the station; the extrusion, whose default is 6, differs from these by 0.26 % and 0.16 %. That is a difference between two discretizations on a mesh of 150 elements, not an error of the 3-D implementation, and it is why the comparison is made at equal penalty. The extruded mesh has 54 lines holding 393 of its 450 elements (the longest 12 elements, 3 colours); 72900 unknowns in blocks of 162. An extrusion has only faces whose two sides use the same local axes, and the flow has no spanwise component to speak of: the study does not exercise the face-orientation tables, the two further components of the vorticity in the model or a flow that varies along the span. 2-D section versus 3-D extrusion Three-dimensional consistency with the Spalart-Allmaras model quantity 2-D section, penalty 6 3-D extrusion (per unit span) relative difference 2-D section, default penalty relative difference of the extrusion to it drag coefficient cd 0.00576871 0.00576871 5.2271e-09 0.00575365 0.00261786 skin friction cf at x = 0.97008 (mean over the span) 0.00270543 0.00270543 3.8640e-09 0.00270103 0.00162879 d cd / d Mach (adjoint) -0.0499672 -0.0499672 2.4161e-11 -0.0498736 0.00187671 The 3-D solution along the span (reported, not checked) Three-dimensional consistency with the Spalart-Allmaras model quantity value residual of the extruded solution of the section: its own equations / free stream 6.8406e-10 residual of the extruded solution of the section: spanwise momentum / free stream 2.0127e-08 largest spanwise momentum |rho w| of the 3-D solution (free stream: rho u = 1) 6.9476e-07 largest difference between spanwise nodes, in-plane variables 7.4299e-07 skin friction at x = 0.97008: largest |cf / mean - 1| over the spanwise node lines 2.4340e-09 3-D state, mean over the span, against the section (in-plane variables) 1.6715e-08 3-D state against the section, node for node (in-plane variables) 4.9774e-07 side-force coefficient cs 3.0839e-19 Problem sizes and solver statistics Three-dimensional consistency with the Spalart-Allmaras model case dim elements unknowns BR2 penalty reference area Newton steps Krylov iterations residual drop Krylov iterations of the adjoint solve residual reduction of the adjoint solve 2-D section 2 150 6750 6 1 32 248 7.4716e-10 57 6.8090e-11 3-D extrusion 3 450 72900 6 3 32 249 5.3546e-10 70 8.6562e-11 2-D section, default penalty 2 150 6750 4 1 34 267 1.5218e-11 58 6.1890e-11 Lines of the meshes Three-dimensional consistency with the Spalart-Allmaras model case lines elements in lines elements longest line colours 2-D section 18 131 150 12 3 3-D extrusion 54 393 450 12 3 2-D section, default penalty 18 131 150 12 3\nThe flat plate is solved on its two-dimensional section and on a spanwise-periodic extrusion of that section into hexahedra ( mesh.params.nz , span ). Everything the model adds runs through the three-dimensional code path there: the turbulent fluxes and source terms, the wall distance, the boundary conditions, the line smoother on a wall-resolved mesh of hexahedra, the force integration and the discrete adjoint. The drag per unit span, the skin friction at the reference station and the adjoint derivative of the drag with respect to the Mach number must be those of the section. Section and extrusion have the same in-plane equations only at the same BR2 penalty, whose default is the number of faces of an element: four for quadrilaterals, six for hexahedra. The comparison is therefore made with the section at penalty six; the last two columns of the table show the section at its own default, which differs by the amount two discretizations differ on this coarse mesh.\nThe solution of the section, extruded, satisfies the in-plane equations of the three-dimensional discretization as well as the section satisfies its own, which is checked before the three-dimensional solve and tests the operator without the solver. It does not satisfy the spanwise momentum equation exactly. In the BR2 scheme the viscous flux inside an element uses the gradient lifted from all its faces, the flux through a face the local gradient and the lifting of that face alone; where the in-plane velocity jumps across in-plane faces, the two differ in the normal stress on the spanwise faces. The converged three-dimensional solution therefore carries a small spanwise momentum that changes sign across the mid-plane of every element: the layers repeat each other, every layer is symmetric about its mid-plane and there is no net spanwise force, which are the three structural checks of the study. The table reports its size.\nAn extrusion has only faces whose two sides use the same local axes, and the flow has no spanwise component to speak of: the face-orientation tables, the two further components of the vorticity in the model and a flow that varies along the span are not exercised here. The orientation tables are covered for laminar flow on the sphere ; a turbulent flow that is three-dimensional in itself has not been run.\nPassed Adjoint gradients with the Spalart-Allmaras model 6 of 6 checks passed · 119 s · quick suite Total derivatives of the drag coefficient of the turbulent flat plate (NASA TMR conditions, M = 0.2, Re = 5e6, coarse mesh of 150 elements, p = 1, 3000 unknowns, cd = 6.284664e-03) from one discrete-adjoint solve of the coupled RANS-SA system, against central differences of re-converged solutions. Inputs: Mach number, Reynolds number, angle of the inflow, free-stream nu_tilde / nu, and a wall-normal bump of the plate surface (a shape derivative, which includes the sensitivity of the wall distance). dcd/dmach: adjoint vs central difference (rel.) 5.41e-09 expected < 1e-5 pass dcd/dre: adjoint vs central difference (rel.) 4.01e-09 expected < 1e-5 pass dcd/dalpha: adjoint vs central difference (rel.) 6.55e-11 expected < 1e-5 pass dcd/dchi_inf: adjoint vs central difference (rel., best of 3 steps) 3.18e-08 expected < 1e-5 pass dcd/dshape (wall bump, directional): adjoint vs central difference (rel.) 1.79e-08 expected < 1e-5 pass adjoint linear solve reached its tolerance (residual reduction) 9.21e-11 expected < 1e-10 pass Run notes The mesh is wall resolved, with element aspect ratios of the order of 1000 at the wall. Element-block Jacobi does not see that anisotropy: as the only preconditioner it left the adjoint solve short of its tolerance (see the comment in the study); the piecewise-constant coarse level of the p-multigrid hierarchy is what makes it converge. There is no line-implicit or incomplete-factorisation preconditioner. Adjoint and finite-difference derivatives Adjoint gradients with the Spalart-Allmaras model derivative adjoint central difference step relative difference dcd/dmach -0.0566437 -0.0566437 1.0000e-05 5.4084e-09 dcd/dre -2.4900e-10 -2.4900e-10 50 4.0069e-09 dcd/dalpha -83.2169 -83.2169 1.0000e-05 6.5461e-11 dcd/dchi_inf 7.2791e-06 7.2791e-06 0.001 3.1754e-08 dcd/dshape (wall bump, directional) 0.00757566 0.00757566 1.0000e-06 1.7934e-08 Solver statistics Adjoint gradients with the Spalart-Allmaras model quantity value Newton steps of the baseline solve 37 Krylov iterations of the baseline solve 971 Krylov iterations of the adjoint solve 149 residual reduction of the adjoint solve 9.2074e-11 multigrid levels (polynomial degrees) 1, 0\nTotal derivatives of the drag coefficient of the turbulent plate with respect to the flow parameters, the model data and a displacement of the wall, from one adjoint solve each, against central differences of flows re-converged to the round-off floor. The shape derivative includes the change of the wall distance; the finite difference recomputes the distance for the displaced wall.\nThe mesh has element aspect ratios of the order of a thousand at the wall. With element-block Jacobi as the only preconditioner the adjoint solve of this case stopped short of its tolerance after 960 Krylov iterations, and since a stalled linear solve is an error and not a number ( discipline contract ), the study would have ended there. It runs with two multigrid levels, the solution degree and the piecewise-constant degree zero; the table shows what the solve then takes. This is a statement about iteration counts on one small case, not about cost.","title":"Turbulence closure","url":"verification/turbulence.html"},{"audience":[],"chunks":[{"anchor":"","end":7944,"heading":"","level":2,"start":0},{"anchor":"what-each-group-of-rows-establishes","end":9149,"heading":"What each group of rows establishes","level":2,"start":7945},{"anchor":"what-the-linear-tolerance-buys-and-why-the-two-modes-differ","end":9942,"heading":"What the linear tolerance buys, and why the two modes differ","level":3,"start":9150},{"anchor":"openmdao","end":10392,"heading":"OpenMDAO","level":2,"start":9943}],"headings":["What each group of rows establishes","What the linear tolerance buys, and why the two modes differ","OpenMDAO"],"keywords":["MDO","discipline","apply_linear","solve_linear","dot product","OpenMDAO","check_partials","coupled adjoint"],"level":2,"max_level":3,"path":"verification/discipline-contract.md","section":"verification","section_title":"Verification","summary":"The interface a multidisciplinary framework integrates against is itself verified. Forward and reverse products are exact transposes, the linear solves invert them, the products agree with finite differences at the solution and at a point the framework names, a stalled linear solve is never silent, and the OpenMDAO implicit component passes OpenMDAO's own derivative checks.","text":"A design framework never sees the flow solver. It sees a handful of operations ( MDO integration ): evaluate the residual, apply its linearisation forwards and backwards, solve with it, and return outputs. If one of them is slightly inconsistent with the others, the framework’s coupled derivatives are wrong and no test of the flow solver alone would show it. This study checks the operations against each other. Passed Discipline contract for MDO integration 15 of 15 checks passed · 249.9 s · quick suite The interface a multidisciplinary framework integrates against is checked on a small inviscid aerofoil case (NACA 0012, M = 0.5, α = 2°, 64 elements of degree 2). (i) The partial-derivative products of residual and outputs (apply_linear) in forward and reverse mode must be transposes of each other, with respect to the state and to all inputs including the surface displacement field, both at the discipline's solution and at a point named by the caller that is not a solution. (ii) The forward products must agree with central finite differences of the residual (apply_nonlinear) and of the outputs, taken along smooth directions (the numerical flux is only piecewise smooth, so a rough direction would test the finite difference and not the derivative). (iii) solve_linear must invert apply_linear in both modes, and the two modes must be transposes of each other. (iv) The reduced (total) products tangent_product and adjoint_product must be transposes of each other. (v) A linear solve that cannot reach its tolerance must raise, or be flagged when the caller asks for that; and adjoint and direct totals, which solve different linear systems of the same linearisation, must approach each other as the tolerance of those solves is tightened. (vi) Wrapped as an OpenMDAO implicit component, the discipline must pass OpenMDAO's own check of partial derivatives, reproduce the discipline's total derivatives, and, coupled to a linear structure in a block Gauss–Seidel cycle, give a coupled total derivative that agrees with a finite difference of re-converged coupled analyses. flow solve converged yes expected true pass apply_linear forward vs reverse: largest relative dot-product defect over both points 2.51e-16 expected < 1e-11 (round-off) pass solve_linear forward vs reverse: largest relative dot-product defect 4.05e-12 expected < 1e-8 (two Krylov solves to 1e-11) pass solve_linear inverts apply_linear in both modes: largest relative residual, recomputed with apply_linear 9.55e-12 expected < 1e-9 (solves to 1e-11; preconditioned residual) pass partials (residual, cl, cd, cm, f_surface) with respect to state and inputs vs central differences: largest relative difference 1.15e-08 expected < 1e-6 (the bound of the linearization study for JVP vs central differences; smooth directions) pass total products, tangent_product vs adjoint_product: relative dot-product defect 6.64e-10 expected < 1e-7 (two linear solves to 1e-10) pass a linear solve that misses its tolerance raises LinearSolveError and reports the reduction it achieved 1.65e-14 expected raised, with 0 < reduction < 1e-10 pass with on_failure='flag' the same solve returns with info.converged no expected false pass adjoint and direct totals meet as the linear tolerance tightens: largest relative difference at 1e-8, 1e-10, 1e-12 2.9e-05 / 1.3e-06 / 5.8e-09 expected decreasing, below 1e-7 at 1e-12 pass OpenMDAO is installed (extra 'mdo') yes expected true pass OpenMDAO check_partials of the implicit flow component and the functionals component (forward and reverse vs central differences): largest relative error 5.65e-09 expected < 1e-6 pass OpenMDAO totals through the implicit component vs the discipline's adjoint totals (rel., largest) 4.14e-16 expected < 1e-6 (linear solves to 1e-10) pass coupled flow–structure adjoint through the implicit component vs finite difference (rel.) 1.23e-07 expected < 5e-6 (finite-difference step 1e-4) pass coupled totals, forward vs reverse mode (rel.) 9.71e-10 expected < 1e-6 (linear block Gauss–Seidel to 1e-9) pass the coupling is active: largest deflection [chords] 0.005555 expected > 1e-5 pass Run notes OpenMDAO 3.45.2. The coupled derivative differs from the rigid one by +0.31 %. Measured in this run: largest dot-product defect of apply_linear 2.5e-16, of solve_linear 4.1e-12, of the total products 6.6e-10; largest difference between partials and central differences 1.2e-08; the solve with an unreachable tolerance stopped at a reduction of 1.6e-14. Linear operators of the discipline Discipline contract for MDO integration point check left side / value right side / reference relative difference solution (the discipline's point) apply_linear: forward vs reverse (dot product) 1812.39 1812.39 2.5091e-16 solution (the discipline's point) residual partials vs central difference (step 1e-06) 44.4916 44.4916 1.3197e-09 solution (the discipline's point) partials of cl vs central difference (step 1e-06) 0.0919221 0.0919221 2.7630e-09 solution (the discipline's point) partials of cd vs central difference (step 1e-06) 0.191244 0.191244 1.2509e-10 solution (the discipline's point) partials of cm vs central difference (step 1e-06) 0.0144446 0.0144446 8.9697e-09 solution (the discipline's point) partials of f_surface vs central difference (step 1e-06) 0.516143 0.516143 5.4903e-10 solution (the discipline's point) solve_linear fwd: |A x − b| / |b| by apply_linear 35 9.5501e-12 9.5505e-12 solution (the discipline's point) solve_linear rev: |Aᵀ y − c| / |c| by apply_linear 35 6.3456e-12 6.3456e-12 solution (the discipline's point) solve_linear: forward vs reverse (dot product) 7684.66 7684.66 1.3401e-12 named point (perturbed state and inputs) apply_linear: forward vs reverse (dot product) 1883.39 1883.39 0 named point (perturbed state and inputs) residual partials vs central difference (step 1e-06) 50.8941 50.8941 1.0737e-09 named point (perturbed state and inputs) partials of cl vs central difference (step 1e-06) 0.0943423 0.0943423 4.3592e-09 named point (perturbed state and inputs) partials of cd vs central difference (step 1e-06) 0.190731 0.190731 1.8486e-10 named point (perturbed state and inputs) partials of cm vs central difference (step 1e-06) 0.0146643 0.0146643 1.1538e-08 named point (perturbed state and inputs) partials of f_surface vs central difference (step 1e-06) 0.517176 0.517176 2.2628e-10 named point (perturbed state and inputs) solve_linear fwd: |A x − b| / |b| by apply_linear 35 6.8446e-12 6.8460e-12 named point (perturbed state and inputs) solve_linear rev: |Aᵀ y − c| / |c| by apply_linear 36 3.7119e-12 3.7120e-12 named point (perturbed state and inputs) solve_linear: forward vs reverse (dot product) 7763.84 7763.84 4.0510e-12 solution (the discipline's point) tangent_product vs adjoint_product (dot product) -4.14465 -4.14465 6.6374e-10 Adjoint and direct totals against the tolerance of their linear solves Discipline contract for MDO integration linear tolerance derivative adjoint direct relative difference 1.0000e-08 d cl / d alpha 7.51815 7.51815 5.2558e-08 1.0000e-08 d cl / d mach 0.186709 0.186704 2.8636e-05 1.0000e-10 d cl / d alpha 7.51815 7.51815 1.4219e-09 1.0000e-10 d cl / d mach 0.186709 0.186709 1.3386e-06 1.0000e-12 d cl / d alpha 7.51815 7.51815 1.5087e-11 1.0000e-12 d cl / d mach 0.186709 0.186709 5.7951e-09 OpenMDAO integration Discipline contract for MDO integration quantity value check_partials flow.solver: 4 (of, wrt) pairs, directional 3.0315e-09 check_partials flow.functionals: 12 (of, wrt) pairs, directional 5.6471e-09 totals through the implicit component (reverse) vs FlowDiscipline.totals 4.1434e-16 coupled dcl/dα, OpenMDAO reverse mode 7.54117 coupled dcl/dα, OpenMDAO forward mode 7.54117 coupled dcl/dα, central difference of re-converged coupled analyses 7.54117 relative difference, reverse mode vs finite difference 1.2307e-07 dcl/dα of the rigid aerofoil 7.51815 largest deflection [chords] 0.00555541\nForward versus reverse. For random directions the product of the forward derivative with a reverse seed equals the product of the reverse derivative with the forward direction, to round-off. This holds with respect to the state and to every input, including the surface displacement field and the load field, so a framework may use either mode, or mix them, and get the same coupled derivative. Two points. Every check is made at the discipline’s own converged solution and at a point the caller names that is not a solution (a perturbed state and other inputs). A framework that owns the state, as in a full-space or a block-coupled solve, linearises about its own iterate; the discipline must not assume that iterate is converged. Against finite differences. The forward products agree with central differences of the residual and of the outputs along smooth directions. The numerical flux is only piecewise smooth, so a rough direction would measure the finite difference, not the derivative. The linear solves. The solution returned by solve_linear is put back through apply_linear : the residual of that test is the tolerance asked for, in both modes, and the two modes are transposes of each other.\nThe adjoint and the direct mode solve different linear systems of the same linearisation. The error each leaves in a total derivative is the residual of its solve multiplied by the solution of the other system: the sensitivity of the state for the adjoint mode, the adjoint times the input derivative of the residual for the direct mode. These factors differ by orders of magnitude between inputs, so at one and the same tolerance the modes reach different accuracies; the table shows them approaching each other as the tolerance tightens. A framework that compares modes should do so at a tolerance tight enough for the comparison it wants, and a failure to reach a tolerance is reported, never absorbed: LinearSolveError by default, a flag in the solve record when the caller asks for that.\nThe same discipline wrapped as an OpenMDAO implicit component ( adapter ) passes OpenMDAO’s check_partials in forward and reverse mode, reproduces the discipline’s own total derivatives through OpenMDAO’s linear solver, and, coupled to a linear structural model in a block Gauss–Seidel cycle, returns a coupled total derivative that agrees with a finite difference of re-converged coupled analyses. The record names the OpenMDAO version it ran with.","title":"Discipline contract","url":"verification/discipline-contract.html"},{"audience":[],"chunks":[{"anchor":"","end":1446,"heading":"","level":2,"start":0},{"anchor":"why-p-1-stands-apart","end":2029,"heading":"Why p = 1 stands apart","level":2,"start":1447},{"anchor":"effectivity","end":2877,"heading":"Effectivity","level":2,"start":2030},{"anchor":"how-to-read-the-table","end":3252,"heading":"How to read the table","level":2,"start":2878},{"anchor":"raw-and-corrected-outputs","end":4057,"heading":"Raw and corrected outputs","level":3,"start":3253},{"anchor":"what-limits-the-estimator","end":4495,"heading":"What limits the estimator","level":4,"start":4058}],"headings":["Why p = 1 stands apart","Effectivity","How to read the table","Raw and corrected outputs","What limits the estimator"],"keywords":["error estimate","effectivity","adjoint-weighted residual","superconvergence"],"level":2,"max_level":4,"path":"verification/output-error.md","section":"verification","section_title":"Verification","summary":"The adjoint-weighted-residual estimate predicts the change of an output between polynomial degree p and p + 1 from a single linear solve. Its effectivity is measured against the actually converged p + 1 solution.","text":"Passed Output error estimation 5 of 5 checks passed · 367.7 s · quick suite Inviscid subsonic flow over a smooth Gaussian bump. The exact flow is isentropic, so the entropy error measures solution accuracy directly. For the drag coefficient the adjoint-weighted residual predicts the change of the output between degree p and p + 1 from a single linear solve; effectivity compares that prediction with the actually converged p + 1 solution. p = 1: effectivity on finest mesh 0.933 expected 0.7 … 1.3 pass p = 1: error remaining after correction (finest mesh) 0.04869 expected < 0.25 pass p = 2: effectivity on finest mesh 0.9667 expected 0.7 … 1.3 pass p = 2: error remaining after correction (finest mesh) 0.02162 expected < 0.25 pass p = 2: entropy-error order 3.803 expected >= 2.5 (design rate p + 1 = 3, tolerance 0.5) pass Run notes With p = 1 the wall is piecewise linear, and the entropy-error order is reported, not required: it was 0.09 on these meshes, against a design rate of 2. Spurious entropy generated at the kinks of a polygonal wall need not decay at the design rate, the classical argument for curved elements. The effectivity checks do apply at p = 1: the p = 2 space that the estimate targets has a curved wall. The test case is inviscid subsonic flow over a smooth Gaussian bump in a channel. The exact flow is isentropic, so the entropy error is a direct measure of solution accuracy: Entropy error Output error estimation\nElements are isoparametric: the geometry is interpolated at the solution degree. At p = 1 the wall is therefore a polygon, and each kink generates spurious entropy that need not decay at the design rate — the classical observation that motivated curved elements (Bassi and Rebay, 1997). The entropy-order check applies to p \\ge 2 ; at p = 1 the order is reported, not required, and the run notes under the status state the order that was measured. The effectivity checks do apply at p = 1 : the p = 2 space that the estimate targets has a curved wall. On curved walls, use p \\ge 2 .\nAdjoint-weighted-residual estimate versus the p + 1 solution Output error estimation mesh p cd (p) estimate δ corrected cd (p + 1, converged) effectivity remaining / original error 12x4 1 0.0177275 -0.00857008 0.00634203 0.00569478 0.929779 0.0537907 24x8 1 0.012233 -0.00806656 9.2333e-04 3.4443e-04 0.93304 0.0486938 12x4 2 0.00569478 -0.0026344 5.0612e-04 1.4077e-04 0.878207 0.065781 24x8 2 3.4443e-04 -2.0704e-04 2.1391e-05 1.4254e-05 0.966676 0.0216164 Drag error: raw and adjoint-corrected Output error estimation Reference: finest mesh at degree 4, a solution used by no plotted point and no estimate. It differs from the degree-3 output on the same mesh by 1.4e-05; the reference is itself a computed value, and differences of that size or below are not resolved. Corrected outputs are the injected p + 1 output plus the adjoint estimate.\nFor each mesh and degree p : the output computed at degree p ; the adjoint estimate \\delta of the change to degree p+1 ; the corrected output; and the output of an actual, fully converged p+1 computation. Effectivity is the ratio of the predicted to the actual change — one is perfect. The last column is the error that remains after correction relative to the error before.\nThe chart plots the distance of the raw and of the corrected drag from a reference. The correction is designed to remove the leading error term; how far it does is measured by the last column of the effectivity table and its check (error remaining after correction below a quarter of the original on the finest mesh). The reference is the finest mesh at a degree two above the highest plotted one, a solution used by no plotted point and no estimate. It is itself a computed value: the chart note states its distance from the solution one degree lower, and differences of that size or below are not resolved. If that reference solve does not converge, the chart falls back to the solution one degree lower and omits the one corrected point that would be measured against its own target; the note says so.\nThe estimate is first order in the difference between the injected and the converged fine-space solutions, so its relative accuracy improves as the mesh is refined (asymptotic exactness) and degrades on meshes too coarse for the linearisation to hold. It measures the error relative to the p+1 space, not to the exact solution; where the solution is singular (a sharp trailing edge) enrichment in p alone captures less of the true error.","title":"Output error estimation","url":"verification/output-error.html"},{"audience":[],"chunks":[{"anchor":"","end":4557,"heading":"","level":2,"start":0},{"anchor":"what-the-checks-establish","end":5251,"heading":"What the checks establish","level":2,"start":4558},{"anchor":"the-estimate-against-an-independent-solution","end":5752,"heading":"The estimate against an independent solution","level":2,"start":5252},{"anchor":"what-this-study-does-not-prove","end":6525,"heading":"What this study does not prove","level":3,"start":5753},{"anchor":"reproduce-it","end":6871,"heading":"Reproduce it","level":3,"start":6526}],"headings":["What the checks establish","The estimate against an independent solution","What this study does not prove","Reproduce it"],"keywords":["order selection","p-enrichment","error estimate","tolerance","adaptive order"],"level":2,"max_level":3,"path":"verification/order-selection.md","section":"verification","section_title":"Verification","summary":"Output-driven order selection raises the polynomial degree until the estimated error of an output meets a tolerance. The study checks the selection rule itself, compares the estimate with solutions two degrees higher, and cross-checks every continued level against an independent solve.","text":"Passed Output-driven order selection 13 of 13 checks passed · 364.4 s · quick suite The polynomial degree of the inviscid bump-channel case (12 x 4 elements) is raised from p = 1, each level continued from the one below, until the adjoint-based estimate of the error of the drag coefficient meets a tolerance. The checks are those of the contract: when the result says the tolerance is met, the estimated error of the selected degree is within it and that of every lower degree is not; a tighter tolerance never selects a lower degree; a tolerance that cannot be met within the allowed degrees is reported as not met. The estimate itself is compared with the difference to an independent solution two degrees higher, and every level with an independent solve at its degree. independent solves from the free stream at p = 1 to 5: converged 5 of 5 expected all pass tolerance 0.02: met, and |estimated error| at the selected degree p = 1 0.01139 expected <= 0.02 pass tolerance 0.008: met, and |estimated error| at the selected degree p = 2 0.005189 expected <= 0.008 pass tolerance 0.008: smallest |estimated error| of the degrees below the selected one 0.01139 expected > 0.008 (the lowest sufficient degree is selected) pass tolerance 0.001: met, and |estimated error| at the selected degree p = 3 7.77e-05 expected <= 0.001 pass tolerance 0.001: smallest |estimated error| of the degrees below the selected one 0.005189 expected > 0.001 (the lowest sufficient degree is selected) pass selected degrees for tolerances 0.02, 0.008, 0.001 1, 2, 3 expected non-decreasing as the tolerance is tightened pass tolerance 1e-06 with at most degree 2: reported as not met, |estimated error| at degree 2 0.005189 expected > 1e-06, met = false, degree = 2 pass cd of a degree reached by continuation versus an independent solve from the free stream (largest difference) 8.42e-15 expected < 1e-10 (both converged to a relative residual of 1e-12) pass Newton steps of a level continued from the degree below, relative to a solve from the free stream (largest) 0.6 expected < 1 pass p = 1: |cd − cd at p + 2| / |estimated error| 1.545 expected 0.4 … 2.5 (regression bound, not a guarantee of the estimator; the ratios measured in this run are in the notes) pass p = 2: |cd − cd at p + 2| / |estimated error| 1.086 expected 0.4 … 2.5 (regression bound, not a guarantee of the estimator; the ratios measured in this run are in the notes) pass p = 3: |cd − cd at p + 2| / |estimated error| 1.59 expected 0.4 … 2.5 (regression bound, not a guarantee of the estimator; the ratios measured in this run are in the notes) pass Run notes Measured in this run: tolerances 0.02, 0.008, 0.001 selected degrees 1, 2, 3. The difference to the solution two degrees higher was 1.09 to 1.59 times the estimated error (degrees 1 to 3); the estimate predicts the output one degree higher, so a ratio of one is not expected exactly. Levels continued from the degree below needed at most 0.60 of the Newton steps of a solve from the free stream. The exact drag of this inviscid subsonic flow is zero. The test case is the inviscid bump channel on 12 × 4 elements, the case of the output-error study . The exact drag of this subsonic inviscid flow is zero, so the drag coefficient is pure discretization error. The degree is raised from p = 1 for a series of tolerances on the drag coefficient, each level continued from the one below. Estimated and actual error of the drag coefficient by degree Output-driven order selection Horizontal lines are the tolerances; the selected degree is the first whose estimated error lies below the line. Levels of each selection Output-driven order selection tolerance p DOF cd |estimated error| |cd − cd at p + 2| actual / estimated Newton steps outcome 0.02 1 768 0.0177275 0.0113855 0.0175867 1.54466 9 selected 0.008 1 768 0.0177275 0.0113855 0.0175867 1.54466 9 error above the tolerance 0.008 2 1728 0.00569478 0.00518866 0.00563334 1.0857 6 selected 0.001 1 768 0.0177275 0.0113855 0.0175867 1.54466 9 error above the tolerance 0.001 2 1728 0.00569478 0.00518866 0.00563334 1.0857 6 error above the tolerance 0.001 3 3072 1.4077e-04 7.7704e-05 1.2357e-04 1.59032 6 selected 1.0000e-06 1 768 0.0177275 0.0113855 0.0175867 1.54466 9 error above the tolerance 1.0000e-06 2 1728 0.00569478 0.00518866 0.00563334 1.0857 6 last allowed degree, tolerance not met Independent solves from the free stream Output-driven order selection p DOF cd Newton steps 1 768 0.0177275 9 2 1728 0.00569478 10 3 3072 1.4077e-04 10 4 4800 6.1441e-05 11 5 6912 1.7195e-05 11\nThe rule. When the result says the tolerance is met, the estimated error at the selected degree is within the tolerance and that of every lower degree is not: the lowest sufficient degree is selected. Monotonicity. A tighter tolerance never selects a lower degree. Honest failure. A tolerance that cannot be met within the allowed degrees is reported as not met, with the highest allowed degree and its estimate. Continuation changes cost, not the answer. The drag of every degree reached by continuation must equal that of an independent solve from the free stream at the same degree to solver tolerance, and a continued level must need fewer Newton steps than the solve from the free stream.\nFor each degree the table compares the estimated error with the difference to an independent solution two degrees higher. The estimate predicts the output one degree higher, and the comparison is with two degrees higher, so a ratio of exactly one is not expected. The check accepts ratios between 0.4 and 2.5. That interval is a regression bound around the ratios measured when the study was introduced; it is not a guarantee of the estimator. The ratios of the run are in the notes under the status.\nOne case, one output. A smooth subsonic flow on a mesh that resolves the geometry is where p-enrichment works best. Nothing here covers a singular or shocked flow, where the estimate relative to the next degree captures less of the true error. Estimated, not true, error. The selection rule is checked against the estimate. That the true error is within the tolerance is not asserted; the comparison with degree p + 2 shows how far the two were apart in this run. The retry path. A degree that fails from the interpolated solution is tried again with the full continuation and from the free stream. The study has no check on that path; it is covered by unit tests in tests/test_adapt.py . No statement about cost in general. The Newton-step ratio is measured on this case.\ngyre verify --only order_selection gyre run bump_euler --set discretization.order = 1 --target-error cd = 1e-3 --max-order 4 The quick suite uses three tolerances; --suite full adds a fourth, tighter one and with it one more degree. Usage of order selection is described in Gradients and error estimates , its limits in Output error estimation .","title":"Order selection","url":"verification/order-selection.html"},{"audience":[],"chunks":[{"anchor":"","end":7436,"heading":"","level":2,"start":0},{"anchor":"reading-gmsh-files","end":9086,"heading":"Reading Gmsh files","level":2,"start":7437},{"anchor":"what-the-fixtures-are-and-what-they-prove","end":10240,"heading":"What the fixtures are and what they prove","level":2,"start":9087},{"anchor":"operator-identities-on-unstructured-meshes","end":11776,"heading":"Operator identities on unstructured meshes","level":2,"start":10241},{"anchor":"four-tests-and-what-each-can-see","end":13131,"heading":"Four tests, and what each can see","level":2,"start":11777},{"anchor":"face-orientations-that-gmsh-does-not-produce","end":13535,"heading":"Face orientations that Gmsh does not produce","level":3,"start":13132},{"anchor":"from-a-file-to-forces-and-derivatives","end":14439,"heading":"From a file to forces and derivatives","level":2,"start":13536},{"anchor":"the-same-mesh-generated-and-imported","end":14907,"heading":"The same mesh, generated and imported","level":2,"start":14440},{"anchor":"an-unstructured-mesh-from-gmsh","end":15266,"heading":"An unstructured mesh from Gmsh","level":2,"start":14908},{"anchor":"what-this-study-does-not-cover","end":15948,"heading":"What this study does not cover","level":3,"start":15267},{"anchor":"reproduce-it","end":16169,"heading":"Reproduce it","level":3,"start":15949}],"headings":["Reading Gmsh files","What the fixtures are and what they prove","Operator identities on unstructured meshes","Four tests, and what each can see","Face orientations that Gmsh does not produce","From a file to forces and derivatives","The same mesh, generated and imported","An unstructured mesh from Gmsh","What this study does not cover","Reproduce it"],"keywords":["Gmsh","mesh import","unstructured","face orientation","node ordering","NPZ"],"level":2,"max_level":3,"path":"verification/imported-meshes.md","section":"verification","section_title":"Verification","summary":"A mesh read from a Gmsh or NPZ file must behave exactly like a generated one. The study reads genuine Gmsh files, tests the node-ordering tables, the operator identities on unstructured curved meshes in 2-D and 3-D, and solves a flow with an adjoint derivative on an imported mesh.","text":"Passed Imported meshes 29 of 29 checks passed · 184.5 s · quick suite A mesh read from a file must behave exactly like a generated one, including unstructured topology, where neighbouring elements use different local axes. (i) Every Gmsh fixture of the test suite is read; the files are genuine Gmsh output (tools/make_mesh_fixtures.py), and on the straight-sided ones every high-order node must be the multilinear image of the element's vertices, which tests the node-ordering tables for quadrilaterals of degree 1 to 5 and hexahedra of degree 1 to 4. (ii) On curved unstructured meshes from Gmsh in 2-D and 3-D, and on a 3-D mesh whose elements were randomly re-oriented and passed through a file, a uniform flow must remain steady, the residuals of a perturbed state must sum to zero, and the inviscid residual of a continuous state must not depend on the Riemann solver away from the boundary (the jump across every face is zero only if its nodes are matched with the right ones of the neighbour; the viscous terms cancel in this comparison, so it is made for the Euler equations only). The coupling of the viscous terms across faces (gradient traces of the neighbour, lifted jumps) is tested separately, for both sets of equations: a rough state with varying velocity and temperature and a jump on every face must give the same residual, node by node, when every element of the mesh is stored in other, randomly chosen local axes. (iii) The coarse annulus of the cylinder case, written to a Gmsh file after randomly re-orienting its elements and read back through a case specification, must give the force coefficients of the generated mesh to solver tolerance. (iv) Laminar flow past a cylinder in a channel (Re = 40, M = 0.2, slip walls) is solved on the unstructured example mesh at degree 2, and the adjoint derivative of the drag coefficient with respect to the Mach number is compared with a central finite difference of re-converged flows. usable Gmsh fixtures read 15 of 15 expected all pass unusable Gmsh fixtures (triangles, binary, untagged) refused with a message 3 of 3 expected all pass straight-sided fixtures cover quadrilaterals of degree 1–5 and hexahedra of degree 1–4 9 expected = 9 (dimension, degree) pairs pass node ordering: largest distance of a node of a straight-sided mesh from the multilinear map of its vertices 4.44e-15 expected < 1e-13 (round-off; coordinates of order one) pass straight-sided meshes: relative error of the area / volume 3.06e-16 expected < 1e-12 (the quadrature is exact for multilinear elements) pass every straight-sided fixture is unstructured: fewest face pairs with a non-identity orientation 44 expected > 0 pass curved quadrilateral meshes (circular hole): area error at geometry degree pg + 1 relative to pg, largest 0.1473 expected < 1 (the boundary interpolant converges with the degree) pass MSH 2.2 and MSH 4.1 files of one periodic mesh: largest node difference 0 expected < 1e-12 pass periodic node pairs of the file became topology (slave nodes) 13 expected > 0, and no boundary group on the periodic sides pass hole_quad_p3.msh (Gmsh, curved, 2-D), inviscid: perturbed state is not trivial (max |Rw|) 0.4158 expected > 1e-3 pass hole_quad_p3.msh (Gmsh, curved, 2-D), viscous: perturbed state is not trivial (max |Rw|) 0.4359 expected > 1e-3 pass hole_quad_p3.msh (Gmsh, curved, 2-D): face pairs with a non-identity orientation 48 expected > 0 pass hole_hex_p2.msh (Gmsh, curved, 3-D), inviscid: perturbed state is not trivial (max |Rw|) 0.6203 expected > 1e-3 pass hole_hex_p2.msh (Gmsh, curved, 3-D), viscous: perturbed state is not trivial (max |Rw|) 0.3062 expected > 1e-3 pass hole_hex_p2.msh (Gmsh, curved, 3-D): face pairs with a non-identity orientation 72 expected > 0 pass periodic warped 3x3x3 box, re-oriented, written and read back, inviscid: perturbed state is not trivial (max |Rw|) 0.1151 expected > 1e-3 pass periodic warped 3x3x3 box, re-oriented, written and read back, viscous: perturbed state is not trivial (max |Rw|) 0.1204 expected > 1e-3 pass periodic warped 3x3x3 box, re-oriented, written and read back: face pairs with a non-identity orientation 74 expected > 0 pass re-oriented 3-D box read from a file: relative face orientations present 8 expected = 8 (all) pass free stream on imported meshes: largest max |Rw| 4.44e-15 expected < 1e-12 pass conservation on imported meshes: largest |sum of Rw| of a perturbed state 1.38e-14 expected < 1e-12 (residuals of order 0.1 to 1) pass neighbour matching on imported meshes (inviscid): largest |Rw(Roe) − Rw(LLF)| off the boundary for a continuous state 9.99e-16 expected < 1e-12 (zero jump across every correctly matched face; residuals of order 0.1 to 1) pass neighbour coupling on imported meshes, Euler and Navier–Stokes: largest difference of the residual of a rough state between two descriptions of the mesh (elements re-oriented) 2.48e-14 expected < 1e-12 (round-off; residuals of order 0.1 to 1) pass the rough state exercises the viscous terms: smallest max |Rw(Navier–Stokes) − Rw(Euler)| 0.02271 expected > 1e-3 (velocity and temperature vary; far above the bound of the coupling check) pass generated and imported annulus: both solves converged yes expected true pass imported annulus: face pairs with a non-identity orientation 42 expected > 0 (elements were re-oriented before writing) pass force and moment coefficients, |imported − generated| (largest) 2.13e-14 expected < 1e-10 (both solves reduce the residual by 1e-12) pass cylinder in a channel (examples/meshes/cylinder_channel_p3.msh), Re = 40, p = 2: converged yes expected true pass cylinder in a channel: dcd/dmach, adjoint vs central finite difference (rel.) 1.18e-07 expected < 1e-6 pass Run notes Measured in this run: the largest free-stream residual on the imported meshes was 4.4e-15, the largest conservation defect 1.4e-14 and the largest dependence of the inviscid residual of a continuous state on the Riemann solver 1.0e-15. The residual of a rough state changed by at most 2.5e-14 when the elements were re-oriented, with and without viscous terms; the viscous terms contributed between 2.3e-02 and 1.4e-01 to that residual. Conservation alone would not notice a wrong index map between two straight faces (their normals are constant); the Riemann-solver comparison (trace matching, an inviscid test: the viscous terms cancel in it), the comparison of two descriptions (which includes the gradient traces and lifted jumps of the viscous discretization) and the solves of parts (iii) and (iv) do. The annulus written to a file with re-oriented elements (42 of 84 face pairs with a non-identity orientation) gave force coefficients within 2.1e-14 of those on the generated mesh. On the example mesh the adjoint derivative of the drag coefficient with respect to the Mach number differs from the central difference by a relative 1.2e-07. The Gmsh fixtures contain only two of the eight relative face orientations of hexahedra (see the table); all eight occur in the re-oriented box, which is written by Gyre's own writer, not by Gmsh. A generated mesh is a structured block: neighbouring elements share their local axes. A mesh from a file is unstructured, so two elements can meet with any relative orientation of their common face, and the file’s node ordering has to be mapped onto Gyre’s tensor-product lattice. The study tests those two things, then the whole path from a file to forces and derivatives.\nGmsh fixtures (tests/data/meshes) Imported meshes file dim geometry degree elements nodes face pairs with non-identity orientation min scaled Jacobian distance from the multilinear map relative area / volume error format / outcome hole_hex_p2.msh 3 2 78 896 72 0.138625 — 4.3003e-05 gmsh-4.1 hole_quad_p2.msh 2 2 136 600 48 0.361963 — 2.7250e-06 gmsh-4.1 hole_quad_p3.msh 2 3 136 1308 48 0.361963 — 4.0127e-07 gmsh-2.2 hole_quad_p4.msh 2 4 136 2288 48 0.361963 — 1.0421e-09 gmsh-4.1 periodic_channel_p3_v2.msh 2 3 24 247 0 0.886508 — — gmsh-2.2 periodic_channel_p3_v4.msh 2 3 24 247 0 0.886508 — — gmsh-4.1 reject_binary.msh — — — — — — — — refused: binary MSH files are not supported: save as ASCII (Mesh.Binary = 0, or `gmsh -format msh41 reject_triangles.msh — — — — — — — — refused: the mesh contains triangles. Gyre needs an all-quadrilateral (2-D) or all-hexahedral (3-D) reject_untagged.msh — — — — — — — — refused: 32 boundary faces belong to no physical group (the first one is near (3, -1.125)). Give ev straight_hex_p1.msh 3 1 60 126 44 0.310302 0 1.5292e-16 gmsh-2.2 straight_hex_p2.msh 3 2 60 720 44 0.310302 1.3323e-15 0 gmsh-4.1 straight_hex_p3.msh 3 3 60 2142 44 0.310302 2.2204e-15 0 gmsh-2.2 straight_hex_p4.msh 3 4 60 4752 44 0.310302 4.4409e-15 3.0585e-16 gmsh-4.1 straight_quad_p1.msh 2 1 120 144 44 0.473634 0 1.2234e-16 gmsh-4.1 straight_quad_p2.msh 2 2 120 528 44 0.473634 1.3323e-15 0 gmsh-2.2 straight_quad_p3.msh 2 3 120 1152 44 0.473634 1.3323e-15 2.4468e-16 gmsh-4.1 straight_quad_p4.msh 2 4 120 2016 44 0.473634 2.4425e-15 2.4468e-16 gmsh-2.2 straight_quad_p5.msh 2 5 120 3120 44 0.473634 3.1086e-15 2.4468e-16 gmsh-4.1\nThe files under tests/data/meshes are genuine Gmsh output, produced by tools/make_mesh_fixtures.py . The reader is therefore tested against Gmsh’s node ordering, not against Gyre’s own writer. Node ordering. On the straight-sided fixtures every high-order node must be the multilinear image of the element’s vertices, to round-off. A wrong entry in an ordering table moves a node by a fraction of an element. The fixtures cover quadrilaterals of degree 1 to 5 and hexahedra of degree 1 to 4, which is every element type the reader accepts. Area and volume. The quadrature of a multilinear element is exact, so the measure of each straight-sided mesh must match the geometry to round-off. Curved boundaries. On the meshes with a circular hole, the error of the area must fall when the geometry degree is raised. Format versions and periodicity. The MSH 2.2 and MSH 4.1 files of one periodic mesh must give the same nodes, and the periodic node pairs of the file must become periodic topology with no boundary group on the periodic sides. Refusal. Files with triangles, a binary file and a file with untagged boundary faces must be refused with a message.\nFree stream, conservation and neighbour coupling on imported meshes Imported meshes mesh p equations elements interior face pairs non-identity orientation relative orientations present free stream max |Rw| perturbed nodes perturbed elements max |Rw| of the perturbed state |sum of Rw| (conservation) max |Rw| of the continuous state (inviscid) max |Rw(Roe) − Rw(LLF)| off the boundary (inviscid) non-identity orientation after re-orienting max |Rw| of the rough state max |Rw(Navier–Stokes) − Rw(Euler)| of the rough state max |Rw − Rw re-oriented| of the rough state hole_quad_p3.msh (Gmsh, curved, 2-D) 3 Euler 136 244 48 2 of 2 4.4409e-15 1956 of 2176 136 0.415774 6.0785e-15 0.374242 9.9920e-16 131 0.825899 — 2.4758e-14 hole_quad_p3.msh (Gmsh, curved, 2-D) 3 Navier–Stokes 136 244 48 2 of 2 4.4409e-15 1344 of 2176 84 0.435898 1.3767e-14 — — 131 0.857855 0.108184 2.4813e-14 hole_hex_p2.msh (Gmsh, curved, 3-D) 2 Euler 78 172 72 2 of 8 1.0527e-15 1162 of 2106 78 0.620272 3.9101e-15 0.732109 5.2736e-16 156 0.846379 — 7.3830e-15 hole_hex_p2.msh (Gmsh, curved, 3-D) 2 Navier–Stokes 78 172 72 2 of 8 1.0527e-15 162 of 2106 6 0.306193 5.2173e-15 — — 156 0.853996 0.139857 7.3830e-15 periodic warped 3x3x3 box, re-oriented, written and read back 2 Euler 27 81 74 8 of 8 5.2633e-16 729 of 729 27 0.115116 5.4123e-16 0.113226 2.6368e-16 67 0.181751 — 4.4686e-15 periodic warped 3x3x3 box, re-oriented, written and read back 2 Navier–Stokes 27 81 74 8 of 8 5.2633e-16 729 of 729 27 0.120425 1.8596e-15 — — 67 0.18704 0.0227105 4.4756e-15\nThe meshes are a curved 2-D Gmsh mesh, a curved 3-D Gmsh mesh, and a periodic warped 3-D box whose elements were randomly re-oriented, written to a file and read back. Free stream. A uniform flow must leave the residual at round-off. Conservation. The residuals of a perturbed state must sum to zero. This test alone would not notice a wrong index map between two straight faces, whose normals are constant. Neighbour matching. For a continuous state the inviscid residual must not depend on the Riemann solver away from the boundary: the jump across a face is zero only if its nodes are matched with the right nodes of the neighbour. The viscous terms cancel in this comparison, so it is an Euler test. Neighbour coupling, including the viscous terms. A rough state with varying velocity and temperature and a jump on every face must give the same residual, node by node, when every element of the mesh is stored in other, randomly chosen local axes. This covers the gradient traces of the neighbour and the lifted jumps of the viscous discretization, for the Euler and the Navier–Stokes equations. A companion check requires that the viscous terms contribute to that residual, so the comparison is not vacuous. Each configuration also has a check that the perturbed state is not trivial and that the mesh has face pairs with a non-identity orientation.\nTwo hexahedra can meet in eight relative face orientations. The table lists which of them occur in each mesh; the run notes state how many the Gmsh fixtures contain. All eight are required of the re-oriented box, and that mesh is written by Gyre’s own writer, not by Gmsh. The complete set of orientations is therefore tested on the operator, through a file, but not on files from an external generator.\nCylinder Re = 40 on the generated annulus (12 x 4, p = 2) and on the same mesh read from a file Imported meshes quantity generated imported difference cd 1.69824 1.69824 2.1316e-14 cl -2.0933e-14 -2.1852e-14 9.1940e-16 cm -2.8159e-15 -6.9159e-15 4.1000e-15 Newton steps 13 13 — final / reference residual 4.0911e-14 3.5832e-14 — Residual histories on generated and imported meshes Imported meshes The histories of the generated and of the imported annulus coincide. Cylinder in a channel on examples/meshes/cylinder_channel_p3.msh Imported meshes quantity value elements 284 geometry degree 3 degrees of freedom 10224 face pairs with a non-identity orientation 104 of 528 Newton steps 14 final / reference residual 7.7014e-13 cd 3.21039 cl 2.2180e-04 cm (about the cylinder axis) -0.00200862 dcd/dmach, adjoint 0.331089 dcd/dmach, central difference (step 0.0001) 0.331089 relative difference 1.1825e-07\nThe coarse annulus of the cylinder case is written to a Gmsh file after its elements were randomly re-oriented, and read back through a case specification with mesh.generator: file . Both solves must converge, and the force and moment coefficients must agree with those on the generated mesh to solver tolerance. This exercises the case path for mesh files (boundary conditions per group, reference quantities) and the solver on a mesh with non-identity orientations.\nLaminar flow past a cylinder in a channel ( Re = 40 , M = 0.2 , slip walls) is solved at degree 2 on examples/meshes/cylinder_channel_p3.msh , an unstructured mesh of cubic quadrilaterals. The solve must converge, and the adjoint derivative of the drag coefficient with respect to the Mach number must match a central finite difference of re-converged flows.\nNo flow solve on an imported 3-D mesh. The 3-D evidence is the operator identities above; the 3-D solver path is tested on a generated mesh in Three-dimensional consistency . No comparison with a reference solution on the channel mesh. That case shows that the solver converges and that the adjoint is consistent with the discrete problem; it is not an accuracy study. Mesh quality. The reader refuses elements with a non-positive Jacobian determinant and reports the smallest scaled Jacobian. It does not judge whether a valid mesh resolves the flow. The NPZ format and malformed files are covered by the unit tests in tests/test_meshio.py ( Testing strategy ), not by this study.\ngyre verify --only imported_mesh gyre mesh info examples/meshes/cylinder_channel_p3.msh The second command prints the summary of the example mesh: size, geometry degree, boundary groups, quality numbers and content hash.","title":"Imported meshes","url":"verification/imported-meshes.html"},{"audience":[],"chunks":[{"anchor":"","end":1637,"heading":"","level":2,"start":0},{"anchor":"what-is-compared","end":2101,"heading":"What is compared","level":2,"start":1638},{"anchor":"what-else-the-study-asserts","end":2947,"heading":"What else the study asserts","level":3,"start":2102}],"headings":["What is compared","What else the study asserts"],"keywords":["validation","cylinder","drag","laminar"],"level":2,"max_level":3,"path":"verification/validation.md","section":"verification","section_title":"Verification","summary":"A canonical viscous flow — the steady wake of a circular cylinder at Reynolds number 40 — is compared with the literature as a plausibility check on the physics, boundary conditions and force evaluation.","text":"Passed Laminar cylinder at Re = 40 7 of 7 checks passed · 417.2 s · quick suite Steady separated flow past a circular cylinder at Reynolds number 40 (based on diameter) and Mach 0.2 on a 24 x 10 curved O-mesh extending 40 diameters. The drag coefficient is compared with the incompressible literature value of about 1.5 (e.g. 1.52 in Dennis & Chang 1970, 1.50 in Fornberg 1980); small differences are expected from compressibility and the finite domain. This is a plausibility check against physics, complementing the exact verification studies. The numerical energy flux through the adiabatic wall is zero by construction (its viscous part is masked out) and is therefore not checked; instead the conductive flux that the converged solution carries at the wall when the mask is lifted, which is zero for the exact solution, is required to decrease with the polynomial degree. p = 2: converged yes expected true pass p = 2: symmetric wake, |cl| 3.35e-14 expected < 1e-6 pass p = 3: converged yes expected true pass p = 3: symmetric wake, |cl| 1.72e-14 expected < 1e-6 pass drag coefficient (highest order) 1.537 expected 1.45 … 1.65 pass drag change between the two highest orders (rel.) 0.0015 expected < 0.02 pass wall heat conduction of the solution with the adiabatic mask lifted: p = 3 relative to p = 2 0.4056 expected < 1 (vanishes for the exact solution: must decrease under p-refinement) pass Drag by polynomial degree Laminar cylinder at Re = 40 p DOF converged cd cl wall heat conduction with the mask lifted (∫|q_n| dS) 2 8640 yes 1.53957 -3.3453e-14 0.0653139 3 15360 yes 1.53727 -1.7153e-14 0.0264902 reference — — 1.52 0 0\nAt Re_D = 40 the flow past a cylinder is steady with a closed recirculation bubble. Its drag coefficient is about 1.5 in the incompressible limit (for example 1.52 in the computations of Dennis and Chang, 1970, and 1.50 in Fornberg, 1980). Gyre computes the flow at Mach 0.2 on a 24 × 10 curved O-mesh extending 40 diameters; small differences from the incompressible values are expected from compressibility and the finite domain, so the check accepts 1.45–1.65.\nthe lift coefficient is zero to solver tolerance (the steady wake is symmetric); the conduction that the converged solution carries at the adiabatic wall decreases from the second-highest to the highest polynomial degree. The numerical energy flux through the wall is zero by construction (the energy row of the viscous boundary flux is masked) for any state, so it is not checked; the quantity checked is the conductive flux with the mask lifted, which vanishes for the exact solution. Its value per degree is in the table; the drag changes by less than 2 % between the two highest polynomial degrees. This is a plausibility check, not a validation campaign: one flow, one quantity. Its role is to catch gross errors in the physical model that exact verification cannot see — a wrong sign in the stress tensor satisfies every conservation test.","title":"Physical validation","url":"verification/validation.html"},{"audience":[],"chunks":[{"anchor":"","end":2420,"heading":"","level":1,"start":0}],"headings":[],"keywords":["engineering"],"level":1,"max_level":1,"path":"engineering/index.md","section":"engineering","section_title":"Software Engineering","summary":"How the capability is built: design decisions and their costs, the differentiable-programming patterns the solver relies on, code structure, the service architecture, testing, pipelines and provenance, performance, and process.","text":"For people who change or operate the code. The pages record decisions together with the alternatives that were rejected, and the lessons that shaped the current design. Design decisions L2 The handful of decisions that shape everything else — JAX as the kernel language, split-form DGSEM, geometry as differentiable data, split-argument residuals, one execution path, isolated worker processes — with the alternatives that were rejected. Differentiable programming patterns L2 How the solver is structured so that automatic differentiation yields exact, efficient linearisations — the handful of JAX patterns the code relies on, and the pitfalls each one avoids. Service architecture L2 The service is an asyncio API process that owns no numerical state, plus solver subprocesses of two kinds — one-shot job workers and long-lived sessions — over a file-based store that also holds the meshes clients upload. This page describes lifecycles, failure handling, caching and security. Code structure L2 A map of the package — which module owns which concept, how data is laid out in memory, and the conventions that the numerical kernels, the solver and the service share. Testing strategy L2 Tests are layered by what they can prove — algebraic invariants, exact derivative identities, end-to-end behaviour with real processes — and by cost. Verification studies sit above them as slower, reported evidence. Pipelines and provenance L2 Analyses, verification, documentation builds and deployment are stages of declarative pipelines. Stages are cached by content, run in isolation, and leave a provenance record — which is how every number in the documentation can be traced to a revision. Performance L2 Measured kernel throughput on CPU and GPU, the cost of derivatives relative to a residual evaluation, and where time goes in practice — compilation for small problems, Krylov iterations for large ones. Development process L2 How the project is organised for contributors — human or agent: the roadmap as hand-off state, the effort ledger, commit and verification discipline, and the conventions that keep four interfaces consistent. Effort ledger L1 How the development time was split between core capability, verification and logistics: the measured numbers, how they are measured, and how far they can be trusted. The split does not meet the 70 / 25 / 10 target cleanly, and a large part of the record is an estimate.","title":"Software Engineering","url":"engineering/index.html"},{"audience":[],"chunks":[{"anchor":"","end":2256,"heading":"","level":2,"start":0},{"anchor":"1-jax-as-the-kernel-language","end":3257,"heading":"1 — JAX as the kernel language","level":3,"start":2257},{"anchor":"2-split-form-dgsem","end":3980,"heading":"2 — Split-form DGSEM","level":3,"start":3258},{"anchor":"4-split-argument-residual","end":4714,"heading":"4 — Split-argument residual","level":3,"start":3981},{"anchor":"7-and-8-one-path-isolated-processes","end":5359,"heading":"7 and 8 — One path, isolated processes","level":3,"start":4715},{"anchor":"10-the-turbulence-closure-as-a-row-of-the-residual","end":6084,"heading":"10 — The turbulence closure as a row of the residual","level":3,"start":5360},{"anchor":"11-which-interface-flux-is-the-default","end":7082,"heading":"11 — Which interface flux is the default","level":3,"start":6085},{"anchor":"12-a-stalled-linear-solve-is-an-error","end":7611,"heading":"12 — A stalled linear solve is an error","level":3,"start":7083},{"anchor":"13-line-smoothing-as-an-option","end":10468,"heading":"13 — Line smoothing as an option","level":3,"start":7612},{"anchor":"14-single-precision-where-exactness-is-not-needed","end":11640,"heading":"14 — Single precision where exactness is not needed","level":3,"start":10469},{"anchor":"what-was-deliberately-left-out","end":11838,"heading":"What was deliberately left out","level":2,"start":11641}],"headings":["1 — JAX as the kernel language","2 — Split-form DGSEM","4 — Split-argument residual","7 and 8 — One path, isolated processes","10 — The turbulence closure as a row of the residual","11 — Which interface flux is the default","12 — A stalled linear solve is an error","13 — Line smoothing as an option","14 — Single precision where exactness is not needed","What was deliberately left out"],"keywords":["architecture decision records","JAX","design","trade-offs"],"level":2,"max_level":3,"path":"engineering/design-decisions.md","section":"engineering","section_title":"Software Engineering","summary":"The handful of decisions that shape everything else — JAX as the kernel language, split-form DGSEM, geometry as differentiable data, split-argument residuals, one execution path, isolated worker processes — with the alternatives that were rejected.","text":"Each decision below records the context, the choice and what it costs. They are kept current in devlog/ROADMAP.md . # Decision Main consequence 1 JAX (double precision) as the kernel language exact JVP/VJP of every operator; CPU and GPU from one source 2 Split-form DGSEM on Gauss–Lobatto nodes entropy-conservative volume terms (a proven entropy inequality with the es_roe and llf interface fluxes, not with the default roe ); tensor-product, dimension-generic kernels 3 BR2 with strong gradient and weak divergence compact stencil, symmetric (adjoint-consistent) diffusion 4 Split-argument residual R(U_own, U_nbr) exact element blocks from plain JVPs; no colouring, no assembly 5 Geometry enters as data inside the traced function shape derivatives without dedicated code 6 Weak-form residual for all linear algebra balanced norms, Galerkin restriction is P^{\\mathsf T} , adjoint is a nodal field 7 One execution path ( CaseSpec → Case → run_case ) CLI, service, pipelines and agents cannot diverge 8 Solver work only in subprocesses isolation, per-job device choice, trivial cancellation 9 Evidence generated by pipeline, rendered by the docs published numbers always carry provenance 10 The turbulence closure is one more row of the same residual Newton, adjoint and shape derivatives stay exact with the model on, wall distance included; no second code path 11 The classical Roe flux stays the default; the entropy-stable es_roe is an option the default is not provably entropy stable; switching it is a measured trade, not a free improvement 12 A linear solve that misses its tolerance is an error derivatives are never returned from a stalled solve; weak preconditioning shows up as a failure instead of a wrong number 14 The preconditioner may be single precision; everything it preconditions is double the blocks are what limits 3-D size on one device and the only part that need not be exact; the default stays double until the option has been measured more widely 13 Line smoothing is built from the mesh and from AD, and is an option wall-resolved meshes get an exact solve along the wall-normal lines without an assembled matrix; the default stays block Jacobi because the gain was measured on two small cases and only for tight linear solves\nContext. An MDO discipline needs Jacobian-vector products for Newton–Krylov, transposed products for adjoints, and derivatives with respect to geometry and flow parameters. Hand-differentiated solvers carry a permanent maintenance burden and a permanent risk that the derivative code lags the primal code. Decision. Write every numerical kernel as a pure function of JAX arrays; obtain all derivatives with jax.jvp , jax.vjp , jax.linearize and jax.grad ; vectorise with array programming and vmap . Cost. Just-in-time compilation: tens of seconds for each new combination of mesh size and polynomial degree, which dominates small runs. Mitigations: a persistent compilation cache, warm sessions, CPU execution for small problems. Static shapes rule out some adaptive algorithms (e.g. per-element polynomial degree) without restructuring. Rejected. A C++ core with source-transformation or operator-overloading AD: faster cold start, but far more code, a separate adjoint build, and no free GPU path.\nContext. High-order methods are attractive for design (accuracy per degree of freedom, smooth dependence of outputs on inputs) but classical collocation schemes are fragile when under-resolved. Decision. Flux-differencing DGSEM on Gauss–Lobatto nodes with entropy-conservative two-point fluxes in the volume and upwind dissipation at interfaces. Cost. Two-point fluxes are evaluated for node pairs rather than nodes (mitigated by the symmetric pair tables), and Gauss–Lobatto collocation is somewhat less accurate per degree of freedom than Gauss collocation. Rejected. Standard (over-integrated) DG: robust only with expensive over-integration and no discrete entropy statement. Finite volume: far simpler, but low order.\nContext. Matrix-free Krylov solvers need a preconditioner; the workhorse for DG is element block Jacobi, usually inside multigrid. Extracting blocks from an AD Jacobian normally needs graph colouring and many JVPs, or sparse assembly. Decision. Give the residual two state arguments: one used for everything element-local, one for everything gathered from neighbours. The partial derivative with respect to the first is exactly block diagonal, so block_size JVPs with identical per-element unit tangents return all blocks. Cost. Every boundary condition and every face term must respect the convention (covered by a test that compares against a dense Jacobian for each wall type), and each interior face is evaluated from both sides.\nContext. A capability exposed through four interfaces tends to grow four subtly different behaviours; and a numerical solver inside a web server can stall or crash it. Decision. All interfaces construct a CaseSpec and call the same runner. The API process never executes solver kernels: jobs run in fresh worker processes, sessions in long-lived ones, each with its own JAX runtime and device selection. MCP tools call the REST API through an in-process ASGI transport, so agents exercise the same code path as programs. Cost. Process start-up and JIT per job (hence sessions and the result cache), and data crossing process boundaries as JSON.\nContext. A RANS model brings stiff sources, a wall distance and a variable six orders smaller than the mean flow. Solvers often treat it apart: loosely coupled, frozen in the adjoint, with a wall distance that is not differentiated. Decision. The model variable is appended to the state and its equation to the residual ( formulation ). Sources are integrated at Gauss points with the BR2 gradient; the wall distance is stored as foot-point weights on mesh nodes and re-evaluated inside the differentiable geometry; solver norms weight the rows. Cost. Element blocks one variable larger, sources evaluated at a second set of points, and a residual whose switches had to be smoothed where the model has kinks. One model only.\nContext. With an entropy-conservative volume flux the scheme is provably entropy stable only if the interface flux is. The classical Roe flux is not; es_roe , added later, is, and has the same wave-by-wave dissipation. Decision. Keep roe as the default and offer es_roe . Measured on the development machine before deciding: on four subsonic presets the two fluxes needed the same Newton steps and Krylov iterations to within 4 % and gave outputs within the discretization error; the transonic preset, which converges with roe in 238 of its 250 steps, did not converge within 250 with es_roe ; the residual kernel of es_roe took 1.04 times as long as that of roe on a 2-D case at p = 3 and 1.67 times on a 3-D case at p = 2 , where faces carry more of the work (working tree before revision f36e10e , RTX 3060 Ti). Cost. The default configuration carries no entropy proof, which every page that mentions stability has to say. Revisit when the transonic case converges with the entropy-stable flux.\nContext. Total derivatives are exact up to the tolerance of a linear solve. A solve that stops short returns numbers that look like derivatives. Decision. solve_linear , the total products and totals raise LinearSolveError with the reduction they achieved; returning the result with a flag is something the caller asks for. The OpenMDAO components turn it into an analysis error. Cost. Cases that used to return a number now stop. The first was the adjoint of the turbulent flat plate with block Jacobi alone ( what it showed ).\nContext. On the wall-resolved mesh of the turbulent flat plate, block Jacobi alone left the adjoint solve short of its tolerance (decision 12 made that visible), and the piecewise-constant multigrid level only repaired it. The standard remedy for such meshes is to solve implicitly along lines of strongly coupled elements. Gyre assembles no matrix, so the usual implementation, which picks the line blocks out of a stored Jacobian, was not available. Decision. Build the lines from the geometry of the mesh (face area over centre distance, a threshold on the anisotropy, links only where both elements agree), take the two off-diagonal blocks of a line element from derivatives of the split-argument residual with respect to the neighbour state, with one batch of tangents per colour of a colouring of the line elements, and invert the block-tridiagonal systems exactly. The lines do not depend on the flow, the same code serves every multigrid level and the transposed solve, and a mesh without anisotropy gets block Jacobi back unchanged. The option is solver.smoother: line ; the default smoother and the preset flat_plate_sa are unchanged. Cost. Three more batches of block derivatives at every update of the preconditioner (three colours on aligned lines), two more blocks of storage per stored line slot ( how the slots are bounded ), and a longer first compilation. Two comparisons are recorded, the turbulent flat plate in two dimensions and a laminar sphere in three. In both, the linear solves to a tight tolerance need fewer than half the Krylov iterations ( study ), and in both the flow solve from the free stream, whose loose Newton steps spend their time in assembling the preconditioner, gains nothing: Flow solve from the free stream Line-implicit smoothing on a wall-resolved mesh preconditioner nonlinear steps Krylov iterations converged drag coefficient wall time, first solve [s] wall time, repeated solve (compiled) [s] repeated solve converged block Jacobi, levels 2-1-0 34 540 yes 0.00575365 86.6006 33.1422 yes line Jacobi, levels 2-1-0 34 267 yes 0.00575365 159.257 233.618 yes block Jacobi, one level 40 3498 yes 0.00575365 136.85 113.283 yes line Jacobi, one level 34 1914 yes 0.00575365 245.519 392.492 yes line Jacobi, levels 2-1-0, blocks every 2nd step 34 268 yes 0.00575365 189.424 147.94 yes Flow solve from the free stream Line-implicit smoothing in three dimensions preconditioner levels nonlinear steps Krylov iterations converged drag coefficient wall time, first solve [s] wall time, repeated solve (compiled) [s] repeated solve converged block Jacobi 2, 1, 0 13 193 yes 1.13546 141.43 13.8529 yes line Jacobi 2, 1, 0 13 123 yes 1.13546 109.475 19.5358 yes Making it the default for wall-resolved cases needs the same measurement on a large mesh and on a turbulent case in three dimensions, which has not been made.\nContext. In three dimensions the element blocks of the smoother, and the working memory of their assembly, decide what fits on one device. Everything else the solver keeps is a few dozen state vectors. Decision. solver.precond_precision: single rounds the blocks, column by column as they are assembled, stores them and applies them in single precision. The inverses and the factorisation of the line systems are still computed in double precision, because that is where a badly conditioned block loses digits, and the operator, the Krylov vectors and the transfers are never touched: a rounded preconditioner changes how fast flexible GMRES converges, not what it converges to. The option is part of the case, so a session, the error estimate and the adjoint use it consistently. Cost and evidence. On the three cases of the comparison the iteration counts and the answers do not change. The largest 3-D solve is run with it. The default remains double : three small cases and one large one are not every flow, shocks with artificial viscosity among them, and a default that changes the iteration path of every existing case should be changed on more evidence than that.\nNo optimizer, no design-variable abstraction, no geometry engine: those belong to the MDO framework. No bespoke workflow scheduler beyond the small pipeline engine. No database: the store is files.","title":"Design decisions","url":"engineering/design-decisions.html"},{"audience":[],"chunks":[{"anchor":"","end":128,"heading":"","level":2,"start":0},{"anchor":"in-brief","end":702,"heading":"In brief","level":1,"start":129},{"anchor":"pure-kernels-with-explicit-data","end":1006,"heading":"Pure kernels with explicit data","level":2,"start":703},{"anchor":"why-not-close-over-the-tables","end":1327,"heading":"Why not close over the tables?","level":3,"start":1007},{"anchor":"flow-parameters-and-geometry-are-pytrees","end":1866,"heading":"Flow parameters and geometry are pytrees","level":2,"start":1328},{"anchor":"linearise-inside-the-solve","end":1867,"heading":"Linearise inside the solve","level":2,"start":1867},{"anchor":"the-pattern","end":2469,"heading":"The pattern","level":3,"start":1868},{"anchor":"split-arguments-for-structure","end":2470,"heading":"Split arguments for structure","level":2,"start":2470},{"anchor":"getting-sparsity-structure-from-ad","end":2821,"heading":"Getting sparsity structure from AD","level":3,"start":2471},{"anchor":"safe-branches","end":2822,"heading":"Safe branches","level":2,"start":2822},{"anchor":"where-differentiates-both-sides","end":3437,"heading":"`where` differentiates both sides","level":3,"start":2823},{"anchor":"compilation-as-a-first-class-cost","end":3857,"heading":"Compilation as a first-class cost","level":2,"start":3438},{"anchor":"what-dominates","end":4326,"heading":"What dominates","level":4,"start":3858},{"anchor":"double-precision-and-devices","end":4607,"heading":"Double precision and devices","level":2,"start":4327}],"headings":["In brief","Pure kernels with explicit data","Why not close over the tables?","Flow parameters and geometry are pytrees","Linearise inside the solve","The pattern","Split arguments for structure","Getting sparsity structure from AD","Safe branches","`where` differentiates both sides","Compilation as a first-class cost","What dominates","Double precision and devices"],"keywords":["JAX","automatic differentiation","linearize","vjp","jit","pytrees","compilation"],"level":2,"max_level":4,"path":"engineering/differentiable-programming.md","section":"engineering","section_title":"Software Engineering","summary":"How the solver is structured so that automatic differentiation yields exact, efficient linearisations — the handful of JAX patterns the code relies on, and the pitfalls each one avoids.","text":"Gyre’s numerical kernels are ordinary array code. What makes them a differentiable solver is a small set of structural patterns.\nThe flow solver is written once, as pure array functions, and JAX differentiates that code. Nothing is differentiated by hand: Newton’s Jacobian products, the adjoint’s transposed products and the sensitivities with respect to shape and flow conditions all come from the kernel that computes the residual, so they cannot fall out of step with it. The price is compilation time — tens of seconds for a new mesh size or polynomial degree — which is why sessions and a persistent cache exist. The patterns below are what a contributor has to keep intact for this to stay true.\nEvery kernel is a pure function of arrays. Operator tables (derivative matrices, gather indices, masks) live in one pytree, Discretization.ops , and are passed to jitted functions as runtime arguments : f = jax . jit ( lambda ops , U , geom , th : _residual ( disc , ops , U , geom , th , None , True ))\nArrays captured by a jitted closure are baked into the compiled program as constants. For index tables with millions of entries that bloats compile time and memory and defeats the persistent cache (the program text changes with every mesh). Passing them as arguments keeps the compiled program a function of shapes only.\nth is a flat dict of scalars; Geometry is a named tuple of arrays computed inside the traced function from node coordinates. Because both are pytrees, jax.grad , jvp and vjp differentiate with respect to them with no extra code: def lagrangian ( d , th , ops , U , psi ): return J ( U , d , th , ops ) - jnp . sum ( psi * R ( U , d , th , ops )) dJ_dd , dJ_dth = jax . grad ( lagrangian , argnums = ( 0 , 1 ))( d , th , ops , U , psi ) One reverse sweep returns the sensitivity with respect to every surface node and every flow parameter.\n\ndef solve ( ops , U , geom , th , rhs , ... ): # one jitted function _ , lin = jax . linearize ( lambda V : Rw ( V ), U ) # primal once A = lambda v : idm * v + lin ( v ) # tangent per iteration return fgmres ( A , M , rhs , ... ) # lax.while_loop jax.linearize returns a function closing over the primal intermediates. Calling it from Python each Newton step would re-trace; jitting it would re-compile. Tracing the whole Krylov solve in one function avoids both: XLA computes the primal part once (it is loop-invariant) and the loop body is the tangent map only. The adjoint solve swaps in jax.vjp .\n\nAD produces products, not matrices. To obtain the element-diagonal blocks, the residual takes the state twice (own / neighbour roles) and the blocks are jax.lax.map over unit tangents of the own-role argument, batched to bound memory. The same idea — route a dependency through a separate argument to select part of a Jacobian — is general and cheap.\n\njnp.where(c, a, b) evaluates and differentiates both branches; a nan derivative in the unselected branch still poisons the result. The logarithmic mean therefore feeds its unselected branch safe substitute arguments (the double-where idiom in physics.ln_mean ), and a unit test asserts finite forward- and reverse-mode derivatives on its diagonal. The other switches — the entropy fix of the Roe flux, the pressure-outlet and boundary-face selections, the shock-capturing ramp — are plain where s whose branches all have finite derivatives; the total-pressure inlet clamps the static pressure before taking a root.\nMechanism Effect persistent XLA cache ( ~/.cache/gyre/jit , GYRE_JIT_CACHE ) compiled kernels reused across processes; tracing remains sessions one process keeps its compiled functions for a whole design study per-process device ( GYRE_DEVICE ) CPU for small problems (faster compile), GPU for large lazily built, keyed jit caches on Discretization , Hierarchy , FlowDiscipline each entry point compiled once per object\nFor small cases cold compilation of the residual, the preconditioner set-up and the linear solve takes tens of seconds in total — noticeably longer on the GPU than on the CPU — while the compiled residual itself runs in milliseconds ( measured kernel timings ). With a warm persistent cache only tracing remains. The practical rules that follow: keep unit tests small and on the CPU, share solved fixtures across tests, and route repeated evaluations through sessions.\nimport gyre enables 64-bit floats (implicit solvers and adjoints need them), selects the device from GYRE_DEVICE , and disables GPU memory pre-allocation so several solver processes can share one GPU. Host CPUs without AVX2 must use jax < 0.12 ; scripts/bootstrap.sh detects this.","title":"Differentiable programming patterns","url":"engineering/differentiable-programming.html"},{"audience":[],"chunks":[{"anchor":"in-brief-what-an-operator-needs-to-know","end":993,"heading":"In brief: what an operator needs to know","level":1,"start":0},{"anchor":"components","end":1677,"heading":"Components","level":2,"start":994},{"anchor":"job-lifecycle","end":1884,"heading":"Job lifecycle","level":2,"start":1678},{"anchor":"what-happens-on-submit","end":3713,"heading":"What happens on submit","level":3,"start":1885},{"anchor":"mesh-store","end":3714,"heading":"Mesh store","level":2,"start":3714},{"anchor":"files-from-clients","end":5593,"heading":"Files from clients","level":3,"start":3715},{"anchor":"progress-streaming","end":5870,"heading":"Progress streaming","level":2,"start":5594},{"anchor":"sessions","end":5871,"heading":"Sessions","level":2,"start":5871},{"anchor":"why-a-second-execution-model","end":6596,"heading":"Why a second execution model","level":2,"start":5872},{"anchor":"failure-handling","end":8328,"heading":"Failure handling","level":2,"start":6597},{"anchor":"security-and-exposure","end":8329,"heading":"Security and exposure","level":2,"start":8329},{"anchor":"assumptions","end":9738,"heading":"Assumptions","level":2,"start":8330},{"anchor":"observability","end":10120,"heading":"Observability","level":2,"start":9739},{"anchor":"the-agent-interface-is-a-client","end":10121,"heading":"The agent interface is a client","level":2,"start":10121},{"anchor":"mcp-over-the-same-api","end":10756,"heading":"MCP over the same API","level":3,"start":10122}],"headings":["In brief: what an operator needs to know","Components","Job lifecycle","What happens on submit","Mesh store","Files from clients","Progress streaming","Sessions","Why a second execution model","Failure handling","Security and exposure","Assumptions","Observability","The agent interface is a client","MCP over the same API"],"keywords":["FastAPI","jobs","sessions","mesh store","SSE","cache","failure modes","security","MCP"],"level":2,"max_level":3,"path":"engineering/service-architecture.md","section":"engineering","section_title":"Software Engineering","summary":"The service is an asyncio API process that owns no numerical state, plus solver subprocesses of two kinds — one-shot job workers and long-lived sessions — over a file-based store that also holds the meshes clients upload. This page describes lifecycles, failure handling, caching and security.","text":"One API process, many solver processes. The service process validates requests, keeps queues and serves the documentation; it never computes a flow. Every solve runs in a subprocess — a one-shot worker per job, or a long-lived session — so a crashing or diverging solve cannot take the service down. State is files. Jobs, results, the result cache, uploaded meshes and pipeline records live under the data directory ( gyre serve --data DIR ). There is no database. Finished jobs and the cache survive a restart; running jobs become interrupted and sessions are lost. Limits are environment variables ( GYRE_MAX_SESSIONS , GYRE_MAX_QUEUED_JOBS , GYRE_MAX_DOF , the mesh-store limits); see the deployment guide . The network is the trust boundary. The service binds all interfaces; without GYRE_API_TOKEN anyone who can reach the port can run jobs and upload meshes. Health and identity: GET /api/v1/health , GET /api/v1/info (revision, process id, start time), GET /api/v1/metrics (Prometheus).\nComponent Module Responsibility API application gyre.service.app routing, validation, error shaping, static docs, MCP mount, metrics, auth Job manager gyre.service.jobs queue, worker subprocesses, cancellation, result cache, event streams Worker gyre.service.worker run one case; write status, events, artefacts Session manager gyre.service.sessions spawn/command/reap warm solver processes Store gyre.service.store atomic JSON files, event logs Mesh store gyre.service.meshes uploaded mesh files: names, validation, limits Pipeline manager gyre.service.pipelines definitions, runs as subprocesses in their own process group MCP server gyre.agent.server agent tools over the REST API\nqueued ──▶ running ──▶ succeeded │ ├─────▶ failed (exception in the solver; traceback in error.txt) └──────────▶└─────▶ cancelled (DELETE /jobs/{id}) interrupted (service restarted while the job was active)\nThe request is resolved to a CaseSpec (preset + dotted overrides, or a full document) and validated; errors return a structured body with a hint . A string case is only looked up among the presets and is never opened as a file. A CaseSpec object with mesh.generator: file names a mesh of the mesh store in mesh.params.path : the name is checked against the store’s pattern before anything is opened, the stored file is read for the size estimate, and its boundary groups are compared with discretization.boundaries . An adapt request that cannot start is refused here as well. All of this file work runs in a worker thread, not on the event loop. A cache key is computed from the canonical spec JSON, the gradient, error-estimate and adapt requests, the SHA-256 of a mesh file’s contents and the code fingerprint ( gyre.io.code_fingerprint : version, revision and a hash of the package sources). If a successful job with that key exists, its artefacts are copied and the new job is returned already succeeded with cached_from set. A worker recomputes the key when it starts and records it, so a result is filed under the code that produced it. Otherwise the job directory is created ( request.json , status.json ) and the id queued. A dispatcher task starts python -m gyre.service.worker <dir> with GYRE_DEVICE chosen from the estimated problem size (GPU above 60 000 degrees of freedom, if one is present; GPU jobs are serialised). The worker appends progress to events.jsonl (one JSON object per line) and rewrites status.json atomically; on completion it writes result.json and artefacts ( solution.npz , flow.vtu , field.json ). With adapt the worker runs the order selection ( gyre.adapt.adaptive_order ) through the same run_case : one adapt event per degree, and the result of the final degree with an adaptation section.\n\nThe store is the directory <data_dir>/meshes , one file per name. PUT /meshes/{name} checks the name against ^[A-Za-z0-9][A-Za-z0-9_.-]{0,79}\\.(msh|npz)$ , refuses a body above GYRE_MESH_MAX_BYTES from the Content-Length header and again while it streams the body into a temporary file of the same directory, and then validates that file in a worker thread with gyre.meshio.inspect_mesh , the reader every solver process uses. Validations run one at a time. An .npz archive whose members would expand to more than four times the size limit is refused from its directory, before any array is read. Only an accepted file is moved to its name ( os.replace ), so an existing mesh is replaced by a valid one or not at all, and a refused upload leaves nothing behind. The store is bounded in number and total size ( GYRE_MESH_STORE_MAX_FILES , GYRE_MESH_STORE_MAX_BYTES ; 507 MeshStoreFull ). A ValueError of the reader carries a message written for the client and is returned as 422 InvalidMesh , with the temporary file name replaced by the client’s name. Any other exception of the reader is also a 422 InvalidMesh , with the message <name> could not be read as a mesh (<ExceptionType>) ; the detail goes to the log. When the application starts it sets GYRE_MESH_DIR to the store (absolute path) and GYRE_MESH_RESTRICT=1 in its own environment and restores the previous values at shutdown. Job workers, session processes and pipeline runs inherit both, and gyre.meshio.resolve_mesh_path then accepts only a bare file name inside that directory. The confinement therefore holds in the processes that read the mesh, not only in the request handler. A request for a case on a stored mesh that reaches an application without this environment is answered 503 MeshStoreInactive ; the name check (422 InvalidMeshName ) and the lookup in the store (404 UnknownMesh ) come before that test.\nGET /jobs/{id}/events?stream=true is a Server-Sent-Events stream that tails the event log until the job reaches a final state; ?since=N returns a JSON page for polling clients (and agents). Because the log is a file, a client can reconnect and resume from any sequence number.\n\nA design loop evaluates one case many times at nearby inputs. A session process builds the case once, keeps its compiled kernels, warm-starts each solve from the previous state in the Newton regime and caches the linearisation between gradient calls. A session on a mesh file reads the file once, at start-up, and reports the case digest of that moment; replacing or deleting the mesh in the store afterwards does not affect it. Commands ( evaluate , gradients , adjoint , error_estimate , surface , distributions , field , state ) travel over a pipe; one command at a time per session. Idle sessions are reaped after 30 minutes ( GYRE_SESSION_IDLE_S ), and the number of sessions is capped ( GYRE_MAX_SESSIONS , default 4).\nFailure Behaviour invalid case, override or request 4xx with {\"error\": {\"type\", \"message\", \"hint\"}} , the shape of every error of the API; nothing is queued unexpected exception in the API 500 InternalError in the same shape, with an incident id as the only detail; the exception type and text are logged (logger gyre.service ) under that id, because they can contain server paths solver does not converge job succeeded with converged: false , the reason and the outputs of the last iterate; requested gradients and error estimates are not computed and listed under skipped . A session reports it, keeps its last converged state as warm start, and refuses derivative, error-estimate, distribution and field calls (422 RuntimeError ) until an evaluation converges uploaded mesh unusable, too large, or the store is full 422 InvalidMesh , 413 MeshTooLarge , 507 MeshStoreFull ; nothing is stored mesh deleted while a job on it is being submitted 404 UnknownMesh mesh deleted while a job on it is queued the job fails when its worker builds the case exception in a worker job failed ; traceback in the error.txt artefact worker killed or crashed in native code dispatcher marks the job failed (“exited without a result”) cancel before the worker process exists (queued, waiting for the GPU, being spawned) the job stays cancelled ; no worker runs it service restart active jobs become interrupted ; finished jobs and the cache persist session process dies the call that meets the death (or the first call after it) returns 410 SessionDead ; the session is removed, so later calls return 404 NotFound session process dies while starting 500 SessionStartFailed command error in a session reported with its type; the session stays usable\n\nThe service binds to all interfaces so that tailnet machines can reach it; the tailnet is the trust boundary. Setting GYRE_API_TOKEN requires Authorization: Bearer … on /api/v1 (except /health and /info ) and on /mcp . Artefact downloads are restricted to a fixed allow-list of file names; job ids are validated before touching the file system, and case strings are never treated as paths. A CaseSpec object with mesh.generator: file names a mesh of the mesh store; the service sets GYRE_MESH_RESTRICT=1 and GYRE_MESH_DIR for itself and its subprocesses, so mesh.params.path is a bare file name inside the store and never a path. Mesh uploads are as open as the rest of the API: without a token, anyone on the tailnet can store files up to the size and number limits, and each file is parsed by the reader. The reader checks declared counts against the contents of a file before it allocates, and measures an .npz archive before it reads an array. Pipeline runs started through the service load a case from a preset name or an inline specification only ( GYRE_PIPELINE_CASE_FILES=0 ). Pipelines with shell stages run arbitrary commands as the service user and are therefore excluded from the service and MCP by default; GYRE_PIPELINES_ALLOW_SHELL=1 in the service’s environment opts in. The MCP endpoint disables Host-header pinning because tailnet clients address the service by tailnet names and addresses.\nGET /api/v1/metrics exposes Prometheus counters and gauges (jobs submitted / succeeded / failed / cancelled, cache hits, queue length, running jobs, solve seconds, sessions, evaluations, gradient calls, uptime, build info). GET /api/v1/info identifies the deployment: version, commit, process id, start time, deployment name, whether documentation is built and from which revision.\n\nThe MCP server holds no logic of its own: each tool is a described wrapper that calls the REST API — in-process through an ASGI transport when mounted at /mcp , or over the network in the standalone stdio proxy ( python -m gyre.agent --service URL ). Tool results summarise large arrays (shape, extrema, norm) unless full_arrays is requested, and API errors are passed through with their hints so an agent can correct itself. The mesh tools check the name against the store’s pattern themselves, because a name with a / would address another URL, and upload_mesh decodes its content (text or base64) into the request body of the PUT .","title":"Service architecture","url":"engineering/service-architecture.html"},{"audience":[],"chunks":[{"anchor":"in-brief","end":625,"heading":"In brief","level":1,"start":0},{"anchor":"module-map","end":3310,"heading":"Module map","level":2,"start":626},{"anchor":"data-layout","end":3311,"heading":"Data layout","level":2,"start":3311},{"anchor":"arrays","end":3964,"heading":"Arrays","level":3,"start":3312},{"anchor":"connectivity-without-coordinate-matching","end":4482,"heading":"Connectivity without coordinate matching","level":3,"start":3965},{"anchor":"conventions","end":4483,"heading":"Conventions","level":2,"start":4483},{"anchor":"residual-sign-and-scaling","end":4680,"heading":"Residual sign and scaling","level":2,"start":4484},{"anchor":"static-versus-traced","end":4946,"heading":"Static versus traced","level":3,"start":4681},{"anchor":"adding-things","end":5575,"heading":"Adding things","level":2,"start":4947}],"headings":["In brief","Module map","Data layout","Arrays","Connectivity without coordinate matching","Conventions","Residual sign and scaling","Static versus traced","Adding things"],"keywords":["modules","data layout","conventions","package"],"level":2,"max_level":3,"path":"engineering/code-structure.md","section":"engineering","section_title":"Software Engineering","summary":"A map of the package — which module owns which concept, how data is laid out in memory, and the conventions that the numerical kernels, the solver and the service share.","text":"One Python package, src/gyre , in five layers. Numerics ( basis , mesh , physics , dg , implicit , linear , functionals ) are pure JAX kernels. The discipline ( discipline , error , adapt , deform ) turns them into “solve, outputs, derivatives”. Cases and execution ( cases , runner , io , cli ) describe an analysis as data and run it. Delivery ( service , agent , client , pipeline ) exposes the same execution path over HTTP, MCP and YAML pipelines. Evidence ( verify , docsite ) produces the verification records and this site. Everything above the discipline calls runner.run_case , so the interfaces cannot drift apart.\nModule Owns gyre.basis LGL nodes and weights, derivative and interpolation matrices, SBP operators, pair tables, p-transfer operators (NumPy, set-up time) gyre.mesh Mesh (global high-order nodes, element→node map, boundary tags), structured-block builder, face connectivity and orientation gyre.meshgen generators: airfoil_omesh , annulus , bump_channel , flat_plate , sphere_shell (cubed sphere), box , spanwise extrusion gyre.meshio mesh files: Gmsh MSH reader (2.2 and 4.1, ASCII) and writer (2.2), the NPZ format, validation, inspection, lookup of the file a case names gyre.physics compressible Euler/Navier–Stokes kernels: variable sets, two-point and interface fluxes (classical and entropy stable), viscous flux, flow parameters gyre.turbulence negative Spalart–Allmaras closure: model functions, source terms, boundary values, wall distance to curved walls as differentiable foot-point tables, manufactured solutions gyre.dg Discretization , differentiable geometry , boundary conditions, the residual kernel gyre.timestep explicit low-storage Runge–Kutta (verification) gyre.linear flexible GMRES in JAX control flow gyre.implicit Hierarchy (p-multigrid, block inverses, line factors, forward/transposed solves), SteadySolver gyre.lines lines of strongly coupled elements of an anisotropic mesh and the colouring used to assemble their coupling blocks (NumPy only) gyre.functionals surface integration from residual fluxes, force/moment/heat functionals, nodal loads, distributions gyre.deform inverse-distance-weighted mesh deformation: a stored matrix for small meshes, matrix-free with a hand-written transpose above 2\\times10^7 entries gyre.discipline FlowDiscipline : the MDO contract gyre.error adjoint-weighted-residual error estimation gyre.adapt output-driven order selection: uniform p-enrichment until the error estimate of an output meets a tolerance gyre.mms manufactured solutions with AD-generated forcing gyre.cases CaseSpec (pydantic), presets, Case gyre.runner , gyre.io end-to-end execution record; checkpoints, VTK, browser payloads gyre.identity the one definition of “which code is this”: version, commit, and what makes a tree dirty; stamps results, verification records, pipeline records and the documentation gyre.service.* API application, job manager, worker, sessions, mesh store, pipeline runs, limits, host validation, response schemas, store gyre.agent.* MCP server (mounted or standalone proxy) gyre.client HTTP client and RemoteDiscipline gyre.pipeline.* pipeline engine and compute tasks gyre.verify.* verification studies and suite runner gyre.docsite.* documentation site builder gyre.integrations.* optional framework adapters (OpenMDAO)\n\nQuantity Shape Notes state U (nvar, K, n, n[, n]) variables first; n = p + 1 nodes per direction geometry x (dim, K, n, …) solution nodes metrics Ja (dim, dim, K, n, …) Ja[a, b] = component b of J\\mathbf a^a face data (…, K, F, nq) F = 2·dim faces, nq = n^(dim-1) face nodes neighbour gather plus (K, F, nq) int flat index into a (K·F·nq) face array block inverses (K, bs, bs) bs = nvar · n^dim Local face f = 2a + s is the face normal to reference direction a at \\xi_a = -1 ( s = 0 ) or +1 ( s = 1 ). Face-node arrays keep the remaining reference directions in ascending order; the neighbour’s ordering is reconciled by a permutation encoded in plus .\nNeighbours are found topologically: each face is keyed by the sorted global ids of its corner geometry nodes (after mapping periodic images to a common id). The relative orientation of two matching faces — one of two in 2-D, one of eight in 3-D — is found by comparing their geometry node ids under each index symmetry, and the result is stored as three booleans (transpose, flip, flip) . The gather index for any polynomial degree is generated from those booleans, so every p-multigrid level reuses one connectivity.\n\nresidual(…) returns \\mathbf R with \\mathrm d\\mathbf U/\\mathrm dt = -\\mathbf R ; weak=True returns \\mathbf R_w = (wJ)\\,\\mathbf R . Solvers, adjoints and error estimates use \\mathbf R_w exclusively.\nPython-level structure (dimension, degree, boundary kinds, flux names) is fixed when a Discretization is built and never appears inside traced code as data-dependent control flow. Everything numerical — state, geometry, flow parameters, operator tables — is traced.\nA boundary condition : write a function returning (UL, UR, Ub) and register it in BOUNDARY_CONDITIONS ; add it to WALL_KINDS / viscous masks if applicable; add it to the linearisation study’s configurations. A functional : return a Functional from a function of (U, geom, th) ; build surface quantities with SurfaceOps so they reuse the residual’s fluxes. A mesh generator : produce a geometry lattice and call structured_mesh ; add a preset. A pipeline stage : add a task function in gyre.pipeline.tasks and its name to COMPUTE_STAGES . An agent tool : add a REST endpoint first, then a described wrapper in gyre.agent.server .","title":"Code structure","url":"engineering/code-structure.html"},{"audience":[],"chunks":[{"anchor":"","end":4090,"heading":"","level":2,"start":0},{"anchor":"in-brief-how-to-run-the-checks","end":4747,"heading":"In brief: how to run the checks","level":1,"start":4091},{"anchor":"principles","end":6155,"heading":"Principles","level":3,"start":4748},{"anchor":"tests-versus-verification-studies","end":6427,"heading":"Tests versus verification studies","level":2,"start":6156}],"headings":["In brief: how to run the checks","Principles","Tests versus verification studies"],"keywords":["tests","pytest","invariants","end-to-end","CI"],"level":2,"max_level":3,"path":"engineering/testing-strategy.md","section":"engineering","section_title":"Software Engineering","summary":"Tests are layered by what they can prove — algebraic invariants, exact derivative identities, end-to-end behaviour with real processes — and by cost. Verification studies sit above them as slower, reported evidence.","text":"Layer File(s) What it proves Cost operators test_basis.py , test_physics.py quadrature, SBP, flux consistency/symmetry, entropy conservation condition, safe derivatives seconds mesh and geometry test_mesh.py connectivity in 2-D/3-D, neighbour traces coincide, GCL, differentiable metrics seconds mesh files test_meshio.py Gmsh node ordering on genuine Gmsh files, MSH 2.2 against 4.1, periodic node pairs, curved boundaries, round trips through .msh and .npz , re-oriented hexahedra through a file, refusal of unusable, truncated and malformed files, size checks of NPZ archives, a case on a mesh file seconds to a minute spatial operator test_operator.py free-stream preservation, conservation, entropy balance, split-argument blocks versus dense Jacobian, dot-product identity ~2 min line smoother test_lines.py lines follow the thin direction, none on an isotropic mesh, loops across a periodic direction cut once, the colouring isolates every line neighbour; the line solve against a dense solve of the block-tridiagonal part of an AD Jacobian, forward and transposed, in 2-D, 3-D and with the turbulence model; forward and transposed multigrid solves with line smoothing ~3 min single-precision blocks test_precision.py the single-precision blocks are the rounded double-precision ones and are inverted in double precision; forward and transposed multigrid solves with them, with block and with line smoothing, satisfy the double-precision system; the option reaches the hierarchy ~2 min Krylov solver test_linear.py FGMRES against direct solves, flexibility, restarts seconds discipline test_discipline.py nonlinear convergence, loads sum to forces, adjoint/direct totals versus finite differences, transposed solves, failed-solve state retention and residual reference, refusal of derivatives after a failed solve, error-estimate force normalisation, periodic mesh deformation minutes (compile-bound) order selection test_adapt.py prolongation exact for polynomials, argument checks before any solve, multigrid levels following the degree, retries of a failed degree, the degree raised until the estimate meets the tolerance minutes (compile-bound) integration test_integrations.py coupled aero-structural adjoint; OpenMDAO adapter totals; OpenMDAO adapter after a failed point minutes command line test_cli.py overrides, --target-error and its exit statuses, gyre mesh , the gyre verify summary line, generated references seconds pipelines test_pipeline.py DAG validation, reference resolution, caching, gates < 1 s service test_service.py real server on a free port: jobs, SSE, cache, cancellation, sessions, MCP workflow, error shapes, non-loopback reachability ~1–2 min turbulence closure test_turbulence.py closure functions and their smoothness, wall distance to curved walls, free stream and conservation with the model, exact linearisation on both model branches in 2-D and 3-D, row-scaled Krylov solves, a turbulent plate from the free stream, adjoint against finite differences, diagnostics and field output of a turbulent state minutes three dimensions test_threed.py cubed-sphere meshes and their face orientations, 3-D force and moment functionals, sideslip, wind axes, 3-D adjoint products minutes sessions test_sessions.py checkpoints, restore and rebuild of a session, the remote example script (which contains no optimizer) minutes documentation test_docsite.py builder: levels, shortcodes, links, search index, expanded Markdown for agents, build-time mathematics seconds documentation content test_docs_content.py the pages against the code: solver failure reasons, pipeline stage inputs, every output defined, example scripts referenced, the executed Python sample, the effort page against the ledger, no host-specific data seconds tooling test_tooling.py , test_worklog.py deployment manager, smoke test, bootstrap and lock scripts, CI definitions, the effort ledger tool seconds verification studies test_verify.py the suite runner, record format, study helpers with stubbed solves (a wrong Jacobian, a stagnated residual and NaN must fail their checks) seconds\nmake test # unit and integration tests on the CPU (not the service or slow tests) make test-service # end-to-end: a real server with worker and session processes make lint # ruff GYRE_DEVICE = cpu .venv/bin/python -m pytest tests/test_operator.py -q -k entropy # one test make verify # the verification studies (slower; see Verification) make ci # all of the above plus the documentation build and a deployment Tests answer “is it still right?” and gate every change; verification studies answer “how right is it?” and are published with their provenance. Most test time is JIT compilation, not arithmetic: the cost column below is what to expect per file.\nInvariants first. Round-off-level identities are the cheapest and sharpest tests, so they form the base of the pyramid. Share expensive fixtures. JIT compilation dominates, so a converged aerofoil and a converged cylinder are module-scoped fixtures reused by a dozen assertions each. Real processes for the service. Service tests start the actual server and let it spawn worker and session processes; nothing is mocked. A tiny case keeps this affordable. Negative tests. Invalid meshes, unknown inputs, bad overrides and forbidden artefact names are asserted to fail with the documented error. External formats are tested against the external tool. The .msh fixtures under tests/data/meshes are genuine Gmsh output, generated by tools/make_mesh_fixtures.py : the node-ordering tables are tested against Gmsh itself, not against Gyre’s own writer. Gmsh is not a dependency of Gyre; only that script needs it, and the fixtures are kept in the repository. Files Gmsh would not write (truncated, with lost section lines, with counts that do not match) are derived from the fixtures inside the tests. Failure paths without long solves. Tests of a solve that does not converge limit the solver to one step on an existing fixture, or replace the solve, instead of constructing a flow that diverges. CPU by default. Tests set GYRE_DEVICE=cpu for reproducibility; the GPU path is exercised by the verification suite.\nTests gate every commit and answer “is it still right?”. Verification studies are slower, produce charts and tables with provenance, answer “how right is it?”, and are published in the Verification section. The CI pipeline runs both and refuses to deploy if either fails.","title":"Testing strategy","url":"engineering/testing-strategy.html"},{"audience":[],"chunks":[{"anchor":"in-brief","end":726,"heading":"In brief","level":1,"start":0},{"anchor":"the-engine","end":1444,"heading":"The engine","level":2,"start":727},{"anchor":"content-addressed-caching","end":3756,"heading":"Content-addressed caching","level":3,"start":1445},{"anchor":"failure-semantics","end":4045,"heading":"Failure semantics","level":2,"start":3757},{"anchor":"provenance","end":4046,"heading":"Provenance","level":2,"start":4046},{"anchor":"what-is-recorded-where","end":5082,"heading":"What is recorded where","level":2,"start":4047},{"anchor":"what-makes-a-revision-dirty","end":5783,"heading":"What makes a revision dirty","level":3,"start":5083},{"anchor":"when-a-record-counts-as-evidence-for-the-build","end":6510,"heading":"When a record counts as evidence for the build","level":3,"start":5784},{"anchor":"continuous-integration-and-deployment","end":6707,"heading":"Continuous integration and deployment","level":2,"start":6511},{"anchor":"the-last-complete-run","end":7335,"heading":"The last complete run","level":2,"start":6708},{"anchor":"pipelines-ci-yaml","end":8154,"heading":"`pipelines/ci.yaml`","level":3,"start":7336},{"anchor":"deployment-forms","end":8794,"heading":"Deployment forms","level":2,"start":8155},{"anchor":"effort-ledger","end":9081,"heading":"Effort ledger","level":2,"start":8795}],"headings":["In brief","The engine","Content-addressed caching","Failure semantics","Provenance","What is recorded where","What makes a revision dirty","When a record counts as evidence for the build","Continuous integration and deployment","The last complete run","`pipelines/ci.yaml`","Deployment forms","Effort ledger"],"keywords":["pipeline","DAG","cache","provenance","CI","deployment"],"level":2,"max_level":3,"path":"engineering/pipelines-and-provenance.md","section":"engineering","section_title":"Software Engineering","summary":"Analyses, verification, documentation builds and deployment are stages of declarative pipelines. Stages are cached by content, run in isolation, and leave a provenance record — which is how every number in the documentation can be traced to a revision.","text":"A pipeline is a YAML file of stages ( solve , sweep , order_study , verify , docs , shell , assert , report ); gyre pipeline run FILE executes it ( how to write one ). A stage whose inputs and code have not changed is not run again: its recorded outputs are reused. Any edit of the working tree re-runs the stages that read it. Every result — a run, a verification study, a pipeline stage, a deployment, the documentation site — records the revision, device and time that produced it. The numbers on the verification pages are rendered from those records, never typed in. The same engine is the continuous integration: make ci runs lint, tests, verification, the documentation build and a smoke-tested deployment on port 8601.\nA pipeline is a YAML document: parameters, and a list of stages with a type ( uses ), inputs ( with ) and dependencies ( needs ). References ${params.x} and ${stages.<id>.outputs.<path>} wire stages together and create implicit dependencies. Stage type Runs Purpose solve subprocess one case, optional gradients / error estimate / order selection ( adapt ) sweep subprocess warm-started parameter sweep in one solver process order_study subprocess the same case at several polynomial degrees verify subprocess verification studies docs subprocess build the documentation site shell local any command (lint, tests, deployment) assert local quality gates on values from earlier stages report local Markdown/JSON summary\nA stage’s key is the SHA-256 of its type, its resolved inputs, the content digest of a case file named by case , the keys of the stages it depends on and the code state. If the stored record of a stage has the same key and succeeded, the stage is skipped and its outputs reused. Changing an input or an upstream stage therefore invalidates exactly the stages that depend on it. --force ignores the cache; cache: false marks stages that must always run (deployment). A stage whose execution fails replaces an earlier success record, so it is not served from the cache afterwards. The code state is the commit for a clean working tree and <commit>+dirty.<digest> otherwise, where the digest covers git diff HEAD and the untracked, non-ignored files: every uncommitted edit, and every new file that git does not ignore, invalidates every stage. Three paths of the Gyre checkout are left out, because no stage reads them: uncommitted changes under devlog/ and .github/ and to CLAUDE.md do not change the code state (a commit still does). Every other file counts, README.md included. Where git cannot describe the tree Gyre runs from (an installed package, the container image, no git) the code state is the revision followed by a hash of the package sources, and docs and verify stages always run, because they read files outside the package. A shell stage reads the tree of the directory it runs in ( with.cwd , else the engine’s working directory). If that directory is not inside the Gyre checkout, the state of the git work tree around it enters the stage key as well; if there is no git work tree there, the stage is never served from the cache. The key is formed before a stage runs. If the code state has changed by the time a compute or shell stage ends, nobody knows which code the stage saw: its record gets code_changed: true and revision_after , its stage.json is stored with key: null so that it is never served from the cache, the engine prints the code changed while the stage ran (...); its result is not cached , and the stages that follow are keyed on the new state. Runs that share <workdir>/<name> are serialised by an advisory file lock on that directory (POSIX; without fcntl runs are not serialised): a second run waits for the first, then finds its results in the cache where the keys match.\nA failed stage (non-zero exit, exception, or a gate whose check fails) marks the pipeline failed; stages depending on it are recorded as skipped with the reason; independent stages still run. The pipeline record ( pipeline.json ) lists every stage with status, key, wall time and outputs.\n\nArtefact Record run result ( result.json ) spec and its digest, outputs, history, timings, version, commit (+dirty), device, timestamp verification study ( <id>.json ) checks, charts, tables, version, commit, device, timestamp, wall time pipeline stage ( stage.json ) key, status, stage type, revision (the code state of the stage key), finish time; for a succeeded stage the resolved inputs, outputs and wall time, for a failed stage the error pipeline run ( pipeline.json ) parameters, revision (the same code state), start and finish time, and every stage’s status, key, wall time and outputs deployment ( deployment.json , /api/v1/info ) commit, start time, URLs, smoke-test result documentation site ( site-manifest.json ) build stamp shown in the footer of every page The documentation build reads verification files and renders their provenance next to each chart and table. A deployment builds its site from the working tree it runs, so the served documentation, the served API and the published evidence refer to one revision.\n+dirty on a stamp means that code a run can depend on differed from the commit. Three kinds of path do not count: the effort ledger and roadmap under devlog/ , the hosted workflow definition and the agent instructions, which no code reads; and the verification records under var/verification/ , which runs write and no computation reads. Without the last exception a verification run would be dirty by its own output: the first study to finish rewrites its tracked record, and every later study of the same run would carry +dirty on a clean checkout. The records are committed after the run, so a record names the commit of the code that produced it, which is the parent of the commit that stores it.\nNext to every chart and table the site compares the revision of the record with the revision it was built from. A record is marked with a tick when it was produced by the build’s own commit, by its parent, or by an earlier commit with the same code: the build walks back from its commit through the commits that change only documentation text ( docs/content , the README), evidence records or bookkeeping, up to and including the last commit that changed anything else ( gyre.identity.code_base ), and a record from any of them was produced by the code of the build. A record from any other commit, or from a tree with uncommitted changes, is marked as a mismatch. The theme and the builder of the documentation count as code.\nlint ─┬─▶ verification-short └─▶ unit-tests ─┬─▶ service-tests ───────────────┐ └─▶ verification ─┬─▶ docs ──────┴─▶ deploy (+ smoke test over the tailnet) └─▶ image (build + container smoke test)\nThe whole pipeline was last run with make ci on the development host (8 CPU threads, RTX 3060 Ti) at revision 76a489be21 , from a clean working tree, started 2026-11-11 10:38: Stage Result Wall time [s] lint succeeded 0 verification-short succeeded 41 unit-tests succeeded 1256 service-tests succeeded 118 verification succeeded 1971 docs succeeded 7 image succeeded 67 deploy succeeded 26 58 minutes in all. verification-short (six cheap studies) and lint form the fast tier ( make ci-fast ); verification is the whole suite, whose records this site renders. On that host the image stage needs GYRE_BUILD_NETWORK=host ( why ).\nThe CI pipeline is an ordinary pipeline, runnable locally with make ci ; a hosted workflow ( .github/workflows/ci.yml ) mirrors it without the deployment. Because of its shell stages, ci is not startable through the service unless the operator sets GYRE_PIPELINES_ALLOW_SHELL=1 . The final stage calls tools/deploy.py up , which refuses a port on which something already listens, builds the site, starts the service on all interfaces, and runs deploy/smoke.py against the tailnet address: health, deployment info, documentation pages, presets, structured errors, pipeline definitions, the MCP tool list, a real solver job with an adjoint gradient (never answered from the result cache), and metrics. deploy.py smoke-tests and records only the process it started: an answer from another service on the port is an error.\nNative ( tools/deploy.py up|down|status ): named deployments under var/deploy/<name>/ , each with its own site, data directory, log and record. Container ( deploy/Dockerfile , deploy/compose.yaml ): multi-stage image that installs the package, stamps the revision, builds the site, ships the analysis pipeline definitions and the examples, and serves on port 8600 bound to all interfaces; GPU and AVX-only hosts are handled by build arguments. systemd ( tools/deploy.py systemd , from the template deploy/gyre.service.in ): a user unit for this checkout that builds the documentation before every start and restarts the service on failure.\ntools/worklog.py keeps an append-only ledger of category switches (core capability, verification, logistics) and of delegated agent time in devlog/time.jsonl ; make report integrates it. The measured split, how it is measured and how far it can be trusted are on the effort ledger page.","title":"Pipelines and provenance","url":"engineering/pipelines-and-provenance.html"},{"audience":[],"chunks":[{"anchor":"","end":928,"heading":"","level":2,"start":0},{"anchor":"in-brief","end":1702,"heading":"In brief","level":1,"start":929},{"anchor":"kernel-throughput","end":5845,"heading":"Kernel throughput","level":2,"start":1703},{"anchor":"cpu","end":9521,"heading":"CPU","level":2,"start":5846},{"anchor":"what-derivatives-cost","end":10111,"heading":"What derivatives cost","level":2,"start":9522},{"anchor":"where-time-goes","end":11039,"heading":"Where time goes","level":2,"start":10112},{"anchor":"memory-of-the-block-assembly","end":12075,"heading":"Memory of the block assembly","level":3,"start":11040},{"anchor":"why-a-gpu-helps-a-method-like-this","end":12456,"heading":"Why a GPU helps a method like this","level":4,"start":12076}],"headings":["In brief","Kernel throughput","CPU","What derivatives cost","Where time goes","Memory of the block assembly","Why a GPU helps a method like this"],"keywords":["performance","throughput","GPU","CPU","compilation","cost model"],"level":2,"max_level":4,"path":"engineering/performance.md","section":"engineering","section_title":"Software Engineering","summary":"Measured kernel throughput on CPU and GPU, the cost of derivatives relative to a residual evaluation, and where time goes in practice — compilation for small problems, Krylov iterations for large ones.","text":"The numbers on this page are measurements recorded by the verification pipeline, not portable performance guarantees. A record written by the current timing studies states the device it was taken on and the load of the host during the measurement (the host table), with a flag for a host that was busy; read the timings together with that table and the run notes. Each timing is the minimum over at least 7 interleaved rounds, and the timing studies run after all other studies of an invocation, one at a time. On a host without a GPU the GPU study is skipped: gyre verify writes no GPU record and leaves a performance_gpu.json of an earlier invocation in the output directory untouched. The blocks below then show that earlier record, marked “Earlier run”, with the checks and tables it was written with (a block for a table the record does not contain reports it as missing); with no record in the directory they show no data.\nSmall cases are compile-bound. A new combination of mesh size and polynomial degree costs tens of seconds of JIT compilation before the first iteration; the solve itself is then short. A persistent cache and warm sessions exist to pay that once. Derivatives cost a small multiple of a residual evaluation — the measured ratios are in the tables below. CPU for small problems, GPU for large ones. The service chooses per job (GPU above 60 000 degrees of freedom if one is present); GYRE_DEVICE chooses per process. Memory, not time, limits large cases: the Krylov basis and the element-block inverses. Everything below is a measurement on one host, recorded with its load; none of it is a portable performance guarantee, and no scaling study on many cores or devices exists.\nPassed Kernel throughput (NVIDIA GeForce RTX 3060 Ti) 2 of 2 checks passed · 103.5 s · quick suite Wall-clock cost of the three kernels everything else is built from — the weak residual, a Jacobian-vector product (forward mode, including its primal sweep) and a vector-Jacobian product (reverse mode) — on warped periodic meshes, after compilation. Throughput is degrees of freedom per second for one residual evaluation. Each kernel is called 2 times before it is timed; the three kernels of a configuration are then timed in alternation, one call of each per round, and each timing is the minimum over the rounds: at least 7, continued until 5 consecutive rounds lower no minimum by more than 2 % (at most 60 rounds, and at most 20 s of timed calls per configuration beyond the first 7 rounds). Other work on the machine can only lengthen a call, so the minimum is the statistic least affected by it, and alternation spreads a burst of load over all three kernels. The ratio of the median to the minimum of the rounds is tabulated as a measure of the disturbance. Timings are measurements on the device and under the host load recorded with them (second table), not portable performance guarantees. Algorithmic differentiation bounds the operation count of a Jacobian-vector product by about 2.5 and of a vector-Jacobian product by about 4 to 5 residual evaluations; the two checks are regression guards about three times above those counts, leaving room for memory traffic, not derived limits. JVP cost / residual cost (worst case) 2.392 expected < 8 (regression guard, about 3 x the operation-count bound of forward mode; measured while other work loaded the machine, see the notes) pass VJP cost / residual cost (worst case) 3.088 expected < 12 (regression guard, about 3 x the operation-count bound of reverse mode; measured while other work loaded the machine, see the notes) pass Run notes The timings were taken on a busy machine (the GPU was 17 % utilized when the study started). The reported timings are minima over interleaved rounds, which other work can raise but not lower: they are upper bounds of the undisturbed kernel costs. The median of the rounds was up to 2.20 times the minimum, which shows the size of the disturbance. The ratios of the minima, which the two checks bound from above, are less affected than the timings themselves but may still be; the checks are marked accordingly, and the throughput figures should be read as lower bounds. Timings are minima over 7 to 32 interleaved rounds. Residual throughput on NVIDIA GeForce RTX 3060 Ti Kernel throughput (NVIDIA GeForce RTX 3060 Ti) Kernel timings Kernel throughput (NVIDIA GeForce RTX 3060 Ti) case elements DOF residual [ms] JVP [ms] VJP [ms] residual throughput [M DOF/s] JVP / residual VJP / residual timed rounds median / minimum (worst kernel) 2-D Euler, p = 3 16x16 16384 1.9998 1.9226 1.8676 8.19282 0.961396 0.933893 8 1.07115 2-D Euler, p = 3 32x32 65536 1.9668 2.0278 3.0082 33.3211 1.03101 1.52949 13 1.55257 2-D Euler, p = 3 64x64 262144 3.2801 4.3277 6.9116 79.9196 1.31938 2.10713 26 2.02972 2-D Euler, p = 3 128x128 1048576 9.15879 21.9113 28.2789 114.489 2.39238 3.08762 7 1.41766 2-D Euler, p = 3 192x192 2359296 11.2903 21.1802 34.8137 208.967 1.87596 3.08351 14 2.07155 2-D Navier–Stokes, p = 3 32x32 65536 1.879 1.8656 2.7449 34.8781 0.992868 1.46083 15 1.86878 2-D Navier–Stokes, p = 3 64x64 262144 3.5792 5.2204 7.61879 73.241 1.45854 2.12863 14 2.19849 2-D Navier–Stokes, p = 3 128x128 1048576 17.3127 26.6045 45.6822 60.567 1.5367 2.63866 10 1.40094 3-D Euler, p = 2 8x8x8 69120 3.3434 4.1497 4.3832 20.6736 1.24116 1.311 8 1.24168 3-D Euler, p = 2 12x12x12 233280 4.56369 9.09878 10.1464 51.1165 1.99373 2.22328 32 1.82769 3-D Euler, p = 2 24x24x24 1866240 44.3982 60.4688 96.4992 42.0342 1.36197 2.1735 7 1.46526 Device and host load during the measurement Kernel throughput (NVIDIA GeForce RTX 3060 Ti) device platform CPUs CPU share of other processes load average at start (1, 5, 15 min) load average at end GPU utilization at start [%] busy NVIDIA GeForce RTX 3060 Ti gpu 8 0.0949 6.72 / 9.29 / 11.39 2.71 / 7.12 / 10.38 17 yes\nPassed Kernel throughput (cpu) 2 of 2 checks passed · 123.8 s · quick suite Wall-clock cost of the three kernels everything else is built from — the weak residual, a Jacobian-vector product (forward mode, including its primal sweep) and a vector-Jacobian product (reverse mode) — on warped periodic meshes, after compilation. Throughput is degrees of freedom per second for one residual evaluation. Each kernel is called 2 times before it is timed; the three kernels of a configuration are then timed in alternation, one call of each per round, and each timing is the minimum over the rounds: at least 7, continued until 5 consecutive rounds lower no minimum by more than 2 % (at most 60 rounds, and at most 20 s of timed calls per configuration beyond the first 7 rounds). Other work on the machine can only lengthen a call, so the minimum is the statistic least affected by it, and alternation spreads a burst of load over all three kernels. The ratio of the median to the minimum of the rounds is tabulated as a measure of the disturbance. Timings are measurements on the device and under the host load recorded with them (second table), not portable performance guarantees. Algorithmic differentiation bounds the operation count of a Jacobian-vector product by about 2.5 and of a vector-Jacobian product by about 4 to 5 residual evaluations; the two checks are regression guards about three times above those counts, leaving room for memory traffic, not derived limits. JVP cost / residual cost (worst case) 3.035 expected < 8 (regression guard, about 3 x the operation-count bound of forward mode; measured while other work loaded the machine, see the notes) pass VJP cost / residual cost (worst case) 7.329 expected < 12 (regression guard, about 3 x the operation-count bound of reverse mode; measured while other work loaded the machine, see the notes) pass Run notes The timings were taken on a busy machine (other processes used 12 % of the capacity of the 8 CPUs while the study ran). The reported timings are minima over interleaved rounds, which other work can raise but not lower: they are upper bounds of the undisturbed kernel costs. The median of the rounds was up to 1.85 times the minimum, which shows the size of the disturbance. The ratios of the minima, which the two checks bound from above, are less affected than the timings themselves but may still be; the checks are marked accordingly, and the throughput figures should be read as lower bounds. Timings are minima over 11 to 20 interleaved rounds. Residual throughput on cpu Kernel throughput (cpu) Kernel timings Kernel throughput (cpu) case elements DOF residual [ms] JVP [ms] VJP [ms] residual throughput [M DOF/s] JVP / residual VJP / residual timed rounds median / minimum (worst kernel) 2-D Euler, p = 3 16x16 16384 3.5397 6.4003 10.6923 4.62864 1.80815 3.02068 11 1.53423 2-D Euler, p = 3 32x32 65536 9.6774 29.3689 70.9214 6.77207 3.03479 7.32856 11 1.85188 2-D Euler, p = 3 64x64 262144 110.097 188.667 420.119 2.38102 1.71364 3.81589 11 1.11999 2-D Navier–Stokes, p = 3 32x32 65536 22.791 62.0382 90.5424 2.87552 2.72205 3.97273 13 1.60486 2-D Navier–Stokes, p = 3 64x64 262144 227.548 355.136 591.178 1.15204 1.56071 2.59804 12 1.28127 3-D Euler, p = 2 8x8x8 69120 22.8335 49.8549 98.9936 3.02713 2.18341 4.33546 20 1.48412 3-D Euler, p = 2 12x12x12 233280 148.557 258.628 353.32 1.57031 1.74094 2.37834 15 1.30874 Device and host load during the measurement Kernel throughput (cpu) device platform CPUs CPU share of other processes load average at start (1, 5, 15 min) load average at end GPU utilization at start [%] busy cpu cpu 8 0.1184 9.36 / 11.72 / 12.43 7.04 / 9.44 / 11.46 — yes\nA Jacobian–vector product (forward mode, including the primal sweep it needs) and a vector–Jacobian product (reverse mode) each cost a small multiple of one residual evaluation — the columns JVP / residual and VJP / residual of the tables. The two checks of each study bound those ratios as regression guards, about three times above the operation counts of forward and reverse mode; they are not derived limits. Inside the Krylov solver the primal sweep is shared by all iterations ( linearise inside the solve ), so the per-iteration cost is lower than the stand-alone JVP measured here.\nProblem size Dominant cost What helps small (up to ~10⁴–10⁵ DOF) JIT compilation, tens of seconds per new mesh/order persistent cache, sessions, CPU execution medium Krylov iterations × tangent sweeps; block-inverse set-up lagging the block inverses ( solver.precond_lag ), GPU; on wall-resolved meshes the line smoother for solves to a tight tolerance ( measured on two small cases ); p-multigrid lowers the Krylov iteration count; its net effect on operator applications and on compiled wall time is measured per case , not claimed in general large memory: Krylov basis and block inverses ( K \\cdot block size²) solver.precond_precision: single halves the blocks and the working memory of their assembly without changing the answers ( comparison , largest solve ); lower krylov_dim ; more device memory The service picks the device per job from the estimated number of degrees of freedom (GPU above 60 000 if one is present).\nThe element blocks of the preconditioner come from batched Jacobian-vector products: one tangent per column of a block, for all elements at once. One tangent of the flux-differencing residual holds p + 1 two-point fluxes per node and direction, so the working memory of a batch grows with \\text{batch}\\times N_\\text{dof}\\times(p+1)\\times d , and in three dimensions at higher degree a batch of all columns no longer fits: at p = 4 on 135 000 unknowns the assembly asked an 8 GB device for 8.1 GiB. The batch is therefore sized to keep that product below a budget ( gyre.implicit.BLOCK_BATCH_BUDGET , 6.4\\times10^{7} ; about 1 GiB for the largest single allocation, by measurement on that device), and the columns are processed in several sweeps. The blocks do not depend on the batch; only memory and time do. GYRE_BLOCK_BATCH_BUDGET raises the budget on a device with more memory. The blocks themselves, K\\,(n_\\text{var}(p+1)^d)^2 numbers and as many again for their inverses, remain the floor: 675 MB each at p = 4 for 216 hexahedra.\nEvery kernel is a sequence of element-wise operations and small tensor contractions over arrays with a leading element axis — the pattern accelerators execute best. There is no sparse matrix assembly, no indirect addressing beyond one gather for neighbour traces, and no per-element Python control flow. The same source runs on both devices; the choice is an environment variable.","title":"Performance","url":"engineering/performance.html"},{"audience":[],"chunks":[{"anchor":"in-brief-working-on-the-code","end":670,"heading":"In brief: working on the code","level":1,"start":0},{"anchor":"hand-off-state","end":941,"heading":"Hand-off state","level":2,"start":671},{"anchor":"effort-balance","end":1352,"heading":"Effort balance","level":2,"start":942},{"anchor":"definition-of-done-for-numerical-changes","end":1704,"heading":"Definition of done for numerical changes","level":2,"start":1353},{"anchor":"releasing-a-state","end":1705,"heading":"Releasing a state","level":2,"start":1705},{"anchor":"freeze-verify-record-deploy","end":2724,"heading":"Freeze, verify, record, deploy","level":2,"start":1706},{"anchor":"conventions-that-prevent-drift","end":2725,"heading":"Conventions that prevent drift","level":2,"start":2725},{"anchor":"one-path-generated-references","end":3164,"heading":"One path, generated references","level":2,"start":2726},{"anchor":"environment-facts-worth-knowing","end":3547,"heading":"Environment facts worth knowing","level":3,"start":3165},{"anchor":"lessons-recorded-during-development","end":6250,"heading":"Lessons recorded during development","level":4,"start":3548}],"headings":["In brief: working on the code","Hand-off state","Effort balance","Definition of done for numerical changes","Releasing a state","Freeze, verify, record, deploy","Conventions that prevent drift","One path, generated references","Environment facts worth knowing","Lessons recorded during development"],"keywords":["process","roadmap","contributing","agents","conventions"],"level":2,"max_level":4,"path":"engineering/development-process.md","section":"engineering","section_title":"Software Engineering","summary":"How the project is organised for contributors — human or agent: the roadmap as hand-off state, the effort ledger, commit and verification discipline, and the conventions that keep four interfaces consistent.","text":"make bootstrap # .venv with uv (handles CPUs without AVX2 and NVIDIA GPUs) make test # unit and integration tests make test-service # end-to-end service make lint # ruff make docs # build the site into site/ make verify # verification studies -> var/verification, rendered by the docs make ci # the whole pipeline with stage caching Read devlog/ROADMAP.md first (decisions, milestones, status log, open items), then CLAUDE.md (environment, conventions, definition of done). A numerical change is done when the invariants, the affected verification studies and the derivative checks pass and the documentation is updated. Gyre contains no optimizer and must not grow one.\nThree files let anyone resume work cold: devlog/ROADMAP.md — mission, decision table, milestones, dated status log; CLAUDE.md — environment constraints, commands, conventions, definition of done; devlog/time.jsonl — the effort ledger ( python3 tools/worklog.py report ).\nThe project brief asked for effort split roughly 70 % core capability, 25 % testing and verification, 10 % logistics and tooling (targets that sum to 105 %). The split is recorded in an append-only ledger, part live and part entered afterwards. The measured split, the rule by which it is compared with the target, and what limits its accuracy are on the effort ledger page, which is generated from the ledger.\nUnit invariants pass (free-stream preservation, conservation, entropy balance, split-argument blocks against a dense Jacobian, dot-product identity). Affected verification studies pass ( gyre verify --only … ). Derivatives are checked against finite differences. Documentation is updated at the appropriate depth level and builds without broken links.\n\nThe documentation marks a verification record whose revision is neither the revision the site was built from nor its parent. A release therefore has a fixed order: Freeze. Tests green, code committed: revision X. Verify. Run the whole suite on a clean checkout of X (a separate git worktree leaves the working tree free). Every record is then stamped X, not dirty. Record. Commit the records as the direct child of X, together with prose that describes the run (its size and duration) if any. Deploy from that commit: the served pages show each record as “parent revision of this build”. make ci does the same in one pipeline (tests, verification, documentation, image, deployment on the CI port). Later commits that change only documentation text, evidence records or bookkeeping leave the records valid: the pages then mark them “same code as this build”. A commit that changes code, tests, tools or the documentation theme makes every record a mismatch until the suite has run again ( what makes a revision dirty ).\n\nNew capability goes into the discipline or runner first, then REST, then MCP — never the other way round. Reference pages (case schema, presets, CLI, REST routes, MCP tools) are generated from the code at documentation build time ( gyre.docgen ). Verification pages render study files; text may interpret numbers but must not restate them by hand. Sample or synthetic data never appears outside the authoring guide, and is labelled there.\nTooling is project-local ( .tools/bin/uv , .venv/ ); no system packages are required. CPUs without AVX2 need jax < 0.12 (the bootstrap script detects this). Services bind 0.0.0.0 ; port 8600 is the development deployment, 8601 the CI deployment. Scratch scripts must not shadow standard-library module names — a bisect.py next to a script breaks import jax through random → bisect .\nA mesh generator bug looked like a solver bug. Cosine clustering left zero parametric speed at the aerofoil trailing edge; the singular Jacobian there stalled Newton at a residual of 10^{-3} with a nonsensical entropy level. A mesh-quality check (minimum scaled Jacobian) found it immediately; such a check is now part of the mesh tests. Measure the measurement — and test the explanation. Several verification failures were caused by the study, not the code: an ill-conditioned finite difference, a step sweep that left the smooth regime, a mesh too coarse to represent the geometry. One plausible explanation (“time-integration error dominates at p = 4 ”) was itself wrong and was caught only because the fix was verified to change the numbers — it did not. Warm starts need their own convergence logic. Re-solving from a converged neighbour first restarted pseudo-time continuation at low CFL, then chased a tolerance relative to its own tiny initial residual. Sessions now start in the Newton regime and converge relative to the cold-solve residual scale. One definition of “which code is this”. Result files and the service reported the revision through one function; verification records and the documentation build through an older copy that called any uncommitted file dirty, the effort ledger and the records themselves included. A verification run on a clean checkout stamped every record after the first as dirty. The stamp now has a single source. A strict failure policy finds what a permissive one hides. When a linear solve that misses its tolerance became an error, the adjoint of the turbulent flat plate stopped: on a wall-resolved mesh block Jacobi alone had been returning derivatives from a solve that had not converged. The coarse multigrid level fixes it; the policy stays. A threshold belongs to a regime. The manufactured solution of the turbulence closure demanded the design rate and measured half an order less. The eddy viscosity had moved the mean flow into the regime where the laminar discretization converges at that rate too: the study now solves the laminar problem at the effective Reynolds number and requires the closure to change neither the order nor the error. Reverse mode through a chunked loop stores the loop. The matrix-free mesh deformation recomputes its weights chunk by chunk, but differentiating it in reverse mode kept the weights of every chunk, the dense matrix it was meant to avoid. Its transpose is written out by hand as a second chunked loop. Compilation is the user-visible cost. For small cases tens of seconds of JIT dwarf milliseconds of solve; this shaped sessions, the persistent cache, per-job device selection and test-suite structure.","title":"Development process","url":"engineering/development-process.html"},{"audience":["contributors","reviewers"],"chunks":[{"anchor":"","end":1721,"heading":"","level":1,"start":0},{"anchor":"how-the-ledger-works","end":4651,"heading":"How the ledger works","level":2,"start":1722},{"anchor":"the-full-report","end":6969,"heading":"The full report","level":2,"start":4652},{"anchor":"uncertainties-largest-first","end":8725,"heading":"Uncertainties, largest first","level":2,"start":6970},{"anchor":"checking-the-tool","end":9474,"heading":"Checking the tool","level":3,"start":8726}],"headings":["How the ledger works","The full report","Uncertainties, largest first","Checking the tool"],"keywords":["effort","ledger","worklog","time","balance","delegated","uncertainty"],"level":1,"max_level":3,"path":"engineering/effort-ledger.md","section":"engineering","section_title":"Software Engineering","summary":"How the development time was split between core capability, verification and logistics: the measured numbers, how they are measured, and how far they can be trusted. The split does not meet the 70 / 25 / 10 target cleanly, and a large part of the record is an estimate.","text":"The project brief asked for the work to be balanced: about 70 % core capability development, 25 % testing and verification, 10 % development logistics and tooling. Those targets sum to 105 % , so they are read here as proportions: 66.7 / 23.8 / 9.5 %. What the ledger shows (snapshot of devlog/time.jsonl up to the entry of 2026-11-12T12:07:53-05:00 ; regenerate with python3 tools/effort_page.py , which writes this table, the conclusions below and the full report from tools/worklog.py ): Basis Core Verification Logistics Hours Target as given 70 % 25 % 10 % Target as a share of 105 66.7 % 23.8 % 9.5 % Lead, every closed interval in full 63.0 % 24.1 % 12.9 % 21.11 Lead, without the 4 long intervals 43.0 % 34.4 % 22.5 % 12.04 Delegated agents and workflows (12.15 h of them estimated) 46.2 % 52.2 % 1.6 % 12.28 Lead plus delegated 56.8 % 34.4 % 8.7 % 33.39 What can and cannot be concluded (a category counts as at its share within 3 points of the target read as a share of 105 %). Core is below its share on the combined basis: 56.8 % against 66.7 % (lead alone 63.0 %, lead without the long intervals 43.0 %, delegated 46.2 %). Verification is above its share on the combined basis: 34.4 % against 23.8 % (lead alone 24.1 %, lead without the long intervals 34.4 %, delegated 52.2 %). Logistics is at its share on the combined basis: 8.7 % against 9.5 % (lead alone 12.9 %, lead without the long intervals 22.5 %, delegated 1.6 %). The core share of the lead’s time is known only as a range , 43 % to 63 %, depending on how the 4 long intervals are treated: they hold 544 of the lead’s 1267 minutes. The target was therefore not met within 3 points on the combined basis (core below, verification above its share).\ntools/worklog.py appends one JSON line per event to devlog/time.jsonl ; the file is never rewritten. Command Records start <core\\|verify\\|logistics> \"<note>\" the lead switched category now (stamp with seconds) stop \"<note>\" the lead stopped working; closes the current interval backfill <ISO time> <category> \"<note>\" a switch typed in afterwards; stored with \"backfilled\": true and counted as an estimate delegate <category> <minutes> \"<note>\" wall-clock minutes of a background agent or workflow: an estimate delegate <category> --from <ISO> --to <ISO> \"<note>\" the same from two time stamps: measured idle <ISO from> <ISO to> \"<note>\" a gap inside an interval in which the lead did not work; subtracted from that interval report [--json \\| --markdown \\| --summary] [--until <ISO time>] the integration shown on this page; --until integrates the ledger as it stood at that stamp The report follows four rules, each of which replaces a weaker one in the first version of the tool: Nothing is capped. Every closed interval counts in full, less recorded idle gaps (gaps that overlap each other are merged first, so no minute is subtracted twice). (The first version cut every interval at 45 minutes, which hid two long intervals.) Intervals longer than 45 minutes without a recorded switch are listed individually, and the split is also shown without them. An open interval is not counted. If the last entry is not stop , the report names the open interval and leaves it out, so the numbers do not depend on when the report is run. (The first version closed the last interval at the current time.) Backfilled stamps are estimates. New ones are marked. Entries from before the marker existed are recognised by a whole-minute stamp, since start writes seconds; this can mislabel about one live entry in sixty. Delegated time is kept separate. It is wall-clock time of agents that ran in parallel with the lead, often several at once. Adding it to the lead’s hours adds two different kinds of time; the combined table is labelled as indicative. Workflows of many agents. A workflow is booked with its wall-clock time, split between the categories; for the workflows of the first day the split was a judgement. The review of the 0.3.0 candidate (132 agents, 245.8 minutes of wall-clock on 2026-11-11) had every agent report when it started and finished, and its wall-clock is split by those measured agent-minutes: 230 of implementers (core) and 868 of reviewers and of the second readers who tried to refute each finding (verification). Booked in agent-minutes instead, the same workflow would be 3.8 hours of core and 14.5 hours of verification. Either way it is the main reason why the combined split shows more verification than the target: the review was sized for thoroughness, not for the ratio, and the fixes it led to were then made by the lead. The lead’s own record is the row to read for how the directed work was divided.\nLedger from 2026-11-10T09:18:00-05:00 to 2026-11-12T10:22:11-05:00; 85 closed lead intervals, 14 delegated streams. Lead, closed intervals as recorded (nothing capped) Category Minutes Share Target as given Target as a share of 105 Share minus normalised target Of which between backfilled stamps (min) Core capability 798.4 63.0 % 70 % 66.7 % -3.6 212.8 Testing and verification 305.3 24.1 % 25 % 23.8 % +0.3 134.7 Logistics and tooling 162.8 12.9 % 10 % 9.5 % +3.3 98.0 Total 1266.5 (21.11 h) 100 % 105 % 100 % Recorded idle gaps subtracted: 1583.2 min. Long intervals (more than 45 minutes with no recorded switch; counted in full above) From To Minutes Category Note 15:05:39 16:31:44 86.1 core documentation accuracy fixes, integrating review fixes 16:31:44 20:59:58 268.2 core external mesh import (Gmsh/NPZ), reference quantities, file-mesh cases; orchestrating fix + docs-sync workflows 20:59:58 21:56:33 56.6 verify full tests, clean verification run, redeploy + smoke 21:56:33 06:16:45 133.4 core phase 2: turbulence closure, 3-D, MDO contract, audit fixes (orchestration) Lead, long intervals left out (sensitivity) Category Minutes Share Target as given Target as a share of 105 Share minus normalised target Core capability 310.6 43.0 % 70 % 66.7 % -23.7 Testing and verification 248.8 34.4 % 25 % 23.8 % +10.6 Logistics and tooling 162.8 22.5 % 10 % 9.5 % +13.0 Total 722.2 (12.04 h) 100 % 105 % 100 % Delegated agents and workflows (wall-clock time, parallel to the lead; kept separate) Category Minutes Share Target as given Target as a share of 105 Share minus normalised target Of which estimated (min) Core capability 340.4 46.2 % 70 % 66.7 % -20.5 332.4 Testing and verification 384.4 52.2 % 25 % 23.8 % +28.4 384.4 Logistics and tooling 12.0 1.6 % 10 % 9.5 % -7.9 12.0 Total 736.8 (12.28 h) 100 % 105 % 100 % Lead plus delegated (two different kinds of time added; indicative only) Category Minutes Share Target as given Target as a share of 105 Share minus normalised target Core capability 1138.8 56.8 % 70 % 66.7 % -9.8 Testing and verification 689.7 34.4 % 25 % 23.8 % +10.6 Logistics and tooling 174.8 8.7 % 10 % 9.5 % -0.8 Total 2003.3 (33.39 h) 100 % 105 % 100 % Open interval, not counted: logistics since 2026-11-12T10:22:11-05:00 (freeze of 0.4.0, verification run on a clean checkout).\n4 long intervals carry 43 % of the lead’s recorded time (544.4 of 1266.5 minutes). They are listed in the report above. During the longest the lead was partly waiting for delegated agents, partly reviewing their results (which is verification work) and partly idle; no switch and no idle gap was recorded then, and none has been added afterwards because it would be a guess. The two tables “as recorded” and “long intervals left out” bracket the truth. 729 of the 737 delegated minutes are estimates. Workflows were entered as round figures after they finished and split between the categories by judgement (implementers and writers to core, reviewers and auditors to verification). The parallel groups of the second evening were not recorded by the session that ran them at all: their wall-clock window was reconstructed from file modification times in the following session. 35 % of the lead’s minutes lie between backfilled stamps (445 of 1267; the last column of the first table). A backfilled entry places work after the fact: the work exists, the minutes are an estimate. Documentation is booked as core. Writing the documentation content and building the documentation engine are counted as core capability, because served documentation is one of the deliverables. A reader who regards documentation as logistics should move those minutes. Categories are coarse. An interval has one category; a session that mixed debugging a study (verification) with fixing the code it exposed (core) is booked to whichever was entered. The ledger records 3 calendar days (2026-11-10, 2026-11-11, 2026-11-12), with a recorded idle gap of 1583 minutes where a session ended without a stop . It is evidence of how those days were spent, not a time-and-motion study.\ntests/test_worklog.py builds small ledgers in a temporary directory and asserts the rules above: long intervals counted in full and listed, an open interval left out and reported, a report that does not change with the clock, idle gaps subtracted, backfilled entries marked or inferred, delegated time kept out of the lead’s figures, targets shown as given and normalised, refusal of malformed input, and a snapshot ( report --until ) that leaves out everything stamped later. The numbers on this page are not typed in: tools/effort_page.py writes the table, the conclusions and the full report from the ledger for the stamp named at the top, and tests/test_docs_content.py fails when the page is not what that generator would write for that stamp.","title":"Effort ledger","url":"engineering/effort-ledger.html"},{"audience":[],"chunks":[{"anchor":"","end":3824,"heading":"","level":1,"start":0}],"headings":[],"keywords":["theory"],"level":1,"max_level":1,"path":"theory/index.md","section":"theory","section_title":"Mathematics & Numerical Methods","summary":"The equations Gyre solves and the numerical methods it uses — split-form DG spectral elements, entropy-conservative fluxes, curvilinear metrics, BR2 viscous terms, the Spalart–Allmaras turbulence closure, shock capturing, the implicit solver, adjoints and output error estimation — with derivations.","text":"Each page states the method at a practical level and keeps derivations and proofs at depth 4. Claims are tied to the verification studies that test them, and a statement that is a theorem is given with its conditions: where the default configuration is outside those conditions, the page says so. Governing equations L2 Gyre solves the compressible Euler and Navier–Stokes equations in conservation form, non-dimensionalised by the free stream, and uses their entropy structure as the organising principle of the discretization. Split-form DG spectral elements L2 Space is discretized by a nodal discontinuous Galerkin spectral element method on Gauss–Lobatto points. Its derivative operator satisfies summation by parts, which lets the volume terms be written as differences of symmetric two-point fluxes and gives discrete conservation and entropy statements on curved meshes. Two-point and interface fluxes L2 The volume terms use symmetric two-point fluxes that conserve entropy exactly; dissipation is added only at element interfaces through an upwind flux. A discrete entropy inequality is proven for the two interface fluxes built on the entropy-conservative flux (es_roe with matrix dissipation, llf with scalar dissipation); for the classical Roe flux, which is the default, and for HLLC entropy decay is measured, not proven. Curvilinear elements and metric terms L2 Elements are curved with the same polynomial technology as the solution. Metric terms are computed so that a uniform flow stays exactly uniform on any mesh, and they are evaluated inside the differentiated code so that shape derivatives come for free. Viscous terms — the BR2 scheme L3 Viscous fluxes use the second scheme of Bassi and Rebay with lifting operators evaluated on Gauss–Lobatto nodes. The discretization is compact (face neighbours only) and symmetric, which the implicit solver and the adjoint both rely on. Boundary conditions L2 Boundary conditions are imposed weakly through the same numerical fluxes as interior faces. Walls use a boundary state rather than a ghost-cell Riemann problem, which keeps force functionals adjoint consistent. Implicit solver L2 Steady states are found by pseudo-transient Newton–Krylov iteration. The Jacobian is never formed — its action comes from automatic differentiation — and it is preconditioned by p-multigrid with element-block-Jacobi smoothing whose blocks are also obtained by AD. Adjoints and sensitivities L2 Gyre computes exact derivatives of outputs with respect to flow parameters and surface shape with one transposed linear solve per output. All partial derivatives come from automatic differentiation of the discrete operators, so gradients are consistent with the solver to solver tolerance. Output error estimation L3 The adjoint that provides gradients also measures how much each element's unresolved residual contaminates an output. One extra linear solve yields an error estimate, a corrected output and per-element indicators. Shock capturing L2 Flows with shocks are stabilised by an artificial viscosity that a smoothness sensor switches on only in the elements containing a shock. The term lives inside the differentiated residual, so Newton convergence and adjoint derivatives stay exact. Turbulence closure (RANS, Spalart–Allmaras) L2 Turbulent flows are computed with the Reynolds-averaged equations closed by the negative Spalart–Allmaras model. The model equation is one more row of the same discrete operator, so the Newton linearisation, the adjoint and the shape derivatives (including the wall distance) remain exact. Outputs and functionals L2 The definition, sign convention, normalisation and units of every output name a result can contain — force and moment coefficients, heat, the volume diagnostics, the surface load field and the surface distributions.","title":"Mathematics & Numerical Methods","url":"theory/index.html"},{"audience":[],"chunks":[{"anchor":"","end":862,"heading":"","level":2,"start":0},{"anchor":"fluxes","end":1657,"heading":"Fluxes","level":2,"start":863},{"anchor":"non-dimensionalisation","end":2457,"heading":"Non-dimensionalisation","level":2,"start":1658},{"anchor":"entropy-structure","end":3971,"heading":"Entropy structure","level":4,"start":2458},{"anchor":"what-th-contains","end":4370,"heading":"What `th` contains","level":3,"start":3972}],"headings":["Fluxes","Non-dimensionalisation","Entropy structure","What `th` contains"],"keywords":["Navier-Stokes","Euler","entropy variables","non-dimensionalisation"],"level":2,"max_level":4,"path":"theory/governing-equations.md","section":"theory","section_title":"Mathematics & Numerical Methods","summary":"Gyre solves the compressible Euler and Navier–Stokes equations in conservation form, non-dimensionalised by the free stream, and uses their entropy structure as the organising principle of the discretization.","text":"Gyre models a calorically perfect gas in d = 2 or 3 space dimensions. The unknowns are the conserved variables \\mathbf U = \\begin{pmatrix} \\rho \\\\ \\rho \\mathbf u \\\\ \\rho E \\end{pmatrix}, \\qquad p = (\\gamma - 1)\\left(\\rho E - \\tfrac12 \\rho\\,|\\mathbf u|^2\\right), \\qquad T = \\frac{p}{\\rho R}, and the governing equations are a system of conservation laws with a source \\mathbf s that is zero except in manufactured-solution studies: \\partial_t \\mathbf U + \\nabla\\cdot\\mathbf F(\\mathbf U) - \\nabla\\cdot\\mathbf F_v(\\mathbf U, \\nabla \\mathbf W) = \\mathbf s . For turbulent flow the same system is read as the Reynolds-averaged equations: the state gains one transported variable, \\rho\\tilde\\nu , and the viscosity and conductivity gain an eddy contribution. That closure has its own page, Turbulence closure ; everything below describes the laminar system it extends.\nThe inviscid flux in Cartesian direction b and the viscous flux are \\mathbf F_b = \\begin{pmatrix} \\rho u_b \\\\ \\rho \\mathbf u\\, u_b + p\\,\\mathbf e_b \\\\ (\\rho E + p)\\, u_b \\end{pmatrix}, \\qquad \\mathbf F_{v,b} = \\begin{pmatrix} 0 \\\\ \\boldsymbol\\tau_{\\cdot b} \\\\ \\mathbf u\\cdot\\boldsymbol\\tau_{\\cdot b} + \\kappa\\, \\partial_b T \\end{pmatrix}, with the Newtonian stress under Stokes’ hypothesis and Fourier conduction, \\boldsymbol\\tau = \\mu\\left(\\nabla\\mathbf u + \\nabla\\mathbf u^{\\mathsf T} - \\tfrac23 (\\nabla\\cdot\\mathbf u)\\,\\mathbf I\\right), \\qquad \\kappa = \\frac{\\mu\\, c_p}{Pr}, \\quad c_p = \\frac{\\gamma R}{\\gamma - 1}. The viscous flux depends on the gradient of only d+1 quantities, the gradient variables \\mathbf W = (\\mathbf u, T) . The discretization lifts and differentiates exactly these.\nReference density, velocity and temperature are the free-stream values, and the reference length is one mesh unit (the chord of the built-in aerofoils, the diameter-independent unit of the cylinder mesh). Consequently \\rho_\\infty = 1,\\quad |\\mathbf u_\\infty| = 1,\\quad T_\\infty = 1,\\quad p_\\infty = \\frac{1}{\\gamma M_\\infty^2},\\quad R = \\frac{1}{\\gamma M_\\infty^2},\\quad \\mu = \\frac{1}{Re}. Force coefficients are therefore C = 2F / S_\\text{ref} , and the pressure coefficient is c_p = 2\\,(p - p_\\infty) . The Mach number, the angle of attack \\alpha (which sets the direction of \\mathbf u_\\infty and of the lift/drag axes), the Reynolds number, \\gamma and Pr are entries of one flat parameter dictionary th ; because it is a JAX pytree, each entry is a differentiable input without any further code.\nThe discretization is organised around the mathematical entropy \\eta(\\mathbf U) = -\\frac{\\rho s}{\\gamma - 1}, \\qquad s = \\ln p - \\gamma \\ln \\rho , which is strictly convex for \\rho, p > 0 . Its gradient defines the entropy variables \\mathbf w = \\frac{\\partial \\eta}{\\partial \\mathbf U} = \\left( \\frac{\\gamma - s}{\\gamma - 1} - \\frac{\\rho |\\mathbf u|^2}{2p},\\; \\frac{\\rho \\mathbf u}{p},\\; -\\frac{\\rho}{p} \\right)^{\\mathsf T}. Contracting the Euler equations with \\mathbf w gives a scalar conservation law for smooth solutions, \\partial_t \\eta + \\nabla\\cdot(\\eta\\,\\mathbf u) = 0 , because \\mathbf w^{\\mathsf T} \\partial_{\\mathbf U}\\mathbf F_b = \\partial_{\\mathbf U}(\\eta u_b) . The entropy potential \\psi_b = \\mathbf w^{\\mathsf T}\\mathbf F_b - \\eta u_b = \\rho u_b is what the entropy-conservation condition for two-point fluxes is written in. With viscosity and heat conduction the entropy can only decay: \\tfrac{\\mathrm d}{\\mathrm dt}\\int \\eta \\le 0 up to boundary terms. A scheme that reproduces this inequality discretely cannot blow up through the mechanism that destabilises under-resolved high-order methods (aliasing-driven entropy production of the wrong sign). Which parts of this Gyre reproduces discretely, and under which conditions, is stated with the interface fluxes : the inviscid volume terms always, the interface terms provably only with es_roe or llf , the viscous terms not at all. The unit test test_entropy_variables_are_gradient_of_entropy checks the formula above against jax.grad of \\eta .\nkey meaning default gamma ratio of specific heats 1.4 mach free-stream Mach number 0.5 alpha angle of attack in radians (API accepts alpha_deg ) 0 re Reynolds number per unit length; 10^{30} when inviscid — pr Prandtl number 0.72 p_back , pt_in , tt_in , t_wall boundary data relative to free-stream values case dependent suth Sutherland constant S/T_\\infty ; absent means constant viscosity absent","title":"Governing equations","url":"theory/governing-equations.html"},{"audience":[],"chunks":[{"anchor":"","end":456,"heading":"","level":2,"start":0},{"anchor":"why-gausslobatto-collocation","end":1132,"heading":"Why Gauss–Lobatto collocation","level":2,"start":457},{"anchor":"summation-by-parts","end":2133,"heading":"Summation by parts","level":3,"start":1133},{"anchor":"from-the-strong-form-to-flux-differencing","end":2566,"heading":"From the strong form to flux differencing","level":2,"start":2134},{"anchor":"the-split-form","end":4603,"heading":"The split form","level":3,"start":2567},{"anchor":"conservation-and-entropy-conservation-in-three-lines","end":7152,"heading":"Conservation and entropy conservation in three lines","level":4,"start":4604},{"anchor":"how-the-volume-term-is-evaluated","end":7978,"heading":"How the volume term is evaluated","level":3,"start":7153},{"anchor":"accuracy-you-should-expect","end":8574,"heading":"Accuracy you should expect","level":2,"start":7979}],"headings":["Why Gauss–Lobatto collocation","Summation by parts","From the strong form to flux differencing","The split form","Conservation and entropy conservation in three lines","How the volume term is evaluated","Accuracy you should expect"],"keywords":["DGSEM","summation by parts","flux differencing","collocation","Gauss-Lobatto"],"level":2,"max_level":4,"path":"theory/dgsem.md","section":"theory","section_title":"Mathematics & Numerical Methods","summary":"Space is discretized by a nodal discontinuous Galerkin spectral element method on Gauss–Lobatto points. Its derivative operator satisfies summation by parts, which lets the volume terms be written as differences of symmetric two-point fluxes and gives discrete conservation and entropy statements on curved meshes.","text":"The domain is partitioned into curved quadrilateral or hexahedral elements. Each element is the image of the reference cube [-1,1]^d under a polynomial mapping \\mathbf x(\\boldsymbol\\xi) , and the solution is a tensor-product polynomial of degree p in \\boldsymbol\\xi , stored by its values at the (p+1)^d Legendre–Gauss–Lobatto (LGL) nodes. Neighbouring elements are coupled only through numerical fluxes on shared faces — the solution may jump across them.\nUsing the same LGL points for interpolation and quadrature makes three things true at once: the mass matrix is diagonal, \\mathbf M = \\operatorname{diag}(w_0,\\dots,w_p) , so no linear system is needed to evaluate a time derivative; face values are simply nodal values (the end points are nodes), so no interpolation to faces; the derivative matrix \\mathbf D with D_{ij} = \\ell_j'(\\xi_i) satisfies summation by parts (SBP), the discrete integration-by-parts identity below. The price is that LGL quadrature with p+1 points is exact only to degree 2p-1 , one short of what the mass matrix of degree- p polynomials needs. The split form turns this weakness into a controlled one.\n\\mathbf Q = \\mathbf M \\mathbf D, \\qquad \\mathbf Q + \\mathbf Q^{\\mathsf T} = \\mathbf B = \\operatorname{diag}(-1, 0, \\dots, 0, +1). For nodal vectors \\mathbf u, \\mathbf v this is \\mathbf u^{\\mathsf T}\\mathbf M\\mathbf D\\mathbf v + (\\mathbf D\\mathbf u)^{\\mathsf T}\\mathbf M\\mathbf v = u_p v_p - u_0 v_0 : integration by parts, exactly, for the discrete inner product. Every stability and conservation property of the scheme is a consequence of this identity; none relies on exact integration. The verification study “Summation-by-parts operators” checks it to round-off for all degrees in use. Passed Summation-by-parts operators 1 of 1 checks passed · 0.29 s · quick suite Gauss–Lobatto collocation operators: the SBP identity M D + (M D)^T = B, quadrature exactness to degree 2p-1, exact differentiation of degree-p polynomials, skew-symmetry of the flux-differencing matrix M S and consistency of the p-multigrid transfers. largest defect over all identities and degrees 6.22e-15 expected < 1e-11 pass\nIn reference coordinates a conservation law reads J\\,\\partial_t \\mathbf U + \\sum_a \\partial_{\\xi_a} \\tilde{\\mathbf F}_a = 0 with contravariant fluxes \\tilde{\\mathbf F}_a = \\sum_b (J a^a_b)\\,\\mathbf F_b (see curvilinear metrics ). The standard strong-form DGSEM applies \\mathbf D to the nodal fluxes and penalises the difference between the numerical flux \\mathbf F^* and the interior flux at the two end nodes of each line of nodes.\nGyre replaces the derivative of the nodal flux by a flux-differencing operator. Along one line of nodes in direction a , at node i : J_i\\,\\partial_t \\mathbf U_i + \\sum_{m=0}^{p} S_{im}\\; \\tilde{\\mathbf F}^{\\#}_a(\\mathbf U_i, \\mathbf U_m) + \\frac{\\delta_{i\\in\\partial}}{w_i}\\; \\mathbf F^*(\\mathbf U^-, \\mathbf U^+;\\mathbf n)\\, s_J = 0, \\qquad \\mathbf S = 2\\mathbf D - \\mathbf M^{-1}\\mathbf B , where \\mathbf F^{\\#} is a symmetric, consistent two-point flux and the contravariant two-point flux uses the average of the metric terms at the two nodes, \\tilde{\\mathbf F}^{\\#}_a(\\mathbf U_i,\\mathbf U_m) = \\sum_b \\tfrac12\\big[(Ja^a_b)_i + (Ja^a_b)_m\\big]\\, \\mathbf F^{\\#}_b(\\mathbf U_i,\\mathbf U_m) . The last term lifts the interface flux onto the two face nodes with the factor 1/w_i ( s_J is the surface Jacobian, \\mathbf n the outward normal). Two remarks make this concrete: \\mathbf M\\mathbf S = \\mathbf Q - \\mathbf Q^{\\mathsf T} is skew-symmetric and \\mathbf S has a zero diagonal. The interior-flux boundary correction of the strong form has been absorbed into \\mathbf S using consistency, \\mathbf F^{\\#}(\\mathbf U,\\mathbf U) = \\mathbf F(\\mathbf U) . With the arithmetic-mean flux \\mathbf F^{\\#} = \\tfrac12(\\mathbf F_i + \\mathbf F_m) (option central ) the scheme is algebraically identical to the standard strong-form DGSEM on elements whose metric terms are constant along each node line (affine elements). On a curved element it is not: because the metric terms are averaged as well, the volume term is \\sum_m S_{im}\\,\\tfrac14\\big[(Ja)_i + (Ja)_m\\big](\\mathbf F_i + \\mathbf F_m) , which differs from the standard \\sum_m S_{im}\\,\\tfrac12\\big[(Ja\\,\\mathbf F)_i + (Ja\\,\\mathbf F)_m\\big] by -\\tfrac14\\sum_m S_{im}\\big[(Ja)_i - (Ja)_m\\big](\\mathbf F_i - \\mathbf F_m) . There central is the metric-split form of Gassner, Winters and Kopriva (2016), which is still conservative and free-stream preserving. Other choices of \\mathbf F^{\\#} correspond to split (skew-symmetric) forms of the nonlinear terms, which is where the name comes from.\nConservation. Multiply the volume term by w_i and sum over i : because \\mathbf M\\mathbf S is skew-symmetric and \\mathbf F^{\\#} is symmetric, \\sum_{i,m} (MS)_{im}\\,\\mathbf F^{\\#}(\\mathbf U_i, \\mathbf U_m) = 0 . Only the interface fluxes remain, and they cancel between neighbours for any conservative \\mathbf F^* . Entropy. Contract with the entropy variables \\mathbf w_i instead. Skew-symmetry gives \\sum_{i,m} (MS)_{im}\\, \\mathbf w_i^{\\mathsf T}\\mathbf F^{\\#}_{im} = \\tfrac12 \\sum_{i,m} (MS)_{im}\\, (\\mathbf w_i - \\mathbf w_m)^{\\mathsf T}\\mathbf F^{\\#}_{im} . If the two-point flux satisfies Tadmor’s condition (\\mathbf w_i - \\mathbf w_m)^{\\mathsf T}\\mathbf F^{\\#}_{im} = \\psi_i - \\psi_m , the right-hand side telescopes, by SBP, to boundary values of the entropy potential — exactly the discrete counterpart of \\int \\mathbf w^{\\mathsf T}\\partial_\\xi \\mathbf F = [\\psi] . No quadrature exactness was used, so the statement survives the aliasing of LGL collocation and, with metric averaging and discretely divergence-free metrics, holds on curved elements. With an entropy-conservative interface flux the semi-discrete scheme conserves \\sum w J\\,\\eta to round-off. Entropy decay needs a condition on the interface flux. What remains after the telescoping is, per interior face, (\\mathbf w^+ - \\mathbf w^-)^{\\mathsf T}\\mathbf F^* - (\\psi^+ - \\psi^-)\\cdot \\mathbf n . The total entropy decays if this is non-positive for every pair of states, i.e. if \\mathbf F^* is itself entropy stable . The llf flux is: it is the entropy-conservative flux minus \\tfrac12\\lambda(\\mathbf U^+ - \\mathbf U^-) , and (\\mathbf w^+ - \\mathbf w^-)^{\\mathsf T}(\\mathbf U^+ - \\mathbf U^-) \\ge 0 because the entropy is convex. So is es_roe , which subtracts a symmetric positive semi-definite matrix applied to \\mathbf w^+ - \\mathbf w^- and keeps the wave-by-wave dissipation of Roe’s solver. The default roe flux and hllc are not built on the entropy-conservative flux and no such inequality is proven for them; that their entropy rate is negative in the chart below is a measurement on the states of the study. The argument also covers only the inviscid terms on interior or periodic faces, with positive density and pressure; see which combinations are provably entropy stable . Entropy production rate by flux pairing Entropy conservation and stability Measured here: with an entropy-conservative volume flux and the dissipation-free interface flux the entropy rate is at most 8.1e-15 in magnitude; with the upwind interface fluxes it lies between -5.5e-02 and -1.8e-02.\nA naive implementation evaluates \\mathbf F^{\\#} for all (p+1)^2 ordered node pairs per line. Since \\mathbf F^{\\#} is symmetric and S_{ii} = 0 , only the p(p+1)/2 unordered pairs are needed. Basis1D.pair_tables() returns index vectors iL , iR and a matrix C so that the flux-differencing sum is one gather, one batched flux evaluation and one small matrix product per direction: f = phys . flux_volume ( jnp . take ( U , iL , axis = ax ), jnp . take ( U , iR , axis = ax ), 0.5 * ( jnp . take ( Ja [ a ], iL , axis = ax ) + jnp . take ( Ja [ a ], iR , axis = ax )), th ) JR = JR + apply_1d ( f , C , ax ) # C[i, q] = S[i, m] for pair q = {i, m} The same code runs for d = 2 and 3 : the array has d nodal axes and the loop runs over them. For p = 3 this more than halves the cost of the dominant kernel (6 pairs instead of 16).\nFor smooth solutions the design order of accuracy is p + 1 . Gyre’s verification studies show: p+1 for linear-in-effect problems and for steady inviscid manufactured solutions on Cartesian meshes; between p and p+1 for the strongly nonlinear unsteady isentropic vortex, and somewhat below p+1 on strongly warped coarse meshes — reduced rates of this kind are a known characteristic of Gauss–Lobatto collocation; in viscous, diffusion-dominated flow, reduced rates between p and p+1 with pressure the slowest variable (see order of accuracy ). Density error after convection Advected density wave","title":"Split-form DG spectral elements","url":"theory/dgsem.html"},{"audience":[],"chunks":[{"anchor":"","end":678,"heading":"","level":2,"start":0},{"anchor":"entropy-conservation-tadmor-s-condition","end":1332,"heading":"Entropy conservation: Tadmor's condition","level":3,"start":679},{"anchor":"the-ranocha-flux","end":2134,"heading":"The Ranocha flux","level":4,"start":1333},{"anchor":"a-logarithmic-mean-that-is-safe-to-differentiate","end":3048,"heading":"A logarithmic mean that is safe to differentiate","level":4,"start":2135},{"anchor":"interface-fluxes","end":4080,"heading":"Interface fluxes","level":2,"start":3049},{"anchor":"which-combinations-are-provably-entropy-stable","end":6304,"heading":"Which combinations are provably entropy stable","level":2,"start":4081},{"anchor":"properties-verified-for-every-interface-flux","end":10213,"heading":"Properties verified for every interface flux","level":3,"start":6305},{"anchor":"matrix-dissipation-in-entropy-variables","end":13119,"heading":"Matrix dissipation in entropy variables","level":4,"start":10214},{"anchor":"why-dissipation-only-at-interfaces","end":13713,"heading":"Why dissipation only at interfaces","level":3,"start":13120}],"headings":["Entropy conservation: Tadmor's condition","The Ranocha flux","A logarithmic mean that is safe to differentiate","Interface fluxes","Which combinations are provably entropy stable","Properties verified for every interface flux","Matrix dissipation in entropy variables","Why dissipation only at interfaces"],"keywords":["entropy conservative","entropy stable","Tadmor","Ranocha","Chandrashekar","Roe","HLLC","matrix dissipation","logarithmic mean"],"level":2,"max_level":4,"path":"theory/two-point-fluxes.md","section":"theory","section_title":"Mathematics & Numerical Methods","summary":"The volume terms use symmetric two-point fluxes that conserve entropy exactly; dissipation is added only at element interfaces through an upwind flux. A discrete entropy inequality is proven for the two interface fluxes built on the entropy-conservative flux (es_roe with matrix dissipation, llf with scalar dissipation); for the classical Roe flux, which is the default, and for HLLC entropy decay is measured, not proven.","text":"A two-point flux \\mathbf F^{\\#}(\\mathbf U_L, \\mathbf U_R; \\mathbf n) is a function of two states and a direction. Gyre requires it to be consistent , \\mathbf F^{\\#}(\\mathbf U,\\mathbf U;\\mathbf n) = \\mathbf F(\\mathbf U)\\cdot\\mathbf n , symmetric in its two states, and linear in \\mathbf n (so that metric-weighted directions can be passed directly). name entropy conservative kinetic-energy preserving use ranocha (default) yes yes, and pressure-equilibrium preserving production chandrashekar yes yes alternative kennedy_gruber no yes comparison central no no the standard DGSEM on elements with constant metric terms; a metric-split variant of it on curved elements ( details )\nA two-point flux is entropy conservative when, for every pair of states and every direction, (\\mathbf w_R - \\mathbf w_L)^{\\mathsf T}\\, \\mathbf F^{\\#}(\\mathbf U_L, \\mathbf U_R; \\mathbf n) = (\\psi_R - \\psi_L)\\cdot\\mathbf n, \\qquad \\boldsymbol\\psi = \\rho\\,\\mathbf u , with \\mathbf w the entropy variables . Inserted into the flux-differencing volume term this makes the element-wise entropy production telescope to its boundary ( proof sketch ). The property is tested pointwise for random admissible states in 2-D and 3-D ( test_entropy_conservation_condition ), and the non-conservative fluxes are tested to violate it, so the test cannot pass vacuously.\nWith arithmetic means \\{\\!\\{a\\}\\!\\} = \\tfrac12(a_L + a_R) and logarithmic means a^{\\ln} = (a_L - a_R)/(\\ln a_L - \\ln a_R) , \\begin{aligned} f_\\rho &= \\rho^{\\ln}\\, \\{\\!\\{u_n\\}\\!\\}, \\\\ \\mathbf f_{\\rho\\mathbf u} &= f_\\rho\\, \\{\\!\\{\\mathbf u\\}\\!\\} + \\{\\!\\{p\\}\\!\\}\\,\\mathbf n, \\\\ f_{\\rho E} &= f_\\rho \\left( \\tfrac12\\, \\mathbf u_L\\!\\cdot\\!\\mathbf u_R + \\frac{1}{(\\gamma-1)\\,(\\rho/p)^{\\ln}} \\right) + \\tfrac12\\left( p_L\\, u_{n,R} + p_R\\, u_{n,L} \\right), \\end{aligned} where u_n = \\mathbf u\\cdot\\mathbf n . Besides conserving entropy it preserves kinetic energy (the momentum flux is f_\\rho\\{\\!\\{\\mathbf u\\}\\!\\} plus a pressure term) and keeps a moving contact with uniform pressure and velocity exactly in equilibrium, which suppresses a known source of spurious pressure oscillations in split-form schemes.\nThe logarithmic mean is 0/0 when its arguments coincide — which happens on the diagonal of every flux-differencing sum and wherever the flow is uniform. Gyre evaluates a^{\\ln} = \\frac{a + b}{2 + f^2\\left(\\tfrac23 + f^2\\left(\\tfrac25 + \\tfrac27 f^2\\right)\\right)} \\quad\\text{if } f^2 < 10^{-4}, \\qquad f^2 = \\frac{a(a - 2b) + b^2}{a(a + 2b) + b^2}, and the closed form (b - a)/\\ln(b/a) otherwise. Automatic differentiation adds a subtlety: jnp.where differentiates both branches, and the derivative of the unselected closed form is nan at a = b , which would poison Jacobians and adjoints even though the value is correct. The implementation therefore feeds the unselected branch harmless substitute arguments (the “double-where” idiom). The unit test checks that forward- and reverse-mode derivatives are finite everywhere, equal \\tfrac12 on the diagonal, and that the function is smooth across the branch switch.\nAt element faces the two traces are genuinely different states and information must be upwinded. Gyre offers roe (default): Roe’s approximate Riemann solver — the arithmetic mean of the two physical fluxes minus Roe’s matrix dissipation — with Harten’s entropy fix on the acoustic waves (which also makes |\\lambda| differentiable through sonic points). It does not use the two-point volume flux; hllc : the classical three-wave HLLC solver. It does not use the two-point volume flux either; es_roe : the two-point volume flux plus Roe-type matrix dissipation applied to the jump of the entropy variables, \\tfrac12\\,\\mathbf R\\,|\\boldsymbol\\Lambda|\\,\\mathbf T\\,\\mathbf R\\T (\\mathbf w_R - \\mathbf w_L) : each wave is damped with its own speed, as in Roe’s solver, and the flux is entropy stable by construction; llf : the two-point volume flux plus scalar dissipation \\tfrac12\\lambda_{\\max}(\\mathbf U_R - \\mathbf U_L) : every wave is damped with the fastest speed; ec : the bare two-point flux, used only to test entropy conservation.\nThe statement “the discrete entropy cannot grow” is a theorem only under conditions, and the default configuration does not meet all of them. Interface flux Volume flux ranocha or chandrashekar Status ec entropy conserved to round-off proven, and measured llf entropy rate \\le 0 for every admissible nodal state proven (entropy-conservative flux plus a dissipation that is non-negative in entropy variables), and measured es_roe entropy rate \\le 0 for every admissible nodal state proven (entropy-conservative flux plus a symmetric positive semi-definite matrix acting on the jump of the entropy variables), and measured roe (default) entropy rate negative in the measured states measured only ; no proof hllc entropy rate negative in the measured states measured only ; no proof With the volume fluxes kennedy_gruber and central there is no entropy statement for any interface flux. What the proof for llf and es_roe covers, and what no proof in Gyre covers: It is semi-discrete (exact time integration), for the inviscid terms, on periodic or interior faces, and it assumes positive density and pressure at every node. Positivity is not enforced by the scheme; a state that leaves the admissible set ends the argument. (The steady solver’s line search rejects updates with non-positive density or pressure; a solve that finds no admissible update ends with the reason pseudo-time step collapsed .) Wall and far-field boundary conditions are imposed through the interface flux with a constructed exterior state; they carry no discrete entropy proof. The viscous terms (BR2 with gradients of velocity and temperature rather than of the entropy variables) and the shock-capturing viscosity carry no discrete entropy proof. The steady solver is an implicit pseudo-time iteration; nothing above bounds its iterates. So the default configuration ( ranocha volume flux, roe interface flux) gives entropy-conservative volume terms — aliasing inside an element cannot produce entropy of the wrong sign — and an interface dissipation that removed entropy in every state of the verification study, but not a proven entropy inequality. Choose surface_flux: llf when the proven property matters more than the lower dissipation of Roe.\nconsistency: \\mathbf F^*(\\mathbf U,\\mathbf U;\\mathbf n) = \\mathbf F(\\mathbf U)\\cdot\\mathbf n ; conservation: \\mathbf F^*(\\mathbf U_L,\\mathbf U_R;\\mathbf n) = -\\mathbf F^*(\\mathbf U_R,\\mathbf U_L;-\\mathbf n) , so what leaves one element enters its neighbour even though each face is evaluated from both sides; full upwinding in supersonic flow, under a condition on the wave-speed estimates , not on the Mach numbers of the two traces. For flow from left to right, HLLC returns the left flux when its left wave speed \\min(u_{n,L} - c_L,\\ \\hat u_n - \\hat c) \\ge 0 , and Roe when the Roe-averaged normal Mach number \\hat u_n/\\hat c \\ge 1.1 (hats denote the Roe average). Between 1 and 1.1 the slow acoustic eigenvalue \\hat u_n - \\hat c lies inside the band |\\lambda| < 0.1\\,\\hat c of Harten’s fix, where |\\lambda| is replaced by (\\lambda^2/\\delta + \\delta)/2 , so the Roe flux differs slightly from the upstream flux there. For nearly equal traces the Roe average is the trace state and the condition is the trace Mach number. For very different traces it is not: the Roe-averaged sound speed contains the jump of the tangential velocity, so two traces that are each supersonic along the normal can have a subsonic Roe average, and then neither flux is the upstream flux. The unit test of this property runs at Mach 2.5 with nearby traces. An evaluated example ( \\gamma = 1.4 , normal along x ; executed by tests/test_docs_content.py ): left trace \\rho = 1 , u = 1 , v = 3 at normal Mach number 2, right trace \\rho = 1.3 , u = 1.1 , v = -3 at normal Mach number 1.5. The Roe-averaged normal Mach number is 0.711, and the largest component of the difference to the left flux is 6.7\\cdot10^{-2} for roe and 5.4\\cdot10^{-2} for hllc . With v = \\pm 1 instead (Roe-averaged normal Mach number 1.36) both return the left flux to round-off. Entropy dissipation is not in this list of properties. For llf and es_roe it follows from the construction; for roe and hllc it is an observation: in the study below the discrete entropy rate was negative for one perturbed periodic state in 2-D and one in 3-D, each on a warped mesh. That is evidence for those states, not a guarantee for every state. Entropy rate and conservation defect Entropy conservation and stability dim volume flux interface flux d/dt ∫ η |Σ Rw| 2-D ranocha ec -5.9781e-15 4.4631e-14 2-D chandrashekar ec -8.0969e-15 4.8850e-14 2-D kennedy_gruber ec -5.9810e-04 4.2411e-14 2-D central ec -4.5667e-04 4.3077e-14 2-D ranocha llf -0.0550953 4.6629e-14 2-D ranocha es_roe -0.0183493 4.4631e-14 2-D chandrashekar es_roe -0.0183493 4.7296e-14 2-D ranocha roe -0.0183487 4.1300e-14 2-D ranocha hllc -0.0183448 4.0856e-14 3-D ranocha ec -3.6880e-15 1.1970e-14 3-D chandrashekar ec -3.5232e-15 1.1685e-14 3-D kennedy_gruber ec -0.00119758 2.6854e-15 3-D central ec -0.00744448 1.8180e-15 3-D ranocha llf -0.0543174 1.1796e-14 3-D ranocha es_roe -0.0177799 1.1602e-14 3-D chandrashekar es_roe -0.0177799 1.2261e-14 3-D ranocha roe -0.0178301 1.3600e-15 3-D ranocha hllc -0.0178184 8.1879e-16 The second table tests the interface fluxes alone. For a pair of states the entropy produced at a face is (\\mathbf w_R - \\mathbf w_L)\\T\\mathbf F^* - (\\psi_R - \\psi_L)\\cdot\\mathbf n ; an entropy-stable flux makes it non-positive for every pair, which is the property the proof rests on. The pairs are random and far apart (densities and pressures over a decade, Mach numbers up to about three), far outside what a resolved computation presents to a face. Entropy production of the interface flux alone, random state pairs (Ranocha two-point flux) Entropy conservation and stability dim interface flux pairs largest production pairs with production > 0 [%] 2-D llf 5000 -0.0571705 0 2-D es_roe 5000 -0.0301779 0 2-D roe 5000 1365.06 18.5 2-D hllc 5000 -0.0156571 0 3-D llf 5000 -0.228196 0 3-D es_roe 5000 -0.0411714 0 3-D roe 5000 812.676 19.18 3-D hllc 5000 -0.0401914 0\nLet \\mathbf A = \\partial(\\mathbf F\\cdot\\mathbf n)/\\partial\\mathbf U with eigenvalues \\boldsymbol\\Lambda = \\operatorname{diag}(u_n - a,\\ u_n,\\dots,u_n,\\ u_n + a) and right eigenvectors \\mathbf R . Roe’s solver subtracts \\tfrac12\\mathbf R|\\boldsymbol\\Lambda|\\mathbf R^{-1}(\\mathbf U_R - \\mathbf U_L) from the mean of the two fluxes; nothing guarantees the sign of the entropy this produces. Barth’s observation is that the eigenvectors can be scaled so that \\mathbf R\\,\\mathbf T\\,\\mathbf R\\T = \\partial\\mathbf U/\\partial\\mathbf w with a positive diagonal \\mathbf T . Writing the jump in entropy variables then gives a dissipation \\mathbf D = \\mathbf R\\,|\\boldsymbol\\Lambda|\\,\\mathbf T\\,\\mathbf R\\T\\,(\\mathbf w_R - \\mathbf w_L), \\qquad (\\mathbf w_R - \\mathbf w_L)\\T\\mathbf D = \\sum_k |\\lambda_k|\\,T_k\\,\\big(\\mathbf r_k\\T(\\mathbf w_R - \\mathbf w_L)\\big)^2 \\ \\ge\\ 0 , for any positive mean state at which \\mathbf R , \\boldsymbol\\Lambda and \\mathbf T are evaluated. Subtracting \\tfrac12\\mathbf D from an entropy-conservative flux therefore gives a flux whose entropy production at a face is non-positive: the same argument as for llf , with a matrix in place of the scalar \\lambda_{\\max} . For nearby states \\mathbf w_R - \\mathbf w_L \\approx (\\partial\\mathbf w/\\partial\\mathbf U)(\\mathbf U_R - \\mathbf U_L) and \\mathbf D tends to Roe’s |\\mathbf A|(\\mathbf U_R - \\mathbf U_L) . Gyre uses the mean state of Winters, Derigs, Gassner and Walch: logarithmic means \\rho^{\\ln} and \\beta^{\\ln} of the density and of \\beta = \\rho/(2p) , the arithmetic mean of the velocity, \\hat p = \\{\\!\\{\\rho\\}\\!\\}/(2\\{\\!\\{\\beta\\}\\!\\}) , \\hat a^2 = \\gamma\\hat p/\\rho^{\\ln} , \\overline{u^2} = 2\\abs{\\{\\!\\{\\mathbf u\\}\\!\\}}^2 - \\{\\!\\{\\abs{\\mathbf u}^2\\}\\!\\} , \\hat h = \\gamma/\\big(2\\beta^{\\ln}(\\gamma - 1)\\big) + \\tfrac12\\overline{u^2} , and \\mathbf r_\\pm = \\begin{pmatrix} 1 \\\\ \\{\\!\\{\\mathbf u\\}\\!\\} \\pm \\hat a\\,\\mathbf n \\\\ \\hat h \\pm \\hat a\\,u_n \\end{pmatrix},\\quad \\mathbf r_0 = \\begin{pmatrix} 1 \\\\ \\{\\!\\{\\mathbf u\\}\\!\\} \\\\ \\tfrac12\\overline{u^2} \\end{pmatrix},\\quad \\mathbf r_{t_k} = \\begin{pmatrix} 0 \\\\ \\mathbf t_k \\\\ \\{\\!\\{\\mathbf u\\}\\!\\}\\cdot\\mathbf t_k \\end{pmatrix}, \\qquad \\mathbf T = \\operatorname{diag}\\Big(\\frac{\\rho^{\\ln}}{2\\gamma},\\ \\frac{(\\gamma-1)\\rho^{\\ln}}{\\gamma},\\ \\hat p,\\dots,\\ \\frac{\\rho^{\\ln}}{2\\gamma}\\Big). The tangential waves are summed without constructing tangent vectors, using \\sum_k \\mathbf t_k\\mathbf t_k\\T = \\mathbf I - \\mathbf n\\mathbf n\\T , so one kernel serves two and three dimensions. The acoustic eigenvalues carry the same Harten fix as roe , which only increases |\\lambda| and so does not affect the inequality. With this mean state a contact discontinuity at rest is left exactly alone (no mass flux through it), where llf diffuses it with the speed of sound; unit tests check the inequality for random pairs, the limit |\\mathbf A| , the symmetry of the operator and the stationary contact.\nInside an element the polynomial resolves the solution, and adding dissipation there would cost accuracy. Jumps at interfaces measure under-resolution: they are \\mathcal O(h^{p+1}) for resolved features and large only where the mesh cannot represent the solution. Interface dissipation proportional to the jump is therefore automatically high-order where the solution is smooth and stabilising where it is not, while the entropy-conservative volume term guarantees that the volume terms — where aliasing errors arise — neither produce nor destroy entropy (given positive density and pressure).","title":"Two-point and interface fluxes","url":"theory/two-point-fluxes.html"},{"audience":[],"chunks":[{"anchor":"","end":508,"heading":"","level":2,"start":0},{"anchor":"free-stream-preservation","end":1475,"heading":"Free-stream preservation","level":2,"start":509},{"anchor":"how-the-metrics-are-computed","end":2614,"heading":"How the metrics are computed","level":3,"start":1476},{"anchor":"geometry-is-data-not-configuration","end":4424,"heading":"Geometry is data, not configuration","level":3,"start":2615},{"anchor":"the-finite-volume-level-p-0","end":5080,"heading":"The finite-volume level p = 0","level":4,"start":4425},{"anchor":"mesh-generators","end":6043,"heading":"Mesh generators","level":3,"start":5081},{"anchor":"meshes-from-files","end":6943,"heading":"Meshes from files","level":3,"start":6044}],"headings":["Free-stream preservation","How the metrics are computed","Geometry is data, not configuration","The finite-volume level p = 0","Mesh generators","Meshes from files"],"keywords":["metric identities","free-stream preservation","geometric conservation law","isoparametric","shape derivative"],"level":2,"max_level":4,"path":"theory/curvilinear-metrics.md","section":"theory","section_title":"Mathematics & Numerical Methods","summary":"Elements are curved with the same polynomial technology as the solution. Metric terms are computed so that a uniform flow stays exactly uniform on any mesh, and they are evaluated inside the differentiated code so that shape derivatives come for free.","text":"High-order accuracy near curved walls needs curved elements: a straight-sided mesh limits the whole solution to second order regardless of p , and its kinks generate spurious entropy. Each Gyre element carries (p_g+1)^d geometry nodes on an LGL lattice (degree p_g , default 4) that define its polynomial mapping \\mathbf x(\\boldsymbol\\xi) . Geometry nodes on shared faces are shared, so any smooth displacement of the global node array keeps the mesh watertight — which is how surface design variables enter.\nOn a curved element the divergence of a constant flux is discretised as \\sum_a \\mathbf D_a\\,(J\\mathbf a^a)\\cdot\\mathbf F_\\text{const} . It vanishes only if the discrete metric identities \\sum_{a=1}^{d} \\mathbf D_a\\,(J a^a_b) = 0 \\qquad (b = 1,\\dots,d) hold. If they do not, a uniform flow generates spurious waves at every curved element. Gyre satisfies the identities to round-off in 2-D and 3-D, including with viscous terms: Uniform-flow residual on warped meshes Free-stream preservation on curved meshes dim elements p geometry degree viscous min J / max J max |Rw| surface closure 2-D 6x5 3 4 no 0.451047 1.3492e-15 3.0531e-16 2-D 6x5 3 4 yes 0.451047 1.3368e-15 3.0531e-16 2-D 4x4 5 4 no 0.453556 2.8560e-15 4.9960e-16 2-D 4x4 5 4 yes 0.453556 2.9091e-15 4.9960e-16 3-D 3x3x3 2 2 no 0.276159 2.6316e-16 3.4694e-17 3-D 3x3x3 2 2 yes 0.276159 2.6316e-16 3.4694e-17 3-D 3x3x3 3 3 no 0.417089 3.2526e-16 6.9823e-17 3-D 3x3x3 3 3 yes 0.417089 3.2823e-16 6.9823e-17\nThe geometry is first interpolated from the geometry nodes to the solution nodes (degree p ), then differentiated with the same \\mathbf D used for the solution. Two dimensions. The cross-product form is already divergence-free discretely: J\\mathbf a^1 = (y_\\eta,\\,-x_\\eta), \\qquad J\\mathbf a^2 = (-y_\\xi,\\; x_\\xi), \\qquad J = x_\\xi y_\\eta - x_\\eta y_\\xi . Three dimensions. Products such as y_\\eta z_\\zeta are of degree 2p and their derivatives no longer commute after interpolation, so the cross-product form violates the identities. Gyre uses Kopriva’s conservative curl form, J a^i_n = -\\hat{\\mathbf x}_i\\cdot\\nabla_\\xi \\times \\left( I^p\\!\\left( X_l\\,\\nabla_\\xi X_m \\right) \\right), \\qquad (n, m, l)\\ \\text{cyclic}, where I^p is nodal interpolation at degree p . Being the discrete curl of a polynomial, each contravariant vector is discretely divergence-free by construction. Outward face normals and surface Jacobians are the face values of the same arrays: \\mathbf n\\, s_J = \\pm J\\mathbf a^a on the two faces normal to \\xi_a , which makes the normals of two elements sharing a face exactly opposite — a requirement for conservation.\nMetric terms are not precomputed constants: Discretization.geometry(X) is an ordinary JAX function of the global node coordinates \\mathbf X . The residual is \\mathbf R(\\mathbf U, \\mathbf X, \\theta) and the chain \\text{surface displacement } \\mathbf d \\;\\xrightarrow{\\text{deformation}}\\; \\mathbf X(\\mathbf d) \\;\\xrightarrow{\\text{metrics}}\\; (J, J\\mathbf a, \\mathbf n, s_J) \\;\\xrightarrow{\\text{operator}}\\; \\mathbf R,\\, J is differentiated end-to-end by reverse-mode AD. There is no separate “mesh sensitivity” code to keep consistent with the solver. The verification study on mesh deformation checks the derivative of a geometric integral through this chain against finite differences. Passed Differentiable mesh deformation 9 of 9 checks passed · 2.45 s · quick suite Surface displacements are propagated to all high-order geometry nodes by inverse-distance weighting. The study deforms a NACA 0012 O-mesh with a thickness bump of increasing amplitude and checks that elements stay valid, that wall nodes follow the prescription exactly while far-field nodes stay fixed, and that the derivative of a geometric quantity through deformation and metric evaluation matches finite differences. scale 0.0: mesh valid (min J > 0) 4.33e-05 expected > 0 pass scale 0.0: surface/far-field prescription defect 0 expected < 1e-13 pass scale 0.5: mesh valid (min J > 0) 4.33e-05 expected > 0 pass scale 0.5: surface/far-field prescription defect 0 expected < 1e-13 pass scale 1.0: mesh valid (min J > 0) 4.33e-05 expected > 0 pass scale 1.0: surface/far-field prescription defect 0 expected < 1e-13 pass scale 2.0: mesh valid (min J > 0) 4.33e-05 expected > 0 pass scale 2.0: surface/far-field prescription defect 0 expected < 1e-13 pass d(weighted area)/d(surface displacement): AD vs FD 1.08e-08 expected < 1e-7 pass\nThe coarsest p-multigrid level uses one node per element. Differentiating a one-point “polynomial” gives no metrics, so this level takes its geometry from the degree- p_g description by exact reduction: the cell Jacobian is the mean \\sum w J / 2^d , face normals are the face integrals \\int \\mathbf n\\,\\mathrm dS divided by the reference face area. For the inviscid terms the resulting scheme is a first-order finite-volume method on the curved cell whose faces close exactly (geometric conservation), verified in test_finite_volume_geometry_reduction . The viscous terms keep only the face-normal two-point difference at this level ( penalty parameter ).\nAll built-in generators evaluate a smooth mapping on the LGL lattice, so elements are curved to degree p_g : generator topology notes airfoil_omesh O-mesh around a NACA 4-digit section or a spline fit of coordinate data sharp trailing edge and leading edge at element vertices; wall-normal lines from a smoothed normal field; geometric wall-normal growth from a first cell of the requested height annulus O-mesh between two circles cylinder flows bump_channel channel with a Gaussian bump smooth-wall verification box Cartesian block, 2-D or 3-D optional smooth warp that preserves periodicity A detail worth knowing: classical cosine clustering along the chord has zero parametric speed at the trailing edge, which makes the Jacobian vanish at the trailing-edge vertex. The aerofoil generator uses x = \\sin^2(\\tfrac{c\\pi}{2} v)/\\sin^2(\\tfrac{c\\pi}{2}) with c < 1 , which keeps the smooth \\sqrt{x} behaviour at the nose but a finite tangent at the trailing edge.\nA mesh read from a file ( mesh.generator: file ) becomes the same global node array and element lattice as a generated one, so everything on this page applies unchanged. For a Gmsh file the reader maps Gmsh’s node ordering of each element onto the tensor-product lattice, re-orients left-handed elements, and interpolates the equispaced Gmsh nodes to the Legendre–Gauss–Lobatto distribution of the same degree (the identity up to degree 2). Lattice nodes are numbered topologically (a vertex by its file node, an edge or face node by the vertices of that edge or face and its position on it), so neighbouring elements share nodes exactly and nodes that merely coincide in space, at a slit or a periodic pair, stay distinct. Elements may meet in any relative orientation. A mesh is refused if the Jacobian determinant is not positive at every geometry node. The imported-mesh study tests these steps.","title":"Curvilinear elements and metric terms","url":"theory/curvilinear-metrics.html"},{"audience":[],"chunks":[{"anchor":"","end":269,"heading":"","level":3,"start":0},{"anchor":"formulation","end":1458,"heading":"Formulation","level":3,"start":270},{"anchor":"why-this-pairing-is-symmetric","end":3408,"heading":"Why this pairing is symmetric","level":4,"start":1459},{"anchor":"penalty-parameter","end":4720,"heading":"Penalty parameter","level":3,"start":3409},{"anchor":"compact-stencil","end":6816,"heading":"Compact stencil","level":3,"start":4721},{"anchor":"what-accuracy-to-expect","end":8027,"heading":"What accuracy to expect","level":2,"start":6817},{"anchor":"why-the-rate-is-reduced-a-hypothesis-with-the-evidence-for-and-against","end":9545,"heading":"Why the rate is reduced: a hypothesis, with the evidence for and against","level":3,"start":8028}],"headings":["Formulation","Why this pairing is symmetric","Penalty parameter","Compact stencil","What accuracy to expect","Why the rate is reduced: a hypothesis, with the evidence for and against"],"keywords":["BR2","lifting operator","interior penalty","adjoint consistency","viscous flux"],"level":3,"max_level":4,"path":"theory/viscous-br2.md","section":"theory","section_title":"Mathematics & Numerical Methods","summary":"Viscous fluxes use the second scheme of Bassi and Rebay with lifting operators evaluated on Gauss–Lobatto nodes. The discretization is compact (face neighbours only) and symmetric, which the implicit solver and the adjoint both rely on.","text":"A discontinuous solution has no gradient at element faces, and using only element-interior gradients gives an inconsistent — and unstable — diffusion operator. Gyre uses the BR2 scheme: the gradient is corrected by lifting the solution jumps at faces into the elements.\nLet \\mathbf W = (\\mathbf u, T) be the gradient variables and \\mathbf G = \\tfrac1J \\sum_a (J\\mathbf a^a)\\,\\mathbf D_a \\mathbf W the element-local (strong, non-conservative) gradient. For each face f of an element define the face lifting, non-zero only at that face’s nodes: \\mathbf r_f = \\frac{s_J}{w_\\text{end}\\, J}\\,(\\mathbf W^* - \\mathbf W^-)\\otimes\\mathbf n, \\qquad \\mathbf W^* = \\begin{cases} \\tfrac12(\\mathbf W^- + \\mathbf W^+) & \\text{interior face} \\\\ \\mathbf W_b & \\text{boundary face.} \\end{cases} The scheme then uses two different gradients: \\underbrace{\\mathbf q = \\mathbf G + \\sum_f \\mathbf r_f}_{\\text{volume flux } \\mathbf F_v(\\mathbf U,\\mathbf q)} \\qquad\\qquad \\underbrace{\\mathbf q_f = \\mathbf G + \\eta\\,\\mathbf r_f}_{\\text{face flux } \\tfrac12\\left[\\mathbf F_v(\\mathbf U^-, \\mathbf q_f^-) + \\mathbf F_v(\\mathbf U^+, \\mathbf q_f^+)\\right]\\cdot\\mathbf n} and the divergence of the volume flux is taken in weak form with \\mathbf D^w = \\mathbf M^{-1} \\mathbf D^{\\mathsf T}\\mathbf M , the adjoint of \\mathbf D in the quadrature inner product. Using only the face’s own lifting in the face flux (rather than the full \\mathbf q , as BR1 does) is what keeps the stencil compact.\nBecause the LGL mass matrix is diagonal, the lifting relation \\int_K \\mathbf r_f\\cdot\\boldsymbol\\tau = \\int_f (\\mathbf W^* - \\mathbf W^-)\\,\\mathbf n\\cdot\\boldsymbol\\tau is satisfied pointwise by the formula above — no linear solve. Summing the volume term over the two elements adjacent to a face gives, for a linear flux \\mathbf F_v = \\mathbf K\\mathbf q , \\sum_K (\\mathbf K\\,\\mathbf R([\\mathbf W]),\\, \\mathbf G(\\mathbf v))_K = -\\sum_f \\langle [\\mathbf W],\\, \\{\\!\\{\\mathbf K^{\\mathsf T}\\mathbf G(\\mathbf v)\\}\\!\\}\\cdot\\mathbf n\\rangle_f , which is the mirror image of the consistency term -\\sum_f\\langle\\{\\!\\{\\mathbf K\\mathbf G(\\mathbf W)\\}\\!\\}\\cdot\\mathbf n, [\\mathbf v]\\rangle_f produced by the face flux, and the penalty term \\eta\\sum_f\\langle\\mathbf r_f([\\mathbf W]),\\mathbf r_f([\\mathbf v])\\rangle is symmetric positive semi-definite. The discrete operator is therefore the symmetric BR2 bilinear form evaluated with LGL quadrature. Symmetry is not cosmetic: it is the discrete statement of adjoint consistency , the property that lets functionals converge faster than the solution and makes the output error estimate sharp. The requirement that the gradient ( \\mathbf D , strong, non-conservative) and divergence ( \\mathbf D^w , weak) be exact adjoints is why Gyre mixes the two forms rather than using one throughout. Relative asymmetry and smallest eigenvalue Structure of the BR2 viscous operator warp boundaries p asym. (heat) asym. (stress) min eig (heat) min eig (stress) 0 periodic 2 2.7046e-16 2.0254e-16 -5.4470e-16 6.5560e-16 0 periodic 3 2.2839e-16 2.5421e-16 1.1123e-16 -1.8926e-15 0 Dirichlet 2 7.9058e-17 1.1709e-16 0.0423156 0.0252442 0 Dirichlet 3 6.5254e-17 1.4417e-16 0.0246366 0.0147011 0.05 periodic 2 1.9043e-16 2.8210e-16 1.5375e-15 5.5369e-17 0.05 periodic 3 2.3864e-16 2.9273e-16 1.1192e-15 -5.0847e-16 0.05 Dirichlet 2 5.4250e-17 1.5964e-16 0.0417777 0.0247796 0.05 Dirichlet 3 1.3399e-16 1.6369e-16 0.0242548 0.0143994\nThe coercivity proofs for BR2 (Brezzi, Manzini, Marini, Pietra and Russo 2000; Arnold, Brezzi, Cockburn and Marini 2002) need \\eta strictly greater than the number of faces of an element; at equality the argument gives only a non-negative, not a coercive, form. Gyre’s default is the customary borderline value \\eta = 2d (4 for quadrilaterals, 6 for hexahedra), which lies on the edge of the proven range, not inside it. The proofs are also for exact integration, whereas Gyre uses Gauss–Lobatto quadrature. No instability has been observed with the default in the tests and studies; the value is an input of the discretization ( Discretization(..., eta_br2=...) in Python) for anyone who wants to be inside the proven range. At the finite-volume level p = 0 there is no interior gradient and the lifting alone carries the viscous flux. With \\eta = 2 it is the two-point difference along the face normal: exact on uniform spacing, too large by (h_1 + h_2)^2 / (4 h_1 h_2) between cells of widths h_1 and h_2 . The gradient components tangential to a face, and with them the cross-derivative terms of the stress, are absent. The p = 0 viscous operator is therefore a p-multigrid preconditioner level, not a consistent Navier–Stokes discretization, and cannot be selected as the solution degree of a viscous case.\nThe residual of an element depends only on itself and its face neighbours. This is verified by inspecting the sparsity of a dense AD Jacobian, and it is what allows the element-diagonal blocks used by the preconditioner to be extracted exactly with the split-argument trick . Passed Structure of the BR2 viscous operator 17 of 17 checks passed · 116.9 s · quick suite At a quiescent state the energy–energy and momentum–momentum blocks of the weak-form Jacobian are exactly the discrete heat-conduction and viscous-stress operators. Their symmetry (the hallmark of an adjoint-consistent diffusion discretization) and positive semi-definiteness are checked on Cartesian and warped meshes with periodic and Dirichlet boundaries, and the Jacobian sparsity pattern is checked to be compact (face neighbours only), which element-block preconditioning relies on. warp=0.0, periodic, p=2: asymmetry 2.70e-16 expected < 1e-12 pass warp=0.0, periodic, p=2: smallest eigenvalue -5.45e-16 expected >= -1e-12 pass warp=0.0, periodic, p=3: asymmetry 2.54e-16 expected < 1e-12 pass warp=0.0, periodic, p=3: smallest eigenvalue -1.89e-15 expected >= -1e-12 pass warp=0.0, Dirichlet, p=2: asymmetry 1.17e-16 expected < 1e-12 pass warp=0.0, Dirichlet, p=2: smallest eigenvalue 0.02524 expected >= -1e-12 pass warp=0.0, Dirichlet, p=3: asymmetry 1.44e-16 expected < 1e-12 pass warp=0.0, Dirichlet, p=3: smallest eigenvalue 0.0147 expected >= -1e-12 pass warp=0.05, periodic, p=2: asymmetry 2.82e-16 expected < 1e-12 pass warp=0.05, periodic, p=2: smallest eigenvalue 5.54e-17 expected >= -1e-12 pass warp=0.05, periodic, p=3: asymmetry 2.93e-16 expected < 1e-12 pass warp=0.05, periodic, p=3: smallest eigenvalue -5.08e-16 expected >= -1e-12 pass warp=0.05, Dirichlet, p=2: asymmetry 1.60e-16 expected < 1e-12 pass warp=0.05, Dirichlet, p=2: smallest eigenvalue 0.02478 expected >= -1e-12 pass warp=0.05, Dirichlet, p=3: asymmetry 1.64e-16 expected < 1e-12 pass warp=0.05, Dirichlet, p=3: smallest eigenvalue 0.0144 expected >= -1e-12 pass element couplings outside the face-neighbour stencil 0 expected == 0 pass\nThe diffusion operator on its own reaches the design rate: heat conduction in a quiescent gas converges at p+1 , and an independent one-dimensional implementation of exactly this formulation converges at p+1 for every degree ( study ). For the coupled compressible Navier–Stokes system in a diffusion-dominated regime the state converges at a reduced rate between p and p+1 , with pressure the slowest variable. Convection-dominated flows recover approximately p+1 . The reduced rate is a measurement; its cause has not been established. Observed orders on the finest mesh pair Manufactured solutions: Navier–Stokes regime p L2 error (finest) state velocity pressure temperature heat conduction (quiescent) 2 3.0899e-05 3.60088 3.6433 3.60046 3.05238 heat conduction (quiescent) 3 9.7099e-07 3.99849 4.94591 3.99795 4.58993 diffusion dominated, Re = 10 2 4.9402e-04 2.33256 2.72687 2.33663 3.1517 diffusion dominated, Re = 10 3 2.7784e-06 3.79396 4.19354 3.79776 4.61291 diffusion dominated, no mean flow, Re = 10 2 0.00135697 1.99132 2.57181 1.99047 2.70647 convection dominated, Re = 1000 2 8.6201e-05 2.852 2.99477 2.84167 2.95746 convection dominated, Re = 1000 3 8.8491e-07 3.91862 3.98075 3.90575 4.11096\nHypothesis. When diffusion dominates, the momentum and energy equations behave like an equal-order discretization of a Stokes-like system, in which the pressure-like variables are controlled only through the velocity divergence and lose one order. Evidence for. The diffusion operator alone reaches p+1 : heat conduction in a quiescent gas, and an independent one-dimensional BR2 model that includes the nonlinear work term. The viscous operator is symmetric to round-off, so the loss is not an adjoint-inconsistency of the implementation. Pressure is the slowest variable and the loss shrinks as convection takes over, which is the pattern the hypothesis predicts. Evidence against, and what is missing. In the compressible equations solved here pressure is a thermodynamic variable, not a Lagrange multiplier of a divergence constraint. The manufactured solution runs at Mach 0.5, far from the incompressible limit in which the Stokes argument is exact. Nothing in the repository isolates the mechanism. Three other candidates are present in this very discretization and have not been excluded: under-integration of the BR2 form by Gauss–Lobatto quadrature, the penalty sitting exactly on the coercivity bound (see above), and the non-conservative gradient in (\\mathbf u, T) . The studies that would discriminate — the rate against Mach number, Gauss against Gauss–Lobatto quadrature, a sweep of \\eta — have not been run. Until they are, treat the rates in the table as the fact and the explanation as a conjecture.","title":"Viscous terms — the BR2 scheme","url":"theory/viscous-br2.html"},{"audience":[],"chunks":[{"anchor":"","end":1540,"heading":"","level":2,"start":0},{"anchor":"direct-boundary-states-and-adjoint-consistency","end":2572,"heading":"Direct boundary states and adjoint consistency","level":3,"start":1541},{"anchor":"functionals-reuse-the-residual-s-fluxes","end":3084,"heading":"Functionals reuse the residual's fluxes","level":3,"start":2573},{"anchor":"no-slip-walls","end":3929,"heading":"No-slip walls","level":3,"start":3085},{"anchor":"boundary-conditions-under-differentiation","end":5390,"heading":"Boundary conditions under differentiation","level":4,"start":3930}],"headings":["Direct boundary states and adjoint consistency","Functionals reuse the residual's fluxes","No-slip walls","Boundary conditions under differentiation"],"keywords":["boundary conditions","adjoint consistency","slip wall","no-slip wall","far field"],"level":2,"max_level":4,"path":"theory/boundary-conditions.md","section":"theory","section_title":"Mathematics & Numerical Methods","summary":"Boundary conditions are imposed weakly through the same numerical fluxes as interior faces. Walls use a boundary state rather than a ghost-cell Riemann problem, which keeps force functionals adjoint consistent.","text":"Every boundary face is treated like an interior face whose exterior data are supplied by a boundary function of the interior trace \\mathbf U^- , the outward normal and the flow parameters. Each boundary function returns three states: \\mathbf U_L, \\mathbf U_R — handed to the interface Riemann flux; \\mathbf U_b — the boundary state for viscous fluxes and gradient lifting (or none). condition inviscid flux viscous treatment farfield Riemann flux between \\mathbf U^- and the free stream interior gradient, no lifting slip_wall , symmetry \\mathbf n\\cdot\\mathbf F(\\mathbf U_\\Gamma) , normal momentum removed no viscous flux slip_wall_mirror Riemann flux against the mirrored state no viscous flux noslip_adiabatic \\mathbf n\\cdot\\mathbf F(\\mathbf U_\\Gamma) , zero velocity \\mathbf u = 0 lifted, zero heat flux noslip_isothermal as above with wall temperature t_wall \\mathbf u = 0 , T = T_w lifted pressure_outlet \\mathbf n\\cdot\\mathbf F(\\mathbf U_\\Gamma) with p = p_\\text{back} (interior p if supersonic) interior gradient total_inlet \\mathbf n\\cdot\\mathbf F(\\mathbf U_\\Gamma) from total pressure/temperature and flow angle, interior static pressure interior gradient Three conditions read data from the flow parameters: noslip_isothermal the wall temperature t_wall , pressure_outlet the back pressure p_back and total_inlet the total pressure and temperature pt_in , tt_in , all as ratios to the free-stream static values. A condition whose data are missing is rejected with an error that names the boundary group, the condition and the key.\nFor walls and for the internal-flow inlet/outlet conditions Gyre sets \\mathbf U_L = \\mathbf U_R = \\mathbf U_\\Gamma(\\mathbf U^-) . Any consistent numerical flux then returns exactly the physical flux of the boundary state, \\mathbf n\\cdot\\mathbf F(\\mathbf U_\\Gamma) , with no dissipation. For a slip wall \\mathbf U_\\Gamma = \\left(\\rho,\\; \\rho\\mathbf u - (\\rho\\mathbf u\\cdot\\mathbf n)\\,\\mathbf n,\\; \\rho E\\right), \\qquad \\mathbf n\\cdot\\mathbf F(\\mathbf U_\\Gamma) = (0,\\; p_\\Gamma\\,\\mathbf n,\\; 0), so the wall flux is a pure pressure force with p_\\Gamma = p(\\mathbf U_\\Gamma) . Following Hartmann and Leicht, a boundary flux of the form \\mathbf n\\cdot\\mathbf F(\\mathbf U_\\Gamma(\\mathbf U^-)) , combined with functionals evaluated from the same boundary flux, yields an adjoint-consistent discretization: the discrete adjoint is a consistent discretization of the continuous adjoint problem including its boundary conditions. The mirrored-ghost-state alternative is available for comparison but is not adjoint consistent in this sense.\nForces are not recomputed from interpolated pressure. SurfaceOps.face_forces calls the residual kernel with faces=True and integrates the momentum components of the numerical boundary flux — inviscid minus viscous — that the residual itself used. Two consequences: the force is discretely conservative: summing the surface loads and the far-field momentum flux reproduces the residual sum exactly; the functional is adjoint consistent, a prerequisite for the superconvergence measured in the output-error study.\nThe boundary state keeps density and total energy and sets the velocity to zero, \\mathbf U_\\Gamma = (\\rho, \\mathbf 0, \\rho E) (adiabatic), or replaces the energy by \\rho\\,c_v T_w (isothermal). Velocity (and wall temperature) enter the BR2 lifting as Dirichlet data, \\mathbf W^* = \\mathbf W(\\mathbf U_\\Gamma) ; the viscous boundary flux is \\mathbf F_v(\\mathbf U_\\Gamma, \\mathbf G + \\eta\\,\\mathbf r_f)\\cdot\\mathbf n , with the energy component removed for adiabatic walls (zero heat flux and zero velocity leave no energy transfer). The numerical energy flux through an adiabatic wall is therefore zero by construction, for any state. The cylinder validation checks instead that the wall heat conduction of the converged solution, the flux that is removed, decreases between the two highest polynomial degrees; it vanishes for the exact solution.\nBoundary functions are ordinary JAX code of (\\mathbf U^-, \\mathbf n, \\mathbf x, \\theta) . Their dependence on flow parameters (the free-stream state in farfield , the back pressure in pressure_outlet ) and on geometry (through \\mathbf n ) is therefore differentiated together with everything else. The linearization study verifies JVPs, VJPs and the block preconditioner for every wall type, the mirrored slip wall ( slip_wall_mirror ) with each interface flux included, for a channel mixing four different conditions and for an inlet/outlet channel ( total_inlet , pressure_outlet , symmetry , slip_wall_mirror ). Linearization defects Exact linearization by automatic differentiation configuration p DOF JVP vs FD (rel.) dot-product (rel.) |B⁻¹ J_ee - I| split residual defect Euler, slip wall 2 432 5.3652e-10 7.4656e-16 6.7502e-14 0 NS, adiabatic wall 2 432 3.7139e-10 7.5418e-15 1.2397e-14 0 NS, isothermal wall 2 432 3.1571e-10 1.0274e-13 1.6348e-14 0 NS, mixed channel 3 576 5.2611e-10 4.3341e-16 1.4949e-14 0 Euler, mirrored slip wall 2 432 1.9949e-10 1.0578e-13 5.6843e-14 0 Euler, mirrored slip wall, HLLC flux 2 432 5.6037e-10 7.5694e-14 2.4869e-14 0 Euler, mirrored slip wall, LLF flux 2 432 1.3770e-10 1.4703e-15 1.1713e-14 0 Euler, inlet/outlet channel 2 324 2.1088e-10 1.2508e-13 5.3846e-15 0 Euler, mirrored slip wall, ES-Roe flux 2 432 1.8848e-10 1.3925e-14 3.8192e-14 0 NS, adiabatic wall, ES-Roe flux 2 432 3.0017e-10 2.9248e-15 1.6784e-14 0","title":"Boundary conditions","url":"theory/boundary-conditions.html"},{"audience":[],"chunks":[{"anchor":"","end":334,"heading":"","level":2,"start":0},{"anchor":"pseudo-transient-continuation","end":1090,"heading":"Pseudo-transient continuation","level":2,"start":335},{"anchor":"globalisation","end":2647,"heading":"Globalisation","level":3,"start":1091},{"anchor":"matrix-free-krylov-solver","end":2648,"heading":"Matrix-free Krylov solver","level":2,"start":2648},{"anchor":"linearize-once-apply-many-times","end":3565,"heading":"Linearize once, apply many times","level":3,"start":2649},{"anchor":"preconditioning-without-assembling-a-matrix","end":3566,"heading":"Preconditioning without assembling a matrix","level":2,"start":3566},{"anchor":"exact-element-blocks-from-split-arguments","end":4531,"heading":"Exact element blocks from split arguments","level":3,"start":3567},{"anchor":"p-multigrid","end":6240,"heading":"p-multigrid","level":3,"start":4532},{"anchor":"line-smoothing-on-anisotropic-meshes","end":10838,"heading":"Line smoothing on anisotropic meshes","level":3,"start":6241},{"anchor":"single-precision-blocks","end":13366,"heading":"Single-precision blocks","level":3,"start":10839},{"anchor":"line-smoothing-in-detail-lines-colours-factors","end":17971,"heading":"Line smoothing in detail: lines, colours, factors","level":4,"start":13367},{"anchor":"cost-model-and-its-consequence","end":19267,"heading":"Cost model and its consequence","level":4,"start":17972}],"headings":["Pseudo-transient continuation","Globalisation","Matrix-free Krylov solver","Linearize once, apply many times","Preconditioning without assembling a matrix","Exact element blocks from split arguments","p-multigrid","Line smoothing on anisotropic meshes","Single-precision blocks","Line smoothing in detail: lines, colours, factors","Cost model and its consequence"],"keywords":["Newton-Krylov","pseudo-transient continuation","GMRES","p-multigrid","block Jacobi","line smoother","line-implicit","matrix-free","mixed precision","single precision"],"level":2,"max_level":4,"path":"theory/implicit-solver.md","section":"theory","section_title":"Mathematics & Numerical Methods","summary":"Steady states are found by pseudo-transient Newton–Krylov iteration. The Jacobian is never formed — its action comes from automatic differentiation — and it is preconditioned by p-multigrid with element-block-Jacobi smoothing whose blocks are also obtained by AD.","text":"The steady problem is \\mathbf R_w(\\mathbf U) = \\mathbf 0 , where \\mathbf R_w = (wJ)\\,\\mathbf R is the weak-form residual (the pointwise residual scaled by the diagonal mass matrix). Scaling by the mass balances large and small elements in every norm the solver uses, and makes adjoint variables nodal values of the continuous adjoint.\nEach nonlinear iteration solves \\left( \\frac{\\mathbf M}{\\Delta\\tau} + \\frac{\\partial \\mathbf R_w}{\\partial \\mathbf U} \\right) \\Delta\\mathbf U = -\\mathbf R_w(\\mathbf U), \\qquad \\Delta\\tau_e = \\mathrm{CFL}\\cdot\\Delta t_e , with an element-local time scale \\Delta t_e from the convective and viscous spectral radii. Small CFL gives robust, damped steps far from the solution; as CFL grows the method becomes Newton’s method with an exact Jacobian. The linear systems are solved to a fixed relative tolerance (inexact Newton, lin_tol , 10^{-2} by default) and the CFL grows by at most a factor 10 per step, so the terminal convergence is linear, not quadratic: the residual falls by about the factor lin_tol per step. The solver study checks that contraction.\nCFL evolution. After an accepted full step the CFL is multiplied by the residual reduction factor, clipped to [1.5, 10] ; after a rejected step it is divided by 10. Line search. A step length \\omega \\in \\{1, \\tfrac12, \\dots\\} is accepted if the state stays admissible ( \\rho, p > 0 ) and the unsteady residual \\|\\mathbf R_w(\\mathbf U + \\omega\\Delta\\mathbf U) + \\tfrac{\\mathbf M}{\\Delta\\tau}\\omega\\Delta\\mathbf U\\| drops below \\|\\mathbf R_w(\\mathbf U)\\| . Requiring the steady residual to drop would reject legitimate transient steps at low CFL. Inexact Newton. Linear systems are solved to a relative tolerance of 10^{-2} by default. Warm starts. When the initial state is the converged solution of a neighbouring problem — the normal situation inside a design loop — continuation starts directly at \\mathrm{CFL} = 10^4 ; the line search still protects against a bad step. Round-off floor. If the residual stops halving for six steps in the Newton regime, the solver stops and reports whether the plateau is below the acceptance level. Failure is reported, not raised. A solve ends unconverged when the step limit is reached, the residual stalls above the acceptance level, the residual is not finite or the pseudo-time step collapses; the history carries converged: false and the reason. The discipline then keeps its last converged state as the warm start of the next solve, holds the failed iterate separately ( failed_state ), and refuses derivative calls until a solve converges ( Python API ). Residual history Nonlinear and linear solver convergence\n\nThe linear solver is restarted flexible GMRES written in JAX control flow ( jax.lax.while_loop ). The operator is built inside the same traced function : _ , lin = jax . linearize ( lambda V : residual_weak ( V ), U ) # one primal sweep, kept by XLA A = lambda v : idm * v + lin ( v ) # tangent sweep per Krylov iteration x , info = fgmres ( A , M , rhs , x0 , m = 40 , restarts = 2 , tol = 1e-2 ) Because linearisation and Krylov loop live in one compiled graph, the primal intermediate values are computed once and every iteration costs only a tangent evaluation. The adjoint solver is the same code with jax.vjp in place of jax.linearize : the transposed operator is exact, with no separately derived adjoint residual. Orthogonalisation is classical Gram–Schmidt with one re-orthogonalisation pass — two dense matrix–vector products per iteration, as robust as modified Gram–Schmidt and far better suited to GPUs.\n\nThe residual kernel takes its state twice, residual(U_own, geom, th, U_nbr) : U_own supplies everything element-local, U_nbr supplies every neighbour trace (and neighbour gradients). Evaluated at U_own = U_nbr = U it is the residual; differentiated with respect to U_own alone it is exactly block diagonal , one dense block per element: \\mathbf B_e = \\frac{\\mathbf M_e}{\\Delta\\tau_e} + \\left.\\frac{\\partial \\mathbf R_{w,e}}{\\partial \\mathbf U_e}\\right|_{\\text{neighbours frozen}} . A single JVP whose tangent is the same unit vector in every element therefore returns one column of all blocks simultaneously. All blocks cost block_size batched JVPs ( n_\\text{var}(p+1)^d , e.g. 64 for p=3 in 2-D), independent of the number of elements, with no graph colouring and no sparse assembly. The blocks are inverted in one batched call. The linearization study multiplies these inverses with the diagonal blocks of a dense AD Jacobian and finds the identity to 10^{-13} .\nBlock Jacobi removes high-frequency, element-local error but not smooth error, so its Krylov iteration counts grow with mesh size. Gyre wraps it in a V-cycle over polynomial degrees (default p, \\lfloor p/2\\rfloor, \\dots, 0 ) on the same mesh: levels : full discretizations at lower degree, linearised about the L^2 -projected state; the lowest level p=0 is a finite-volume scheme on the curved cells; smoother : damped block Jacobi ( \\omega = 0.7 , two pre- and post-sweeps) or a few block-Jacobi-preconditioned GMRES iterations; transfers : prolongation is nodal interpolation \\mathbf P ; residual restriction is \\mathbf P^{\\mathsf T} — the Galerkin restriction, exact because the residual is in weak form; adjoint : the transposed hierarchy uses the same transfers with transposed level operators and transposed blocks, so forward and adjoint solves share one implementation. Solver effort Nonlinear and linear solver convergence case preconditioner levels Newton steps Krylov iterations max Krylov / step converged final / reference residual terminal contraction (mean) terminal contraction (worst step) wall time [s] Euler NACA 0012, p = 2 block Jacobi 2 16 1206 182 yes 2.8557e-12 0.0109235 0.0134203 58.5489 Euler NACA 0012, p = 2 p-multigrid 2, 1, 0 16 142 17 yes 1.9270e-12 0.010203 0.0154498 104.262 Laminar cylinder Re = 40, p = 2 block Jacobi 2 15 1707 320 yes 1.5243e-12 0.00980497 0.00996111 48.199 Laminar cylinder Re = 40, p = 2 p-multigrid 2, 1, 0 14 143 19 yes 8.2062e-11 0.0076328 0.00863853 73.4588 Euler NACA 0012, p = 3 block Jacobi 3 16 1360 202 yes 3.3017e-12 0.0117945 0.0171502 74.6616 Euler NACA 0012, p = 3 p-multigrid 3, 1, 0 15 154 23 yes 5.9042e-11 0.00582304 0.00610324 79.4693\nA wall-resolved mesh has cells that are hundreds or thousands of times longer than they are thick. Across the thin direction an element is coupled to its neighbours far more strongly than in the others, and a smoother that freezes the neighbours, as block Jacobi does, leaves the error along that direction almost untouched. The coarser levels of the hierarchy have the same cells and inherit the anisotropy: the piecewise-constant level makes the linear solves on such a mesh converge, but they stay slow (first rows of the table below). With smoother: line the elements are grouped into lines that follow the strong coupling, and the smoother inverts the block-tridiagonal system of every line exactly (Mavriplis 1998; Fidkowski, Oliver, Lu and Darmofal 2005): \\mathbf T = \\begin{pmatrix} \\mathbf B_1 & \\mathbf U_1 \\\\ \\mathbf L_2 & \\mathbf B_2 & \\mathbf U_2 \\\\ & \\ddots & \\ddots & \\ddots \\\\ & & \\mathbf L_m & \\mathbf B_m \\end{pmatrix}, \\qquad \\mathbf L_j = \\frac{\\partial \\mathbf R_{w,e_j}}{\\partial \\mathbf U_{e_{j-1}}},\\quad \\mathbf U_j = \\frac{\\partial \\mathbf R_{w,e_j}}{\\partial \\mathbf U_{e_{j+1}}} . Lines come from the mesh. The coupling of two elements that share a face is the face area divided by the distance of their centres, the coefficient of a two-point diffusion flux. An element joins a line through the pair of opposite faces of its strongest direction if that direction is at least four times stronger than the next one (for a box-shaped cell: an aspect ratio of two), and two elements are linked only where both choose the shared face. Elements of isotropic regions belong to no line and are smoothed by block Jacobi; on a mesh without lines the option changes nothing. The lines do not depend on the flow and are the same on every level. Off-diagonal blocks from the neighbour argument. The derivative of the split-argument residual with respect to U_nbr contains every coupling block. The line elements are coloured so that a tangent which is one unit vector in all elements of one colour puts into each line element a column of the block of exactly one line neighbour. Aligned lines need three colours, so the two off-diagonal blocks cost three times the diagonal ones. Factorisation. \\mathbf D_1 = \\mathbf B_1 , \\mathbf D_j = \\mathbf B_j - \\mathbf L_j \\mathbf D_{j-1}^{-1} \\mathbf U_{j-1} , followed by one forward and one backward substitution per application. All lines are processed together; only the steps along a line are sequential. Adjoint. The transposed systems are solved with the same factors, \\mathbf T\\T = (\\mathbf I + \\mathbf W)\\T (\\mathbf D + \\mathbf L)\\T with \\mathbf W_j = \\mathbf D_j^{-1}\\mathbf U_j , so forward and adjoint solves again share one implementation. The unit tests compare both with a dense solve of \\mathbf T cut out of an AD Jacobian, in two and three dimensions and with the turbulence model. Linear solves at the converged flow (tolerance 1e-10) Line-implicit smoothing on a wall-resolved mesh preconditioner system Krylov iterations residual reduction tolerance reached wall time of the Krylov solve, compiled, preconditioner assembled before [s] block Jacobi, levels 2-1-0 forward 238 8.9804e-11 yes 6.18439 block Jacobi, levels 2-1-0 transposed (adjoint) 247 9.4097e-11 yes 10.9862 line Jacobi, levels 2-1-0 forward 66 9.5595e-11 yes 4.98718 line Jacobi, levels 2-1-0 transposed (adjoint) 70 9.4827e-11 yes 7.34169 block Jacobi, one level forward 720 1.1248e-04 no 4.00668 block Jacobi, one level transposed (adjoint) 720 2.8702e-04 no 12.8319 line Jacobi, one level forward 441 9.9505e-11 yes 5.62206 line Jacobi, one level transposed (adjoint) 465 8.7870e-11 yes 5.46822 What the option costs. The assembly of the preconditioner takes one more batch of block derivatives per colour on every level, three on aligned lines, so about four times the derivatives of block Jacobi; the measured time of one assembly is in the table below. The storage is the block-Jacobi inverse of every element plus two blocks per stored line slot; the lines are stored in a few tables by length, so the slots exceed the line elements only by the padding within a table (the summary of a case and gyre mesh info report both numbers). The study records the wall times of two small cases, one in two and one in three dimensions; nothing is claimed beyond them. One assembly of the preconditioner at the converged flow Line-implicit smoothing on a wall-resolved mesh preconditioner wall time, compiled, fastest of five [s] block Jacobi, levels 2-1-0 0.1873 line Jacobi, levels 2-1-0 4.04077 block Jacobi, one level 3.00486 line Jacobi, one level 4.08774\nThe blocks of the smoother are the largest thing the solver stores: n_\\text{var}^2 (p+1)^{2d} numbers per element and level, and two more blocks per line slot with the line smoother. In three dimensions they, and the working memory of their assembly, are what limits the size of a problem on one device. They are also the one part of the solver that need not be exact: flexible GMRES converges with any preconditioner, and converges to the solution of the system it is given. With solver.precond_precision: single every column of the blocks is rounded to single precision as the batched tangents produce it, so the blocks never exist in double precision and the assembly needs half the memory as well as the storage; the inverses, and the recurrence that factorises the line systems, are computed in double precision from the rounded blocks, a batch of elements at a time, and their results are rounded: a block of a wall-resolved cell with a turbulence model can have a condition number that single precision does not resolve, so the inversion is where the digits are needed; the smoother is applied in single precision, to a rounded copy of the vector it is given. The residual, the Jacobian–vector and vector–Jacobian products, the Krylov basis and the multigrid transfers stay in double precision. The discrete equations, and therefore the converged flow, the adjoint and every derivative, are unchanged; only the number of Krylov iterations could differ, and on the cases measured it does not: Double against single precision of the smoother's blocks Single-precision blocks in the preconditioner case blocks unknowns nonlinear steps Krylov iterations, flow solve drag coefficient Krylov iterations, forward solve Krylov iterations, transposed solve linear solutions: relative difference to double stored blocks [MB] turbulent flat plate (SA) double 6750 35 540 0.00575365 238 249 — 2.94 turbulent flat plate (SA) single 6750 35 540 0.00575365 238 249 1.7075e-11 1.47 turbulent flat plate (SA), line smoother double 6750 35 267 0.00575365 66 63 — 8.8984 turbulent flat plate (SA), line smoother single 6750 35 267 0.00575365 66 63 4.4650e-11 4.4492 laminar sphere (3-D), Re = 100, degree 1 double 3840 14 79 2.13486 46 43 — 1.248 laminar sphere (3-D), Re = 100, degree 1 single 3840 14 79 2.13486 46 43 4.1034e-14 0.624 What it buys is in the record of the largest solve , which states what happened when the same problem was tried with double-precision blocks on the same device. The default stays double ( decision 14 ).\nLines. For a face shared by the elements e and f the coupling is c_{ef} = A_{ef} / (d_e + d_f) , with A_{ef} the area of the face and d_e , d_f the distances of the two element centres from the face centre (so a periodic neighbour is at its true distance); centres and areas are those of the straight-sided element through the corner nodes. A quadrilateral or hexahedron has one pair of opposite faces per local direction a ; its strength is s_a(e) = \\max(c_{e,2a}, c_{e,2a+1}) . The element chooses its strongest direction if s_{a^*} \\ge 4\\, s_{\\text{second}} . For a box of sizes h_a , c \\propto h_b h_c / h_a , so the ratio of two directions is (h_b/h_a)^2 and the threshold is an aspect ratio of two. Two elements are linked where each chooses the face they share; every element then has at most two links, so the connected sets are paths, or closed loops across a periodic direction, which are cut at their weakest link. A single anisotropic layer next to isotropic cells forms no line, because its neighbour does not choose the shared face. Colours. Let \\mathcal N(e) be e and its face neighbours that belong to lines. A tangent that is the unit vector \\mathbf v in every element of colour c and zero elsewhere gives, differentiated through the neighbour argument, \\Bigl(\\frac{\\partial \\mathbf R_{w}}{\\partial \\mathbf U_\\text{nbr}}\\,\\mathbf t_c\\Bigr)_e = \\sum_{f \\in \\mathcal N(e),\\ \\operatorname{colour}(f) = c} \\frac{\\partial \\mathbf R_{w,e}}{\\partial \\mathbf U_f}\\,\\mathbf v . This is one column of the block of a line neighbour f of e exactly when no other member of \\mathcal N(e) shares the colour of f : that is the condition the colouring enforces, for every line element and both of its line neighbours. On a block of aligned lines the position along the line modulo three satisfies it (the line neighbours of e have the two other colours, the neighbours across the lines have the colour of e itself), hence three colours. Where lines of different directions meet (the wall-normal lines and the circumferential loops of an O-mesh) more can be needed, and no single heuristic is best on every mesh: a saturation-degree colouring and three greedy passes in different orders are tried, and the colouring with the fewest colours is kept. The number is part of the case summary. The cost of assembling the preconditioner is therefore (1 + n_\\text{colours})\\,n_\\text{block} batched tangents per level where block Jacobi takes n_\\text{block} . Factors. Per line, with \\mathbf D_j from the recurrence above, \\mathbf G_j = \\mathbf D_j^{-1}\\mathbf L_j and \\mathbf W_j = \\mathbf D_j^{-1}\\mathbf U_j are stored, and \\mathbf D_j^{-1} takes the place of the block-Jacobi inverse of the element: two blocks per line slot in addition to the one per element that block Jacobi stores. Lines of very different length occur together (the circumferential loops in the far field of an O-mesh next to its wall-normal lines), and one table as long as the longest line would be mostly padding that is factorised, stored and swept like real slots. The lines are therefore sorted by length and cut into at most four tables so that the stored slots are fewest; a further table is added only while the slots exceed the line elements by more than a quarter. One application is \\mathbf c = \\mathbf D^{-1}\\mathbf b,\\qquad \\mathbf z_j = \\mathbf c_j - \\mathbf G_j \\mathbf z_{j-1},\\qquad \\mathbf x_j = \\mathbf z_j - \\mathbf W_j \\mathbf x_{j+1}, and the transposed one, from \\mathbf T\\T = (\\mathbf I + \\mathbf W)\\T(\\mathbf D + \\mathbf L)\\T , \\mathbf y_j = \\mathbf b_j - \\mathbf W_{j-1}\\T \\mathbf y_{j-1},\\qquad \\mathbf u_j = \\mathbf y_j - \\mathbf G_{j+1}\\T \\mathbf u_{j+1},\\qquad \\mathbf x = \\mathbf D^{-\\mathsf T}\\mathbf u . The first (last) product is taken for all elements at once and is all that elements outside the lines receive, which is block Jacobi. The two sweeps run, table by table, over the slots of the longest line of the table; each step is one batched block product over its lines. What stays approximate. \\mathbf T contains the coupling along the lines only. The coupling across lines and between elements outside them is left to the Krylov iteration and to the other levels, so line Jacobi is a smoother with the same damping as block Jacobi, not a solver. The lines follow the mesh, not the flow: strong convection across thin cells is not what they are built for. A cell that is thin in two directions at once (the band of cells along the edge where two walls meet in three dimensions) has two strong directions of similar strength, fails the anisotropy test and gets no line: it stays with block Jacobi.\nLet c_R be the cost of one residual evaluation. A Krylov iteration at the fine level costs about one tangent sweep ( \\approx 1 – 2\\,c_R ) plus the V-cycle (a few fine-level operator applications for smoothing plus cheaper coarse levels): counted from the cycle, about 7.6 fine-level applications per iteration with the default sweeps and the levels 2, 1, 0 in two dimensions, against one for block Jacobi. The hierarchy therefore saves operator work only where it cuts the iteration count by more than that factor ( measured per case ). Building the block inverses costs block_size tangent sweeps of the split residual and is amortised over all Krylov iterations of a Newton step (or several, with precond_lag ). Memory is dominated by the Krylov basis (2 × krylov_dim state vectors for flexible GMRES) and the block inverses ( K \\cdot block_size ^2 ): for p=3 in 3-D the blocks are 320\\times320 per element, which bounds what fits on a single small GPU. The dominant fixed cost in practice is JIT compilation: tens of seconds per new combination of mesh size and degree. Sessions exist to pay this once per design study. This cost model is an operation count, not a measurement: the solver study records Krylov iteration counts and does not show that the V-cycle lowers total work or wall time.","title":"Implicit solver","url":"theory/implicit-solver.html"},{"audience":[],"chunks":[{"anchor":"","end":291,"heading":"","level":2,"start":0},{"anchor":"two-ways-to-get-the-same-number","end":2227,"heading":"Two ways to get the same number","level":2,"start":292},{"anchor":"where-the-partial-derivatives-come-from","end":3255,"heading":"Where the partial derivatives come from","level":3,"start":2228},{"anchor":"accuracy-exact-for-the-discrete-problem","end":4024,"heading":"Accuracy: exact for the discrete problem","level":3,"start":3256},{"anchor":"shape-as-an-input-surface-nodes-deformation-loads","end":5876,"heading":"Shape as an input: surface nodes, deformation, loads","level":3,"start":4025},{"anchor":"building-a-coupled-adjoint-from-reduced-products","end":7195,"heading":"Building a coupled adjoint from reduced products","level":4,"start":5877},{"anchor":"why-the-discrete-adjoint-and-why-adjoint-consistency-still-matters","end":7682,"heading":"Why the discrete adjoint, and why adjoint consistency still matters","level":4,"start":7196}],"headings":["Two ways to get the same number","Where the partial derivatives come from","Accuracy: exact for the discrete problem","Shape as an input: surface nodes, deformation, loads","Building a coupled adjoint from reduced products","Why the discrete adjoint, and why adjoint consistency still matters"],"keywords":["discrete adjoint","total derivative","shape sensitivity","coupled adjoint","direct method","MDO"],"level":2,"max_level":4,"path":"theory/adjoint-and-sensitivities.md","section":"theory","section_title":"Mathematics & Numerical Methods","summary":"Gyre computes exact derivatives of outputs with respect to flow parameters and surface shape with one transposed linear solve per output. All partial derivatives come from automatic differentiation of the discrete operators, so gradients are consistent with the solver to solver tolerance.","text":"An optimizer needs \\mathrm dJ/\\mathrm d\\mathbf x for outputs J (lift, drag, moment, loads) and inputs \\mathbf x (angle of attack, Mach number, Reynolds number, the position of every surface node). The state \\mathbf U is tied to \\mathbf x implicitly by \\mathbf R_w(\\mathbf U, \\mathbf x) = 0 .\n\\frac{\\mathrm dJ}{\\mathrm d\\mathbf x} = \\frac{\\partial J}{\\partial \\mathbf x} - \\boldsymbol\\psi^{\\mathsf T}\\frac{\\partial \\mathbf R_w}{\\partial \\mathbf x}, \\qquad \\left(\\frac{\\partial \\mathbf R_w}{\\partial \\mathbf U}\\right)^{\\!\\mathsf T}\\boldsymbol\\psi = \\left(\\frac{\\partial J}{\\partial \\mathbf U}\\right)^{\\!\\mathsf T} \\qquad\\text{(adjoint)} \\frac{\\mathrm dJ}{\\mathrm dx_k} = \\frac{\\partial J}{\\partial x_k} + \\frac{\\partial J}{\\partial \\mathbf U}\\frac{\\mathrm d\\mathbf U}{\\mathrm dx_k}, \\qquad \\frac{\\partial \\mathbf R_w}{\\partial \\mathbf U}\\frac{\\mathrm d\\mathbf U}{\\mathrm dx_k} = -\\frac{\\partial \\mathbf R_w}{\\partial x_k} \\qquad\\text{(direct)} The adjoint costs one linear solve per output and is independent of the number of inputs — the only practical choice for shape design, where inputs number in the hundreds to millions. The direct method costs one solve per input . totals(mode=\"auto\") picks the cheaper and both are verified against each other and against finite differences. Adjoint, finite-difference and direct derivatives Adjoint gradients versus finite differences case derivative adjoint finite difference rel. diff. (adjoint, FD) rel. diff. (adjoint, direct) Euler NACA 0012 dcl/dalpha 7.59626 7.59626 9.8180e-11 2.7336e-09 Euler NACA 0012 dcd/dalpha 0.0222355 0.0222355 2.0356e-08 2.8444e-08 Euler NACA 0012 dcm/dalpha 0.0123702 0.0123702 1.1288e-08 4.6115e-07 Euler NACA 0012 dcl/dmach 0.218262 0.218262 1.9822e-09 2.6393e-06 Euler NACA 0012 dcd/dmach 3.8734e-04 3.8734e-04 2.1349e-08 1.1047e-05 Euler NACA 0012 dcm/dmach 0.00128519 0.00128519 7.8285e-09 6.9061e-05 Euler NACA 0012 dcl/dshape (directional) 0.0130491 0.0130491 6.7282e-09 — Euler NACA 0012 dcd/dshape (directional) -2.4501e-05 -2.4501e-05 1.6480e-08 — Euler NACA 0012 dcm/dshape (directional) 3.9401e-04 3.9401e-04 3.8413e-08 — Cylinder Re = 40 dcd/dre -0.0125001 -0.0125001 4.7607e-10 — Cylinder Re = 40 dcd/dmach 0.270411 0.270411 1.3254e-09 —\nNothing is hand-differentiated. With \\mathbf d the surface displacement field and \\theta the flow parameters, the code defines two pure functions, R ( U , d , th ) = residual_weak ( U , geometry ( deform ( d )), th ) J ( U , d , th ) = functional ( U , geometry ( deform ( d )), th ) and obtains \\partial J/\\partial\\mathbf U by jax.grad ; \\boldsymbol\\psi by FGMRES on the transposed operator from jax.vjp(R, U) , preconditioned by the transposed p-multigrid hierarchy; all remaining terms in a single reverse sweep, the gradient of the Lagrangian \\mathcal L(\\mathbf d, \\theta) = J(\\mathbf U, \\mathbf d, \\theta) - \\boldsymbol\\psi^{\\mathsf T}\\mathbf R(\\mathbf U, \\mathbf d, \\theta) with \\mathbf U and \\boldsymbol\\psi held fixed. The last step delivers the derivative with respect to every surface node and every flow parameter at the cost of roughly one residual evaluation. Shape sensitivities pass through mesh deformation, metric terms, normals, boundary conditions and the functional’s own geometric dependence automatically.\nThe adjoint derivative differs from the true derivative of the discrete output only through the tolerances of the nonlinear and linear solves. Finite differences, by contrast, have a truncation error that falls like \\varepsilon^2 and a round-off error that grows like 1/\\varepsilon ; they agree with the adjoint only in a narrow window of step sizes: Finite-difference error of dcl/dα versus step size Adjoint gradients versus finite differences Smallest difference in this run: 1.2e-10 at a step of 1e-05. The reference line is the ε² truncation error of a central difference; round-off of the re-converged flows grows as 1/ε. This matters in optimization: gradient noise from badly chosen steps or loosely converged solves is a classical cause of stalled optimizers.\nThe shape input x_surface is the displacement of the high-order geometry nodes on the wall (shape (n_surface_nodes, dim) ); surface_coordinates() returns their baseline positions. A geometry engine or parameterisation outside Gyre maps design variables to these displacements and applies the chain rule to the returned sensitivity field. Volume nodes follow by inverse-distance weighting over all boundary nodes: moving-wall nodes carry their displacement, all other boundary nodes carry zero. The map is linear, \\mathbf X = \\mathbf X_0 + \\mathbf W\\mathbf d , with a weight matrix that depends on the baseline mesh only. \\mathbf W is dense, volume nodes by surface nodes: small meshes store it, and above 2\\times10^7 entries it is applied matrix-free, recomputing the weights of a chunk of nodes each time. The transposed map \\mathbf W\\T , which every shape derivative needs, is written out the same way, because reverse-mode differentiation through the chunked loop would keep the weights of every chunk, that is, the dense matrix again. Both representations are the same map to round-off, in value, tangent and transpose. On periodic meshes the weighting is periodic: distances are taken to the nearest periodic image, twin nodes on the two periodic planes are one source and share one row of \\mathbf W , so the two sides of a periodic interface stay matched for any surface displacement. Twin surface nodes keep their own rows of \\mathbf d but move by the mean of the two prescribed values, and the derivative with respect to \\mathbf d is shared equally between them. Only translational periodicity is supported. The matching output for structural coupling is f_surface : consistent nodal loads obtained by integrating the wall traction against the geometry shape functions. They sum exactly to the total force and are work-conjugate to x_surface .\nFor a coupled aero-structural system \\mathbf d = \\mathbf C\\,\\mathbf f(\\mathbf d, \\alpha) with objective J(\\mathbf d, \\alpha) the total derivative is \\frac{\\mathrm dJ}{\\mathrm d\\alpha} = \\frac{\\partial J}{\\partial\\alpha} + \\boldsymbol\\lambda^{\\mathsf T}\\mathbf C\\,\\frac{\\partial\\mathbf f}{\\partial\\alpha}, \\qquad \\boldsymbol\\lambda = \\frac{\\partial J}{\\partial\\mathbf d}^{\\mathsf T} + \\left(\\frac{\\partial\\mathbf f}{\\partial\\mathbf d}\\right)^{\\!\\mathsf T}\\mathbf C^{\\mathsf T}\\boldsymbol\\lambda , where all flow derivatives are total derivatives at converged flow (the flow state is eliminated). The flow discipline has to supply exactly two operations: totals(of=J, wrt=[alpha, x_surface]) — the reduced gradient of a scalar; adjoint_product({\"f_surface\": seed}, wrt=[...]) — the reverse product (\\mathrm d\\mathbf f/\\mathrm d\\mathbf x)^{\\mathsf T}\\mathbf{seed} , one flow-adjoint solve whatever the number of load components. The second is what FlowDiscipline.adjoint_product provides (and its forward counterpart tangent_product ). The aero-structural example iterates the \\boldsymbol\\lambda equation with these calls and matches finite differences of the fully re-converged coupled system to about 10^{-6} . Frameworks that prefer to own the linear solve instead use the partial products jvp / vjp and solve_linear .\nDifferentiating the discrete equations (rather than discretizing the continuous adjoint equations) guarantees that the gradient is the gradient of the function the optimizer actually evaluates, at any resolution. It does not by itself guarantee that \\boldsymbol\\psi approximates the continuous adjoint: that additionally requires an adjoint-consistent discretization. Gyre has both properties, which is why the same \\boldsymbol\\psi can be reused as a weight in output error estimation .","title":"Adjoints and sensitivities","url":"theory/adjoint-and-sensitivities.html"},{"audience":[],"chunks":[{"anchor":"","end":220,"heading":"","level":3,"start":0},{"anchor":"the-estimate","end":1202,"heading":"The estimate","level":3,"start":221},{"anchor":"why-it-works-and-when","end":2789,"heading":"Why it works, and when","level":4,"start":1203},{"anchor":"how-to-use-it","end":3917,"heading":"How to use it","level":2,"start":2790},{"anchor":"choosing-the-degree-from-the-estimate","end":4469,"heading":"Choosing the degree from the estimate","level":2,"start":3918},{"anchor":"what-the-selected-degree-does-and-does-not-guarantee","end":6081,"heading":"What the selected degree does and does not guarantee","level":3,"start":4470}],"headings":["The estimate","Why it works, and when","How to use it","Choosing the degree from the estimate","What the selected degree does and does not guarantee"],"keywords":["dual-weighted residual","adjoint-weighted residual","error estimate","superconvergence","adaptation"],"level":3,"max_level":4,"path":"theory/error-estimation.md","section":"theory","section_title":"Mathematics & Numerical Methods","summary":"The adjoint that provides gradients also measures how much each element's unresolved residual contaminates an output. One extra linear solve yields an error estimate, a corrected output and per-element indicators.","text":"A design optimizer exploits whatever errors the analysis has. Knowing the discretization error of the objective — not of the solution in some norm — is therefore the relevant quality measure, and the adjoint provides it.\nLet h denote the working space (degree p ) and H an enriched space (degree p+1 on the same mesh). Inject the converged coarse solution, \\mathbf U_H^h = \\mathbf I\\,\\mathbf U_h . It does not satisfy the fine equations; the fine residual \\mathbf R_{w,H}(\\mathbf U_H^h) measures by how much. Linearising the fine output about the injected state gives \\delta J \\;\\equiv\\; J_H(\\mathbf U_H) - J_H(\\mathbf U_H^h) \\;\\approx\\; -\\boldsymbol\\psi_H^{\\mathsf T}\\,\\mathbf R_{w,H}(\\mathbf U_H^h), with \\boldsymbol\\psi_H the fine-space discrete adjoint linearised about \\mathbf U_H^h . Gyre solves that one linear system with the p-multigrid hierarchy (p+1, p, \\dots, 0) and returns value = J_h , estimate =\\delta J , corrected = J_H(\\mathbf U_H^h) + \\delta J , and error $= J_h - $ corrected ; indicators : \\eta_e = \\big|\\sum_{i\\in e}\\boldsymbol\\psi_{H,i}\\cdot\\mathbf R_{w,H,i}\\big| , the share of each element — the quantity an adaptation strategy would act on. No fine nonlinear solve is needed.\nThe estimate is the first-order term of a Taylor expansion, so its error is quadratic in the distance between \\mathbf U_H^h and \\mathbf U_H . Its quality hinges on \\boldsymbol\\psi_H being a good approximation of the continuous adjoint, i.e. on adjoint consistency of the discretization and of the output definition (see BR2 and boundary conditions ). For an adjoint-consistent scheme, outputs converge at up to twice the rate of the solution in the energy norm, and the corrected output converges faster still. Singularities (a sharp trailing edge) reduce these rates, as they reduce everything else. Effectivity is assessed against the actually converged p+1 solution — an exact test of the estimator, independent of any “true” solution: Adjoint-weighted-residual estimate versus the p + 1 solution Output error estimation mesh p cd (p) estimate δ corrected cd (p + 1, converged) effectivity remaining / original error 12x4 1 0.0177275 -0.00857008 0.00634203 0.00569478 0.929779 0.0537907 24x8 1 0.012233 -0.00806656 9.2333e-04 3.4443e-04 0.93304 0.0486938 12x4 2 0.00569478 -0.0026344 5.0612e-04 1.4077e-04 0.878207 0.065781 24x8 2 3.4443e-04 -2.0704e-04 2.1391e-05 1.4254e-05 0.966676 0.0216164 Drag error: raw and adjoint-corrected Output error estimation Reference: finest mesh at degree 4, a solution used by no plotted point and no estimate. It differs from the degree-3 output on the same mesh by 1.4e-05; the reference is itself a computed value, and differences of that size or below are not resolved. Corrected outputs are the injected p + 1 output plus the adjoint estimate.\ngyre run naca0012_euler --error cd or request error_estimate: [\"cd\"] in a job, or call session_error_estimate from an agent. The command prints value , estimate , corrected and error for each output. To keep the per-element indicators , add -o <dir> (the full record is then in result.json ) or use a job or a session (the agent tool session_error_estimate returns them summarised). In a design loop the estimate answers three practical questions: whether the current mesh/order is adequate for the objective at the current design, whether a change in the objective between two designs is larger than its numerical uncertainty, and where resolution is missing. The estimate exists for the built-in scalar outputs, which can be rebuilt in the enriched space with the same reference quantities; a user-defined functional or f_surface raises KeyError . It is computed at a converged state only: a job, run_case() or gyre run whose solve did not converge returns no estimate. The job result and the result of run_case() list it under skipped ; for gyre run that entry is in result.json (with -o <dir> ), not in the printed summary.\nOrder selection turns the estimate into a stopping rule. Given an output and an absolute tolerance, the case is solved at its own degree p_0 , the error of the output is estimated, and the degree is raised by one until |J_h - (J_H(\\mathbf U_H^h) + \\delta J)| \\le \\text{tolerance}, that is, until |error| of the estimate above is within the tolerance, or the largest allowed degree is reached. Each new degree starts from the interpolated solution of the one below. Usage is in Gradients and error estimates ; the order-selection study checks the rule.\nThe lowest sufficient degree among those tried. Degrees are tried in ascending order from the case’s own, and the first whose estimate meets the tolerance is returned. Degrees below the starting one are never tried. Relative to the next degree. The estimate at degree p predicts the output at p + 1 on the same mesh. The tolerance therefore bounds the estimated distance to the p + 1 output, not to the exact one. Where enrichment in p captures little of the true error (a singularity, an under-resolved shock, a geometry that the mesh itself misrepresents) the estimate, and with it the selection, is optimistic. An estimate, not a bound. \\delta J is a linearisation; on a mesh too coarse for it to hold, met: true says that the estimate is within the tolerance, nothing more. Only a certified estimate counts. The tolerance is met only by a finite estimate whose adjoint solve converged. If a degree does not converge, after it has also been tried with the full continuation and from the free stream, the selection stops with met: false and the last converged degree. Uniform enrichment. Every element gets the same degree. The indicators \\eta_e are reported with the estimate but not used; the mesh is not changed. One output. The degree that suffices for one output need not suffice for another; other outputs of the result carry no accuracy statement unless their own estimate is requested. A discrete choice. The selected degree can change between two nearby designs, and the output then changes by the difference between two discretizations. Inside an optimization, select once and keep the degree fixed.","title":"Output error estimation","url":"theory/error-estimation.html"},{"audience":[],"chunks":[{"anchor":"","end":713,"heading":"","level":2,"start":0},{"anchor":"using-it","end":1100,"heading":"Using it","level":2,"start":714},{"anchor":"how-it-works-in-brief","end":2175,"heading":"How it works, in brief","level":2,"start":1101},{"anchor":"sensor","end":2906,"heading":"Sensor","level":3,"start":2176},{"anchor":"viscosity","end":4054,"heading":"Viscosity","level":3,"start":2907},{"anchor":"why-it-is-differentiable-and-why-that-matters","end":5077,"heading":"Why it is differentiable, and why that matters","level":3,"start":4055},{"anchor":"verified-properties","end":5813,"heading":"Verified properties","level":3,"start":5078}],"headings":["Using it","How it works, in brief","Sensor","Viscosity","Why it is differentiable, and why that matters","Verified properties"],"keywords":["shock capturing","artificial viscosity","sensor","transonic","Persson-Peraire"],"level":2,"max_level":3,"path":"theory/shock-capturing.md","section":"theory","section_title":"Mathematics & Numerical Methods","summary":"Flows with shocks are stabilised by an artificial viscosity that a smoothness sensor switches on only in the elements containing a shock. The term lives inside the differentiated residual, so Newton convergence and adjoint derivatives stay exact.","text":"A polynomial cannot represent a discontinuity. Entropy-conservative volume terms and a dissipative interface flux remove the aliasing instability, but at a shock the solution still oscillates, and nothing in the scheme keeps density and pressure positive. Gyre adds a small amount of physical-looking diffusion exactly where the solution stops being resolved, and nowhere else. Warning Limits The viscosity is constant within an element and discontinuous between elements, which is adequate for the weak-to-moderate shocks of transonic aerodynamics. It has been exercised on one transonic aerofoil case at p = 2 . Strong shocks, supersonic free streams and higher degrees are untested; positivity is not enforced.\n.venv/bin/gyre run naca0012_transonic # preset: M = 0.8, α = 1.25° .venv/bin/gyre run naca0012_euler --set flow.mach = 0 .8 --set discretization.shock_capturing = true discretization.shock_capturing enables the term; discretization.av_coef scales it (default 1). Smooth flows are unaffected: the sensor is exactly zero for a uniform state and far below threshold for resolved solutions.\nDetect. In each element the density is expanded in Legendre modes. If a noticeable part of its energy sits in the highest modes, the element is not resolving the solution — the signature of a shock. This gives one smooth sensor value per element. Diffuse. Where the sensor exceeds a threshold, a Laplacian artificial viscosity is switched on, scaled with the element size divided by the polynomial degree and with the local wave speed, and with av_coef . It spreads the shock over a few nodes inside the element instead of letting the polynomial oscillate. Differentiate. Sensor and viscosity are smooth functions of the state and part of the residual, so the Newton linearisation and the adjoint include them exactly. This is why a transonic case converges to machine precision and why its gradients agree with finite differences ( transonic example ). What it is not: a limiter, a positivity guarantee or an entropy-stable shock treatment. It is verified on a transonic aerofoil with weak shocks and untested for strong ones; the limits are listed at the end of this page.\nIn every element the density is transformed to an orthonormal Legendre basis. The sensor is the fraction of its energy carried by the highest polynomial shell (all modes with at least one index equal to p ): S_e = \\frac{\\sum_{\\text{modes with } \\max(i,j,k) = p} \\hat\\rho^{\\,2}}{\\sum_{\\text{all modes}} \\hat\\rho^{\\,2}}, \\qquad s_e = \\log_{10} S_e . For a smooth, resolved solution the spectrum decays rapidly and s_e is very negative; across a shock the spectrum is flat and s_e approaches zero. The numerator is evaluated as a direct sum of squares — computing it as “total minus low modes” lets round-off make it negative in near-uniform far-field elements, and the logarithm of a negative number poisons the whole linearisation.\nThe element-wise coefficient ramps smoothly from zero to its maximum, \\varepsilon_e = \\varepsilon_{\\max}\\cdot \\begin{cases} 0 & s_e \\le s_0 - \\kappa \\\\ \\tfrac12\\left(1 + \\sin\\dfrac{\\pi (s_e - s_0)}{2\\kappa}\\right) & |s_e - s_0| < \\kappa \\\\ 1 & s_e \\ge s_0 + \\kappa \\end{cases} \\qquad \\varepsilon_{\\max} = c_\\text{av}\\,\\frac{h_e}{p}\\,\\overline{(|\\mathbf u| + c)}_e , with threshold s_0 = -(1.5 + 4\\log_{10} p) , half-width \\kappa = 1 , h_e the element size and the bar an element mean. The element size is the square root of the element area in 2-D and the cube root of the volume in 3-D, both taken from the nodal mean of the Jacobian. On a spanwise extrusion of a 2-D section it is the square root of the in-plane area, so that an extruded case carries, for the same state, exactly the viscosity of its section, independent of span and nz . The scaling makes the shock about one element wide. The diffusive flux \\varepsilon_e \\nabla\\mathbf U acts on all conserved variables and is discretized with the same BR2 machinery as the physical viscous terms: lifted gradients, weak divergence, averaged interface flux. No flux crosses domain boundaries.\nThe sensor, the ramp (which is C^1 ) and the viscosity are ordinary JAX code inside the residual. Therefore: the Newton linearisation includes the dependence of \\varepsilon_e on the solution — the solver reduces the residual of a transonic case by ten orders, at the linear rate of its inexact Newton iteration ( implicit solver ), rather than stalling in the limit cycle that frozen or non-smooth limiters are known for; the element blocks of the preconditioner remain exact (the viscosity of an element depends only on its own state; the neighbour’s enters through the neighbour-role argument); the discrete adjoint contains the shock-capturing term, so total derivatives are those of the function that is actually computed. Adjoint derivatives through the shock Example: transonic aerofoil with shock capturing derivative adjoint finite difference relative difference dcl/dalpha 17.6219 17.6219 1.4558e-06 dcd/dalpha 1.31459 1.31459 9.9160e-07 dcl/dmach 4.46992 4.46988 9.9365e-06 dcd/dmach 0.688727 0.688725 3.7501e-06\nUnit tests assert that the viscosity is exactly zero for uniform and smooth resolved data and non-zero only in elements at a steep profile; that free-stream preservation and conservation hold with the term active (2-D and 3-D); that it increases entropy dissipation; that a spanwise extrusion reproduces the viscosity of its 2-D section for different spans, and its residual for a state that is continuous across elements (where the state jumps between elements the two residuals differ through the default penalty , 6 for hexahedra against 4 for quadrilaterals); and that block inverses, JVPs and VJPs remain exact with the sensor partially ramped ( tests/test_operator.py ). The transonic example checks the flow and its derivatives.","title":"Shock capturing","url":"theory/shock-capturing.html"},{"audience":[],"chunks":[{"anchor":"","end":434,"heading":"","level":2,"start":0},{"anchor":"the-idea-in-one-sentence","end":619,"heading":"The idea in one sentence","level":1,"start":435},{"anchor":"using-it","end":1411,"heading":"Using it","level":2,"start":620},{"anchor":"what-is-and-is-not-included","end":2464,"heading":"What is and is not included","level":2,"start":1412},{"anchor":"model-equations","end":2465,"heading":"Model equations","level":2,"start":2465},{"anchor":"transport-equation","end":3492,"heading":"Transport equation","level":3,"start":2466},{"anchor":"closure-functions-and-constants","end":4491,"heading":"Closure functions and constants","level":4,"start":3493},{"anchor":"why-the-negative-branch","end":5079,"heading":"Why the negative branch","level":3,"start":4492},{"anchor":"discretization","end":5080,"heading":"Discretization","level":2,"start":5080},{"anchor":"one-more-row-of-the-same-operator","end":5933,"heading":"One more row of the same operator","level":3,"start":5081},{"anchor":"sources-are-integrated-at-gauss-points","end":6436,"heading":"Sources are integrated at Gauss points","level":3,"start":5934},{"anchor":"lifting-the-gradient-into-the-source-dual-consistency","end":6931,"heading":"Lifting the gradient into the source: dual consistency","level":4,"start":6437},{"anchor":"smoothness-what-was-changed-in-the-model","end":7648,"heading":"Smoothness: what was changed in the model","level":4,"start":6932},{"anchor":"boundary-conditions","end":8013,"heading":"Boundary conditions","level":3,"start":7649},{"anchor":"wall-distance","end":8014,"heading":"Wall distance","level":2,"start":8014},{"anchor":"exact-distance-to-the-curved-wall","end":8400,"heading":"Exact distance to the curved wall","level":3,"start":8015},{"anchor":"the-wall-distance-is-part-of-the-shape-derivative","end":9058,"heading":"The wall distance is part of the shape derivative","level":3,"start":8401},{"anchor":"solver","end":9159,"heading":"Solver","level":2,"start":9059},{"anchor":"row-weights","end":9943,"heading":"Row weights","level":3,"start":9160},{"anchor":"limits-and-open-questions","end":11202,"heading":"Limits and open questions","level":3,"start":9944}],"headings":["The idea in one sentence","Using it","What is and is not included","Model equations","Transport equation","Closure functions and constants","Why the negative branch","Discretization","One more row of the same operator","Sources are integrated at Gauss points","Lifting the gradient into the source: dual consistency","Smoothness: what was changed in the model","Boundary conditions","Wall distance","Exact distance to the curved wall","The wall distance is part of the shape derivative","Solver","Row weights","Limits and open questions"],"keywords":["RANS","turbulence","Spalart-Allmaras","SA-neg","wall distance","eddy viscosity"],"level":2,"max_level":4,"path":"theory/turbulence-model.md","section":"theory","section_title":"Mathematics & Numerical Methods","summary":"Turbulent flows are computed with the Reynolds-averaged equations closed by the negative Spalart–Allmaras model. The model equation is one more row of the same discrete operator, so the Newton linearisation, the adjoint and the shape derivatives (including the wall distance) remain exact.","text":"At flight Reynolds numbers the boundary layers on an aircraft are turbulent, and resolving the turbulence itself is out of reach for design work. Gyre therefore offers the Reynolds-averaged Navier–Stokes (RANS) equations with a one-equation closure: the Spalart–Allmaras model in its negative form (SA-neg), the variant written for discretizations, such as high-order DG, whose solution can undershoot at the edge of a boundary layer.\nOne extra transported quantity, \\tilde\\nu , produces an eddy viscosity \\mu_t that is added to the molecular viscosity seen by the mean flow; everything else in the solver is unchanged.\nflow : mach : 0.2 reynolds : 5.0e6 turbulence : sa # 'none' (default) or 'sa'; needs reynolds extra : { chi_inf : 3.0 , prt : 0.9 } # optional: free-stream nu_tilde/nu, turbulent Prandtl number With turbulence: sa the state has one more variable per node, \\rho\\tilde\\nu , stored after the energy. No-slip walls impose \\tilde\\nu = 0 and define the wall distance; the far field and inlets supply \\tilde\\nu = \\chi_\\infty \\nu . Outputs, gradients, error estimates and the discipline interface work as for laminar flow; chi_inf and prt are ordinary differentiable flow parameters. The model is integrated to the wall (no wall functions), so the first element must be thin enough for the polynomial inside it to resolve the viscous sublayer: see the flat-plate study for the resolution used there.\nIncluded: SA-neg with the 2012 modifications of Allmaras, Johnson and Spalart (the \\tilde S limiter that keeps the modified vorticity positive, the negative branch, the f_{t2} laminar-suppression term), a constant turbulent Prandtl number, an exact curved wall distance that takes part in shape derivatives. Not included: trip terms and transition modelling (the flow is turbulent wherever the model produces eddy viscosity), rotation/curvature and compressibility corrections, the quadratic constitutive relation, the \\tfrac23 \\rho k term of the Boussinesq stress (neglected as is usual for this model), wall functions, other models. Verified on a manufactured solution, a linearisation study, the zero-pressure-gradient flat plate of the NASA Turbulence Modeling Resource and adjoint-versus-finite-difference derivatives ( verification ); in three dimensions a spanwise extrusion of that plate reproduces its section. Separated flows, aerofoils at flight Reynolds number and turbulent flows that are three-dimensional in themselves have not been run.\n\nThe working variable is \\tilde\\nu , with \\chi = \\tilde\\nu/\\nu . Gyre solves the conservative form for \\rho\\tilde\\nu , \\pd{(\\rho\\tilde\\nu)}{t} + \\nabla\\cdot(\\rho\\mathbf u\\,\\tilde\\nu) = \\rho\\,(P - D) + \\frac{1}{\\sigma}\\nabla\\cdot\\big(\\rho(\\nu + \\tilde\\nu f_n)\\nabla\\tilde\\nu\\big) - \\frac{1}{\\sigma}(\\nu + \\tilde\\nu f_n)\\,\\nabla\\rho\\cdot\\nabla\\tilde\\nu + \\frac{c_{b2}}{\\sigma}\\,\\rho\\,\\abs{\\nabla\\tilde\\nu}^2 , \\tag{1} with production P and destruction D that depend on the sign of \\tilde\\nu : \\tilde\\nu \\ge 0 \\tilde\\nu < 0 P c_{b1}(1 - f_{t2})\\,\\tilde S\\,\\tilde\\nu c_{b1}(1 - c_{t3})\\,S\\,\\tilde\\nu D \\big(c_{w1} f_w - \\dfrac{c_{b1}}{\\kappa^2} f_{t2}\\big)\\dfrac{\\tilde\\nu^2}{d^2} -c_{w1}\\dfrac{\\tilde\\nu^2}{d^2} f_n 1 \\dfrac{c_{n1} + \\chi^3}{c_{n1} - \\chi^3} \\mu_t \\rho\\tilde\\nu f_{v1} , \\; f_{v1} = \\dfrac{\\chi^3}{\\chi^3 + c_{v1}^3} 0 S is the magnitude of the vorticity and d the distance to the nearest no-slip wall. The mean flow sees the viscosity \\mu + \\mu_t and the conductivity c_p(\\mu/\\mathit{Pr} + \\mu_t/\\mathit{Pr}_t) .\nFor \\tilde\\nu \\ge 0 the modified vorticity is \\tilde S = \\begin{cases} S + \\bar S & \\bar S \\ge -c_{v2} S \\4pt] S + \\dfrac{S\\,(c_{v2}^2 S + c_{v3}\\bar S)}{(c_{v3} - 2c_{v2})S - \\bar S} & \\text{otherwise} \\end{cases} \\qquad \\bar S = \\frac{\\tilde\\nu f_{v2}}{\\kappa^2 d^2},\\quad f_{v2} = 1 - \\frac{\\chi}{1 + \\chi f_{v1}} , which is positive whenever S is and has a continuous first derivative at the switch. The wall function of the destruction term is f_w = g\\left(\\frac{1 + c_{w3}^6}{g^6 + c_{w3}^6}\\right)^{1/6},\\qquad g = r + c_{w2}(r^6 - r),\\qquad r = \\min\\!\\Big(\\frac{\\tilde\\nu}{\\tilde S\\kappa^2 d^2},\\, r_\\text{lim}\\Big), and f_{t2} = c_{t3}\\exp(-c_{t4}\\chi^2) . Constants: c_{b1} = 0.1355 , c_{b2} = 0.622 , \\sigma = 2/3 , \\kappa = 0.41 , c_{w1} = c_{b1}/\\kappa^2 + (1 + c_{b2})/\\sigma , c_{w2} = 0.3 , c_{w3} = 2 , c_{v1} = 7.1 , c_{v2} = 0.7 , c_{v3} = 0.9 , c_{t3} = 1.2 , c_{t4} = 0.5 , c_{n1} = 16 , r_\\text{lim} = 10 ; \\mathit{Pr}_t = 0.9 and \\chi_\\infty = 3 by default ( prt , chi_inf ).\nA polynomial that represents the sharp edge of a boundary layer undershoots, so \\tilde\\nu becomes slightly negative there. The original model then loses its footing: the eddy viscosity turns negative and the diffusion coefficient can change sign. SA-neg continues the equation to \\tilde\\nu < 0 so that it stays well posed: no eddy viscosity, a diffusion coefficient that remains positive through f_n , and source terms that drive \\tilde\\nu back towards zero. The positive and the negative branch join with continuous first derivatives, which is what Newton’s method and the adjoint need.\n\nEquation (1) goes through the machinery of the mean-flow equations ( DGSEM , BR2 ): Convection. In the volume, the two-point mass flux of the mean flow times the arithmetic mean of \\tilde\\nu ; on faces, upwinding of \\tilde\\nu with the interface mass flux f_\\rho^* of the Riemann solver, f^*_{\\rho\\tilde\\nu} = \\tfrac12 f_\\rho^*(\\tilde\\nu_L + \\tilde\\nu_R) - \\tfrac12\\sqrt{(f_\\rho^*)^2 + \\delta^2}\\,(\\tilde\\nu_R - \\tilde\\nu_L) . Diffusion. The conservative diffusion term is one more row of the viscous flux and takes the BR2 path unchanged. \\tilde\\nu and \\rho join the velocity and the temperature as gradient variables. Sources. Production, destruction and the two non-conservative gradient terms are evaluated with the BR2 gradient q = G + \\sum_f r_f , the same lifted gradient the viscous volume flux uses, so that they see the jumps between elements.\nThe Gauss–Lobatto solution nodes include points on the wall, where d = 0 and \\tilde\\nu/d is undefined for a discrete \\tilde\\nu that satisfies its wall condition only weakly. The source is therefore integrated with Gauss quadrature: state and BR2 gradient are interpolated to the (p+1)^d Gauss points of each element, where d > 0 , the source is evaluated there, and the result is tested against the nodal basis, (J R)_{i} \\mathrel{-}= \\frac{1}{w_i}\\sum_g \\ell_i(\\xi_g)\\, w_g\\, J_g\\, s_t(U_g, q_g, d_g).\nA source that depends on the gradient of the solution is adjoint consistent only if it is evaluated with a gradient that includes the interface contributions (Oliver and Darmofal). Using the element-local gradient G alone would leave the primal scheme consistent but make the discrete adjoint inconsistent with the continuous one, and functionals would lose their superconvergence. The BR2 gradient q already contains the face liftings, so the same array serves the viscous flux and the source.\nNewton’s method and the adjoint need a residual with continuous first derivatives. The model’s own switches (the \\tilde S limiter and the sign branch) have them by construction. The remaining kinks are smoothed: Quantity Model Gyre Size of the change vorticity magnitude \\abs{\\boldsymbol\\omega} \\sqrt{\\abs{\\boldsymbol\\omega}^2 + \\epsilon^2} - \\epsilon at most \\epsilon = 10^{-6} (units u_\\infty/L_\\text{ref} ); exactly zero in irrotational flow limiter of r \\min(r, r_\\text{lim}) C^1 blend on r_\\text{lim} \\pm 1 none for r \\le 9 f_{v1} and \\mu_t at \\tilde\\nu = 0 \\chi^3 \\max(\\chi, 0)^3 none ( C^2 already) interface upwinding \\abs{f_\\rho^*} \\sqrt{(f_\\rho^*)^2 + \\delta^2} a dissipation floor \\delta = 10^{-3}\\bar\\rho\nCondition \\tilde\\nu no-slip walls \\tilde\\nu = 0 , in the convective flux and in the BR2 lifting (Dirichlet) farfield exterior state \\chi_\\infty\\mu_\\infty ; the mass-flux upwinding takes it on inflow and the interior value on outflow total_inlet \\chi_\\infty\\,\\mu(T_b) at the boundary state slip walls, symmetry, pressure_outlet the interior value; no diffusive flux\n\nThe wall distance is computed once per discretization for every Gauss point: a k-d tree over samples of the wall faces proposes candidate faces, and a projected Gauss–Newton iteration on each candidate’s polynomial (of the geometry degree) finds the closest point. The result is stored as interpolation weights of the foot point on the geometry nodes of its wall face, not as a number.\nBecause the foot point is stored as weights on mesh nodes, the distance is re-evaluated inside the differentiable geometry, d(X) = \\abs{x_g(X) - x_\\text{foot}(X)} , whenever the node coordinates X change. By the envelope theorem the derivative of a minimum with respect to a parameter is the partial derivative at the minimiser, so holding the foot-point parameters fixed gives the exact first derivative of the distance with respect to X , as long as the closest face does not change. Shape derivatives of turbulent cases therefore include the wall-distance sensitivity; the adjoint study checks them against finite differences that recompute the distance.\nReferences: Allmaras, Johnson and Spalart (2012); Oliver and Darmofal (2009); see the bibliography .\nIn the units of the solver the mean-flow equations are of order one and the turbulence equation of order \\tilde\\nu \\sim \\chi/\\mathit{Re} , six or seven orders smaller at flight Reynolds number. Unweighted, an inexact Krylov solve and the convergence monitor would not see it. The rows of the turbulence equation are therefore weighted by \\mathit{Re}/\\chi_\\text{ref} with \\chi_\\text{ref} = 100 : the Newton residual norm is \\norm{s\\,R_w} , the forward Krylov solver works on (SA)x = Sb and the adjoint one on (S^{-1}A\\T)x = S^{-1}b . The solutions are those of the unscaled systems; only the norm the tolerances refer to changes. The implicit solver , the block-Jacobi preconditioner and p-multigrid are otherwise the same as for laminar flow, with element blocks one variable larger.\nThe eddy viscosity enters the stable time step and the block preconditioner, but there is no positivity enforcement for \\rho , p or \\mu + \\mu_t beyond what the model provides. Start-up from a uniform free stream relies on pseudo-transient continuation; there is no dedicated turbulence start-up strategy and no measured robustness on separated flows. Wall-resolved meshes have element aspect ratios of the order of a thousand at the wall. Element-block Jacobi does not see that anisotropy: used alone it leaves linear solves on such meshes slow or short of their tolerance. Two remedies exist. The piecewise-constant coarse level of the p-multigrid hierarchy ( solver.levels at its default, which includes p = 0 ) makes them converge with block Jacobi (solver statistics of the adjoint study ). The line smoother ( solver.smoother: line , how it works ) inverts the coupling along the wall-normal lines of elements exactly and needs a fraction of the Krylov iterations ( measured ). There is no incomplete-factorisation preconditioner, and nothing has been measured on a turbulent mesh larger than the flat plate of the preset. The entropy analysis of the two-point fluxes covers the mean-flow convective terms only; nothing is proven for the model equation.","title":"Turbulence closure (RANS, Spalart–Allmaras)","url":"theory/turbulence-model.html"},{"audience":["MDO integrators","CFD practitioners"],"chunks":[{"anchor":"","end":1586,"heading":"","level":2,"start":0},{"anchor":"forces-and-moments","end":1587,"heading":"Forces and moments","level":2,"start":1587},{"anchor":"definitions-and-signs","end":3200,"heading":"Definitions and signs","level":2,"start":1588},{"anchor":"three-dimensions-sideslip-side-force-and-three-moments","end":4968,"heading":"Three dimensions: sideslip, side force and three moments","level":2,"start":3201},{"anchor":"reference-quantities","end":6827,"heading":"Reference quantities","level":2,"start":4969},{"anchor":"why-the-numerical-boundary-flux-and-what-that-means-for-accuracy","end":7489,"heading":"Why the numerical boundary flux, and what that means for accuracy","level":3,"start":6828},{"anchor":"heat","end":7490,"heading":"Heat","level":2,"start":7490},{"anchor":"definition-and-sign","end":8230,"heading":"Definition and sign","level":2,"start":7491},{"anchor":"volume-diagnostics","end":8231,"heading":"Volume diagnostics","level":2,"start":8231},{"anchor":"definitions","end":9589,"heading":"Definitions","level":2,"start":8232},{"anchor":"surface-fields","end":9590,"heading":"Surface fields","level":2,"start":9590},{"anchor":"f-surface-nodal-loads","end":10357,"heading":"`f_surface`: nodal loads","level":2,"start":9591},{"anchor":"surface-distributions-cp-and-cf","end":11133,"heading":"Surface distributions: `cp` and `cf`","level":2,"start":10358},{"anchor":"units-of-derivatives","end":11134,"heading":"Units of derivatives","level":2,"start":11134},{"anchor":"what-a-derivative-is-per","end":11664,"heading":"What a derivative is per","level":2,"start":11135}],"headings":["Forces and moments","Definitions and signs","Three dimensions: sideslip, side force and three moments","Reference quantities","Why the numerical boundary flux, and what that means for accuracy","Heat","Definition and sign","Volume diagnostics","Definitions","Surface fields","`f_surface`: nodal loads","Surface distributions: `cp` and `cf`","Units of derivatives","What a derivative is per"],"keywords":["cl","cd","cm","cx","cy","heat","entropy_error","kinetic_energy","total_entropy","f_surface","cp","cf","sign convention","reference area"],"level":2,"max_level":3,"path":"theory/outputs-and-functionals.md","section":"theory","section_title":"Mathematics & Numerical Methods","summary":"The definition, sign convention, normalisation and units of every output name a result can contain — force and moment coefficients, heat, the volume diagnostics, the surface load field and the surface distributions.","text":"Every quantity below is non-dimensional. The reference values are the free stream: \\rho_\\infty = |\\mathbf u_\\infty| = T_\\infty = 1 , so the free-stream pressure is p_\\infty = 1/(\\gamma M_\\infty^2) and the free-stream dynamic pressure is \\tfrac12 . Lengths are in the unit of the mesh coordinates. The free-stream direction is (\\cos\\alpha, \\sin\\alpha) in the x – y plane. Name Kind Exists for Definition in one line cx , cy scalar every case with a wall force on the body along the mesh axes x , y , divided by \\tfrac12 S_\\text{ref} cd , cl scalar every case with a wall the same force along and normal to the free stream cm scalar every case with a wall moment about reference.moment_center , nose-up positive, divided by \\tfrac12 S_\\text{ref} L_\\text{ref} cz , cs scalar 3-D cases with a wall force along the mesh axis z ; side force in the wind axes cmx , cmy , cmz scalar 3-D cases with a wall rolling, yawing and pitching moment about reference.moment_center ; cmz is the same output as cm heat scalar viscous cases heat flow rate from the walls into the fluid (not normalised) entropy_error scalar every case root-mean-square relative deviation of p/\\rho^\\gamma from its free-stream value kinetic_energy scalar every case volume average of \\tfrac12\\rho|\\mathbf u|^2 total_entropy scalar every case volume integral of the mathematical entropy \\eta f_surface field (n_surface_nodes, dim) every case with a wall consistent nodal loads on the wall geometry nodes Scalars can be differentiated with totals ; the field f_surface through adjoint_product and tangent_product ( gradients ).\n\nLet \\Gamma_w be all boundary groups with a wall condition ( slip_wall , symmetry , slip_wall_mirror , noslip_adiabatic , noslip_isothermal ) and \\mathbf n the unit normal pointing out of the fluid, into the body. The force the fluid exerts on the body is the momentum flux leaving the fluid through the wall, \\mathbf F = \\int_{\\Gamma_w} \\big( p\\,\\mathbf n - \\boldsymbol\\tau\\,\\mathbf n \\big)\\,\\mathrm dS , evaluated with the numerical boundary flux of the residual (see below). With S_\\text{ref} = reference.area and L_\\text{ref} = reference.length : c_x = \\frac{2F_x}{S_\\text{ref}},\\qquad c_y = \\frac{2F_y}{S_\\text{ref}},\\qquad c_d = c_x\\cos\\alpha + c_y\\sin\\alpha,\\qquad c_l = -c_x\\sin\\alpha + c_y\\cos\\alpha . cx , cy are in the body (mesh) axes ; cd , cl are in the wind axes given by the input alpha . Drag is positive downstream, lift positive towards +y at \\alpha = 0 . The factor 2 is 1/(\\tfrac12\\rho_\\infty u_\\infty^2) . The moment is taken about \\mathbf x_\\text{ref} = reference.moment_center : c_m = -\\frac{2}{S_\\text{ref} L_\\text{ref}} \\int_{\\Gamma_w} \\big[(x - x_\\text{ref})\\,\\mathrm dF_y - (y - y_\\text{ref})\\,\\mathrm dF_x\\big] . The integral is the moment about the z axis, counter-clockwise positive; the minus sign makes cm positive nose-up for a body whose nose points towards -x (flow from left to right). Because alpha enters cd and cl explicitly, their derivatives with respect to alpha contain the rotation of the axes as well as the change of the flow: \\partial c_l/\\partial\\alpha|_\\text{explicit} = -c_d and \\partial c_d/\\partial\\alpha|_\\text{explicit} = c_l . The totals include both parts.\nIn 3-D, x points downstream, y up and z along the span ( z = x \\times y ). The free stream has an angle of attack \\alpha and a sideslip angle \\beta (input beta , radians; flow.beta_deg in a case), and the wind axes are the right-handed triad \\mathbf d = \\begin{pmatrix}\\cos\\alpha\\cos\\beta\\\\ \\sin\\alpha\\cos\\beta\\\\ \\sin\\beta\\end{pmatrix},\\qquad \\mathbf e_L = \\begin{pmatrix}-\\sin\\alpha\\\\ \\cos\\alpha\\\\ 0\\end{pmatrix},\\qquad \\mathbf e_S = \\mathbf d\\times\\mathbf e_L = \\begin{pmatrix}-\\cos\\alpha\\sin\\beta\\\\ -\\sin\\alpha\\sin\\beta\\\\ \\cos\\beta\\end{pmatrix}. The force coefficients are the components of 2\\mathbf F/S_\\text{ref} along them, and along the mesh axes: Name Component cd , cl , cs along \\mathbf d (drag), \\mathbf e_L (lift), \\mathbf e_S (side force) cx , cy , cz along the mesh axes x , y , z so cs equals cz at zero sideslip and the two-dimensional definitions are recovered for \\beta = 0 . The moment about \\mathbf x_\\text{ref} is the vector \\mathbf M = \\int_{\\Gamma_w} (\\mathbf x - \\mathbf x_\\text{ref}) \\times \\mathrm d\\mathbf F , and the coefficients follow the aerodynamic sign convention, which in these axes is minus the right-hand-rule moment for all three: c_{m,x} = -\\frac{2 M_x}{S_\\text{ref} L_\\text{ref}}\\ \\text{(roll)},\\qquad c_{m,y} = -\\frac{2 M_y}{S_\\text{ref} L_\\text{ref}}\\ \\text{(yaw)},\\qquad c_{m,z} = -\\frac{2 M_z}{S_\\text{ref} L_\\text{ref}}\\ \\text{(pitch, nose-up positive)} . cmz and cm are the same output. For a spanwise-extruded mesh the default reference area is the span, so the coefficients are per unit span; for a sphere_shell mesh it is the frontal area of the body. As with alpha , the totals with respect to beta contain the rotation of the wind axes. The 3-D outputs and their derivatives are verified in Three-dimensional flows .\nField of the case Default Enters reference.area 1 in 2-D; the span for a spanwise-extruded generator mesh; the frontal area of the body for a sphere_shell mesh all force and moment coefficients reference.length 1 the moment coefficients only reference.moment_center [0.25, 0, 0] the moment coefficients only reference.groups every wall group which walls the force, moment and heat-flux integrals cover reference.components none named sets of wall groups whose integrals are additional outputs, cd_wing , cmz_tail , … In 2-D the “area” is a length (the chord): the coefficients are per unit depth. The built-in aerofoil generators produce a unit chord, so the defaults are the usual aerofoil convention with the moment about the quarter-chord point. A mesh from a file has whatever size it was drawn at: set all three ( mesh files ). By default the coefficients cover all wall groups of the case. reference.groups restricts them to the named walls, which gives the loads of one component of a configuration (and, case by case, of each of them); the integrals of the selected groups add up to the integral over all walls, and their adjoint derivatives and error estimates are those of the restricted integral. The nodal load field and the surface displacement are the coupling interface and are not affected. reference.components gives the breakdown from one solve: for every named component the same integrals are taken over its groups and returned as <coefficient>_<component> , normalised with the same reference area, length and moment point, so the outputs of components that partition the walls add up to the plain coefficients. Each is a functional of its own, built from the same numerical boundary fluxes, with its own adjoint: a drag constraint on one component, or a bending-moment proxy of another, costs one transposed solve like any other output.\nThe force is not computed from interpolated pressure. It is the sum over wall face nodes of the momentum components of the numerical boundary flux that the residual itself uses, times the face quadrature weight and surface Jacobian ( functionals.SurfaceOps.face_forces ). For a slip wall that flux is the wall pressure flux; for a no-slip wall it also contains the BR2 viscous traction. Two consequences: the forces are exactly the momentum the discrete scheme loses through the wall (discrete conservation holds for them), and the functional is adjoint consistent, which is what makes its error converge faster than the solution’s and the error estimate sharp.\n\nheat exists when the case has a Reynolds number. It is minus the energy flux that leaves the fluid through the wall faces, Q = -\\int_{\\Gamma_w} \\mathbf F_E^{*}\\cdot\\mathbf n\\,\\mathrm dS , again from the numerical boundary flux. On a no-slip wall the velocity is zero, so the energy flux is the conductive heat flux alone and Q is the heat flow rate from the wall into the fluid (positive when the wall heats the fluid; zero up to discretization error on an adiabatic wall). It is not divided by a reference quantity: its unit is \\rho_\\infty u_\\infty^3 L^{d-1} , per unit depth in 2-D. If the case also has slip walls they are part of \\Gamma_w , where the flux contributes p\\,\\mathbf u\\cdot\\mathbf n , which the slip condition makes vanish.\n\nWith V = \\int_\\Omega \\mathrm dV and integrals evaluated by the Gauss–Lobatto quadrature of the scheme: \\texttt{entropy\\_error} = \\sqrt{\\frac1V\\int_\\Omega \\Big(\\frac{s}{s_\\infty} - 1\\Big)^2 \\mathrm dV}, \\qquad s = \\frac{p}{\\rho^\\gamma}, \\texttt{kinetic\\_energy} = \\frac1V\\int_\\Omega \\tfrac12\\rho\\,|\\mathbf u|^2\\,\\mathrm dV, \\qquad \\texttt{total\\_entropy} = \\int_\\Omega \\eta\\,\\mathrm dV,\\quad \\eta = -\\frac{\\rho\\,(\\ln p - \\gamma\\ln\\rho)}{\\gamma - 1}. entropy_error is zero for an exact solution of a smooth inviscid flow from a uniform free stream (the entropy function s is then constant), so for such cases it measures the spurious entropy the discretization produces and falls under refinement. With shocks, viscosity or heat transfer it contains physical entropy changes and is not an error. kinetic_energy equals \\tfrac12 in the undisturbed free stream. total_entropy uses the mathematical entropy \\eta , which has the opposite sign of the physical one and is not normalised by the volume. Its value is dominated by the size of the domain: for the free stream at Mach 0.5, \\eta_\\infty = -\\ln p_\\infty/(\\gamma - 1) \\approx -2.62 per unit area, and the O-mesh of the naca0012_euler preset reaches 50 chords, so the first run prints total_entropy \\approx -2.06\\times10^{4} . It is a diagnostic for unsteady entropy studies, of little use as a design output.\n\nf_surface has one row per wall geometry node, in the order of surface_coordinates() and of the input x_surface . Row j is \\int_{\\Gamma_w} N_j\\,\\mathrm d\\mathbf F with N_j the geometry shape function of the node: the traction integrated against the basis in which a displacement of the surface is interpolated. The loads are therefore conservative : they sum to the total force, f_surface.sum(axis=0) = \\mathbf F , i.e. 2 * f_surface.sum(0) / reference.area equals (cx, cy) (of all walls: the load field is not restricted by reference.groups ); work-consistent with x_surface : the virtual work of a displacement field is the dot product of the two arrays. They are forces on the structure (per unit depth in 2-D), not coefficients: no factor 2 and no reference area.\nresult.json , the session command distributions and SurfaceOps.distributions give, at the wall face nodes, the coordinates x and c_p = 2\\,(t_n - p_\\infty), \\qquad c_f = 2\\,\\mathbf t\\cdot\\mathbf s , where \\mathbf t is the traction of the numerical boundary flux (momentum flux per unit area), t_n = \\mathbf t\\cdot\\mathbf n its component along the normal that points into the body, and \\mathbf s = (-n_y, n_x) the tangent obtained by rotating that normal by +90° . On a slip wall t_n is the wall pressure, so cp is the usual pressure coefficient (p - p_\\infty)/(\\tfrac12\\rho_\\infty u_\\infty^2) ; on a no-slip wall it also contains the normal viscous stress. cf is signed by the direction of \\mathbf s , exists in 2-D only, and is zero up to discretization error on a slip wall.\n\nInput Unit of the input Note alpha radian the only angle the in-process discipline knows alpha_deg degree accepted by the CLI, jobs, sessions, sweeps; the derivative is per degree, i.e. the alpha derivative times \\pi/180 mach , re , gamma , pr dimensionless x_surface mesh length unit displacement of each wall geometry node; the derivative has the shape of the input A derivative is returned under the name it was requested with, so alpha and alpha_deg cannot be confused in a result — but they differ by a factor of about 57.3.","title":"Outputs and functionals","url":"theory/outputs-and-functionals.html"},{"audience":[],"chunks":[{"anchor":"","end":1171,"heading":"","level":2,"start":0}],"headings":[],"keywords":["reference"],"level":2,"max_level":2,"path":"reference/index.md","section":"reference","section_title":"Reference","summary":"Lookup material generated from the code — case schema, command line, REST routes, agent tools — plus glossary, bibliography and the authoring guide for this site.","text":"The tables on these pages are produced at build time from the code that is deployed, so they cannot drift. Case schema L2 Every field of the case specification with its type, default and, where the model carries one, a description — generated from the pydantic model that validates it. Fields whose description cell is empty are explained in the Cases guide. Command-line reference L2 Every `gyre` sub-command with its options, generated from the program itself. REST API reference L2 All endpoints of the service, generated from its OpenAPI description. The live, interactive version is served by every deployment at /api/docs. MCP tool reference L2 The tools, resources and prompts the agent interface exposes, generated from the server definition. Glossary L1 Short definitions of the terms used across this documentation, with pointers to where each is explained. Bibliography L2 The literature Gyre's methods come from, grouped by topic. Authoring guide L2 How to write pages for this site: front matter, the four depth levels, callouts, mathematics, code, tabs, charts, tables, status badges, the field viewer and diagrams. Every feature is shown live on this page.","title":"Reference","url":"reference/index.html"},{"audience":[],"chunks":[{"anchor":"","end":5273,"heading":"","level":2,"start":0},{"anchor":"presets","end":6926,"heading":"Presets","level":2,"start":5274}],"headings":["Presets"],"keywords":["schema","CaseSpec","reference"],"level":2,"max_level":2,"path":"reference/case-schema.md","section":"reference","section_title":"Reference","summary":"Every field of the case specification with its type, default and, where the model carries one, a description — generated from the pydantic model that validates it. Fields whose description cell is empty are explained in the Cases guide.","text":"CaseSpec — Complete, serialisable description of a steady flow analysis. field type default description name string case (max length 80, pattern ^[A-Za-z0-9_.-]+$) description string mesh MeshSpec *required* flow FlowSpec discretization DiscretizationSpec solver SolverSpec reference ReferenceSpec MeshSpec — Built-in mesh generator and its parameters, or a mesh file ( generator: file ). field type default description generator airfoil_omesh | annulus | bump_channel | flat_plate | sphere_shell | box | file *required* Name of the built-in mesh generator, or ‘file’ for an external mesh (params: path, optional scale; Gmsh .msh 2.2/4.1 ASCII or Gyre .npz). params mapping Keyword arguments of the generator (e.g. n_surf, n_radial, r_far, code); unknown, missing or mistyped arguments are validation errors. geometry_order integer 4 Polynomial degree of the curved elements (generators only: a mesh file carries its own degree). (≥ 1, ≤ 8) FlowSpec — Free-stream / reference flow conditions (all differentiable inputs). field type default description mach number 0.5 Free-stream Mach number. (> 0.0, < 5.0) alpha_deg number 0.0 Angle of attack in degrees. (≥ -90.0, ≤ 90.0) beta_deg number 0.0 Sideslip angle in degrees (3-D only; ignored in 2-D). Free-stream direction: (cos a cos b, sin a cos b, sin b). (≥ -90.0, ≤ 90.0) reynolds number or null None Reynolds number (unit reference length); null means inviscid. prandtl number 0.72 (> 0.0) gamma number 1.4 (> 1.0) turbulence none | sa none Turbulence closure: ‘none’ (laminar or inviscid) or ‘sa’ (negative Spalart-Allmaras RANS model; requires reynolds). extra mapping Additional boundary data: p_back, pt_in, tt_in, t_wall, suth; with turbulence ‘sa’ also chi_inf (free-stream nu_tilde/nu, default 3) and prt (turbulent Prandtl number, default 0.9). DiscretizationSpec — Spatial discretization. field type default description order integer 2 Solution polynomial degree p (accuracy p+1); viscous cases need p >= 1. (≥ 0, ≤ 8) two_point_flux ranocha | chandrashekar | kennedy_gruber | central ranocha surface_flux roe | es_roe | hllc | llf roe Interface flux: ‘roe’ and ‘hllc’ are the classical solvers; ‘es_roe’ (matrix dissipation in entropy variables) and ‘llf’ (scalar dissipation) are entropy stable with an entropy-conservative two_point_flux. shock_capturing boolean False Sensor-based artificial viscosity for flows with shocks. av_coef number 1.0 Artificial-viscosity scale (multiples of h/p * (|u|+c)). (> 0.0, ≤ 10.0) boundaries mapping Boundary group -> condition; defaults are chosen per generator. SolverSpec — Nonlinear and linear solver controls. field type default description levels list of integer or auto | single auto p-multigrid degrees fine->coarse, ‘auto’ (p, p//2, …, 0) or ‘single’. tol_rel number 1e-10 (> 0.0, < 1.0) max_steps integer 150 (≥ 1, ≤ 5000) cfl0 number 5.0 (> 0.0) cfl_growth number 10.0 (≥ 1.0) cfl_shrink number 1.0 Factor applied to the CFL number after a step that the line search shortened to a quarter or less (1: hold the CFL). (> 0.0, ≤ 1.0) lin_tol number 0.01 (> 0.0, < 1.0) krylov_dim integer 40 (≥ 5, ≤ 400) restarts integer 2 (≥ 1, ≤ 50) smoother jacobi | gmres | line jacobi Smoother of the p-multigrid levels: damped block Jacobi, block-Jacobi GMRES, or line Jacobi (block-tridiagonal solves along lines of strongly coupled elements of an anisotropic mesh; block Jacobi where the mesh has none). precond_lag integer 1 Assemble the blocks of the smoother every this many nonlinear steps (1: every step). In between, the last blocks are reused. (≥ 1, ≤ 50) precond_precision double | single double Precision in which the blocks of the smoother are assembled, stored and applied. ‘single’ halves their memory, which is what bounds the size of a 3-D problem on one device; the residual, its Jacobian products, the Krylov vectors and the inverses stay in double precision, so the solution is unchanged and only the iteration count can differ. ReferenceSpec — Reference quantities of the force and moment coefficients. field type default description area number or null None Reference area (length in 2-D). Default: 1 in 2-D, the span of a spanwise-extruded generator mesh in 3-D, the frontal area of the body of a sphere_shell mesh. length number 1.0 Reference length of the moment coefficient. (> 0.0) moment_center list of number Point about which the moment is taken. groups list of string or null None Wall boundary groups over which the force, moment and heat-flux coefficients are integrated (the loads of one component). Default: every wall group of the mesh. The nodal load field f_surface and the surface displacement x_surface always cover all walls. components mapping or null None Named components of the walls, each a list of wall boundary groups. Every force, moment and heat-flux coefficient is then also an output per component, named _ (cd_wing, cl_tail, …), from the same solve and with its own derivatives. Names are letters and digits. The table is generated from the model, so a field has a description here only if the model declares one. The meaning of the remaining fields — the flux choices, the solver controls, flow.prandtl , flow.gamma and the keys of flow.extra — is given under Cases , and the generator parameters under Mesh generators .\npreset description elements DOF model naca0012_euler Inviscid subsonic flow over a NACA 0012 aerofoil (M=0.5, alpha=2 deg). 256 9,216 Euler, 2-D naca0012_laminar Laminar flow over a NACA 0012 aerofoil (M=0.5, alpha=1 deg, Re=5000). 480 17,280 Navier–Stokes, 2-D naca2412_euler Inviscid flow over a cambered NACA 2412 aerofoil (M=0.4, alpha=1 deg). 256 9,216 Euler, 2-D naca0012_transonic Transonic inviscid flow over a NACA 0012 aerofoil (M=0.8, alpha=1.25 deg) with sensor-based shock capturing. 1024 36,864 Euler, 2-D naca0012_euler_3d Spanwise-periodic 3-D extrusion of the inviscid NACA 0012 case (hexahedra): exercises the 3-D operators, solver and adjoint; coefficients per unit span. 192 25,920 Euler, 3-D cylinder_re40 Steady separated laminar flow past a circular cylinder (Re_D=40, M=0.2). 240 15,360 Navier–Stokes, 2-D sphere_euler Inviscid subsonic flow past a sphere (M=0.2) on a cubed-sphere mesh of curved hexahedra: a genuinely 3-D, unstructured-topology case whose exact solution has no drag and no entropy rise. 216 29,160 Euler, 3-D sphere_re100 Steady laminar flow past a sphere (Re_D=100, M=0.2): 3-D Navier-Stokes with a closed recirculation bubble; drag referred to the frontal area. 576 77,760 Navier–Stokes, 3-D flat_plate_sa Turbulent flat plate at zero pressure gradient (M=0.2, Re=5e6 per unit length) with the Spalart-Allmaras model: the NASA Turbulence Modeling Resource verification case on a coarse wall-resolved mesh. 150 6,750 Navier–Stokes, 2-D bump_euler Inviscid subsonic channel flow over a smooth Gaussian bump (M=0.5): the exact solution is isentropic, so the entropy error measures accuracy. 48 3,072 Euler, 2-D","title":"Case schema","url":"reference/case-schema.html"},{"audience":[],"chunks":[{"anchor":"","end":1000,"heading":"","level":2,"start":0},{"anchor":"gyre-presets","end":1077,"heading":"gyre presets","level":2,"start":1001},{"anchor":"gyre-schema","end":1153,"heading":"gyre schema","level":2,"start":1078},{"anchor":"gyre-run","end":2514,"heading":"gyre run","level":2,"start":1154},{"anchor":"gyre-mesh","end":2966,"heading":"gyre mesh","level":2,"start":2515},{"anchor":"gyre-mesh-info","end":3555,"heading":"gyre mesh info","level":2,"start":2967},{"anchor":"gyre-mesh-convert","end":3851,"heading":"gyre mesh convert","level":2,"start":3556},{"anchor":"gyre-mesh-export","end":4242,"heading":"gyre mesh export","level":2,"start":3852},{"anchor":"gyre-serve","end":4593,"heading":"gyre serve","level":2,"start":4243},{"anchor":"gyre-docs","end":4847,"heading":"gyre docs","level":2,"start":4594},{"anchor":"gyre-verify","end":5058,"heading":"gyre verify","level":2,"start":4848},{"anchor":"gyre-pipeline","end":5992,"heading":"gyre pipeline","level":2,"start":5059},{"anchor":"exit-codes","end":6963,"heading":"Exit codes","level":2,"start":5993}],"headings":["gyre presets","gyre schema","gyre run","gyre mesh","gyre serve","gyre docs","gyre verify","gyre pipeline","Exit codes"],"keywords":["CLI","command line","reference"],"level":2,"max_level":2,"path":"reference/cli.md","section":"reference","section_title":"Reference","summary":"Every `gyre` sub-command with its options, generated from the program itself.","text":"usage: gyre [-h] {presets,schema,run,mesh,serve,docs,verify,pipeline} ... Command-line interface: ``gyre <command>``. gyre presets list built-in cases gyre schema print the CaseSpec JSON schema gyre run naca0012_euler -o runs/a solve a preset or a spec file gyre run CASE --target-error cd=1e-4 raise the order until the error estimate of cd is met gyre mesh info wing.msh inspect, convert or export a mesh file gyre serve --port 8600 start the service (API + docs + MCP) gyre docs build build the documentation site gyre verify --suite quick run the verification pipeline gyre pipeline run pipelines/x.yaml execute a declarative pipeline positional arguments: {presets,schema,run,mesh,serve,docs,verify,pipeline} presets list built-in cases schema print the case JSON schema run solve a case mesh inspect, convert and export mesh files serve start the Gyre service docs documentation tools verify run the verification suite pipeline pipeline tools options: -h, --help show this help message and exit\nusage: gyre presets [-h] options: -h, --help show this help message and exit\nusage: gyre schema [-h] options: -h, --help show this help message and exit\nusage: gyre run [-h] [-o OUTPUT] [--set PATH=VALUE] [--gradients] [--of OF] [--wrt WRT] [--error ERROR] [--target-error OUTPUT=TOL] [--max-order N] [-q] case positional arguments: case preset name or YAML/JSON spec file options: -h, --help show this help message and exit -o OUTPUT, --output OUTPUT directory for artefacts --set PATH=VALUE override, e.g. flow.mach=0.6; the value is read as JSON (number, true/false, list), anything else as text, and a field that holds text keeps text (mesh.params.code=2412 is the string \"2412\") --gradients compute total derivatives --of OF comma-separated outputs for gradients --wrt WRT comma-separated inputs for gradients --error ERROR comma-separated outputs for adjoint error estimates; prints value, estimate, corrected and error per output (the per-element indicators are in result.json, with -o) --target-error OUTPUT=TOL choose the polynomial degree by accuracy: starting at the case's order, raise it until the adjoint error estimate of OUTPUT is at most TOL (absolute), e.g. cd=1e-4; gradients, fields and the printed outputs are those of the final degree; exit status 3 if TOL is not met at --max-order, 1 (with a message) if the request cannot work: OUTPUT is not a scalar output of the case, or --max-order is below the case's order --max-order N highest degree --target-error may try (default: 5) -q, --quiet\nusage: gyre mesh [-h] {info,convert,export} ... Tools for external meshes (Gmsh MSH 2.2 / 4.1 ASCII and Gyre NPZ). Each command prints a JSON summary; a file that cannot be used ends with a message and exit status 1. positional arguments: {info,convert,export} info read and validate a mesh file, print its summary convert convert between .msh and .npz export write the mesh of a preset or case file options: -h, --help show this help message and exit\nusage: gyre mesh info [-h] [--scale SCALE] file Read and validate a mesh file and print its summary as JSON: dimension, elements, geometry order, boundary groups, bounding box, quality numbers, size and SHA-256, and under \"lines\" the element lines that solver.smoother: line would use on this mesh (\"lines\": 0 if it has none) with the largest and the median cell anisotropy and the threshold at which a cell joins a line. positional arguments: file mesh file (.msh or .npz) options: -h, --help show this help message and exit --scale SCALE factor applied to the coordinates (default: 1.0)\nusage: gyre mesh convert [-h] IN OUT Read a mesh file (which validates it) and write it in the format given by the extension of OUT (.msh or .npz). positional arguments: IN mesh file to read (.msh or .npz) OUT mesh file to write (.msh or .npz) options: -h, --help show this help message and exit\nusage: gyre mesh export [-h] [--set PATH=VALUE] case OUT Build (or read) the mesh of a case and write it to OUT (.msh or .npz), e.g. to view a generator mesh in Gmsh. positional arguments: case preset name or YAML/JSON spec file OUT mesh file to write (.msh or .npz) options: -h, --help show this help message and exit --set PATH=VALUE override as for `gyre run`, e.g. mesh.params.n_surf=64\nusage: gyre serve [-h] [--host HOST] [--port PORT] [--data DATA] [--site SITE] [--workers WORKERS] options: -h, --help show this help message and exit --host HOST bind address (default: all interfaces) --port PORT --data DATA job/session storage directory --site SITE built documentation directory to serve --workers WORKERS concurrent solver workers\nusage: gyre docs [-h] [-o OUTPUT] [--verification VERIFICATION] {build} positional arguments: {build} options: -h, --help show this help message and exit -o OUTPUT, --output OUTPUT --verification VERIFICATION directory with verification results to embed\nusage: gyre verify [-h] [--suite {quick,full}] [-o OUTPUT] [--only ONLY] options: -h, --help show this help message and exit --suite {quick,full} -o OUTPUT, --output OUTPUT --only ONLY comma-separated study ids\nusage: gyre pipeline [-h] [-o OUTPUT] [--force] [--param NAME=VALUE] [--param-json NAME=JSON] [--stage ID] [--record FILE] {run} file positional arguments: {run} file options: -h, --help show this help message and exit -o OUTPUT, --output OUTPUT --force ignore the stage cache --param NAME=VALUE override a pipeline parameter; the value is read like --set (JSON, else text; a parameter whose default is text keeps text); a name the pipeline does not declare is refused --param-json NAME=JSON override a pipeline parameter with a JSON value taken exactly as given (for programs: 2412 is a number and '\"2412\"' text, whatever the declared default is); wins over --param for the same name --stage ID run only this stage and the stages it depends on (repeatable); the other stages are recorded as skipped, reason 'not selected' --record FILE also write this run's record to FILE (<output>/<name>/pipeline.json is replaced by the next run)\ngyre run exits 0 on a converged solve and 2 if the solver did not converge. With --target-error it exits 0 only if the tolerance was also met, 3 if the solve converged but the tolerance was not met at --max-order , and 1, with a message and before any flow is solved, if the request cannot work (the tolerance is not a positive number, --max-order is given without --target-error , is below 1 or below the case’s order, or the output is not a scalar output of the case). Every gyre mesh command exits 0 after printing its JSON summary; if it cannot use a file or case it prints gyre mesh <action>: <message> to standard error, nothing to standard output, and exits 1. gyre verify ends with the line P/R studies of this invocation passed , followed by ; stale failing results not re-run: id, id when failing results of earlier invocations are in the output directory; it exits 1 only if a study of this invocation failed. gyre pipeline run exits 1 if the pipeline failed.","title":"Command-line reference","url":"reference/cli.html"},{"audience":[],"chunks":[{"anchor":"","end":66,"heading":"","level":2,"start":0},{"anchor":"meta","end":406,"heading":"Meta","level":2,"start":67},{"anchor":"cases","end":851,"heading":"Cases","level":2,"start":407},{"anchor":"meshes","end":1567,"heading":"Meshes","level":2,"start":852},{"anchor":"jobs","end":2552,"heading":"Jobs","level":2,"start":1568},{"anchor":"sessions","end":4258,"heading":"Sessions","level":2,"start":2553},{"anchor":"pipelines","end":5779,"heading":"Pipelines","level":2,"start":4259}],"headings":["Meta","Cases","Meshes","Jobs","Sessions","Pipelines"],"keywords":["REST","API","endpoints","OpenAPI"],"level":2,"max_level":2,"path":"reference/rest-api.md","section":"reference","section_title":"Reference","summary":"All endpoints of the service, generated from its OpenAPI description. The live, interactive version is served by every deployment at /api/docs.","text":"The Service API guide explains usage; this page lists every route.\nmethod path description GET /api/v1/health Liveness probe; never needs a token. GET /api/v1/info Identity of this deployment: version, source revision, process id, device, uptime, operator limits and allowed host names; never needs a token. GET /api/v1/metrics Prometheus text exposition: build, uptime, job, session and pipeline counters.\nmethod path description GET /api/v1/presets Built-in cases: name, description and size estimate of each. GET /api/v1/presets/{name} The complete CaseSpec of a preset (404 UnknownPreset). GET /api/v1/schema/case JSON Schema of a case specification (CaseSpec). GET /api/v1/schema/job JSON Schema of a job request (the body of POST /jobs). POST /api/v1/cases/validate Dry run: resolve a case, report its size, where it would execute and its names.\nmethod path description GET /api/v1/meshes Meshes of the mesh store under meshes , each with its summary (see GET /meshes/{name}). PUT /api/v1/meshes/{name} Store a mesh file (Gmsh .msh 2.2 / 4.1 ASCII, or Gyre .npz ) under name . GET /api/v1/meshes/{name} Summary of a stored mesh: name, bytes, sha256, dim, elements, geometry_order, nodes, boundaries, bounding_box, quality numbers. 404 UnknownMesh; 422 InvalidMesh for a file of the store’s directory that is not a usable mesh (placed there by hand). DELETE /api/v1/meshes/{name} Remove a mesh from the store. Jobs that were queued on it will fail; finished results and open sessions are unaffected (a session has read its mesh when it started). 404 UnknownMesh.\nmethod path description POST /api/v1/jobs Queue a batch analysis; returns immediately with the job record. GET /api/v1/jobs Jobs, newest first; state filters (queued, running, succeeded, failed, cancelled, interrupted). GET /api/v1/jobs/{job_id} Job record; wait long-polls up to that many seconds for completion. DELETE /api/v1/jobs/{job_id} Cancel a queued or running job (its worker process is terminated); returns the job record. A job that has already ended is returned unchanged. GET /api/v1/jobs/{job_id}/result Result of a finished job: outputs, history, provenance and, if requested, gradients, error estimates and the order selection. 409 NotReady before the job has succeeded. GET /api/v1/jobs/{job_id}/events Progress events; stream=true returns a Server-Sent-Events stream. GET /api/v1/jobs/{job_id}/artifacts/{name} Download a file of a job: result.json, solution.npz (checkpoint), flow.vtu, field.json, events.jsonl, request.json, error.txt (traceback of a failed job).\nmethod path description GET /api/v1/sessions Open sessions with their case, device, call counts and idle time. POST /api/v1/sessions Start a warm solver process for repeated evaluations of one case. GET /api/v1/sessions/{sid} A session: case, device, counters and interface (names, shapes and current values of the inputs; the outputs); commands when the solver process reports the commands of POST /sessions/{id}/{command}. DELETE /api/v1/sessions/{sid} Close a session and end its solver process (its flow state is discarded). POST /api/v1/sessions/{sid}/evaluate Set inputs, converge the flow (warm start) and return outputs. POST /api/v1/sessions/{sid}/gradients Total derivatives of scalar outputs at the current converged state. POST /api/v1/sessions/{sid}/adjoint Reverse total product (d outputs / d inputs)^T seeds (coupled-adjoint block). POST /api/v1/sessions/{sid}/error-estimate Adjoint-based discretization-error estimates of scalar outputs. GET /api/v1/sessions/{sid}/surface Coordinates of the surface nodes that x_surface displaces. GET /api/v1/sessions/{sid}/distributions Wall distributions (cp, cf) of the converged state. 422 RuntimeError before the first evaluation and while the last evaluation did not converge. GET /api/v1/sessions/{sid}/field Flow fields of the converged state for plotting. 422 RuntimeError before the first evaluation and while the last evaluation did not converge. GET /api/v1/sessions/{sid}/state Current inputs, whether a flow state exists ( solved ), whether the last evaluation converged ( converged ) and the residual norm of the kept state. POST /api/v1/sessions/{sid}/{command} Run any command of the session’s solver process: the generic pass-through.\nmethod path description GET /api/v1/pipelines Pipeline definitions of this deployment (name, stages, params with their defaults, parameters : every name a run may set, runnable ; reason when a definition cannot be started through the service). POST /api/v1/pipelines/{name}/runs Start a pipeline run; unchanged stages are served from the stage cache. GET /api/v1/pipelines/runs Pipeline runs, newest first. GET /api/v1/pipelines/runs/{run_id} Run status and progress; wait long-polls up to that many seconds for the end. DELETE /api/v1/pipelines/runs/{run_id} Cancel an active pipeline run: its process group (the engine and the stage it is running) is terminated. Returns the run’s status document, cancelled once the processes have ended (the call waits up to wait seconds for that; otherwise cancel_requested is true and the status follows). A run that has already ended is returned unchanged. Stages that completed stay in the stage cache. GET /api/v1/pipelines/runs/{run_id}/artifacts/{stage} Names of the files stage stage of this run left behind (under artifacts ). GET /api/v1/pipelines/runs/{run_id}/artifacts/{stage}/{name} Download a file a stage of this run produced: report.md and report.json of a report stage, result.json, solution.npz and flow.vtu of a solve stage, sweep.json, orders.json, stage.log. 404 when the stage did not succeed in this run, the name is not a plain file name, or a later run of the pipeline has replaced the stage’s files (stage directories are shared by the runs of a pipeline).","title":"REST API reference","url":"reference/rest-api.html"},{"audience":[],"chunks":[{"anchor":"","end":59,"heading":"","level":2,"start":0},{"anchor":"gyre-info","end":141,"heading":"gyre_info","level":2,"start":60},{"anchor":"list-presets","end":224,"heading":"list_presets","level":2,"start":142},{"anchor":"get-case-schema","end":298,"heading":"get_case_schema","level":2,"start":225},{"anchor":"validate-case","end":626,"heading":"validate_case","level":2,"start":299},{"anchor":"submit-job","end":2199,"heading":"submit_job","level":2,"start":627},{"anchor":"get-job","end":2432,"heading":"get_job","level":2,"start":2200},{"anchor":"get-job-result","end":2883,"heading":"get_job_result","level":2,"start":2433},{"anchor":"get-job-events","end":3053,"heading":"get_job_events","level":2,"start":2884},{"anchor":"list-jobs","end":3195,"heading":"list_jobs","level":2,"start":3054},{"anchor":"cancel-job","end":3276,"heading":"cancel_job","level":2,"start":3196},{"anchor":"get-job-artifact","end":4057,"heading":"get_job_artifact","level":2,"start":3277},{"anchor":"open-session","end":4444,"heading":"open_session","level":2,"start":4058},{"anchor":"session-evaluate","end":4930,"heading":"session_evaluate","level":2,"start":4445},{"anchor":"session-gradients","end":5407,"heading":"session_gradients","level":2,"start":4931},{"anchor":"session-error-estimate","end":5636,"heading":"session_error_estimate","level":2,"start":5408},{"anchor":"session-adjoint","end":6731,"heading":"session_adjoint","level":2,"start":5637},{"anchor":"session-surface","end":7036,"heading":"session_surface","level":2,"start":6732},{"anchor":"session-state","end":7251,"heading":"session_state","level":2,"start":7037},{"anchor":"get-session","end":7483,"heading":"get_session","level":2,"start":7252},{"anchor":"session-command","end":8517,"heading":"session_command","level":2,"start":7484},{"anchor":"list-sessions","end":8601,"heading":"list_sessions","level":2,"start":8518},{"anchor":"close-session","end":8699,"heading":"close_session","level":2,"start":8602},{"anchor":"list-meshes","end":8854,"heading":"list_meshes","level":2,"start":8700},{"anchor":"upload-mesh","end":9555,"heading":"upload_mesh","level":2,"start":8855},{"anchor":"inspect-mesh","end":9745,"heading":"inspect_mesh","level":2,"start":9556},{"anchor":"delete-mesh","end":9858,"heading":"delete_mesh","level":2,"start":9746},{"anchor":"list-pipelines","end":10146,"heading":"list_pipelines","level":2,"start":9859},{"anchor":"run-pipeline","end":10842,"heading":"run_pipeline","level":2,"start":10147},{"anchor":"get-pipeline-run","end":11438,"heading":"get_pipeline_run","level":2,"start":10843},{"anchor":"list-pipeline-runs","end":11567,"heading":"list_pipeline_runs","level":2,"start":11439},{"anchor":"cancel-pipeline-run","end":11855,"heading":"cancel_pipeline_run","level":2,"start":11568},{"anchor":"get-pipeline-artifact","end":12514,"heading":"get_pipeline_artifact","level":2,"start":11856},{"anchor":"search-docs","end":12867,"heading":"search_docs","level":2,"start":12515},{"anchor":"read-doc","end":13119,"heading":"read_doc","level":2,"start":12868},{"anchor":"resources","end":13229,"heading":"Resources","level":2,"start":13120},{"anchor":"prompts","end":13408,"heading":"Prompts","level":2,"start":13230}],"headings":["gyre_info","list_presets","get_case_schema","validate_case","submit_job","get_job","get_job_result","get_job_events","list_jobs","cancel_job","get_job_artifact","open_session","session_evaluate","session_gradients","session_error_estimate","session_adjoint","session_surface","session_state","get_session","session_command","list_sessions","close_session","list_meshes","upload_mesh","inspect_mesh","delete_mesh","list_pipelines","run_pipeline","get_pipeline_run","list_pipeline_runs","cancel_pipeline_run","get_pipeline_artifact","search_docs","read_doc","Resources","Prompts"],"keywords":["MCP","tools","agents","reference"],"level":2,"max_level":2,"path":"reference/mcp-tools.md","section":"reference","section_title":"Reference","summary":"The tools, resources and prompts the agent interface exposes, generated from the server definition.","text":"See the Agent interface guide for connection and workflows.\nIdentify this Gyre deployment: version, source commit, device, endpoints, uptime.\nList built-in cases (name, description, size estimate) under presets . Start here.\nJSON Schema of a case specification (mesh, flow, discretization, solver).\nDry-run a case without solving: resolved spec, element/DOF estimate, chosen device. case is a preset name or a full CaseSpec object; overrides are dotted paths such as {“flow.mach”: 0.6, “discretization.order”: 3, “mesh.params.n_surf”: 48}. argument type required default case string or mapping yes overrides mapping or null no\nQueue a steady flow analysis and return the job record. Names are checked when the job is accepted: an unknown output or input in gradients_of , gradients_wrt , error_estimate or adapt_output is refused at once (UnknownName; the hint lists the valid names, as does interface of validate_case ). Also refused: a case above the deployment’s size limit (CaseTooLarge) and a full queue (QueueFull); gyre_info shows the limits. Set gradients_of (e.g. [“cl”,”cd”]) and/or gradients_wrt (e.g. [“alpha”,”mach”, “x_surface”]) to also compute total derivatives by the discrete adjoint; alpha_deg is accepted in gradients_wrt wherever alpha is (the derivative is then per degree). Set error_estimate (e.g. [“cd”]) for adjoint-weighted-residual error estimates. Set adapt_output (e.g. “cd”) together with adapt_tolerance (absolute, e.g. 1e-4) to let the solver choose the polynomial degree: it is raised from the case’s discretization.order up to adapt_max_order until the estimated error of that output meets the tolerance; the result is that of the final degree and its adaptation section says whether the tolerance was met. wait_seconds (<= 300) long-polls for completion so short jobs return in one call. argument type required default case string or mapping yes overrides mapping or null no gradients_of list of string or null no gradients_wrt list of string or null no error_estimate list of string or null no device string no \"auto\" label string or null no wait_seconds number no 0.0 adapt_output string or null no adapt_tolerance number or null no adapt_max_order integer no 5\nJob state (queued/running/succeeded/failed/cancelled), progress and result summary. wait_seconds (<= 300) blocks until the job finishes or the time elapses. argument type required default job_id string yes wait_seconds number no 0.0\nResult of a finished job. sections selects parts: outputs, gradients, error_estimate, adaptation, history, surface, case, spec, timing, provenance (default: outputs, gradients, error_estimate, adaptation, case, timing, provenance). Arrays are summarised unless full_arrays is true. Values that are not finite (a diverged solve) are null. argument type required default job_id string yes sections list of string or null no full_arrays boolean no false\nProgress events of a job (nonlinear iterations etc.) starting at sequence since . argument type required default job_id string yes since integer no 0 limit integer no 40\nRecent jobs under jobs , newest first; optionally filter by state. argument type required default state string or null no limit integer no 20\nCancel a queued or running job. argument type required default job_id string yes\nRead a file a job produced. name is one of result.json (complete result), events.jsonl (progress events), request.json, error.txt (traceback of a failed job), field.json (sampled 2-D fields for plotting), solution.npz (checkpoint of the converged state, binary) and flow.vtu (VTK file for ParaView). Text files are returned under content , paged with offset and max_chars ( truncated says whether more follows). Binary and large files are described instead: bytes , sha256 and download , the REST path from which a program fetches the file; set max_base64_bytes to also receive the content as base64 when the file is not larger than that. argument type required default job_id string yes name string yes max_chars integer no 12000 offset integer no 0 max_base64_bytes integer no 0\nOpen a warm analysis session (a dedicated solver process) for repeated evaluations. Returns the session id and its interface: the available inputs and outputs. Use sessions for parameter sweeps, design loops and coupled analyses; close them when done. argument type required default case string or mapping yes overrides mapping or null no device string no \"auto\" label string or null no\nSet inputs (e.g. {“alpha_deg”: 3.0, “mach”: 0.55}), converge the flow, return outputs. Solves are warm-started from the previous state, so nearby evaluations are cheap. If converged is false the outputs are those of the iterate the solve ended at; the session keeps its last converged state, and gradients / error estimates are refused until an evaluation converges again. argument type required default session_id string yes inputs mapping or null no outputs list of string or null no\nExact total derivatives d(of)/d(wrt) at the session’s current converged state. wrt may contain flow parameters and “x_surface” (shape sensitivities at every surface node, summarised unless full_arrays ); alpha_deg is accepted wherever alpha is and returns the derivative per degree. mode: auto | adjoint | direct. argument type required default session_id string yes of list of string or null no wrt list of string or null no mode string no \"auto\" full_arrays boolean no false\nAdjoint-weighted-residual discretization-error estimate for outputs of . Returns the estimate, a corrected value and per-element indicators (summarised). argument type required default session_id string yes of list of string yes\nReverse (adjoint) total product at the session’s converged state: the coupled-adjoint building block. Returns derivatives[input] = (d outputs / d inputs)^T seeds with one flow-adjoint solve, whatever the number of seeded outputs. seeds maps output names to cotangents: a number for a scalar output (e.g. {“cl”: 1.0}) and, for the surface loads f_surface , an array with one row per surface node (the shape is interface.outputs. f_surface.shape of open_session ; session_surface gives the nodes). wrt selects inputs (default: all): flow parameters and x_surface (the result is then the sensitivity with respect to every surface-node displacement, summarised unless full_arrays ); alpha_deg is accepted wherever alpha is. In a coupled aero-structural adjoint the structural solver supplies the f_surface seed and receives the x_surface product. Refused (RuntimeError) while the last evaluation did not converge. info reports the linear solve (iterations, converged). argument type required default session_id string yes seeds mapping yes wrt list of string or null no full_arrays boolean no false\nSurface nodes of the session’s case: coordinates (one row per node, the nodes that the input x_surface displaces and on which f_surface acts), the current displacement and nodes . Arrays are summarised unless full_arrays . argument type required default session_id string yes full_arrays boolean no false\nCurrent inputs of a session, whether a flow state exists ( solved ), whether the last evaluation converged ( converged ) and the residual norm of the kept state. argument type required default session_id string yes\nA session’s case, device, counters and interface (inputs with shapes and current values, outputs); commands lists what session_command accepts when the solver process reports it. argument type required default session_id string yes\nRun any command of the session’s solver process (generic pass-through). For commands without a dedicated tool: distributions (wall cp / cf), field , and whatever else the session’s solver process implements - residual evaluation at the current or other inputs, forward (tangent) total products, state download and upload, checkpoint and restore, reverting a failed evaluation ( commands of get_session lists them when the session reports them). arguments is the JSON object of the command’s keyword arguments. For a command that takes bytes (a state or checkpoint to upload) pass them as data_base64 ; a command that returns bytes is answered with bytes and sha256 , plus base64 when the result is not larger than max_base64_bytes . An unknown command is refused with UnknownCommand. Arrays in a JSON result are summarised unless full_arrays . argument type required default session_id string yes command string yes arguments mapping or null no data_base64 string or null no max_base64_bytes integer no 0 full_arrays boolean no false\nOpen sessions (under sessions ) with their case, device, call counts and idle time.\nClose a session and free its solver process. argument type required default session_id string yes\nMeshes in this deployment’s mesh store, under meshes : name, size, element count, geometry order, boundary groups (faces per group), bounding box, sha256.\nStore a mesh file so that cases can use it; returns its summary. name is a bare file name ending in .msh (Gmsh MSH 2.2 / 4.1 ASCII, quadrilaterals or hexahedra, a named physical group on every boundary) or .npz (Gyre mesh). content is the file: its text with encoding “text”, or base64 with encoding “base64” (required for .npz). The file is validated in full; an unusable file is refused (InvalidMesh, with the reason) and not stored. Then use mesh = {“generator”: “file”, “params”: {“path”: name}} in a case and give a boundary condition for each group listed under boundaries in discretization.boundaries. argument type required default name string yes content string yes encoding string no \"text\"\nSummary of a stored mesh: bytes, sha256, dim, elements, geometry_order, nodes, boundaries (faces per group), bounding_box and quality numbers. argument type required default name string yes\nRemove a mesh from the store (jobs still queued on it will fail). argument type required default name string yes\nDeclarative pipelines of this deployment, under pipelines . Each entry has name, description, parameters, stages and runnable . A pipeline with runnable: false cannot be started through the service; its reason says why (e.g. it contains shell stages, which only the operator may enable).\nStart a pipeline run (a DAG of cached stages: solves, sweeps, gates, reports). Returns the run id; poll get_pipeline_run , stop it with cancel_pipeline_run . Unchanged stages are reused from the cache unless force is true. params override the pipeline’s parameters ( parameters of list_pipelines ; any other name is refused with UnknownParameter); strings stay strings (“123” is not turned into a number). Only pipelines that list_pipelines reports as runnable are started; runs of the same pipeline execute one after another, and the deployment caps the number of active runs (TooManyPipelineRuns). argument type required default name string yes params mapping or null no force boolean no false\nStatus and progress of a pipeline run. status is running, succeeded, failed, cancelled or interrupted; stages maps every stage to pending / running / succeeded / cached / failed / skipped / cancelled; while it runs, current_stage and stage_log_tail show what the running stage is doing (a solve logs each nonlinear step) and phase is “waiting” while another run of the same pipeline holds the work directory. record has per-stage outputs and timings. wait_seconds (<= 300) blocks until the run ends or the time elapses. argument type required default run_id string yes wait_seconds number no 0.0\nRecent pipeline runs under runs , newest first (id, pipeline, status, times). argument type required default limit integer no 20\nCancel an active pipeline run (the engine and the stage it is running are terminated); returns the run’s status, cancelled once its processes have ended. Stages that had completed stay in the stage cache. A finished run is returned as is. argument type required default run_id string yes\nFiles a stage of a pipeline run produced. Without name : the list of file names under artifacts (e.g. report.md and report.json of a report stage; result.json, flow.vtu and solution.npz of a solve stage; sweep.json; orders.json; stage.log). With name : the file; text is returned under content (paged with offset / max_chars ), binary files are described by bytes , sha256 and the REST path download . Available for stages that succeeded (or were served from the cache) in this run, until a later run replaces their files. argument type required default run_id string yes stage string yes name string or null no max_chars integer no 12000 offset integer no 0\nFull-text search of the Gyre documentation served with this deployment. Returns ranked pages with title, section, depth level, summary and a path for read_doc . Covers overview, user guide, examples, verification evidence, software engineering and the mathematical / numerical methods. argument type required default query string yes limit integer no 6\nRead a documentation page as Markdown (math in LaTeX). path comes from search_docs (or use “llms.txt” for the site index). Long pages are paged with offset . argument type required default path string yes max_chars integer no 12000 offset integer no 0\nURI description gyre://presets Built-in case presets gyre://schema/case JSON Schema of the case specification\nname description analyze_case Run a case, check convergence, report outputs with error estimates. gradient_check Verify adjoint gradients against finite differences in a session.","title":"MCP tool reference","url":"reference/mcp-tools.html"},{"audience":[],"chunks":[{"anchor":"","end":5281,"heading":"","level":1,"start":0}],"headings":[],"keywords":["glossary","terms","definitions"],"level":1,"max_level":1,"path":"reference/glossary.md","section":"reference","section_title":"Reference","summary":"Short definitions of the terms used across this documentation, with pointers to where each is explained.","text":"Term Meaning x_surface / f_surface Surface-node displacements (input) and consistent nodal loads (output): the coupling fields. Adjoint (discrete) Solution \\boldsymbol\\psi of the transposed linearised residual equation with an output’s state derivative as right-hand side; yields derivatives with respect to all inputs at once. Theory Adjoint consistency Property that the discrete adjoint is a consistent discretization of the continuous adjoint problem; enables superconvergent outputs and sharp error estimates. Block Jacobi Preconditioner that inverts the element-diagonal blocks of the Jacobian. BR2 Second Bassi–Rebay scheme for viscous terms: local gradient plus face liftings, compact stencil. Theory Case specification The document ( CaseSpec ) describing mesh, flow, discretization and solver. Guide Checkpoint A saved point of a discipline or session (inputs and state) from which it can be restored or rebuilt. Guide Cubed sphere Mesh topology of six blocks, one per face of a cube, projected onto a sphere; three blocks meet at each corner, so it is unstructured. Example Deployment A running service instance together with the documentation built from its revision. DGSEM Discontinuous Galerkin spectral element method: nodal DG on tensor-product Gauss-type points. Theory Direct method Derivative computation by one linearised solve per input. Discipline In MDO, one analysis (here: the flow solver) exposing inputs, outputs and derivatives. Guide Eddy viscosity Viscosity \\mu_t that a turbulence closure adds to the molecular viscosity seen by the mean flow. Theory Effectivity Ratio of an estimated to an actual error (one is ideal). Entropy conservative / stable A scheme whose discrete entropy is conserved / cannot grow, mirroring the continuous equations. Entropy-stable flux Interface flux that produces no entropy for any pair of states; in Gyre es_roe (matrix dissipation in entropy variables) and llf . Theory FGMRES Flexible GMRES: Krylov method tolerating a preconditioner that changes between iterations. Flux differencing Writing volume terms as differences of symmetric two-point fluxes. Free-stream preservation A uniform flow remains exactly uniform on a curved mesh. JIT Just-in-time compilation of traced JAX functions by XLA. JVP / VJP Jacobian–vector / vector–Jacobian product (forward / reverse mode AD). Law of the wall Universal velocity profile of a turbulent boundary layer in wall units: u^+ = y^+ in the sublayer, logarithmic further out. Example LGL Legendre–Gauss–Lobatto points: quadrature nodes that include the interval end points. Lifting Mapping of face jumps into an element-wise gradient correction. Line smoother Smoother that solves the block-tridiagonal system of each line of strongly coupled elements exactly; for the thin cells of a wall-resolved mesh. Mixed precision Using single precision for a part of a computation that need not be exact; here the stored blocks of the smoother, while residual, Jacobian products and Krylov vectors stay double. Matrix-free Applying a linear map without storing its matrix; here the Jacobian (always) and the mesh deformation (above a size limit). MCP Model Context Protocol: standard through which AI agents call tools. Guide MDO Multidisciplinary design optimization. Mesh store The directory of a deployment that holds uploaded mesh files; a case refers to a stored mesh by its name. Guide MMS Method of manufactured solutions: forcing the equations so that a chosen function is the exact solution. Order selection Raising the polynomial degree of a case until the adjoint-based error estimate of one output meets a tolerance ( adapt , --target-error ). p-multigrid Multigrid over polynomial degrees on a fixed mesh. Physical group In Gmsh, a named set of curves or surfaces; the physical groups of the boundary become the boundary groups of an imported mesh. Guide Provenance Revision, device and time attached to a result. PTC Pseudo-transient continuation: globalised Newton iteration with an artificial time step. RANS Reynolds-averaged Navier–Stokes equations: the mean flow of a turbulent flow, closed by a turbulence model. Theory SA-neg The Spalart–Allmaras one-equation turbulence model in its negative form, which stays well posed when \\tilde\\nu undershoots below zero. Theory SBP Summation by parts: discrete integration by parts. Session A long-lived solver process in the service for repeated evaluations. Sideslip Angle \\beta between the free stream and the x – y plane in 3-D; input beta . Theory Split-argument residual Residual taking the state in “own” and “neighbour” roles; its own-derivative is block diagonal. Tailnet A private network of devices (Tailscale); Gyre services bind to all interfaces to be reachable on it. Total derivative Derivative of an output including the implicit dependence through the converged state. Two-point flux Symmetric numerical flux of two states used inside elements. Theory Wall distance Distance d to the nearest no-slip wall, an input of the turbulence model; computed to the curved wall and differentiated with the geometry. Theory Wall units Lengths and velocities scaled with the friction velocity and the viscosity: y^+ , u^+ . Weak-form residual Residual scaled by the diagonal mass matrix, \\mathbf R_w = (wJ)\\mathbf R .","title":"Glossary","url":"reference/glossary.html"},{"audience":[],"chunks":[{"anchor":"","end":91,"heading":"","level":2,"start":0},{"anchor":"split-form-and-entropy-stable-dg","end":961,"heading":"Split-form and entropy-stable DG","level":2,"start":92},{"anchor":"two-point-and-interface-fluxes","end":2432,"heading":"Two-point and interface fluxes","level":2,"start":962},{"anchor":"curvilinear-meshes","end":2736,"heading":"Curvilinear meshes","level":2,"start":2433},{"anchor":"viscous-discretization-and-adjoint-consistency","end":3881,"heading":"Viscous discretization and adjoint consistency","level":2,"start":2737},{"anchor":"turbulence-modelling","end":4434,"heading":"Turbulence modelling","level":2,"start":3882},{"anchor":"implicit-solvers","end":5353,"heading":"Implicit solvers","level":2,"start":4435},{"anchor":"adjoints-error-estimation-and-mdo","end":6544,"heading":"Adjoints, error estimation and MDO","level":2,"start":5354},{"anchor":"reference-data","end":7589,"heading":"Reference data","level":2,"start":6545},{"anchor":"software","end":7720,"heading":"Software","level":2,"start":7590},{"anchor":"relevant-to-the-stated-limits","end":8031,"heading":"Relevant to the stated limits","level":2,"start":7721}],"headings":["Split-form and entropy-stable DG","Two-point and interface fluxes","Curvilinear meshes","Viscous discretization and adjoint consistency","Turbulence modelling","Implicit solvers","Adjoints, error estimation and MDO","Reference data","Software","Relevant to the stated limits"],"keywords":["references","literature","bibliography"],"level":2,"max_level":2,"path":"reference/bibliography.md","section":"reference","section_title":"Reference","summary":"The literature Gyre's methods come from, grouped by topic.","text":"Bibliographic details were entered by hand; check them against the publisher before citing.\nE. Tadmor (1987). The numerical viscosity of entropy stable schemes for systems of conservation laws. I. Mathematics of Computation 49, 91–103. T. C. Fisher, M. H. Carpenter (2013). High-order entropy stable finite difference schemes for nonlinear conservation laws: finite domains. Journal of Computational Physics 252, 518–557. M. H. Carpenter, T. C. Fisher, E. J. Nielsen, S. H. Frankel (2014). Entropy stable spectral collocation schemes for the Navier–Stokes equations: discontinuous interfaces. SIAM Journal on Scientific Computing 36(5), B835–B867. G. J. Gassner, A. R. Winters, D. A. Kopriva (2016). Split form nodal discontinuous Galerkin schemes with summation-by-parts property for the compressible Euler equations. Journal of Computational Physics 327, 39–66. D. A. Kopriva (2009). Implementing Spectral Methods for Partial Differential Equations. Springer.\nF. Ismail, P. L. Roe (2009). Affordable, entropy-consistent Euler flux functions II: entropy production at shocks. Journal of Computational Physics 228, 5410–5436. P. Chandrashekar (2013). Kinetic energy preserving and entropy stable finite volume schemes for compressible Euler and Navier–Stokes equations. Communications in Computational Physics 14(5), 1252–1286. H. Ranocha (2018). Generalised summation-by-parts operators and entropy stability of numerical methods for hyperbolic balance laws. PhD thesis, TU Braunschweig. C. A. Kennedy, A. Gruber (2008). Reduced aliasing formulations of the convective terms within the Navier–Stokes equations for a compressible fluid. Journal of Computational Physics 227, 1676–1700. P. L. Roe (1981). Approximate Riemann solvers, parameter vectors, and difference schemes. Journal of Computational Physics 43, 357–372. E. F. Toro, M. Spruce, W. Speares (1994). Restoration of the contact surface in the HLL-Riemann solver. Shock Waves 4, 25–34. T. J. Barth (1999). Numerical methods for gasdynamic systems on unstructured meshes. In An Introduction to Recent Developments in Theory and Numerics for Conservation Laws , Lecture Notes in Computational Science and Engineering 5, Springer, 195–285. A. R. Winters, D. Derigs, G. J. Gassner, S. Walch (2017). A uniquely defined entropy stable matrix dissipation operator for high Mach number ideal MHD and compressible Euler simulations. Journal of Computational Physics 332, 274–289.\nD. A. Kopriva (2006). Metric identities and the discontinuous spectral element method on curvilinear meshes. Journal of Scientific Computing 26(3), 301–327. E. Luke, E. Collins, E. Blades (2012). A fast mesh deformation method using explicit interpolation. Journal of Computational Physics 231, 586–601.\nF. Bassi, S. Rebay (1997). A high-order accurate discontinuous finite element method for the numerical solution of the compressible Navier–Stokes equations. Journal of Computational Physics 131, 267–279. F. Bassi, S. Rebay, G. Mariotti, S. Pedinotti, M. Savini (1997). A high-order accurate discontinuous finite element method for inviscid and viscous turbomachinery flows. Proc. 2nd European Conference on Turbomachinery Fluid Dynamics and Thermodynamics. D. N. Arnold, F. Brezzi, B. Cockburn, L. D. Marini (2002). Unified analysis of discontinuous Galerkin methods for elliptic problems. SIAM Journal on Numerical Analysis 39(5), 1749–1779. R. Hartmann (2007). Adjoint consistency analysis of discontinuous Galerkin discretizations. SIAM Journal on Numerical Analysis 45(6), 2671–2696. R. Hartmann, T. Leicht (2015). Generalized adjoint consistent treatment of wall boundary conditions for compressible flows. Journal of Computational Physics 300, 754–778. G. J. Gassner, A. R. Winters, F. J. Hindenlang, D. A. Kopriva (2018). The BR1 scheme is stable for the compressible Navier–Stokes equations. Journal of Scientific Computing 77, 154–200.\nP. R. Spalart, S. R. Allmaras (1994). A one-equation turbulence model for aerodynamic flows. La Recherche Aérospatiale 1, 5–21. S. R. Allmaras, F. T. Johnson, P. R. Spalart (2012). Modifications and clarifications for the implementation of the Spalart–Allmaras turbulence model. Seventh International Conference on Computational Fluid Dynamics (ICCFD7) , paper ICCFD7-1902. T. A. Oliver, D. L. Darmofal (2009). Analysis of dual consistency for discontinuous Galerkin discretizations of source terms. SIAM Journal on Numerical Analysis 47(5), 3507–3525.\nD. A. Knoll, D. E. Keyes (2004). Jacobian-free Newton–Krylov methods: a survey of approaches and applications. Journal of Computational Physics 193, 357–397. Y. Saad (1993). A flexible inner-outer preconditioned GMRES algorithm. SIAM Journal on Scientific Computing 14(2), 461–469. C. T. Kelley, D. E. Keyes (1998). Convergence analysis of pseudo-transient continuation. SIAM Journal on Numerical Analysis 35(2), 508–523. K. J. Fidkowski, T. A. Oliver, J. Lu, D. L. Darmofal (2005). p-Multigrid solution of high-order discontinuous Galerkin discretizations of the compressible Navier–Stokes equations. Journal of Computational Physics 207, 92–113. D. J. Mavriplis (1998). Multigrid strategies for viscous flow solvers on anisotropic unstructured meshes. Journal of Computational Physics 145, 141–165. M. H. Carpenter, C. A. Kennedy (1994). Fourth-order 2N-storage Runge–Kutta schemes. NASA Technical Memorandum 109112.\nR. Becker, R. Rannacher (2001). An optimal control approach to a posteriori error estimation in finite element methods. Acta Numerica 10, 1–102. D. A. Venditti, D. L. Darmofal (2002). Grid adaptation for functional outputs: application to two-dimensional inviscid flows. Journal of Computational Physics 176, 40–69. K. J. Fidkowski, D. L. Darmofal (2011). Review of output-based error estimation and mesh adaptation in computational fluid dynamics. AIAA Journal 49(4), 673–694. J. R. R. A. Martins, J. T. Hwang (2013). Review and unification of methods for computing derivatives of multidisciplinary computational models. AIAA Journal 51(11), 2582–2599. G. K. W. Kenway, G. J. Kennedy, J. R. R. A. Martins (2014). Scalable parallel approach for high-fidelity steady-state aeroelastic analysis and adjoint derivative computations. AIAA Journal 52(5), 935–951. J. S. Gray, J. T. Hwang, J. R. R. A. Martins, K. T. Moore, B. A. Naylor (2019). OpenMDAO: an open-source framework for multidisciplinary design, analysis, and optimization. Structural and Multidisciplinary Optimization 59, 1075–1104. J. R. R. A. Martins, A. Ning (2021). Engineering Design Optimization. Cambridge University Press.\nS. C. R. Dennis, G.-Z. Chang (1970). Numerical solutions for steady flow past a circular cylinder at Reynolds numbers up to 100. Journal of Fluid Mechanics 42(3), 471–489. B. Fornberg (1980). A numerical study of steady viscous flow past a circular cylinder. Journal of Fluid Mechanics 98(4), 819–855. NASA Langley Research Center, Turbulence Modeling Resource: 2D zero pressure gradient flat plate verification case, Spalart–Allmaras results (skin friction of CFL3D and FUN3D on the 545 × 385 grid). The values used are stored with their source in src/gyre/verify/studies_turbulence.py . R. Clift, J. R. Grace, M. E. Weber (1978). Bubbles, Drops, and Particles. Academic Press (standard drag curve of the sphere). T. A. Johnson, V. C. Patel (1999). Flow past a sphere up to a Reynolds number of 300. Journal of Fluid Mechanics 378, 19–70. Lord Rayleigh (1916). On the flow of compressible fluid past an obstacle. Philosophical Magazine 32, 1–6 (the Janzen–Rayleigh expansion of the compressible potential in powers of the Mach number squared).\nJ. Bradbury et al. (2018). JAX: composable transformations of Python+NumPy programs. Model Context Protocol specification (2024–).\nS. Hennemann, A. M. Rueda-Ramírez, F. J. Hindenlang, G. J. Gassner (2021). A provably entropy stable subcell shock capturing approach for high order split form DG for the compressible Euler equations. Journal of Computational Physics 426, 109935. — the natural route to shock capturing within this formulation.","title":"Bibliography","url":"reference/bibliography.html"},{"audience":["contributors","developers"],"chunks":[{"anchor":"","end":662,"heading":"","level":2,"start":0},{"anchor":"pages-and-front-matter","end":2593,"heading":"Pages and front matter","level":2,"start":663},{"anchor":"how-sections-order-and-navigation-are-derived","end":3011,"heading":"How sections, order and navigation are derived","level":3,"start":2594},{"anchor":"depth-levels","end":4933,"heading":"Depth levels","level":2,"start":3012},{"anchor":"the-idea-in-one-sentence","end":5027,"heading":"The idea in one sentence","level":1,"start":4934},{"anchor":"what-the-builder-does-with-a-block","end":5485,"heading":"What the builder does with a block","level":3,"start":5028},{"anchor":"why-the-reading-position-is-stable-when-the-dial-moves","end":5893,"heading":"Why the reading position is stable when the dial moves","level":4,"start":5486},{"anchor":"callouts","end":6763,"heading":"Callouts","level":2,"start":5894},{"anchor":"mathematics","end":8583,"heading":"Mathematics","level":2,"start":6764},{"anchor":"a-worked-example-at-four-depths","end":9442,"heading":"A worked example at four depths","level":2,"start":8584},{"anchor":"in-plain-terms","end":9675,"heading":"In plain terms","level":1,"start":9443},{"anchor":"mechanics-why-this-is-still-a-derivative","end":10262,"heading":"Mechanics: why this is still a derivative","level":3,"start":9676},{"anchor":"theory-the-summation-by-parts-property","end":10872,"heading":"Theory: the summation-by-parts property","level":4,"start":10263},{"anchor":"theory-entropy-conservation-of-the-volume-term","end":11901,"heading":"Theory: entropy conservation of the volume term","level":4,"start":10873},{"anchor":"code","end":13024,"heading":"Code","level":2,"start":11902},{"anchor":"tabs","end":13694,"heading":"Tabs","level":2,"start":13025},{"anchor":"tables-lists-and-other-markdown","end":14481,"heading":"Tables, lists and other Markdown","level":2,"start":13695},{"anchor":"links","end":15159,"heading":"Links","level":2,"start":14482},{"anchor":"data-components","end":15722,"heading":"Data components","level":2,"start":15160},{"anchor":"charts","end":17578,"heading":"Charts","level":2,"start":15723},{"anchor":"tables-from-a-study","end":18298,"heading":"Tables from a study","level":2,"start":17579},{"anchor":"status-badges-and-whole-studies","end":24304,"heading":"Status badges and whole studies","level":2,"start":18299},{"anchor":"when-data-is-missing","end":24466,"heading":"When data is missing","level":2,"start":24305},{"anchor":"field-viewer","end":25711,"heading":"Field viewer","level":2,"start":24467},{"anchor":"data-contracts","end":28644,"heading":"Data contracts","level":3,"start":25712},{"anchor":"diagrams","end":29770,"heading":"Diagrams","level":2,"start":28645},{"anchor":"generated-content-and-page-lists","end":30409,"heading":"Generated content and page lists","level":2,"start":29771},{"anchor":"registering-a-generator","end":31193,"heading":"Registering a generator","level":3,"start":30410},{"anchor":"building-and-checking","end":31494,"heading":"Building and checking","level":2,"start":31194},{"anchor":"what-the-build-writes-and-where-it-looks","end":33017,"heading":"What the build writes and where it looks","level":3,"start":31495},{"anchor":"checklist-for-a-new-page","end":33411,"heading":"Checklist for a new page","level":2,"start":33018},{"anchor":"notes","end":33951,"heading":"Notes","level":2,"start":33412}],"headings":["Pages and front matter","How sections, order and navigation are derived","Depth levels","The idea in one sentence","What the builder does with a block","Why the reading position is stable when the dial moves","Callouts","Mathematics","A worked example at four depths","In plain terms","Mechanics: why this is still a derivative","Theory: the summation-by-parts property","Theory: entropy conservation of the volume term","Code","Tabs","Tables, lists and other Markdown","Links","Data components","Charts","Tables from a study","Status badges and whole studies","When data is missing","Field viewer","Data contracts","Diagrams","Generated content and page lists","Registering a generator","Building and checking","What the build writes and where it looks","Checklist for a new page","Notes"],"keywords":["markdown","front matter","depth levels","shortcodes","callouts","math","charts","field viewer","diagrams"],"level":2,"max_level":4,"path":"reference/authoring.md","section":"reference","section_title":"Reference","summary":"How to write pages for this site: front matter, the four depth levels, callouts, mathematics, code, tabs, charts, tables, status badges, the field viewer and diagrams. Every feature is shown live on this page.","text":"Pages are Markdown files under docs/content/ . A small pre-processor adds depth levels, callouts, tabs, mathematics and data-driven components to standard Markdown; gyre docs build turns the files into this site. This page is both the reference and the test card: each feature is described and then used. L1 Essentials what it is and why it matters anyone L2 Practice how to use it users and MDO integrators L3 Mechanics how it works: algorithms and architecture developers L4 Theory derivations, proofs and numerical analysis numerical analysts Move the depth dial in the top bar (or press [ and ] ) while you read this page to see how passages fold and unfold.\nA page is one file. docs/content/guide/quickstart.md becomes guide/quickstart.html , and the Markdown source is published next to it as guide/quickstart.md for agents and tools. Files and directories whose name starts with _ or . are ignored; any other non-Markdown file under docs/content/ is copied to the site unchanged, so images can sit next to the page that uses them. Every page starts with YAML front matter: --- title : Quickstart summary : The shortest path from an installed package to a converged flow solution. section : guide order : 20 level : 2 audience : [ users ] keywords : [ first run , preset ] --- Field Required Meaning title yes Page title, shown as the top heading. Do not repeat it as a # heading in the body. summary yes One or two plain-text sentences. Shown as the “in one minute” lead of the page, in navigation cards, in search results and in llms.txt . No Markdown. section no One of overview , guide , examples , verification , engineering , theory , reference . Defaults to the top-level directory of the file. order no Integer position within the section (default 100; index.md defaults to 0). Ties are broken by title. level no Base depth level of the page, 1 to 4 (default 1). See Depth levels . audience no List of intended readers, shown as chips under the title. keywords no List of search keywords. study no Id of the verification study this page reports. Links the study card on the verification overview to this page, and lets status and study shortcodes omit the id. studies no List of further study ids reported on this page. Their cards on the verification overview link here too; shortcodes for them name the id. The file index.md of a section is that section’s landing page: the section title in the sidebar links to it. docs/content/index.md is the home page; its front matter also carries hero , paths (the persona reading paths) and highlights , which the home template lays out.\nSections and their display names are fixed in gyre.docsite.build.SECTIONS ; a page that names another section fails the build. Within a section the landing page comes first, then pages by (order, title) . The same linear order drives the previous/next links at the bottom of each page. The sidebar shows, next to each page, the deepest level that page reaches, measured from the rendered content rather than declared.\nEvery passage of the documentation belongs to one of four levels. The reader’s dial decides which passages are open; a passage deeper than the dial is not removed but folded into a one-line stub that shows its level, title and length, and opens in place. Text outside any block belongs to the page’s base level ( level in the front matter). A block assigns a different level to a passage: ::: level 3 \"Why flux differencing conserves entropy\" Any Markdown: paragraphs, lists, tables, math, shortcodes, ```python fenced_code = \"also works\" ``` and callouts. ::: The level may be written as a number ( 3 ), as L3 , or by name ( mechanics ). The quoted title is optional but strongly recommended: it is what the reader sees on the folded stub and in the table of contents. A block without a title shows its opening words instead. Three blocks follow, one per level other than this page’s base level 2. Rules worth knowing: Folding follows the dial. A block is open when its level is at or below the dial. Opening or closing a single block by hand is remembered until the dial crosses that block’s level. Pages deeper than level 1 fold too. On a page whose base level is 2 or more, the text under each ## heading is treated as a passage at the base level and folds when the dial is shallower. The lead-in before the first ## heading, the headings themselves, and any explicit block shallower than the base level always stay visible. This page has base level 2: set the dial to 1 to see its outline. Do not nest a shallower block inside a deeper one. It would be hidden whenever its parent is folded. Deeper inside shallower is fine. Blocks start at the left margin. The ::: lines must not be indented. Links unfold their target. Following a link, a search result or a table-of-contents entry into a folded passage opens it. Readers can pin a depth in a link with ?depth=3 , and the dial position is remembered between visits.\nWrite the short, plain answer first, then let readers who want more open the deeper passages.\nThe pre-processor parses the page into a tree before Markdown runs. Each block is rendered on its own and wrapped in a <section class=\"lv\" data-level=\"3\"> with a toggle button; whether it is folded is decided in the browser by two attributes, data-depth on the document (the dial) and data-user on the block (the reader’s choice for that one block), using CSS only. The builder also counts the words at each level for the depth map in the right-hand column.\nBefore the dial changes, the script records the first content element at the top of the viewport and its offset y_0 . After the change it measures the same element again (or, if that element is now inside a folded passage, the stub that replaced it) at offset y_1 and scrolls by y_1 - y_0 . Content above the reading position may grow or shrink by any amount; the element being read does not move on screen.\nFive kinds of callout share one syntax. The title is optional. ::: warning \"Check the units\" The angle of attack is given in degrees. ::: Note A note adds context that is useful but not essential to the argument. Tip Prefer a preset A tip suggests a better way of doing something. Warning Check the units A warning marks something that goes wrong silently if it is ignored. Definition Summation-by-parts operator A definition introduces a term. A first-derivative matrix D with a diagonal, positive mass matrix M is a summation-by-parts operator if Q = MD satisfies Q + Q\\T = B with B = \\operatorname{diag}(-1, 0, \\dots, 0, 1) . Result Discrete integration by parts A result states something that is proved or verified. For all grid functions u and v , u\\T M D v + (Du)\\T M v = u_N v_N - u_0 v_0 . Callouts may sit inside level blocks, and level blocks inside callouts.\nTeX mathematics is written between dollar signs and rendered in the browser by a vendored copy of KaTeX, so it works offline. Inline math such as $a^2 + b^2 = c^2$ gives a^2 + b^2 = c^2 ; a display equation goes between double dollars: $$ \\pd{u}{t} + \\nabla \\cdot f(u) = 0 \\label{eq:conservation} $$ \\pd{u}{t} + \\nabla \\cdot f(u) = 0 \\tag{1} A display equation that carries \\label{eq:name} is numbered and can be cited from the text with \\eqref{eq:name} , as in: the conservation law (1) . References may come before the equation. Equation numbers are per page. Things to know: Math is protected from Markdown: underscores, asterisks, backslashes and vertical bars inside it are left alone, including in tables and headings. A literal dollar sign is written \\$ . A $ followed by a space, or a closing $ followed by a digit, is not treated as math, so prices such as $5 and $10 are safe. … and … are accepted as alternative delimiters. Code spans and fenced code are never searched for math. Use multi-line environments inside a display: aligned , cases , pmatrix and the other KaTeX environments work. \\begin{aligned} \\avg{u} &= \\tfrac12\\,(u^- + u^+), &\\qquad \\jump{u} &= u^+ - u^-, \\\\ \\norm{u}_M^2 &= u\\T M u, &\\qquad \\abs{J} &= \\det \\pd{x}{\\xi}. \\end{aligned} The site defines a few macros for recurring notation: Macro Renders as Meaning \\R \\R the real numbers \\dd \\dd x upright differential, \\dd x \\pd{u}{x} \\pd{u}{x} partial derivative \\jump{u} \\jump{u} jump across an interface \\avg{u} \\avg{u} average across an interface \\norm{u} \\norm{u} norm \\abs{J} \\abs{J} absolute value u\\T u\\T transpose Note Macros and Markdown sources The macros live in docs/theme/static/js/site.js ( MACROS ). Readers of the raw .md files, including AI agents, see the macro names, so keep the list short and the names self-explanatory.\nThis section shows how one topic reads at each level. The mathematics is standard 1 2 ; it is here to exercise long-form math, numbering and nested levels. On the N + 1 Gauss–Lobatto nodes of an element, with quadrature weights w_i and Lagrange polynomials \\ell_j , the collocation derivative matrix and the mass matrix are D_{ij} = \\ell_j'(\\xi_i), \\qquad M = \\operatorname{diag}(w_0, \\dots, w_N). \\tag{2} In practice the volume term of a conservation law is evaluated with a symmetric two-point flux f^{\\#} instead of the nodal flux, which is called flux differencing: \\left.\\pd{f}{\\xi}\\right|_i \\;\\approx\\; 2 \\sum_{j=0}^{N} D_{ij}\\, f^{\\#}(u_i, u_j). \\tag{3} At dial level 2 this section shows the two definitions and three stubs; at level 4 it is a complete derivation. The depth map in the right-hand column shows how much of the page sits at each level.\nHigh-order methods can be made robust by arranging the discrete derivative so that it obeys the same integration-by-parts rule as the exact derivative. Whatever the exact equations conserve, the discrete equations then conserve too.\nThe two-point flux must be symmetric, f^{\\#}(u_i, u_j) = f^{\\#}(u_j, u_i) , and consistent, f^{\\#}(u, u) = f(u) . With the central choice f^{\\#}(u_i, u_j) = \\tfrac12 (f_i + f_j) , formula (3) reduces to the standard collocation derivative, because the rows of D sum to zero: 2 \\sum_j D_{ij}\\, \\tfrac12 (f_i + f_j) \\;=\\; f_i \\sum_j D_{ij} + \\sum_j D_{ij} f_j \\;=\\; (D f)_i . Other two-point fluxes change the aliasing behaviour of the scheme without changing its order of accuracy. The cost is one flux evaluation per pair of nodes along each grid line, O(N^2) per line instead of O(N) .\nClaim. With (2) , the matrix Q = MD satisfies Q + Q\\T = B, \\qquad B = \\operatorname{diag}(-1, 0, \\dots, 0, 1). \\tag{4} Proof. Gauss–Lobatto quadrature with N + 1 nodes is exact for polynomials of degree at most 2N - 1 . The product \\ell_i\\, \\ell_j' has degree 2N - 1 , so, using \\ell_i(\\xi_k) = \\delta_{ik} , Q_{ij} = w_i\\, \\ell_j'(\\xi_i) = \\sum_{k=0}^{N} w_k\\, \\ell_i(\\xi_k)\\, \\ell_j'(\\xi_k) = \\int_{-1}^{1} \\ell_i\\, \\ell_j' \\,\\dd\\xi . Therefore Q_{ij} + Q_{ji} = \\int_{-1}^{1} (\\ell_i\\, \\ell_j)' \\,\\dd\\xi = \\ell_i(1)\\,\\ell_j(1) - \\ell_i(-1)\\,\\ell_j(-1) = B_{ij}, since the end points are nodes. \\blacksquare\nLet s(u) be the entropy function, v = \\partial s / \\partial u the entropy variables, \\psi the entropy potential and F = v\\T f - \\psi the entropy flux. Suppose the two-point flux satisfies Tadmor’s condition 3 (v_j - v_i)\\T f^{\\#}(u_i, u_j) = \\psi_j - \\psi_i . \\tag{5} Contract the volume term (3) with w_i\\, v_i\\T and sum over the nodes. Writing 2Q = (Q - Q\\T) + B from (4) and using the symmetry of f^{\\#} , \\begin{aligned} \\sum_{i,j} 2\\, Q_{ij}\\, v_i\\T f^{\\#}_{ij} &= \\sum_{i,j} Q_{ij}\\, (v_i - v_j)\\T f^{\\#}_{ij} \\;+\\; \\sum_{i} B_{ii}\\, v_i\\T f_i \\\\ &= -\\sum_{i,j} Q_{ij}\\, (\\psi_j - \\psi_i) \\;+\\; \\bigl[v\\T f\\bigr]_0^N \\\\ &= -\\bigl[\\psi\\bigr]_0^N + \\bigl[v\\T f\\bigr]_0^N \\;=\\; F_N - F_0 . \\end{aligned} The third line uses \\sum_j Q_{ij} = 0 (rows of D sum to zero) and \\sum_i Q_{ij} = B_{jj} (from (4) ). The volume term therefore changes the discrete entropy \\sum_i w_i\\, s_i only through the entropy flux at the two end points, exactly as in the continuous equation: no entropy is produced or destroyed inside the element.\nFenced code is highlighted at build time. The info string after the opening fence takes the language and two optional attributes: title=\"...\" labels the block and hl=\"...\" highlights lines (numbers and ranges). ```python title=\"build_docs.py\" hl=\"3-4\" from gyre.docsite import build_site site = build_site ( \"site\" , verification_dir = \"var/verification\" ) print ( f \"documentation written to { site } \" ) ``` from gyre.docsite import build_site site = build_site ( \"site\" , verification_dir = \"var/verification\" ) print ( f \"documentation written to { site } \" ) Any language known to Pygments works; the ones used most here are python , bash , json , yaml and http . Use console for a terminal session: the copy button then copies the commands without the prompts and the output. $ gyre docs build -o site --verification var/verification documentation written to site GET /api/v1/info HTTP / 1.1 Accept : application/json { \"name\" : \"gyre\" , \"version\" : \"0.1.0\" , \"describe\" : \"abc123+dirty\" , \"deployment\" : { \"name\" : \"dev\" , \"host\" : \"workstation\" }} Inline code uses single backticks: build_site(output_dir=\"site\") .\nAlternatives that a reader chooses between go into tabs. A tab group is a ::: tabs block whose panels start with === \"Label\" : ::: tabs === \"Command line\" ```bash gyre docs build -o site ``` === \"Python\" ```python from gyre.docsite import build_site build_site ( \"site\" ) ``` ::: gyre docs build -o site from gyre.docsite import build_site build_site ( \"site\" ) Choosing a label selects the tab with the same label in every group on the page and is remembered between pages, so a reader who prefers Python sees Python everywhere. A panel may hold any Markdown, not only code. Without JavaScript, and in print, all panels are shown one after the other with their labels.\nStandard Markdown tables are supported, with alignment given by colons in the separator row. Wide tables scroll sideways instead of breaking the layout. Level Name Typical content Words per page (guide) 1 Essentials what and why 100 to 300 2 Practice commands, options, examples 300 to 1200 3 Mechanics algorithms, architecture as needed 4 Theory derivations, proofs as needed Also available: definition lists, abbreviations, attribute lists ( {#custom-id} after a heading sets its anchor), footnotes ( [^key] in the text and [^key]: note anywhere on the page), and raw HTML. Straight quotes become typographic quotes in prose; dashes are left as typed, so write --flag in code and an en dash or em dash as the character itself. Term A definition list pairs a term with its explanation.\nLink to other pages by the relative path of their Markdown file, optionally with an anchor: See the [ verification overview ]( ../verification/index.md ) and [ how levels work ]( authoring.md#depth-levels ). The builder rewrites .md links to the built pages and checks every internal link and anchor. A link to a page or anchor that does not exist fails the build with a list of all broken links. Because sources and built pages mirror each other, the same relative links also work for agents that read the .md files. Links that start with / (such as /api/docs , the interactive API explorer served by the Gyre service) and links with a scheme ( https://… ) are left untouched.\nShortcodes embed data-driven components. A shortcode sits alone on a line: {{ chart: isentropic_vortex/error_vs_h }} Verification numbers on this site come only from the files written by gyre verify , one <study-id>.json per study, and every chart and table shows the revision, time and device recorded in that file. The examples on this page use synthetic sample data from docs/theme/samples/ instead, referenced with the prefix sample: . Anything rendered from a sample is labelled “Synthetic illustration”; never use the prefix on a page that reports results.\n{{ chart: <study-id>/<chart-id> }} draws one chart of a study. Charts are plain SVG and need no network; hover or use the arrow keys to read values, click a legend entry to hide a series, open the data table, or download the chart as a standalone SVG. A convergence chart has logarithmic axes, ticks at the mesh sizes and a slope triangle for each reference slope: Error against mesh size Demonstration study Synthetic illustration Convergence chart: log-log axes, ticks at the mesh sizes, reference-slope triangles. A history chart draws dense series without markers: Residual history Demonstration study Synthetic illustration History chart: dense lines without markers. A bar chart compares categories. It draws thin vertical bars on a zero baseline; when the category labels are long or numerous it switches to horizontal bars, and when the value axis is logarithmic it becomes a dot plot, because a bar has no honest length on a log scale. A line chart with a single series needs no legend: Relative cost per degree of freedom Demonstration study Synthetic illustration Bar chart: grouped, thin bars from a zero baseline. A single smooth curve Demonstration study Synthetic illustration Line chart with one series: no legend is needed. Charts are forgiving about data, and say so: A null (or non-finite) value breaks a line: the series is drawn as separate segments, with no markers. Use this to draw several short pieces, such as tangents, as one series. Zero and negative values cannot be drawn on a logarithmic axis. They are left out and counted in a note under the chart; the data table keeps them. Series that lie on top of each other are drawn dashed with small markers, so both stay visible, and share one direct label. A reference slope is drawn as a triangle next to the part of a series it explains, on any chart with two logarithmic axes.\n{{ table: <study-id>/<table-id> }} renders one table of a study. Numbers are right-aligned in tabular figures with six significant digits, in scientific notation below 0.001 and from a million up; booleans read yes or no and missing values are shown as a dash. A table wider than the text column scrolls sideways inside its frame. Errors and observed orders Demonstration study Synthetic illustration p N h L2 error order 1 2 0.5 0.234467 — 1 4 0.25 0.0541215 2.115 1 8 0.125 0.0145244 1.898 1 16 0.0625 0.00342 2.086 2 2 0.5 0.0548869 — 2 4 0.25 0.00624785 3.135 2 8 0.125 8.6089e-04 2.859 2 16 0.0625 9.7412e-05 3.144 3 2 0.5 0.0125882 — 3 4 0.25 7.7034e-04 4.03 3 8 0.125 4.9834e-05 3.95 3 16 0.0625 2.9699e-06 4.069\n{{ status: <study-id> }} shows whether a study passed, and each of its checks with the measured value and the expectation. Pass and fail are marked by an icon and a word, never by colour alone. Passed Demonstration study 3 of 3 checks passed · 0 s Synthetic illustration Synthetic illustration: numbers generated by a formula to demonstrate the component. The numbers imitate a convergence study with design order p + 1. observed order, p = 1 2.086 expected >= 1.8 pass observed order, p = 2 3.144 expected >= 2.8 pass observed order, p = 3 4.069 expected >= 3.8 pass Failed Demonstration of a failing study 2 of 3 checks passed · 0 s Synthetic illustration Synthetic illustration: numbers generated by a formula to demonstrate the component. One check is made to fail so that the failure styling can be seen. free-stream residual 3.10e-15 expected <= 1e-12 pass observed order, p = 3 3.12 expected >= 3.8 fail entropy production -2.40e-16 expected <= 1e-13 pass {{ study: <study-id> }} renders everything a study recorded: the status, then every chart and every table. On a page that sets study: in its front matter the id can be omitted, so a verification page can consist of an explanation followed by {{ study }} . {{ studies }} renders a summary grid of all studies found in the verification directory, each linked to the page that declares it. The grid below is the real one for this build: 39 of 39 studies passed Passed Summation-by-parts operators operators 1/1 checks · 0.29 s 39979fcf90 Same code as this build Passed Free-stream preservation on curved meshes free_stream 1/1 checks · 18.67 s 39979fcf90 Same code as this build Passed Entropy conservation and stability entropy_conservation 41/41 checks · 15.77 s 39979fcf90 Same code as this build Passed Structure of the BR2 viscous operator viscous_operator 17/17 checks · 116.9 s 39979fcf90 Same code as this build Passed Independent 1-D model of the BR2 formulation diffusion_1d 17/17 checks · 4.93 s 39979fcf90 Same code as this build Passed Exact linearization by automatic differentiation linearization 40/40 checks · 198.1 s 39979fcf90 Same code as this build Passed Differentiable mesh deformation mesh_deformation 9/9 checks · 2.45 s 39979fcf90 Same code as this build Passed Advected density wave density_wave 4/4 checks · 40.98 s 39979fcf90 Same code as this build Passed Isentropic vortex convection isentropic_vortex 10/10 checks · 65.18 s 39979fcf90 Same code as this build Passed Manufactured solution: Euler mms_euler 6/6 checks · 212.5 s 39979fcf90 Same code as this build Passed Manufactured solutions: Navier–Stokes mms_navier_stokes 14/14 checks · 1225 s 39979fcf90 Same code as this build Passed Nonlinear and linear solver convergence solver_convergence 16/16 checks · 890.1 s 39979fcf90 Same code as this build Passed Line-implicit smoothing on a wall-resolved mesh line_smoother 7/7 checks · 2160 s 39979fcf90 Same code as this build Passed Line-implicit smoothing in three dimensions line_smoother_3d 6/6 checks · 620.4 s 39979fcf90 Same code as this build Passed Single-precision blocks in the preconditioner mixed_precision 6/6 checks · 2649 s 39979fcf90 Same code as this build Passed Adjoint gradients versus finite differences adjoint_gradients 18/18 checks · 1176 s 39979fcf90 Same code as this build Passed Output error estimation output_error 5/5 checks · 367.7 s 39979fcf90 Same code as this build Passed Laminar cylinder at Re = 40 cylinder_re40 7/7 checks · 417.2 s 39979fcf90 Same code as this build Passed Three-dimensional consistency extruded_3d 8/8 checks · 300.8 s 39979fcf90 Same code as this build Passed Inviscid flow past a sphere (3-D) sphere_inviscid 9/9 checks · 424.7 s 39979fcf90 Same code as this build Passed Laminar flow past a sphere, Re = 100 (3-D) sphere_re100 5/5 checks · 187.8 s 39979fcf90 Same code as this build Passed Adjoint derivatives of 3-D outputs adjoint_3d 6/6 checks · 297.7 s 39979fcf90 Same code as this build Passed Largest 3-D solve on one device scale_3d 7/7 checks · 452 s 39979fcf90 Same code as this build Passed Manufactured solutions: RANS with the Spalart-Allmaras model rans_mms 9/9 checks · 455.8 s 39979fcf90 Same code as this build Passed Exact linearization with the Spalart-Allmaras model rans_linearization 20/20 checks · 158.6 s 39979fcf90 Same code as this build Passed Turbulent flat plate (NASA TMR) with the Spalart-Allmaras model rans_flat_plate 8/8 checks · 162.1 s 39979fcf90 Same code as this build Passed Three-dimensional consistency with the Spalart-Allmaras model rans_extruded_3d 10/10 checks · 675.1 s 39979fcf90 Same code as this build Passed Adjoint gradients with the Spalart-Allmaras model rans_adjoint 6/6 checks · 119 s 39979fcf90 Same code as this build Passed Discipline contract for MDO integration discipline_contract 15/15 checks · 249.9 s 39979fcf90 Same code as this build Passed Imported meshes imported_mesh 29/29 checks · 184.5 s 39979fcf90 Same code as this build Passed Output-driven order selection order_selection 13/13 checks · 364.4 s 39979fcf90 Same code as this build Passed Example: inviscid NACA 0012 example_airfoil 4/4 checks · 104.5 s 39979fcf90 Same code as this build Passed Example: laminar cylinder, Re = 40 example_cylinder 4/4 checks · 51.5 s 39979fcf90 Same code as this build Passed Example: turbulent flat plate (Spalart-Allmaras) example_flat_plate 5/5 checks · 119.9 s 39979fcf90 Same code as this build Passed Example: lift polar with adjoint slopes example_polar 5/5 checks · 686.1 s 39979fcf90 Same code as this build Passed Example: coupled aero-structural adjoint example_coupling 2/2 checks · 823 s 39979fcf90 Same code as this build Passed Example: transonic aerofoil with shock capturing example_transonic 9/9 checks · 151.8 s 39979fcf90 Same code as this build Passed Kernel throughput (cpu) performance_cpu 2/2 checks · 123.8 s 39979fcf90 Same code as this build Passed Kernel throughput (NVIDIA GeForce RTX 3060 Ti) performance_gpu 2/2 checks · 103.5 s 39979fcf90 Same code as this build\nA missing study, chart, table or field never fails the build. The reader sees a placeholder that says what to run, like this one for a study that does not exist:\n{{ field: <path> | field=mach | title=… | height=420 }} embeds an interactive viewer for a two-dimensional solution sample written by gyre.io.field_payload (the file field.json in a run directory). The path is relative to the runs directory, var/runs by default. Drag to pan. Click the view, then scroll to zoom (or hold Ctrl while scrolling); pinch on a touch screen. The value under the pointer is read out in the corner. The tool bar selects the field and the colour map and toggles element edges and isolines. Analytic vortex on an invented grid 100 elements · 6×6 points each Synthetic illustration The next viewer asks for a real sample, smoke/field.json . It appears if that run exists in the runs directory when the site is built, and is replaced by a placeholder otherwise: Option Default Meaning field first field Field shown initially. title case name Caption above the viewer. height 420 Height of the view in pixels. view the sample’s own view , else everything Initial window as xmin,xmax,ymin,ymax ; also what the reset button returns to. The window is fitted inside the view at equal scale in both directions. cmap viridis Colour map: viridis or magma (both perceptually uniform). edges off on to start with element edges drawn.\nA study record is a JSON object: { \"id\" : \"isentropic_vortex\" , \"title\" : \"Isentropic vortex\" , \"description\" : \"...\" , \"passed\" : true , \"wall_time\" : 12.3 , \"checks\" : [{ \"name\" : \"observed order p=3\" , \"value\" : 3.6 , \"expected\" : \">= 3.4\" , \"passed\" : true }], \"charts\" : [{ \"id\" : \"error_vs_h\" , \"type\" : \"convergence\" , \"title\" : \"...\" , \"xlabel\" : \"h\" , \"ylabel\" : \"L2 error\" , \"xscale\" : \"log\" , \"yscale\" : \"log\" , \"series\" : [{ \"name\" : \"p=2\" , \"x\" : [ 0.5 , 0.25 ], \"y\" : [ 1e-2 , 1.3e-3 ]}], \"reference_slopes\" : [{ \"slope\" : 3 , \"label\" : \"h^3\" }], \"note\" : \"optional caption\" }], \"tables\" : [{ \"id\" : \"orders\" , \"title\" : \"...\" , \"columns\" : [ \"N\" , \"error\" , \"order\" ], \"rows\" : [[ 16 , 1e-3 , null ]]}], \"provenance\" : { \"version\" : \"0.1.0\" , \"commit\" : \"...\" , \"describe\" : \"abc123+dirty\" , \"timestamp\" : \"...\" , \"device\" : { \"platform\" : \"gpu\" , \"device_kind\" : \"...\" }}} Chart type is convergence , history , line or bar ; xscale and yscale are log or linear , and xscale may be category for bar charts, which take category names in x . Entries of x and y may be null ; non-finite numbers are treated as null . A reference slope may name the series it belongs to with \"series\": \"p=2\" ; otherwise the slopes are matched to the series in order of steepness when there are as many of one as of the other, and to the series with the closest fitted slope if not. A chart series may also hold null where a study deliberately omits a point. A study may also carry level (the suite it ran in), notes (a paragraph the study generates from the numbers of its own run, shown under the status) and error (the traceback of a study that raised, shown in the status block); a timing study also carries device and host_load . Files in the verification directory that are not study records (such as index.json ) are ignored, except that index.json sets the order of the overview grid. index.json is written by gyre verify . Its studies list has one entry per result file in the directory, each with id , title , passed , checks , failed_checks , wall_time and the level , commit , device and timestamp of the run that produced it, plus stale (true if that run was not the last invocation). suite , generated , wall_time , provenance , requested (the study ids of the last invocation), skipped (studies not run, each with id and reason ) and summary ( ran , passed , failed , stale , stale_failed ) describe the last invocation. A field sample is {\"m\", \"elements\", \"x\", \"y\", \"fields\": {name: {\"data\", \"min\", \"max\"}}} , where x , y and each data are base64-encoded little-endian float32 arrays of shape (elements, m, m) : every element is a structured m × m patch. An optional key \"view\": [xmin, xmax, ymin, ymax] gives the window to show first; without it the viewer fits the whole sample. The builder copies the sample to data/fields/ in the site. If a result.json lies next to the sample, its provenance is shown under the viewer.\n{{ diagram: <file.svg> | caption=… | width=… }} inlines an SVG file into the page, so that it can use the theme’s colours and follow the light and dark themes. The file is looked up next to the page, then relative to docs/content/ , then in docs/theme/diagrams/ (where the one below lives). Keep diagram sources that belong to the content in a directory whose name starts with _ (for example docs/content/_diagrams/ ) so that they are not also copied to the site as loose files. The build: inputs on the left, the three modules of gyre.docsite in the middle, the static site on the right. Do not put colours in the SVG. Use these classes instead: Class Use dg-line , dg-accent , dg-faint strokes: normal, emphasised, background dg-box , dg-box-accent filled rectangles and shapes dg-fill , dg-fill-accent solid fills, such as arrow heads dg-text , dg-small , dg-mono labels: normal, secondary, code dg-l1 … dg-l4 fills in the colours of the four depth levels Give the <svg> a viewBox , a <title> and, when it carries information, a <desc> . Ids inside the file (markers, gradients) must be unique on the page, so prefix them.\n{{ generated: <name> }} calls a registered Python function that returns Markdown and renders the result in place. This is how reference material that lives in the code, such as tables of presets or a schema, stays in step with the code. The build information below is produced this way: Build Package gyre Version 0.4.0 Revision ceb1f4b0be Commit ceb1f4b0bed41de53e586df10858079f5a2bcdd8 Working tree clean Two more shortcodes help with navigation: {{ pages }} lists the pages of the current section as cards ( {{ pages: guide }} lists another section), and {{ levels }} prints the legend of the four levels shown at the top of this page.\nGenerators are registered in src/gyre/docsite/generators.py , which the builder imports before rendering. A generator takes no arguments and returns Markdown; anything the Markdown dialect supports may be used in it, including level blocks and other shortcodes. from .shortcodes import register_generator @register_generator ( \"presets\" ) def presets_table () -> str : from ..cases import PRESETS # import heavy modules lazily rows = [ f \"| ` { name } ` | { spec . description } |\" for name , spec in PRESETS . items ()] return \" \\n \" . join ([ \"| Preset | Description |\" , \"|---|---|\" , * rows ]) A generator that is not registered, or that raises, does not fail the build: the page shows a “Shortcode problem” box and the problem is recorded in the warnings of site-manifest.json .\n$ gyre docs build -o site --verification var/verification documentation written to site The build is fast, has no network access and needs no tool chain beyond Python. Opening site/index.html from the file system works; the live deployment stamp in the footer then falls back to the build-time stamp.\nbuild_site(output_dir=\"site\", verification_dir=\"var/verification\", content_dir=None, runs_dir=\"var/runs\") returns the output path. content_dir defaults to docs/content and the theme to docs/theme of the source checkout that contains the package (falling back to the working directory); the environment variables GYRE_DOCS_CONTENT and GYRE_DOCS_THEME override them. The other paths are relative to the working directory. Output Purpose <page>.html , <page>.md every page, and its Markdown source next to it search-index.json , search-index.js full-text index (the .js copy lets search work from the file system) llms.txt , llms-full.txt index of all pages for AI agents, and all pages in one file site-manifest.json pages, sections, levels, build stamp, warnings and the list of files written site-data.js build stamp and reading paths for the scripts assets/ the theme’s CSS, JavaScript and vendored KaTeX data/fields/ field samples used by field shortcodes The output is deterministic: the same inputs give the same bytes, and no wall-clock time is written unless SOURCE_DATE_EPOCH is set. Files are rewritten only when their content changes, and files that a previous build wrote but this one does not are removed; nothing else in the output directory is touched. Authoring errors (unknown section, unclosed block, broken internal link or anchor) raise gyre.docsite.DocsBuildError listing every problem. Missing data, unknown shortcodes and failing generators do not; they are shown on the page and recorded as warnings.\nFront matter has title , summary , order and the right level . The summary can be read alone and says what the page delivers. The page reads well with the dial at its base level; deeper passages are in titled level blocks. Numbers come from verification output through shortcodes, not typed by hand. gyre docs build succeeds: no broken links, and no unexpected warnings in site-manifest.json .\nT. C. Fisher and M. H. Carpenter, “High-order entropy stable finite difference schemes for nonlinear conservation laws: finite domains”, Journal of Computational Physics 252 (2013). ↩ G. J. Gassner, A. R. Winters and D. A. Kopriva, “Split form nodal discontinuous Galerkin schemes with summation-by-parts property for the compressible Euler equations”, Journal of Computational Physics 327 (2016). ↩ E. Tadmor, “The numerical viscosity of entropy stable schemes for systems of conservation laws. I”, Mathematics of Computation 49 (1987). ↩","title":"Authoring guide","url":"reference/authoring.html"}],"levels":{"1":"Essentials","2":"Practice","3":"Mechanics","4":"Theory"},"sections":{"engineering":"Software Engineering","examples":"Examples","guide":"User Guide","overview":"Overview","reference":"Reference","theory":"Mathematics & Numerical Methods","verification":"Verification"},"version":1}