fix: underwater Wenz reference pressure, solver guards and new independent oracles (#167)
* fix: Wenz wind-noise reference pressure, mode-resolution guards and exact ray gradients
Underwater domain review follow-ups:
- The Wenz/Knudsen rule-of-fives anchor (25 dB at 1 kHz / 5 kn) is stated
re 0.0002 dyn/cm2 = 20 uPa inside the quoted source; the module labelled
the unconverted value dB re 1 uPa, leaving every wind spectrum level and
the ambient composite 26.02 dB low and moving the wind/thermal crossover
from ~63 kHz to ~12 kHz. The anchor is now 25 + 20*lg(20) = 51.02 dB re
1 uPa^2/Hz, matching the published Wenz chart (~50 dB at 1 kHz for
4-6 kn); slopes verified unchanged, test and conformance anchors moved to
the corrected value plus independent chart anchors, figures regenerated.
- normal_modes derives its default grid from the physics
(max(400, ceil(60*D*f/c_min))), restricts the eigensolve to the
propagating band (select='v'), discards eigenvalues inside the O(dz^2)
finite-difference error band about cutoff (previously a spurious extra
mode appeared at under-resolved settings, e.g. 67 instead of 66 modes at
500 Hz / 100 m with n = 400) and warns when a retained near-cutoff mode
sits within ten times that band. Mode-shape interpolation at zs/zr is one
vectorised lookup.
- ray_trace evaluates dc/dz exactly per profile segment (piecewise-constant
for the piecewise-linear profile) instead of np.gradient on an
interpolated fine grid, which smeared thermocline kinks and biased
turning depths by metres (SOFAR test case: 3.4/8.0 m -> 0.6/1.4 m).
- New independent oracles: image-source absolute TL anchors for the modal
sum (which the previous analytic test could not calibrate, sharing the
implementation's prefactor) and the long-range PE; the published UNESCO
EOS-80 canonical value 1731.995 m/s; six more JOMOPANS File S1 rows; the
Ainslie-McColm Table I oceans vs Francois-Garrison. Conformance now 198.
- Documentation: ISO 17208-2 band-edge interpretation (the standard leaves
16-20 kHz unassigned), A-M ~10 % caveat at domain corners, the inherent
F-G A3 step at 20 C, sound-speed validity domains (the equations
extrapolate silently), Wenz wind-speed validity, the PE paraxial limit,
and stale cross-references; sound_speed_profile is vectorised.
* test: pin the RANDI ship spectrum to the report's representative levels
The RANDI 3.1 Physics Description (Breeding et al., NRL, public release)
tabulates representative source levels per ship class computed from its own
Eq. 2-5; the module reproduces the Large and Super Tanker rows to 0.06 dB at
every tabulated frequency and the Merchant/Tanker rows at all but one printed
cell each, which are excluded with the rationale documented.
* docs: note the RANDI Table 2 oracle in the changelog
* fix: cap the physics-derived normal-mode depth grid
The automatic n_depth_points default is capped at 20000 points so extreme
frequency-depth products cannot allocate unbounded eigenvector arrays; the
near-cutoff warning reports whether the capped grid suffices and an explicit
value overrides the cap.
* fix: regenerate numerical-propagation figures and review follow-ups
The near-cutoff eigenvalue guard legitimately changes the modal figure (the
previously included mode sat inside the finite-difference error band), so the
four numerical-propagation variants are regenerated. The propagating-band
filter moves to a helper; the vectorised profile path validates salinity like
the scalar path; the RANDI table test indexes by enumeration.
fix: underwater Wenz reference pressure, solver guards and new independent oracles (#167)
* fix: Wenz wind-noise reference pressure, mode-resolution guards and exact ray gradients
Underwater domain review follow-ups:
- The Wenz/Knudsen rule-of-fives anchor (25 dB at 1 kHz / 5 kn) is stated
re 0.0002 dyn/cm2 = 20 uPa inside the quoted source; the module labelled
the unconverted value dB re 1 uPa, leaving every wind spectrum level and
the ambient composite 26.02 dB low and moving the wind/thermal crossover
from ~63 kHz to ~12 kHz. The anchor is now 25 + 20*lg(20) = 51.02 dB re
1 uPa^2/Hz, matching the published Wenz chart (~50 dB at 1 kHz for
4-6 kn); slopes verified unchanged, test and conformance anchors moved to
the corrected value plus independent chart anchors, figures regenerated.
- normal_modes derives its default grid from the physics
(max(400, ceil(60*D*f/c_min))), restricts the eigensolve to the
propagating band (select='v'), discards eigenvalues inside the O(dz^2)
finite-difference error band about cutoff (previously a spurious extra
mode appeared at under-resolved settings, e.g. 67 instead of 66 modes at
500 Hz / 100 m with n = 400) and warns when a retained near-cutoff mode
sits within ten times that band. Mode-shape interpolation at zs/zr is one
vectorised lookup.
- ray_trace evaluates dc/dz exactly per profile segment (piecewise-constant
for the piecewise-linear profile) instead of np.gradient on an
interpolated fine grid, which smeared thermocline kinks and biased
turning depths by metres (SOFAR test case: 3.4/8.0 m -> 0.6/1.4 m).
- New independent oracles: image-source absolute TL anchors for the modal
sum (which the previous analytic test could not calibrate, sharing the
implementation's prefactor) and the long-range PE; the published UNESCO
EOS-80 canonical value 1731.995 m/s; six more JOMOPANS File S1 rows; the
Ainslie-McColm Table I oceans vs Francois-Garrison. Conformance now 198.
- Documentation: ISO 17208-2 band-edge interpretation (the standard leaves
16-20 kHz unassigned), A-M ~10 % caveat at domain corners, the inherent
F-G A3 step at 20 C, sound-speed validity domains (the equations
extrapolate silently), Wenz wind-speed validity, the PE paraxial limit,
and stale cross-references; sound_speed_profile is vectorised.
* test: pin the RANDI ship spectrum to the report's representative levels
The RANDI 3.1 Physics Description (Breeding et al., NRL, public release)
tabulates representative source levels per ship class computed from its own
Eq. 2-5; the module reproduces the Large and Super Tanker rows to 0.06 dB at
every tabulated frequency and the Merchant/Tanker rows at all but one printed
cell each, which are excluded with the rationale documented.
* docs: note the RANDI Table 2 oracle in the changelog
* fix: cap the physics-derived normal-mode depth grid
The automatic n_depth_points default is capped at 20000 points so extreme
frequency-depth products cannot allocate unbounded eigenvector arrays; the
near-cutoff warning reports whether the capped grid suffices and an explicit
value overrides the cap.
* fix: regenerate numerical-propagation figures and review follow-ups
The near-cutoff eigenvalue guard legitimately changes the modal figure (the
previously included mode sat inside the finite-difference error band), so the
four numerical-propagation variants are regenerated. The propagating-band
filter moves to a helper; the vectorised profile path validates salinity like
the scalar path; the RANDI table test indexes by enumeration.
refactor: reorganize the package into twelve domain subpackages (3.2) (#162)
* refactor: private core to _internal, module-path shims, _plot skeleton (C1)
Foundation of the 3.2 package reorganization:
- _validation/_types/_warnings/_levels_math/utils move (git mv) into the
private phonometry/_internal/ package; ~90 import sites retargeted.
- New phonometry/_compat.py: dynamic sys.modules shims keep every moved
public module path importable for one deprecation cycle (silent import,
DeprecationWarning on attribute access); generalizes and absorbs the
former loudness.py PEP 562 shim (its 3.1 wording preserved).
- _plotting.py moves (git mv) to phonometry/_plot/common.py; a silent
explicit re-export shim keeps the old private path and the 84 lazy
.plot() call sites working until each domain retargets them.
- _warn_renamed gains a 'since' parameter (3.2 for the package moves).
- tests: new test_package_architecture.py (ast edge whitelist +
fresh-interpreter subpackage imports); test_deprecated_aliases gains the
frozen 85-path pre-move snapshot and the _MOVED shim behavior test;
pytest pythonpath=tests for the upcoming mirrored test tree.
- .git-blame-ignore-revs scaffold (hashes appended at the end of Phase 1).
* refactor: metrology subpackage (C2)
core, filter_design, frequencies, parametric_filters, levels, calibration,
compliance and uncertainty move (git mv) into phonometry/metrology/ with a
curated re-export __init__; the flat API and the old module paths keep
working (facade re-exports + _compat shims). Uncertainty renderers carve out
of _plot/common.py into _plot/metrology.py; the 14 metrology test files move
to tests/metrology/ and deep imports across tests/scripts point at the new
canonical paths.
* refactor: psychoacoustics subpackage (C3)
The thirteen psychoacoustics modules (Zwicker/ECMA/Moore-Glasberg loudness,
contours, sharpness, tonality, roughness, fluctuation strength, annoyance,
tonal audibility, private Zwicker tables) move into
phonometry/psychoacoustics/; renderers carve into _plot/psychoacoustics.py;
twelve test files move to tests/psychoacoustics/. The phonometry.loudness
alias now resolves to the relocated canonical module in a single hop.
* refactor: hearing and emission subpackages (C4+C5)
hearing/ gains threshold (renamed from hearing.py), noise-induced hearing
loss, occupational exposure, SII and STI; emission/ gains the ISO 3740
sound-power family, ISO 9614 intensity and ISO/TS 7849 vibration-based
power. Renderers carve into _plot/hearing.py and _plot/emission.py; the
eleven domain test files move under tests/hearing/ and tests/emission/.
* refactor: materials, room and building subpackages (C6-C8)
materials/ collects ISO 354/11654/12999-2 absorption, scattering-diffusion,
road absorption, impedance tube, airflow resistance and EN 29052-1 dynamic
stiffness; room/ the room-acoustics, impulse-response, open-plan, room-noise,
reverberation-prediction and EN 12354-6 modules; building/ the eleven-module
EN 12354 / ISO 717 / ISO 16283 insulation family with EN 15657 and
EN 12354-5 structure-borne sound. Renderers carve into the matching _plot
modules; twenty-six test files move under their domain directories.
* fix: complete the building plot TYPE_CHECKING imports (C8 follow-up)
* refactor: vibration and environmental subpackages (C9+C10)
vibration/ collects human vibration (ISO 8041-1/2631/5349), multiple-shock
(ISO 2631-5), mechanical mobility (ISO 7626-1) and transfer stiffness
(ISO 10846); environmental/ collects the renamed rating (Lden/Ldn) and
measurement (ISO 1996-2) modules plus outdoor propagation (ISO 9613-2),
air absorption (ISO 9613-1), wind-turbine noise (IEC 61400-11) and NT
ACOU 112 impulse prominence. Renderers carve into their _plot modules and
ten test files move under the domain directories.
* refactor: aircraft, underwater and electroacoustics subpackages (C11-C13)
aircraft/ collects EPNL, the renamed atmospheric absorption, ECAC Doc 29
airport contours and ECAC Doc 32 rotorcraft hemispheres; underwater/ the
renamed acoustics/propagation/sound_speed modules with ship noise, pile
driving, ambient noise, sonar equation, seabed reflection and the numerical
solvers; electroacoustics/ distortion and frequency response. Renderers
carve into their _plot modules; seventeen test files move under the domain
directories. All twelve domain subpackages are now in place.
* refactor: finalize the 3.2 package reorganization (C14)
- api-reference gains a Namespaces section (the twelve subpackages with
scope and the 'from phonometry import aircraft as air' idiom); README
quick-start notes the namespaces; CHANGELOG records the added namespaces,
the deprecated flat module paths and the pickle note.
- plot_excitation moves from _plot/common.py to _plot/room.py and the
facade imports it from there; _plot/common.py now holds only shared
helpers and infrastructure.
- hearing/ and environmental/ package __init__ re-export the full public
surface of their renamed modules (threshold, rating) so the pre-move
package-path imports stay silent, as promised by the migration contract.
- SonarCloud cpd exclusion follows core.py to metrology/; llms.txt
regenerated; .git-blame-ignore-revs lists the move commits and the local
blame config registers it.
Full suite 2647 passed; conformance 194/194; golden baseline byte-stable;
import time unchanged vs main (1.09 s vs 1.17 s).
* fix: re-export the measurement surface on phonometry.environmental
Subagent-review finding: the generated environmental/__init__.py missed the
ISO 1996-2 measurement module (its facade import block was momentarily
path-corrupted when the subpackage exports were collected), so
phonometry.environmental.assess_tonal_audibility and 15 sibling names
raised AttributeError while every other domain namespace was complete. Adds
the missing re-exports (including EnvironmentalMeasurementWarning on the
domain namespace) and a new architecture invariant test asserting that
every name the facade imports from a domain submodule is reachable on that
subpackage, so this class of gap cannot recur.
* test: forbid absolute self-imports in the architecture check
Review feedback: the ast edge extraction only saw relative imports, so an
absolute 'from phonometry.x import y' inside the package would bypass the
cross-package whitelist. Such imports now fail the architecture test
directly (the codebase has none; relative imports are the convention).
refactor: reorganize the package into twelve domain subpackages (3.2) (#162)
* refactor: private core to _internal, module-path shims, _plot skeleton (C1)
Foundation of the 3.2 package reorganization:
- _validation/_types/_warnings/_levels_math/utils move (git mv) into the
private phonometry/_internal/ package; ~90 import sites retargeted.
- New phonometry/_compat.py: dynamic sys.modules shims keep every moved
public module path importable for one deprecation cycle (silent import,
DeprecationWarning on attribute access); generalizes and absorbs the
former loudness.py PEP 562 shim (its 3.1 wording preserved).
- _plotting.py moves (git mv) to phonometry/_plot/common.py; a silent
explicit re-export shim keeps the old private path and the 84 lazy
.plot() call sites working until each domain retargets them.
- _warn_renamed gains a 'since' parameter (3.2 for the package moves).
- tests: new test_package_architecture.py (ast edge whitelist +
fresh-interpreter subpackage imports); test_deprecated_aliases gains the
frozen 85-path pre-move snapshot and the _MOVED shim behavior test;
pytest pythonpath=tests for the upcoming mirrored test tree.
- .git-blame-ignore-revs scaffold (hashes appended at the end of Phase 1).
* refactor: metrology subpackage (C2)
core, filter_design, frequencies, parametric_filters, levels, calibration,
compliance and uncertainty move (git mv) into phonometry/metrology/ with a
curated re-export __init__; the flat API and the old module paths keep
working (facade re-exports + _compat shims). Uncertainty renderers carve out
of _plot/common.py into _plot/metrology.py; the 14 metrology test files move
to tests/metrology/ and deep imports across tests/scripts point at the new
canonical paths.
* refactor: psychoacoustics subpackage (C3)
The thirteen psychoacoustics modules (Zwicker/ECMA/Moore-Glasberg loudness,
contours, sharpness, tonality, roughness, fluctuation strength, annoyance,
tonal audibility, private Zwicker tables) move into
phonometry/psychoacoustics/; renderers carve into _plot/psychoacoustics.py;
twelve test files move to tests/psychoacoustics/. The phonometry.loudness
alias now resolves to the relocated canonical module in a single hop.
* refactor: hearing and emission subpackages (C4+C5)
hearing/ gains threshold (renamed from hearing.py), noise-induced hearing
loss, occupational exposure, SII and STI; emission/ gains the ISO 3740
sound-power family, ISO 9614 intensity and ISO/TS 7849 vibration-based
power. Renderers carve into _plot/hearing.py and _plot/emission.py; the
eleven domain test files move under tests/hearing/ and tests/emission/.
* refactor: materials, room and building subpackages (C6-C8)
materials/ collects ISO 354/11654/12999-2 absorption, scattering-diffusion,
road absorption, impedance tube, airflow resistance and EN 29052-1 dynamic
stiffness; room/ the room-acoustics, impulse-response, open-plan, room-noise,
reverberation-prediction and EN 12354-6 modules; building/ the eleven-module
EN 12354 / ISO 717 / ISO 16283 insulation family with EN 15657 and
EN 12354-5 structure-borne sound. Renderers carve into the matching _plot
modules; twenty-six test files move under their domain directories.
* fix: complete the building plot TYPE_CHECKING imports (C8 follow-up)
* refactor: vibration and environmental subpackages (C9+C10)
vibration/ collects human vibration (ISO 8041-1/2631/5349), multiple-shock
(ISO 2631-5), mechanical mobility (ISO 7626-1) and transfer stiffness
(ISO 10846); environmental/ collects the renamed rating (Lden/Ldn) and
measurement (ISO 1996-2) modules plus outdoor propagation (ISO 9613-2),
air absorption (ISO 9613-1), wind-turbine noise (IEC 61400-11) and NT
ACOU 112 impulse prominence. Renderers carve into their _plot modules and
ten test files move under the domain directories.
* refactor: aircraft, underwater and electroacoustics subpackages (C11-C13)
aircraft/ collects EPNL, the renamed atmospheric absorption, ECAC Doc 29
airport contours and ECAC Doc 32 rotorcraft hemispheres; underwater/ the
renamed acoustics/propagation/sound_speed modules with ship noise, pile
driving, ambient noise, sonar equation, seabed reflection and the numerical
solvers; electroacoustics/ distortion and frequency response. Renderers
carve into their _plot modules; seventeen test files move under the domain
directories. All twelve domain subpackages are now in place.
* refactor: finalize the 3.2 package reorganization (C14)
- api-reference gains a Namespaces section (the twelve subpackages with
scope and the 'from phonometry import aircraft as air' idiom); README
quick-start notes the namespaces; CHANGELOG records the added namespaces,
the deprecated flat module paths and the pickle note.
- plot_excitation moves from _plot/common.py to _plot/room.py and the
facade imports it from there; _plot/common.py now holds only shared
helpers and infrastructure.
- hearing/ and environmental/ package __init__ re-export the full public
surface of their renamed modules (threshold, rating) so the pre-move
package-path imports stay silent, as promised by the migration contract.
- SonarCloud cpd exclusion follows core.py to metrology/; llms.txt
regenerated; .git-blame-ignore-revs lists the move commits and the local
blame config registers it.
Full suite 2647 passed; conformance 194/194; golden baseline byte-stable;
import time unchanged vs main (1.09 s vs 1.17 s).
* fix: re-export the measurement surface on phonometry.environmental
Subagent-review finding: the generated environmental/__init__.py missed the
ISO 1996-2 measurement module (its facade import block was momentarily
path-corrupted when the subpackage exports were collected), so
phonometry.environmental.assess_tonal_audibility and 15 sibling names
raised AttributeError while every other domain namespace was complete. Adds
the missing re-exports (including EnvironmentalMeasurementWarning on the
domain namespace) and a new architecture invariant test asserting that
every name the facade imports from a domain submodule is reachable on that
subpackage, so this class of gap cannot recur.
* test: forbid absolute self-imports in the architecture check
Review feedback: the ast edge extraction only saw relative imports, so an
absolute 'from phonometry.x import y' inside the package would bypass the
cross-package whitelist. Such imports now fail the architecture test
directly (the codebase has none; relative imports are the convention).