Skip to content

Maxwell-accommodating wall boundary conditions (opt-in :wall face)#26

Merged
sbryngelson merged 1 commit into
mainfrom
feat/wall-bc
Jul 26, 2026
Merged

Maxwell-accommodating wall boundary conditions (opt-in :wall face)#26
sbryngelson merged 1 commit into
mainfrom
feat/wall-bc

Conversation

@sbryngelson

Copy link
Copy Markdown
Member

Adds grid-aligned kinetic walls — the capability gap that blocked microflows entirely (slip, temperature jump and the Knudsen layer are the physics there) and also blocked Couette/Fourier validation. Opt-in; defaults unchanged, no golden moves.

Physics

A wall is a half-space moment problem, and a 35-moment vector cannot encode the split. The ghost form is therefore an approximation — but two of its three ingredients are exact:

M_ghost = alpha * M_Maxwellian(rho_w, u_w, T_w) + (1-alpha) * M_specular(M_int)
  • specular (alpha=0) is EXACT — reflection maps v_n -> -v_n, i.e. M_{ijk} -> (-1)^i M_{ijk}, a sign flip on moments of odd normal order. Measure-preserving, so realizable in, realizable out, with tangential velocity untouched (free slip).
  • diffuse (alpha=1) sets rho_w from zero net normal mass flux, the incoming flux being a closed-form half-space Gaussian integral. This Gaussian assumption is the one real approximation.
  • the blend is convex, so realizability follows from the same argument bgk_relax_tup and theta*-IDP already rest on — no projection, no new failure mode.

Halo-refill only: no changes to the residual, WENO5, theta-IDP or the flux kernels*, so it works at every spatial order and on GPU immediately.

Ghost layer k mirrors interior layer k rather than copying the nearest cell — specular reflection of a half-space is the mirror map, which is load-bearing at order 3 (g=8). On GPU this is why _axis_src returns a mirrored index plus a wall_side flag: unlike :inlet, whose ghost is a constant vector, a wall ghost is a function of the adjacent interior cell.

The slip coefficient — measured, and it works

The design deliberately left open whether the Gaussian half-space assumption reproduces Maxwell slip. It does:

alpha zeta measured (2-alpha)/alpha implied sigma_p
1.00 1.1853 1.000 1.185
0.75 1.7209 1.667 1.033
0.50 2.8435 3.000 0.948

The (2-alpha)/alpha accommodation scaling is reproduced, and sigma_p = 0.95–1.19 against a theoretical 1.0–1.1. Interior profiles linear to R^2 = 0.99999.

So no escalation is needed. Had this been badly off, the fix would have been a prescribed wall flux (KFVS-at-wall), touching the residual and the theta*-IDP anchor. It isn't.

lambda uses the shared convention lambda_mu = mu/(rho sqrt(2RT)) = tau_ref*sqrt(Theta/2) — the same one bridged to SPARTA, so the number is comparable across codes.

Verification

  • equilibrium wall is an EXACT fixed pointmax|dM|/|M| = 0.000e+00 on both CPU and GPU, bitwise. This is the test that catches a wrong rho_w normalization: a factor error in the half-space mass balance shows up here and nowhere else, so exactly zero says the balance closes analytically.
  • specular: mass drift 0, energy drift 0, tangential velocity preserved at exactly 0.300, with T_w = 99 set absurdly to prove it is ignored.
  • half-space influx closed form vs numerical quadrature: 3.3e-7 (independent computation).
  • unit gates W1–W5: 265/265, registered in runtests.jl.
  • full suite 1689 passed / 5 broken / 0 failed / 0 errored = 1424 baseline + 265 new; stdout diffed against baseline is identical apart from the new tests' own output.

Also fixed — all found while finishing this

  • Walls were unreachable by users. simulation_runner never populated WALL_SPEC, so the feature only worked if a caller poked the global directly, as the tests did. Now wired via a wall_spec param, with a warning if a :wall face is set without one. Verified end-to-end through the runner.
  • test_wall_bc.jl was not in runtests.jl — CI would not have run it.
  • HIGHORDER.md was a generation stale — the doc handed to Fox and Posey. It called itself notes on the projection35-port branch, listed spatial_order as only 1 or 2, and said the scheme was "not yet robust at Ma=100". Order-3 has been the default since High-order WENO5 + θ*-IDP (CPU+GPU, DRY) — order-3 as default #18 and Ma=100 is CI-guarded. Refreshed with dated correction notes, keeping the Ma=100 failure-mechanism paragraph (the cancellation in u = M100/M000 at rho~1e-5 is still what to understand when a high-Ma run misbehaves). Also added an options table it lacked entirely — Pr, omega, stage_bgk_exact and :wall/wall_spec were documented nowhere a collaborator would look, so someone could match omega to argon, get Pr right, and still have mu 1.85x low with no warning.

Scope / not done

Grid-aligned faces, stationary in the normal direction (tangential motion allowed — covers Couette, Fourier, planar microchannel). Immersed and curved walls are a separate spec — that is what would fix the cylinder case's held-gas bounce-back and its ~10% blockage.

…l face)

Adds grid-aligned kinetic walls, the capability gap that blocked microflows entirely --
slip, temperature jump and the Knudsen layer ARE the physics there -- and that also
blocked Couette/Fourier validation. Opt-in via a new :wall face type; defaults unchanged
and no golden moves.

PHYSICS. A wall is a HALF-SPACE moment problem and a 35-moment vector cannot encode the
split, so the ghost-cell form is an approximation -- but two of its three ingredients are
exact:

  M_ghost = alpha * M_Maxwellian(rho_w, u_w, T_w) + (1-alpha) * M_specular(M_int)

  * specular (alpha=0) is EXACT: reflection maps v_n -> -v_n, i.e. M_{ijk} -> (-1)^i
    M_{ijk}, a sign flip on moments of odd normal order. Measure-preserving, so it takes
    a realizable state to a realizable state with no approximation, and tangential
    velocity passes through untouched (free slip).
  * diffuse (alpha=1) sets rho_w from zero net normal mass flux, with the incoming flux
    from a half-space Gaussian integral (closed form in erf/exp). THIS Gaussian
    assumption is the one real approximation in the design.
  * the blend is CONVEX, so realizability follows from the same argument bgk_relax_tup
    and the theta*-IDP limiter already rest on. No projection, no new failure mode.

Halo-refill only: NO changes to the residual, WENO5, theta*-IDP or the flux kernels, so
it works at every spatial order and on GPU immediately.

Ghost layer k MIRRORS interior layer k rather than copying the nearest interior cell
(what :outflow does). Specular reflection of a half-space IS the mirror map, so ghost
layer k must see the interior cell at the same distance from the wall -- load-bearing at
order 3, where g=8. On the GPU this is why _axis_src returns a mirrored index plus a
wall_side flag instead of a plain index remap: unlike :inlet, whose ghost is a CONSTANT
vector, a wall ghost is a FUNCTION of the adjacent interior cell.

VERIFICATION:
- equilibrium wall (T_w = T, at rest) is an EXACT fixed point -- max|dM|/|M| = 0.000e+00
  on BOTH CPU and GPU, bitwise, not merely small. This is the test that catches a wrong
  rho_w normalization: a factor error in the half-space mass balance shows up here and
  nowhere else, so landing at exactly zero says the balance closes analytically.
- specular wall: mass drift 0, energy drift 0, tangential velocity preserved at exactly
  0.300, with T_w = 99 deliberately absurd to prove it is ignored.
- half-space influx closed form vs numerical quadrature: 3.3e-7 (independent computation,
  the same tactic used on the Isserlis table).
- unit gates W1-W5: 265/265, registered in runtests.jl so CI runs them.
- full suite 1689 passed / 5 broken / 0 failed / 0 errored = 1424 baseline + 265 new;
  stdout diffed against baseline is identical apart from the new tests' own output.

ALSO FIXED, all found while finishing this:
- walls were UNREACHABLE by users. simulation_runner never populated WALL_SPEC, so the
  feature only worked if a caller poked the global directly (as the tests did). Now wired
  via a `wall_spec` param, with a warning if a :wall face is set without one. Verified
  end-to-end through the runner.
- test_wall_bc.jl was not registered in runtests.jl, so CI would not have run it.
- HIGHORDER.md -- the doc handed to Fox and Posey -- was a full generation stale: it
  described itself as notes on the `projection35-port` branch, listed spatial_order as
  only 1 or 2, and said the scheme was "not yet robust at Ma=100". Order-3 has been the
  DEFAULT since #18 and Ma=100 is CI-guarded. Refreshed with dated correction notes rather
  than silent edits, keeping the Ma=100 failure-mechanism paragraph because the
  cancellation in u = M100/M000 at rho~1e-5 is still what to understand when a high-Ma run
  misbehaves. Also added the options table it was missing entirely: Pr, omega,
  stage_bgk_exact and :wall/wall_spec were documented nowhere a collaborator would look --
  someone could match omega to argon, get Pr right, and still have mu 1.85x low with no
  indication anything was wrong.

SCOPE / NOT DONE: grid-aligned faces only, stationary in the normal direction (tangential
motion allowed, which covers Couette/Fourier/planar microchannel). Immersed and curved
walls are a separate spec -- that is what would fix the cylinder case's held-gas
bounce-back. The slip coefficient is MEASURED, not asserted, by
test/validate_wall_slip.jl: the Gaussian half-space assumption may not reproduce Maxwell's
sigma_p ~ 1.0-1.1, and if it is badly off the escalation is a prescribed wall flux
(KFVS-at-wall), which would have to touch the residual and the theta*-IDP anchor.
@sbryngelson
sbryngelson merged commit 668dd86 into main Jul 26, 2026
8 checks passed
@sbryngelson
sbryngelson deleted the feat/wall-bc branch July 26, 2026 17:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant