Reserve a pressure condition on Jet, velocity remains the only implemented one

Jet gains a last field condition='velocity'. 'pressure' raises
NotImplementedError in __post_init__ before profile/amplitude validation, so a
pressure jet never reaches surface_link_velocity, tables or kernels; unknown
values raise ValueError. Amplitude stays a lattice velocity (not a flow rate);
no pressure units, field reuse, tables or kernel changes are introduced.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Claude
2026-10-17 02:59:09 +00:00
co-authored by Claude Opus 5.5
parent 1e8e2c9a69
commit fbf6f1d122
2 changed files with 48 additions and 1 deletions
+16 -1
View File
@@ -9,6 +9,11 @@ whose links are evaluated; callers convert frames/units before evaluation.
One physical link (receiver, direction) may be driven by at most one jet, and
one jet may drive the links of only one body (checked at assignment, any amplitude).
``condition`` names the prescribed boundary type. Only ``"velocity"`` (the
default: amplitude is a signed lattice velocity, not a flow rate) is
implemented; ``"pressure"`` is reserved and rejected at construction, before
any amplitude validation, so it never reaches assignment or a kernel.
Edge rule: the lateral bound |s| < 1 is exclusive (the profile vanishes
there for parabolic/quartic); Sector r_min/r_max and Tube 0 <= along <= length
are inclusive. Jets are immutable; an amplitude change replaces the Jet.
@@ -25,6 +30,9 @@ from dataclasses import dataclass, replace
import numpy as np
CONDITIONS = ("velocity",)
RESERVED_CONDITIONS = ("pressure",)
PROFILES = {
"uniform": lambda s: np.ones_like(s),
"parabolic": lambda s: 1 - s*s,
@@ -122,7 +130,8 @@ class Parallel:
@dataclass(frozen=True)
class Jet:
"""Region (Sector|Tube), field (Radial|Parallel), profile name, signed amplitude.
"""Region (Sector|Tube), field (Radial|Parallel), profile name, signed amplitude,
prescribed boundary condition (velocity only; pressure reserved).
Frozen: change amplitude with ``with_amplitude`` (validated, new object);
the cached per-link shapes stay valid because region/field/profile cannot change.
@@ -131,8 +140,14 @@ class Jet:
field: object
profile: str = "uniform"
amplitude: float = 0.0
condition: str = "velocity"
def __post_init__(self):
# The condition decides what amplitude and field mean: check it first.
if self.condition in RESERVED_CONDITIONS:
raise NotImplementedError(f"{self.condition} jets are reserved but not implemented")
if self.condition not in CONDITIONS:
raise ValueError(f"Jet condition must be one of {CONDITIONS + RESERVED_CONDITIONS}")
if self.profile not in PROFILES:
raise ValueError(f"Jet profile must be one of {sorted(PROFILES)}")
_finite(self.amplitude)
+32
View File
@@ -0,0 +1,32 @@
"""Jet boundary-condition hook: velocity implemented, pressure reserved (host only, no CUDA)."""
import pytest
from CelerisLab.body.jets import Jet, Parallel, Tube
def region():
return Tube((16., 12.), 0., 3., .7)
def test_default_is_velocity_and_survives_amplitude_change():
jet = Jet(region(), Parallel(0.), amplitude=.01)
assert jet.condition == "velocity"
assert jet.with_amplitude(-.02).condition == "velocity"
# positional calls keep their meaning; condition is the new last field
assert (Jet(region(), Parallel(0.), "parabolic", .01, "velocity")
== Jet(region(), Parallel(0.), "parabolic", .01))
def test_pressure_is_rejected_before_amplitude_validation():
# An amplitude that the velocity check would reject must not decide the error.
for amplitude in (0., 5., float("nan")):
with pytest.raises(NotImplementedError, match="pressure jets are reserved"):
Jet(region(), Parallel(0.), amplitude=amplitude, condition="pressure")
with pytest.raises(NotImplementedError):
Jet(region(), None, profile="no-such-profile", condition="pressure")
def test_unknown_condition_is_a_value_error():
for condition in ("Velocity", "flow_rate", "", None):
with pytest.raises(ValueError, match="condition"):
Jet(region(), Parallel(0.), amplitude=.01, condition=condition)