feat(theory): publish steady cloak result package
Freeze the bounded steady-flow evidence, reader documentation, lattice-exact plotting package, and reproducible diagnostics for writing and review. Co-authored-by: Cursor <cursoragent@cursor.com>
@@ -0,0 +1,641 @@
|
|||||||
|
# Stage 1: boundary contract and claim limits
|
||||||
|
|
||||||
|
This is the authoritative mathematical derivation for stage 1. It defines the inviscid problem; it does not validate the existing numerical solver or establish the later circulation-cancellation mechanism. Every substantive statement below has exactly one registry label.
|
||||||
|
|
||||||
|
## 0. Statement registry and audit rule
|
||||||
|
|
||||||
|
Labels have these exact meanings:
|
||||||
|
|
||||||
|
- **proved mathematical result**: derived here from stated hypotheses.
|
||||||
|
- **primary-literature-supported result**: taken from or directly corroborated by the cited primary source.
|
||||||
|
- **numerical observation**: measured in this project; no numerical observation is promoted to a theorem.
|
||||||
|
- **modeling assumption**: an input, closure, convention, or extrapolation not implied by Euler.
|
||||||
|
|
||||||
|
| ID | Exact label | Auditable statement |
|
||||||
|
|---|---|---|
|
||||||
|
| S1 | modeling assumption | Stage 1 studies a steady, two-dimensional, incompressible outer field around three fixed circles, either in a free-slip strip or its unbounded-plane limit. |
|
||||||
|
| S2 | proved mathematical result | Subject to the regularity, far-field, compatibility, and function-space hypotheses in §3, an irrotational Euler/Neumann solution is unique after all body circulations are fixed. |
|
||||||
|
| S3 | proved mathematical result | Rigid rotation of a circle about its center contributes no normal wall velocity. |
|
||||||
|
| S4 | primary-literature-supported result | A multiply connected irrotational outer flow contains independent circulation periods not selected by impermeability; Crowdy (2006), especially (16), (18), (20), and (34), supplies an exact zero-circulation construction. |
|
||||||
|
| S5 | proved mathematical result | For one circle, the exact uniform-flow-plus-circulation family has wall velocity `u_theta(a,theta)=-2U sin(theta)+Gamma/(2 pi a)`. |
|
||||||
|
| S6 | proved mathematical result | The case `U=0`, `Gamma=2 pi a^2 Omega` exactly matches the rotating wall's tangential speed while remaining an Euler slip solution; therefore an absolute impossibility claim is false. |
|
||||||
|
| S7 | proved mathematical result | For the one-circle family with `U != 0`, no constant `Omega` can make tangential no-slip hold at every angle. |
|
||||||
|
| S8 | proved mathematical result | In general multi-body geometry, prescribed normal and tangential traces form overdetermined Cauchy data; the finite-dimensional circulation freedom can satisfy only tangential data lying in its compatible finite-dimensional trace space. |
|
||||||
|
| S9 | primary-literature-supported result | Inviscid circulation is transported under Kelvin's hypotheses, while viscous rotating-cylinder problems impose full wall velocity and generate/diffuse vorticity; Ueda et al. (2003), (2.4), (2.7)–(2.10), exhibits that viscous boundary-value structure. |
|
||||||
|
| S10 | modeling assumption | Any map `Gamma_eff(s)` from nondimensional wall speed to an outer circulation is a viscous-interface/empirical closure, not an Euler boundary condition. |
|
||||||
|
| S11 | numerical observation | Existing project aggregate CFD records report downstream profile errors; no surviving endpoint velocity fields support a fitted effective circulation, so historical fitted values are withdrawn from current evidence. |
|
||||||
|
| S12 | modeling assumption | Later stages may test rear-opposite circulations as a cloak mechanism, but stage 1 neither derives nor validates that mechanism. |
|
||||||
|
|
||||||
|
## 1. Historical Stage-1 charter (superseded as current governance)
|
||||||
|
|
||||||
|
**1.1 — Proposition [proved mathematical result].** For the domains and spaces stated below, fixing the three circulation periods removes the harmonic nonuniqueness of the irrotational Euler/Neumann problem; centered circular rotation does not enter impermeability; and imposing the complete rotating-wall tangential trace as well is in general incompatible, though the explicit `U=0`, `Gamma=2 pi a^2 Omega` one-circle case is compatible.
|
||||||
|
|
||||||
|
**1.2 — Pass evidence [modeling assumption].** This stage passes only if the proposition is supported by numbered definitions and proofs, an explicit counterexample, primary-source links at the inviscid/viscous interface, an equation ledger, and an end-stage claim/file audit.
|
||||||
|
|
||||||
|
**1.3 — Falsification or claim-limiting evidence [modeling assumption].** A missing flux condition, an unacknowledged strip/exterior function-space issue, a counterexample to fixed-period uniqueness under the stated hypotheses, or evidence that a claimed result holds only for one circle forces repair or narrowing; numerical agreement cannot repair a mathematical failure.
|
||||||
|
|
||||||
|
## 2. Geometry, nondimensionalization, and signs
|
||||||
|
|
||||||
|
**2.1 — Domain [modeling assumption; S1].** Use cylinder diameter `D`, inflow speed `U_inf>0`, and time `D/U_inf` as scales. Dimensionless coordinates and velocity are `x=x*/D` and `u=u*/U_inf`; pressure is scaled by `rho U_inf^2`. Three disjoint closed disks are
|
||||||
|
|
||||||
|
`B_j={x in R^2: |x-c_j| <= a_j}`, `j=1,2,3`,
|
||||||
|
|
||||||
|
with positive gaps. The unbounded fluid domain is `Omega_inf=R^2 \ union_j B_j`. The strip domain is
|
||||||
|
|
||||||
|
`Omega_H={-infinity<x<infinity, -H<y<H} \ union_j B_j`,
|
||||||
|
|
||||||
|
where every disk lies strictly between the walls. The project's canonical equal-circle geometry is a later parameter choice, not needed in the proofs.
|
||||||
|
|
||||||
|
**2.2 — Boundary orientation [modeling assumption].** On circle `j`, write `e_r=(x-c_j)/a_j`, `e_theta=e_z cross e_r`, so `e_theta` points counterclockwise. The normal `n` used in fluid-domain divergence theorems points out of the fluid; hence `n=-e_r` on a body and `n=+/- e_y` on the strip walls. Define positive circulation geometrically counterclockwise,
|
||||||
|
|
||||||
|
`Gamma_j = integral_(0)^(2 pi) u(c_j+a_j e_r(theta)) dot e_theta(theta) a_j dtheta`.
|
||||||
|
|
||||||
|
This is opposite to circulation computed with the positively oriented boundary of the exterior fluid domain. All signs below use the geometric convention.
|
||||||
|
|
||||||
|
**2.3 — Far field and unbounded limit [modeling assumption].** In `Omega_inf`, require `u-U_inf e_x=o(1)` as `|x|->infinity`, with derivatives sufficient for the integrations below. In `Omega_H`, require free slip `u dot n=0` at `y=+/-H` and approach to a specified through-flow as `x->+/-infinity`. The phrase “unbounded limit” means taking `H->infinity` only after the strip and exterior problems have each been defined; it is not an automatic uniform convergence claim.
|
||||||
|
|
||||||
|
## 3. Euler, Hodge periods, compatibility, and uniqueness
|
||||||
|
|
||||||
|
**3.1 — Governing equations [primary-literature-supported result; S4].** The steady incompressible Euler equations in the fluid are
|
||||||
|
|
||||||
|
`(u dot grad)u = -grad p`, `div u=0`.
|
||||||
|
|
||||||
|
For the irrotational outer subclass, `curl u=0`. Crowdy (2006), (16), imposes uniform behavior at infinity; its constant-streamfunction boundary conditions (18)–(19) encode impermeability, and (20), (25)–(26), (34) construct the multiply connected zero-circulation complex potential. These equations support the formulation, not viscous no-slip or circulation selection.
|
||||||
|
|
||||||
|
**3.2 — Harmonic decomposition [proved mathematical result].** Assume a sufficiently smooth domain and a single-valued harmonic potential `phi` for the zero-period part. Choose harmonic vector fields `h_j` satisfying
|
||||||
|
|
||||||
|
`div h_j=curl h_j=0`, `h_j dot n=0`, and `integral_(CCW around B_k) h_j dot dl=delta_jk`.
|
||||||
|
|
||||||
|
Then every admissible irrotational field with the prescribed far field can be written
|
||||||
|
|
||||||
|
`u = u_N + sum_(j=1)^3 Gamma_j h_j`,
|
||||||
|
|
||||||
|
where `u_N` has zero periods and solves the required Neumann data. This is the planar Hodge/de Rham separation of an exact part from the three cohomological periods. Crowdy's single-valued uniform-flow potential is an exact representation of the zero-period member, consistent with (33)–(34).
|
||||||
|
|
||||||
|
**3.3 — Flux compatibility [proved mathematical result; S2].** For a truncated domain `Omega_R`, the divergence theorem gives
|
||||||
|
|
||||||
|
`0=integral_(Omega_R) div u dA=integral_(partial Omega_R) u dot n ds`.
|
||||||
|
|
||||||
|
Therefore prescribed normal data must have zero total flux after including outer truncation/end sections. For fixed rigid bodies and impermeable strip walls, all solid-wall contributions vanish. In the exterior problem, uniform inflow has zero net flux through a large closed circle. In the strip, the upstream and downstream section fluxes must agree. A source-based representation must likewise have zero total source; imposing zero source per body is stronger than global compatibility and is a numerical representation choice, not a new Euler law.
|
||||||
|
|
||||||
|
**3.4 — Fixed-circulation uniqueness theorem [proved mathematical result; S2].** Assume: (i) two `C^1` irrotational, divergence-free solutions have identical normal traces, far/end behavior, and all three geometric circulations; (ii) their difference `w` and a potential for it lie in a class permitting integration by parts; and (iii) the boundary term at infinity or the two strip ends tends to zero. Then the two velocity fields coincide.
|
||||||
|
|
||||||
|
*Proof.* The difference satisfies `div w=curl w=0`, `w dot n=0`, zero far/end data, and zero periods around every hole. Zero periods make the closed one-form `w dot dx` exact, so `w=grad chi` for a single-valued harmonic `chi`. Green's identity on a truncation gives
|
||||||
|
|
||||||
|
`integral |grad chi|^2 dA = integral_(boundary) chi partial_n chi ds`.
|
||||||
|
|
||||||
|
Body and free-slip-wall terms vanish because `partial_n chi=0`; the outer/end term vanishes by assumption. Thus `grad chi=0`, so `w=0`. QED.
|
||||||
|
|
||||||
|
**3.5 — Caveats [modeling assumption].** The theorem is conditional, not an existence theorem. Nonzero net circulation in the unbounded plane has a `1/r` velocity tail and logarithmically divergent total kinetic energy, so “finite energy” must not be imposed on the full field unless the circulation class is adjusted; the *difference* in the uniqueness proof has zero periods and can have finite energy. In an infinite strip, end modes and through-flow must be fixed, and sufficient decay must exclude additional harmonic end modes. Corners, touching bodies, non-smooth traces, or weak Euler solutions require separate functional analysis. Pressure is then recovered locally from Bernoulli for a steady irrotational field, up to constants compatible with the connected fluid domain.
|
||||||
|
|
||||||
|
**3.6 — Literature scope [primary-literature-supported result; S4].** Glass, Lacave, Munnier & Sueur (2019) provides a modern perfect-fluid/rigid-body framework in which vorticity and body circulations are independent state data and circulation-induced harmonic fields enter the dynamics. The present fixed-body theorem is narrower and is proved above rather than attributed to an unstated theorem number.
|
||||||
|
|
||||||
|
## 4. Rigid-body impermeability
|
||||||
|
|
||||||
|
**4.1 — Euler wall condition [primary-literature-supported result].** For a rigid body with center velocity `V_j` and angular velocity `Omega_j e_z`, its boundary velocity is
|
||||||
|
|
||||||
|
`U_B,j(x)=V_j + Omega_j e_z cross (x-c_j)`.
|
||||||
|
|
||||||
|
The inviscid kinematic condition is `(u-U_B,j) dot n=0`. Modern rigid-body Euler formulations, including Glass et al. (2019), use this normal matching while retaining circulations as separate data.
|
||||||
|
|
||||||
|
**4.2 — Centered-circle cancellation [proved mathematical result; S3].** On a circle, `x-c_j=a_j e_r`, so the rotational velocity is `Omega_j a_j e_theta`. Since `e_theta dot n=e_theta dot (-e_r)=0`,
|
||||||
|
|
||||||
|
`(Omega_j e_z cross (x-c_j)) dot n=0`.
|
||||||
|
|
||||||
|
Thus centered rotation drops out of normal data exactly. Translation does not: `U_B,j dot n=V_j dot n`. For the fixed circles here, impermeability is simply `u dot n=0`, independent of `Omega_j`.
|
||||||
|
|
||||||
|
## 5. Exact one-cylinder audit and counterexample
|
||||||
|
|
||||||
|
**5.1 — Exact potential [primary-literature-supported result; S5].** For a circle `r=a` centered at the origin, uniform flow `U e_x`, and geometric CCW circulation `Gamma`, classical Lamb/Milne-Thomson circle-theorem theory gives
|
||||||
|
|
||||||
|
`W(z)=U(z+a^2/z) - i Gamma/(2 pi) log(z/a)`,
|
||||||
|
|
||||||
|
with complex velocity `dW/dz=u_x-i u_y`. Crowdy (2006), (15) and the `M=0` reduction following (34), independently recover the zero-circulation term. Classical edition/page/equation details for Lamb and Milne-Thomson remain marked for bibliographic verification; none is guessed here.
|
||||||
|
|
||||||
|
**5.2 — Velocity and wall trace [proved mathematical result; S5].** Direct differentiation or polar differentiation gives
|
||||||
|
|
||||||
|
`u_r=U(1-a^2/r^2) cos(theta)`,
|
||||||
|
|
||||||
|
`u_theta=-U(1+a^2/r^2) sin(theta)+Gamma/(2 pi r)`.
|
||||||
|
|
||||||
|
Hence `u_r(a,theta)=0` and
|
||||||
|
|
||||||
|
`u_theta(a,theta)=-2U sin(theta)+Gamma/(2 pi a)`.
|
||||||
|
|
||||||
|
The entire one-parameter family satisfies the same Euler impermeability condition. Therefore wall rotation does not select `Gamma`.
|
||||||
|
|
||||||
|
**5.3 — Explicit compatible special case [proved mathematical result; S6].** Set `U=0`. The outer field is the line-vortex field `u_theta=Gamma/(2 pi r)`, irrotational for `r>a`. A wall rotating CCW with angular velocity `Omega` has tangential speed `a Omega`. Choosing
|
||||||
|
|
||||||
|
`Gamma=2 pi a^2 Omega`
|
||||||
|
|
||||||
|
makes `u_theta(a,theta)=a Omega` for every `theta`, while `u_r=0`. This is an exact special counterexample to “potential flow can never match a rotating circular wall.” It does not show that Euler selects this circulation: every other `Gamma` still satisfies Euler impermeability.
|
||||||
|
|
||||||
|
**5.4 — Nonzero-inflow obstruction [proved mathematical result; S7].** If constant rotating no-slip held for `U != 0`, then
|
||||||
|
|
||||||
|
`-2U sin(theta)+Gamma/(2 pi a)=a Omega`
|
||||||
|
|
||||||
|
for all `theta`. Evaluating at `theta=pi/2` and `3pi/2` gives respectively `-2U+C=a Omega` and `2U+C=a Omega`, which imply `U=0`, a contradiction. Thus within this exact one-cylinder family, no constants `Gamma,Omega` give full rotating no-slip when `U != 0`.
|
||||||
|
|
||||||
|
## 6. Why multi-body no-slip is generally overdetermined
|
||||||
|
|
||||||
|
**6.1 — Trace-space argument [proved mathematical result; S8].** Solve the Neumann problem first. With geometry, far field, and normal data fixed, its irrotational solutions form the affine family
|
||||||
|
|
||||||
|
`u|_(boundary) = u_N|_(boundary) + sum_j Gamma_j h_j|_(boundary)`.
|
||||||
|
|
||||||
|
The freely adjustable tangential traces therefore lie in a space of dimension at most three. Full rigid-wall no-slip would prescribe one function of arclength on every body,
|
||||||
|
|
||||||
|
`u dot t = U_B,j dot t` on `partial B_j`,
|
||||||
|
|
||||||
|
in addition to normal data. Such target traces belong to an infinite-dimensional function space. They are attainable only if their difference from `u_N dot t` lies exactly in the span of the three harmonic tangential traces. Consequently compatibility can occur in special symmetric cases, but in general—and generically under perturbation of geometry, inflow, or wall speeds—it does not.
|
||||||
|
|
||||||
|
**6.2 — Cauchy-data formulation [proved mathematical result; S8].** Locally write the zero-period part as `grad phi` with `Delta phi=0`. Normal velocity prescribes `partial_n phi`; tangential velocity prescribes `partial_s phi` (after subtracting harmonic circulation fields). Together they determine the full boundary gradient and hence constitute Cauchy data for an elliptic equation. Arbitrary Cauchy data do not satisfy the nonlocal harmonic compatibility relation. This is an overdetermination argument, not a claim that compatibility is never possible; §5.3 is the required exception.
|
||||||
|
|
||||||
|
**6.3 — Scope [modeling assumption].** The argument establishes “in general” or “generically,” not a measure-theoretic genericity theorem for every geometry class. A rigorous transversality statement is outside stage 1. Noncircular rotation can also contribute normal velocity if rotation is not about a symmetry center or the body motion changes the domain; those cases are not covered by §4.2.
|
||||||
|
|
||||||
|
## 7. Kelvin, vorticity, and the viscous interface
|
||||||
|
|
||||||
|
**7.1 — Kelvin transport [primary-literature-supported result; S9].** Under the classical hypotheses of inviscid barotropic flow, conservative body forces, sufficient smoothness, and a closed material contour `C(t)`, Kelvin's theorem states
|
||||||
|
|
||||||
|
`d/dt integral_(C(t)) u dot dl = 0`.
|
||||||
|
|
||||||
|
Glass et al. (2019) explicitly treats body circulations as conserved parameters for smooth two-dimensional perfect-fluid/rigid-body motion. Original Kelvin bibliographic details and any historical equation number require later verification; no quotation or number is supplied here.
|
||||||
|
|
||||||
|
**7.2 — Vorticity consequence [proved mathematical result; S9].** In two-dimensional constant-density Euler, taking curl gives `partial_t omega+u dot grad omega=0`. Thus initially irrotational fluid remains irrotational along smooth trajectories; Euler slip supplies no viscous wall-vorticity flux capable of selecting a new `Gamma` from `Omega`. Multiply connected nonzero periods may nevertheless exist with `omega=0` in the fluid because loops around holes need not bound fluid-only surfaces.
|
||||||
|
|
||||||
|
**7.3 — Viscous boundary [primary-literature-supported result; S9].** A viscous rotating-body problem instead uses Navier–Stokes and full no-slip/no-penetration `u=U_B` on the wall. Ueda et al. (2003), (2.4), prescribes uniform far velocity and rotating-cylinder wall velocity; (2.7) relates velocity, streamfunction, and vorticity; and (2.8)–(2.10) carry the boundary/vorticity coupling into their low-Re formulation. These equations supply no Euler rule `Gamma=2 pi a^2 Omega`.
|
||||||
|
|
||||||
|
**7.4 — Outer/inner closure [primary-literature-supported result; S10].** Glauert (1957) treats rapidly rotating, nonseparating flow by taking an irrotational outer flow with unknown circulation and using a viscous boundary-layer solution/no-slip matching to determine it in that restricted regime. Moore's 1957 rotating-cylinder work is a related boundary-layer source. These are precedents for a viscous closure, not universal formulas for this three-cylinder, finite-Re, possibly separated flow. Exact Moore equation mapping and edition details require later bibliographic verification.
|
||||||
|
|
||||||
|
**7.5 — Project closure [modeling assumption; S10].** Define nondimensional wall-speed ratio `s=Omega a/U_inf` (or record explicitly if code uses `Omega D/U_inf`). Any `Gamma_eff(s)` used by this project must be obtained from a stated viscous model or empirical outer-field procedure—e.g. contour plateaus or held-out outer-profile fits—with sign, contour/mask, uncertainty, Reynolds number, geometry, and confinement recorded. It must not be set to `2 pi a^2 Omega` merely because §5.3 makes that identity compatible at `U=0`.
|
||||||
|
|
||||||
|
## 8. Project implications and prohibited claims
|
||||||
|
|
||||||
|
**8.1 — Allowed implications [proved mathematical result; S2–S8].** The outer potential problem may legitimately treat body circulations as independent prescribed controls, and a fixed-circulation solution can serve as a mathematically unique reference under §3.4 assumptions. Circular wall rotation cannot be inserted through impermeability; it must enter through a separately justified circulation/viscous interface.
|
||||||
|
|
||||||
|
**8.2 — Empirical boundary [numerical observation; S11].** Historical fitted `Gamma_eff/(UD)` values without source arrays remain withdrawn. The later v4 endpoint campaign supplies direct fields and geometric contour sign; its corrected WLS is explicitly a descriptive projection rather than a causal wall-speed law.
|
||||||
|
|
||||||
|
**8.3 — Later mechanism status [modeling assumption; S12].** Opposite rear-cylinder circulations, multipole cancellation, and any “cloak” interpretation are hypotheses reserved for later stages. Nothing in Euler uniqueness implies disturbance reduction, an optimum, stability, or agreement with Navier–Stokes.
|
||||||
|
|
||||||
|
**8.4 — Prohibited claims [modeling assumption].** Until later evidence exists, this project must not claim: (a) Euler wall rotation selects circulation; (b) potential flow can *never* match a rotating wall; (c) `Gamma_eff=2 pi a^2 Omega` for nonzero inflow or multiple bodies; (d) impermeability implies no-slip; (e) a small collocation residual proves physical accuracy; (f) potential flow predicts separation, drag, torque, wall vorticity, or viscous stability; (g) the strip result follows automatically from the plane result; or (h) rear-opposite circulation is the demonstrated cloak mechanism.
|
||||||
|
|
||||||
|
## 9. Equation-to-source-to-implementation/test ledger
|
||||||
|
|
||||||
|
| Derivation item | Source/proof | Current implementation/test touchpoint | Stage-1 status |
|
||||||
|
|---|---|---|---|
|
||||||
|
| Domain, signs, `Gamma_j` | §2 definition | `contract.py`; sign tests in `tests/test_core.py` | Documentation only; code not revalidated here |
|
||||||
|
| Euler far field and streamline boundaries | Crowdy (2006), (16), (18)–(20), (34) | `theory.py` outer MFS | Mathematical contract frozen; numerical validation deferred |
|
||||||
|
| Prime-function exact reference | Crowdy (2006), (25)–(26), (34) | No exact Crowdy implementation | Later independent-reference task |
|
||||||
|
| Hodge circulation basis | §3.2–§3.4 proof; Glass et al. (2019) context | circulation basis in `theory.py` | Contract only; dense/off-grid tests deferred |
|
||||||
|
| Flux compatibility | §3.3 proof | zero-source constraints in `theory.py` | Distinguishes PDE compatibility from representation choice |
|
||||||
|
| Rigid Euler impermeability | §4 proof; Glass et al. (2019) | body normal collocation | Contract frozen |
|
||||||
|
| One-cylinder exact solution | classical Lamb/Milne-Thomson; Crowdy (2006), (15), (34) reduction | intended exact test in `tests/test_core.py` | Equation frozen; test audit deferred to stage 2 |
|
||||||
|
| Special `U=0` counterexample | §5.3 proof | no implementation required | Passed analytically |
|
||||||
|
| `U!=0` sine obstruction | §5.4 proof | no implementation required | Passed analytically |
|
||||||
|
| General Cauchy overdetermination | §6 trace-space proof | no no-slip solver should be inferred from MFS | Passed with genericity limit |
|
||||||
|
| Viscous wall/vorticity system | Ueda et al. (2003), (2.4), (2.7)–(2.10) | CFD is external to outer MFS | Interface distinguished |
|
||||||
|
| Circulation transport | Kelvin theorem; Glass et al. (2019) | prescribed `Gamma` | Source details flagged where unverified |
|
||||||
|
| `Gamma_eff(s)` closure | Glauert (1957); Moore (1957) as restricted precedents | later contour/profile fitting | Explicitly modeling/empirical, deferred |
|
||||||
|
|
||||||
|
## 10. Concentrated end-stage review
|
||||||
|
|
||||||
|
**10.1 — Derivation and citation review: PASS [modeling assumption].** Sections 2–7 give numbered definitions, compatibility, conditional uniqueness, circle rotation, exact solution, overdetermination, and viscous interface. Crowdy and the relied-upon Ueda equation mappings were checked against accessible primary PDFs. Classical-book edition details, original Kelvin bibliographic details, and exact Moore equation mapping remain publication holds; Kharlamov–Filip is contextual only and no equation mapping is asserted.
|
||||||
|
|
||||||
|
**10.2 — Counterexample review: PASS [proved mathematical result; S6].** Section 5.3 explicitly verifies both normal and tangential wall velocity for `U=0`, `Gamma=2 pi a^2 Omega`, and separately states that Euler does not select this value.
|
||||||
|
|
||||||
|
**10.3 — Claim-limit review: PASS [modeling assumption].** The text uses “in general” and “generically” for multi-body incompatibility, preserves the special exception, separates strip from exterior function spaces, and prohibits circulation-selection, no-slip, stability, and later-mechanism claims.
|
||||||
|
|
||||||
|
**10.4 — File-budget review: PASS [numerical observation].** At review time the package has 9 root source files including the sole new root file `DERIVATION.md`, and 10 total source files when `tests/test_core.py` is included. Transient `__pycache__` is excluded because it is generated bytecode, not source. No Python file was changed in stage 1. Both counts satisfy root `<=10` and total `<=30`.
|
||||||
|
|
||||||
|
**10.5 — Stage-1 verdict [modeling assumption].** PASS, conditional on preserving the bibliographic-verification flags. This verdict freezes only the boundary contract and claim limits; it does not pass any stage-2-or-later solver, mechanism, DNS, or stability objective.
|
||||||
|
|
||||||
|
## References (primary where used)
|
||||||
|
|
||||||
|
- Crowdy, D. G. (2006), “Analytical solutions for uniform potential flow past multiple cylinders,” *European Journal of Mechanics B/Fluids* 25, 459–470. DOI `10.1016/j.euromechflu.2005.11.005`.
|
||||||
|
- Glass, O., Lacave, C., Munnier, A. & Sueur, F. (2019), “Dynamics of rigid bodies in a two dimensional incompressible perfect fluid,” *Journal of Differential Equations*. DOI `10.1016/j.jde.2019.04.017`.
|
||||||
|
- Glauert, M. B. (1957), “The flow past a rapidly rotating circular cylinder,” *Proceedings of the Royal Society A*. DOI `10.1098/rspa.1957.0157`.
|
||||||
|
- Kharlamov, A. A. & Filip, P. (2012), “Generalisation of the method of images for the calculation of inviscid potential flow past several arbitrarily moving parallel circular cylinders,” *Journal of Engineering Mathematics* 77. Context only; no equation mapping or project result relies on it.
|
||||||
|
- Moore, D. W. (1957), “The flow past a rapidly rotating circular cylinder in a uniform stream,” *Journal of Fluid Mechanics* 2, 541–550. DOI `10.1017/S002211205700035X`; exact equation mapping not used here.
|
||||||
|
- Ueda, Y., Sellier, A., Kida, T. & Nakanishi, M. (2003), “On the low-Reynolds-number flow about two rotating circular cylinders,” *Journal of Fluid Mechanics* 495, 255–281. DOI `10.1017/S002211200300627X` (PDF-verified; DOI hold lifted).
|
||||||
|
- Lamb, *Hydrodynamics*, and Milne-Thomson, *Theoretical Hydrodynamics*: classical circle-flow sources; edition/page/equation details require bibliographic verification.
|
||||||
|
- Kelvin circulation theorem: classical result; original historical citation details require bibliographic verification. Glass et al. (2019) is the modern application used here.
|
||||||
|
|
||||||
|
|
||||||
|
## 11. Stage 2: reference-validation formulation
|
||||||
|
|
||||||
|
**11.1 — Core proposition [modeling assumption].** The prescribed-circulation MFS field is accepted as a high-order outer/strip reference only on geometrically valid inputs and only when independent, dense off-collocation diagnostics and parameter convergence bound its numerical error; collocation residual alone is not validation.
|
||||||
|
|
||||||
|
**11.2 — MFS equations [proved mathematical result].** On each circle, interior logarithmic sources are constrained to have zero sum body by body. Their strengths solve the normal-velocity equations for uniform flow plus prescribed geometric-CCW point-vortex periods. Equal-sign source images and opposite-sign vortex images enforce the horizontal-wall streamline symmetry in the infinite-image limit. Finite image layers are a truncation and must converge numerically.
|
||||||
|
|
||||||
|
**11.3 — Exact audit [proved mathematical result].** The one-circle velocity used as the analytic oracle is exactly §5.2: `u_r=U(1-a^2/r^2)cos(theta)` and `u_theta=-U(1+a^2/r^2)sin(theta)+Gamma/(2 pi r)`, with positive `Gamma` counterclockwise. Its wall normal trace is independent of `Gamma`, while its tangential trace changes by `Gamma/(2 pi a)`.
|
||||||
|
|
||||||
|
**11.4 — Independent formulation [modeling assumption].** The unbounded, zero-circulation cross-check measures agreement between independently discretized MFS and Fourier/Laurent boundary collocation: a uniform complex potential plus finite, single-valued Laurent multipoles `sum_(j,n) C_(jn) a^n/(z-c_j)^n`. It shares geometry and boundary data with MFS but uses neither logarithmic source points nor the MFS source radius. It is independently discretized by oversampled angular collocation. It is not Crowdy exact, does not represent prescribed circulation, and does not validate strip images.
|
||||||
|
|
||||||
|
**11.5 — Dense diagnostics [modeling assumption].** Validation samples use angular nodes distinct from all solve nodes; the Laurent check changes the angles rather than merely moving solve angles radially. Report per-body `L_inf` and RMS normal residuals; gap-facing and outer-facing angular-sector residuals; strip-wall normal residual on an explicitly declared finite `x` interval; circulation quadrature on exterior body contours; far-field recovery; singular values, numerical rank, condition number, coefficient norms, and per-body zero-source error. In-sample residuals are labeled `collocation_*` only.
|
||||||
|
|
||||||
|
**11.6 — Common-exterior error [modeling assumption].** On a fixed rectangle, use a tensor grid with composite-trapezoid weights, discard points within a declared exclusion radius of every body, and compute the weighted RMS velocity difference. Normalize by the weighted RMS reference disturbance from the declared uniform velocity; also report normalization by `|U|` when nonzero. The rectangle, grid, mask, retained weight, and normalization are part of every result. This metric is distinct from the pointwise `x/D=10` `E_inf_vector` profile objective.
|
||||||
|
|
||||||
|
**11.7 — Scientific acceptance rule [modeling assumption].** Stage 2 scientific status is judged from exact-circle identities; strict failure controls; dense off-grid body, circulation, and far-field diagnostics; order and source-radius sensitivity for both zero and rear-opposite circulation; independently discretized Laurent agreement; metric grid/mask sensitivity; and finite-image field differences. Thresholds chosen after observing outputs are regression guards only, not preregistered or frozen scientific evidence. Because the tested strip differences do not support a defensible asymptotic tail estimate, strip status is bounded finite-image consistency rather than a converged reference.
|
||||||
|
|
||||||
|
**11.8 — Falsification or claim limit [modeling assumption].** Rank loss, nonfinite output, uncontrolled condition/coefficient growth, off-grid or sector residual drift, circulation error, image/source/order sensitivity, mask/quadrature sensitivity, or disagreement with the Laurent formulation forces repair or limits the MFS reference claim. This stage establishes no multipole cloak mechanism, viscous closure, or stability result.
|
||||||
|
|
||||||
|
**11.9 — Stage-2 review [numerical observation].** Exact-circle and failure controls pass. Dense source-radius comparisons for both `Gamma=0` and `(0,2,-2)` agree on the weighted common exterior; the Fourier/Laurent solve uses distinct shifted check angles and agrees with MFS under the declared grid and exclusion-mask alternatives. The unbounded Stage-2 reference therefore **PASSes for bounded agreement between independently discretized MFS and Fourier/Laurent boundary collocation**, not as exact independent truth. Strip wall residuals decrease and 20/40/80-layer exterior field differences shrink, but no defensible asymptotic tail estimate is inferred; the strip verdict is **BOUNDED finite-image consistency**. Numerical cutoffs in tests are post-hoc regression guards with stated safety margins, not frozen scientific evidence.
|
||||||
|
|
||||||
|
|
||||||
|
## 12. Stage 3: frozen circulation--multipole derivation and charter
|
||||||
|
|
||||||
|
**12.1 — Frozen core proposition [modeling assumption].** In the unbounded outer problem, geometric-CCW rear circulations `(0,+Gamma,-Gamma)` have zero net circulation and therefore no logarithmic potential term. They generate a real potential-dipole coefficient `-Gamma b/pi` before body-induced source readjustment. Stage 3 passes only if the complete MFS coefficient is affine in real `Gamma`, its least-squares cancellation lowers both frozen reduced-field errors, and source truncations converge on declared common-exterior/profile regions and at held-out `Gamma`. It is falsified or claim-limited if the coefficient/sign is absent or wrong, the dipole is non-dominant, cancellation is not attainable on the real affine line, or reduced fields fail those held-out tests. Topology, stagnation, DNS, stability, and new metrics are excluded.
|
||||||
|
|
||||||
|
**12.2 — Complex convention [proved mathematical result].** Let `z=x+i y`, `W=phi+i psi`, and
|
||||||
|
|
||||||
|
`q(z)=dW/dz=u_x-i u_y`.
|
||||||
|
|
||||||
|
A real source strength `Q` and geometric-CCW vortex circulation `Gamma` at `zeta` have
|
||||||
|
|
||||||
|
`W_Q=Q/(2 pi) log(z-zeta)`, `q_Q=Q/[2 pi(z-zeta)]`,
|
||||||
|
|
||||||
|
`W_Gamma=-i Gamma/(2 pi) log(z-zeta)`, `q_Gamma=-i Gamma/[2 pi(z-zeta)]`.
|
||||||
|
|
||||||
|
Indeed `q_Q` gives `(u_x,u_y)=Q(dx,dy)/(2 pi r^2)`, while `q_Gamma` gives `Gamma(-dy,dx)/(2 pi r^2)`. These are exactly the signs in `singular_velocity`; adding `U(z+a^2/z)` gives the sign in `cylinder_velocity`.
|
||||||
|
|
||||||
|
**12.3 — Exact rear-pair expansion and hypothesis challenge [proved mathematical result].** Put `z_+=x_r+i b`, `z_-=x_r-i b`, with assignments `(Gamma_+,Gamma_-)=(Gamma,-Gamma)`. For `|z|>max(|z_+|,|z_-|)`, on one consistent logarithm branch,
|
||||||
|
|
||||||
|
`W_pair=-i Gamma/(2 pi)[log(z-z_+)-log(z-z_-)]`
|
||||||
|
|
||||||
|
`= i Gamma/(2 pi) sum_(n>=1) (z_+^n-z_-^n)/(n z^n)`.
|
||||||
|
|
||||||
|
The two `log z` terms cancel exactly because `Gamma_++Gamma_-=0`. The exact `1/z` potential coefficient is
|
||||||
|
|
||||||
|
`C_pair=i Gamma(z_+-z_-)/(2 pi)=-Gamma b/pi`.
|
||||||
|
|
||||||
|
Thus positive geometric-CCW circulation on the upper rear cylinder produces a **negative real** dipole coefficient; the prior narrative is accepted only with this sign. A positive real zero-circulation blockage coefficient can therefore be opposed by `Gamma>0`. This verifies the algebraic core hypothesis for the prescribed vortices, but does not yet prove that the body-readjusted coefficient has the same slope or that the dipole dominates finite-distance error.
|
||||||
|
|
||||||
|
**12.4 — Reflection and parity [proved mathematical result].** Let `R(x,y)=(x,-y)` and `R_v=diag(1,-1)`. A physical velocity is a polar vector, so reflection acts as
|
||||||
|
|
||||||
|
`(T u)(x,y)=R_v u(x,-y)`.
|
||||||
|
|
||||||
|
A reflection-symmetric field satisfies `T u=u`, equivalently `u_x(x,-y)=u_x(x,y)` and `u_y(x,-y)=-u_y(x,y)`. The zero-circulation geometry, uniform inflow, and unique fixed-period solution are reflection invariant, hence have this parity. For a vortex field, direct substitution gives `T v_Gamma(.;x_0,y_0)=v_-Gamma(.;x_0,-y_0)`: geometric circulation changes sign under orientation reversal. Therefore the geometric assignment upper `+Gamma`, lower `-Gamma` is invariant as a whole and its velocity has the same `(even,odd)` parity. This statement concerns a geometric CCW assignment. Calling `Gamma` a scalar without its orientation convention is misleading; circulation/vorticity is pseudoscalar under reflection. The common-sign rear assignment instead maps to its negative and has perturbation parity `(u_x odd in y, u_y even in y)` when isolated from the symmetric base.
|
||||||
|
|
||||||
|
**12.5 — Bodywise source multipoles [proved mathematical result].** For MFS sources `Q_jk` at `s_jk=c_j+delta_jk`,
|
||||||
|
|
||||||
|
`W_s,j=sum_k Q_jk/(2 pi) log(z-s_jk)`.
|
||||||
|
|
||||||
|
When `sum_k Q_jk=0`, and `|z-c_j|>rho_j=max_k|delta_jk|`,
|
||||||
|
|
||||||
|
`W_s,j=sum_(n>=1) A_jn/(z-c_j)^n`,
|
||||||
|
|
||||||
|
`A_jn=-[1/(2 pi n)] sum_k Q_jk delta_jk^n`,
|
||||||
|
|
||||||
|
`q_s,j=sum_(n>=1) -n A_jn/(z-c_j)^(n+1)`.
|
||||||
|
|
||||||
|
Truncation through `N` retains `1<=n<=N`; circulations remain separate exact vortex terms. Convergence is absolute outside every source ring. The implemented reconstruction therefore excludes all points with `|z-c_j|<=rho_j`; it is not a boundary-interior continuation and says nothing about viscous layers.
|
||||||
|
|
||||||
|
**12.6 — Global unbounded far field [proved mathematical result].** Expanding all singularities about the fixed global origin gives
|
||||||
|
|
||||||
|
`W(z)=U z + L log z + C/z+O(z^-2)`,
|
||||||
|
|
||||||
|
`L=[sum Q_k-i sum Gamma_j]/(2 pi)`,
|
||||||
|
|
||||||
|
`C=-(1/(2 pi)) sum_k Q_k s_k + (i/(2 pi)) sum_j Gamma_j z_j`.
|
||||||
|
|
||||||
|
Bodywise zero source makes `sum Q_k=0`; rear-opposite circulation makes `sum Gamma_j=0`, so `L=0`. The source moment includes the circulation-dependent MFS readjustment, while the second term is the prescribed vortex-position moment. Because the boundary equations are linear, solves at `Gamma=0` and unit rear-opposite basis give exactly
|
||||||
|
|
||||||
|
`C(Gamma)=C_0+Gamma dC`.
|
||||||
|
|
||||||
|
For real `Gamma`, minimizing `|C_0+Gamma dC|^2` gives
|
||||||
|
|
||||||
|
`Gamma_*=-Re(conj(dC) C_0)/|dC|^2`,
|
||||||
|
|
||||||
|
provided `dC != 0`. Exact cancellation occurs iff the residual `C_*` vanishes (within a declared numerical tolerance); otherwise `|C_*|` is the distance from the origin to the real affine coefficient line. This is a closest complex cancellation, not permission to use complex circulation.
|
||||||
|
|
||||||
|
**12.7 — Frozen reduced diagnostics [modeling assumption].** For term counts `N=1,2,...`, compare the source-multipole reconstruction plus exact circulation terms against the full unbounded MFS field using only: (i) the Stage-2 weighted common-exterior velocity error on `[-3,6] x [-3,3]`, `181 x 121`, exclusion `1.1a`; (ii) the complete `x/D=10` profile over `y/D in [-3,3]` with 1201 points and `profile_errors`; and (iii) reflection parity defect, the maximum absolute mismatch in `u_x(x,y)-u_x(x,-y)` and `u_y(x,y)+u_y(x,-y)`, normalized by `U`. No topology or stagnation diagnostic enters Stage 3.
|
||||||
|
|
||||||
|
**12.8 — Strip asymptotic result [proved mathematical result].** The horizontal strip does not have the unbounded-plane Laurent basis as its downstream asymptotic basis. Wall-compatible harmonic disturbances separate into transverse Neumann/Dirichlet modes with exponential streamwise factors, while the uniform through-flow is the zero mode. Finite image sums therefore do not justify an algebraic `1/z` far-field claim.
|
||||||
|
|
||||||
|
**12.9 — Strip scope [modeling assumption].** No strip modal diagnostic is added in Stage 3: cancellation and `Gamma_*` are scoped strictly to the unbounded outer solution, and any strip mechanism claim remains bounded pending a separately derived converged wall-compatible modal analysis.
|
||||||
|
|
||||||
|
|
||||||
|
**12.10 — Concentrated Stage-3 review [numerical observation].** Derivation review: PASS; §§12.2–12.6 derive every implemented sign and coefficient directly. Reproducibility/counterexample review: PASS; direct source velocities, pair sign, zero log coefficient, source-series convergence, polar reflection, affine prediction, real cancellation, and held-out `Gamma=1.37`/region convergence are tested. Claim review: PASS with limitation; the unbounded canonical dipole cancels essentially exactly and the downstream profile improves, but finite common-exterior disturbance does not vanish and strip/DNS/topology claims remain excluded. File review: PASS; 9 package-root source files and 10 source files including `tests/test_core.py`; no file was added. Stage-3 verdict: **PASS for an unbounded outer dipole-cancellation mechanism, bounded to its far-field and tested finite-distance consequences.**
|
||||||
|
|
||||||
|
|
||||||
|
## 13. Historical Stage-4 mechanism-test charter and preregistration
|
||||||
|
|
||||||
|
**13.1 — Sole question [modeling assumption].** Does the geometric-CCW rear-opposite basis `(0,+Gamma,-Gamma)` explain the observed downstream disturbance reduction? Passing requires the frozen outer-model counterfactuals and genuinely held-out viscous evidence to agree in sign, relative strength, profile shape, and optimum-side degradation. Comparable counterfactual performance, a wrong sign/parity, failed held-out prediction, or absent field provenance limits or falsifies the claim. No stability question enters Stage 4.
|
||||||
|
|
||||||
|
**13.2 — Frozen bases and norm [modeling assumption].** In body order `(front,rear_y_plus,rear_y_minus)`, define unit Euclidean-circulation-norm controls `b_ro=(0,1,-1)/sqrt(2)`, `b_cr=(0,1,1)/sqrt(2)`, and `b_f=(1,0,0)`. A scalar amplitude `g` means `Gamma_vector=g b`, so all ablations use identical `||Gamma_vector||_2=|g|`, geometry, discretization, and profile; no basis-specific rescaling is allowed after results are seen.
|
||||||
|
|
||||||
|
**13.3 — Affine basis response [proved mathematical result].** The MFS Neumann system is linear at fixed geometry and discretization; therefore `u_b(g)=u_0+g v_b` and every global Laurent coefficient is affine in real `g`.
|
||||||
|
|
||||||
|
**13.4 — Frozen analytic tests [modeling assumption].** For each basis, report: net geometric circulation/log coefficient; `dC/dg` for the complex dipole coefficient; reflection parity; the smooth complete-profile disturbance norm `J_2(g)=[mean_y |u_b(g)-U e_x|^2]^(1/2)` and its analytic directional derivative at `g=0`, `J_2'(0)=mean[(u_0-Ue_x) dot v_b]/J_2(0)` when `J_2(0)>0`; and `E_inf_vector` one-sided derivatives at `g=0` for `h={0.2,0.1,0.05,0.025}` with convergence reported, not relabeled as an analytic derivative. Also minimize `J_2` on each real affine basis at fixed norm convention, report the resulting dipole and full `x/D=10`, `y/D in [-3,3]`, 1201-point profile errors, and evaluate equal-amplitude ablations. Rear-opposite passes the theoretical-basis gate only if it alone has zero net circulation, correct symmetric `(u_x even,u_y odd)` parity, a dipole direction opposing blockage, and materially larger smooth-profile reduction than both counterfactuals. `E_inf` is acknowledged as nonsmooth; only declared finite-difference directional slopes are admissible.
|
||||||
|
|
||||||
|
**13.5 — Frozen gap-flux diagnostic [modeling assumption].** Gap flux is the oriented line integral `integral u dot n ds` across each shortest center-to-center gap segment after removing the two radii; the line normal is the left rotation of the segment tangent. Report signed upper-front and lower-front values and their sum. It is descriptive and cannot replace the dipole/profile gates.
|
||||||
|
|
||||||
|
**13.6 — DNS preregistration and evidence contract [modeling assumption].** A DNS circulation mapping may be fit only from endpoint `ux,uy` arrays with coordinates, body mask, `D,U`, action/body IDs, and q-in provenance. Positive fitted circulation is geometric CCW and must be inferred from an explicitly sampled CCW contour or from outer velocity WLS using the same `u_x-i u_y` convention as §12.2. The training mask is frozen as `s={3.45,3.65}` and held-out mask as `s={3.55}` for a linear `Gamma_eff(s)` closure; complete `x/D=10` profiles and the Stage-2 common-exterior norm are predicted without retuning. D20 aggregate scan points `s={3.4,3.5,3.6,3.8}` and D30 points `s={3.45,3.55,3.65}` are frozen only for the independent optimum-side performance test: the held-out center must be no worse than the neighboring values, and at least one point on each sufficiently separated side must degrade. Aggregate errors cannot determine `Gamma_eff`, profile shape, contour circulation, full-exterior error, or the action-to-geometric-circulation sign.
|
||||||
|
|
||||||
|
**13.7 — Scope omissions frozen before implementation [modeling assumption].** Robust stagnation/separatrix/virtual-body diagnostics are omitted because the existing files have no continuation, root-completeness, or separatrix-closure machinery; isolated grid/root guesses would be fragile. Potential pressure and individual/total force are omitted because Bernoulli/Blasius force is not needed to answer the sole dipole/profile hypothesis and DNS pressure/force fields are absent. These omissions bound topology and force claims to “not tested.” The minimum implemented metrics are therefore dominant log/dipole coefficients, analytic smooth-profile sensitivity, converged one-sided `E_inf` slopes, parity, gap flux, complete profile errors, common-exterior capability, and held-out DNS/performance evidence when its contract is met.
|
||||||
|
|
||||||
|
**13.8 — Frozen execution rule [modeling assumption].** Search narrowly for existing endpoint arrays first. If absent, aggregate summaries receive only their limited performance role; historical fitted `Gamma_eff=-4.293...` values are non-evidence until source arrays are recovered. No CFD is launched unless field-level evidence remains essential after the analytic/counterfactual result, and any run must be the smallest serial D20 train/held-out design with printed body/action/wall oracle and external `/tmp` output. Completion may be PASS, FAIL, or BOUNDED.
|
||||||
|
|
||||||
|
|
||||||
|
**13.9 — Historical Stage-4 review [numerical observation].** This pre-campaign review was BOUNDED because endpoint fields were then absent. It is superseded by §§16–18 and retained only as execution history: v4 now supplies direct fields and contour sign, while corrected descriptive WLS and the strip sign comparison reject the actual-actuation cancellation hypothesis.
|
||||||
|
|
||||||
|
|
||||||
|
## 14. Stage 5: frozen physical-value and stability hierarchy
|
||||||
|
|
||||||
|
**14.1 — Sole question [modeling assumption].** For the fixed prescribed-circulation steady harmonic outer solution used by the sole rear-opposite mechanism, which precisely defined notions of well-posedness, numerical stability, structural persistence, and dynamical stability are supported? Passing requires the Stage-1 fixed-period uniqueness result, Stage-2 convergence/conditioning evidence, and a reproducible sampled audit showing nondegenerate interior stagnation roots persist under the frozen small parameter perturbations. A missed/failed root, count change, small Jacobian singular value, uncontrolled discretization drift, or absent evolution law limits the claim. No Euler or Navier–Stokes eigenanalysis is introduced.
|
||||||
|
|
||||||
|
**14.2 — Four noninterchangeable levels [modeling assumption].** (i) *Mathematical BVP well-posedness* means existence, uniqueness, and continuous dependence in stated spaces; Stage 1 supplies only conditional fixed-circulation uniqueness. (ii) *Numerical stability* means conditioning plus convergence under representation parameters; Stage 2's convergence and independently discretized agreement are scientific evidence, while measured singular values and conditions are reported diagnostics with broad fail-fast regression guards. (iii) *Structural stability* here means only local persistence of sampled interior stagnation roots. (iv) *Dynamical stability* requires a state, evolution operator, equilibrium, and perturbation class. The present model prescribes no such dynamics, so decay, spectral, Hamiltonian, Euler-vorticity, and Navier–Stokes stability are undefined rather than positive.
|
||||||
|
|
||||||
|
**14.3 — Discrete continuous dependence [proved mathematical result].** For a fixed nondegenerate discrete MFS matrix `A(mu)`, source coefficients satisfy `A(mu)q(mu)=b(mu)`. Continuity of `A,b` and `sigma_min(A(mu_0))>0` imply locally continuous `q(mu)=A(mu)^+b(mu)`, and affine dependence on prescribed circulation is exact at fixed geometry. This is discrete continuous dependence, not a complete PDE existence or shape-differentiability theorem.
|
||||||
|
|
||||||
|
**14.4 — Interior stagnation definition and Jacobian [proved mathematical result].** For parameters `mu` (circulation, geometry, or a separately valid confinement representation), an interior stagnation point is `x_*` in the open fluid domain with
|
||||||
|
|
||||||
|
`F(x_*,mu)=u(x_*;mu)=0`, `r=||F(x_*,mu)||_2`.
|
||||||
|
|
||||||
|
The velocity Jacobian is `J=D_x F`, approximated by centered differences
|
||||||
|
|
||||||
|
`J[:,k]=[F(x+h e_k,mu)-F(x-h e_k,mu)]/(2h)`.
|
||||||
|
|
||||||
|
Record `det J` and singular values `sigma_max>=sigma_min>=0`. If `F` is continuously differentiable and `det J(x_*,mu_*) != 0` (equivalently `sigma_min>0`), the implicit-function theorem gives a unique local root branch `x(mu)` and
|
||||||
|
|
||||||
|
`dx/dmu = -J^(-1) partial_mu F`.
|
||||||
|
|
||||||
|
This proves local root persistence only. It does not prove global root completeness, separatrix equivalence, or absence of bifurcations elsewhere. Near `det J=0`, Brøns & Hartnack (1999) is the primary topology precedent for local streamline bifurcations near degenerate interior critical points; no equation number is imported.
|
||||||
|
|
||||||
|
**14.5 — Frozen sampled-root audit [modeling assumption].** The canonical unbounded geometry is centers `(0,0),(1.3,+0.75),(1.3,-0.75)`, radius `a=0.5`, `U=1`, `n_boundary=64`, source ratio `0.65`, and Stage-3 `Gamma_*=4.818148415799425` in `(0,+Gamma,-Gamma)`. Search only the compact rectangle `[-2,5] x [-2.5,2.5]` from a `29 x 25` seed grid. Accept roots only at least `0.08D` outside every body (`distance >= a+0.08`), with residual `<=1e-9`; use centered-Jacobian step `h=2e-5`, deduplicate within `2e-5D`, and classify as nondegenerate only when `sigma_min>=1e-4`. Because a finite seed grid cannot establish completeness, every result is called a **sampled root audit**, never a proof of global topology. Boundary stagnation points are excluded: they require boundary coordinates and a separate boundary-critical-point theory.
|
||||||
|
|
||||||
|
**14.6 — Frozen perturbations and continuation [modeling assumption].** Track baseline sampled roots by one-to-one nearest continuation, flagging unmatched roots, count changes, displacement above `0.20D`, residual failure, or `sigma_min<1e-4`. The preregistered unbounded perturbations are `Gamma/Gamma_* in {0.98,1.02}` and rear-center changes `(delta x_r,delta b) in {(-0.02,0),(+0.02,0),(0,-0.02),(0,+0.02)}`, applied symmetrically as rear centers `(1.3+delta x_r,+/-(0.75+delta b))`. Strip `H` and image-layer perturbations are excluded: Stage 3 established that the plane Laurent mechanism does not transfer to strip asymptotics, and no converged strip `Gamma_*` or structural charter is defined. This is an explicit scope limit, not evidence of confinement persistence.
|
||||||
|
|
||||||
|
**14.7 — Analytic failure controls [proved mathematical result].** For the exact one-cylinder field with `Gamma=0`, the two stagnation points lie on the body boundary. The affine field `F(x,y)=(x,0)` has a singular Jacobian and a continuum of roots, while `F(x,y)=(x,y)` has one nondegenerate root.
|
||||||
|
|
||||||
|
**14.8 — Required control behavior [modeling assumption].** The audit excludes the exact-circle boundary roots by its interior margin, classifies `F=(x,0)` as degenerate and `F=(x,y)` as nondegenerate, and returns no roots for a deliberately root-free constant field. These controls prevent an audit that always passes.
|
||||||
|
|
||||||
|
**14.9 — Dynamical precedents and non-transfer [primary-literature-supported result].** Crowdy & Marshall (2005) derive Hamiltonian point-vortex motion around multiple circular islands; that problem evolves vortex position and is different from evaluating a prescribed harmonic velocity field. Classical Föppl and Protas's higher-order Föppl models evolve free point vortices or finite-area vortex moments and discuss stability of those equilibria; they do not establish stability of this fixed prescribed-circulation BVP. Passive-particle dynamics `dot x=u(x)` could define trajectory stability of a stagnation point, and Euler-vorticity or Navier–Stokes perturbations could define other stability problems, but none is introduced in Stage 5.
|
||||||
|
|
||||||
|
**14.10 — Concentrated-review rule [modeling assumption].** Exactly one end-stage review must jointly check derivation/citations, sampled-root reproducibility and failure controls, claim limits, tests, no-CFD compliance, and file budget. The final verdict may be PASS, BOUNDED, or FAIL. Uniqueness, numerical convergence, and structural persistence must never be relabeled as perturbation decay or global hydrodynamic stability.
|
||||||
|
|
||||||
|
|
||||||
|
**14.11 — Concentrated Stage-5 review [numerical observation].** Derivation/citation review: PASS; §§14.1–14.7 separate the four levels, derive the root/Jacobian/implicit-function definitions, and scope Brøns–Hartnack, Crowdy–Marshall, and Föppl/Protas without invented equation numbers. Reproducibility/failure-control review: PASS; exact affine roots verify residual and centered Jacobians, a shifted affine root verifies nearest continuation, and singular, constant, and exact-circle boundary cases do not pass as nondegenerate interior persistence. Canonical sampled-root review: BOUNDED; the frozen `29 x 25` audit finds no admissible interior root at `Gamma_*`, `0.98/1.02 Gamma_*`, or the four rear-geometry perturbations. Thus there is no interior root branch to qualify; unchanged sampled count zero is evidence only on the frozen seeds/rectangle, not a global topology theorem. Numerical review: PASS for the tested convergence evidence; as a regression snapshot, at `n=64` the canonical matrix has `sigma_min=2.11372e-5`, condition `6.98404e5` independent of `Gamma`, and the `+/-2%` fields obey affine midpoint continuity to the test tolerance. Claim review: PASS with limitation; conditional uniqueness, numerical convergence, and the absence of sampled interior degeneracy are physically useful outer-reference properties, but dynamic stability is undefined and confinement persistence is untested. No-CFD/file review: PASS; no CFD was run and no file was added. Stage-5 verdict: **BOUNDED**—the hierarchy is rigorous and the sampled audit is numerically controlled, but it supplies no positive interior-root persistence branch and no dynamical-stability result.
|
||||||
|
|
||||||
|
|
||||||
|
## 15. Historical Stage-6 evidence-freeze charter
|
||||||
|
|
||||||
|
**15.1 — Sole question [modeling assumption].** Does the accumulated evidence support one bounded mechanism claim: prescribed rear-opposite circulation is the unique tested direction that cancels the leading unbounded potential-flow dipole and reduces the declared downstream disturbance, while its causal mapping to the finite-Re rotating-cylinder flow remains unvalidated? Stage 6 introduces no science, metric, simulation, or fit.
|
||||||
|
|
||||||
|
**15.2 — Pass evidence [modeling assumption].** The evidence package passes only if the numbered derivation and four-category statement audit are internally consistent; the source-to-equation ledger retains every bibliographic hold; Stage 2 and Stage 3 theoretical assertions are regenerated by `pinball_math`; Stage 4 is resolved as a split result: endpoint fields and contour sign now exist; geometric sign is PASS, affine WLS closure is BOUNDED, and the actual-actuation favorable-cancellation hypothesis FAILS; Stage 5 remains BOUNDED and dynamical stability remains undefined; aggregate CFD performance is retained only with provenance and explicit scalar-only limitations; the authority/reproduction order is unambiguous; and root/total file counts remain within `10/30` excluding generated bytecode.
|
||||||
|
|
||||||
|
**15.3 — Claim-limiting evidence [modeling assumption].** Any mixed statement label, unsupported citation or equation mapping, missing reproduction command, claim based on absent arrays, inferred equality between wall action and geometric circulation, promotion of aggregate errors to field evidence, stability claim without an evolution law, unresolved cross-stage contradiction, failed test/compile/lint check, or file-budget breach prevents a Stage-6 package PASS until repaired or explicitly bounded.
|
||||||
|
|
||||||
|
**15.4 — Concentrated-review rule [modeling assumption].** Exactly one final review jointly audits derivation, citations, reproducibility, counterexamples and held-out limits, claim matrix, environment guidance, public API, scoped diff, and file budget. Scientific stages may remain BOUNDED even if the evidence package passes, provided every boundary is explicit and no missing evidence is silently promoted.
|
||||||
|
|
||||||
|
|
||||||
|
**15.5 — Concentrated Stage-6 review record [numerical observation].** The evidence-package governance verdict is **PASS** after the v4 reconciliation: endpoint contour sign is PASS, corrected WLS and strip representation are BOUNDED, the actual-actuation favorable-cancellation hypothesis is FAIL, and Stage 5 remains BOUNDED with dynamical stability undefined. The package makes no causal WLS, full-PPO, or no-slip-channel claim.
|
||||||
|
|
||||||
|
|
||||||
|
## 16. Finite-Re endpoint bridge and strip charter
|
||||||
|
|
||||||
|
**16.1 — Endpoint evidence contract [modeling assumption].** The only production endpoint set is canonical D20 `q_in` plus accepted numeric wall branch `[0,+Omega,-Omega]` at `s={3.45,3.55,3.65}` under one frozen configuration; `s=3.45,3.65` are training endpoints and `s=3.55` is held out. `q_blk` is nonranking, and diagnostics are separate artifacts. Schema v3 preserves raw uint16 solver flags and defines fluid cells exactly by Celeris `FLUID=0x0001`; `body_id=-1` means solver fluid, `-2` means static solver-nonfluid wall/inlet/outlet, and controlled circle interiors receive registry IDs `0,1,2` in semantic order `(front,rear_y_plus,rear_y_minus)`. The body-config loader ignores a JSON `id` key and allocates those IDs by insertion order. Geometry may label a body only where the solver says nonfluid, and every controlled ID must be nonempty. Every artifact also preserves coordinates, `(y,x)` axes, masks, centers, signed action and wall oracle, complete configurations, source/environment identity and hashes, final lattice and advective times, settling windows, finite/shape validation, and no-clobber hashes. Publication is from a validated temporary sibling by an atomic no-replace rename where supported, so a failed write is not an endpoint artifact. Production endpoints are prospectively frozen at final `tU/D=250`, sampling interval `2 D/U`, and two adjacent 10-sample windows. For each window, average `[E_inf_vector,E_L2_vector]`; define settling change as the Euclidean RMS across the two componentwise mean differences and require a finite value `<=1e-4`. Equality passes. A short diagnostic with fewer than 20 samples records JSON `null`, `computed=false`, `qualified=false`, and `reason=insufficient_samples`. The `1e-4` threshold is below 0.5% of the historical low-error magnitude near `0.023` and above historical scalar tail ranges up to approximately `4.6e-5`; those preexisting values informed prospective resolution design but do not prove that any new endpoint qualifies. This is an evidence schema, not physical evidence.
|
||||||
|
|
||||||
|
**16.2 — Geometric contour sign [proved mathematical result].** Parameterize every contour explicitly counterclockwise by `r(theta)=c+R(cos theta,sin theta)`, `0<=theta<2pi`, with tangent `t=(-sin theta,cos theta)`. Then `Gamma_CCW=integral u(r(theta)) dot t R dtheta`; a positive point vortex gives positive `Gamma_CCW`. Bilinear interpolation is admissible only when all four stencil cells are finite fluid cells. Multiple radii must agree in sign before the numeric wall branch is named by geometric circulation.
|
||||||
|
|
||||||
|
**16.3 — Preregistered outer WLS [modeling assumption].** On one frozen common-fluid mask separated from bodies and strip walls, fit the endpoint difference `d=u_ctl-u_in` to the strip rear-opposite unit field `v` with fixed nonnegative quadrature weights: `Gamma_eff=sum w d dot v/sum w |v|^2`. Report mask, weights, denominator and weighted vector residual. The fit has no intercept because `d=0` at zero model amplitude by construction; contour and WLS signs must agree.
|
||||||
|
|
||||||
|
**16.4 — Two-point closure [proved mathematical result].** Two distinct training pairs `(s_-,Gamma_-)`, `(s_+,Gamma_+)` determine exactly `Gamma_eff(s)=a+b s`, with `b=(Gamma_+-Gamma_-)/(s_+-s_-)` and `a=Gamma_--b s_-`. The value at `s=3.55`, its complete `x/D=10` two-component profile, and frozen common-exterior field are predictions with no retuning. No odd symmetry is imposed.
|
||||||
|
|
||||||
|
**16.5 — Exact free-slip strip modes [proved mathematical result].** Downstream of all bodies, an irrotational disturbance has potential `phi`; `Delta phi=0`, wall impermeability gives `partial_y phi=0` at `y=+-H`, and fixed through-flow removes any varying zero mode. The normalized Neumann potential basis is `cos(k_n(y+H))`, `k_n=n pi/(2H)`, whose squared integral is `H` for `n>=1`. A downstream-decaying potential `phi_n=-(A_n/k_n) exp[-k_n(x-x0)] cos[k_n(y+H)]` gives `u'_x=A_n exp[-k_n dx] cos[k_n(y+H)]` and `u'_y=A_n exp[-k_n dx] sin[k_n(y+H)]`. Thus longitudinal cosine and transverse sine coefficients are equal at a station and both propagate by `exp(-k_n dx)`. Under midline reflection, odd `n` potential modes are antisymmetric and even `n` modes symmetric; velocity parity follows by differentiating.
|
||||||
|
|
||||||
|
**16.6 — Strip numerical representation [modeling assumption].** `strip_basis_affine` constructs `rear_opposite`, `common_rear`, and `front_only` equal-norm affine fields with finite MFS image layers. Modal station projection and exponential propagation, image-layer differences, station stability, and ablation determine the bounded strip claim. Unbounded `far_field_coefficients` are prohibited for strip asymptotics; decreasing wall residual alone is insufficient.
|
||||||
|
|
||||||
|
**16.7 — No-slip-channel PDE contract [modeling assumption].** The independent future problem is steady incompressible Navier–Stokes at `Re_D=50`, with fully developed no-body `q_in`, stationary no-slip walls, rotating-cylinder full no-slip, and direct profiles against that same `q_in`. Its optimum `s` is not inherited from uniform/free-slip flow. Method priority is direct steady/mean continuation and convergence, then viscous gap momentum/boundary-layer/separation/base-bleeding analysis, then mean-linearized or resolvent modes after a qualified base. A future PASS requires settled profile improvement plus held-out or perturbation mechanism support; FAIL is nonimprovement/nonconvergence or wrong mechanism sign/shape; BOUNDED is performance without mechanism/robustness. No no-slip run/eigenanalysis is performed here.
|
||||||
|
|
||||||
|
**16.8 — Asymptotic limit [primary-literature-supported result].** Watson's Stokes–Oseen rotating-cylinder construction concerns `Re<<1`; it does not asymptotically control `Re_D=50`. Attached boundary-layer closures likewise lack a controlled parameter in a separated moderate-Re three-body channel. These methods are limiting references, not the current no-slip model.
|
||||||
|
|
||||||
|
**16.9 — Stage-3 field-analysis preregistration [modeling assumption].** Before reading endpoint field values, freeze normalized coordinates about the front center, velocity by `U`, and differences `delta q=q_ctl-q_in`. Primary `E_inf_vector` is the maximum pointwise norm over the complete common solver-fluid `x/D=10` cross-section after excluding the two outermost lattice rows; `x/D={2,4,6}` are diagnostics. Rear-body CCW contours use radii `r/D={0.60,0.65,0.70}` for both bodies: each exceeds `R/D=0.5` by at least two D20 lattice cells, paired `0.70D` contours do not intersect because rear centers are `1.5D` apart, and they remain separated from the front body. Every bilinear stencil must be fluid in both endpoint and q-in. Radius variation is reported as viscous enclosed-vorticity evidence, not required to form a mathematical plateau or identified with a topological potential period.
|
||||||
|
|
||||||
|
**16.10 — Stage-3 strip-WLS preregistration [modeling assumption].** Fit dimensionless `delta q` with uniform weights on the common solver-fluid tensor subset `x/D in [-2,10]`, `|y/D|<=6`, sampled every second D20 lattice cell (`Delta x/D=Delta y/D=0.1`), excluding distance `<0.75D=1.5R` from every body. This domain is already at least `9D` from the free-slip walls, so no additional active wall clipping occurs; record the `1D` wall-margin rule. The model is `strip_basis_affine` at normalized centers `[(0,0),(1.3,0.75),(1.3,-0.75)]`, `R=0.5`, `H=15`, `U=1`, rear-opposite unit-L2 basis `(0,+1/sqrt(2),-1/sqrt(2))`, `n_boundary=32`, source ratio `0.65`, and 40 image layers. The fitted scalar `g` converts to each rear geometric circulation as `Gamma_upper/(UD)=g/sqrt(2)` and `Gamma_lower/(UD)=-g/sqrt(2)`. Only `s={3.45,3.65}` train `g(s)=a+bs`; `s=3.55` is held out. Report held-out scalar error, full-profile vector residual, common-domain weighted RMS/relative RMS, and residual reflection-parity defect. No material-usefulness threshold was preregistered, so predictive usefulness receives no numerical PASS threshold after observing data. The sign gate passes only if upper/lower contours are respectively positive/negative at every valid frozen radius and the WLS per-rear sign agrees; opposite signs fail, while missing/strongly inconsistent magnitudes bound the claim.
|
||||||
|
|
||||||
|
**16.11 — Scientific status [numerical observation].** The v4 immutable endpoint analysis validates contour sampling, corrected blockage-subtracted WLS, two-point held-out arithmetic, and direct profiles. The geometric contour sign is a PASS field observation; WLS interpolation and full-field prediction remain BOUNDED. WLS is descriptive projection, not a causal action-to-circulation map.
|
||||||
|
|
||||||
|
**16.12 — Stage-3 endpoint verdict [numerical observation].** The immutable v4 analysis passes the contour geometric sign gate: accepted numeric `[0,+Omega,-Omega]` produces upper negative/lower positive geometric circulation. Corrected WLS gives `g={-12.02563,-12.20055,-12.37465}`; scalar held-out error is `4.17e-4`, while common-field relative residual is `0.117943` and profile residual remains nonzero, so affine closure is BOUNDED. The projected WLS amplitude is descriptive and cannot be called causal effective circulation.
|
||||||
|
|
||||||
|
## 17. Stage-4 free-slip strip modal qualification charter
|
||||||
|
|
||||||
|
**17.1 — Modal sign convention [proved mathematical result].** For `phi_n=B_n exp[-k_n(x-x0)] cos[k_n(y+H)]`, direct differentiation gives `u_x=-k_n B_n exp[-k_n dx] cos[k_n(y+H)]` and `u_y=-k_n B_n exp[-k_n dx] sin[k_n(y+H)]`. Defining velocity amplitude `A_n=-k_n B_n` therefore gives equal signed cosine/sine coefficients. `strip_mode_field` accepts `A_n`; `strip_modal_projection` independently recovers the `u_x` cosine and `u_y` sine coefficients. No sign is inserted to improve agreement.
|
||||||
|
|
||||||
|
**17.2 — Frozen modal audit [modeling assumption].** Use canonical normalized geometry, `H/D=15`, `U=1`, `R/D=0.5`, `n_boundary=32`, source ratio `0.65`, image layers `{20,40,80}`, stations `x/D={4,6,8,10}`, a 3001-point full-wall-to-wall `y` grid, and modes `n=1,...,12`. All bodies lie upstream because the rears end at `x/D=1.8`. Since `k_1=pi/30`, the slowest mode decays only by `exp(-pi Delta x/30)`. For base disturbance `u_0-Ue_x` and each unit-L2 affine direction, report zero mode, independently projected `u_x/u_y` coefficient disagreement, and station-collapse values obtained by multiplying station coefficients by `exp[k_n(x-4)]`. Modes whose reference vector amplitude exceeds `1e-8` times the largest reference mode are significant; no post-observation coefficient threshold is introduced.
|
||||||
|
|
||||||
|
**17.3 — Frozen objective and ablation [modeling assumption].** Before computing Stage 4, freeze the complete smooth full-height profile objective at `x/D=10`, `J(g)=[mean_y |u_0+g v_b-Ue_x|^2]^(1/2)`, on the same 3001-point grid. For each unchanged equal-L2 basis `rear_opposite`, `common_rear`, and `front_only`, use the analytic real optimum from the affine quadratic and report `J(0)`, `g_opt`, `J_opt`, complete-profile `E_inf_vector`, and leading modal coefficients. Rear-opposite is uniquely favorable only if its `J_opt` is strictly below both counterfactual values at every image layer. A held amplitude `g=0.5 g_opt` must equal direct affine reconstruction to roundoff; this checks exact discrete linearity, not independent PDE validity.
|
||||||
|
|
||||||
|
**17.4 — Convergence and verdict [modeling assumption].** Compare projected coefficients and `g_opt` at 20/40/80 image layers; decreasing differences are evidence of bounded finite-image consistency, while the previously observed approximate `0.5` image-tail ratio prohibits claiming a certified infinite-image error bound. The confinement cancellation mechanism PASSes only if rear-opposite is uniquely favorable, modal relations and station propagation are qualitatively consistent, and theoretical `g_opt` has the same sign as the actual finite-Re WLS `g<0`. Opposite theoretical/actual signs FAIL the proposed cancellation link without redefining the basis. Otherwise unresolved image/modal behavior yields BOUNDED. No-slip remains outside this stage.
|
||||||
|
|
||||||
|
**17.5 — Stage-4 observed audit [numerical observation].** The modal sign audit confirms the `A_n=-k_nB_n` convention: synthetic projected mode tests recover equal signed components. At `H/D=15`, `k_1=0.1047198`. For the canonical zero-circulation base, the through-flow mode is approximately `-2.09e-5` at stations `4,6,8,10`; rear-opposite/common-rear/front-only direction zero modes are approximately `+3.07e-6/-9.26e-5/-6.55e-5`. At layer 40, significant symmetry-allowed even rear-opposite modes satisfy the independent ux/uy projection and station-collapse audit; the prior order-one comparison came from negligible forbidden odd `n=1` coefficients and is withdrawn.
|
||||||
|
|
||||||
|
**17.6 — Stage-4 qualification verdict [numerical observation].** Rear-opposite is uniquely favorable under the frozen full-profile smooth RMS objective at layers 20/40/80: `J_opt` is approximately `1.47136e-4/1.47095e-4/1.47085e-4`, versus about `5.26245e-3/5.26244e-3/5.26247e-3` for both counterfactuals. Its optimal theoretical amplitude is `g_opt=+6.14794` at layer 40. The omitted-base v1 value `g=-14.63058` is withdrawn; corrected v4 WLS gives `g=-12.20055`, still opposite in sign. Exact held-amplitude affine reconstruction errors are below `9e-15`, which validates discrete linearity only. Image-layer changes in `g_opt` shrink from `1.20e-5` to `2.28e-7`, but the prior approximate image-tail ratio still prevents an infinite-image certification. Because the favorable theoretical sign opposes the measured finite-Re sign, the actual-actuation-as-favorable-strip-cancellation hypothesis is **FAIL** under the frozen Stage-4 charter; the bounded finite-image numerical observations and finite-Re WLS sign result remain separately reported.
|
||||||
|
|
||||||
|
## 18. Critical Stage-3/4 correction
|
||||||
|
|
||||||
|
**18.1 — Correct finite-Re comparison [proved mathematical result].** If `u_0` is the zero-circulation three-body strip field and `v=u_1-u_0` is the unchanged unit-L2 circulation direction, then the theoretical controlled disturbance relative to uniform no-body inflow is `(u_0-Ue_x)+g v`. Because the CFD reference `q_in` has no bodies, fitting `q_ctl-q_in` directly to `g v` omits blockage and biases `g`. The corrected WLS fits `[q_ctl-q_in-(u_0-Ue_x)]` to `v`, and held-out prediction is `(u_0-Ue_x)+g_pred v`. Contour circulation is unaffected.
|
||||||
|
|
||||||
|
**18.2 — Symmetry correction [proved mathematical result].** Reflection-symmetric polar velocity has `u_x` even and `u_y` odd. In the shifted strip basis, even `n` gives `cos[n pi(y+H)/(2H)]` even about `y=0` and `sin[...]` odd; odd `n` occupies the opposite parity sector. Therefore rear-opposite and the symmetric base must be audited on significant even modes, beginning with `n=2`; the prior order-one relative error of negligible forbidden `n=1` is methodologically invalid and withdrawn. Common-rear/front-only are not required to share that sector.
|
||||||
|
|
||||||
|
**18.3 — Corrected modal audit [modeling assumption].** Use stations `x/D={4,6,8,10,12,14,16}`; all are downstream of physical singularities at `x<=1.3`, while transverse images share those x coordinates. For each station and mode report raw coefficients and absolute disagreement. A coefficient is significant only when its vector amplitude exceeds `max(1e-8` times the dominant station coefficient, `1e-12)`; relative diagnostics below that floor are forbidden. For rear-opposite/base conclusions use significant even modes. Exact propagation is an infinite-strip property; finite image truncation and trapezoidal endpoint quadrature can leave absolute residuals, which must decrease or remain resolution-bounded with image layers.
|
||||||
|
|
||||||
|
**18.4 — Corrected results [numerical observation].** At layer 40, rear-opposite `n=2` coefficients are `(-2.84940170e-3,-2.84940177e-3)` at `x/D=4`, `(-8.10966882e-4,-8.10967014e-4)` at 10, and `(-2.30808880e-4,-2.30809072e-4)` at 16. Absolute ux/uy disagreements are `7.28e-11`, `1.32e-10`, and `1.92e-10`; collapsed n=2 relative spreads are below `3.1e-7`. Thus the prior modal-propagation failure is withdrawn: significant symmetry-allowed rear-opposite modes pass the finite-image modal audit to the reported numerical resolution.
|
||||||
|
|
||||||
|
**18.5 — Corrected qualification [numerical observation].** Corrected Stage-3 WLS gives `g={-12.02563,-12.20055,-12.37465}` with cosine similarities `{-0.99078,-0.98924,-0.98755}` after subtracting theoretical blockage. Held-out common-field relative residual improves from `0.292` to `0.11794`, while scalar interpolation error remains `4.17e-4`. The negative coefficient is a descriptive projection and is strongly anti-correlated with the unchanged positive unit direction. The favorable theoretical total-field optimum remains `g_opt=+6.14794`; conventions and strip implementation are identical. The earlier modal reason for FAIL is withdrawn; the sign contradiction alone rejects the proposed actual-actuation cancellation mechanism. The confinement mechanism remains **FAIL** specifically because measured finite-Re control drives the opposite potential direction; finite-image modal representation is BOUNDED and numerically consistent, and Stage-3 closure remains BOUNDED.
|
||||||
|
|
||||||
|
## 19. Finite free-vortex feasibility model
|
||||||
|
|
||||||
|
**19.1 — Scope and state [modeling assumption].** In the unbounded plane only, let the fixed three-cylinder base solution `u_base` be the existing MFS field with its prescribed geometric-CCW body circulations `Gamma_j`. Add a finite set of free point vortices at exterior positions `X_i` with fixed geometric-CCW strengths `kappa_i`. This first feasibility model excludes free-slip walls, vortex shedding or production, viscosity and diffusion, finite vortex cores, and Mow's global dispersion relation; it is an instantaneous inviscid transport model, not a Karman-street generation or stability theory.
|
||||||
|
|
||||||
|
**19.2 — Instantaneous impermeability response [proved mathematical result].** The physical incident velocity of the free vortices is
|
||||||
|
|
||||||
|
`u_free(x;X,kappa)=sum_i kappa_i K(x-X_i)`, `K(dx,dy)=(-dy,dx)/(2 pi (dx^2+dy^2))`,
|
||||||
|
|
||||||
|
with the same positive-CCW sign as `singular_velocity(..., vortex=True)`. Because `u_free dot n` is generally nonzero on a cylinder, static superposition with `u_base` violates impermeability. At every vortex configuration solve a zero-period MFS Neumann response `u_resp`: its source strengths have zero sum on each body, its far field decays, it contains no body-vortex terms, and its boundary data satisfy `u_resp dot n=-u_free dot n`. Therefore `(u_base+u_free+u_resp) dot n=0` up to MFS discretization error, while the prescribed base body circulations remain unchanged.
|
||||||
|
|
||||||
|
**19.3 — Finite-vortex ODE [proved mathematical result].** Omitting only the singular physical self term, each vortex is transported by the regular velocity
|
||||||
|
|
||||||
|
`dX_i/dt = u_base(X_i) + sum_(m != i) kappa_m K(X_i-X_m) + u_resp(X_i;X,kappa)`.
|
||||||
|
|
||||||
|
The response is generated by the complete incident set and is retained at `X_i`; in particular, a vortex moves under its own body-induced regular image/response field. Mutual induction retains the geometric-CCW signs above. Exterior, distinct positions are required so neither body collision nor vortex-vortex collision reaches a singular state.
|
||||||
|
|
||||||
|
**19.4 — Hamiltonian status [modeling assumption].** Exact point-vortex motion around fixed bodies can be written using the domain Green function/Kirchhoff--Routh Hamiltonian, including its regular Robin part. The present collocated, zero-period MFS response is only a noncanonical numerical Hamiltonian approximation: it is not asserted to be an exact discrete Hamiltonian system, and MFS truncation and least-squares error may cause Hamiltonian drift. This model therefore tests instantaneous kinematic feasibility, not long-time conservative fidelity.
|
||||||
|
|
||||||
|
**19.5 — Transmission observables [modeling assumption].** For a prescribed incoming finite staggered train, transparent passage can be assessed by matching vortices upstream and downstream and reporting nondimensional position/phase displacement, transverse displacement, spacing distortion, and velocity/strength preservation, together with minimum body clearance and accumulated impermeability error. These are conceptual diagnostics for later trajectory integrations; no acceptance threshold, campaign, or claim that the train passes unchanged is introduced here.
|
||||||
|
|
||||||
|
**19.6 — Numerical qualification [numerical observation].** Qualification is limited to dense shifted-angle impermeability, zero bodywise source monopoles/no added body circulation, far-field decay of the response, correct no-body mutual induction, singular-self exclusion, finite RHS values, and fail-fast invalid/collision controls. Passing those checks establishes a narrow CPU feasibility prototype only.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## 20. Stage 0: primary-paper equation maps and exact CPU oracles
|
||||||
|
|
||||||
|
**20.1 — Vasconcelos--Moura--Schakel map [primary-literature-supported result].** Vasconcelos et al. (2012), DOI `10.1063/1.3667269`, nondimensionalize lengths by cylinder radius `a`, velocities by `U`, time by `a/U`, and define the upper wake vortex strength by `kappa=-Gamma/(2 pi U a)>0`; the upper vortex is therefore clockwise in this paper's convention and the lower one is its opposite-sign reflection. On the symmetric invariant subspace, their (7)--(8) give `H=y(1-1/r^2)-(kappa/2)log[y(r^2-1)/sqrt((r^2-1)^2+4y^2)]`, `xdot=H_y`, `ydot=-H_x`. Their Föppl equations (9)--(10) give `r0^2-1=2r0 y0` and `kappa=(r0^2+1)(r0^2-1)^2/r0^5`. At `r0=2`, the exact downstream point is `(sqrt(55)/4,3/4)` and `kappa=45/32`. Their critical level (26), `Hc=(kappa/2)[1-log(kappa/2)]`, belongs to the nilpotent saddle at infinity `(x,y)=(+/-infinity,kappa/2)`; it is **not** the finite Föppl equilibrium's Hamiltonian value.
|
||||||
|
|
||||||
|
**20.2 — Kármán-street map [primary-literature-supported result].** Mowlavi, Arratia & Gallaire (2016), DOI `10.1017/jfm.2016.195`, use streamwise spacing `a`, row separation `h`, circulation magnitude `Gamma`, lengths scaled by `a`, and time `2 pi a^2/Gamma`. For the physical unconfined street (`n=0` in their confined array), lower vortices are `(ma,-h/2)` with `+Gamma` in the paper and upper vortices are `(ma+a/2,+h/2)` with `-Gamma`. With this project's geometric-CCW kernel, that paper assignment translates in negative x (consistent with the paper's negative unconfined values of `v0`). To expose the requested positive-advection baseline without hiding a sign change, `staggered_karman_train` reverses both rows: lower `-Gamma`, upper `+Gamma`. For this explicitly reversed convention the common positive x self-advection is `U_self=Gamma/(2a)tanh(pi h/a)`. The exact unconfined temporal-neutral aspect is `p0=h/a=asinh(1)/pi`. The finite constructor is only a symmetric truncation oracle; it converges algebraically to the infinite sum. The confined dispersion coefficients (2.7), dispersion relation (2.11), unconfined coefficients (4.4), and Briggs--Bers continuation are not implemented: no temporal growth curve or convective threshold is claimed.
|
||||||
|
|
||||||
|
**20.3 — Green/Robin/Routh map [primary-literature-supported result].** Crowdy & Marshall (2005), DOI `10.1063/1.1900583`, formulate one-vortex motion around multiple circular islands through the domain Green function, its regular Robin self-part, and the Kirchhoff--Routh Hamiltonian, in the special case of zero circulation around every island. Any imposed irrotational background contributes its streamfunction term `H_B`; the vortex moves with the regular velocity after its singular self field is removed (Routh rule). `exact_circle_vortex_response` is the independently derivable one-circle reduction: a physical vortex is accompanied by its opposite image and a same-sign central vortex so the island circulation is zero, and both image terms contribute to regular self velocity. `MFSBodyResponse.response` is compared to this exact reduction; it is not promoted to Crowdy--Marshall's exact multiply connected prime-function formula.
|
||||||
|
|
||||||
|
**20.4 — Routh/body coupling scope [primary-literature-supported result].** Streitlien's 1994 thesis record, DOI `10.1575/1912/5563`, treats incoming vortex streets interacting with a Joukowski foil using two-dimensional potential flow, complex-variable force/moment formulas, and nonlinear foil--vortex coupling. It supports retaining each incoming vortex's regular body-induced response and coupling the street to the body boundary; it does not make a prescribed finite train an equilibrium, prove transparent passage, or transfer foil force formulas to three fixed circles.
|
||||||
|
|
||||||
|
**20.5 — Steady evidence map and sign non-equivalence [primary-literature-supported result].** Cornejo Maceda et al. (2021), DOI `10.1017/jfm.2021.301`, order controls `(front,bottom,top)` as `(b1,b2,b3)`, define positive command as counter-clockwise wall rotation, and call `b2=-b3<0` base bleed while `b2=-b3>0` is boat tailing/Coanda forcing. Chan et al. (2011), DOI `10.1017/jfm.2011.134`, define their side-by-side pair by figure 2's **doublet-like** and **reverse doublet-like** rotation drawings and associate the former at high speed with a virtual elliptical body; those names encode that paper's pair geometry and arrows, not this project's rear-pair labels. Rodrigues et al. (2026), DOI `10.1016/j.expthermflusci.2026.111798`, now has a **partially lifted** figure/sign map in `LITERATURE.md`: Eqs. (1)–(2) and Fig. 3 authorize wall-sense comparison, with project accepted `[0,+Omega,-Omega]` mapping to the literature **base-bleeding / gap-jet** family and literature **boat-tailing** mapping to project reverse. Quantitative Rod26 thresholds, forces, and `Re=9100` coordinates remain held. None of `base bleed`, `boat tailing`, `doublet-like`, `reverse doublet-like`, or this project's `rear_opposite` may be equated as identical mechanisms across different body orderings, Reynolds numbers, wall laws, or metrics; wall-sense family labels are comparative only.
|
||||||
|
|
||||||
|
**20.6 — Stage-0 implementation boundary [modeling assumption].** Public CPU helpers are limited to the Vas Hamiltonian/RHS/equilibrium/critical level, one-circle exact zero-period response and Routh velocity, and finite/infinite Kármán baselines. Tests cover the exact Vas residual and critical-level distinction, finite-difference Hamiltonian gradient, short high-accuracy Hamiltonian conservation, exact-circle MFS response convergence, finite-window Kármán convergence, and the `sinh(pi p0)=1` identity. No CFD, wall images for the Kármán street, finite-train equilibrium, temporal growth oracle, convective threshold, multi-island prime function, or viscous branch transfer is implemented.
|
||||||
|
|
||||||
|
## 21. Steady-contract mechanism comparison derivation
|
||||||
|
|
||||||
|
1. **Frozen question and failure rule [modeling assumption].** Under one D20, uniform-inlet/free-slip, `Re_D=50` CFD setting, compare four preregistered branch outcomes: qualified no-body `q_in`, intentionally unsteady stationary pinball, qualified accepted `plus-minus` at `s=3.55`, and the historical reverse `minus-plus` stability candidate at `s=5.2`. The analyzer must fail if reverse `s=5.2` is absent or unqualified; in that event the stable-reverse claim is rejected. The settling threshold remains `1e-4`.
|
||||||
|
|
||||||
|
2. **Role lock [modeling assumption].** Reverse production permits only `s={3.55,5.2}`. Reverse `s=3.55` is the same-amplitude negative control and is reported separately when present; it is not required to qualify and cannot support the stable-reverse claim. Reverse `s=5.2` is preregistered as the historical stability candidate because the earlier stable, high-deficit wrong-direction observation came from old `s=5.2` runs. Accepted production support remains `s={3.45,3.55,3.65}` and the frozen mechanism role is accepted `s=3.55`.
|
||||||
|
|
||||||
|
3. **Comparison boundary [modeling assumption].** All roles retain exact shared grid, physics, boundary, runtime, sampling, settling, coordinate, and topology conventions; only the role parameter `s` may differ. Accepted `s=3.55` and reverse `s=5.2` are therefore not exact action reversals at equal amplitude. This comparison separates branch outcomes and is **not an equal-amplitude causal effect**. Source provenance is reported per role because the follow-up artifact is acquired after this preregistration, while exact CFD settings remain mandatory.
|
||||||
|
|
||||||
|
4. **Output contract [modeling assumption].** The analyzer computes direct profile differences against `q_in` at `x/D={2,4,6,10}`, pairwise two-component field L2 on common-fluid masks, and controlled-case force-tail mean/std. If reverse `s=3.55` exists, the same descriptive outputs and its qualification/settling record are included under a separate negative-control report. No threshold relaxation, stability inference from an unqualified endpoint, or circulation/separation/causal mechanism metric is permitted.
|
||||||
|
|
||||||
|
|
||||||
|
## 22. Steady residual S2 charter
|
||||||
|
|
||||||
|
**22.1 — Sole S2 question [modeling assumption].** Under the frozen D20, uniform-inlet/free-slip, `Re_D=50` geometry, can a common rear-opposite affine outer-potential description account for both the qualified accepted low-deficit endpoint at `s=3.55` and the qualified stable reverse high-deficit endpoint at `s=5.2`; and, after subtracting each endpoint's corrected total potential model (including zero-circulation blockage), which endpoint-feasible viscous residual observable distinguishes those outcomes? Fits are descriptive per endpoint because `s` differs; fitted amplitudes are not equal-intervention effects. A final endpoint cannot establish temporal causality.
|
||||||
|
|
||||||
|
**22.2 — Frozen fields, signs, and outer fit [modeling assumption].** Normalize coordinates about the front center by `D` and velocity by `U_inf`. For accepted and stable reverse separately, fit `q_case-q_in=(q_0-U_inf e_x)+g(q_1-q_0)` by uniform-weight vector WLS on the Stage-3 strip mask `x/D in [-2,10]`, `|y/D|<=6`, every second D20 cell, common solver-fluid cells, distance `>=0.75D` from each cylinder, and at least `1D` from strip walls. The basis is the unchanged canonical strip rear-opposite unit-L2 circulation field with `H/D=15`, `R/D=0.5`, 32 boundary points, source ratio `0.65`, and 40 image layers. Report `g`, per-rear geometric circulation, cosine sign, weighted residual, and geometric-CCW contour circulation at `r/D={0.60,0.65,0.70}`. The residual total field is `q_nu=q_case-[q_in+(q_0-U_inf e_x)+g(q_1-q_0)]`. Opposite measured and favorable theoretical signs remain a contradiction; no canceled-circulation mechanism may be revived.
|
||||||
|
|
||||||
|
**22.3 — Frozen residual regions and quadrature [modeling assumption].** Every area metric uses the common q-in/case solver-fluid mask and physical composite-trapezoid cell weights, with cylinder/nonfluid cells excluded exactly by that mask. Regions in front-centered coordinates are: `near_gap: -0.5<=x/D<1.8, |y/D|<=1.5`; `immediate_wake: 1.8<=x/D<4, |y/D|<=3`; `recovery: 4<=x/D<=10, |y/D|<=3`; and the observer line at the nearest `x/D=10` column with the two outermost rows excluded. Report explicit quadrature `L2=[integral |q_nu|^2 dA]^(1/2)` (never RMS), normalized `L2/[integral |q_case-q_in|^2 dA]^(1/2)`, retained area/line length, and observer-line `L2=[integral |q_nu|^2dy]^(1/2)` with the analogous normalization. Empty masks, nonfinite fields, or zero normalizers fail fast.
|
||||||
|
|
||||||
|
**22.4 — Frozen viscous observables [modeling assumption].** Compute `omega_z=partial_x u_y-partial_y u_x` only where conservative centered differences have fluid neighbors on both sides; all other cells are invalid. Report endpoint-field gap fluxes across the shortest rear--rear, front--upper-rear, and front--lower-rear open segments, requiring every bilinear stencil to be common fluid. In `near_gap` and `immediate_wake`, report absolute-vorticity-weighted transverse centroid and width using physical area weights. Define recirculation by `u_x/U_inf<0` in the frozen symmetric window `1.8<=x/D<=10`, `|y/D|<=1.5`: report weighted area and streamwise extent from occupied columns (zero if absent). At nearest stations `x/D={2,4,6,10}`, report momentum-deficit proxy `integral max(0,1-u_x/U_inf)dy`, positive-deficit line measure, and signed deficit integral over common fluid rows after excluding the two outermost rows. Force observables are bodywise and total `Fx,Fy` means and population tail standard deviations over the final 10 samples; they are not pressure drag.
|
||||||
|
|
||||||
|
**22.5 — Spatial-sequence and classification rule [modeling assumption].** Endpoint fields can support only ordered spatial coexistence/association. A mechanism-association PASS requires at least one preregistered observable to distinguish accepted from stable reverse in one direction at two or more of `x/D={2,4,6,10}`, with a nonzero strict difference at each station and no direction reversal across those stations; the reported candidate and all station values must be named. If no observable meets that criterion but all required data and metrics are valid, S2 is BOUNDED. A required-mask/model/qualification contradiction or an observable direction reversal falsely presented as consistent is FAIL. Reverse `s=3.55` is unsteady negative context only and cannot serve as a mechanism endpoint.
|
||||||
|
|
||||||
|
**22.6 — Claim and literature limits [modeling assumption].** Rod26 (partial sign lift), Cornejo Maceda et al. (2021), and Chan et al. (2011) are qualitative literature frameworks for rotating-cylinder branches, gap transport, and wake organization. Wall-sense comparison may use the Stage-1 master map (accepted ↔ base-bleed family; boat-tail ↔ reverse), but Reynolds numbers, geometry, confinement, force thresholds, and drag optima do not transfer quantitatively to this D20 three-cylinder endpoint comparison. S2 may conclude spatial association, not temporal causality, pressure-drag decomposition, equal-amplitude intervention response, a literature proof that “boat-tailing explains the cloak,” or a universal circulation law.
|
||||||
|
|
||||||
|
**22.7 — Immutable output and verdict contract [modeling assumption].** The analyzer writes a no-clobber external directory containing strict `analysis.json` and only essential NPZ arrays, records SHA-256 hashes of every endpoint payload and the analyzed source files, and records the exact reproduction command. Required controls are synthetic quadrature/regions, conservative masked vorticity, gap flux, recirculation, wake moments, sign/parity, empty/invalid mask failure, and runner no-clobber/role contract. The final verdict is exactly PASS, BOUNDED, or FAIL under §22.5.
|
||||||
|
|
||||||
|
|
||||||
|
## 23. K1 finite unbounded Kármán baseline qualification
|
||||||
|
|
||||||
|
1. **Sole K1 question [modeling assumption].** Does the existing positive-advection, unbounded point-vortex Kármán street convention reproduce the infinite-street translation at the center and a short finite-horizon central propagation oracle under independent train-window and RK4-step refinement? This is a mathematical qualification baseline only. It is not CFD parameter identification: no trustworthy phase-resolved `q_in` extraction is available, so `a`, `h`, and `Gamma` are dimensionless Mowlavi-literature baseline quantities rather than fitted pinball values.
|
||||||
|
|
||||||
|
2. **Signs, rows, stagger, and scaling [proved mathematical result].** Coordinates are x-right/y-up and positive circulation is geometric CCW, with kernel `K(dx,dy)=(-dy,dx)/(2 pi r^2)`. The implemented positive-x street reverses Mowlavi et al.'s physical signs explicitly: lower row `(m a,-h/2)` has `-Gamma`; upper row `((m+1/2)a,+h/2)` has `+Gamma`. Set `a=Gamma=1`, `p=h/a`, velocity scale `Gamma/(2 pi a)`, and time scale `2 pi a^2/Gamma`; the code uses the equivalent dimensional kernel and reports physical code time. The exact common speed is `U_self=Gamma/(2a)tanh(pi h/a)`, and the nominal aspect is `p0=asinh(1)/pi`.
|
||||||
|
|
||||||
|
3. **Finite train and frozen observer [modeling assumption].** For half-window `M`, retain lower indices `m=-M,...,M` and upper indices `m=-M-1,...,M`, exactly as `staggered_karman_train`. Use `M={12,24,48}`. Freeze the observer by initial labels, not moving coordinates: both rows with initial `|x|<=2.5a`. End vortices remain in the ODE but never enter central acceptance metrics. The finite open train is not an equilibrium; only convergence of this fixed central observer as its separation from the ends grows may be claimed.
|
||||||
|
|
||||||
|
4. **Evolution and collision scope [modeling assumption].** Integrate `dX_i/dt=sum_(j!=i) Gamma_j K(X_i-X_j)` by deterministic classical RK4 over `0<=t<=0.25 a^2/Gamma`, with fixed steps `dt={0.01,0.005,0.0025}` (the final step is shortened only to land exactly on the horizon). Reject nonfinite states or pair distance `<=1e-6 a`. No cylinders, pinball, walls, images, viscosity, shedding, CFD, GPU, or confinement are present.
|
||||||
|
|
||||||
|
5. **Exact finite-plane Hamiltonian convention [proved mathematical result].** With fixed reference length exactly `a`, use
|
||||||
|
`H=-sum_(i<j) Gamma_i Gamma_j/(2 pi) log(r_ij/a)`.
|
||||||
|
Then `Gamma_i xdot_i=partial H/partial y_i` and `Gamma_i ydot_i=-partial H/partial x_i`, exactly matching the geometric-CCW RHS. No arbitrary additive constant is inserted. Report drift as `max_t |H(t)-H(0)|/max(|H(0)|,sum_(i<j)|Gamma_i Gamma_j log(r_ij(0)/a)|/(2 pi),eps)`, so a zero or cancellation-prone initial Hamiltonian cannot create a singular normalization.
|
||||||
|
|
||||||
|
6. **Frozen metrics [modeling assumption].** At `t=0`, report observer mean RHS, RMS nonuniformity about that mean, and error from `(U_self,0)`. At the horizon, fit one global translation vector by the mean observer displacement and report observer vortex-position L2 residual, rowwise mean-spacing drift, `a/h` drift, stagger/phase drift (upper-minus-lower row-center x offset relative to its initial value after removing the same global translation), row-center transverse shift, minimum all-pair distance, and normalized Hamiltonian drift. The propagation refinement oracle is the observer position-L2 difference between aligned solutions at common labels; translation is refit for each comparison. A perturbed case moves the initially central lower vortex upward by `0.01h`; its aligned central deviation from the nominal solution and amplification relative to the initial aligned deviation are reported solely as **finite-train numerical sensitivity**, not Mowlavi temporal or convective growth.
|
||||||
|
|
||||||
|
7. **Frozen PASS/FAIL rule [modeling assumption].** The mathematical K1 core passes only if: (i) `sinh(pi p0)=1` within `5e-15`; (ii) center translation-speed error and observer RHS nonuniformity decrease at both window refinements, with finest values `<3e-3 U_self` and `<1e-2 U_self`; (iii) nominal short-horizon window-aligned observer L2 differences decrease from `12->24` to `24->48`, with the finest `<1e-2 a`; (iv) at `M=48`, successive time-step aligned differences decrease or both reach a `8 eps a` roundoff plateau, and the `0.005->0.0025` value is `<1e-7 a`; (v) finest normalized Hamiltonian drift is `<1e-8`, minimum pair distance remains `>0.2h`, and every metric is finite; and (vi) rerunning the complete analysis yields byte-identical canonical config/result payloads. Failure of any item makes K1 **FAIL** and stops the K lane.
|
||||||
|
|
||||||
|
8. **Classification and K2 gate [modeling assumption].** Even when the mathematical core passes, overall K1 is **BOUNDED**, not PASS, because the attached plan requires at least a growth/impulse oracle while Mowlavi confined dispersion/growth and trustworthy phase-resolved extraction remain publication-held. The implemented perturbation observable is explicitly a finite-open-train sensitivity and cannot substitute for Mowlavi's periodic dispersion relation. Therefore K2 is **NO-GO** unless a later, separately frozen source-verified Mowlavi growth/impulse oracle is reproduced. No Mowlavi dispersion coefficient, temporal/convective stability, or confinement conclusion may be invented or inferred here.
|
||||||
|
|
||||||
|
9. **Immutable output [modeling assumption].** The CPU runner publishes `/tmp/karman-baseline-20260806` atomically without replacement, containing strict canonical `config.json`, `result.json`, and `manifest.json` with SHA-256 hashes of both payloads and the four allowed edited source files. Arrays are omitted because all acceptance data are scalar or short metric records. Existing output is an error, never overwritten.
|
||||||
|
|
||||||
|
|
||||||
|
## 24. Inviscid→viscous theory boundary (Stage-1 continuous closure)
|
||||||
|
|
||||||
|
This section continuously closes the Stage-1 inviscid contract against the finite-Re accepted branch. It does not reopen §§3–6 proofs, does not revive the canceled-blockage identification, and does not import Kármán/Föppl into the steady manuscript claim path.
|
||||||
|
|
||||||
|
**24.1 — Impermeability and independent circulations [proved mathematical result; S2–S8].** Subject to §§3–6: the irrotational outer problem is a Neumann impermeability BVP whose body circulations are independent periods; centered circular rotation drops out of normal data (§4.2); the one-cylinder family shows wall rotation does not select `Gamma` under Euler slip (§5); and full rotating no-slip is generically overdetermined once periods are free only finitely (§6). Crowdy/Glass literature support for impermeable boundaries and independent perfect-fluid circulations remains as in §7 and S4/S9. Consequently any map from wall speed to outer circulation is a viscous-interface/empirical closure (S10), not an Euler boundary condition.
|
||||||
|
|
||||||
|
**24.2 — Natural cancellation direction of prescribed `(0,+Gamma,-Gamma)` [proved mathematical result + numerical observation].** With body order `(front,rear_y_plus,rear_y_minus)`, the geometric-CCW rear-opposite assignment has zero net circulation, reflection-symmetric `(u_x` even, `u_y` odd) parity, and pair dipole coefficient `-Gamma b/pi` before body-source readjustment (§§12.3–12.6). Among the three equal-norm bases of §13.2, the strip smooth full-height profile objective uniquely favors that direction with theoretical optimum `g_opt=+6.147944` at 40 image layers (§§17.6, 18.5). That sign is therefore the natural *prescribed-circulation* cancellation direction for the outer potential problem; it is not a prediction that Navier–Stokes wall rotation must produce the same geometric periods.
|
||||||
|
|
||||||
|
**24.3 — Accepted CFD effective sign contradiction [numerical observation; failed identification].** Accepted numeric wall action `[0,+Omega,-Omega]` at held-out `s=3.55` produces geometric contour circulation `(0,-,+)` and corrected descriptive WLS amplitude `g=-12.200554` on the same unit-L2 rear-opposite basis (§§16.12, 18.5). The theoretical favorable sign `g_opt=+6.147944` and the measured descriptive sign `g=-12.200554` therefore contradict under identical geometry, basis, and convention. The rear-opposite dipole algebra remains true; what fails is identifying prescribed potential-circulation cancellation as the accepted CFD mechanism.
|
||||||
|
|
||||||
|
**24.4 — Mathematical entry of viscous wall vorticity, separation, and gap transport [primary-literature-supported result + modeling assumption].** Euler slip and Kelvin transport supply no wall-vorticity flux that can select `Gamma` from `Omega` (§§7.1–7.2). The viscous rotating-body problem instead imposes full no-slip and couples wall velocity to vorticity generation/diffusion (Ueda et al. 2003; §7.3). Restricted attached-layer closures (Glauert/Moore) do not control this moderate-Re, possibly separated three-body flow (§§7.4, 16.8). Therefore the continuum location where the Stage-1 outer contract ends—and where wall vorticity, separation/reattachment, and gap momentum transport must enter—is exactly the viscous no-slip interface and its near-wall/gap vorticity field, not the Neumann period problem. Downstream deficit then responds to that viscous near-field organization rather than to an a-priori prescribed outer `Gamma`.
|
||||||
|
|
||||||
|
**24.5 — Sole surviving mechanism chain [modeling assumption / hypothesis].** After the sign contradiction, the sole modeling chain retained for the steady accepted/reverse comparison is the ordered spatial association already gated in §22:
|
||||||
|
|
||||||
|
`wall rotation direction → gap / outer shear → recirculation / integrated force → downstream momentum deficit`.
|
||||||
|
|
||||||
|
This is an endpoint coexistence hypothesis, not a proved evolution law. It does not assert equal-amplitude causality across different `s`, pressure-drag decomposition, or a universal `Gamma(Omega)` formula.
|
||||||
|
|
||||||
|
**24.6 — Claim labels and falsification [modeling assumption].** Labels for this boundary:
|
||||||
|
|
||||||
|
| Claim | Label |
|
||||||
|
|---|---|
|
||||||
|
| §§3–6 impermeability, independent periods, no-slip overdetermination | proved mathematical result |
|
||||||
|
| Crowdy/Glass/Ueda/Glauert interface distinction | primary-literature-supported result |
|
||||||
|
| `g_opt=+6.147944` vs accepted `g=-12.200554` and geometric `(0,-,+)` | numerical observation |
|
||||||
|
| Identification of prescribed `(0,+Gamma,-Gamma)` as the accepted CFD cancellation mechanism | failed/withdrawn |
|
||||||
|
| Chain wall → gap/shear → recirculation/force → downstream deficit | modeling assumption / hypothesis |
|
||||||
|
| Any `Gamma_eff(s)` as Euler wall law | modeling assumption (forbidden as proved) |
|
||||||
|
|
||||||
|
Falsification or claim-limiting conditions: (i) a derived Euler rule selecting `Gamma` from centered `Omega` under the Stage-1 hypotheses; (ii) accepted geometric/WLS sign agreeing with `g_opt>0` under the frozen basis without redefinition; (iii) an equal-amplitude controlled intervention showing the residual chain reverses while the outer potential identification holds; (iv) promoting aggregate scalars or absent endpoint arrays to field mechanism evidence; (v) treating dynamical stability as proved for the static prescribed-circulation BVP (§14).
|
||||||
|
|
||||||
|
**24.7 — Kármán/Föppl quarantine [modeling assumption].** Vasconcelos–Föppl oracles (§20.1), Kármán-street constructors/baselines (§§20.2, 23), and related free-vortex feasibility code (§19) remain available as mathematical CPU oracles only. They are **frozen quarantine** relative to the steady manuscript claim path: no Kármán phase, street transmission, Föppl equilibrium stability, or PPO-feedback conclusion is part of the Stage-1→S2 steady cloak identification. K1 FAIL / K2 NO-GO (§23) reinforces the pause; those results do not amend §§24.1–24.5.
|
||||||
|
|
||||||
|
|
||||||
|
## 25. Historical mechanism-completion charter (steady only)
|
||||||
|
|
||||||
|
**25.1 — Sole scientific proposition [modeling assumption].** In the canonical uniform-inlet/free-slip `Re_D=50` pinball, accepted numeric rear-symmetric action `[0,+Omega,-Omega]` produces a qualified steady low-deficit state relative to uniform no-body `q_in` at `x/D=10`, whereas the potential-flow-favored opposite circulation direction corresponds to a stable high-deficit reverse branch rather than a cloak. The surviving explanatory hypothesis is viscous wall actuation that reorganizes gap transport and outer shear layers, then recirculation/force, then downstream deficit. Prescribed-circulation dipole cancellation is a proved outer-field property and a falsified CFD mechanism.
|
||||||
|
|
||||||
|
**25.2 — Evidence governance [modeling assumption].** Physical payloads reside only under `/home/frank14f/optane/DynamisLab/steady_pinball_theory/` with top-level directories `authoritative/` and `archive/`. The package exposes exactly one entry, the symlink `evidence/`. `/tmp` is the sole scratch/discovery area. Promotion is atomic, no-clobber, one canonical result per scientific question, with `PROMOTION.json` hashes. Failed diagnostics are deleted unless uniquely valuable negative evidence. Kármán/Föppl implementations (§§19,23 and related APIs) are quarantine: they may remain for historical CPU tests but do not enter the steady manuscript claim path. CCD is excluded.
|
||||||
|
|
||||||
|
**25.3 — Metric hierarchy [modeling assumption].** Primary cloak metric: full two-component `E_inf_vector(q_ctl,q_in)` at front-centered `x/D=10`. Secondary: explicit integral profile `L2`. Diagnostic only: stations `2,4,6`, field residuals, force tails, gap fluxes, vorticity widths, recirculation. `q_blk` never ranks.
|
||||||
|
|
||||||
|
**25.4 — Inviscid→viscous entry point [proved mathematical result + numerical observation].** Impermeability and independent circulations are Euler data (§§3–6). The rear-opposite basis `(0,+Gamma,-Gamma)` has zero net circulation and an affine dipole (§12). Strip cancellation selects favorable `g_opt=+6.147944` [numerical observation]. Accepted CFD instead yields geometric `(0,-,+)` and descriptive `g=-12.200554` [numerical observation]. Therefore wall-speed → outer-circulation cancellation cannot be the measured cloak mechanism. Viscous no-slip generates wall vorticity, modifies separation and gap jets, and must enter before any finite-Re cloak claim (§§5–7,22,24).
|
||||||
|
|
||||||
|
**25.5 — Surviving mechanism chain [modeling assumption].** Pre-registered ordered association only:
|
||||||
|
`wall direction → gap/outer shear → recirculation/force → downstream deficit`.
|
||||||
|
PASS requires at least one local, one near-wake, and one downstream discriminator coherent across stations without direction reversal. Temporal causality requires a separately frozen transient intervention. Incomplete control-volume ledgers must list unavailable pressure/viscous/torque terms and must not be called closed balances.
|
||||||
|
|
||||||
|
**25.6 — Falsification [modeling assumption].** The viscous association fails if accepted and reverse are not distinguished by the pre-registered chain, if discriminators reverse across stations, if D20/D30 disagree beyond claim-tolerable uncertainty on the primary profile, or if a new equal-setting intervention shows local response after, not before, the observer-plane change. Cross-project SR/DRL salience remains contextual and cannot rescue a failed local mechanism.
|
||||||
|
|
||||||
|
**25.7 — Mechanism-completion gate status [numerical observation; superseded].** Stage-2 credibility remains a numerical-observation **PASS** and Stage-3 remains a spatial-association **PASS** only; causality is **NOT_ESTABLISHED** and prescribed-circulation cancellation remains **FAIL**. The promoted semi-analytic artifact subsequently failed audit (§26.0), so no composite, validation, or manuscript-completion PASS survives. Section 27 supersedes the mechanism-completion path with an NS-first preregistration.
|
||||||
|
|
||||||
|
|
||||||
|
## 26. Withdrawn composite semi-analytic charter
|
||||||
|
|
||||||
|
**26.0 — Audit disposition [failed/withdrawn].** This charter records a historical preregistration, not an active mechanism route. The promoted artifact double-subtracted the reference in its profile metric; its valid internal comparison is approximately `70.29%` improvement for `MFS0+Gamma` versus `70.51%` for the full fit. `q_VI` is independent of `omega`/branch, `q_wake` is inactive, and the speed/direction gates refit or read held-out CFD rather than independently predicting it. Every composite held-out PASS is withdrawn. The immutable artifact is bounded descriptive historical evidence at most and is non-authoritative for active claims.
|
||||||
|
|
||||||
|
**26.1 — Historical target [modeling assumption; withdrawn].** Explain accepted numeric `[0,+Omega,-Omega]` (project base-bleed / gap-jet wall sense) with a low-dimensional composite model
|
||||||
|
|
||||||
|
\[
|
||||||
|
q_{SA}=q_{MFS,0}+q_{VI}+q_{gap}+q_{wake},
|
||||||
|
|
||||||
|
\]
|
||||||
|
|
||||||
|
optionally compared against descriptive `q_{Gamma}` only. Pure prescribed `(0,+Gamma,-Gamma)` cancellation remains a **failed** outer counterexample (§§17–18, 24.3), not the measured mechanism or a stopping condition.
|
||||||
|
|
||||||
|
**26.2 — Component contracts [modeling assumption].**
|
||||||
|
- `q_MFS,0`: zero-period `solve_mfs` strip field; impermeable geometric backbone only.
|
||||||
|
- `q_VI`: zero-net-per-body Neumann transpiration from moving-wall displacement proxy `v_n=d(U_e delta^*)/ds`; accepted/reverse signs enter through the wall oracle and branch, never through empirical circulation sign flips.
|
||||||
|
- `q_gap`: finite-gap Couette–Poiseuille / localized jet ansatz at `h/D=0.5`; reports `Q_g`, `J_g`, power scale; **not** a lubrication claim.
|
||||||
|
- `q_wake`: convected Gaussian deficit with parameters fit only from `x/D<=2`, wall, or gap regions; predicts `x/D={4,6,10}`.
|
||||||
|
- `q_Gamma`: descriptive `strip_basis_affine` rear-opposite circulation; optional baseline only.
|
||||||
|
|
||||||
|
**26.3 — Historical fitting and held-out gates [modeling assumption; withdrawn].** These preregistered gates were not validly met and cannot support PASS. Three baselines: `MFS0`, `MFS0+Gamma`, full composite. Spatial held-out: fit near field only, score `x/D={4,6,10}` profiles and `E_inf_vector`. Speed held-out: train `s={3.45,3.65}`, predict `s=3.55`. Direction held-out: same equations on stable reverse `s=5.2` must yield higher deficit ranking and opposite gap-flux sign. **PASS** requires downstream ranking, gap sign, and significant held-out improvement over both potential baselines; else **BOUNDED** or **FAIL** by component. CFD fit alone is insufficient for mechanism claims.
|
||||||
|
|
||||||
|
**26.4 — Historical literature method map [primary-literature-supported result + modeling assumption; withdrawn implementation].** Viscous–inviscid transpiration and displacement-thickness closures follow Fansler–Danberg / Celik–Patel / Yiu–Giles / Soares families; gap transport follows Dormy–Moffatt / Ueda / Watson / Umemura low-Re and finite-gap ansätze; wake replenishment follows Leal–Acrivos / Steiros / Freund–Mungal base-bleed motifs; branch topology follows Chan / Maceda / Rod26 qualitative maps (`LITERATURE.md` Stage-2 ledger). All asymptotic formulas are sign/scale templates at `Re_D=50`, `h/D=0.5`.
|
||||||
|
|
||||||
|
**26.5 — Historical causality and minimal numerics [modeling assumption; withdrawn].** Reuse existing D20/D30 endpoints unless a missing quantity would change model selection. Equal-amplitude transients and V5 sanity run only if composite PASS still leaves direction causality ambiguous. Legacy steady-field ratios do not substitute for V5 sanity.
|
||||||
|
|
||||||
|
**26.6 — Historical claim boundary [modeling assumption; withdrawn].** The never-achieved **PASS** would have meant: potential flow supplies the outer geometry; accepted wall actuation is represented by locatable viscous corrections that improve held-out profiles and preserve accepted/reverse ordering. It does **not** mean potential flow equals Navier–Stokes, nor that `Gamma_eff(s)` is causal.
|
||||||
|
|
||||||
|
## 27. Historical NS-first execution charter
|
||||||
|
|
||||||
|
**27.1 — Sole proposition [modeling assumption].** Determine whether accepted rear action `[0,+Omega,-Omega]` establishes a qualified stable low-deficit Navier–Stokes base flow through measurable actuator power, boundary-vorticity production, gap momentum, separation/base-pressure/recirculation reorganization, force and wake momentum-energy recovery, and stability. Potential flow remains an impermeability reference and failed mechanism counterexample; velocity-field fitting alone cannot pass.
|
||||||
|
|
||||||
|
**27.2 — Four noninterchangeable roles [modeling assumption].** Freeze: (i) stationary; (ii) accepted `s=3.55`; (iii) equal-amplitude reverse `s=3.55`, which is an unsteady intervention/causal control; and (iv) stable reverse `s=5.2`, which is a different-amplitude steady topology control. No result from role (iv) may be represented as an equal-amplitude causal effect, and role (iii) need not qualify as a steady endpoint.
|
||||||
|
|
||||||
|
**27.3 — Conservation and stability chain [modeling assumption].** The preregistered order is
|
||||||
|
`wall torque / actuator power → boundary vorticity + gap mass/momentum → separation, base pressure, recirculation → body force + wake momentum/mechanical-energy deficit → stability → x/D=10 E_inf_vector`.
|
||||||
|
Resolved station mass, momentum, kinetic/mechanical-energy fluxes, volume viscous dissipation, wall work, and control-volume residuals must retain physical quadrature and signs. Missing pressure, viscous, boundary, or torque terms force an explicit partial-ledger/BOUNDED status; no residual may be called closed without grid/control-volume convergence. Stability is separate: settling is not global stability; perturbation return is empirical evidence, while spectral claims require a qualified base state, perturbation class, and converged linear operator.
|
||||||
|
|
||||||
|
**27.4 — Stage reflection and memory contract [modeling assumption].** Before each stage: read current Working Memory, perform targeted durable/thread recall when prior decisions matter, reread the governing plan, code contracts, and relevant Undermind source ledger, then preregister equations, dimensions, controls, PASS/BOUNDED/FAIL, and stopping rules here. After each stage: state what was answered, what remains absent, whether the result became a fit rather than a conservation/mechanism test, update `RESULTS.md`, `LITERATURE.md`, and `README.md`, and revise rather than duplicate durable memory. Code, reproducible CFD, immutable artifacts, and primary literature outrank memory summaries.
|
||||||
|
|
||||||
|
**27.5 — Stage gates and chronology [modeling assumption + audited numerical status].** Stage 0 froze these gates before later acquisition. Torque endpoints, an NS diagnostic ledger, a same-parent switch, and pulse returns were subsequently run; therefore it is stale to call all NS work unimplemented. The 2026-08-08 audit nevertheless finds the gates unmet: fluxes and dissipation do not share a control volume, pressure was unavailable and represented as zero, the single deterministic switch uses heuristic gates, and the dimensionful return norm cannot identify a basin transition. Thus energy closure, causal mechanism, branch switching, nonlinear stability, held-out reduction, and global stability remain unestablished.
|
||||||
|
|
||||||
|
|
||||||
|
## 28. 2026-08-08 NS-first evidence boundary
|
||||||
|
|
||||||
|
The Stage-1 source-only cut-link inference was falsified by the runtime manufactured oracle. Fluid-to-solid link traversal and the positive-drag force convention do not determine ownership of the public torque readback. Fresh `+omega`, `-omega`, and zero runs show same-sign `tau_read` and `omega`, odd parity defect `1.14e-5`, and zero/driven ratio `9.5e-6`; the project torque telemetry work convention is therefore `celeris_torque_channel_work_sign=actuator_on_fluid`. This does not establish Newton-pair ownership of the force channel: force and moving-wall torque readback semantics may differ, so force ownership remains unestablished. The runtime oracle overrides the earlier torque-work inference:
|
||||||
|
\[P_{act\to fluid}=\sum_{i=1}^3\Omega_i\tau_{i,read}^{act\to fluid},\qquad C_P=P_{act\to fluid}/(\tfrac12\rho_{ref} U_\infty^3D).\]
|
||||||
|
`read_force/read_torque(..., normalize=True)` means the cut-link lattice sum averaged once per accumulated LBM step. It performs no body, force/torque-coefficient, density, dynamic-pressure, or span normalization. All force, torque, and power quantities here are two-dimensional per-unit-span. The frozen project conversion is `x*=x/D`, `t*=t U_inf/D`, `nu*=nu/(U_inf D)`, `omega*=omega D/U_inf`, `Q*=Q/(U_inf D)`, `J*=J/(rho_ref U_inf^2 D)`, `C_F=F/(0.5 rho_ref U_inf^2 D)`, `C_tau=tau/(0.5 rho_ref U_inf^2 D^2)`, and `C_P=P/(0.5 rho_ref U_inf^3 D)`.
|
||||||
|
The accepted/reverse endpoints provide bounded power, signed rear-gap, kinetic-flux, and wake diagnostics. They do not form a mechanical-energy ledger: station fluxes cover x/D=2-to-10 while dissipation covers the full domain, pressure is absent despite zero-valued placeholders, and lateral/storage terms are missing. The switch is one deterministic same-parent response screened by heuristic polarity/dose/onset gates, so causal language is not licensed. Pulse norms describe finite-time field-distance histories only; their dimensionful threshold cannot determine branch switching. The promoted switch/return analyses also lack strong authenticated loader, telemetry-hash, and payload provenance. Matrix-free Arnoldi/global stability remains unavailable.
|
||||||
|
|
||||||
|
|
||||||
|
## 29. Publication-v3 formula freeze
|
||||||
|
|
||||||
|
Exactly four main formula groups govern the article: (1) Celeris wall law `(Uw,Vw)=(-omega ry,omega rx)` and the x-right/y-up sign map; (2) lattice-to-dimensionless conversion, including `C_P=sum(omega tau_read)/(0.5 rho_ref U_inf^3 D)`, `normalize=True` step averaging only, and two-dimensional per-unit-span scope; (3) same-body-enclosing-CV momentum/mechanical-energy spatial terms, with storage/time-consistent and body-adjacent gradients explicitly unavailable and no residual; (4) `E_inf_vector(q_ctl,q_in)=max_y ||q_ctl-q_in||/U_inf` on the complete common-fluid `x/D=10` profile after excluding two outer rows. Potential amplitude `g` and finite-horizon return `r,m` are secondary explanations, not additional main formula groups.
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
# Steady pinball history and lessons
|
||||||
|
|
||||||
|
## Purpose
|
||||||
|
|
||||||
|
Navigation and methodological history, not scientific evidence. Claims resolve through [evidence/INDEX.json](evidence/INDEX.json) and immutable artifacts. Plans, memory, and Undermind are **navigation not evidence**.
|
||||||
|
|
||||||
|
## Chronology
|
||||||
|
|
||||||
|
### Minimal reset
|
||||||
|
The package was reduced to verified mathematics, contracts, and a small evidence surface after prior finite-Re claims proved untrustworthy. Plan: [separated roadmap](/home/frank14f/.cursor/plans/steady-karman_separated_roadmap_6c64c1c0.plan.md). Lesson: remove unsupported authority while preserving unique negative evidence and provenance.
|
||||||
|
|
||||||
|
### Finite-Re bridge
|
||||||
|
Four D20 endpoints connected action to measured outer fields and corrected total-field WLS. Plan: [finite-Re bridge](/home/frank14f/.cursor/plans/steady-cloak_finite-re_bridge_9074c427.plan.md). The result falsified potential cancellation by sign.
|
||||||
|
|
||||||
|
### Mechanism completion
|
||||||
|
Credibility, endpoint residual diagnostics, and bounded DRL sanity were organized under one evidence root. Plan: [mechanism completion](/home/frank14f/.cursor/plans/steady-cloak_mechanism_completion_492ea630.plan.md). Endpoint association survived; causality did not.
|
||||||
|
|
||||||
|
### Semi-analytic failure
|
||||||
|
A composite model was promoted too early. Audit found metric double subtraction, nearly identical baseline/full improvement, inactive wake contribution, and non-independent gates. Plan: [semi-analytic closure](/home/frank14f/.cursor/plans/accepted-direction_semi-analytic_closure_67ca3c02.plan.md). Its immutable payload remains failed descriptive history.
|
||||||
|
|
||||||
|
### NS-first report
|
||||||
|
The project shifted from fitting to torque, momentum, vorticity, energy, intervention, and return diagnostics. Plan: [NS mechanism report](/home/frank14f/.cursor/plans/ns_cloak_mechanism_report_d2305159.plan.md). Runtime manufacture corrected torque-work interpretation; audit bounded ledger, switch, and return claims.
|
||||||
|
|
||||||
|
### Article closeout
|
||||||
|
Exact formulas, hashes, figures, and the direct v5 metric were frozen while retaining publication supersession. Plan: [article closeout](/home/frank14f/.cursor/plans/steady理论收尾_7e4c9476.plan.md).
|
||||||
|
|
||||||
|
### Current display
|
||||||
|
Authenticated endpoints and diagnostics were rendered into figures/CSVs without CFD. Plan: [display closeout](/home/frank14f/.cursor/plans/steady结果整理收尾_bd8df5c5.plan.md). Display v2 supersedes v1 as a reader view, not new evidence.
|
||||||
|
|
||||||
|
## Key mistakes
|
||||||
|
|
||||||
|
1. **Sign/body mapping:** array position or link traversal cannot replace semantic body IDs and wall-point oracles.
|
||||||
|
2. **Potential fit vs mechanism:** projection quality is descriptive; opposite `g` sign falsifies cancellation.
|
||||||
|
3. **Metric double subtraction:** never feed an already-subtracted disturbance to an absolute-profile metric.
|
||||||
|
4. **Lattice nondimensionalization:** `normalize=True` is one step average, not coefficient/density/dynamic-pressure/body/span normalization.
|
||||||
|
5. **Torque ownership/source inference:** cut-link direction did not establish API ownership. Runtime evidence establishes torque work sign only; force ownership remains open.
|
||||||
|
6. **Control-volume mismatch:** station fluxes and full-domain dissipation are not a common-CV balance.
|
||||||
|
7. **Mixed-unit transient:** dimensionful norms and arbitrary thresholds cannot identify a branch/basin.
|
||||||
|
8. **Immutable disposition:** archiving and supersession are index states, not permission to move or rewrite payloads.
|
||||||
|
|
||||||
|
## Durable rules
|
||||||
|
|
||||||
|
- Freeze order, signs, coordinates, units, metric, comparator, and claim boundary before analysis.
|
||||||
|
- Separate endpoint observation, association, intervention, conservation closure, and stability.
|
||||||
|
- Authenticate derived outputs to exact source paths/hashes; displays create no authority.
|
||||||
|
- Use no-clobber outputs and replacement links; preserve failures and superseded artifacts.
|
||||||
|
- A residual requires one control volume and all storage, boundary, pressure, viscous, and work terms.
|
||||||
|
- State realization count, horizon, amplitude matching, and uncertainty beside transient claims.
|
||||||
|
- Keep SR and CCD separate: SR supplies rear-constant context; CCD is pending for the steady total claim.
|
||||||
|
|
||||||
|
## Memory and literature navigation
|
||||||
|
|
||||||
|
Useful Nowledge topics: “Steady pinball minimal verified reset,” “finite-Re bridge,” and “mechanism-completion closure.” Useful Undermind topics: rotating-wall viscous interfaces, base-bleed/boat-tail context, and qualified-base stability. These are **navigation not evidence**; repository proofs/tests, primary-source mappings, and immutable artifacts remain authority.
|
||||||
@@ -1,18 +1,208 @@
|
|||||||
# Source-to-equation ledger
|
# Frozen source-to-claim ledger
|
||||||
|
|
||||||
This ledger separates inviscid geometry, finite-Re closure, and viscous stability.
|
`DERIVATION.md` is authoritative for project proofs. `RESULTS.md` freezes numeric observations. This ledger attributes only external equations/results actually used, with explicit transferable conclusions and prohibited transfers to the canonical D20 `Re_D=50` free-slip-channel pinball (cylinder walls rotate under Celeris no-slip; channel walls free-slip). A verification hold means the detail is deliberately not asserted; no DOI or equation mapping is inferred from secondary metadata alone.
|
||||||
|
|
||||||
- Crowdy (2006), *Analytical solutions for uniform potential flow past multiple cylinders*: exact multiply connected circular-domain uniform-flow construction. Reuse: independent reference for impermeability, circulation periods and far field; it does not impose no slip or predict separation.
|
## Stage-1 literature charter
|
||||||
- Crowdy & Marshall (2007), *Green's functions for Laplace's equation in multiply connected domains*: Schottky–Klein Green-function machinery. Reuse: exact/reference formulation for circular multiply connected domains.
|
|
||||||
- Kharlamov & Filip (2012), generalized method of images for several moving parallel cylinders. Reuse: iterative image cross-check and convergence logic.
|
|
||||||
- Chan, Jameson & Smits (2011), *Vortex suppression and drag reduction in the wake of counter-rotating cylinders*: viscous doublet-like/reverse-doublet topologies and virtual-body mechanism. Reuse: mechanism observables, not a sign oracle for this code.
|
|
||||||
- Mittal (2001), *Control of flow past bluff bodies using rotating control cylinders*: steady-wake suppression near tip-speed ratio five in a different geometry. Reuse: prior for search scale only, never a fixed optimum.
|
|
||||||
- Watson (1996), *Slow viscous flow past two rotating cylinders*: matched/Oseen rotating-cylinder precedent. Reuse: conceptual finite-Re interface; regime and geometry differ.
|
|
||||||
- Deng et al. (2018), fluidic-pinball bifurcations; Sierra et al. (2020), rotating-cylinder bifurcations. Reuse: require continuation/perturbation and do not assume unique symmetric steady state.
|
|
||||||
- Marquet, Sipp & Jacquin (2008): global sensitivity of cylinder flow. Reuse: independent viscous base-flow/eigenvalue workflow after empirical stability.
|
|
||||||
|
|
||||||
## Equations and claim limits
|
**Scope.** Deepen the viscous-mechanism half of the steady-cloak plan: why accepted rear-symmetric rotation can be both steady and low-deficit while potential-favored cancellation fails by sign. Corrected CCD is admitted only as contextual triangulation; Kármán transmission claims and CCD mechanism claims are not imported into the steady chain.
|
||||||
|
|
||||||
The outer field is `u = U_inf e_x + sum_j grad[Q_j log|z-z_j|/(2pi)] + sum_k Gamma_k e_theta/(2pi r)` with source strengths chosen to satisfy cylinder no penetration and zero net source per body. In a strip, scalar sources use equal-sign reflections and vortices opposite-sign reflections at free-slip walls. Truncation, source radius, collocation order, precision, boundary residual, wall residual and an independent exact/image formulation must be reported.
|
**Frozen project frame (not literature-derived).**
|
||||||
|
- Body order: `(front, rear_y_plus, rear_y_minus)`.
|
||||||
|
- Accepted numeric action: `[0,+Omega,-Omega]` with `Omega>0`, `s=Omega a/U_inf`.
|
||||||
|
- Celeris wall law: `(Uw,Vw)=(-omega*ry, omega*rx)` in x-right/y-up; positive `omega` matches geometric-CCW wall sense (outer cardinal of the upper rear driven upstream; gap-facing cardinals downstream).
|
||||||
|
- Primary metric: full two-component `E_inf_vector` of `q_ctl` versus uniform no-body `q_in` at `x/D=10`.
|
||||||
|
- Project telemetry contract (source-audited, not literature-derived): Celeris cut-link loads are lattice sums; `normalize=True` averages once per accumulated LBM step and applies no coefficient/density/dynamic-pressure/span normalization. The runtime manufactured oracle establishes raw torque as boundary/actuator-on-fluid for project use; actuator-to-fluid power is `sum(omega*tau_read)`. This runtime result overrides the falsified source-only cut-link ownership inference.
|
||||||
|
- Potential favorable cancellation `(0,+Gamma,-Gamma)` / `g_opt=+6.147944` **FAILS** against accepted geometric circulation `(0,-,+)` and descriptive `g=-12.200554` (`RESULTS.md`).
|
||||||
|
- Surviving modeling hypothesis: viscous gap momentum, shear/separation modification, and wake replenishment (literature boat-tail / base-bleed families as qualitative templates only).
|
||||||
|
|
||||||
`Gamma_k` is prescribed in the inviscid problem. Mapping wall rotation to an effective circulation is a finite-Re empirical interface requiring contour plateaus and held-out outer profiles; it is not `Gamma=2pi R^2 omega` by assumption. Potential flow cannot predict viscous separation, drag, base pressure, or global stability.
|
**Claim IDs used below.** Cross-link to `RESULTS.md` classes: `L-PF` potential/interface; `L-VIS` viscous rotating-wall; `L-BR` branch/topology context; `L-ST` stability-window context; `L-CLO` cloaking analogy; `H-ROD` Rod26 hold status; `X-KAR` Kármán quarantine; `X-CCD` CCD exclusion.
|
||||||
|
|
||||||
|
## Sign-convention master map
|
||||||
|
|
||||||
|
| Source | Body order | Positive rotation | Boat-tail / outer-shear family | Base-bleed / gap-jet family | Map to project accepted `[0,+Omega,-Omega]` |
|
||||||
|
|---|---|---|---|---|---|
|
||||||
|
| Project (Celeris) | `(front, rear_y_plus, rear_y_minus)` | geometric-CCW / Celeris `+omega` | `[0,-Omega,+Omega]` (outer downstream) | `[0,+Omega,-Omega]` (gap downstream) | **is** the gap-downstream family |
|
||||||
|
| Mac20 / Mac21 [= Cornejo Maceda et al. 2021] | `(front,bottom,top)=(b1,b2,b3)` | CCW | `b2=-b3>0` (bottom CCW, top CW) | `b2=-b3<0` | accepted ↔ Mac20 **base bleed** wall sense, not boat-tail |
|
||||||
|
| Rod26 | `(1 front, 2 upper, 3 lower)` | CCW-positive lab frame for mapping | `p>0`: upper CW, lower CCW (Fig. 3b,c) | `p<0`: upper CCW, lower CW (Fig. 3e,f) | accepted ↔ Rod26 **base-bleeding** wall sense (`p<0` family) |
|
||||||
|
| Cha11b | side-by-side pair | Fig. 2 arrows | doublet-like: upper CW, lower CCW (gap with freestream) | reverse doublet-like: upper CCW, lower CW (gap opposing) | names are pair-only; do not rename project branches |
|
||||||
|
|
||||||
|
Do not call project accepted “boat-tailing” by Mac20/Rod26 labels. Literature boat-tail drag optima are not project-accepted optima.
|
||||||
|
|
||||||
|
## Primary-source ledger
|
||||||
|
|
||||||
|
- **Crowdy (2006), “Analytical solutions for uniform potential flow past multiple cylinders,” *European Journal of Mechanics B/Fluids* 25, 459–470, DOI `10.1016/j.euromechflu.2005.11.005` [Cro06].** Equations (16), (18)–(20), (25)–(26), and (34) support uniform far behavior, constant-streamfunction impermeable boundaries, the prime-function construction, and a zero-period (zero round-obstacle circulation) uniform-flow potential. **Transfer:** outer impermeability reference (`L-PF`). **Prohibit:** no-slip, `Gamma(Omega)`, separation, drag, or cloak metric (`X-CCD` unused).
|
||||||
|
- **Glass, Lacave, Munnier & Sueur (2019), “Dynamics of rigid bodies in a two dimensional incompressible perfect fluid,” *Journal of Differential Equations*, DOI `10.1016/j.jde.2019.04.017`.** Supports rigid-body normal matching and treatment of vorticity/body circulations as independent perfect-fluid state data. No theorem number is imported; the narrower fixed-body uniqueness result is proved in `DERIVATION.md` (`L-PF`).
|
||||||
|
- **Ueda, Sellier, Kida & Nakanishi (2003), “On the low-Reynolds-number flow about two rotating circular cylinders,” *Journal of Fluid Mechanics* 495, 255–281, DOI `10.1017/S002211200300627X` [Ued03].** PDF-verified. Equations (2.4) and (2.7)–(2.10) prescribe far velocity plus rotating no-slip walls, relate `(u,v,w)` to streamfunction, and carry modified vorticity into the unsteady Oseen form. **Transfer:** viscous wall/vorticity distinction vs Euler impermeability (`L-VIS`). **Prohibit:** quantitative forces or closures at `Re_D=50` (analysis is `Re≪1`). **DOI hold lifted.**
|
||||||
|
- **Glauert (1957), “The flow past a rapidly rotating circular cylinder,” *Proceedings of the Royal Society A*, DOI `10.1098/rspa.1957.0157` [Gla57].** Supports a restricted irrotational-outer/viscous-boundary-layer circulation closure precedent, not a universal `Gamma(Omega)` law (`L-VIS`). **Hold:** Undermind PDF unavailable; no new equation mapping.
|
||||||
|
- **Moore (1957), “The flow past a rapidly rotating circular cylinder in a uniform stream,” *Journal of Fluid Mechanics* 2, 541–550, DOI `10.1017/S002211205700035X` [Moo57].** Related rotating-cylinder boundary-layer precedent (`L-VIS`). **Hold:** PDF unavailable; no equation mapping.
|
||||||
|
- **Brøns & Hartnack (1999), “Streamline topologies near simple degenerate critical points in two-dimensional flow away from boundaries,” *Physics of Fluids* 11, 314–324, DOI `10.1063/1.869881`.** Primary precedent for local bifurcation near degenerate interior critical points. No equation number or global-topology theorem is imported.
|
||||||
|
- **Crowdy & Marshall (2005), “The motion of a point vortex around multiple circular islands,” *Physics of Fluids* 17, DOI `10.1063/1.1900583`.** Establishes a Hamiltonian problem with evolving vortex position; it does not establish stability of a fixed prescribed-circulation harmonic field (`X-KAR` if reused for street motion).
|
||||||
|
- **Protas (2006), “Higher-order Föppl models of steady wake flows,” *Physics of Fluids* 18, DOI `10.1063/1.2389033`.** Provides free-vortex/finite-area-vortex equilibrium and stability context; it does not transfer to this static BVP.
|
||||||
|
|
||||||
|
## Stage-1 viscous-mechanism paper ledger
|
||||||
|
|
||||||
|
Each entry records geometry, Re, body order, rotation convention, control parameter, key equations/figures, stability criterion, gap/shear/separation/force evidence, transferable conclusions, and prohibited transfers to D20 `Re_D=50` free-slip-channel pinball.
|
||||||
|
|
||||||
|
### Rod26 — Rodrigues-Asensio et al. (2026) [claim `H-ROD` partially lifted]
|
||||||
|
|
||||||
|
- **Citation.** “On the turbulent wake of the actuated fluidic pinball: Dynamics, bifurcations and control authority,” *Experimental Thermal and Fluid Science* 177, 111798, DOI `10.1016/j.expthermflusci.2026.111798`. PDF available in Undermind DynamisLab.
|
||||||
|
- **Geometry / Re.** Three equal cylinders, `D=30 mm`, equilateral centres `1.5D`, upstream-pointing triangle; water tunnel, blockage ~14%; `Re=U_∞D/ν=9100`.
|
||||||
|
- **Body order.** Cylinder 1 front (fixed); 2 upper rear; 3 lower rear (Fig. 1).
|
||||||
|
- **Rotation / control.** `b_i=Ω_i D/(2U_∞)` (Eq. 1); boat-tailing parameter `p=(b_3-b_2)/2` (Eq. 2). Verbatim physical labels (§2.2): base-bleeding `p<0` “opposes the freestream and generates a jet between the two downstream cylinders”; boat-tailing `p>0` “weakens the gap flow and accelerates the outer shear layer.” Fig. 3: `p>0` upper CW / lower CCW; `p<0` upper CCW / lower CW. In CCW-positive lab frame, boat-tail ↔ `(0,-,+)`, base-bleed ↔ `(0,+,-)` on `(front,upper,lower)`.
|
||||||
|
- **Key figures.** Fig. 3 mean wakes; Fig. 7 separation motion under boat-tailing; Fig. 8 `C_D(p)` with optimum near `p≈1.8` and loss of authority beyond; Fig. 9 three-dimensional actuation manifold with two inverse-pitchfork symmetrizations (`p≥0.8` boat-tail; `p≤-2.6` base-bleed).
|
||||||
|
- **Stability / forces.** Symmetrization thresholds above; drag non-monotonic on boat-tail branch; strong base-bleed can change shedding symmetry (mode-S) without changing mean-wake character.
|
||||||
|
- **Transfer (`L-BR`).** Qualitative: distinct boat-tail vs base-bleed topologies; gap vs outer-shear organization; actuation magnitude, mean symmetry, and drag are different coordinates; excessive actuation can lose authority. Exact Fig. 3 / Eq. 2 sign map now authorized for literature comparison.
|
||||||
|
- **Prohibit.** Do not transfer `Re=9100`, turbulent thresholds `p=0.8/-2.6`, `p≈1.8` drag optimum, force magnitudes, or URANS details to D20 `Re_D=50`. Do not equate project accepted `[0,+Omega,-Omega]` with Rod26 boat-tailing: accepted maps to the **base-bleeding wall-sense family**. Do not import Kármán-street transmission claims (`X-KAR`).
|
||||||
|
- **Hold status.** Full-text/figure-sign publication hold **partially lifted** (sign/branch map above). Remaining hold: no quantitative equivalence of thresholds, forces, or manifold coordinates to the project CFD.
|
||||||
|
|
||||||
|
### Mac20 / Mac21 — Cornejo Maceda et al. (2021) [claim `L-BR`]
|
||||||
|
|
||||||
|
- **Citation.** “Stabilization of the fluidic pinball with gradient-enriched machine learning control,” *J. Fluid Mech.* 917, A42, DOI `10.1017/jfm.2021.301`. Undermind cite key `Mac20`; published 2021 (Mac20/Mac21 denote the same JFM paper / thesis lineage, not two mechanisms).
|
||||||
|
- **Geometry / Re.** Equilateral centres `1.5D`, upstream apex; domain `[-6,20]×[-6,6]`; `Re_D=100`.
|
||||||
|
- **Body order / sign.** `(b1,b2,b3)=(front,bottom,top)`; positive command = CCW (§2.1). Symmetric scan `b2=-b3`: boat-tail `b2>0` accelerates outer layers and sucks near-wake fluid upstream; base-bleed `b2<0` ejects a gap jet (§4.1, Fig. 6).
|
||||||
|
- **Control / cost.** Commands `b`; cost `Ja` is space-time `L²` distance to unstable symmetric steady NS target `u_s` (Eqs. 2.3–2.4)—**not** project `E_inf(q_ctl,q_in)` at `x/D=10`.
|
||||||
|
- **Key regimes.** Six mechanisms (§2.2): boat-tailing, base bleeding, phasor, Magnus/unified rotation, high- and low-frequency forcing. Symmetric regimes include steady branches for `b2<-4` and `b2>2.375` (strong Coanda boat-tail), with global symmetric `Ja` minimum at weak base-bleed `b2=-0.375` still unsteady/chaotic (§4.1).
|
||||||
|
- **Transfer.** Two high-amplitude symmetric steady branches can exist with different physics; steady ≠ close to `u_s`; gap jet / outer shear are the organizing viscous motifs for rear-opposite rotation.
|
||||||
|
- **Prohibit.** Do not transfer `Ja` optima, `b` thresholds, `Re=100`, asymmetric EGM/gMLC laws, or sensor-feedback structure to the rear-only D20 cloak metric. Do not relabel project accepted as Mac20 boat-tailing (see master map).
|
||||||
|
|
||||||
|
### Cha11b — Chan et al. (2011) [claim `L-BR`]
|
||||||
|
|
||||||
|
- **Citation.** “Vortex suppression and drag reduction in the wake of counter-rotating cylinders,” *J. Fluid Mech.* 679, 343–382, DOI `10.1017/jfm.2011.134`.
|
||||||
|
- **Geometry / Re.** Side-by-side equal cylinders; gaps `g*=1,3,5`; `Re=100,150,200`; no front cylinder.
|
||||||
|
- **Sign.** Fig. 2: doublet-like = upper CW / lower CCW (gap with freestream); reverse doublet-like = opposite. `Ω=ωd/(2U)`.
|
||||||
|
- **Stability.** Unsteadiness suppressed when force fluctuations vanish (`σ(C_L)`, `σ(C_D)` → 0); `Ω_crit` in Table 1. Doublet-like high-`Ω` forms virtual elliptic body (Figs. 8–9) with gap flow reversal and total drag ≈ 0 (Fig. 11); reverse doublet-like also steadies by separating wakes, generally without the elliptic body.
|
||||||
|
- **Transfer.** Both opposite counter-rotation directions can suppress shedding via **different** topologies; “steady” ≠ “cloak-like / zero-deficit.”
|
||||||
|
- **Prohibit.** No quantitative `Ω_crit`, drag-zero claim, or pair-gap thresholds for the three-cylinder D20 case; figure names are not project branch names.
|
||||||
|
|
||||||
|
### Mit01 — Mittal (2001) [claim `L-VIS` / `L-BR`]
|
||||||
|
|
||||||
|
- **Citation.** “Control of flow past bluff bodies using rotating control cylinders,” *J. Fluids Struct.* 15, 291–326, DOI `10.1006/jfls.2000.0337`.
|
||||||
|
- **Geometry / Re.** Main cylinder + two small controls, `D/D2=20`, centres on a line normal to the stream; gaps `0.01D`, `0.075D`; `Re=100` and `10^4`.
|
||||||
|
- **Sign / control.** Upper CW, lower anti-clockwise (Fig. 4); tip-speed ratio `Uc/U=5` yields steady narrow wake at `Re=100` (Fig. 6) and organized narrower wake at `Re=10^4`.
|
||||||
|
- **Transfer.** Order-of-magnitude tip-speed ~O(1–5) and rotating-surface momentum injection as a viscous wake-narrowing motif.
|
||||||
|
- **Prohibit.** Not equilateral three-equal-cylinder geometry; MSBC relies on cylinder no-slip (project cylinders rotate no-slip, but size ratio and alignment differ); no free-slip-channel transfer; power/drag numbers do not carry to D20.
|
||||||
|
|
||||||
|
### Ued03 — Ueda et al. (2003) [claim `L-VIS`]
|
||||||
|
|
||||||
|
- See primary-source ledger. Two cylinders, arbitrary radii/Ω, `Re≪1` matched Stokes–Oseen. **Transfer:** rotating no-slip selects vorticity; Euler does not. **Prohibit:** `Re_D=50` forces/closures.
|
||||||
|
|
||||||
|
### Wat96 — Watson (1996) [claim `L-VIS`]
|
||||||
|
|
||||||
|
- **Citation.** “Slow viscous flow past two rotating cylinders,” *Q. J. Mech. Appl. Math.* 49, 195–216, DOI `10.1093/qjmam/49.2.195`.
|
||||||
|
- **Geometry / regime.** Bipolar Stokes inner + Oseen outer; `Re=Uc/ν≪1`; equal contra-rotating cases with stream normal or parallel to the line of centres; lift can be O(drag).
|
||||||
|
- **Transfer.** Low-Re rotating-pair force coupling precedent only.
|
||||||
|
- **Prohibit.** Does not control `Re_D=50` free-slip or no-slip channel pinball; no cloak metric.
|
||||||
|
|
||||||
|
### Gla57 / Moo57 — Glauert & Moore (1957) [claim `L-VIS`, PDF hold]
|
||||||
|
|
||||||
|
- Single rapidly rotating cylinder boundary-layer / outer irrotational closure precedents. **Transfer:** restricted `Gamma`–rotation coupling idea. **Prohibit:** universal `Gamma(Omega)` for multi-body `Re_D=50`. PDFs unavailable in Undermind → equation-level hold retained.
|
||||||
|
|
||||||
|
### Kim92 — Kimura, Tsutahara & Wang (1992) [claim `L-ST`]
|
||||||
|
|
||||||
|
- **Citation.** “Wake of a rotating circular cylinder,” *AIAA J.* 30, 555–556, DOI `10.2514/3.10953`.
|
||||||
|
- **Geometry / Re.** Single cylinder; experiments `Re~2e2–7e4`; spin `α=` peripheral/freestream up to ~2.5.
|
||||||
|
- **Findings.** Wake meandering amplitude falls and St rises with spin; critical spin decreases as Re increases (Fig. 3).
|
||||||
|
- **Transfer.** Qualitative: higher spin can suppress meandering; lower Re may need larger spin.
|
||||||
|
- **Prohibit:** single-body thresholds; no three-cylinder or free-slip-channel map; lowest Re still above project `50` shedding neighborhood.
|
||||||
|
|
||||||
|
### Sie20 — Sierra et al. (2020) [claim `L-ST`]
|
||||||
|
|
||||||
|
- **Citation.** “Bifurcation scenario in the two-dimensional laminar flow past a rotating cylinder,” *J. Fluid Mech.* 905, A2, DOI `10.1017/jfm.2020.692`.
|
||||||
|
- **Geometry / Re.** Single rotating cylinder to `Re=200`; `α=ΩD/(2U_∞)` with positive = CW.
|
||||||
|
- **Findings.** Codimension-two points (cusp, Takens–Bogdanov, generalized Hopf); multiple steady states; bistability windows (Fig. 5, Table 1).
|
||||||
|
- **Transfer.** Finite actuation windows and coexisting steady/unsteady states are generic for rotating cylinders—motivates treating reverse high-deficit steady as a distinct branch, not a sign error.
|
||||||
|
- **Prohibit.** No transfer of `(Re,α)` coordinates to the three-cylinder pinball.
|
||||||
|
|
||||||
|
### Den18b — Deng et al. (2018/2019) [claim `L-ST`]
|
||||||
|
|
||||||
|
- **Citation.** “Low-order model for successive bifurcations of the fluidic pinball,” *J. Fluid Mech.*, DOI `10.1017/jfm.2019.959` (Undermind `Den18b`).
|
||||||
|
- **Geometry / Re.** Equilateral three equal cylinders, side `3R`, upstream apex; unforced Hopf `Re1≈18` and pitchfork `Re2≈68` (D-based).
|
||||||
|
- **Model.** Five-mode mean-field Galerkin system (Eqs. 3.30–3.31, 5.5) for shedding + shift + jet-deflection modes.
|
||||||
|
- **Transfer.** Unforced context: at `Re_D=50` (between Hopf and pitchfork) the natural mean is symmetric with unstable steady `u_s`; controlled steady cloak must suppress the Hopf.
|
||||||
|
- **Prohibit.** Coefficients/ROM not recalibrated for rotating control; no cloak or CCD claim (`X-CCD`).
|
||||||
|
|
||||||
|
### Cro06 — Crowdy (2006) [claim `L-PF`]
|
||||||
|
|
||||||
|
- See primary-source ledger. Zero-circulation multi-cylinder potential only.
|
||||||
|
|
||||||
|
### Urz12 — Urzhumov & Smith (2012) [claim `L-CLO`, workspace gap]
|
||||||
|
|
||||||
|
- **Citation (mem-backed).** “Flow stabilization with active hydrodynamic cloaks,” *Phys. Rev. E* 86, 056313 (2012). Active Brinkman porous shell raises critical Re ~5× for a single cylinder.
|
||||||
|
- **Transfer.** Analogy only: active momentum injection can stabilize a low-signature steady wake.
|
||||||
|
- **Prohibit.** Not rotating pinball; Brinkman `κ(r)` ≠ cylinder `Ω`; no PDF in the Undermind folder for this Stage-1 pass → no equation import. **Bibliographic gap:** add PDF to workspace before any equation-level use.
|
||||||
|
|
||||||
|
## Publication holds and unused context
|
||||||
|
|
||||||
|
- **Kharlamov & Filip (2012), generalized images for several parallel cylinders:** only a broad independent image-method precedent. No equation-number mapping is asserted; pages and DOI remain unverified and are not needed for the implemented MFS or Laurent cross-check. This publication hold does not block the present evidence-package verdict.
|
||||||
|
- **Lamb, *Hydrodynamics*; Milne-Thomson, *Theoretical Hydrodynamics*:** classical circle-flow sources. Edition/page/equation details remain unverified; the velocity formulas and counterexample are directly derived and tested.
|
||||||
|
- **Kelvin circulation theorem:** used under its classical inviscid/barotropic/conservative-force/smooth-material-loop hypotheses; original historical citation details remain unverified. Glass et al. supplies the modern application context.
|
||||||
|
- Crowdy & Marshall (2007) [Cro07b] remains multiply-connected Green context only unless a prime-function backend is implemented.
|
||||||
|
- **Gla57 / Moo57:** PDF unavailable → equation hold retained (above).
|
||||||
|
- **Urz12:** not in Undermind folder PDF set for this pass → equation hold (above).
|
||||||
|
|
||||||
|
## Project-derived equations and implementation boundary
|
||||||
|
|
||||||
|
The following are derived in `DERIVATION.md` and validated by project tests, not attributed to external equation numbers:
|
||||||
|
|
||||||
|
- Hodge-period form `u=u_N+sum_j Gamma_j h_j`, fixed-period uniqueness under stated hypotheses, flux compatibility, centered-circle rotational impermeability, and the one-circle trace `u_theta(a,theta)=-2U sin(theta)+Gamma/(2 pi a)`.
|
||||||
|
- MFS zero-source constraints, image truncation, diagnostics, independent finite Laurent collocation, and the common-exterior metric.
|
||||||
|
- Rear-pair log cancellation, `C_pair=-Gamma b/pi`, body-source multipoles, the affine coefficient `C(Gamma)=C0+Gamma dC`, real least-squares cancellation, symmetry parity, equal-norm ablation, gap flux, and sampled-root/Jacobian controls.
|
||||||
|
|
||||||
|
`Gamma_j` is prescribed in the inviscid problem. Any `Gamma_eff(s)` is an empirical or viscous closure requiring endpoint fields, coordinates, masks, sign convention, uncertainty, Reynolds number, geometry, confinement, and held-out validation. Historical fitted values without source arrays remain withdrawn. The v4 endpoint fields now support contour geometric sign and a corrected descriptive WLS projection, but not a causal `Gamma_eff(s)` law. Potential flow alone does not predict no-slip vorticity generation, separation, drag, torque, base pressure, or viscous/global/dynamical stability.
|
||||||
|
|
||||||
|
## Finite-Re/no-slip scope addition
|
||||||
|
|
||||||
|
- **Watson (1996)** supports only a low-Reynolds-number Stokes–Oseen reference. It does not control the present `Re_D=50` free-slip or no-slip channel.
|
||||||
|
- **Mao, Blackburn & Sherwin (2015), JFM, DOI `10.1017/jfm.2015.304`; Kharlamov & Filip (2012), DOI `10.1007/S10665-012-9532-6`.** Retained as channel/global-mode and image-method context respectively; neither supplies the finite-Re circulation closure or licenses transfer from free-slip potential flow to no-slip flow.
|
||||||
|
|
||||||
|
The exact free-slip strip modal relations in `DERIVATION.md` are project derivations from Laplace separation with Neumann walls. Watson is scoped only to low-Re Stokes–Oseen flow; Mao, Blackburn & Sherwin supplies channel/global-mode method context; Kharlamov–Filip supplies image-method context. None transfers the free-slip optimum or rescues the failed potential-circulation mechanism in a no-slip `Re_D=50` channel. New direct Navier–Stokes evidence is required because no-slip changes wall-vorticity production, base flow, separation, and modal operators.
|
||||||
|
|
||||||
|
## Stage-0 paper-oracle ledger
|
||||||
|
|
||||||
|
- **Vasconcelos, Moura & Schakel (2012), “Vortex motion around a circular cylinder,” DOI `10.1063/1.3667269`.** Equations (7)--(10), (13), and (26) map respectively to the symmetric Hamiltonian/canonical equations, Föppl locus/strength, infinity saddle, and its critical Hamiltonian level. The exact `kappa=45/32` finite equilibrium and the distinct infinity level are tested.
|
||||||
|
- **Mowlavi, Arratia & Gallaire (2016), “Spatio-temporal stability of the Kármán vortex street and the effect of confinement,” DOI `10.1017/jfm.2016.195`.** Physical-row coordinates/signs and scales are mapped from §2; the unconfined self-advection and `p0=asinh(1)/pi` are exact baselines. Equations (2.7), (2.11), and (4.3)--(4.9) are documented but intentionally not reproduced; temporal growth and convective thresholds remain publication holds (`X-KAR`).
|
||||||
|
- **Crowdy & Marshall (2005), “The motion of a point vortex around multiple circular islands,” DOI `10.1063/1.1900583`.** Supports Green/Robin/Kirchhoff--Routh motion for zero island circulations, background-flow Hamiltonian contribution, and regular self-response. Only the independently derived exact one-circle image reduction is implemented.
|
||||||
|
- **Streitlien (1994), “Extracting energy from unsteady flows through vortex control,” DOI `10.1575/1912/5563`.** Supports incoming-street/body coupling and Routh-regular vortex transport in a Joukowski-foil setting; no three-circle force or transparency claim is imported.
|
||||||
|
- **Cornejo Maceda et al. (2021)** — see Stage-1 Mac20 entry; Stage-0 retains paper-specific `(front,bottom,top)` labels only.
|
||||||
|
- **Chan et al. (2011)** — see Stage-1 Cha11b entry; Stage-0 retains figure-2 arrow specificity only.
|
||||||
|
- **Rodrigues et al. (2026)** — see Stage-1 Rod26 entry. Stage-0 metadata-only hold is superseded by the partial lift above; quantitative equivalence remains held (`H-ROD`).
|
||||||
|
|
||||||
|
## Same-setting contract claim boundary
|
||||||
|
|
||||||
|
The four-role steady contract is an internal CFD comparability and residual-readiness design, not a literature-derived mechanism test. Branch labels remain project-specific wall-action labels; accepted/reverse symmetry does not imply circulation, base-bleed, boat-tail, doublet, separation, force, or stability equivalence without the Stage-1 sign map. Fresh fields and forces must be regenerated before any new physical statement is made.
|
||||||
|
|
||||||
|
## S2 residual interpretation boundary
|
||||||
|
|
||||||
|
Rod26 (partially lifted), Cornejo Maceda et al. (2021), and Chan et al. (2011) are used only as qualitative frameworks for distinct steady rotating-cylinder branches, gap transport, and wake organization. The S2 endpoint result does not import their Reynolds numbers, geometry, confinement, body order, branch names, or force thresholds. Under the Stage-1 master map, project accepted wall sense aligns with literature **base-bleed / gap-jet** families, while literature **boat-tail** aligns with project reverse wall sense—yet project reverse is the high-deficit branch. Therefore S2 association (gap flux, shear width, recirculation, `Fx`, downstream deficit) is an internal D20 observation; it is not a literature proof that “boat-tailing explains the cloak,” nor a transfer of Rod26’s boat-tail drag optimum.
|
||||||
|
|
||||||
|
## Frozen cross-project source/evidence map
|
||||||
|
|
||||||
|
- **SR:** `src/SR_analysis/results/README.md`, `src/SR_analysis/PIPELINE.md`, `src/SR_analysis/results/runs/article2-steady-analysis-20260720/interpretation.md`, and the canonical term-deletion table support the ranking `rear constant > rear lift feedback > tested front feedback` only among the tested terms, plus order-of-magnitude steady correspondence. They do not establish necessity, global dominance, PPO mechanism, uniqueness, circulation, or a momentum-balance law.
|
||||||
|
- **CCD (`X-CCD`):** pending/not frozen for the steady total claim. No quantitative CCD value is imported here. Any later interface remains a separate estimand and cannot alone establish steady mechanism, actuator isolation, or causality.
|
||||||
|
- **Rod26 (`H-ROD`):** figure/sign map partially lifted in Stage-1; quantitative thresholds and force equivalence remain held.
|
||||||
|
- **Kármán (`X-KAR`):** vortex-street, phase, feedback, and control conclusions form a separate evidence chain and are not imported into the canonical uniform-inlet steady result.
|
||||||
|
|
||||||
|
## K1 qualification boundary
|
||||||
|
|
||||||
|
The 2026-08-06 K1 run uses only Mowlavi et al.'s documented unconfined row/stagger/scaling baseline and the exact neutral-aspect identity. A finite open train is integrated as a project numerical sensitivity problem; its end effects, central propagation, and perturbation amplification are not equations (2.7), (2.11), or (4.3)--(4.9), and are not temporal or convective growth. Mowlavi confined dispersion/growth and the plan-required growth/impulse oracle remain publication-held. Consequently K2 is NO-GO.
|
||||||
|
|
||||||
|
|
||||||
|
## Mechanism-completion literature boundary
|
||||||
|
|
||||||
|
Stage-1 theory closure and later numerical work remain governed by the ledgers above. Ueda/Glauert/Moore support only the viscous-interface distinction; Maceda/Chan/Rod26 remain qualitative branch/gap context. SR supports rear-constant ranking only among tested terms. CCD remains pending/not frozen for the steady total claim and supplies no quantitative term here. None transfers Legacy layouts, PPO feedback, causality, or steady-mechanism proof into the canonical free-slip direct-`q_in` claim.
|
||||||
|
|
||||||
|
|
||||||
|
## Historical composite discussion
|
||||||
|
|
||||||
|
The failed composite semi-analytic route, its source-family mapping, and its audit are recorded in [HISTORY_AND_LESSONS.md](HISTORY_AND_LESSONS.md#semi-analytic-failure) and the immutable `steady-semi-analytic-analysis-20260807` disposition in [evidence/INDEX.json](evidence/INDEX.json). Those sources remain useful historical context but are not active source-to-claim support.
|
||||||
|
|
||||||
|
## NS-first literature governance
|
||||||
|
|
||||||
|
The intended chain uses Drela for actuator/wake energy-accounting method, Steiros and related base-bleed work for conservation-reduction context, Mittal for rotating-control wake modification, Liu/Terrington for wall-motion and boundary-vorticity accounting, Mao–Blackburn–Sherwin for qualified-base global-stability methods, and Urzhumov–Smith for a limited active-cloak analogy. These anchors do not repair the audited project artifacts: the NS ledger is bounded diagnostics, and the causal/nonlinear promotions lack strong provenance.
|
||||||
|
|
||||||
|
These are method anchors, not transferred results. No paper supplies the project torque sign, `C_P`, gap flux, energy closure, `Re_D=50` coefficients, accepted/reverse ranking, causal timing, or eigenvalues. Equation-level use requires the exact primary-source mapping already verified in the ledger or a publication hold; memory or synthesis text alone cannot authorize a number. The literature chain must follow `power → momentum/vorticity → wake energy → stability`, with each transfer and prohibited transfer recorded before analysis.
|
||||||
|
|
||||||
|
|
||||||
|
## Publication-v3 claim mapping
|
||||||
|
|
||||||
|
The v3 report maps the wall/interface distinction to Crowdy, Glass, Ueda, and Glauert; qualitative gap/base-bleed branch context to Cornejo Maceda, Chan, and Rodrigues-Asensio; and stability-method context to Mao–Blackburn–Sherwin. None supplies the project torque sign, `C_P`, same-CV closure, direct `q_ctl-q_in` metric, causal timing, or return labels. Those are authenticated project results and remain BOUNDED. SR is contextual cross-project evidence only; CCD is pending/not frozen for this steady claim.
|
||||||
|
|||||||
@@ -1,19 +1,61 @@
|
|||||||
# Steady pinball theory (reset)
|
# Steady pinball theory
|
||||||
|
|
||||||
Minimal, from-scratch implementation for the uniform-inflow/free-slip steady-cloak study.
|
A bounded `Re_D=50` steady-flow evidence package. The strongest result is a settling-qualified low-deficit endpoint; prescribed-circulation cancellation fails by sign, and available NS diagnostics do not close a mechanism, causal effect, or stability proof.
|
||||||
Nothing from the deleted implementation is promoted as evidence.
|
|
||||||
|
|
||||||
## Frozen scientific scope
|
## Start here
|
||||||
|
|
||||||
- Canonical geometry: three equal cylinders, rear centers at `(1.3D, +/-0.75D)` from the front; `Re_D=50`; channel half-height `15D`; uniform inlet and free-slip horizontal walls.
|
1. [RESULTS.md](RESULTS.md) — current results and limits.
|
||||||
- Primary observer: full cross-section at `x/D=10` from the front-cylinder center.
|
2. [evidence/README.md](evidence/README.md) — conclusion-to-artifact and family index.
|
||||||
- Primary score: `max_y sqrt((u_ctl-u_in)^2+(v_ctl-v_in)^2)/U_inf`; passive `q_blk` is non-ranking.
|
3. [DERIVATION.md](DERIVATION.md) — mathematics and historical charters.
|
||||||
- Celeris source uses x-right/y-up lattice coordinates and `(Uw,Vw)=(-omega*ry,omega*rx)`. Both signed rear actions are retained until the paired CFD oracle is reviewed. Names such as clockwise or cloak never override numeric body IDs and actions.
|
4. [LITERATURE.md](LITERATURE.md) — active source boundaries.
|
||||||
|
5. [HISTORY_AND_LESSONS.md](HISTORY_AND_LESSONS.md) — chronology and durable rules.
|
||||||
|
|
||||||
## Files and evidence policy
|
Machine authority is [evidence/INDEX.json](evidence/INDEX.json). The sole `evidence/` symlink reaches the external physical root; path/hash-bound directories are never moved for semantic cleanup.
|
||||||
|
|
||||||
The package remains below 30 files. Runtime results are written outside this tree. `contract.py` and its tests define source-level facts; `runner.py` produces paired action-reversal diagnostics; `theory.py` is the MFS outer/strip representation; `metrics.py` owns the direct-q_in objective. Literature assumptions and equation provenance are in `LITERATURE.md`.
|
## Unified reader plotting deliverable
|
||||||
|
|
||||||
## Environments
|
[`plot_results.py`](plot_results.py) regenerates the unified lattice-exact raw field canvases, family-specific error colorbars, analytical-zero potential vorticity, titled PNG/PDF metric plates, CSVs, and summary curves/bars in [`figures/`](figures/):
|
||||||
|
|
||||||
CPU theory/tests: `conda run -n pinball_math ...`. CFD: `PYTHONPATH=CelerisLab/src:src conda run -n pycuda_3_10 python -m steady_pinball_theory.runner ...`. GPU cases execute serially.
|
```bash
|
||||||
|
PYTHONPATH="$PWD/src" conda run -n pinball_math python src/steady_pinball_theory/plot_results.py
|
||||||
|
```
|
||||||
|
|
||||||
|
Raw canvases preserve one pixel per lattice cell; companion plates are explicitly presentation assets rather than pixel-exact arrays. This is a CPU-only, regenerable reader-facing visualization of existing authenticated sources, not new evidence. It refuses to clobber an existing output unless `--overwrite` is explicit.
|
||||||
|
|
||||||
|
## Display and CSV command
|
||||||
|
|
||||||
|
```bash
|
||||||
|
xdg-open "$PWD/src/steady_pinball_theory/evidence/authoritative/steady-results-display-v2-20260809/01_total_velocity_streamlines.pdf"
|
||||||
|
xdg-open "$PWD/src/steady_pinball_theory/evidence/authoritative/steady-results-display-v2-20260809/02_cfd_disturbance_vorticity_streamlines.pdf"
|
||||||
|
xdg-open "$PWD/src/steady_pinball_theory/evidence/authoritative/steady-results-display-v2-20260809/03_ns_diagnostics_partial_no_residual.pdf"
|
||||||
|
python3 -c 'import pandas as pd; print(pd.read_csv("src/steady_pinball_theory/evidence/authoritative/steady-results-display-v2-20260809/summary.csv").to_string(index=False))'
|
||||||
|
```
|
||||||
|
|
||||||
|
Exact CPU-only display reproduction (change only `--out` to another fresh path):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
PYTHONPATH="$PWD/src" TZ=UTC LC_ALL=C.UTF-8 conda run -n pinball_math python -m steady_pinball_theory.runner publish-results-display --steady "/home/frank14f/optane/DynamisLab/steady_pinball_theory/authoritative/steady-contract-v5-torque-d20-20260808" --strip "/home/frank14f/optane/DynamisLab/steady_pinball_theory/authoritative/steady-strip-modal-analysis-20260804-v4/analysis.json" --ledger "/home/frank14f/optane/DynamisLab/steady_pinball_theory/authoritative/ns-ledger-v2-oracle-bound-d20-20260809/analysis.json" --out "/tmp/steady-results-display-v2-REPRO"
|
||||||
|
```
|
||||||
|
|
||||||
|
Verification: reproducing to a fresh `--out` path yields byte-identical figures, CSVs, and display README. `manifest.json` intentionally records the requested output path, so its `exact_reproduction_command` and derived `payload_sha256` differ when `--out` differs; this is provenance-path dependence, not a scientific/data difference.
|
||||||
|
|
||||||
|
The [display manifest](evidence/authoritative/steady-results-display-v2-20260809/manifest.json) authenticates its sources. This derived view ran no CFD.
|
||||||
|
|
||||||
|
## Frozen boundaries
|
||||||
|
|
||||||
|
- Body order `(front, rear_y_plus, rear_y_minus)`; accepted action `[0,+Omega,-Omega]`.
|
||||||
|
- Primary metric: common-fluid `E_inf_vector(q_ctl,q_in)` at `x/D=10`; `q_blk` is nonranking.
|
||||||
|
- Celeris wall law `(Uw,Vw)=(-omega*ry,omega*rx)` in x-right/y-up.
|
||||||
|
- Runtime evidence establishes torque telemetry work sign, not force ownership.
|
||||||
|
- Potential flow is an impermeability/circulation reference, not rotating-wall no-slip closure.
|
||||||
|
- SR supplies only current rear-constant context. CCD is pending/not frozen here and contributes no quantitative term to the steady frozen claim.
|
||||||
|
|
||||||
|
## Verification
|
||||||
|
|
||||||
|
```bash
|
||||||
|
PYTHONPATH="$PWD/src" conda run -n pinball_math python -m pytest -q src/steady_pinball_theory/tests/test_core.py
|
||||||
|
conda run -n pinball_math python -m compileall -q src/steady_pinball_theory
|
||||||
|
git diff --check -- src/steady_pinball_theory
|
||||||
|
```
|
||||||
|
|
||||||
|
No CFD belongs in documentation or display verification.
|
||||||
|
|||||||
@@ -1,46 +1,41 @@
|
|||||||
# Reset campaign results and claim matrix
|
# Current steady results
|
||||||
|
|
||||||
All values below were generated from scratch after deletion of the previous project. Runtime arrays remain outside this source tree under `/tmp/steady-*`; these are diagnostic/production candidates, not a clean tagged archival release.
|
Only current valuable results are indexed here. Exact statuses, replacements, and entry files are in [evidence/README.md](evidence/README.md) and [evidence/INDEX.json](evidence/INDEX.json).
|
||||||
|
|
||||||
## Sign oracle
|
## Potential mathematics and failure
|
||||||
|
|
||||||
Celeris source defines Cartesian lattice coordinates and `(Uw,Vw)=(-omega*ry,omega*rx)`. With body order `front, rear_y_plus, rear_y_minus`, paired D20 runs at `s=5`, `tU/D=250` give:
|
The inviscid problem supports independent prescribed circulations and a rear-opposite dipole basis. The tested cancellation explanation fails: strip analysis requires `g_opt=+6.147944`, while accepted finite-Re flow has descriptive `g=-12.200554`. Potential flow remains a bounded outer reference, not a no-slip closure or viscous mechanism.
|
||||||
|
|
||||||
- `[0,+Omega,-Omega]`: direct `q_ctl-q_in` `E_inf_vector(x/D=10)=0.13764`; compact steady wake.
|
Evidence: [strip analysis](evidence/authoritative/steady-strip-modal-analysis-20260804-v4/analysis.json), [finite-Re analysis](evidence/authoritative/steady-finite-re-bridge-analysis-20260804-v4/analysis.json), [derivation](DERIVATION.md).
|
||||||
- `[0,-Omega,+Omega]`: `E_inf_vector=0.64593`; broad deficit/wake.
|
|
||||||
|
|
||||||
The accepted numerical cloak branch is therefore `[0,+Omega,-Omega]`. Its outer cardinal surfaces move upstream and its gap-facing cardinal surfaces downstream under the solver law; “outer-surface downstream boat-tail” is not an accurate description of this branch.
|
## Steady endpoint
|
||||||
|
|
||||||
## Rotation search
|
In the canonical uniform-inlet/free-slip `Re_D=50` setup, accepted `[0,+Omega,-Omega]`, `s=3.55`, is settling-qualified. Direct current-v5 recomputation gives exactly `E_inf_vector(q_ctl,q_in)=0.0231672176752314` at `x/D=10`; nearby `s={3.45,3.55,3.65}` values are approximately `{0.0232525,0.0231671,0.0312490}`.
|
||||||
|
|
||||||
At D20, `Re_D=50`, `H/D=15`, uniform inlet and free-slip horizontal walls, the accepted branch was scanned from `s=0` to `7` for `tU/D=250`. The direct profile objective falls from `1.046` at passive `s=0` to `0.0232` near `s=3.45-3.55`, then rises to `0.0604` at `s=4`, `0.1376` at `s=5`, and `0.2972` at `s=7`. The current candidate interval is `s=3.45-3.55`; resolution of the flat pointwise maximum does not justify a unique optimum more precise than this interval.
|
This is an endpoint observation, not uniqueness, global attraction, no-slip transfer, or causal mechanism.
|
||||||
|
|
||||||
## Numerical sensitivity
|
Evidence: [v5 contract](evidence/authoritative/steady-contract-v5-torque-d20-20260808/), [publication v3b](evidence/authoritative/steady-article-publication-v3b-20260809/REPORT.md), [display v2](evidence/authoritative/steady-results-display-v2-20260809/README.md).
|
||||||
|
|
||||||
At `s=3.55`, matched q-in references give `E_inf={0.04549,0.02317,0.02348}` for `D={10,20,30}`. D20 and D30 agree within `3.1e-4`; D10 is not converged. Lateral half-height sensitivity at D20 gives `E_inf=0.02382` for `H/D=12`, `0.02317` for `15`, and `0.02478` for `18`. Fine-grid D30 local values are `0.02353,0.02348,0.02921` at `s=3.45,3.55,3.65`.
|
## NS diagnostics
|
||||||
|
|
||||||
Late-window changes in the scanned steady candidates are `O(1e-5-1e-4)` in the profile diagnostic. Neighboring cold-start cases at D30 (`s=3.4,3.5,3.6,3.7`) likewise settle over `tU/D=250`. This is empirical cold-start steadiness only; no checkpoint perturbation decay or global eigenanalysis has been completed.
|
The oracle-bound ledger provides actuator power, signed gap fluxes, wake-profile errors, recirculation, force readbacks, and vorticity diagnostics. It is an endpoint spatial partial ledger: storage/time-consistent and some body-adjacent terms are unavailable, force ownership is not established, and no residual exists. Accepted `s=3.55` and stable reverse `s=5.2` are not amplitude matched.
|
||||||
|
|
||||||
## Analytical representation and finite-Re interface
|
Evidence: [ledger](evidence/authoritative/ns-ledger-v2-oracle-bound-d20-20260809/analysis.json), CSVs [cases](evidence/authoritative/steady-results-display-v2-20260809/ns_case_summary.csv), [gaps](evidence/authoritative/steady-results-display-v2-20260809/ns_gap_fluxes.csv), [stations](evidence/authoritative/steady-results-display-v2-20260809/ns_station_diagnostics.csv).
|
||||||
|
|
||||||
The MFS solver enforces cylinder no penetration and zero source per body for unbounded and image-strip formulations. Tests cover collocation order, source radius and image-layer sensitivity. A strip circulation scan predicts near cancellation at `|Gamma|/(U D) about 4.25` (sign depends on body/circulation convention), with boundary residual below `1e-14` in that solve. Outer-mask fits to D30 CFD at `s=3.45,3.55,3.65` yield signed `Gamma_eff/(UD)=-4.293,-4.339,-4.384`, close in magnitude to the inviscid cancellation value; residual component RMS is about `1.9-2.1% U`. This supports a circulation-dominated outer mechanism but does not close boundary layers, pressure, force or separation.
|
## Limits of transient evidence
|
||||||
|
|
||||||
## Claim matrix
|
Migrated switch and return products are bounded diagnostics. The switch is one deterministic same-parent realization with heuristic gates; return labels are finite-horizon field-distance descriptions. They establish neither replicated causality, branch switching/basins, nonlinear stability, eigenvalues, nor global stability. Earlier pre-migration artifacts have explicit replacements in [INDEX.json](evidence/INDEX.json).
|
||||||
|
|
||||||
Supported now:
|
Evidence: [causal v3c](evidence/authoritative/steady-causal-analysis-v3c-d20-s3.55-20260808T2245/analysis.json), [return v2c](evidence/authoritative/steady-return-analysis-v2c-d20-s3.55-20260808T2250/analysis.json).
|
||||||
- exact numeric action/body mapping and action-reversal ordering;
|
|
||||||
- direct q-in profile metric at the historical `x/D=10` plane;
|
|
||||||
- a D20/D30-converged low-error candidate interval near `s=3.45-3.55` in the tested Celeris setup;
|
|
||||||
- numerical MFS no-penetration/image-strip representation and effective-circulation magnitude agreement.
|
|
||||||
|
|
||||||
Bounded/descriptive:
|
## SR interface
|
||||||
- empirical cold-start steadiness through `tU/D=250`;
|
|
||||||
- circulation-dominated outer-flow interpretation;
|
|
||||||
- lateral-domain sensitivity over `H/D=12-18`.
|
|
||||||
|
|
||||||
Not supported:
|
The only current SR link is contextual: persistent rear constant is the dominant tested term; rear-lift feedback is secondary and the tested front term weak over the declared deletion window. Use [SR claim K2](../SR_analysis/CLAIMS.md#k2--persistent-rear-counter-rotation-is-the-dominant-tested-element--current) and its exact evidence paths. This transfers neither Legacy metrics nor mechanism, necessity, uniqueness, or PPO explanation.
|
||||||
- global stability, unique attractor, perturbation decay, or exact optimum;
|
|
||||||
- independent Navier-Stokes reproduction (no independent NS package is installed);
|
## CCD pending
|
||||||
- pressure/drag/base-bleed/separation closure;
|
|
||||||
- JFM-ready exact Schottky-Klein derivation or clean immutable production release;
|
CCD is **pending/not frozen for the steady total claim**. No quantitative CCD value contributes to this package's frozen conclusion. Any later interface must remain a separately identified estimand and cannot alone prove the steady mechanism.
|
||||||
- three-dimensional, experimental, energetic or other-Re generality.
|
|
||||||
|
## Bottom line
|
||||||
|
|
||||||
|
Evidence supports a qualified low-deficit steady endpoint and bounded NS diagnostics, and rejects potential-circulation cancellation. Mechanical-energy closure, same-amplitude steady comparison, replicated causality, branch/global stability, and no-slip transfer remain unestablished.
|
||||||
|
|||||||
@@ -7,6 +7,59 @@ from __future__ import annotations
|
|||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
|
||||||
|
REVERSE_PRODUCTION_S = (3.55, 5.2)
|
||||||
|
REVERSE_ROLE_BY_S = {3.55: "same_amplitude_negative_control", 5.2: "historical_stability_candidate"}
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class NondimensionalContract:
|
||||||
|
"""2-D per-unit-span lattice-to-project reference contract."""
|
||||||
|
|
||||||
|
diameter: float
|
||||||
|
u_inf: float
|
||||||
|
rho_ref: float
|
||||||
|
|
||||||
|
def __post_init__(self) -> None:
|
||||||
|
refs = np.asarray([self.diameter, self.u_inf, self.rho_ref], dtype=float)
|
||||||
|
if not np.isfinite(refs).all() or np.any(refs <= 0):
|
||||||
|
raise ValueError("diameter, u_inf, and rho_ref must be finite positive references")
|
||||||
|
|
||||||
|
def convert(self, *, x=None, t=None, nu=None, omega=None, Q=None, J=None,
|
||||||
|
force=None, torque=None, power=None, mass_flux=None,
|
||||||
|
momentum_flux=None, energy_flux=None, pressure=None) -> dict:
|
||||||
|
"""Convert supplied lattice quantities to project nondimensional values."""
|
||||||
|
D, U, rho = float(self.diameter), float(self.u_inf), float(self.rho_ref)
|
||||||
|
scales = {
|
||||||
|
"x_star": D, "t_star": D / U, "nu_star": U * D,
|
||||||
|
"omega_star": U / D, "Q_star": U * D, "J_star": rho * U**2 * D,
|
||||||
|
"C_F": .5 * rho * U**2 * D, "C_tau": .5 * rho * U**2 * D**2,
|
||||||
|
"C_P": .5 * rho * U**3 * D,
|
||||||
|
"mass_flux_star": rho * U * D,
|
||||||
|
"momentum_flux_star": rho * U**2 * D,
|
||||||
|
"energy_flux_star": rho * U**3 * D,
|
||||||
|
"pressure_star": rho * U**2,
|
||||||
|
}
|
||||||
|
supplied = {"x_star": x, "t_star": t, "nu_star": nu,
|
||||||
|
"omega_star": omega, "Q_star": Q, "J_star": J,
|
||||||
|
"C_F": force, "C_tau": torque, "C_P": power,
|
||||||
|
"mass_flux_star": mass_flux, "momentum_flux_star": momentum_flux,
|
||||||
|
"energy_flux_star": energy_flux, "pressure_star": pressure}
|
||||||
|
out = {}
|
||||||
|
for name, value in supplied.items():
|
||||||
|
if value is None:
|
||||||
|
continue
|
||||||
|
array = np.asarray(value, dtype=float)
|
||||||
|
if not np.isfinite(array).all():
|
||||||
|
raise ValueError(f"{name} input must be finite")
|
||||||
|
converted = array / scales[name]
|
||||||
|
out[name] = float(converted) if converted.ndim == 0 else converted
|
||||||
|
out["metadata"] = {
|
||||||
|
"reference": {"D": D, "U_inf": U, "rho_ref": rho},
|
||||||
|
"scope": "two-dimensional per-unit-span",
|
||||||
|
"definitions": "x/D; tU_inf/D; nu/(U_inf D); omega D/U_inf; Q/(U_inf D); J/(rho_ref U_inf^2 D); mass/(rho_ref U_inf D); momentum/(rho_ref U_inf^2 D); energy/(rho_ref U_inf^3 D); p/(rho_ref U_inf^2); force, torque, power coefficients use one-half dynamic-pressure scales",
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
|
||||||
|
|
||||||
def wall_velocity(omega: float, rx: float, ry: float) -> tuple[float, float]:
|
def wall_velocity(omega: float, rx: float, ry: float) -> tuple[float, float]:
|
||||||
"""Celeris curved-wall law: (Uw,Vw)=(-omega*ry, omega*rx)."""
|
"""Celeris curved-wall law: (Uw,Vw)=(-omega*ry, omega*rx)."""
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
/home/frank14f/optane/DynamisLab/steady_pinball_theory
|
||||||
@@ -0,0 +1,84 @@
|
|||||||
|
# Unified steady-pinball figures
|
||||||
|
|
||||||
|
Regenerable reader-facing visualization, not new evidence. CPU-only; no CFD/CUDA ran.
|
||||||
|
|
||||||
|
## Redraw
|
||||||
|
```bash
|
||||||
|
PYTHONPATH="$PWD/src" conda run -n pinball_math python src/steady_pinball_theory/plot_results.py --steady "/home/frank14f/optane/DynamisLab/steady_pinball_theory/authoritative/steady-contract-v5-torque-d20-20260808" --strip "/home/frank14f/optane/DynamisLab/steady_pinball_theory/authoritative/steady-strip-modal-analysis-20260804-v4/analysis.json" --ledger "/home/frank14f/optane/DynamisLab/steady_pinball_theory/authoritative/ns-ledger-v2-oracle-bound-d20-20260809/analysis.json" --output "/home/frank14f/DynamisLab/src/steady_pinball_theory/figures"
|
||||||
|
```
|
||||||
|
Defaults permit omitting all flags. Existing output fails unless `--overwrite` is supplied; replacement uses atomic staging and affects only this generated root.
|
||||||
|
|
||||||
|
## Contents and definitions
|
||||||
|
- `fields/error`: six raw `|q_case-q_in|/U_inf` canvases (dimensionless, not percent).
|
||||||
|
- `fields/vorticity`: six raw `omega D/U_inf` canvases. Potential exterior values are the analytical irrotational value zero; CFD retains conservative masked numerical curl.
|
||||||
|
- `fields/streamlines`: total-velocity streamlines, arrows enabled.
|
||||||
|
- `colorbars` and `annotations`: family-named standalone legends and separate metric assets.
|
||||||
|
- `plates/{error,vorticity,streamlines}`: publication-friendly PNG/PDF companions with titles and framed metrics. Plates are presentation assets, not pixel-exact arrays.
|
||||||
|
- `field_metrics.csv`, `profiles_long.csv`, and `summary`: direct redraw data; every summary PNG/PDF has a same-basename CSV.
|
||||||
|
|
||||||
|
Crop indices x=960..1200, y=239..359; exact x/D=[-2,10], y/D=[-3.025,2.975], canvas=241x121 pixels. Arrays are yx, x is horizontal, y is up, origin is the front center. Potential uses these exact CFD lattice coordinates and analytic cylinders; CFD uses solver fluid masks. Scalar images use nearest mapping, one pixel/cell, no axes/titles/padding. Bodies are white with thin black outlines. Streamline paths are vector-rendered, but their PNG canvas has the same exact dimensions and lattice-edge extent.
|
||||||
|
|
||||||
|
Potential and CFD errors use separate pooled-family 97.5-percentile limits: potential `1.131928670277732`, CFD `1.440698623657227`. Both use the same discrete Greys style, but grayscale darkness is only comparable within its named family colorbar. Shared CFD-only symmetric vorticity limit is `+/-2.374693971069057`. Potential displayed/exported vorticity is analytical zero. Values beyond limits map to end bins and raw maxima remain in CSV. Vorticity uses discrete RdBu_r with a forced-white center and no zero contour.
|
||||||
|
|
||||||
|
Crop L2 is `sqrt(integral_common |q_case-q_in|^2 dA / integral_common dA)/U_inf` with tensor trapezoid weights. Profiles are direct differences, not deltas misused with a uniform-reference helper. Accepted x/D=10 direct vector E_inf is `0.0231672176752314`.
|
||||||
|
|
||||||
|
Potential `g_opt=6.1479442395622` and fitted `g=-12.200554143243473` have opposite signs: do not infer a potential mechanism. Body order is front, rear_y_plus, rear_y_minus; accepted is `[0,+Omega,-Omega]`. Stationary is an unsteady reference. Accepted s=3.55 and reverse s=5.2 have mismatched amplitudes. The NS ledger is partial. No mechanism, causality, stability, or closure is established; no CCD numbers are used.
|
||||||
|
|
||||||
|
## Sources
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"ledger": {
|
||||||
|
"path": "/home/frank14f/optane/DynamisLab/steady_pinball_theory/authoritative/ns-ledger-v2-oracle-bound-d20-20260809/analysis.json",
|
||||||
|
"sha256": "c5d1395dd438e0f9c1f98489fd94fe309349ee554d0a21752ddfa571fe4d66cb"
|
||||||
|
},
|
||||||
|
"steady_root": {
|
||||||
|
"files": {
|
||||||
|
"accepted-s3.55/endpoint.npz": {
|
||||||
|
"bytes": 7493746,
|
||||||
|
"path": "/home/frank14f/optane/DynamisLab/steady_pinball_theory/authoritative/steady-contract-v5-torque-d20-20260808/accepted-s3.55/endpoint.npz",
|
||||||
|
"sha256": "b6c70b76c7cf2a2e4330b27fb0d4e82a914b40fde8171076704e825a79ac72db"
|
||||||
|
},
|
||||||
|
"accepted-s3.55/manifest.json": {
|
||||||
|
"bytes": 7701,
|
||||||
|
"path": "/home/frank14f/optane/DynamisLab/steady_pinball_theory/authoritative/steady-contract-v5-torque-d20-20260808/accepted-s3.55/manifest.json",
|
||||||
|
"sha256": "0fef464f8acc445a772db9792d8bb751f5f7a1502b4bf4f545833c29b861eeb1"
|
||||||
|
},
|
||||||
|
"qin/endpoint.npz": {
|
||||||
|
"bytes": 4783748,
|
||||||
|
"path": "/home/frank14f/optane/DynamisLab/steady_pinball_theory/authoritative/steady-contract-v5-torque-d20-20260808/qin/endpoint.npz",
|
||||||
|
"sha256": "88cbeac1069336716c30d18fd5afd4fc9d6ae87617c7f390b1366fe7098e419b"
|
||||||
|
},
|
||||||
|
"qin/manifest.json": {
|
||||||
|
"bytes": 6203,
|
||||||
|
"path": "/home/frank14f/optane/DynamisLab/steady_pinball_theory/authoritative/steady-contract-v5-torque-d20-20260808/qin/manifest.json",
|
||||||
|
"sha256": "e68dc9c2ac5f64f3db024c1c1b0ff729d3403781c2ca6a2cae6740f060ce14ff"
|
||||||
|
},
|
||||||
|
"reverse-s5.2/endpoint.npz": {
|
||||||
|
"bytes": 8376367,
|
||||||
|
"path": "/home/frank14f/optane/DynamisLab/steady_pinball_theory/authoritative/steady-contract-v5-torque-d20-20260808/reverse-s5.2/endpoint.npz",
|
||||||
|
"sha256": "760c1e84dd9b7784151ed180d3f16d17619465a08ddb767ce23a87517cc28ec3"
|
||||||
|
},
|
||||||
|
"reverse-s5.2/manifest.json": {
|
||||||
|
"bytes": 7750,
|
||||||
|
"path": "/home/frank14f/optane/DynamisLab/steady_pinball_theory/authoritative/steady-contract-v5-torque-d20-20260808/reverse-s5.2/manifest.json",
|
||||||
|
"sha256": "99313ced195070fdeb2daefde792ea50d48ccb59ae211eb2493af021713dea69"
|
||||||
|
},
|
||||||
|
"stationary/endpoint.npz": {
|
||||||
|
"bytes": 8795812,
|
||||||
|
"path": "/home/frank14f/optane/DynamisLab/steady_pinball_theory/authoritative/steady-contract-v5-torque-d20-20260808/stationary/endpoint.npz",
|
||||||
|
"sha256": "c2edc9e4173fb6bbde7937314e4984c1c501de75f0dd4916b67aa68388dde8f3"
|
||||||
|
},
|
||||||
|
"stationary/manifest.json": {
|
||||||
|
"bytes": 7197,
|
||||||
|
"path": "/home/frank14f/optane/DynamisLab/steady_pinball_theory/authoritative/steady-contract-v5-torque-d20-20260808/stationary/manifest.json",
|
||||||
|
"sha256": "d9c4d4b8760116011bfe4c077fde8ffda69935c5eb79240aaca5ec9fc2654346"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"path": "/home/frank14f/optane/DynamisLab/steady_pinball_theory/authoritative/steady-contract-v5-torque-d20-20260808"
|
||||||
|
},
|
||||||
|
"strip_analysis": {
|
||||||
|
"path": "/home/frank14f/optane/DynamisLab/steady_pinball_theory/authoritative/steady-strip-modal-analysis-20260804-v4/analysis.json",
|
||||||
|
"sha256": "10aba0798dea28b7803318c912e310aae385b0e7e8886e2f1bf533dc37f0a74c"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
After Width: | Height: | Size: 20 KiB |
|
After Width: | Height: | Size: 20 KiB |
|
After Width: | Height: | Size: 21 KiB |
|
After Width: | Height: | Size: 20 KiB |
|
After Width: | Height: | Size: 19 KiB |
|
After Width: | Height: | Size: 21 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 17 KiB |
@@ -0,0 +1,7 @@
|
|||||||
|
case,label,family,parameter_name,parameter,crop_L2_vector,x10_actual,x10_E_L2_x,x10_E_L2_y,x10_E_L2_vector,x10_E_inf_x,x10_E_inf_y,x10_E_inf_vector,error_max,vorticity_abs_max,source_path,source_sha256,crop_L2_definition,profile_definition
|
||||||
|
mfs-zero,"potential MFS, zero circulation",potential,g,0.0,0.18203706719105045,10.0,0.004105060774649961,0.0037312765646886127,0.005547427220411656,0.008680187804227746,0.0056069498132052175,0.008684020133851821,1.202101548510311,0.0,/home/frank14f/optane/DynamisLab/steady_pinball_theory/authoritative/steady-strip-modal-analysis-20260804-v4/analysis.json,10aba0798dea28b7803318c912e310aae385b0e7e8886e2f1bf533dc37f0a74c,sqrt(integral common-fluid |q_case-q_in|^2 dA / integral common-fluid dA)/U_inf,direct q_case-q_in; trapezoid L2 and pointwise Linf
|
||||||
|
mfs-gopt,potential favorable g_opt,potential,g,6.1479442395622,0.3355483915233398,10.0,0.0017015730991986912,0.00010382641602204063,0.0017047377911516542,0.0018631773275301633,0.00017321182459621807,0.0018631935376256777,3.9970332436824467,0.0,/home/frank14f/optane/DynamisLab/steady_pinball_theory/authoritative/steady-strip-modal-analysis-20260804-v4/analysis.json,10aba0798dea28b7803318c912e310aae385b0e7e8886e2f1bf533dc37f0a74c,sqrt(integral common-fluid |q_case-q_in|^2 dA / integral common-fluid dA)/U_inf,direct q_case-q_in; trapezoid L2 and pointwise Linf
|
||||||
|
mfs-gfit,potential finite-Re fitted g,potential,g,-12.200554143243473,0.573990978668274,10.0,0.011248144975468088,0.011131057331628027,0.015824702294486872,0.022827084217645455,0.01681377744611242,0.022840097461030467,4.344431091608549,0.0,/home/frank14f/optane/DynamisLab/steady_pinball_theory/authoritative/steady-strip-modal-analysis-20260804-v4/analysis.json,10aba0798dea28b7803318c912e310aae385b0e7e8886e2f1bf533dc37f0a74c,sqrt(integral common-fluid |q_case-q_in|^2 dA / integral common-fluid dA)/U_inf,direct q_case-q_in; trapezoid L2 and pointwise Linf
|
||||||
|
cfd-stationary,stationary CFD (unsteady reference),CFD,s,,0.6134001663228681,10.0,0.24380732053243648,0.17501790659901814,0.30012210370920933,0.89350326359272,0.5572226829826832,1.0456532677804014,1.1928564310073853,7.971104932948947,/home/frank14f/optane/DynamisLab/steady_pinball_theory/authoritative/steady-contract-v5-torque-d20-20260808/stationary/endpoint.npz,c2edc9e4173fb6bbde7937314e4984c1c501de75f0dd4916b67aa68388dde8f3,sqrt(integral common-fluid |q_case-q_in|^2 dA / integral common-fluid dA)/U_inf,direct q_case-q_in; trapezoid L2 and pointwise Linf
|
||||||
|
cfd-accepted-s3p55,"accepted CFD, s=3.55",CFD,s,3.55,0.629542973386836,10.0,0.010552478859473947,0.011214912363477216,0.015398995734111074,0.022375281900167465,0.016958430933300406,0.0231672176752314,4.6573591232299805,11.91890798509121,/home/frank14f/optane/DynamisLab/steady_pinball_theory/authoritative/steady-contract-v5-torque-d20-20260808/accepted-s3.55/endpoint.npz,b6c70b76c7cf2a2e4330b27fb0d4e82a914b40fde8171076704e825a79ac72db,sqrt(integral common-fluid |q_case-q_in|^2 dA / integral common-fluid dA)/U_inf,direct q_case-q_in; trapezoid L2 and pointwise Linf
|
||||||
|
cfd-reverse-s5p2,"stable reverse CFD, s=5.2",CFD,s,5.2,0.7716071742597629,10.0,0.26540908803640445,0.014576513091228843,0.26580906452931813,0.6898418292403221,0.022048578830435872,0.6898418411784105,5.7240705490112305,28.39008765295148,/home/frank14f/optane/DynamisLab/steady_pinball_theory/authoritative/steady-contract-v5-torque-d20-20260808/reverse-s5.2/endpoint.npz,760c1e84dd9b7784151ed180d3f16d17619465a08ddb767ce23a87517cc28ec3,sqrt(integral common-fluid |q_case-q_in|^2 dA / integral common-fluid dA)/U_inf,direct q_case-q_in; trapezoid L2 and pointwise Linf
|
||||||
|
|
After Width: | Height: | Size: 3.6 KiB |
|
After Width: | Height: | Size: 4.4 KiB |
|
After Width: | Height: | Size: 3.9 KiB |
|
After Width: | Height: | Size: 3.4 KiB |
|
After Width: | Height: | Size: 2.8 KiB |
|
After Width: | Height: | Size: 3.7 KiB |
|
After Width: | Height: | Size: 29 KiB |
|
After Width: | Height: | Size: 28 KiB |
|
After Width: | Height: | Size: 35 KiB |
|
After Width: | Height: | Size: 28 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 20 KiB |
|
After Width: | Height: | Size: 5.1 KiB |
|
After Width: | Height: | Size: 6.2 KiB |
|
After Width: | Height: | Size: 5.6 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
@@ -0,0 +1,627 @@
|
|||||||
|
{
|
||||||
|
"cases": [
|
||||||
|
{
|
||||||
|
"case": "mfs-zero",
|
||||||
|
"error_scale_family": "potential",
|
||||||
|
"family": "potential",
|
||||||
|
"label": "potential MFS, zero circulation",
|
||||||
|
"parameter": 0.0,
|
||||||
|
"parameter_name": "g",
|
||||||
|
"source_sha256": "10aba0798dea28b7803318c912e310aae385b0e7e8886e2f1bf533dc37f0a74c"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"case": "mfs-gopt",
|
||||||
|
"error_scale_family": "potential",
|
||||||
|
"family": "potential",
|
||||||
|
"label": "potential favorable g_opt",
|
||||||
|
"parameter": 6.1479442395622,
|
||||||
|
"parameter_name": "g",
|
||||||
|
"source_sha256": "10aba0798dea28b7803318c912e310aae385b0e7e8886e2f1bf533dc37f0a74c"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"case": "mfs-gfit",
|
||||||
|
"error_scale_family": "potential",
|
||||||
|
"family": "potential",
|
||||||
|
"label": "potential finite-Re fitted g",
|
||||||
|
"parameter": -12.200554143243473,
|
||||||
|
"parameter_name": "g",
|
||||||
|
"source_sha256": "10aba0798dea28b7803318c912e310aae385b0e7e8886e2f1bf533dc37f0a74c"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"case": "cfd-stationary",
|
||||||
|
"error_scale_family": "CFD",
|
||||||
|
"family": "CFD",
|
||||||
|
"label": "stationary CFD (unsteady reference)",
|
||||||
|
"parameter": null,
|
||||||
|
"parameter_name": "s",
|
||||||
|
"source_sha256": "c2edc9e4173fb6bbde7937314e4984c1c501de75f0dd4916b67aa68388dde8f3"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"case": "cfd-accepted-s3p55",
|
||||||
|
"error_scale_family": "CFD",
|
||||||
|
"family": "CFD",
|
||||||
|
"label": "accepted CFD, s=3.55",
|
||||||
|
"parameter": 3.55,
|
||||||
|
"parameter_name": "s",
|
||||||
|
"source_sha256": "b6c70b76c7cf2a2e4330b27fb0d4e82a914b40fde8171076704e825a79ac72db"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"case": "cfd-reverse-s5p2",
|
||||||
|
"error_scale_family": "CFD",
|
||||||
|
"family": "CFD",
|
||||||
|
"label": "stable reverse CFD, s=5.2",
|
||||||
|
"parameter": 5.2,
|
||||||
|
"parameter_name": "s",
|
||||||
|
"source_sha256": "760c1e84dd9b7784151ed180d3f16d17619465a08ddb767ce23a87517cc28ec3"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"colormaps": {
|
||||||
|
"error": {
|
||||||
|
"bins": 12,
|
||||||
|
"clipping": "end bins; raw values retained",
|
||||||
|
"comparison_warning": "grayscale values compare only within the named family scale",
|
||||||
|
"family_scales": {
|
||||||
|
"CFD": {
|
||||||
|
"colorbars": [
|
||||||
|
"colorbars/error_cfd_horizontal.png",
|
||||||
|
"colorbars/error_cfd_vertical.png"
|
||||||
|
],
|
||||||
|
"edges": [
|
||||||
|
0.0,
|
||||||
|
0.12005821863810222,
|
||||||
|
0.24011643727620444,
|
||||||
|
0.36017465591430664,
|
||||||
|
0.4802328745524089,
|
||||||
|
0.6002910931905111,
|
||||||
|
0.7203493118286133,
|
||||||
|
0.8404075304667156,
|
||||||
|
0.9604657491048177,
|
||||||
|
1.08052396774292,
|
||||||
|
1.2005821863810222,
|
||||||
|
1.3206404050191245,
|
||||||
|
1.4406986236572266
|
||||||
|
],
|
||||||
|
"limit_statistic": "family-pooled 97.5 percentile",
|
||||||
|
"limits": [
|
||||||
|
0,
|
||||||
|
1.4406986236572266
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"potential": {
|
||||||
|
"colorbars": [
|
||||||
|
"colorbars/error_potential_horizontal.png",
|
||||||
|
"colorbars/error_potential_vertical.png"
|
||||||
|
],
|
||||||
|
"edges": [
|
||||||
|
0.0,
|
||||||
|
0.09432738918981097,
|
||||||
|
0.18865477837962194,
|
||||||
|
0.2829821675694329,
|
||||||
|
0.3773095567592439,
|
||||||
|
0.47163694594905486,
|
||||||
|
0.5659643351388658,
|
||||||
|
0.6602917243286768,
|
||||||
|
0.7546191135184878,
|
||||||
|
0.8489465027082987,
|
||||||
|
0.9432738918981097,
|
||||||
|
1.0376012810879207,
|
||||||
|
1.1319286702777316
|
||||||
|
],
|
||||||
|
"limit_statistic": "family-pooled 97.5 percentile",
|
||||||
|
"limits": [
|
||||||
|
0,
|
||||||
|
1.1319286702777316
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"name": "Greys"
|
||||||
|
},
|
||||||
|
"vorticity": {
|
||||||
|
"bins": 21,
|
||||||
|
"center_bin": "white",
|
||||||
|
"clipping": "end bins; raw values retained",
|
||||||
|
"edges": [
|
||||||
|
-2.374693971069057,
|
||||||
|
-2.1485326404910516,
|
||||||
|
-1.922371309913046,
|
||||||
|
-1.6962099793350407,
|
||||||
|
-1.4700486487570352,
|
||||||
|
-1.2438873181790298,
|
||||||
|
-1.0177259876010245,
|
||||||
|
-0.791564657023019,
|
||||||
|
-0.5654033264450136,
|
||||||
|
-0.3392419958670083,
|
||||||
|
-0.11308066528900262,
|
||||||
|
0.11308066528900262,
|
||||||
|
0.3392419958670079,
|
||||||
|
0.5654033264450136,
|
||||||
|
0.7915646570230188,
|
||||||
|
1.0177259876010245,
|
||||||
|
1.2438873181790298,
|
||||||
|
1.470048648757035,
|
||||||
|
1.6962099793350403,
|
||||||
|
1.9223713099130464,
|
||||||
|
2.1485326404910516,
|
||||||
|
2.374693971069057
|
||||||
|
],
|
||||||
|
"limit_statistic": "CFD-only pooled absolute 97.5 percentile",
|
||||||
|
"limits": [
|
||||||
|
-2.374693971069057,
|
||||||
|
2.374693971069057
|
||||||
|
],
|
||||||
|
"name": "RdBu_r",
|
||||||
|
"potential_value": "analytical irrotational zero on valid exterior",
|
||||||
|
"zero_contour": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"crop": {
|
||||||
|
"array_order": "yx",
|
||||||
|
"pixel_dimensions_xy": [
|
||||||
|
241,
|
||||||
|
121
|
||||||
|
],
|
||||||
|
"requested_x_over_D": [
|
||||||
|
-2,
|
||||||
|
10
|
||||||
|
],
|
||||||
|
"requested_y_over_D": [
|
||||||
|
-3,
|
||||||
|
3
|
||||||
|
],
|
||||||
|
"x_indices_inclusive": [
|
||||||
|
960,
|
||||||
|
1200
|
||||||
|
],
|
||||||
|
"x_lattice": [
|
||||||
|
960.0,
|
||||||
|
1200.0
|
||||||
|
],
|
||||||
|
"x_over_D": [
|
||||||
|
-2.0,
|
||||||
|
10.0
|
||||||
|
],
|
||||||
|
"y_indices_inclusive": [
|
||||||
|
239,
|
||||||
|
359
|
||||||
|
],
|
||||||
|
"y_lattice": [
|
||||||
|
239.0,
|
||||||
|
359.0
|
||||||
|
],
|
||||||
|
"y_over_D": [
|
||||||
|
-3.025,
|
||||||
|
2.975
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"definitions": {
|
||||||
|
"crop_L2": "sqrt(integral common-fluid |q_case-q_in|^2 dA / integral common-fluid dA)/U_inf; tensor trapezoid weights",
|
||||||
|
"error": "hypot(ux-qin_ux,uy-qin_uy)/U_inf",
|
||||||
|
"vorticity": "potential analytical zero; CFD conservative omega D/U_inf"
|
||||||
|
},
|
||||||
|
"exact_command": "PYTHONPATH=\"$PWD/src\" conda run -n pinball_math python src/steady_pinball_theory/plot_results.py --steady \"/home/frank14f/optane/DynamisLab/steady_pinball_theory/authoritative/steady-contract-v5-torque-d20-20260808\" --strip \"/home/frank14f/optane/DynamisLab/steady_pinball_theory/authoritative/steady-strip-modal-analysis-20260804-v4/analysis.json\" --ledger \"/home/frank14f/optane/DynamisLab/steady_pinball_theory/authoritative/ns-ledger-v2-oracle-bound-d20-20260809/analysis.json\" --output \"/home/frank14f/DynamisLab/src/steady_pinball_theory/figures\"",
|
||||||
|
"files": {
|
||||||
|
"README.md": {
|
||||||
|
"bytes": 6431,
|
||||||
|
"sha256": "871fb4bfdb64d4f1d32ee4e0aa033441a19e56e911e3e3b5a3efca3b4e19d2a1"
|
||||||
|
},
|
||||||
|
"annotations/cfd-accepted-s3p55_metrics.png": {
|
||||||
|
"bytes": 20146,
|
||||||
|
"sha256": "492662832f9112adfc6e3d8e480499c3c57935eae2487e0ae0a6da7d6503e662"
|
||||||
|
},
|
||||||
|
"annotations/cfd-reverse-s5p2_metrics.png": {
|
||||||
|
"bytes": 20295,
|
||||||
|
"sha256": "bf7306a40657a547d47dd228290d712a1a20b37a1f1295d65ef46a88c739db18"
|
||||||
|
},
|
||||||
|
"annotations/cfd-stationary_metrics.png": {
|
||||||
|
"bytes": 21099,
|
||||||
|
"sha256": "0e20045477c5a4e0e0d03566c7ee423d273c6a26edcc1c3726b2cb791ee17775"
|
||||||
|
},
|
||||||
|
"annotations/mfs-gfit_metrics.png": {
|
||||||
|
"bytes": 20444,
|
||||||
|
"sha256": "c81253c5dee3350b8a8f92be2389f5a98bae8488fc31d5c49455eb5f73361e95"
|
||||||
|
},
|
||||||
|
"annotations/mfs-gopt_metrics.png": {
|
||||||
|
"bytes": 19768,
|
||||||
|
"sha256": "2ddf633bf39fe2f1614a490aa0f9622762b9b99a13f7270b29e0dc8a47be18a0"
|
||||||
|
},
|
||||||
|
"annotations/mfs-zero_metrics.png": {
|
||||||
|
"bytes": 21604,
|
||||||
|
"sha256": "c5c01e24ee841278b6d7beedb7f7ecd02bb318c3af72b4f10af924e121a10730"
|
||||||
|
},
|
||||||
|
"colorbars/error_cfd_horizontal.png": {
|
||||||
|
"bytes": 12585,
|
||||||
|
"sha256": "ef42c0dabed479db73bf1b916820f4586d60372e46fe176d607e132e489c9913"
|
||||||
|
},
|
||||||
|
"colorbars/error_cfd_vertical.png": {
|
||||||
|
"bytes": 16395,
|
||||||
|
"sha256": "345169fe247b884d04ec6c4d5cb563869e9ea42162f4b20253e482b46bfdf993"
|
||||||
|
},
|
||||||
|
"colorbars/error_potential_horizontal.png": {
|
||||||
|
"bytes": 14035,
|
||||||
|
"sha256": "fe900bd3709f262bb60d8fae5fdade3796399313d43790af1f11d4f4b25cf531"
|
||||||
|
},
|
||||||
|
"colorbars/error_potential_vertical.png": {
|
||||||
|
"bytes": 17925,
|
||||||
|
"sha256": "a08d99f5c333987452cbe1dbbcfb3ba1adaa8777cd172a0c55892e34056fc79d"
|
||||||
|
},
|
||||||
|
"colorbars/vorticity_cfd_horizontal.png": {
|
||||||
|
"bytes": 13289,
|
||||||
|
"sha256": "f1009e127fad3e695903d4904eb3906c5672dc80bd8cbe8471bf9408fbba940a"
|
||||||
|
},
|
||||||
|
"colorbars/vorticity_cfd_vertical.png": {
|
||||||
|
"bytes": 17213,
|
||||||
|
"sha256": "f88e5c9e83d5b50da2c7ffa3e8bd0111ad6f2f2f087e409e1fff7ed30f28930d"
|
||||||
|
},
|
||||||
|
"field_metrics.csv": {
|
||||||
|
"bytes": 3618,
|
||||||
|
"sha256": "e6cc9887c6ce8e49fb2d24a1bf9e32236aa93fc2f5f8d9b50bd18207cd247925"
|
||||||
|
},
|
||||||
|
"fields/error/cfd-accepted-s3p55.png": {
|
||||||
|
"bytes": 3695,
|
||||||
|
"sha256": "728a9c02f63700d7ab79f5d721ba7504c469935dccc2ff8b01ba77c12be9f9a0"
|
||||||
|
},
|
||||||
|
"fields/error/cfd-reverse-s5p2.png": {
|
||||||
|
"bytes": 4469,
|
||||||
|
"sha256": "cf2d0fb790931bb3c250eb45430f7754cccde506979cec79645f99c6e62d6b39"
|
||||||
|
},
|
||||||
|
"fields/error/cfd-stationary.png": {
|
||||||
|
"bytes": 3949,
|
||||||
|
"sha256": "5d46db4ba779235edb09bf03a57484c3cb85df229d2d2a090126d99f6dbde5eb"
|
||||||
|
},
|
||||||
|
"fields/error/mfs-gfit.png": {
|
||||||
|
"bytes": 3447,
|
||||||
|
"sha256": "57f7107d1a00de376ff501656681b45243cdd15db5fc392cffd30ecf2b4cf0d4"
|
||||||
|
},
|
||||||
|
"fields/error/mfs-gopt.png": {
|
||||||
|
"bytes": 2817,
|
||||||
|
"sha256": "f4fc08e16a01df92c8a7d3d39748f502665d1551e8b81788c957b3aa6fa6b957"
|
||||||
|
},
|
||||||
|
"fields/error/mfs-zero.png": {
|
||||||
|
"bytes": 3777,
|
||||||
|
"sha256": "826bcd60a590064c54063037ae83136b68330644e526281d7e5f8bd591c26e03"
|
||||||
|
},
|
||||||
|
"fields/streamlines/cfd-accepted-s3p55.png": {
|
||||||
|
"bytes": 29284,
|
||||||
|
"sha256": "71ffdeaa6919f919cd1de6bb399574d7b3a70cf731c90fec7b2812e501d6de18"
|
||||||
|
},
|
||||||
|
"fields/streamlines/cfd-reverse-s5p2.png": {
|
||||||
|
"bytes": 28465,
|
||||||
|
"sha256": "122d09145b2e8969b19d9696e1249e1d2aa8fabddbba2ca55a2f16f2f5483bf3"
|
||||||
|
},
|
||||||
|
"fields/streamlines/cfd-stationary.png": {
|
||||||
|
"bytes": 35885,
|
||||||
|
"sha256": "5ce2032e28dc66765049051694a934e63d03aacf4a03c6e70700c4692efd9d39"
|
||||||
|
},
|
||||||
|
"fields/streamlines/mfs-gfit.png": {
|
||||||
|
"bytes": 29049,
|
||||||
|
"sha256": "297acdf718cfef9f796025a4297b75acdc00a1a1ce860acb690774fba6dbea95"
|
||||||
|
},
|
||||||
|
"fields/streamlines/mfs-gopt.png": {
|
||||||
|
"bytes": 14647,
|
||||||
|
"sha256": "bcdfe160646da2cd58ed51979c01a1b42b0dbcaece9d4d5e81b339b599b5ea03"
|
||||||
|
},
|
||||||
|
"fields/streamlines/mfs-zero.png": {
|
||||||
|
"bytes": 20950,
|
||||||
|
"sha256": "2a74f7003e5f02275e1fb09fac172244010d7c8bf496bc0fadd09544e448db85"
|
||||||
|
},
|
||||||
|
"fields/vorticity/cfd-accepted-s3p55.png": {
|
||||||
|
"bytes": 5221,
|
||||||
|
"sha256": "b50c659bff982186292076249a0d1295172d05af64b29bc9544f400e0782323e"
|
||||||
|
},
|
||||||
|
"fields/vorticity/cfd-reverse-s5p2.png": {
|
||||||
|
"bytes": 6395,
|
||||||
|
"sha256": "1a8020af924a24525dbf4c1880326da3c8685fafe9e27b2dcfa54d406e1484c7"
|
||||||
|
},
|
||||||
|
"fields/vorticity/cfd-stationary.png": {
|
||||||
|
"bytes": 5777,
|
||||||
|
"sha256": "f0b2bf9240548786b46a55ce7ae7dfc7cf65f357dc2c7c1e0b5e4ae5896c4c1e"
|
||||||
|
},
|
||||||
|
"fields/vorticity/mfs-gfit.png": {
|
||||||
|
"bytes": 1415,
|
||||||
|
"sha256": "7c92e631aa2ddc3735d9c0b3834df8e4eac0d7ec3ecd940ff43381e9427d4648"
|
||||||
|
},
|
||||||
|
"fields/vorticity/mfs-gopt.png": {
|
||||||
|
"bytes": 1415,
|
||||||
|
"sha256": "7c92e631aa2ddc3735d9c0b3834df8e4eac0d7ec3ecd940ff43381e9427d4648"
|
||||||
|
},
|
||||||
|
"fields/vorticity/mfs-zero.png": {
|
||||||
|
"bytes": 1415,
|
||||||
|
"sha256": "7c92e631aa2ddc3735d9c0b3834df8e4eac0d7ec3ecd940ff43381e9427d4648"
|
||||||
|
},
|
||||||
|
"plates/error/cfd-accepted-s3p55.pdf": {
|
||||||
|
"bytes": 24616,
|
||||||
|
"sha256": "3ad03ccaf7a64c248ac0449aba67e7872986e8bdb2995cc0a205e2ae200a77b4"
|
||||||
|
},
|
||||||
|
"plates/error/cfd-accepted-s3p55.png": {
|
||||||
|
"bytes": 31628,
|
||||||
|
"sha256": "4e860a0c6a71a62e4f310d101b4ecf40c29d06a245d217e465cc288733dca5bb"
|
||||||
|
},
|
||||||
|
"plates/error/cfd-reverse-s5p2.pdf": {
|
||||||
|
"bytes": 24116,
|
||||||
|
"sha256": "56b34cf9d344ec0384a1b30872c3293c25d1d36a58684f22d2d31167e79f912b"
|
||||||
|
},
|
||||||
|
"plates/error/cfd-reverse-s5p2.png": {
|
||||||
|
"bytes": 32506,
|
||||||
|
"sha256": "03d3c4ecbb5bdeb0ee32cc036cf1e026cc1cf6e4e7b42b306699f44f8f8efd1e"
|
||||||
|
},
|
||||||
|
"plates/error/cfd-stationary.pdf": {
|
||||||
|
"bytes": 27605,
|
||||||
|
"sha256": "9ce97806eba91314dd6182fa673bcdfe5212ecac275dae41d3f35c78f6c389f1"
|
||||||
|
},
|
||||||
|
"plates/error/cfd-stationary.png": {
|
||||||
|
"bytes": 33724,
|
||||||
|
"sha256": "36951582b551a4b249aae524c75cea476467dd603fcf72e0adb01d2bab01777f"
|
||||||
|
},
|
||||||
|
"plates/error/mfs-gfit.pdf": {
|
||||||
|
"bytes": 26843,
|
||||||
|
"sha256": "87ba7343f34fb81e51e2ecbce4684a31638775e5ddaf067d3605a17b76268263"
|
||||||
|
},
|
||||||
|
"plates/error/mfs-gfit.png": {
|
||||||
|
"bytes": 32098,
|
||||||
|
"sha256": "0cf8844de77ba156e88389e4a5a47aa54e02b2d467ef950efc3cc70a5b3a5fdd"
|
||||||
|
},
|
||||||
|
"plates/error/mfs-gopt.pdf": {
|
||||||
|
"bytes": 18694,
|
||||||
|
"sha256": "8d5be07a64a6f341a9f73d601273ec9952b370689790f9fe46be359da14c9df3"
|
||||||
|
},
|
||||||
|
"plates/error/mfs-gopt.png": {
|
||||||
|
"bytes": 27328,
|
||||||
|
"sha256": "9ced96e5ddd772158e1851448cae81ddd79d5f78d7ef258d0ff26e71d199ef08"
|
||||||
|
},
|
||||||
|
"plates/error/mfs-zero.pdf": {
|
||||||
|
"bytes": 22727,
|
||||||
|
"sha256": "23488c6a638486150979ca07207034ec409bd65083e44b8b8adb51c78f2168ad"
|
||||||
|
},
|
||||||
|
"plates/error/mfs-zero.png": {
|
||||||
|
"bytes": 32513,
|
||||||
|
"sha256": "35dee33bdcd5834a26f31b78d2694932895d6df786912105c139f1993241fbc5"
|
||||||
|
},
|
||||||
|
"plates/streamlines/cfd-accepted-s3p55.pdf": {
|
||||||
|
"bytes": 45687,
|
||||||
|
"sha256": "813bb42a214e7ca016f966f8c64f885d3eff147aea0610baab8f88211de09580"
|
||||||
|
},
|
||||||
|
"plates/streamlines/cfd-accepted-s3p55.png": {
|
||||||
|
"bytes": 72423,
|
||||||
|
"sha256": "ddb8a828ac03a54f71f2d2070842f7148286a5a7aff592a3cab2d493e0246e81"
|
||||||
|
},
|
||||||
|
"plates/streamlines/cfd-reverse-s5p2.pdf": {
|
||||||
|
"bytes": 46836,
|
||||||
|
"sha256": "1f72992477419ccd8ae309e506be6281bbaaa9a967ff9a804e496f1a8e3aa336"
|
||||||
|
},
|
||||||
|
"plates/streamlines/cfd-reverse-s5p2.png": {
|
||||||
|
"bytes": 72542,
|
||||||
|
"sha256": "8fa3ce57de0f557cc438cdfcc3b2b7a1858722192f219d952ae38b87297c4b14"
|
||||||
|
},
|
||||||
|
"plates/streamlines/cfd-stationary.pdf": {
|
||||||
|
"bytes": 48248,
|
||||||
|
"sha256": "93082d4edc37cdb200e14c93a27e661446efec30c9b0d1831aaf7ac29303e84b"
|
||||||
|
},
|
||||||
|
"plates/streamlines/cfd-stationary.png": {
|
||||||
|
"bytes": 81979,
|
||||||
|
"sha256": "63b05c00da8bd40d906ecf2b8c2ecbe102e64cd526c1c0ef34863b01b6927491"
|
||||||
|
},
|
||||||
|
"plates/streamlines/mfs-gfit.pdf": {
|
||||||
|
"bytes": 46977,
|
||||||
|
"sha256": "0efd1fef52ae431c3ac1e3730c2e68085765a724ff296f8c691dffc6bfcb641a"
|
||||||
|
},
|
||||||
|
"plates/streamlines/mfs-gfit.png": {
|
||||||
|
"bytes": 72888,
|
||||||
|
"sha256": "e3c3d78e7c6a5e075f1b8c070d79b1bf254551a6be05139c61d2411461b6fe08"
|
||||||
|
},
|
||||||
|
"plates/streamlines/mfs-gopt.pdf": {
|
||||||
|
"bytes": 38152,
|
||||||
|
"sha256": "53fcbaa41222c7a3bf432bcf403579f7fb3c3db2b6867ec2472cd2235f040a73"
|
||||||
|
},
|
||||||
|
"plates/streamlines/mfs-gopt.png": {
|
||||||
|
"bytes": 52085,
|
||||||
|
"sha256": "5e509d63dcea5194cf2d10bede9b2da3fbf3aad73b55473adeafa5c16f4e0597"
|
||||||
|
},
|
||||||
|
"plates/streamlines/mfs-zero.pdf": {
|
||||||
|
"bytes": 43532,
|
||||||
|
"sha256": "5c4a8225ea43ec43ba3b035d3e79cba3be3cd4d4a9dd5f051261370ab42b0d0e"
|
||||||
|
},
|
||||||
|
"plates/streamlines/mfs-zero.png": {
|
||||||
|
"bytes": 63887,
|
||||||
|
"sha256": "82079ff0f593e5dc199246003d72c7090e2268fe3c7e0c6d21d7afd324c03275"
|
||||||
|
},
|
||||||
|
"plates/vorticity/cfd-accepted-s3p55.pdf": {
|
||||||
|
"bytes": 20103,
|
||||||
|
"sha256": "52eb760e9a0af5914ef98684ae9199a76acaaf8d73a7b56147bc44293ef15fc7"
|
||||||
|
},
|
||||||
|
"plates/vorticity/cfd-accepted-s3p55.png": {
|
||||||
|
"bytes": 32691,
|
||||||
|
"sha256": "e88011569775f3a51002a234f680156adb9dc1e5a7e06694d43aacc246a1837b"
|
||||||
|
},
|
||||||
|
"plates/vorticity/cfd-reverse-s5p2.pdf": {
|
||||||
|
"bytes": 25159,
|
||||||
|
"sha256": "5004f1600bc6078786b0b665b8991bfaa520f14c6cb21382cab63d892dd4413d"
|
||||||
|
},
|
||||||
|
"plates/vorticity/cfd-reverse-s5p2.png": {
|
||||||
|
"bytes": 35323,
|
||||||
|
"sha256": "67399eb89a680b87193e5e5af7ec8e582d0a02eb09d7c7176dc478279257944e"
|
||||||
|
},
|
||||||
|
"plates/vorticity/cfd-stationary.pdf": {
|
||||||
|
"bytes": 28538,
|
||||||
|
"sha256": "6daa0e38976c35f94de273ee2f4e92eb064e4ac5a874c058765eec496cb6a750"
|
||||||
|
},
|
||||||
|
"plates/vorticity/cfd-stationary.png": {
|
||||||
|
"bytes": 35876,
|
||||||
|
"sha256": "1d9256f1d86158ec9568401ddbf8640c39ecb5941e39a39c703c222318eaaa59"
|
||||||
|
},
|
||||||
|
"plates/vorticity/mfs-gfit.pdf": {
|
||||||
|
"bytes": 17815,
|
||||||
|
"sha256": "3f0e31f0ebdbd24644ab5ed83ea4c24c7c76eb87db2c446af6178ead275bdf49"
|
||||||
|
},
|
||||||
|
"plates/vorticity/mfs-gfit.png": {
|
||||||
|
"bytes": 27385,
|
||||||
|
"sha256": "e2bdaeef82d5bdbcfe84b397d34c2b9773d3e23438d6071b72ee39a29017dc09"
|
||||||
|
},
|
||||||
|
"plates/vorticity/mfs-gopt.pdf": {
|
||||||
|
"bytes": 17006,
|
||||||
|
"sha256": "d9793820362af7a91e0bd6a1a5134b5723ef4c353bcfc8e247b30cfcaa25f66e"
|
||||||
|
},
|
||||||
|
"plates/vorticity/mfs-gopt.png": {
|
||||||
|
"bytes": 25171,
|
||||||
|
"sha256": "345462f05d296d917a6bf455fcea96a7d52add1c77e4705e81617be2fb6dea0a"
|
||||||
|
},
|
||||||
|
"plates/vorticity/mfs-zero.pdf": {
|
||||||
|
"bytes": 17124,
|
||||||
|
"sha256": "3a2f99ebc6de49c4e6e61500b66bc53096d59e4693ed72c804415f7181984c08"
|
||||||
|
},
|
||||||
|
"plates/vorticity/mfs-zero.png": {
|
||||||
|
"bytes": 28344,
|
||||||
|
"sha256": "0379dc27497ba014cc5c3000aae3e615c1aba9dec1c3747e56c98363fc3fbc33"
|
||||||
|
},
|
||||||
|
"profiles_long.csv": {
|
||||||
|
"bytes": 3104648,
|
||||||
|
"sha256": "dc0597bd93dafa57cbed80aa39ed4df8f396c092fa51a6865bb4580627f0980a"
|
||||||
|
},
|
||||||
|
"summary/actuator_CP.csv": {
|
||||||
|
"bytes": 276,
|
||||||
|
"sha256": "893ab88122be018a529a3ece0f0633385f5f8b74534e46e01bd248416f677981"
|
||||||
|
},
|
||||||
|
"summary/actuator_CP.pdf": {
|
||||||
|
"bytes": 15186,
|
||||||
|
"sha256": "64720f93a8867e2fcc2e57f93b24c9bb4bebfba3881bc35ef4007f1d41a7314d"
|
||||||
|
},
|
||||||
|
"summary/actuator_CP.png": {
|
||||||
|
"bytes": 27092,
|
||||||
|
"sha256": "a12db1cf4e20232c1c48c7cc578f4d3c9d667e9f1d4ef78dec184df0d7058a1b"
|
||||||
|
},
|
||||||
|
"summary/near_wake_diagnostics.csv": {
|
||||||
|
"bytes": 530,
|
||||||
|
"sha256": "2b05967391e4e942ad0b139d48e0e5c4e54bf1beff367475585f4a6b56921220"
|
||||||
|
},
|
||||||
|
"summary/near_wake_diagnostics.pdf": {
|
||||||
|
"bytes": 21918,
|
||||||
|
"sha256": "876b634f5c2c78edfb479e2f3e5ede4f41aaa62e7dbc800e7f0587c242545c47"
|
||||||
|
},
|
||||||
|
"summary/near_wake_diagnostics.png": {
|
||||||
|
"bytes": 59480,
|
||||||
|
"sha256": "5a02165dbcdb990eab9e9f82f59c34871ffa39f504b33f951b474570259af962"
|
||||||
|
},
|
||||||
|
"summary/profile_Einf_vector.csv": {
|
||||||
|
"bytes": 3644,
|
||||||
|
"sha256": "643720d093a49398b1d76277d5d89a777c95eb17b4f3e97f4e255170ebdea5d2"
|
||||||
|
},
|
||||||
|
"summary/profile_Einf_vector.pdf": {
|
||||||
|
"bytes": 29062,
|
||||||
|
"sha256": "95fe5497a7de3fa3a291a498b6829e46a1e295f53769800bedc34f3a1049849c"
|
||||||
|
},
|
||||||
|
"summary/profile_Einf_vector.png": {
|
||||||
|
"bytes": 150363,
|
||||||
|
"sha256": "12fe1537a24f4bb008f14a2c7629bf40c15f61f8627dafcdef542b032760b9ad"
|
||||||
|
},
|
||||||
|
"summary/signed_gap_Qstar.csv": {
|
||||||
|
"bytes": 1487,
|
||||||
|
"sha256": "3ae80d31586c049185e013739953d83718e6b68c9f0206aaf2e3ceb348c9fb98"
|
||||||
|
},
|
||||||
|
"summary/signed_gap_Qstar.pdf": {
|
||||||
|
"bytes": 16007,
|
||||||
|
"sha256": "d1a9be901077d3f2faf838597a47c6a3a0844bb1614f872f55025483a5c21659"
|
||||||
|
},
|
||||||
|
"summary/signed_gap_Qstar.png": {
|
||||||
|
"bytes": 54153,
|
||||||
|
"sha256": "ef4359825116d8e601c5cd7c2ab39d2f62f5684ac7928ba070157c78c2ea1068"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"geometry": {
|
||||||
|
"body_order": [
|
||||||
|
"front",
|
||||||
|
"rear_y_plus",
|
||||||
|
"rear_y_minus"
|
||||||
|
],
|
||||||
|
"centers_over_D": [
|
||||||
|
[
|
||||||
|
0.0,
|
||||||
|
0.0
|
||||||
|
],
|
||||||
|
[
|
||||||
|
1.3,
|
||||||
|
0.75
|
||||||
|
],
|
||||||
|
[
|
||||||
|
1.3,
|
||||||
|
-0.75
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"coordinates": "x-right_y-up",
|
||||||
|
"origin": "front center",
|
||||||
|
"radius_over_D": 0.5
|
||||||
|
},
|
||||||
|
"masks": {
|
||||||
|
"CFD": "case/q_in common solver FLUID bits; conservative centered curl stencil",
|
||||||
|
"potential": "analytic cylinder exterior; displayed/exported vorticity analytically zero"
|
||||||
|
},
|
||||||
|
"overwrite_contract": "default fail; explicit --overwrite atomically replaces only output root",
|
||||||
|
"payload_sha256": "425fd18756b853cb3d5309900d70a5c8a0b922701fe2decb78040811943ca367",
|
||||||
|
"primary_identity": {
|
||||||
|
"E_inf_vector": 0.0231672176752314,
|
||||||
|
"case": "cfd-accepted-s3p55",
|
||||||
|
"x_over_D": 10
|
||||||
|
},
|
||||||
|
"render": {
|
||||||
|
"one_output_pixel_per_lattice_cell": true,
|
||||||
|
"plates": "presentation companions; not pixel-exact raw arrays",
|
||||||
|
"raw_axes_titles": false,
|
||||||
|
"scalar_interpolation": "nearest",
|
||||||
|
"streamline_arrowheads": true
|
||||||
|
},
|
||||||
|
"schema": "steady-pinball-unified-figures/v2",
|
||||||
|
"scientific_scope": "CPU-only regenerable visualization; no CFD/CUDA; not evidence",
|
||||||
|
"sources": {
|
||||||
|
"ledger": {
|
||||||
|
"path": "/home/frank14f/optane/DynamisLab/steady_pinball_theory/authoritative/ns-ledger-v2-oracle-bound-d20-20260809/analysis.json",
|
||||||
|
"sha256": "c5d1395dd438e0f9c1f98489fd94fe309349ee554d0a21752ddfa571fe4d66cb"
|
||||||
|
},
|
||||||
|
"steady_root": {
|
||||||
|
"files": {
|
||||||
|
"accepted-s3.55/endpoint.npz": {
|
||||||
|
"bytes": 7493746,
|
||||||
|
"path": "/home/frank14f/optane/DynamisLab/steady_pinball_theory/authoritative/steady-contract-v5-torque-d20-20260808/accepted-s3.55/endpoint.npz",
|
||||||
|
"sha256": "b6c70b76c7cf2a2e4330b27fb0d4e82a914b40fde8171076704e825a79ac72db"
|
||||||
|
},
|
||||||
|
"accepted-s3.55/manifest.json": {
|
||||||
|
"bytes": 7701,
|
||||||
|
"path": "/home/frank14f/optane/DynamisLab/steady_pinball_theory/authoritative/steady-contract-v5-torque-d20-20260808/accepted-s3.55/manifest.json",
|
||||||
|
"sha256": "0fef464f8acc445a772db9792d8bb751f5f7a1502b4bf4f545833c29b861eeb1"
|
||||||
|
},
|
||||||
|
"qin/endpoint.npz": {
|
||||||
|
"bytes": 4783748,
|
||||||
|
"path": "/home/frank14f/optane/DynamisLab/steady_pinball_theory/authoritative/steady-contract-v5-torque-d20-20260808/qin/endpoint.npz",
|
||||||
|
"sha256": "88cbeac1069336716c30d18fd5afd4fc9d6ae87617c7f390b1366fe7098e419b"
|
||||||
|
},
|
||||||
|
"qin/manifest.json": {
|
||||||
|
"bytes": 6203,
|
||||||
|
"path": "/home/frank14f/optane/DynamisLab/steady_pinball_theory/authoritative/steady-contract-v5-torque-d20-20260808/qin/manifest.json",
|
||||||
|
"sha256": "e68dc9c2ac5f64f3db024c1c1b0ff729d3403781c2ca6a2cae6740f060ce14ff"
|
||||||
|
},
|
||||||
|
"reverse-s5.2/endpoint.npz": {
|
||||||
|
"bytes": 8376367,
|
||||||
|
"path": "/home/frank14f/optane/DynamisLab/steady_pinball_theory/authoritative/steady-contract-v5-torque-d20-20260808/reverse-s5.2/endpoint.npz",
|
||||||
|
"sha256": "760c1e84dd9b7784151ed180d3f16d17619465a08ddb767ce23a87517cc28ec3"
|
||||||
|
},
|
||||||
|
"reverse-s5.2/manifest.json": {
|
||||||
|
"bytes": 7750,
|
||||||
|
"path": "/home/frank14f/optane/DynamisLab/steady_pinball_theory/authoritative/steady-contract-v5-torque-d20-20260808/reverse-s5.2/manifest.json",
|
||||||
|
"sha256": "99313ced195070fdeb2daefde792ea50d48ccb59ae211eb2493af021713dea69"
|
||||||
|
},
|
||||||
|
"stationary/endpoint.npz": {
|
||||||
|
"bytes": 8795812,
|
||||||
|
"path": "/home/frank14f/optane/DynamisLab/steady_pinball_theory/authoritative/steady-contract-v5-torque-d20-20260808/stationary/endpoint.npz",
|
||||||
|
"sha256": "c2edc9e4173fb6bbde7937314e4984c1c501de75f0dd4916b67aa68388dde8f3"
|
||||||
|
},
|
||||||
|
"stationary/manifest.json": {
|
||||||
|
"bytes": 7197,
|
||||||
|
"path": "/home/frank14f/optane/DynamisLab/steady_pinball_theory/authoritative/steady-contract-v5-torque-d20-20260808/stationary/manifest.json",
|
||||||
|
"sha256": "d9c4d4b8760116011bfe4c077fde8ffda69935c5eb79240aaca5ec9fc2654346"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"path": "/home/frank14f/optane/DynamisLab/steady_pinball_theory/authoritative/steady-contract-v5-torque-d20-20260808"
|
||||||
|
},
|
||||||
|
"strip_analysis": {
|
||||||
|
"path": "/home/frank14f/optane/DynamisLab/steady_pinball_theory/authoritative/steady-strip-modal-analysis-20260804-v4/analysis.json",
|
||||||
|
"sha256": "10aba0798dea28b7803318c912e310aae385b0e7e8886e2f1bf533dc37f0a74c"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
After Width: | Height: | Size: 31 KiB |
|
After Width: | Height: | Size: 32 KiB |
|
After Width: | Height: | Size: 33 KiB |
|
After Width: | Height: | Size: 31 KiB |
|
After Width: | Height: | Size: 27 KiB |
|
After Width: | Height: | Size: 32 KiB |
|
After Width: | Height: | Size: 71 KiB |
|
After Width: | Height: | Size: 71 KiB |
|
After Width: | Height: | Size: 80 KiB |
|
After Width: | Height: | Size: 71 KiB |
|
After Width: | Height: | Size: 51 KiB |
|
After Width: | Height: | Size: 62 KiB |
|
After Width: | Height: | Size: 32 KiB |
|
After Width: | Height: | Size: 34 KiB |
|
After Width: | Height: | Size: 35 KiB |
|
After Width: | Height: | Size: 27 KiB |
|
After Width: | Height: | Size: 25 KiB |
|
After Width: | Height: | Size: 28 KiB |
@@ -0,0 +1,3 @@
|
|||||||
|
case,source_case,s,C_P,ledger_sha256
|
||||||
|
cfd-accepted-s3p55,accepted-s3.55,3.55,15.026583843864495,c5d1395dd438e0f9c1f98489fd94fe309349ee554d0a21752ddfa571fe4d66cb
|
||||||
|
cfd-reverse-s5p2,reverse-s5.2,5.2,44.2399034500122,c5d1395dd438e0f9c1f98489fd94fe309349ee554d0a21752ddfa571fe4d66cb
|
||||||
|
|
After Width: | Height: | Size: 26 KiB |
@@ -0,0 +1,3 @@
|
|||||||
|
case,label,s,recirculation_area_D2,immediate_wake_abs_vorticity_integral,definition,source_sha256
|
||||||
|
cfd-accepted-s3p55,"accepted CFD, s=3.55",3.55,0.705,2.5700884680645677,"ux/U<0 on 1.8<=x/D<=10, |y/D|<=1.5; vorticity on 1.8<=x/D<4, |y/D|<=3",b6c70b76c7cf2a2e4330b27fb0d4e82a914b40fde8171076704e825a79ac72db
|
||||||
|
cfd-reverse-s5p2,"stable reverse CFD, s=5.2",5.2,0.9750000000000001,13.16909776804209,"ux/U<0 on 1.8<=x/D<=10, |y/D|<=1.5; vorticity on 1.8<=x/D<4, |y/D|<=3",760c1e84dd9b7784151ed180d3f16d17619465a08ddb767ce23a87517cc28ec3
|
||||||
|
|
After Width: | Height: | Size: 58 KiB |
@@ -0,0 +1,25 @@
|
|||||||
|
case,label,family,parameter_name,parameter,x_over_D,E_inf_vector,source_sha256
|
||||||
|
mfs-zero,"potential MFS, zero circulation",potential,g,0.0,2.0,0.5585889991095836,10aba0798dea28b7803318c912e310aae385b0e7e8886e2f1bf533dc37f0a74c
|
||||||
|
mfs-zero,"potential MFS, zero circulation",potential,g,0.0,4.0,0.07496228937634444,10aba0798dea28b7803318c912e310aae385b0e7e8886e2f1bf533dc37f0a74c
|
||||||
|
mfs-zero,"potential MFS, zero circulation",potential,g,0.0,6.0,0.028540754496172927,10aba0798dea28b7803318c912e310aae385b0e7e8886e2f1bf533dc37f0a74c
|
||||||
|
mfs-zero,"potential MFS, zero circulation",potential,g,0.0,10.0,0.008684020133851821,10aba0798dea28b7803318c912e310aae385b0e7e8886e2f1bf533dc37f0a74c
|
||||||
|
mfs-gopt,potential favorable g_opt,potential,g,6.1479442395622,2.0,0.8357535097075087,10aba0798dea28b7803318c912e310aae385b0e7e8886e2f1bf533dc37f0a74c
|
||||||
|
mfs-gopt,potential favorable g_opt,potential,g,6.1479442395622,4.0,0.01764351179814099,10aba0798dea28b7803318c912e310aae385b0e7e8886e2f1bf533dc37f0a74c
|
||||||
|
mfs-gopt,potential favorable g_opt,potential,g,6.1479442395622,6.0,0.0031010238407214014,10aba0798dea28b7803318c912e310aae385b0e7e8886e2f1bf533dc37f0a74c
|
||||||
|
mfs-gopt,potential favorable g_opt,potential,g,6.1479442395622,10.0,0.0018631935376256777,10aba0798dea28b7803318c912e310aae385b0e7e8886e2f1bf533dc37f0a74c
|
||||||
|
mfs-gfit,potential finite-Re fitted g,potential,g,-12.200554143243473,2.0,1.8806692067937432,10aba0798dea28b7803318c912e310aae385b0e7e8886e2f1bf533dc37f0a74c
|
||||||
|
mfs-gfit,potential finite-Re fitted g,potential,g,-12.200554143243473,4.0,0.25872808255979135,10aba0798dea28b7803318c912e310aae385b0e7e8886e2f1bf533dc37f0a74c
|
||||||
|
mfs-gfit,potential finite-Re fitted g,potential,g,-12.200554143243473,6.0,0.08704644916141394,10aba0798dea28b7803318c912e310aae385b0e7e8886e2f1bf533dc37f0a74c
|
||||||
|
mfs-gfit,potential finite-Re fitted g,potential,g,-12.200554143243473,10.0,0.022840097461030467,10aba0798dea28b7803318c912e310aae385b0e7e8886e2f1bf533dc37f0a74c
|
||||||
|
cfd-stationary,stationary CFD (unsteady reference),CFD,s,,2.0,1.0319734275621697,c2edc9e4173fb6bbde7937314e4984c1c501de75f0dd4916b67aa68388dde8f3
|
||||||
|
cfd-stationary,stationary CFD (unsteady reference),CFD,s,,4.0,1.070730895778441,c2edc9e4173fb6bbde7937314e4984c1c501de75f0dd4916b67aa68388dde8f3
|
||||||
|
cfd-stationary,stationary CFD (unsteady reference),CFD,s,,6.0,1.186555850771337,c2edc9e4173fb6bbde7937314e4984c1c501de75f0dd4916b67aa68388dde8f3
|
||||||
|
cfd-stationary,stationary CFD (unsteady reference),CFD,s,,10.0,1.0456532677804014,c2edc9e4173fb6bbde7937314e4984c1c501de75f0dd4916b67aa68388dde8f3
|
||||||
|
cfd-accepted-s3p55,"accepted CFD, s=3.55",CFD,s,3.55,2.0,2.0237952766768776,b6c70b76c7cf2a2e4330b27fb0d4e82a914b40fde8171076704e825a79ac72db
|
||||||
|
cfd-accepted-s3p55,"accepted CFD, s=3.55",CFD,s,3.55,4.0,0.24530522875344987,b6c70b76c7cf2a2e4330b27fb0d4e82a914b40fde8171076704e825a79ac72db
|
||||||
|
cfd-accepted-s3p55,"accepted CFD, s=3.55",CFD,s,3.55,6.0,0.08731133530155989,b6c70b76c7cf2a2e4330b27fb0d4e82a914b40fde8171076704e825a79ac72db
|
||||||
|
cfd-accepted-s3p55,"accepted CFD, s=3.55",CFD,s,3.55,10.0,0.0231672176752314,b6c70b76c7cf2a2e4330b27fb0d4e82a914b40fde8171076704e825a79ac72db
|
||||||
|
cfd-reverse-s5p2,"stable reverse CFD, s=5.2",CFD,s,5.2,2.0,1.433940096214287,760c1e84dd9b7784151ed180d3f16d17619465a08ddb767ce23a87517cc28ec3
|
||||||
|
cfd-reverse-s5p2,"stable reverse CFD, s=5.2",CFD,s,5.2,4.0,0.7483488865602846,760c1e84dd9b7784151ed180d3f16d17619465a08ddb767ce23a87517cc28ec3
|
||||||
|
cfd-reverse-s5p2,"stable reverse CFD, s=5.2",CFD,s,5.2,6.0,0.6696111083824693,760c1e84dd9b7784151ed180d3f16d17619465a08ddb767ce23a87517cc28ec3
|
||||||
|
cfd-reverse-s5p2,"stable reverse CFD, s=5.2",CFD,s,5.2,10.0,0.6898418411784105,760c1e84dd9b7784151ed180d3f16d17619465a08ddb767ce23a87517cc28ec3
|
||||||
|
|
After Width: | Height: | Size: 147 KiB |
@@ -0,0 +1,7 @@
|
|||||||
|
case,source_case,s,gap,Q_star,normal_x,normal_y,orientation,ledger_sha256
|
||||||
|
cfd-accepted-s3p55,accepted-s3.55,3.55,front_lower_rear,-0.6838239993696138,0.49972245348957717,0.8661855860486004,"center_a to center_b; normal=left(tangent), frozen by argument order",c5d1395dd438e0f9c1f98489fd94fe309349ee554d0a21752ddfa571fe4d66cb
|
||||||
|
cfd-accepted-s3p55,accepted-s3.55,3.55,front_upper_rear,0.6838263694770106,-0.49972245348957717,0.8661855860486004,"center_a to center_b; normal=left(tangent), frozen by argument order",c5d1395dd438e0f9c1f98489fd94fe309349ee554d0a21752ddfa571fe4d66cb
|
||||||
|
cfd-accepted-s3p55,accepted-s3.55,3.55,rear_rear,-1.358293459788561,1.0,0.0,"center_a to center_b; normal=left(tangent), frozen by argument order",c5d1395dd438e0f9c1f98489fd94fe309349ee554d0a21752ddfa571fe4d66cb
|
||||||
|
cfd-reverse-s5p2,reverse-s5.2,5.2,front_lower_rear,1.1521326295486622,0.49972245348957717,0.8661855860486004,"center_a to center_b; normal=left(tangent), frozen by argument order",c5d1395dd438e0f9c1f98489fd94fe309349ee554d0a21752ddfa571fe4d66cb
|
||||||
|
cfd-reverse-s5p2,reverse-s5.2,5.2,front_upper_rear,-1.1521326793028515,-0.49972245348957717,0.8661855860486004,"center_a to center_b; normal=left(tangent), frozen by argument order",c5d1395dd438e0f9c1f98489fd94fe309349ee554d0a21752ddfa571fe4d66cb
|
||||||
|
cfd-reverse-s5p2,reverse-s5.2,5.2,rear_rear,2.3088703284212944,1.0,0.0,"center_a to center_b; normal=left(tangent), frozen by argument order",c5d1395dd438e0f9c1f98489fd94fe309349ee554d0a21752ddfa571fe4d66cb
|
||||||
|
|
After Width: | Height: | Size: 53 KiB |
@@ -3,6 +3,13 @@ from __future__ import annotations
|
|||||||
import numpy as np
|
import numpy as np
|
||||||
|
|
||||||
|
|
||||||
|
def _trapezoid(values, coordinates):
|
||||||
|
"""NumPy-version-independent 1-D composite trapezoid integral."""
|
||||||
|
values=np.asarray(values); coordinates=np.asarray(coordinates)
|
||||||
|
if values.shape[0]!=len(coordinates): raise ValueError("trapezoid axis mismatch")
|
||||||
|
return np.sum((values[:-1]+values[1:])*.5*np.diff(coordinates).reshape((-1,)+(1,)*(values.ndim-1)),axis=0)
|
||||||
|
|
||||||
|
|
||||||
def profile_errors(ux: np.ndarray, uy: np.ndarray, *, u_inf: float) -> dict[str, float]:
|
def profile_errors(ux: np.ndarray, uy: np.ndarray, *, u_inf: float) -> dict[str, float]:
|
||||||
ux=np.asarray(ux,dtype=float); uy=np.asarray(uy,dtype=float)
|
ux=np.asarray(ux,dtype=float); uy=np.asarray(uy,dtype=float)
|
||||||
if ux.shape != uy.shape or ux.ndim != 1: raise ValueError("profiles must be aligned 1-D arrays")
|
if ux.shape != uy.shape or ux.ndim != 1: raise ValueError("profiles must be aligned 1-D arrays")
|
||||||
@@ -19,3 +26,870 @@ def two_window_change(samples: np.ndarray) -> float:
|
|||||||
n=a.shape[0]//2
|
n=a.shape[0]//2
|
||||||
x=np.mean(a[:n],axis=0); y=np.mean(a[-n:],axis=0)
|
x=np.mean(a[:n],axis=0); y=np.mean(a[-n:],axis=0)
|
||||||
return float(np.sqrt(np.mean((y-x)**2)))
|
return float(np.sqrt(np.mean((y-x)**2)))
|
||||||
|
|
||||||
|
|
||||||
|
def common_exterior_error(reference_velocity, candidate_velocity, centers, radius, *,
|
||||||
|
bounds=(-3.0, 6.0, -3.0, 3.0), shape=(181, 121),
|
||||||
|
exclusion_factor=1.1, u_inf=1.0):
|
||||||
|
"""Fixed rectangular tensor quadrature over the declared common exterior."""
|
||||||
|
c = np.asarray(centers, dtype=float)
|
||||||
|
if c.ndim != 2 or c.shape[1] != 2 or not np.isfinite(c).all():
|
||||||
|
raise ValueError("centers must be a finite (B,2) array")
|
||||||
|
vals = np.asarray([radius, exclusion_factor, u_inf, *bounds], dtype=float)
|
||||||
|
if not np.isfinite(vals).all() or radius <= 0 or exclusion_factor <= 1:
|
||||||
|
raise ValueError("invalid exterior-error geometry")
|
||||||
|
if len(bounds) != 4 or bounds[0] >= bounds[1] or bounds[2] >= bounds[3]:
|
||||||
|
raise ValueError("bounds must be (xmin,xmax,ymin,ymax)")
|
||||||
|
if (len(shape) != 2 or any(isinstance(x, (bool, np.bool_)) or
|
||||||
|
not isinstance(x, (int, np.integer)) or x < 3 for x in shape)):
|
||||||
|
raise ValueError("shape must contain two integers >= 3")
|
||||||
|
x = np.linspace(bounds[0], bounds[1], shape[0])
|
||||||
|
y = np.linspace(bounds[2], bounds[3], shape[1])
|
||||||
|
xx, yy = np.meshgrid(x, y, indexing="xy")
|
||||||
|
points = np.column_stack((xx.ravel(), yy.ravel()))
|
||||||
|
mask = np.ones(len(points), dtype=bool)
|
||||||
|
for center in c:
|
||||||
|
mask &= np.linalg.norm(points - center, axis=1) >= exclusion_factor * radius
|
||||||
|
points = points[mask]
|
||||||
|
if not len(points):
|
||||||
|
raise ValueError("exterior mask retains no points")
|
||||||
|
wx = np.ones(shape[0]); wx[[0, -1]] = 0.5
|
||||||
|
wy = np.ones(shape[1]); wy[[0, -1]] = 0.5
|
||||||
|
weights = np.outer(wy, wx).ravel()[mask]
|
||||||
|
ref = np.asarray(reference_velocity(points), dtype=float)
|
||||||
|
cand = np.asarray(candidate_velocity(points), dtype=float)
|
||||||
|
if ref.shape != cand.shape or ref.shape != points.shape or not np.isfinite(ref).all() or not np.isfinite(cand).all():
|
||||||
|
raise ValueError("velocity functions must return finite aligned (N,2) arrays")
|
||||||
|
def wrms(a):
|
||||||
|
return float(np.sqrt(np.sum(weights * np.sum(a * a, axis=1)) / np.sum(weights)))
|
||||||
|
absolute = wrms(cand - ref)
|
||||||
|
disturbance = wrms(ref - np.array([u_inf, 0.0]))
|
||||||
|
return {
|
||||||
|
"weighted_RMS": absolute,
|
||||||
|
"relative_to_reference_disturbance": absolute / disturbance if disturbance > 0 else float("nan"),
|
||||||
|
"relative_to_uniform_speed": absolute / abs(u_inf) if u_inf != 0 else float("nan"),
|
||||||
|
"bounds": [float(v) for v in bounds], "shape": [int(v) for v in shape],
|
||||||
|
"exclusion_factor": float(exclusion_factor), "retained_points": int(len(points)),
|
||||||
|
"retained_weight": float(np.sum(weights)), "reference_disturbance_RMS": disturbance,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def reflection_parity_defect(velocity, points, *, u_inf=1.0):
|
||||||
|
"""Polar-vector reflection defect: ux even and uy odd across y=0."""
|
||||||
|
p = np.asarray(points, dtype=float)
|
||||||
|
if p.ndim != 2 or p.shape[1] != 2 or not np.isfinite(p).all() or u_inf <= 0:
|
||||||
|
raise ValueError("points must be finite (N,2) and u_inf positive")
|
||||||
|
reflected = p.copy(); reflected[:, 1] *= -1
|
||||||
|
v = np.asarray(velocity(p), dtype=float)
|
||||||
|
vr = np.asarray(velocity(reflected), dtype=float)
|
||||||
|
if v.shape != p.shape or vr.shape != p.shape or not np.isfinite(v).all() or not np.isfinite(vr).all():
|
||||||
|
raise ValueError("velocity must return finite aligned (N,2) arrays")
|
||||||
|
component = np.column_stack((v[:, 0] - vr[:, 0], v[:, 1] + vr[:, 1])) / u_inf
|
||||||
|
return {"Linf": float(np.max(np.abs(component))),
|
||||||
|
"RMS": float(np.sqrt(np.mean(component**2)))}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def affine_profile_sensitivity(base_velocity, direction_velocity, *, u_inf=1.0):
|
||||||
|
"""Analytic derivative and optimum of smooth RMS profile disturbance."""
|
||||||
|
u0 = np.asarray(base_velocity, dtype=float)
|
||||||
|
v = np.asarray(direction_velocity, dtype=float)
|
||||||
|
if u0.ndim != 2 or u0.shape[1] != 2 or v.shape != u0.shape:
|
||||||
|
raise ValueError("profile velocities must be aligned (N,2) arrays")
|
||||||
|
if not np.isfinite(u0).all() or not np.isfinite(v).all() or u_inf <= 0:
|
||||||
|
raise ValueError("invalid affine profile")
|
||||||
|
disturbance = u0 - np.array([u_inf, 0.0])
|
||||||
|
a = float(np.mean(np.sum(v*v, axis=1)))
|
||||||
|
b = float(np.mean(np.sum(disturbance*v, axis=1)))
|
||||||
|
c = float(np.mean(np.sum(disturbance*disturbance, axis=1)))
|
||||||
|
if a <= 0 or c <= 0:
|
||||||
|
raise ValueError("nonzero base disturbance and direction are required")
|
||||||
|
optimum = -b/a
|
||||||
|
minimum2 = max(0.0, c - b*b/a)
|
||||||
|
return {"J0": float(np.sqrt(c)), "dJ_dg_at_zero": b/np.sqrt(c),
|
||||||
|
"g_opt": optimum, "J_opt": float(np.sqrt(minimum2)),
|
||||||
|
"quadratic": [a, 2*b, c]}
|
||||||
|
|
||||||
|
|
||||||
|
def one_sided_profile_error_slopes(base_velocity, direction_velocity, steps, *, u_inf=1.0):
|
||||||
|
"""Declared forward quotients for nonsmooth E_inf_vector; not derivatives."""
|
||||||
|
u0 = np.asarray(base_velocity, dtype=float)
|
||||||
|
v = np.asarray(direction_velocity, dtype=float)
|
||||||
|
hs = np.asarray(steps, dtype=float)
|
||||||
|
if u0.ndim != 2 or u0.shape[1] != 2 or v.shape != u0.shape:
|
||||||
|
raise ValueError("profile velocities must be aligned (N,2) arrays")
|
||||||
|
if hs.ndim != 1 or len(hs) < 2 or not np.isfinite(hs).all() or np.any(hs <= 0):
|
||||||
|
raise ValueError("steps must be at least two finite positive values")
|
||||||
|
e0 = profile_errors(u0[:, 0], u0[:, 1], u_inf=u_inf)["E_inf_vector"]
|
||||||
|
values = []
|
||||||
|
for h in hs:
|
||||||
|
uh = u0 + h*v
|
||||||
|
eh = profile_errors(uh[:, 0], uh[:, 1], u_inf=u_inf)["E_inf_vector"]
|
||||||
|
values.append((eh-e0)/h)
|
||||||
|
return {"definition": "forward quotient [E(g=h)-E(g=0)]/h",
|
||||||
|
"steps": hs.tolist(), "slopes": [float(x) for x in values],
|
||||||
|
"last_change": float(abs(values[-1]-values[-2]))}
|
||||||
|
|
||||||
|
|
||||||
|
def gap_flux(velocity, center_a, center_b, radius, *, samples=2001):
|
||||||
|
"""Oriented flux across the shortest open gap, normal=left(tangent)."""
|
||||||
|
a = np.asarray(center_a, dtype=float); b = np.asarray(center_b, dtype=float)
|
||||||
|
if a.shape != (2,) or b.shape != (2,) or not np.isfinite([*a,*b,radius]).all():
|
||||||
|
raise ValueError("invalid gap geometry")
|
||||||
|
d = b-a; length = float(np.linalg.norm(d))
|
||||||
|
if radius <= 0 or length <= 2*radius:
|
||||||
|
raise ValueError("gap must be positive")
|
||||||
|
if isinstance(samples, (bool, np.bool_)) or not isinstance(samples, (int, np.integer)) or samples < 3:
|
||||||
|
raise ValueError("samples must be an integer >= 3")
|
||||||
|
tangent = d/length; normal = np.array([-tangent[1], tangent[0]])
|
||||||
|
t = np.linspace(radius, length-radius, int(samples))
|
||||||
|
points = a + t[:, None]*tangent
|
||||||
|
trace = np.asarray(velocity(points), dtype=float) @ normal
|
||||||
|
return {"flux": float(_trapezoid(trace, t)), "normal": normal.tolist(),
|
||||||
|
"gap_length": length-2*radius, "samples": int(samples)}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def tensor_trapezoid_weights(x, y):
|
||||||
|
"""Physical composite-trapezoid weights for a rectilinear tensor grid."""
|
||||||
|
x=np.asarray(x,dtype=float); y=np.asarray(y,dtype=float)
|
||||||
|
if x.ndim!=1 or y.ndim!=1 or len(x)<2 or len(y)<2 or np.any(np.diff(x)<=0) or np.any(np.diff(y)<=0) or not np.isfinite(x).all() or not np.isfinite(y).all():
|
||||||
|
raise ValueError("x and y must be finite increasing axes")
|
||||||
|
def widths(a):
|
||||||
|
w=np.empty_like(a); w[0]=(a[1]-a[0])/2; w[-1]=(a[-1]-a[-2])/2
|
||||||
|
w[1:-1]=(a[2:]-a[:-2])/2
|
||||||
|
return w
|
||||||
|
return np.outer(widths(y),widths(x))
|
||||||
|
|
||||||
|
|
||||||
|
def quadrature_l2(field, mask, weights, *, normalizer=None):
|
||||||
|
"""Explicit weighted L2 integral norm, never an RMS."""
|
||||||
|
q=np.asarray(field,dtype=float); m=np.asarray(mask,dtype=bool); w=np.asarray(weights,dtype=float)
|
||||||
|
if q.ndim!=3 or q.shape[-1]!=2 or m.shape!=q.shape[:2] or w.shape!=m.shape or not np.isfinite(w).all() or np.any(w<0):
|
||||||
|
raise ValueError("invalid aligned L2 field, mask, or weights")
|
||||||
|
if not m.any() or float(np.sum(w[m]))<=0: raise ValueError("L2 mask retains no positive area")
|
||||||
|
if not np.isfinite(q[m]).all(): raise ValueError("L2 field is nonfinite on mask")
|
||||||
|
value=float(np.sqrt(np.sum(w[m]*np.sum(q[m]*q[m],axis=1))))
|
||||||
|
result={"L2":value,"retained_measure":float(np.sum(w[m])),"points":int(m.sum()),"definition":"sqrt(integral |q|^2)"}
|
||||||
|
if normalizer is not None:
|
||||||
|
n=np.asarray(normalizer,dtype=float)
|
||||||
|
if n.shape!=q.shape or not np.isfinite(n[m]).all(): raise ValueError("invalid L2 normalizer")
|
||||||
|
scale=float(np.sqrt(np.sum(w[m]*np.sum(n[m]*n[m],axis=1))))
|
||||||
|
if scale<=0: raise ValueError("L2 normalizer is zero")
|
||||||
|
result.update(normalizer_L2=scale,normalized_L2=value/scale)
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def conservative_masked_vorticity(x, y, ux, uy, valid_mask):
|
||||||
|
"""Centered curl only where all four axial neighbors are valid."""
|
||||||
|
x=np.asarray(x,dtype=float); y=np.asarray(y,dtype=float); ux=np.asarray(ux,dtype=float); uy=np.asarray(uy,dtype=float); valid=np.asarray(valid_mask,dtype=bool)
|
||||||
|
if ux.shape!=(len(y),len(x)) or uy.shape!=ux.shape or valid.shape!=ux.shape or len(x)<3 or len(y)<3 or np.any(np.diff(x)<=0) or np.any(np.diff(y)<=0) or not np.isfinite(ux).all() or not np.isfinite(uy).all():
|
||||||
|
raise ValueError("invalid vorticity grid")
|
||||||
|
usable=np.zeros_like(valid); usable[1:-1,1:-1]=valid[1:-1,1:-1]&valid[1:-1,:-2]&valid[1:-1,2:]&valid[:-2,1:-1]&valid[2:,1:-1]
|
||||||
|
omega=np.full(ux.shape,np.nan)
|
||||||
|
duy_dx=(uy[1:-1,2:]-uy[1:-1,:-2])/(x[2:]-x[:-2])[None,:]
|
||||||
|
dux_dy=(ux[2:,1:-1]-ux[:-2,1:-1])/(y[2:]-y[:-2])[:,None]
|
||||||
|
interior=usable[1:-1,1:-1]; values=duy_dx-dux_dy
|
||||||
|
omega[1:-1,1:-1][interior]=values[interior]
|
||||||
|
return omega,usable
|
||||||
|
|
||||||
|
|
||||||
|
def absolute_vorticity_moments(y, omega, mask, weights):
|
||||||
|
"""Absolute-vorticity weighted transverse centroid and width."""
|
||||||
|
y=np.asarray(y,dtype=float); o=np.asarray(omega,dtype=float); m=np.asarray(mask,dtype=bool); w=np.asarray(weights,dtype=float)
|
||||||
|
if o.shape!=m.shape or w.shape!=m.shape or o.shape[0]!=len(y): raise ValueError("vorticity moments shape mismatch")
|
||||||
|
use=m&np.isfinite(o); mass=float(np.sum(w[use]*np.abs(o[use])))
|
||||||
|
if not use.any() or mass<=0: raise ValueError("vorticity moment mask has zero absolute vorticity")
|
||||||
|
yy=np.broadcast_to(y[:,None],o.shape); centroid=float(np.sum(w[use]*np.abs(o[use])*yy[use])/mass)
|
||||||
|
width=float(np.sqrt(np.sum(w[use]*np.abs(o[use])*(yy[use]-centroid)**2)/mass))
|
||||||
|
return {"absolute_vorticity_integral":mass,"y_centroid":centroid,"y_width":width,"points":int(use.sum())}
|
||||||
|
|
||||||
|
|
||||||
|
def recirculation_observables(x, ux, mask, weights):
|
||||||
|
"""Area and occupied-column streamwise extent where ux is negative."""
|
||||||
|
x=np.asarray(x,dtype=float); u=np.asarray(ux,dtype=float); m=np.asarray(mask,dtype=bool); w=np.asarray(weights,dtype=float)
|
||||||
|
if u.shape!=m.shape or w.shape!=m.shape or u.shape[1]!=len(x) or not np.isfinite(u).all(): raise ValueError("invalid recirculation inputs")
|
||||||
|
use=m&(u<0); occupied=np.any(use,axis=0)
|
||||||
|
if not occupied.any(): return {"area":0.0,"x_min":None,"x_max":None,"streamwise_extent":0.0,"points":0}
|
||||||
|
indices=np.flatnonzero(occupied); xmin=float(x[indices[0]]); xmax=float(x[indices[-1]])
|
||||||
|
return {"area":float(np.sum(w[use])),"x_min":xmin,"x_max":xmax,"streamwise_extent":xmax-xmin,"points":int(use.sum())}
|
||||||
|
|
||||||
|
|
||||||
|
def wake_deficit_observables(y, ux_profile, mask):
|
||||||
|
"""Frozen station momentum-deficit proxies using line quadrature."""
|
||||||
|
y=np.asarray(y,dtype=float); u=np.asarray(ux_profile,dtype=float); m=np.asarray(mask,dtype=bool)
|
||||||
|
if y.ndim!=1 or u.shape!=y.shape or m.shape!=y.shape or len(y)<2 or not np.isfinite(u).all(): raise ValueError("invalid wake profile")
|
||||||
|
if not m.any(): raise ValueError("wake profile mask is empty")
|
||||||
|
deficit=1-u; positive=np.maximum(deficit,0); indicator=(deficit>0).astype(float)
|
||||||
|
def segments(v):
|
||||||
|
total=0.0
|
||||||
|
for i in range(len(y)-1):
|
||||||
|
if m[i] and m[i+1]: total+=0.5*(v[i]+v[i+1])*(y[i+1]-y[i])
|
||||||
|
return float(total)
|
||||||
|
return {"momentum_deficit_proxy":segments(positive),"positive_deficit_measure":segments(indicator),"signed_deficit_integral":segments(deficit),"points":int(m.sum())}
|
||||||
|
|
||||||
|
def bilinear_sample_velocity(x, y, ux, uy, points, *, valid_mask=None):
|
||||||
|
"""Sample a rectilinear (y,x)-ordered velocity grid by bilinear interpolation."""
|
||||||
|
x=np.asarray(x,dtype=float); y=np.asarray(y,dtype=float)
|
||||||
|
ux=np.asarray(ux,dtype=float); uy=np.asarray(uy,dtype=float); p=np.asarray(points,dtype=float)
|
||||||
|
if x.ndim != 1 or y.ndim != 1 or len(x)<2 or len(y)<2 or np.any(np.diff(x)<=0) or np.any(np.diff(y)<=0): raise ValueError("x and y must be strictly increasing 1-D axes")
|
||||||
|
if ux.shape != (len(y),len(x)) or uy.shape != ux.shape or p.ndim != 2 or p.shape[1] != 2: raise ValueError("fields must be (y,x) and points finite (N,2)")
|
||||||
|
if not np.isfinite(x).all() or not np.isfinite(y).all() or not np.isfinite(ux).all() or not np.isfinite(uy).all() or not np.isfinite(p).all(): raise ValueError("sampling inputs must be finite")
|
||||||
|
if np.any((p[:,0]<x[0]) | (p[:,0]>x[-1]) | (p[:,1]<y[0]) | (p[:,1]>y[-1])): raise ValueError("sampling point outside grid")
|
||||||
|
ix=np.clip(np.searchsorted(x,p[:,0],side="right")-1,0,len(x)-2); iy=np.clip(np.searchsorted(y,p[:,1],side="right")-1,0,len(y)-2)
|
||||||
|
tx=(p[:,0]-x[ix])/(x[ix+1]-x[ix]); ty=(p[:,1]-y[iy])/(y[iy+1]-y[iy])
|
||||||
|
def interp(a): return ((1-tx)*(1-ty)*a[iy,ix]+tx*(1-ty)*a[iy,ix+1]+(1-tx)*ty*a[iy+1,ix]+tx*ty*a[iy+1,ix+1])
|
||||||
|
if valid_mask is not None:
|
||||||
|
m=np.asarray(valid_mask,dtype=bool)
|
||||||
|
if m.shape != ux.shape: raise ValueError("valid_mask shape mismatch")
|
||||||
|
if not (m[iy,ix]&m[iy,ix+1]&m[iy+1,ix]&m[iy+1,ix+1]).all(): raise ValueError("bilinear stencil intersects invalid cells")
|
||||||
|
return np.column_stack((interp(ux),interp(uy)))
|
||||||
|
|
||||||
|
|
||||||
|
def ccw_contour_circulation(x, y, ux, uy, center, radii, *, samples=2048, valid_mask=None):
|
||||||
|
"""Geometric-CCW circular line integrals on explicitly ordered contours."""
|
||||||
|
center=np.asarray(center,dtype=float); radii=np.asarray(radii,dtype=float)
|
||||||
|
if center.shape!=(2,) or radii.ndim!=1 or len(radii)==0 or np.any(~np.isfinite(radii)) or np.any(radii<=0): raise ValueError("center and radii must be finite and positive")
|
||||||
|
if isinstance(samples,(bool,np.bool_)) or not isinstance(samples,(int,np.integer)) or samples<16: raise ValueError("samples must be an integer >= 16")
|
||||||
|
theta=2*np.pi*np.arange(int(samples))/int(samples); normal=np.column_stack((np.cos(theta),np.sin(theta))); tangent=np.column_stack((-np.sin(theta),np.cos(theta)))
|
||||||
|
values=[]
|
||||||
|
for radius in radii:
|
||||||
|
velocity=bilinear_sample_velocity(x,y,ux,uy,center+radius*normal,valid_mask=valid_mask)
|
||||||
|
values.append(float(radius*(2*np.pi/int(samples))*np.sum(np.einsum("ij,ij->i",velocity,tangent))))
|
||||||
|
return {"orientation":"geometric-CCW","radii":radii.tolist(),"circulation":values,"samples":int(samples)}
|
||||||
|
|
||||||
|
|
||||||
|
def preregistered_outer_mask(x, y, centers, radius, *, bounds, exclusion_factor=1.5, wall_margin=2.0):
|
||||||
|
"""Frozen common-exterior point mask for endpoint-field WLS."""
|
||||||
|
x=np.asarray(x,dtype=float); y=np.asarray(y,dtype=float); c=np.asarray(centers,dtype=float)
|
||||||
|
if len(bounds)!=4 or bounds[0]>=bounds[1] or bounds[2]>=bounds[3] or exclusion_factor<=1 or wall_margin<0: raise ValueError("invalid preregistered mask parameters")
|
||||||
|
xx,yy=np.meshgrid(x,y,indexing="xy"); mask=(xx>=bounds[0])&(xx<=bounds[1])&(yy>=bounds[2])&(yy<=bounds[3])
|
||||||
|
mask &= (yy>=y[0]+wall_margin)&(yy<=y[-1]-wall_margin)
|
||||||
|
for center in c: mask &= (xx-center[0])**2+(yy-center[1])**2 >= (exclusion_factor*radius)**2
|
||||||
|
return mask
|
||||||
|
|
||||||
|
|
||||||
|
def outer_wls_effective_circulation(observed_delta, unit_model_delta, mask, *, weights=None):
|
||||||
|
"""Origin-constrained vector WLS for one geometric-circulation amplitude."""
|
||||||
|
obs=np.asarray(observed_delta,dtype=float); model=np.asarray(unit_model_delta,dtype=float); mask=np.asarray(mask,dtype=bool)
|
||||||
|
if obs.shape!=model.shape or obs.ndim!=3 or obs.shape[-1]!=2 or mask.shape!=obs.shape[:2]: raise ValueError("WLS arrays must be aligned (y,x,2) with a (y,x) mask")
|
||||||
|
w=np.ones(mask.shape) if weights is None else np.asarray(weights,dtype=float)
|
||||||
|
if w.shape!=mask.shape or np.any(~np.isfinite(w)) or np.any(w<0) or not np.isfinite(obs).all() or not np.isfinite(model).all(): raise ValueError("invalid WLS arrays or weights")
|
||||||
|
use=mask&(w>0); denominator=float(np.sum(w[use]*np.sum(model[use]**2,axis=1)))
|
||||||
|
if not use.any() or denominator<=0: raise ValueError("WLS mask has no model information")
|
||||||
|
gamma=float(np.sum(w[use]*np.sum(obs[use]*model[use],axis=1))/denominator); residual=obs[use]-gamma*model[use]
|
||||||
|
rms=float(np.sqrt(np.sum(w[use]*np.sum(residual**2,axis=1))/np.sum(w[use]))); data_rms=float(np.sqrt(np.sum(w[use]*np.sum(obs[use]**2,axis=1))/np.sum(w[use])))
|
||||||
|
return {"gamma_eff":gamma,"weighted_RMS":rms,"relative_RMS":rms/data_rms if data_rms>0 else float("nan"),"points":int(use.sum()),"weight_sum":float(w[use].sum()),"denominator":denominator}
|
||||||
|
|
||||||
|
|
||||||
|
def outer_wls_total_model(observed_delta, base_model, unit_model_delta, mask, *, weights=None):
|
||||||
|
"""Fit observed=base+gamma*direction and return the complete prediction."""
|
||||||
|
observed=np.asarray(observed_delta,dtype=float); base=np.asarray(base_model,dtype=float)
|
||||||
|
if observed.shape!=base.shape:
|
||||||
|
raise ValueError("observed and base model must have identical shapes")
|
||||||
|
fit=outer_wls_effective_circulation(observed-base,unit_model_delta,mask,weights=weights)
|
||||||
|
fit["fit_model"]="observed = base_model + gamma_eff * unit_model_delta"
|
||||||
|
fit["prediction"]=base+fit["gamma_eff"]*np.asarray(unit_model_delta,dtype=float)
|
||||||
|
return fit
|
||||||
|
|
||||||
|
|
||||||
|
def fit_two_point_affine_closure(s_train, gamma_train):
|
||||||
|
"""Exact affine closure from the preregistered two distinct endpoints."""
|
||||||
|
s=np.asarray(s_train,dtype=float); g=np.asarray(gamma_train,dtype=float)
|
||||||
|
if s.shape!=(2,) or g.shape!=(2,) or not np.isfinite(s).all() or not np.isfinite(g).all() or s[0]==s[1]: raise ValueError("closure requires two finite distinct training endpoints")
|
||||||
|
slope=float((g[1]-g[0])/(s[1]-s[0])); return {"intercept":float(g[0]-slope*s[0]),"slope":slope,"train_s":s.tolist(),"train_gamma":g.tolist()}
|
||||||
|
|
||||||
|
|
||||||
|
def predict_affine_closure(closure, s): return float(closure["intercept"]+closure["slope"]*float(s))
|
||||||
|
|
||||||
|
|
||||||
|
def strip_modal_projection(y, velocity, H, *, modes):
|
||||||
|
"""Project a station onto exact downstream Neumann-strip velocity modes."""
|
||||||
|
y=np.asarray(y,dtype=float); v=np.asarray(velocity,dtype=float)
|
||||||
|
if y.ndim!=1 or v.shape!=(len(y),2) or len(y)<3 or not np.isfinite(v).all() or H<=0 or y[0] < -H-1e-12 or y[-1] > H+1e-12: raise ValueError("invalid strip station")
|
||||||
|
modes=int(modes)
|
||||||
|
if modes<1: raise ValueError("modes must be positive")
|
||||||
|
rows=[]
|
||||||
|
for n in range(1,modes+1):
|
||||||
|
k=n*np.pi/(2*H); c=np.cos(k*(y+H)); sn=np.sin(k*(y+H))
|
||||||
|
rows.append((n,k,float(_trapezoid(v[:,0]*c,y)/H),float(_trapezoid(v[:,1]*sn,y)/H)))
|
||||||
|
return {"through_flow_zero_mode":float(_trapezoid(v[:,0],y)/(2*H)),"modes":np.asarray(rows),"normalization":"integral/H"}
|
||||||
|
|
||||||
|
|
||||||
|
def strip_mode_parity_sector(name):
|
||||||
|
"""Return the allowed Neumann-mode parity for each affine field part."""
|
||||||
|
sectors={"base":"even","rear_opposite":"even","common_rear":"odd","front_only":"odd"}
|
||||||
|
if name not in sectors: raise ValueError(f"unknown strip parity sector {name!r}")
|
||||||
|
return sectors[name]
|
||||||
|
|
||||||
|
|
||||||
|
def propagate_strip_modes(projection, dx):
|
||||||
|
rows=np.asarray(projection["modes"],dtype=float).copy()
|
||||||
|
if dx<0: raise ValueError("downstream propagation requires dx >= 0")
|
||||||
|
rows[:,2:4] *= np.exp(-rows[:,1]*float(dx))[:,None]
|
||||||
|
return {"through_flow_zero_mode":projection["through_flow_zero_mode"],"modes":rows,"normalization":projection["normalization"]}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def _karman_labels(strengths):
|
||||||
|
k=np.asarray(strengths,dtype=float)
|
||||||
|
if k.ndim!=1 or len(k)<4 or not np.isfinite(k).all() or not np.any(k<0) or not np.any(k>0):
|
||||||
|
raise ValueError("Karman metrics require finite opposite-sign rows")
|
||||||
|
return k<0,k>0
|
||||||
|
|
||||||
|
|
||||||
|
def karman_observer_mask(initial_positions, spacing, *, half_width=2.5):
|
||||||
|
"""Frozen initial-label mask for a central two-row observer."""
|
||||||
|
x=np.asarray(initial_positions,dtype=float)
|
||||||
|
if x.ndim!=2 or x.shape[1]!=2 or not np.isfinite(x).all() or spacing<=0 or half_width<=0:
|
||||||
|
raise ValueError("invalid Karman observer inputs")
|
||||||
|
mask=np.abs(x[:,0])<=float(half_width)*float(spacing)+1e-14
|
||||||
|
if mask.sum()<4: raise ValueError("central observer retains too few vortices")
|
||||||
|
return mask
|
||||||
|
|
||||||
|
|
||||||
|
def aligned_position_l2(reference, candidate, mask):
|
||||||
|
"""RMS position mismatch after one best global translation."""
|
||||||
|
a=np.asarray(reference,dtype=float); b=np.asarray(candidate,dtype=float); m=np.asarray(mask,dtype=bool)
|
||||||
|
if a.shape!=b.shape or a.ndim!=2 or a.shape[1]!=2 or m.shape!=(len(a),) or not m.any() or not np.isfinite(a).all() or not np.isfinite(b).all():
|
||||||
|
raise ValueError("invalid aligned-position inputs")
|
||||||
|
delta=b[m]-a[m]; translation=np.mean(delta,axis=0); residual=delta-translation
|
||||||
|
return {"translation":translation.tolist(),"L2":float(np.sqrt(np.mean(np.sum(residual*residual,axis=1))))}
|
||||||
|
|
||||||
|
|
||||||
|
def karman_propagation_metrics(initial, final, strengths, observer_mask, *, spacing, separation):
|
||||||
|
"""Frozen central-window geometry metrics after best global translation."""
|
||||||
|
x0=np.asarray(initial,dtype=float); x1=np.asarray(final,dtype=float); k=np.asarray(strengths,dtype=float); m=np.asarray(observer_mask,dtype=bool)
|
||||||
|
if x0.shape!=x1.shape or x0.ndim!=2 or x0.shape[1]!=2 or k.shape!=(len(x0),) or m.shape!=(len(x0),) or not m.any() or spacing<=0 or separation<=0 or not np.isfinite(x0).all() or not np.isfinite(x1).all():
|
||||||
|
raise ValueError("invalid Karman propagation inputs")
|
||||||
|
lower,upper=_karman_labels(k); fit=aligned_position_l2(x0,x1,m); translation=np.asarray(fit["translation"]); aligned=x1-translation
|
||||||
|
def row_spacing(points,row):
|
||||||
|
values=np.sort(points[m&row,0]);
|
||||||
|
if len(values)<2: raise ValueError("observer row retains too few vortices")
|
||||||
|
return float(np.mean(np.diff(values)))
|
||||||
|
lower_spacing=row_spacing(aligned,lower); upper_spacing=row_spacing(aligned,upper)
|
||||||
|
h0=float(np.mean(x0[m&upper,1])-np.mean(x0[m&lower,1])); h1=float(np.mean(aligned[m&upper,1])-np.mean(aligned[m&lower,1]))
|
||||||
|
phase0=float(np.mean(x0[m&upper,0])-np.mean(x0[m&lower,0])); phase1=float(np.mean(aligned[m&upper,0])-np.mean(aligned[m&lower,0]))
|
||||||
|
center0=np.array([np.mean(x0[m&lower,1]),np.mean(x0[m&upper,1])]); center1=np.array([np.mean(aligned[m&lower,1]),np.mean(aligned[m&upper,1])])
|
||||||
|
delta=x1[:,None,:]-x1[None,:,:]; distance=np.linalg.norm(delta,axis=2); np.fill_diagonal(distance,np.inf)
|
||||||
|
return {"best_translation":translation.tolist(),"vortex_position_L2":fit["L2"],
|
||||||
|
"lower_spacing_drift":(lower_spacing-spacing)/spacing,"upper_spacing_drift":(upper_spacing-spacing)/spacing,
|
||||||
|
"a_over_h_drift":float(spacing/h1-spacing/h0),"phase_stagger_drift":float((phase1-phase0)/spacing),
|
||||||
|
"row_center_shift":(center1-center0).tolist(),"minimum_pair_distance":float(np.min(distance))}
|
||||||
|
|
||||||
|
|
||||||
|
def normalized_invariant_drift(values, *, scale):
|
||||||
|
v=np.asarray(values,dtype=float)
|
||||||
|
if v.ndim!=1 or not len(v) or not np.isfinite(v).all() or not np.isfinite(scale) or scale<0:
|
||||||
|
raise ValueError("invalid invariant drift inputs")
|
||||||
|
denominator=max(abs(float(v[0])),float(scale),np.finfo(float).eps)
|
||||||
|
return float(np.max(np.abs(v-v[0]))/denominator)
|
||||||
|
|
||||||
|
|
||||||
|
def _adjacent_fluid_to_bodies(fluid_mask, body_id, body_ids):
|
||||||
|
"""Boolean mask of fluid cells with a four-neighbor listed body cell."""
|
||||||
|
fluid=np.asarray(fluid_mask,dtype=bool); body=np.asarray(body_id)
|
||||||
|
if fluid.shape!=body.shape or fluid.ndim!=2: raise ValueError("fluid_mask/body_id shape mismatch")
|
||||||
|
ids=tuple(int(v) for v in body_ids)
|
||||||
|
if not ids: raise ValueError("body_ids must be nonempty")
|
||||||
|
solid=np.zeros_like(fluid)
|
||||||
|
for registry_id in ids: solid |= body==registry_id
|
||||||
|
near=np.zeros_like(fluid)
|
||||||
|
near[:,1:] |= fluid[:,1:]&solid[:,:-1]; near[:,:-1] |= fluid[:,:-1]&solid[:,1:]
|
||||||
|
near[1:,:] |= fluid[1:,:]&solid[:-1,:]; near[:-1,:] |= fluid[:-1,:]&solid[1:,:]
|
||||||
|
return near
|
||||||
|
|
||||||
|
|
||||||
|
def near_wall_tangential_speed(x, y, ux, uy, fluid_mask, body_id, centers, radius, *,
|
||||||
|
body_ids=(1, 2), body_labels=None):
|
||||||
|
"""Sample fluid tangential speed on cells adjacent to listed body interiors."""
|
||||||
|
x=np.asarray(x,dtype=float); y=np.asarray(y,dtype=float)
|
||||||
|
ux=np.asarray(ux,dtype=float); uy=np.asarray(uy,dtype=float)
|
||||||
|
centers=np.asarray(centers,dtype=float)
|
||||||
|
if ux.shape!=(len(y),len(x)) or uy.shape!=ux.shape or centers.ndim!=2 or centers.shape[1]!=2:
|
||||||
|
raise ValueError("invalid near-wall velocity grid or centers")
|
||||||
|
if not np.isfinite(radius) or radius<=0: raise ValueError("radius must be positive")
|
||||||
|
near=_adjacent_fluid_to_bodies(fluid_mask,body_id,body_ids)
|
||||||
|
labels=list(body_labels) if body_labels is not None else [f"body_{i}" for i in body_ids]
|
||||||
|
if len(labels)!=len(body_ids): raise ValueError("body_labels length mismatch")
|
||||||
|
xx,yy=np.meshgrid(x,y,indexing="xy"); report={}
|
||||||
|
for registry_id,label in zip(body_ids,labels):
|
||||||
|
solid=np.asarray(body_id)==int(registry_id)
|
||||||
|
mask=np.zeros_like(near)
|
||||||
|
mask[:,1:] |= near[:,1:]&solid[:,:-1]; mask[:,:-1] |= near[:,:-1]&solid[:,1:]
|
||||||
|
mask[1:,:] |= near[1:,:]&solid[:-1,:]; mask[:-1,:] |= near[:-1,:]&solid[1:,:]
|
||||||
|
if not mask.any():
|
||||||
|
report[label]={"availability":"unavailable_no_adjacent_fluid","points":0}
|
||||||
|
continue
|
||||||
|
cx,cy=centers[int(registry_id)]; dx=xx[mask]-cx; dy=yy[mask]-cy
|
||||||
|
rr=np.hypot(dx,dy); usable=rr>0
|
||||||
|
if not np.any(usable):
|
||||||
|
report[label]={"availability":"unavailable_degenerate_radius","points":int(mask.sum())}
|
||||||
|
continue
|
||||||
|
tx=-dy[usable]/rr[usable]; ty=dx[usable]/rr[usable]
|
||||||
|
speed=ux[mask][usable]*tx+uy[mask][usable]*ty
|
||||||
|
report[label]={"availability":"available","points":int(usable.sum()),
|
||||||
|
"mean_tangential_speed":float(np.mean(speed)),
|
||||||
|
"max_abs_tangential_speed":float(np.max(np.abs(speed))),
|
||||||
|
"rms_tangential_speed":float(np.sqrt(np.mean(speed*speed)))}
|
||||||
|
return {"definition":"fluid cells adjacent to body; geometric-CCW tangent (-dy,dx)/r",
|
||||||
|
"radius":float(radius),"bodies":report}
|
||||||
|
|
||||||
|
|
||||||
|
def wall_vorticity_production_proxy(x, y, ux, uy, fluid_mask, body_id, centers, radius, *,
|
||||||
|
body_ids=(1, 2), body_labels=None, min_pairs=8):
|
||||||
|
"""Fail-closed wall-vorticity proxy from radial tangential gradient when supported."""
|
||||||
|
x=np.asarray(x,dtype=float); y=np.asarray(y,dtype=float)
|
||||||
|
ux=np.asarray(ux,dtype=float); uy=np.asarray(uy,dtype=float)
|
||||||
|
centers=np.asarray(centers,dtype=float); fluid=np.asarray(fluid_mask,dtype=bool)
|
||||||
|
if isinstance(min_pairs,(bool,np.bool_)) or not isinstance(min_pairs,(int,np.integer)) or min_pairs<1:
|
||||||
|
raise ValueError("min_pairs must be a positive integer")
|
||||||
|
near=_adjacent_fluid_to_bodies(fluid,body_id,body_ids)
|
||||||
|
labels=list(body_labels) if body_labels is not None else [f"body_{i}" for i in body_ids]
|
||||||
|
xx,yy=np.meshgrid(x,y,indexing="xy"); report={}
|
||||||
|
for registry_id,label in zip(body_ids,labels):
|
||||||
|
solid=np.asarray(body_id)==int(registry_id)
|
||||||
|
mask=np.zeros_like(near)
|
||||||
|
mask[:,1:] |= near[:,1:]&solid[:,:-1]; mask[:,:-1] |= near[:,:-1]&solid[:,1:]
|
||||||
|
mask[1:,:] |= near[1:,:]&solid[:-1,:]; mask[:-1,:] |= near[:-1,:]&solid[1:,:]
|
||||||
|
iy,ix=np.nonzero(mask); pairs=[]
|
||||||
|
cx,cy=centers[int(registry_id)]
|
||||||
|
for i,j in zip(iy,ix):
|
||||||
|
dx=xx[i,j]-cx; dy=yy[i,j]-cy; rr=float(np.hypot(dx,dy))
|
||||||
|
if rr<=0: continue
|
||||||
|
nx,ny=dx/rr,dy/rr; tx,ty=-ny,nx
|
||||||
|
# One lattice step outward along the discrete neighbor closest to +n.
|
||||||
|
candidates=((i,j+1,1.,0.),(i,j-1,-1.,0.),(i+1,j,0.,1.),(i-1,j,0.,-1.))
|
||||||
|
best=None; best_dot=-np.inf
|
||||||
|
for ii,jj,sx,sy in candidates:
|
||||||
|
if ii<0 or jj<0 or ii>=fluid.shape[0] or jj>=fluid.shape[1] or not fluid[ii,jj]: continue
|
||||||
|
dot=sx*nx+sy*ny
|
||||||
|
if dot>best_dot: best_dot=dot; best=(ii,jj,float(np.hypot(xx[ii,jj]-xx[i,j],yy[ii,jj]-yy[i,j])))
|
||||||
|
if best is None or best_dot<=0 or best[2]<=0: continue
|
||||||
|
ii,jj,ds=best
|
||||||
|
u_theta0=ux[i,j]*tx+uy[i,j]*ty; u_theta1=ux[ii,jj]*tx+uy[ii,jj]*ty
|
||||||
|
pairs.append((u_theta1-u_theta0)/ds)
|
||||||
|
if len(pairs)<int(min_pairs):
|
||||||
|
report[label]={"availability":"unavailable_insufficient_radial_stencil","points":int(mask.sum()),
|
||||||
|
"gradient_pairs":len(pairs),"min_pairs":int(min_pairs)}
|
||||||
|
continue
|
||||||
|
values=np.asarray(pairs,dtype=float)
|
||||||
|
report[label]={"availability":"available","gradient_pairs":int(len(values)),
|
||||||
|
"mean_du_theta_dn":float(np.mean(values)),
|
||||||
|
"rms_du_theta_dn":float(np.sqrt(np.mean(values*values))),
|
||||||
|
"interpretation":"discrete radial gradient of tangential speed; not BEF/tau"}
|
||||||
|
return {"definition":"fail-closed wall-vorticity production proxy","bodies":report}
|
||||||
|
|
||||||
|
|
||||||
|
def separation_reattachment_estimator(x, y, ux, fluid_mask, body_id, centers, radius, *,
|
||||||
|
body_ids=(1, 2), body_labels=None, min_samples=16):
|
||||||
|
"""Estimate separation/reattachment from near-wall ux sign changes; fail closed if ambiguous."""
|
||||||
|
x=np.asarray(x,dtype=float); y=np.asarray(y,dtype=float); ux=np.asarray(ux,dtype=float)
|
||||||
|
centers=np.asarray(centers,dtype=float); fluid=np.asarray(fluid_mask,dtype=bool)
|
||||||
|
if isinstance(min_samples,(bool,np.bool_)) or not isinstance(min_samples,(int,np.integer)) or min_samples<8:
|
||||||
|
raise ValueError("min_samples must be an integer >= 8")
|
||||||
|
near=_adjacent_fluid_to_bodies(fluid,body_id,body_ids)
|
||||||
|
labels=list(body_labels) if body_labels is not None else [f"body_{i}" for i in body_ids]
|
||||||
|
xx,yy=np.meshgrid(x,y,indexing="xy"); report={}
|
||||||
|
for registry_id,label in zip(body_ids,labels):
|
||||||
|
solid=np.asarray(body_id)==int(registry_id)
|
||||||
|
mask=np.zeros_like(near)
|
||||||
|
mask[:,1:] |= near[:,1:]&solid[:,:-1]; mask[:,:-1] |= near[:,:-1]&solid[:,1:]
|
||||||
|
mask[1:,:] |= near[1:,:]&solid[:-1,:]; mask[:-1,:] |= near[:-1,:]&solid[1:,:]
|
||||||
|
if mask.sum()<int(min_samples):
|
||||||
|
report[label]={"availability":"unavailable_insufficient_near_wall_samples",
|
||||||
|
"points":int(mask.sum()),"min_samples":int(min_samples)}
|
||||||
|
continue
|
||||||
|
cx,cy=centers[int(registry_id)]; theta=np.arctan2(yy[mask]-cy,xx[mask]-cx); order=np.argsort(theta)
|
||||||
|
values=ux[mask][order]; signs=np.sign(values); nonzero=signs!=0
|
||||||
|
if nonzero.sum()<int(min_samples)//2 or np.all(signs[nonzero]==signs[nonzero][0]):
|
||||||
|
report[label]={"availability":"unavailable_no_clear_sign_change","points":int(mask.sum()),
|
||||||
|
"theta_span":float(np.ptp(theta))}
|
||||||
|
continue
|
||||||
|
# Circular adjacent sign flips among nonzero samples.
|
||||||
|
idx=np.flatnonzero(nonzero); seq=signs[idx]; flips=[]
|
||||||
|
for k in range(len(seq)):
|
||||||
|
a=seq[k]; b=seq[(k+1)%len(seq)]
|
||||||
|
if a*b<0: flips.append(float(theta[idx[k]]))
|
||||||
|
if len(flips)<2 or len(flips)>6:
|
||||||
|
report[label]={"availability":"unavailable_ambiguous_topology","points":int(mask.sum()),
|
||||||
|
"sign_changes":len(flips),"angles":flips}
|
||||||
|
continue
|
||||||
|
report[label]={"availability":"available","points":int(mask.sum()),"sign_changes":len(flips),
|
||||||
|
"angles":flips,"observable":"near-wall ux zero crossings; not proved separation line"}
|
||||||
|
return {"definition":"fail-closed separation/reattachment estimator from near-wall ux","bodies":report}
|
||||||
|
|
||||||
|
|
||||||
|
def control_volume_streamwise_momentum_ledger(x, y, ux, uy, rho, fluid_mask, *,
|
||||||
|
x_left, x_right, force_history=None,
|
||||||
|
viscosity=None, cs2=1.0/3.0):
|
||||||
|
"""Incomplete streamwise momentum ledger between two stations; never a closed balance."""
|
||||||
|
x=np.asarray(x,dtype=float); y=np.asarray(y,dtype=float)
|
||||||
|
ux=np.asarray(ux,dtype=float); uy=np.asarray(uy,dtype=float); rho=np.asarray(rho,dtype=float)
|
||||||
|
fluid=np.asarray(fluid_mask,dtype=bool)
|
||||||
|
if ux.shape!=(len(y),len(x)) or uy.shape!=ux.shape or rho.shape!=ux.shape or fluid.shape!=ux.shape:
|
||||||
|
raise ValueError("invalid control-volume fields")
|
||||||
|
if not np.isfinite([x_left,x_right,cs2]).all() or x_right<=x_left or cs2<=0:
|
||||||
|
raise ValueError("invalid control-volume stations or cs2")
|
||||||
|
def station(xq):
|
||||||
|
ix=int(np.argmin(np.abs(x-xq))); rows=fluid[:,ix].copy(); rows[[0,-1]]=False
|
||||||
|
if rows.sum()<3: raise ValueError("control-volume station retains too few fluid rows")
|
||||||
|
yy=y[rows]; uu=ux[rows,ix]; vv=uy[rows,ix]; rr=rho[rows,ix]
|
||||||
|
pressure=cs2*(rr-1.0)
|
||||||
|
return {"x":float(x[ix]),"points":int(rows.sum()),
|
||||||
|
"mass_flux":float(_trapezoid(rr*uu,yy)),
|
||||||
|
"momentum_flux":float(_trapezoid(rr*uu*uu,yy)),
|
||||||
|
"pressure_force_proxy":float(_trapezoid(-pressure,yy)),
|
||||||
|
"pressure_model":"LBM weak-compressible p~cs2(rho-1); not incompressible NS pressure"}
|
||||||
|
left=station(x_left); right=station(x_right)
|
||||||
|
body_force={"availability":"unavailable_no_force_history","total_Fx_mean":None}
|
||||||
|
if force_history is not None:
|
||||||
|
force=np.asarray(force_history,dtype=float)
|
||||||
|
if force.ndim!=2 or force.shape[1]<8 or not np.isfinite(force).all():
|
||||||
|
raise ValueError("force_history must be finite with Fx columns")
|
||||||
|
tail=force[-min(10,len(force)):,2:]
|
||||||
|
total_fx=float(np.mean(tail[:,0]+tail[:,2]+tail[:,4]))
|
||||||
|
body_force={"availability":"available_from_force_history_tail",
|
||||||
|
"tail_samples":int(len(tail)),"total_Fx_mean":total_fx,
|
||||||
|
"normalization":"Celeris force-history mean; not certified CV body integral"}
|
||||||
|
viscous={"availability":"unavailable_gradient_unsupported",
|
||||||
|
"reason":"face viscous stress requires supported wall-normal gradients on both stations"}
|
||||||
|
if viscosity is not None:
|
||||||
|
if not np.isfinite(viscosity) or float(viscosity)<=0:
|
||||||
|
raise ValueError("viscosity must be finite and positive when provided")
|
||||||
|
# Even with nu known, this ledger does not assemble a complete NS stress integral.
|
||||||
|
viscous={"availability":"unavailable_incomplete_stress_tensor",
|
||||||
|
"viscosity":float(viscosity),
|
||||||
|
"note":"viscosity present but full face/wall viscous stress closure is absent"}
|
||||||
|
residual_proxy=None
|
||||||
|
if body_force["total_Fx_mean"] is not None:
|
||||||
|
residual_proxy=float((right["momentum_flux"]+right["pressure_force_proxy"])
|
||||||
|
-(left["momentum_flux"]+left["pressure_force_proxy"])
|
||||||
|
-body_force["total_Fx_mean"])
|
||||||
|
return {"definition":"streamwise momentum ledger between rear-gap plane and observer; incomplete",
|
||||||
|
"balance_status":"incomplete_not_closed",
|
||||||
|
"left":left,"right":right,"body_force":body_force,"viscous_stress":viscous,
|
||||||
|
"residual_proxy_if_partial":residual_proxy,
|
||||||
|
"claim_limit":"NEVER a closed momentum balance; pressure is weak-compressible proxy"}
|
||||||
|
|
||||||
|
|
||||||
|
def torque_power_availability(force_history=None, *, torque_history=None):
|
||||||
|
"""Torque/power are unavailable unless an explicit torque history exists."""
|
||||||
|
if torque_history is not None:
|
||||||
|
tau=np.asarray(torque_history,dtype=float)
|
||||||
|
if tau.ndim!=2 or tau.shape[1]<1 or not np.isfinite(tau).all():
|
||||||
|
raise ValueError("torque_history must be finite 2-D")
|
||||||
|
return {"availability":"available_torque_history","samples":int(len(tau)),
|
||||||
|
"mean_torque":np.mean(tau,axis=0).tolist()}
|
||||||
|
if force_history is not None:
|
||||||
|
return {"availability":"unavailable_no_torque_history",
|
||||||
|
"note":"force_history provides Fx/Fy only; torque/power not inferred"}
|
||||||
|
return {"availability":"unavailable_no_torque_history","note":"no force or torque history provided"}
|
||||||
|
|
||||||
|
|
||||||
|
def fit_linear_composite(observed, components, mask, *, names=None):
|
||||||
|
"""Unweighted vector least squares on listed component fields.
|
||||||
|
|
||||||
|
Each component must share the observed field shape (...,2). Returns
|
||||||
|
coefficients, predicted field, residual, condition number, and dof count.
|
||||||
|
"""
|
||||||
|
obs = np.asarray(observed, dtype=float)
|
||||||
|
m = np.asarray(mask, dtype=bool)
|
||||||
|
if obs.ndim != 3 or obs.shape[-1] != 2 or m.shape != obs.shape[:2] or not m.any():
|
||||||
|
raise ValueError("invalid composite fit inputs")
|
||||||
|
cols = []
|
||||||
|
comp_names = []
|
||||||
|
for item in components:
|
||||||
|
if isinstance(item, tuple):
|
||||||
|
name, field = item
|
||||||
|
else:
|
||||||
|
name, field = None, item
|
||||||
|
field = np.asarray(field, dtype=float)
|
||||||
|
if field.shape != obs.shape or not np.isfinite(field[m]).all():
|
||||||
|
raise ValueError("component shape or values invalid on mask")
|
||||||
|
cols.append(field[m].reshape(-1))
|
||||||
|
comp_names.append(name)
|
||||||
|
if names is not None and list(names) != comp_names:
|
||||||
|
raise ValueError("names must match components")
|
||||||
|
y = obs[m].reshape(-1)
|
||||||
|
A = np.column_stack(cols)
|
||||||
|
coeff, _, rank, sv = np.linalg.lstsq(A, y, rcond=1e-12)
|
||||||
|
pred = np.tensordot(A, coeff, axes=(1, 0)).reshape(obs[m].shape)
|
||||||
|
residual = obs[m] - pred
|
||||||
|
full = np.zeros_like(obs)
|
||||||
|
full[m] = pred.reshape(obs[m].shape)
|
||||||
|
res_full = np.zeros_like(obs)
|
||||||
|
res_full[m] = residual.reshape(obs[m].shape)
|
||||||
|
cond = float(sv[0] / sv[-1]) if sv[-1] > 0 else float("inf")
|
||||||
|
return {
|
||||||
|
"names": comp_names,
|
||||||
|
"coefficients": {n: float(v) for n, v in zip(comp_names, coeff)},
|
||||||
|
"rank": int(rank),
|
||||||
|
"condition_number": cond,
|
||||||
|
"dof": int(len(coeff)),
|
||||||
|
"prediction": full,
|
||||||
|
"residual": res_full,
|
||||||
|
"weighted_RMS": float(np.sqrt(np.mean(np.sum(residual * residual, axis=1)))),
|
||||||
|
"relative_RMS": float(np.sqrt(np.mean(np.sum(residual * residual, axis=1))) /
|
||||||
|
max(float(np.sqrt(np.mean(np.sum(y * y)))), np.finfo(float).eps)),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def profile_station_errors(delta, x, y, station, *, fluid_mask=None, exclude_outer_rows=2):
|
||||||
|
"""Full-profile E_inf/L2 at one x/D station on common fluid rows."""
|
||||||
|
x = np.asarray(x, dtype=float)
|
||||||
|
ix = int(np.argmin(np.abs(x - float(station))))
|
||||||
|
d = np.asarray(delta, dtype=float)[:, ix, :]
|
||||||
|
if fluid_mask is not None:
|
||||||
|
rows = np.asarray(fluid_mask, dtype=bool)[:, ix].copy()
|
||||||
|
if exclude_outer_rows:
|
||||||
|
rows[:exclude_outer_rows] = False
|
||||||
|
rows[-exclude_outer_rows:] = False
|
||||||
|
d = d[rows]
|
||||||
|
if d.size == 0:
|
||||||
|
raise ValueError("empty profile at station")
|
||||||
|
return profile_errors(d[:, 0], d[:, 1], u_inf=1.0)
|
||||||
|
|
||||||
|
|
||||||
|
def held_out_improvement(baseline_residual, candidate_residual, mask):
|
||||||
|
"""Relative held-out L2 improvement of candidate over baseline."""
|
||||||
|
m = np.asarray(mask, dtype=bool)
|
||||||
|
b = np.asarray(baseline_residual, dtype=float)
|
||||||
|
c = np.asarray(candidate_residual, dtype=float)
|
||||||
|
if b.shape != c.shape or m.shape != b.shape[:2]:
|
||||||
|
raise ValueError("held-out fields must align")
|
||||||
|
# unit weights for relative comparison on same mask
|
||||||
|
w = np.ones(m.shape, dtype=float)
|
||||||
|
lb = quadrature_l2(b, m, w)["L2"]
|
||||||
|
lc = quadrature_l2(c, m, w)["L2"]
|
||||||
|
return {"baseline_L2": lb, "candidate_L2": lc,
|
||||||
|
"relative_improvement": float((lb - lc) / max(lb, np.finfo(float).eps)),
|
||||||
|
"significant": bool(lc < 0.9 * lb)}
|
||||||
|
|
||||||
|
|
||||||
|
# NS-first dimensional diagnostics: resolved CPU arrays only.
|
||||||
|
def torque_to_actuator_power(torque, angular_velocity, *, torque_ownership, rho_ref, u_ref, diameter):
|
||||||
|
"""Convert raw 2-D per-span torque and return actuator-to-fluid power."""
|
||||||
|
from .contract import NondimensionalContract
|
||||||
|
tau=np.asarray(torque,float); omega=np.asarray(angular_velocity,float)
|
||||||
|
if tau.ndim not in (1,2) or tau.shape!=omega.shape or not tau.size or not np.isfinite(tau).all() or not np.isfinite(omega).all(): raise ValueError("torque/angular_velocity must be finite aligned samples[,bodies]")
|
||||||
|
if torque_ownership not in ("fluid_on_body","actuator_on_fluid"): raise ValueError("invalid torque_ownership")
|
||||||
|
contract=NondimensionalContract(float(diameter),float(u_ref),float(rho_ref))
|
||||||
|
actuator_tau=-tau if torque_ownership=="fluid_on_body" else tau
|
||||||
|
power=actuator_tau*omega
|
||||||
|
total=power if power.ndim==1 else power.sum(axis=1)
|
||||||
|
torque_scale=.5*rho_ref*u_ref**2*diameter**2
|
||||||
|
power_scale=.5*rho_ref*u_ref**3*diameter
|
||||||
|
raw_c_tau=tau/torque_scale; actuator_c_tau=actuator_tau/torque_scale
|
||||||
|
return {"torque_ownership":torque_ownership,"raw_torque":tau,
|
||||||
|
"raw_torque_interpretation":f"{torque_ownership}; Celeris reads are lattice cut-link sums, mean per accumulated LBM step when normalize=True",
|
||||||
|
"sampling_semantics":{"normalize_true":"mean per accumulated LBM step exactly once","kernel_reduction":"sum of cut-link lattice loads","normalizations_not_applied":["force/torque coefficient","body count","density","dynamic pressure","span"],"span":"two-dimensional per-unit-span"},
|
||||||
|
"sign_contract":"omega>0 geometric CCW; power>0 actuator-to-fluid",
|
||||||
|
"actuator_torque":actuator_tau,"raw_C_tau":raw_c_tau,"actuator_C_tau":actuator_c_tau,
|
||||||
|
"power_per_body":power,"total_power":total,"mean_total_power":float(total.mean()),
|
||||||
|
"torque_scale":float(torque_scale),"power_scale":float(power_scale),
|
||||||
|
"C_P_samples":total/power_scale,"C_P":float(total.mean()/power_scale),
|
||||||
|
"nondimensional_reference":contract.convert()["metadata"],
|
||||||
|
"dimensions":{"torque":"M L^2 T^-2/span","power":"M L^2 T^-3/span","C_tau":"1","C_P":"1"}}
|
||||||
|
|
||||||
|
|
||||||
|
def _masked_line_trapezoid(y, value, mask):
|
||||||
|
y=np.asarray(y,float); v=np.asarray(value,float); m=np.asarray(mask,bool)
|
||||||
|
if y.ndim!=1 or v.shape[0]!=len(y) or m.shape!=y.shape or len(y)<2: raise ValueError("line fields must align")
|
||||||
|
use=m[:-1]&m[1:]
|
||||||
|
if not use.any(): raise ValueError("station mask retains no complete segment")
|
||||||
|
shape=(len(y)-1,)+(1,)*(v.ndim-1); factor=(np.diff(y)*use).reshape(shape)
|
||||||
|
return np.sum(.5*(v[:-1]+v[1:])*factor,axis=0)
|
||||||
|
|
||||||
|
|
||||||
|
def station_flux_metrics(y, ux, uy, rho, pressure, mask, *, normal=(1.,0.), q_in=None):
|
||||||
|
"""Mass, vector momentum, kinetic and mechanical-energy flux across a frozen station normal."""
|
||||||
|
y=np.asarray(y,float); ux=np.asarray(ux,float); uy=np.asarray(uy,float); rho=np.asarray(rho,float); pressure=np.asarray(pressure,float); mask=np.asarray(mask,bool); n=np.asarray(normal,float)
|
||||||
|
if y.ndim!=1 or any(a.shape!=y.shape for a in (ux,uy,rho,pressure,mask)): raise ValueError("station profiles must be aligned 1-D")
|
||||||
|
if n.shape!=(2,) or not np.isfinite(n).all() or not np.isclose(np.linalg.norm(n),1,atol=1e-12,rtol=0): raise ValueError("normal must be a finite unit vector")
|
||||||
|
if np.any(np.diff(y)<=0) or np.any(rho[mask]<=0) or not all(np.isfinite(a[mask]).all() for a in (ux,uy,rho,pressure)): raise ValueError("invalid station values")
|
||||||
|
u=np.column_stack((ux,uy)); un=u@n; speed2=ux**2+uy**2; momentum=rho[:,None]*u*un[:,None]+pressure[:,None]*n
|
||||||
|
out={"mass_flux":float(_masked_line_trapezoid(y,rho*un,mask)),"momentum_flux":np.asarray(_masked_line_trapezoid(y,momentum,mask)),
|
||||||
|
"kinetic_energy_flux":float(_masked_line_trapezoid(y,.5*rho*speed2*un,mask)),"pressure_work_flux":float(_masked_line_trapezoid(y,pressure*un,mask)),
|
||||||
|
"mechanical_energy_flux":float(_masked_line_trapezoid(y,(pressure+.5*rho*speed2)*un,mask)),"normal":n.tolist(),"retained_points":int(mask.sum()),
|
||||||
|
"dimensions":{"mass_flux":"M T^-1/span","momentum_flux":"M L T^-2/span","energy_flux":"M L^2 T^-3/span"}}
|
||||||
|
if q_in is not None:
|
||||||
|
keys=("mass_flux","momentum_flux","kinetic_energy_flux","pressure_work_flux","mechanical_energy_flux")
|
||||||
|
if not isinstance(q_in,dict) or any(k not in q_in for k in keys+("normal",)) or not np.allclose(q_in["normal"],n,atol=1e-12,rtol=0): raise ValueError("q_in must be matched with the same frozen normal")
|
||||||
|
out["matched_q_in"]={k:q_in[k] for k in keys}; out["delta_from_q_in"]={k:out[k]-q_in[k] for k in keys}
|
||||||
|
return out
|
||||||
|
|
||||||
|
|
||||||
|
def volume_viscous_dissipation(x,y,ux,uy,viscosity,mask,*,density=None):
|
||||||
|
"""Integrate 2*mu*S:S over valid centered stencils of a masked rectilinear grid."""
|
||||||
|
x=np.asarray(x,float); y=np.asarray(y,float); ux=np.asarray(ux,float); uy=np.asarray(uy,float); m=np.asarray(mask,bool)
|
||||||
|
if ux.shape!=(len(y),len(x)) or uy.shape!=ux.shape or m.shape!=ux.shape or min(len(x),len(y))<3: raise ValueError("invalid masked rectilinear grid")
|
||||||
|
if np.any(np.diff(x)<=0) or np.any(np.diff(y)<=0) or not np.isfinite(ux[m]).all() or not np.isfinite(uy[m]).all() or not np.isfinite(viscosity) or viscosity<=0: raise ValueError("invalid grid, field, or viscosity")
|
||||||
|
use=np.zeros_like(m); use[1:-1,1:-1]=m[1:-1,1:-1]&m[1:-1,:-2]&m[1:-1,2:]&m[:-2,1:-1]&m[2:,1:-1]
|
||||||
|
if not use.any(): raise ValueError("dissipation mask retains no centered stencil")
|
||||||
|
uxx=(ux[1:-1,2:]-ux[1:-1,:-2])/(x[2:]-x[:-2])[None,:]; uyx=(uy[1:-1,2:]-uy[1:-1,:-2])/(x[2:]-x[:-2])[None,:]
|
||||||
|
uxy=(ux[2:,1:-1]-ux[:-2,1:-1])/(y[2:]-y[:-2])[:,None]; uyy=(uy[2:,1:-1]-uy[:-2,1:-1])/(y[2:]-y[:-2])[:,None]
|
||||||
|
if density is None: mu=np.full(ux.shape,float(viscosity)); kind="dynamic"
|
||||||
|
else:
|
||||||
|
density=np.asarray(density,float)
|
||||||
|
if density.shape!=ux.shape or np.any(density[use]<=0) or not np.isfinite(density[use]).all(): raise ValueError("density must be finite positive and aligned")
|
||||||
|
mu=viscosity*density; kind="kinematic_with_density"
|
||||||
|
phi=np.full(ux.shape,np.nan); core=2*uxx**2+2*uyy**2+(uxy+uyx)**2; core_use=use[1:-1,1:-1]
|
||||||
|
phi[1:-1,1:-1][core_use]=mu[1:-1,1:-1][core_use]*core[core_use]; w=tensor_trapezoid_weights(x,y)
|
||||||
|
requested_area=float(w[m].sum()); usable_area=float(w[use].sum())
|
||||||
|
return {"viscous_dissipation":float(np.sum(w[use]*phi[use])),"dissipation_density":phi,"usable_mask":use,
|
||||||
|
"retained_area":usable_area,"requested_fluid_area":requested_area,"usable_stencil_area":usable_area,
|
||||||
|
"usable_stencil_fraction":usable_area/requested_area,"omitted_fluid_area":requested_area-usable_area,
|
||||||
|
"near_body_omission":"partial_centered_stencil_omission_at_mask_boundaries",
|
||||||
|
"points":int(use.sum()),"requested_fluid_points":int(m.sum()),"viscosity_kind":kind,
|
||||||
|
"definition":"partial CV-fluid integral 2*mu*S:S on usable centered stencils",
|
||||||
|
"balance_status":"partial_not_closed_energy","dimensions":"M L^2 T^-3/span"}
|
||||||
|
|
||||||
|
|
||||||
|
def gap_mass_momentum_flux(velocity,density,center_a,center_b,radius,*,samples=2001):
|
||||||
|
"""Frozen a-to-b orientation; Q_g=int u.n, mass=int rho*u.n, J_g=int rho*(u.n)^2."""
|
||||||
|
base=gap_flux(velocity,center_a,center_b,radius,samples=samples); a=np.asarray(center_a,float); b=np.asarray(center_b,float); length=float(np.linalg.norm(b-a))
|
||||||
|
t=np.linspace(radius,length-radius,int(samples)); points=a+t[:,None]*(b-a)[None,:]/length; v=np.asarray(velocity(points),float); rho=np.asarray(density(points),float)
|
||||||
|
if v.shape!=points.shape or rho.shape!=(len(points),) or np.any(rho<=0) or not np.isfinite(rho).all(): raise ValueError("density must return finite positive gap samples")
|
||||||
|
un=v@np.asarray(base["normal"]); base.update(Q_g=base.pop("flux"),mass_flux=float(_trapezoid(rho*un,t)),J_g=float(_trapezoid(rho*un**2,t)),density_min=float(rho.min()),density_max=float(rho.max()),orientation="center_a to center_b; normal=left(tangent), frozen by argument order",dimensions={"Q_g":"L^2 T^-1","mass_flux":"M T^-1/span","J_g":"M L T^-2/span"})
|
||||||
|
return base
|
||||||
|
|
||||||
|
|
||||||
|
def control_volume_mechanical_energy_ledger(left_station,right_station,*,viscous_dissipation=None,actuator_power_to_fluid=None,lateral_mechanical_energy_flux=None,storage_rate=None):
|
||||||
|
"""Report resolved energy terms; compute residual only when every signed term exists."""
|
||||||
|
def e(q,label):
|
||||||
|
if not isinstance(q,dict) or "mechanical_energy_flux" not in q or not np.isfinite(q["mechanical_energy_flux"]): raise ValueError(f"{label} station lacks finite mechanical_energy_flux")
|
||||||
|
return float(q["mechanical_energy_flux"])
|
||||||
|
left=e(left_station,"left"); right=e(right_station,"right"); supplied={"viscous_dissipation":viscous_dissipation,"actuator_power_to_fluid":actuator_power_to_fluid,"lateral_outward_mechanical_energy_flux":lateral_mechanical_energy_flux,"storage_rate":storage_rate}
|
||||||
|
for v in supplied.values():
|
||||||
|
if v is not None and not np.isfinite(v): raise ValueError("ledger terms must be finite")
|
||||||
|
complete=all(v is not None for v in supplied.values()); residual=None if not complete else float(right-left+lateral_mechanical_energy_flux+viscous_dissipation+storage_rate-actuator_power_to_fluid)
|
||||||
|
resolved={"left_mechanical_energy_flux":left,"right_mechanical_energy_flux":right}; resolved.update({k:{"availability":"available" if v is not None else "unavailable_not_supplied","value":None if v is None else float(v)} for k,v in supplied.items()})
|
||||||
|
return {"definition":"right-left + lateral_out + dissipation + storage - actuator_power_to_fluid = residual","resolved_terms":resolved,"closure_available":complete,"balance_status":"residual_available" if complete else "incomplete_not_closed","residual":residual,"claim_limit":"No closure/residual unless every signed term is supplied."}
|
||||||
|
|
||||||
|
|
||||||
|
def fit_conservation_model(features,target,*,feature_names,feature_dimensions,target_dimension,feature_scales,target_scale,held_out=None):
|
||||||
|
"""Dimension-audited low-dimensional scalar closure fit; never fits velocity fields."""
|
||||||
|
X=np.asarray(features,float); y=np.asarray(target,float); names=list(feature_names); dims=list(feature_dimensions); scales=np.asarray(feature_scales,float)
|
||||||
|
if X.ndim!=2 or y.shape!=(X.shape[0],) or X.shape[1]!=len(names) or len(dims)!=len(names): raise ValueError("features/target metadata dimensions mismatch")
|
||||||
|
if X.shape[0]<=X.shape[1] or not np.isfinite(X).all() or not np.isfinite(y).all(): raise ValueError("finite overdetermined scalar data required")
|
||||||
|
if len(set(names))!=len(names) or any(not isinstance(d,str) or not d for d in dims) or not isinstance(target_dimension,str) or not target_dimension: raise ValueError("explicit names and dimensions required")
|
||||||
|
if scales.shape!=(X.shape[1],) or np.any(scales<=0) or not np.isfinite(scales).all() or not np.isfinite(target_scale) or target_scale<=0: raise ValueError("positive dimensional scales required")
|
||||||
|
A=X/scales; z=y/target_scale; beta,_,rank,sv=np.linalg.lstsq(A,z,rcond=1e-12); coef=target_scale*beta/scales; pred=X@coef; residual=y-pred; dof=len(y)-rank
|
||||||
|
sigma2=float(residual@residual/dof) if dof>0 and rank==X.shape[1] else None; cov=None if sigma2 is None else sigma2*np.linalg.inv(X.T@X)
|
||||||
|
result={"feature_names":names,"feature_dimensions":dims,"target_dimension":target_dimension,"coefficient_dimensions":[f"({target_dimension})/({d})" for d in dims],"coefficients":coef,"rank":int(rank),"condition_number":float(sv[0]/sv[-1]) if sv[-1]>0 else float("inf"),"prediction":pred,"residual":residual,"residual_RMS":float(np.sqrt(np.mean(residual**2))),"coefficient_covariance":cov,"uncertainty_available":cov is not None,"model_kind":"scalar low-dimensional conservation closure"}
|
||||||
|
if held_out is not None:
|
||||||
|
if not isinstance(held_out,(tuple,list)) or len(held_out)!=2: raise ValueError("held_out must be (features,target)")
|
||||||
|
Xh=np.asarray(held_out[0],float); yh=np.asarray(held_out[1],float)
|
||||||
|
if Xh.ndim!=2 or Xh.shape[1]!=X.shape[1] or yh.shape!=(len(Xh),) or not np.isfinite(Xh).all() or not np.isfinite(yh).all(): raise ValueError("held-out dimensions mismatch")
|
||||||
|
ph=Xh@coef; result["held_out"]={"prediction":ph,"target":yh,"residual":yh-ph,"RMS":float(np.sqrt(np.mean((yh-ph)**2))),"samples":int(len(yh))}
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def predict_conservation_model(model,features,*,feature_names,feature_dimensions):
|
||||||
|
"""Predict scalar observables after exact feature-name and dimension validation."""
|
||||||
|
if list(feature_names)!=model.get("feature_names") or list(feature_dimensions)!=model.get("feature_dimensions"): raise ValueError("prediction feature names/dimensions do not match model")
|
||||||
|
X=np.asarray(features,float); c=np.asarray(model["coefficients"],float)
|
||||||
|
if X.ndim not in (1,2) or X.shape[-1]!=len(c) or not np.isfinite(X).all(): raise ValueError("invalid prediction features")
|
||||||
|
return X@c
|
||||||
|
|
||||||
|
|
||||||
|
def weak_compressible_pressure_proxy(rho, *, rho_ref, cs2=1.0/3.0):
|
||||||
|
"""LBM weak-compressible pressure proxy p'=cs2*(rho-rho_ref); never a missing-pressure sentinel."""
|
||||||
|
density=np.asarray(rho,float)
|
||||||
|
if not np.isfinite(density).all() or np.any(density<=0) or not np.isfinite([rho_ref,cs2]).all() or rho_ref<=0 or cs2<=0:
|
||||||
|
raise ValueError("pressure proxy requires finite positive density, rho_ref, and cs2")
|
||||||
|
return {"values":cs2*(density-float(rho_ref)),"model":"weak-compressible pressure proxy p'=cs2*(rho-rho_ref)",
|
||||||
|
"cs2":float(cs2),"rho_ref":float(rho_ref),"availability":"available_weak_compressible_proxy",
|
||||||
|
"claim_limit":"Not incompressible Navier-Stokes pressure; zero values mean rho=rho_ref, never unavailable pressure."}
|
||||||
|
|
||||||
|
|
||||||
|
def rectangular_control_volume_fluxes(x,y,ux,uy,rho,fluid_mask,front_center,diameter,*,
|
||||||
|
rho_ref,cs2=1.0/3.0,viscosity=None):
|
||||||
|
"""Four-face endpoint spatial flux ledger on the nearest body-enclosing lattice rectangle."""
|
||||||
|
x=np.asarray(x,float); y=np.asarray(y,float); ux=np.asarray(ux,float); uy=np.asarray(uy,float)
|
||||||
|
rho=np.asarray(rho,float); fluid=np.asarray(fluid_mask,bool); center=np.asarray(front_center,float)
|
||||||
|
shape=(len(y),len(x))
|
||||||
|
if any(a.shape!=shape for a in (ux,uy,rho,fluid)) or center.shape!=(2,) or min(len(x),len(y))<3:
|
||||||
|
raise ValueError("invalid control-volume grid or front center")
|
||||||
|
if np.any(np.diff(x)<=0) or np.any(np.diff(y)<=0) or not np.isfinite([*x,*y,*center,diameter,rho_ref,cs2]).all() or diameter<=0:
|
||||||
|
raise ValueError("control-volume coordinates and references must be finite and valid")
|
||||||
|
targets=(center[0]-2*diameter,center[0]+10*diameter,center[1]-3*diameter,center[1]+3*diameter)
|
||||||
|
il,ir,jb,jt=(int(np.argmin(abs(x-targets[0]))),int(np.argmin(abs(x-targets[1]))),
|
||||||
|
int(np.argmin(abs(y-targets[2]))),int(np.argmin(abs(y-targets[3]))))
|
||||||
|
if not (0<il<ir<len(x)-1 and 0<jb<jt<len(y)-1):
|
||||||
|
raise ValueError("requested control-volume faces lack an interior lattice margin")
|
||||||
|
pressure=weak_compressible_pressure_proxy(rho,rho_ref=rho_ref,cs2=cs2); p=pressure["values"]
|
||||||
|
if viscosity is not None and (not np.isfinite(viscosity) or viscosity<=0): raise ValueError("viscosity must be finite positive")
|
||||||
|
face_specs=(("left",np.arange(jb,jt+1),np.full(jt-jb+1,il),y[jb:jt+1],np.array([-1.,0.])),
|
||||||
|
("right",np.arange(jb,jt+1),np.full(jt-jb+1,ir),y[jb:jt+1],np.array([1.,0.])),
|
||||||
|
("bottom",np.full(ir-il+1,jb),np.arange(il,ir+1),x[il:ir+1],np.array([0.,-1.])),
|
||||||
|
("top",np.full(ir-il+1,jt),np.arange(il,ir+1),x[il:ir+1],np.array([0.,1.])))
|
||||||
|
faces={}; totals={"mass_flux":0.,"momentum_convective":np.zeros(2),"momentum_pressure":np.zeros(2),
|
||||||
|
"kinetic_energy_flux":0.,"pressure_work":0.}
|
||||||
|
if viscosity is not None: totals.update(viscous_traction=np.zeros(2),viscous_work=0.)
|
||||||
|
for name,iy,ix,coord,n in face_specs:
|
||||||
|
if not fluid[iy,ix].all(): raise ValueError(f"control-volume {name} face contains nonfluid points")
|
||||||
|
u=np.column_stack((ux[iy,ix],uy[iy,ix])); rr=rho[iy,ix]; pp=p[iy,ix]; un=u@n; speed2=np.sum(u*u,axis=1)
|
||||||
|
values={"mass_flux":rr*un,"momentum_convective":rr[:,None]*u*un[:,None],
|
||||||
|
"momentum_pressure":pp[:,None]*n,"kinetic_energy_flux":.5*rr*speed2*un,"pressure_work":pp*un}
|
||||||
|
if viscosity is not None:
|
||||||
|
# Centered gradients require fluid support on all four axial neighbors, including face tangents.
|
||||||
|
support=fluid[iy,ix]&fluid[iy,ix-1]&fluid[iy,ix+1]&fluid[iy-1,ix]&fluid[iy+1,ix]
|
||||||
|
if not support.all(): raise ValueError(f"control-volume {name} face has unsupported velocity gradients")
|
||||||
|
dux_dx=(ux[iy,ix+1]-ux[iy,ix-1])/(x[ix+1]-x[ix-1]); duy_dx=(uy[iy,ix+1]-uy[iy,ix-1])/(x[ix+1]-x[ix-1])
|
||||||
|
dux_dy=(ux[iy+1,ix]-ux[iy-1,ix])/(y[iy+1]-y[iy-1]); duy_dy=(uy[iy+1,ix]-uy[iy-1,ix])/(y[iy+1]-y[iy-1])
|
||||||
|
mu=float(viscosity)*rr; traction=np.column_stack((mu*(2*dux_dx*n[0]+(dux_dy+duy_dx)*n[1]),mu*((dux_dy+duy_dx)*n[0]+2*duy_dy*n[1])))
|
||||||
|
values.update(viscous_traction=traction,viscous_work=np.sum(traction*u,axis=1))
|
||||||
|
integrated={key:np.asarray(_trapezoid(value,coord)) for key,value in values.items()}
|
||||||
|
integrated["momentum_total"]=integrated["momentum_convective"]+integrated["momentum_pressure"]
|
||||||
|
for key,value in integrated.items():
|
||||||
|
if key!="momentum_total": totals[key]+=value
|
||||||
|
faces[name]={key:(float(value) if value.ndim==0 else value) for key,value in integrated.items()}
|
||||||
|
faces[name].update(normal=n.tolist(),points=int(len(coord)),actual_lattice_coordinate=float(x[ix[0]]) if name in ("left","right") else float(y[iy[0]]))
|
||||||
|
totals["momentum_total"]=totals["momentum_convective"]+totals["momentum_pressure"]
|
||||||
|
bounds={"requested_front_center_relative_x_over_D":[-2.,10.],"requested_front_center_relative_y_over_D":[-3.,3.],
|
||||||
|
"actual":{"x":[float(x[il]),float(x[ir])],"y":[float(y[jb]),float(y[jt])]},
|
||||||
|
"actual_front_center_relative":{"x_over_D":[float((x[il]-center[0])/diameter),float((x[ir]-center[0])/diameter)],"y_over_D":[float((y[jb]-center[1])/diameter),float((y[jt]-center[1])/diameter)]}}
|
||||||
|
return {"definition":"four-face outward endpoint spatial flux ledger on one body-enclosing rectangular CV","bounds":bounds,"faces":faces,"outward_totals":totals,
|
||||||
|
"pressure":{k:v for k,v in pressure.items() if k!="values"},"viscous_terms":{"availability":"available_centered_fluid_stencils" if viscosity is not None else "unavailable_viscosity_not_supplied"},
|
||||||
|
"balance_status":"endpoint_spatial_partial_not_closed","claim_limit":"Endpoint spatial terms only; storage and body-adjacent contributions are unavailable, so this is not a closed mechanical-energy balance."}
|
||||||
|
|
||||||
|
|
||||||
|
# Stage-3 transient-analysis primitives.
|
||||||
|
def common_mask_field_norms(x, y, reference_velocity, candidate_velocity, common_mask, *, contract):
|
||||||
|
"""Area-quadrature L2* and pointwise Linf* on one declared common mask."""
|
||||||
|
from .contract import NondimensionalContract
|
||||||
|
if not isinstance(contract, NondimensionalContract):
|
||||||
|
raise TypeError("contract must be a NondimensionalContract")
|
||||||
|
x=np.asarray(x,float); y=np.asarray(y,float); ref=np.asarray(reference_velocity,float); cand=np.asarray(candidate_velocity,float); mask=np.asarray(common_mask,bool)
|
||||||
|
if ref.shape!=cand.shape or ref.shape!=(len(y),len(x),2) or mask.shape!=ref.shape[:2]: raise ValueError("field arrays and common mask must align as (y,x,2)/(y,x)")
|
||||||
|
if not mask.any() or not np.isfinite(ref[mask]).all() or not np.isfinite(cand[mask]).all(): raise ValueError("common mask must retain finite fields")
|
||||||
|
xs=contract.convert(x=x)["x_star"]; ys=contract.convert(x=y)["x_star"]
|
||||||
|
weights=tensor_trapezoid_weights(xs,ys); delta=(cand-ref)/contract.u_inf
|
||||||
|
l2=quadrature_l2(delta,mask,weights)
|
||||||
|
return {"L2_star":l2["L2"],"Linf_star":float(np.max(np.linalg.norm(delta[mask],axis=1))),
|
||||||
|
"retained_area_star":l2["retained_measure"],"retained_points":l2["points"],
|
||||||
|
"definition":"L2*=sqrt(integral_common |(u_candidate-u_reference)/U_inf|^2 dA/D^2); Linf*=max_common vector magnitude"}
|
||||||
|
|
||||||
|
|
||||||
|
def branch_order(d_reverse, d_accepted, *, previous=None, margin=0.05, hysteresis=0.02):
|
||||||
|
"""Endpoint-field branch order; never inferred from crossings of unrelated norms."""
|
||||||
|
dr=np.asarray(d_reverse,float); da=np.asarray(d_accepted,float)
|
||||||
|
if dr.shape!=da.shape or not np.isfinite(dr).all() or not np.isfinite(da).all() or np.any(dr<0) or np.any(da<0): raise ValueError("branch distances must be aligned finite nonnegative values")
|
||||||
|
if not np.isfinite([margin,hysteresis]).all() or margin<0 or hysteresis<0 or margin+hysteresis>=1: raise ValueError("invalid branch margin/hysteresis")
|
||||||
|
den=dr*dr+da*da
|
||||||
|
if np.any(den<=0): raise ValueError("branch order undefined when both endpoint distances vanish")
|
||||||
|
value=(dr*dr-da*da)/den
|
||||||
|
state=np.zeros(value.shape,dtype=np.int8)
|
||||||
|
prior=0 if previous is None else int(previous)
|
||||||
|
if prior not in (-1,0,1): raise ValueError("previous branch state must be -1, 0, or 1")
|
||||||
|
for i,v in enumerate(value.flat):
|
||||||
|
threshold=margin+hysteresis if prior==0 else max(0.,margin-hysteresis)
|
||||||
|
if prior==1: prior=1 if v>=-threshold else -1
|
||||||
|
elif prior==-1: prior=-1 if v<=threshold else 1
|
||||||
|
else: prior=1 if v>threshold else (-1 if v<-threshold else 0)
|
||||||
|
state.flat[i]=prior
|
||||||
|
return {"m":value,"state":state,"state_meaning":{"1":"accepted-nearer","-1":"reverse-nearer","0":"unresolved"},
|
||||||
|
"margin":float(margin),"hysteresis":float(hysteresis),"definition":"m=(dR^2-dA^2)/(dR^2+dA^2); state uses margin and Schmitt hysteresis"}
|
||||||
|
|||||||
@@ -0,0 +1,229 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""CPU-only, lattice-exact reader plots from authenticated steady evidence."""
|
||||||
|
from __future__ import annotations
|
||||||
|
import argparse,csv,hashlib,json,os,shutil,tempfile
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from pathlib import Path
|
||||||
|
import matplotlib
|
||||||
|
matplotlib.use("Agg")
|
||||||
|
import matplotlib.pyplot as plt
|
||||||
|
from matplotlib.colorbar import ColorbarBase
|
||||||
|
from matplotlib.colors import BoundaryNorm,ListedColormap
|
||||||
|
from matplotlib.patches import Circle
|
||||||
|
import numpy as np
|
||||||
|
from PIL import Image
|
||||||
|
from steady_pinball_theory.metrics import conservative_masked_vorticity,tensor_trapezoid_weights,profile_errors
|
||||||
|
from steady_pinball_theory.runner import load_validate_artifact
|
||||||
|
from steady_pinball_theory.theory import affine_velocity,strip_basis_affine
|
||||||
|
|
||||||
|
HERE=Path(__file__).resolve().parent
|
||||||
|
DEFAULT_STEADY=Path("/home/frank14f/optane/DynamisLab/steady_pinball_theory/authoritative/steady-contract-v5-torque-d20-20260808")
|
||||||
|
DEFAULT_STRIP=Path("/home/frank14f/optane/DynamisLab/steady_pinball_theory/authoritative/steady-strip-modal-analysis-20260804-v4/analysis.json")
|
||||||
|
DEFAULT_LEDGER=Path("/home/frank14f/optane/DynamisLab/steady_pinball_theory/authoritative/ns-ledger-v2-oracle-bound-d20-20260809/analysis.json")
|
||||||
|
DEFAULT_OUTPUT=HERE/"figures"; STATIONS=(2.,4.,6.,10.); DPI=100; PRIMARY=0.0231672176752314
|
||||||
|
@dataclass
|
||||||
|
class Case:
|
||||||
|
id:str; label:str; family:str; pname:str; parameter:float|None
|
||||||
|
ux:np.ndarray; uy:np.ndarray; fluid:np.ndarray; vort:np.ndarray; vort_mask:np.ndarray
|
||||||
|
source_path:str; source_hash:str
|
||||||
|
|
||||||
|
def sha(path):
|
||||||
|
h=hashlib.sha256()
|
||||||
|
with Path(path).open("rb") as f:
|
||||||
|
for b in iter(lambda:f.read(1<<20),b""): h.update(b)
|
||||||
|
return h.hexdigest()
|
||||||
|
def discrete_colormap(name,lo,hi,bins,*,center_white=False):
|
||||||
|
if not lo<hi or bins<3: raise ValueError("invalid discrete colormap")
|
||||||
|
if center_white and (bins%2==0 or not np.isclose(lo,-hi)): raise ValueError("center-white needs odd symmetric bins")
|
||||||
|
edges=np.linspace(lo,hi,bins+1); colors=plt.get_cmap(name)(np.linspace(0,1,bins))
|
||||||
|
if center_white: colors[bins//2]=(1,1,1,1)
|
||||||
|
return edges,BoundaryNorm(edges,bins,clip=True),ListedColormap(colors)
|
||||||
|
def crop_indices(x,y,bounds=(-2,10,-3,3)):
|
||||||
|
a,b,c,d=bounds
|
||||||
|
# Requested endpoints are mapped to the nearest retained lattice coordinates;
|
||||||
|
# this preserves the authoritative half-cell-centered y grid (239..359).
|
||||||
|
i0,i1=int(np.argmin(abs(x-a))),int(np.argmin(abs(x-b))); j0,j1=int(np.argmin(abs(y-c))),int(np.argmin(abs(y-d)))
|
||||||
|
ix=np.arange(min(i0,i1),max(i0,i1)+1); iy=np.arange(min(j0,j1),max(j0,j1)+1)
|
||||||
|
if len(ix)<3 or len(iy)<3: raise ValueError("crop retains too few lattice coordinates")
|
||||||
|
return ix,iy
|
||||||
|
def area_weighted_vector_l2(du,dv,mask,weights,u_inf):
|
||||||
|
du=np.asarray(du,float); dv=np.asarray(dv,float); mask=np.asarray(mask,bool); weights=np.asarray(weights,float)
|
||||||
|
if du.shape!=dv.shape or mask.shape!=du.shape or weights.shape!=du.shape or u_inf<=0 or not mask.any(): raise ValueError("invalid L2 inputs")
|
||||||
|
return float(np.sqrt(np.sum(weights[mask]*(du[mask]**2+dv[mask]**2))/np.sum(weights[mask]))/u_inf)
|
||||||
|
def solid_mask(x,y,centers,radius):
|
||||||
|
X,Y=np.meshgrid(x,y,indexing="xy"); out=np.zeros(X.shape,bool)
|
||||||
|
for cx,cy in centers: out|=(X-cx)**2+(Y-cy)**2<=radius**2
|
||||||
|
return out
|
||||||
|
def edges(a):
|
||||||
|
m=(a[:-1]+a[1:])/2; return np.r_[a[0]-(a[1]-a[0])/2,m,a[-1]+(a[-1]-a[-2])/2]
|
||||||
|
def axes(nx,ny,xe,ye):
|
||||||
|
f=plt.figure(figsize=(nx/DPI,ny/DPI),dpi=DPI,facecolor="white"); a=f.add_axes([0,0,1,1]); a.set(xlim=(xe[0],xe[-1]),ylim=(ye[0],ye[-1])); a.set_aspect("auto"); a.axis("off"); return f,a
|
||||||
|
def bodies(ax,centers,radius,fill):
|
||||||
|
for x,y in centers: ax.add_patch(Circle((x,y),radius,fc="white" if fill else "none",ec="black",lw=.45,zorder=20))
|
||||||
|
def check_image(path,nx,ny):
|
||||||
|
with Image.open(path) as im:
|
||||||
|
if im.size!=(nx,ny): raise ValueError(f"pixel contract failed: {path}: {im.size}")
|
||||||
|
def save_scalar(path,data,mask,color,xe,ye,centers,radius):
|
||||||
|
ed,norm,cmap=color; ny,nx=data.shape; rgba=cmap(norm(np.nan_to_num(data,nan=ed[0]))); rgba[~mask]=(1,1,1,1)
|
||||||
|
f,a=axes(nx,ny,xe,ye); a.imshow(rgba,origin="lower",interpolation="nearest",extent=(xe[0],xe[-1],ye[0],ye[-1]),aspect="auto"); bodies(a,centers,radius,False)
|
||||||
|
f.savefig(path,dpi=DPI,pad_inches=0,metadata={"Software":"DynamisLab plot_results.py"}); plt.close(f); check_image(path,nx,ny)
|
||||||
|
def save_stream(path,x,y,u,v,fluid,xe,ye,centers,radius):
|
||||||
|
ny,nx=u.shape; f,a=axes(nx,ny,xe,ye); a.streamplot(x,y,np.ma.array(u,mask=~fluid),np.ma.array(v,mask=~fluid),color="black",density=1.35,linewidth=.32,arrowsize=.55,minlength=.08,maxlength=4)
|
||||||
|
bodies(a,centers,radius,True); f.savefig(path,dpi=DPI,pad_inches=0,metadata={"Software":"DynamisLab plot_results.py"}); plt.close(f); check_image(path,nx,ny)
|
||||||
|
def save_bar(path,kind,orientation,color):
|
||||||
|
ed,norm,cmap=color
|
||||||
|
if orientation=="horizontal": f=plt.figure(figsize=(7.2,1.15),dpi=150); a=f.add_axes([.08,.48,.84,.24])
|
||||||
|
else: f=plt.figure(figsize=(1.65,4.8),dpi=150); a=f.add_axes([.22,.08,.22,.84])
|
||||||
|
cb=ColorbarBase(a,cmap=cmap,norm=norm,boundaries=ed,orientation=orientation,spacing="proportional"); cb.set_label(r"vector error $|q-q_{in}|/U_\infty$" if kind=="error" else r"vorticity $\omega D/U_\infty$")
|
||||||
|
f.savefig(path,dpi=150,pad_inches=0); plt.close(f)
|
||||||
|
def profile(du,dv,mask,y,U):
|
||||||
|
e1=np.abs(du[mask])/U; e2=np.abs(dv[mask])/U; e=np.hypot(e1,e2); yy=y[mask]; den=np.trapezoid(np.ones_like(yy),yy)
|
||||||
|
l2=lambda z:float(np.sqrt(np.trapezoid(z*z,yy)/den))
|
||||||
|
return {"E_L2_x":l2(e1),"E_L2_y":l2(e2),"E_L2_vector":l2(e),"E_inf_x":float(e1.max()),"E_inf_y":float(e2.max()),"E_inf_vector":float(e.max()),"common_fluid_points":int(mask.sum())}
|
||||||
|
def write_csv(path,rows):
|
||||||
|
with path.open("w",newline="",encoding="utf-8") as f: w=csv.DictWriter(f,fieldnames=list(rows[0]),lineterminator="\n"); w.writeheader(); w.writerows(rows)
|
||||||
|
def summary_save(f,root,name):
|
||||||
|
f.savefig(root/f"{name}.png",dpi=180,bbox_inches="tight"); f.savefig(root/f"{name}.pdf",bbox_inches="tight",metadata={"CreationDate":None,"ModDate":None}); plt.close(f)
|
||||||
|
def publish(stage,out,overwrite):
|
||||||
|
if out.exists() and not overwrite: raise FileExistsError(f"refusing to clobber {out}; pass --overwrite")
|
||||||
|
old=None
|
||||||
|
if out.exists(): old=out.parent/f".{out.name}.old-{os.getpid()}"; os.replace(out,old)
|
||||||
|
try: os.replace(stage,out)
|
||||||
|
except Exception:
|
||||||
|
if old: os.replace(old,out)
|
||||||
|
raise
|
||||||
|
if old: shutil.rmtree(old)
|
||||||
|
def annotation(path,c,m):
|
||||||
|
f=plt.figure(figsize=(5.2,1.65),dpi=150); a=f.add_axes([0,0,1,1]); a.axis("off")
|
||||||
|
text=(c.label+"\n"+r"$E_{L2}^{crop}=\sqrt{\int|q-q_{in}|^2dA/\int dA}/U_\infty$"+f" = {m['crop_L2_vector']:.6g}\n"+f"x/D={m['x10_actual']:.6g}: E_L2={m['x10_E_L2_vector']:.6g}; E_inf={m['x10_E_inf_vector']:.16g}")
|
||||||
|
a.text(.03,.5,text,va="center",fontsize=9,bbox={"boxstyle":"round,pad=.5","fc":"white","ec":"black"}); f.savefig(path,dpi=150,transparent=True,pad_inches=0); plt.close(f)
|
||||||
|
def save_plate(path,raw,title,metrics):
|
||||||
|
image=plt.imread(raw); f=plt.figure(figsize=(10,5.8),dpi=150,facecolor="white")
|
||||||
|
ax=f.add_axes([.055,.22,.89,.66]); ax.imshow(image,origin="upper",interpolation="nearest",aspect="auto"); ax.axis("off"); ax.set_title(title,loc="left",fontsize=12,pad=8)
|
||||||
|
text=(f"crop E_L2,vector = {metrics['crop_L2_vector']:.6g} "
|
||||||
|
f"x/D={metrics['x10_actual']:.6g}: E_L2,vector = {metrics['x10_E_L2_vector']:.6g} "
|
||||||
|
f"E_inf,vector = {metrics['x10_E_inf_vector']:.16g}")
|
||||||
|
f.text(.5,.095,text,ha="center",va="center",fontsize=9,bbox={"boxstyle":"round,pad=.45","fc":"white","ec":"#444444","lw":.8})
|
||||||
|
f.savefig(path.with_suffix(".png"),dpi=150,metadata={"Software":"DynamisLab plot_results.py"}); f.savefig(path.with_suffix(".pdf"),metadata={"CreationDate":None,"ModDate":None}); plt.close(f)
|
||||||
|
|
||||||
|
def build(a):
|
||||||
|
steady=a.steady.resolve(strict=True); strip=a.strip.resolve(strict=True); ledger=a.ledger.resolve(strict=True); out=a.output.resolve(); out.parent.mkdir(parents=True,exist_ok=True)
|
||||||
|
if out.exists() and not a.overwrite: raise FileExistsError(f"refusing to clobber {out}; pass --overwrite")
|
||||||
|
S=json.loads(strip.read_text()); L=json.loads(ledger.read_text()); loaded={n:load_validate_artifact(steady/n) for n in ("qin","stationary","accepted-s3.55","reverse-s5.2")}
|
||||||
|
qm,q=loaded["qin"]; spec=loaded["accepted-s3.55"][0]["spec"]; D=float(spec["D"]); U=float(spec["config_lbm"]["physics"]["velocity"]); origin=np.asarray(spec["centers_xy"][0],float)
|
||||||
|
centers=(np.asarray(spec["centers_xy"],float)-origin)/D; radius=float(spec["radius"])/D; xf=(q["x"]-origin[0])/D; yf=(q["y"]-origin[1])/D; ix,iy=crop_indices(xf,yf); sl=np.ix_(iy,ix); x=xf[ix]; y=yf[iy]; xe,ye=edges(x),edges(y)
|
||||||
|
qu,qv,qfluid=q["ux"][sl],q["uy"][sl],q["fluid_mask"][sl]; afluid=~solid_mask(x,y,centers,radius); X,Y=np.meshgrid(x,y,indexing="xy")
|
||||||
|
pr=S["preregistration"]; aff=strip_basis_affine(centers,radius,"rear_opposite",H=pr["H_D"],u_inf=1,n_boundary=pr["n_boundary"],source_ratio=pr["source_ratio"],image_layers=40); pts=np.column_stack((X[afluid],Y[afluid])); z=affine_velocity(aff,0,pts); d=affine_velocity(aff,1,pts)-z; g=S["finite_re_reconciliation"]
|
||||||
|
cases=[]
|
||||||
|
for cid,label,val in (("mfs-zero","potential MFS, zero circulation",0.),("mfs-gopt","potential favorable g_opt",g["theoretical_rear_opposite_g_opt_layer40"]),("mfs-gfit","potential finite-Re fitted g",g["actual_wls_g"])):
|
||||||
|
uv=np.zeros(X.shape+(2,)); uv[afluid]=z+val*d; u,v=uv[...,0]*U,uv[...,1]*U; om=np.where(afluid,0.0,np.nan); vm=afluid.copy(); cases.append(Case(cid,label,"potential","g",float(val),u,v,afluid,om,vm,str(strip),sha(strip)))
|
||||||
|
for cid,label,n in (("cfd-stationary","stationary CFD (unsteady reference)","stationary"),("cfd-accepted-s3p55","accepted CFD, s=3.55","accepted-s3.55"),("cfd-reverse-s5p2","stable reverse CFD, s=5.2","reverse-s5.2")):
|
||||||
|
m,r=loaded[n]; om,vm=conservative_masked_vorticity(r["x"],r["y"],r["ux"],r["uy"],r["fluid_mask"]); cases.append(Case(cid,label,"CFD","s",m["spec"].get("s"),r["ux"][sl],r["uy"][sl],r["fluid_mask"][sl]&qfluid,om[sl]*D/U,vm[sl],str(steady/n/"endpoint.npz"),m["files"]["endpoint.npz"]["sha256"]))
|
||||||
|
# Profile exports use the complete CFD y axis. Potential values are evaluated
|
||||||
|
# only on the exact three CFD x columns needed by each centered-curl stencil.
|
||||||
|
profile_fields={}
|
||||||
|
for c in cases:
|
||||||
|
fields={}
|
||||||
|
for req in STATIONS:
|
||||||
|
jf=int(np.argmin(abs(xf-req))); cols=np.arange(jf-1,jf+2); xp=xf[cols]; full_fluid=~solid_mask(xp,yf,centers,radius)
|
||||||
|
if c.family=="potential":
|
||||||
|
PX,PY=np.meshgrid(xp,yf,indexing="xy"); pp=np.column_stack((PX[full_fluid],PY[full_fluid]))
|
||||||
|
amp=c.parameter; vv=np.zeros(PX.shape+(2,)); vv[full_fluid]=affine_velocity(aff,amp,pp); pu,pv=vv[...,0]*U,vv[...,1]*U; pom=np.where(full_fluid,0.0,np.nan); pvm=full_fluid.copy()
|
||||||
|
fields[req]=(float(xf[jf]),pu[:,1],pv[:,1],full_fluid[:,1]&q["fluid_mask"][:,jf],pom[:,1],pvm[:,1])
|
||||||
|
else:
|
||||||
|
source={"cfd-stationary":"stationary","cfd-accepted-s3p55":"accepted-s3.55","cfd-reverse-s5p2":"reverse-s5.2"}[c.id]; rr=loaded[source][1]; om,vm=conservative_masked_vorticity(rr["x"],rr["y"],rr["ux"],rr["uy"],rr["fluid_mask"]); fields[req]=(float(xf[jf]),rr["ux"][:,jf],rr["uy"][:,jf],rr["fluid_mask"][:,jf]&q["fluid_mask"][:,jf],om[:,jf]*D/U,vm[:,jf])
|
||||||
|
profile_fields[c.id]=fields
|
||||||
|
weights=tensor_trapezoid_weights(x,y); metrics=[]; long=[]; stations=[]; errors={"potential":[],"CFD":[]}
|
||||||
|
for c in cases:
|
||||||
|
common=c.fluid&qfluid; err=np.where(common,np.hypot(c.ux-qu,c.uy-qv)/U,np.nan); errors[c.family].append(err[common]); by={}
|
||||||
|
for req in STATIONS:
|
||||||
|
xa,pu,pv,pm,pom,pvm=profile_fields[c.id][req]; jf=int(np.argmin(abs(xf-req))); r=profile(pu-q["ux"][:,jf],pv-q["uy"][:,jf],pm,yf,U)
|
||||||
|
# Preserve the frozen endpoint metric's arithmetic order exactly for
|
||||||
|
# pointwise Linf identity, while retaining trapezoid-weighted L2.
|
||||||
|
direct=profile_errors(U+pu[pm]-q["ux"][pm,jf],pv[pm]-q["uy"][pm,jf],u_inf=U)
|
||||||
|
r.update(E_inf_x=direct["E_inf_x"],E_inf_y=direct["E_inf_y"],E_inf_vector=direct["E_inf_vector"])
|
||||||
|
r.update(case=c.id,label=c.label,family=c.family,parameter_name=c.pname,parameter="" if c.parameter is None else c.parameter,x_over_D=xa,requested_x_over_D=req,source_sha256=c.source_hash); stations.append(r); by[req]=r
|
||||||
|
for i,yd in enumerate(yf):
|
||||||
|
ok=bool(pm[i]); vk=bool(pvm[i]); long.append({"case":c.id,"label":c.label,"family":c.family,"parameter_name":c.pname,"parameter":"" if c.parameter is None else c.parameter,"x_over_D":xa,"requested_x_over_D":req,"y_over_D":float(yd),"ux_over_U":float(pu[i]/U) if ok else "","uy_over_U":float(pv[i]/U) if ok else "","vector_error":float(np.hypot(pu[i]-q["ux"][i,jf],pv[i]-q["uy"][i,jf])/U) if ok else "","vorticity_star":float(pom[i]) if vk else "","fluid_mask":int(ok),"source_sha256":c.source_hash})
|
||||||
|
p=by[10.]; row={"case":c.id,"label":c.label,"family":c.family,"parameter_name":c.pname,"parameter":"" if c.parameter is None else c.parameter,"crop_L2_vector":area_weighted_vector_l2(c.ux-qu,c.uy-qv,common,weights,U),"x10_actual":p["x_over_D"],"x10_E_L2_x":p["E_L2_x"],"x10_E_L2_y":p["E_L2_y"],"x10_E_L2_vector":p["E_L2_vector"],"x10_E_inf_x":p["E_inf_x"],"x10_E_inf_y":p["E_inf_y"],"x10_E_inf_vector":p["E_inf_vector"],"error_max":float(np.nanmax(err)),"vorticity_abs_max":float(np.max(np.abs(c.vort[c.vort_mask]))),"source_path":c.source_path,"source_sha256":c.source_hash,"crop_L2_definition":"sqrt(integral common-fluid |q_case-q_in|^2 dA / integral common-fluid dA)/U_inf","profile_definition":"direct q_case-q_in; trapezoid L2 and pointwise Linf"}; metrics.append(row)
|
||||||
|
if c.id=="cfd-accepted-s3p55" and p["E_inf_vector"]!=PRIMARY: raise ValueError(f"primary identity mismatch: {p['E_inf_vector']!r}")
|
||||||
|
elims={family:float(np.percentile(np.concatenate(values),97.5)) for family,values in errors.items()}
|
||||||
|
ecs={family:discrete_colormap("Greys",0,limit,12) for family,limit in elims.items()}
|
||||||
|
vlim=float(np.percentile(np.concatenate([np.abs(c.vort[c.vort_mask]) for c in cases if c.family=="CFD"]),97.5)); vc=discrete_colormap("RdBu_r",-vlim,vlim,21,center_white=True)
|
||||||
|
stage=Path(tempfile.mkdtemp(prefix=f".{out.name}.stage-",dir=out.parent))
|
||||||
|
try:
|
||||||
|
for dname in ("fields/error","fields/vorticity","fields/streamlines","plates/error","plates/vorticity","plates/streamlines","colorbars","annotations","summary"): (stage/dname).mkdir(parents=True,exist_ok=True)
|
||||||
|
write_csv(stage/"field_metrics.csv",metrics); write_csv(stage/"profiles_long.csv",long)
|
||||||
|
for c,m in zip(cases,metrics):
|
||||||
|
common=c.fluid&qfluid; err=np.where(common,np.hypot(c.ux-qu,c.uy-qv)/U,np.nan)
|
||||||
|
raw_error=stage/"fields/error"/f"{c.id}.png"; raw_vort=stage/"fields/vorticity"/f"{c.id}.png"; raw_stream=stage/"fields/streamlines"/f"{c.id}.png"
|
||||||
|
save_scalar(raw_error,err,common,ecs[c.family],xe,ye,centers,radius); save_scalar(raw_vort,c.vort,c.vort_mask,vc,xe,ye,centers,radius); save_stream(raw_stream,x,y,c.ux/U,c.uy/U,c.fluid,xe,ye,centers,radius); annotation(stage/"annotations"/f"{c.id}_metrics.png",c,m)
|
||||||
|
save_plate(stage/"plates/error"/c.id,raw_error,c.label+" — vector error",m); save_plate(stage/"plates/vorticity"/c.id,raw_vort,c.label+" — vorticity",m); save_plate(stage/"plates/streamlines"/c.id,raw_stream,c.label+" — total-velocity streamlines",m)
|
||||||
|
for family,color in ecs.items():
|
||||||
|
for o in ("horizontal","vertical"): save_bar(stage/"colorbars"/f"error_{family.lower()}_{o}.png","error",o,color)
|
||||||
|
for o in ("horizontal","vertical"): save_bar(stage/"colorbars"/f"vorticity_cfd_{o}.png","vorticity",o,vc)
|
||||||
|
root=stage/"summary"; curves=[{k:r[k] for k in ("case","label","family","parameter_name","parameter","x_over_D","E_inf_vector","source_sha256")} for r in stations]; write_csv(root/"profile_Einf_vector.csv",curves)
|
||||||
|
f,axs=plt.subplots(1,2,figsize=(10.5,4.6),sharex=True,constrained_layout=True)
|
||||||
|
for ax,family,title in zip(axs,("potential","CFD"),("Potential references","CFD endpoints")):
|
||||||
|
for c in cases:
|
||||||
|
if c.family!=family: continue
|
||||||
|
rr=[r for r in curves if r["case"]==c.id]; ax.plot([r["x_over_D"] for r in rr],[r["E_inf_vector"] for r in rr],marker="o",label=c.label)
|
||||||
|
ax.set(xlabel="x/D",title=title); ax.grid(alpha=.25); ax.legend(fontsize=7)
|
||||||
|
axs[0].set_ylabel(r"$E_{\infty,vector}$ vs $q_{in}$"); f.suptitle("Direct common-fluid profile error — family-specific axes"); summary_save(f,root,"profile_Einf_vector")
|
||||||
|
lc=L["cases"]; gaps=[]
|
||||||
|
for cid,n in (("cfd-accepted-s3p55","accepted-s3.55"),("cfd-reverse-s5p2","reverse-s5.2")):
|
||||||
|
for name,zv in lc[n]["gap_fluxes_separate_orientations"].items(): gaps.append({"case":cid,"source_case":n,"s":lc[n]["s"],"gap":name,"Q_star":zv["dimensionless"]["Q_star"],"normal_x":zv["normal"][0],"normal_y":zv["normal"][1],"orientation":zv["orientation"],"ledger_sha256":sha(ledger)})
|
||||||
|
write_csv(root/"signed_gap_Qstar.csv",gaps); f,ax=plt.subplots(figsize=(8,4.8),constrained_layout=True); names=("front_upper_rear","front_lower_rear","rear_rear"); pos=np.arange(3); w=.36; palette=("#4477AA","#AA6655")
|
||||||
|
for k,(cid,lab) in enumerate((("cfd-accepted-s3p55","accepted s=3.55"),("cfd-reverse-s5p2","reverse s=5.2"))): by={r["gap"]:r for r in gaps if r["case"]==cid}; ax.bar(pos+(k-.5)*w,[by[n]["Q_star"] for n in names],w,label=lab,color=palette[k])
|
||||||
|
ax.axhline(0,color="black",lw=.7); ax.set_xticks(pos,names,rotation=12); ax.set_ylabel("signed Q*"); ax.set_title("Signed gap fluxes (frozen normals; amplitudes differ)"); ax.legend(); summary_save(f,root,"signed_gap_Qstar")
|
||||||
|
cp=[{"case":cid,"source_case":n,"s":lc[n]["s"],"C_P":lc[n]["torque_and_actuator_power"]["C_P"],"ledger_sha256":sha(ledger)} for cid,n in (("cfd-accepted-s3p55","accepted-s3.55"),("cfd-reverse-s5p2","reverse-s5.2"))]; write_csv(root/"actuator_CP.csv",cp); f,ax=plt.subplots(figsize=(5.8,4.5),constrained_layout=True); ax.bar(["accepted\ns=3.55","reverse\ns=5.2"],[r["C_P"] for r in cp],color=("#4477AA","#AA6655")); ax.set(ylabel=r"$C_P$",title="Oracle-bound actuator power (amplitudes differ)"); summary_save(f,root,"actuator_CP")
|
||||||
|
diag=[]
|
||||||
|
for c in cases[4:]:
|
||||||
|
common=c.fluid&qfluid; rec=common&(X>=1.8)&(X<=10)&(abs(Y)<=1.5)&(c.ux/U<0); near=c.vort_mask&(X>=1.8)&(X<4)&(abs(Y)<=3); diag.append({"case":c.id,"label":c.label,"s":c.parameter,"recirculation_area_D2":float(np.sum(weights[rec])),"immediate_wake_abs_vorticity_integral":float(np.sum(weights[near]*np.abs(c.vort[near]))),"definition":"ux/U<0 on 1.8<=x/D<=10, |y/D|<=1.5; vorticity on 1.8<=x/D<4, |y/D|<=3","source_sha256":c.source_hash})
|
||||||
|
write_csv(root/"near_wake_diagnostics.csv",diag); f,aa=plt.subplots(1,2,figsize=(9,4.4),constrained_layout=True); labs=["accepted\ns=3.55","reverse\ns=5.2"]; aa[0].bar(labs,[r["recirculation_area_D2"] for r in diag],color=("#4477AA","#AA6655")); aa[0].set(title="Recirculation area",ylabel=r"area/$D^2$"); aa[1].bar(labs,[r["immediate_wake_abs_vorticity_integral"] for r in diag],color=("#4477AA","#AA6655")); aa[1].set(title="Immediate-wake vorticity",ylabel=r"$\int|\omega^*|dA^*$"); f.suptitle("Selected endpoint diagnostics (amplitudes differ; partial ledger)"); summary_save(f,root,"near_wake_diagnostics")
|
||||||
|
sources={"strip_analysis":{"path":str(strip),"sha256":sha(strip)},"ledger":{"path":str(ledger),"sha256":sha(ledger)},"steady_root":{"path":str(steady),"files":{}}}
|
||||||
|
for n in loaded:
|
||||||
|
for fn in ("manifest.json","endpoint.npz"): p=steady/n/fn; sources["steady_root"]["files"][f"{n}/{fn}"]={"path":str(p),"sha256":sha(p),"bytes":p.stat().st_size}
|
||||||
|
command=f'PYTHONPATH="$PWD/src" conda run -n pinball_math python src/steady_pinball_theory/plot_results.py --steady "{steady}" --strip "{strip}" --ledger "{ledger}" --output "{out}"'
|
||||||
|
readme=f"""# Unified steady-pinball figures
|
||||||
|
|
||||||
|
Regenerable reader-facing visualization, not new evidence. CPU-only; no CFD/CUDA ran.
|
||||||
|
|
||||||
|
## Redraw
|
||||||
|
```bash
|
||||||
|
{command}
|
||||||
|
```
|
||||||
|
Defaults permit omitting all flags. Existing output fails unless `--overwrite` is supplied; replacement uses atomic staging and affects only this generated root.
|
||||||
|
|
||||||
|
## Contents and definitions
|
||||||
|
- `fields/error`: six raw `|q_case-q_in|/U_inf` canvases (dimensionless, not percent).
|
||||||
|
- `fields/vorticity`: six raw `omega D/U_inf` canvases. Potential exterior values are the analytical irrotational value zero; CFD retains conservative masked numerical curl.
|
||||||
|
- `fields/streamlines`: total-velocity streamlines, arrows enabled.
|
||||||
|
- `colorbars` and `annotations`: family-named standalone legends and separate metric assets.
|
||||||
|
- `plates/{{error,vorticity,streamlines}}`: publication-friendly PNG/PDF companions with titles and framed metrics. Plates are presentation assets, not pixel-exact arrays.
|
||||||
|
- `field_metrics.csv`, `profiles_long.csv`, and `summary`: direct redraw data; every summary PNG/PDF has a same-basename CSV.
|
||||||
|
|
||||||
|
Crop indices x={ix[0]}..{ix[-1]}, y={iy[0]}..{iy[-1]}; exact x/D=[{x[0]:.12g},{x[-1]:.12g}], y/D=[{y[0]:.12g},{y[-1]:.12g}], canvas={len(x)}x{len(y)} pixels. Arrays are yx, x is horizontal, y is up, origin is the front center. Potential uses these exact CFD lattice coordinates and analytic cylinders; CFD uses solver fluid masks. Scalar images use nearest mapping, one pixel/cell, no axes/titles/padding. Bodies are white with thin black outlines. Streamline paths are vector-rendered, but their PNG canvas has the same exact dimensions and lattice-edge extent.
|
||||||
|
|
||||||
|
Potential and CFD errors use separate pooled-family 97.5-percentile limits: potential `{elims["potential"]:.16g}`, CFD `{elims["CFD"]:.16g}`. Both use the same discrete Greys style, but grayscale darkness is only comparable within its named family colorbar. Shared CFD-only symmetric vorticity limit is `+/-{vlim:.16g}`. Potential displayed/exported vorticity is analytical zero. Values beyond limits map to end bins and raw maxima remain in CSV. Vorticity uses discrete RdBu_r with a forced-white center and no zero contour.
|
||||||
|
|
||||||
|
Crop L2 is `sqrt(integral_common |q_case-q_in|^2 dA / integral_common dA)/U_inf` with tensor trapezoid weights. Profiles are direct differences, not deltas misused with a uniform-reference helper. Accepted x/D=10 direct vector E_inf is `{PRIMARY}`.
|
||||||
|
|
||||||
|
Potential `g_opt={g['theoretical_rear_opposite_g_opt_layer40']}` and fitted `g={g['actual_wls_g']}` have opposite signs: do not infer a potential mechanism. Body order is front, rear_y_plus, rear_y_minus; accepted is `[0,+Omega,-Omega]`. Stationary is an unsteady reference. Accepted s=3.55 and reverse s=5.2 have mismatched amplitudes. The NS ledger is partial. No mechanism, causality, stability, or closure is established; no CCD numbers are used.
|
||||||
|
|
||||||
|
## Sources
|
||||||
|
```json
|
||||||
|
{json.dumps(sources,indent=2,sort_keys=True)}
|
||||||
|
```
|
||||||
|
"""; (stage/"README.md").write_text(readme)
|
||||||
|
man={"schema":"steady-pinball-unified-figures/v2","scientific_scope":"CPU-only regenerable visualization; no CFD/CUDA; not evidence","crop":{"requested_x_over_D":[-2,10],"requested_y_over_D":[-3,3],"x_indices_inclusive":[int(ix[0]),int(ix[-1])],"y_indices_inclusive":[int(iy[0]),int(iy[-1])],"x_lattice":[float(q["x"][ix[0]]),float(q["x"][ix[-1]])],"y_lattice":[float(q["y"][iy[0]]),float(q["y"][iy[-1]])],"x_over_D":[float(x[0]),float(x[-1])],"y_over_D":[float(y[0]),float(y[-1])],"pixel_dimensions_xy":[len(x),len(y)],"array_order":"yx"},"render":{"one_output_pixel_per_lattice_cell":True,"scalar_interpolation":"nearest","raw_axes_titles":False,"plates":"presentation companions; not pixel-exact raw arrays","streamline_arrowheads":True},"masks":{"potential":"analytic cylinder exterior; displayed/exported vorticity analytically zero","CFD":"case/q_in common solver FLUID bits; conservative centered curl stencil"},"colormaps":{"error":{"name":"Greys","bins":12,"family_scales":{family:{"edges":color[0].tolist(),"limits":[0,elims[family]],"limit_statistic":"family-pooled 97.5 percentile","colorbars":[f"colorbars/error_{family.lower()}_horizontal.png",f"colorbars/error_{family.lower()}_vertical.png"]} for family,color in ecs.items()},"comparison_warning":"grayscale values compare only within the named family scale","clipping":"end bins; raw values retained"},"vorticity":{"name":"RdBu_r","bins":21,"edges":vc[0].tolist(),"limits":[-vlim,vlim],"limit_statistic":"CFD-only pooled absolute 97.5 percentile","potential_value":"analytical irrotational zero on valid exterior","center_bin":"white","zero_contour":False,"clipping":"end bins; raw values retained"}},"definitions":{"error":"hypot(ux-qin_ux,uy-qin_uy)/U_inf","vorticity":"potential analytical zero; CFD conservative omega D/U_inf","crop_L2":"sqrt(integral common-fluid |q_case-q_in|^2 dA / integral common-fluid dA)/U_inf; tensor trapezoid weights"},"geometry":{"origin":"front center","coordinates":"x-right_y-up","body_order":["front","rear_y_plus","rear_y_minus"],"centers_over_D":centers.tolist(),"radius_over_D":radius},"cases":[{"case":c.id,"label":c.label,"family":c.family,"parameter_name":c.pname,"parameter":c.parameter,"error_scale_family":c.family,"source_sha256":c.source_hash} for c in cases],"primary_identity":{"case":"cfd-accepted-s3p55","x_over_D":10,"E_inf_vector":PRIMARY},"sources":sources,"exact_command":command,"overwrite_contract":"default fail; explicit --overwrite atomically replaces only output root"}
|
||||||
|
files={}
|
||||||
|
for p in sorted(stage.rglob("*")):
|
||||||
|
if p.is_file(): files[str(p.relative_to(stage))]={"sha256":sha(p),"bytes":p.stat().st_size}
|
||||||
|
man["files"]=files; man["payload_sha256"]=hashlib.sha256(json.dumps(man,sort_keys=True,separators=(",",":")).encode()).hexdigest(); (stage/"manifest.json").write_text(json.dumps(man,indent=2,sort_keys=True)+"\n"); publish(stage,out,a.overwrite)
|
||||||
|
except Exception: shutil.rmtree(stage,ignore_errors=True); raise
|
||||||
|
return out
|
||||||
|
|
||||||
|
def main(argv=None):
|
||||||
|
p=argparse.ArgumentParser(description=__doc__); p.add_argument("--steady",type=Path,default=DEFAULT_STEADY); p.add_argument("--strip",type=Path,default=DEFAULT_STRIP); p.add_argument("--ledger",type=Path,default=DEFAULT_LEDGER); p.add_argument("--output",type=Path,default=DEFAULT_OUTPUT); p.add_argument("--overwrite",action="store_true"); a=p.parse_args(argv); print(f"wrote unified figures to {build(a)}"); return 0
|
||||||
|
if __name__=="__main__": raise SystemExit(main())
|
||||||