Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fbf6f1d122 |
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
Reference in New Issue
Block a user