feat: underwater seabed reflection, ambient noise and ship-traffic source levels (#149)
* feat: underwater seabed reflection, ambient noise and ship-traffic source levels
Second half of the closed-form underwater propagation sub-phase (P1), building
on the transmission-loss/sound-speed/sonar core:
- seabed_reflection: fluid-fluid Rayleigh reflection coefficient (Medwin & Clay),
critical grazing angle arccos(c1/c2) and bottom loss BL = -20 lg|R|.
- ocean_ambient_noise: Wenz-framework spectrum levels, energy-summing wind noise
(rule of fives) and Mellen thermal noise, with an optional caller-supplied
shipping term.
- ship_traffic_noise: predicted ship source-level spectra from vessel class,
speed and length via JOMOPANS-ECHO (MacGillivray & de Jong 2021, default,
validated to the authors' File S1 reference calculator within 0.01 dB), plus
RANDI 3.1 and Wales & Heitmeyer (2002).
Each module exposes a frozen result dataclass with .plot(), a conformance check
with an independent oracle, one figure in four language/theme variants, and full
English docs plus EN/ES site guides. Conformance 179/179; full suite 2450 passed.
* refactor: address PR review feedback on underwater modules
- seabed_reflection: guard the c1 == c2 & grazing = 0 singular limit
(0/0 -> normal-incidence coefficient) so |R| never returns NaN; add test.
- ocean_ambient_noise: allow a calm sea (wind_speed_knots = 0) to return -inf
(zero energy contribution) rather than raising; add test.
- Migrate the three modules' input validation to the shared _validation helpers
(new require_positive_array enforces finite, positive, 1-D); removes
duplicated hand-rolled checks.
- Clarify units: source spectral-density is "dB re 1 µPa²/Hz at 1 m" (not the
ambiguous "/Hz m") in the plot label, figure, api-reference and docs; scope the
plane-wave reference note so it no longer claims to cover source levels; note
the wind-noise validity range (500 Hz-5 kHz) for the wide-range Wenz example.
- Label the wind/thermal component curves in the ambient-noise figure.
Kept the conformance thermal oracle's hardcoded physical constants (Boltzmann,
1 µPa) for clean-room independence from the module under test. Conformance
179/179; full suite 2452 passed.
* fix: SonarCloud float-equality and long-title docs-deploy failures
- seabed_reflection / ocean_ambient_noise: avoid float equality checks
(SonarCloud S1244). Detect the seabed 0/0 singular limit via np.isnan of the
result instead of comparing the denominator to 0.0; gate the calm-sea branch
with require_non_negative + a relational check.
- Shorten four site-guide titles so the rendered "<title> | phonometry" stays
within the 70-char html-validate limit (long-title), which was blocking the
docs deployment: the two underwater-propagation guides (EN/ES, lengthened in
this PR) and the two pre-existing wind-turbine-noise guides (EN/ES). The full
detail stays in each guide's description.
feat: underwater seabed reflection, ambient noise and ship-traffic source levels (#149)
* feat: underwater seabed reflection, ambient noise and ship-traffic source levels
Second half of the closed-form underwater propagation sub-phase (P1), building
on the transmission-loss/sound-speed/sonar core:
- seabed_reflection: fluid-fluid Rayleigh reflection coefficient (Medwin & Clay),
critical grazing angle arccos(c1/c2) and bottom loss BL = -20 lg|R|.
- ocean_ambient_noise: Wenz-framework spectrum levels, energy-summing wind noise
(rule of fives) and Mellen thermal noise, with an optional caller-supplied
shipping term.
- ship_traffic_noise: predicted ship source-level spectra from vessel class,
speed and length via JOMOPANS-ECHO (MacGillivray & de Jong 2021, default,
validated to the authors' File S1 reference calculator within 0.01 dB), plus
RANDI 3.1 and Wales & Heitmeyer (2002).
Each module exposes a frozen result dataclass with .plot(), a conformance check
with an independent oracle, one figure in four language/theme variants, and full
English docs plus EN/ES site guides. Conformance 179/179; full suite 2450 passed.
* refactor: address PR review feedback on underwater modules
- seabed_reflection: guard the c1 == c2 & grazing = 0 singular limit
(0/0 -> normal-incidence coefficient) so |R| never returns NaN; add test.
- ocean_ambient_noise: allow a calm sea (wind_speed_knots = 0) to return -inf
(zero energy contribution) rather than raising; add test.
- Migrate the three modules' input validation to the shared _validation helpers
(new require_positive_array enforces finite, positive, 1-D); removes
duplicated hand-rolled checks.
- Clarify units: source spectral-density is "dB re 1 µPa²/Hz at 1 m" (not the
ambiguous "/Hz m") in the plot label, figure, api-reference and docs; scope the
plane-wave reference note so it no longer claims to cover source levels; note
the wind-noise validity range (500 Hz-5 kHz) for the wide-range Wenz example.
- Label the wind/thermal component curves in the ambient-noise figure.
Kept the conformance thermal oracle's hardcoded physical constants (Boltzmann,
1 µPa) for clean-room independence from the module under test. Conformance
179/179; full suite 2452 passed.
* fix: SonarCloud float-equality and long-title docs-deploy failures
- seabed_reflection / ocean_ambient_noise: avoid float equality checks
(SonarCloud S1244). Detect the seabed 0/0 singular limit via np.isnan of the
result instead of comparing the denominator to 0.0; gate the calm-sea branch
with require_non_negative + a relational check.
- Shorten four site-guide titles so the rendered "<title> | phonometry" stays
within the 70-char html-validate limit (long-title), which was blocking the
docs deployment: the two underwater-propagation guides (EN/ES, lengthened in
this PR) and the two pre-existing wind-turbine-noise guides (EN/ES). The full
detail stays in each guide's description.
feat: environmental-noise determination (ISO 1996-2 tonal adjustment + uncertainty) (#133)
* feat: environmental-noise determination (ISO 1996-2 tonal adjustment + uncertainty)
ISO 1996-2:2017 is the determination part of the ISO 1996 environmental-noise
family (the descriptors Lden/Ldn live in ISO 1996-1, already in
phonometry.environmental). New module phonometry.environmental_measurement:
* Tonal adjustment (engineering method, ISO 1996-2:2007 Annex C):
tonal_audibility() forms ΔLta = Lpt − Lpn + 2 + lg[1 + (fc/502)^2.5] dB
(Formula C.3) and tonal_adjustment() the piecewise Kt (Formulae C.4-C.6);
assess_tonal_audibility() bundles them with the critical bandwidth (Table
C.1) into a plottable result. tonal_seeking_survey() is the one-third-octave
15/8/5 dB screen (Annex K) and tonal_adjustment_from_mean_audibility() the
Table J.1 mean-audibility route.
* Residual-noise correction: residual_sound_correction() (Formula 16, flags a
<3 dB margin as an upper bound) and gaussian_residual_level() (Annex I).
* Measurement uncertainty: combined_standard_uncertainty() (Formula 2),
environmental_expanded_uncertainty() (k=2/1.3), residual_correction_uncertainty()
(Formulae F.7-F.9) and uncertainty_from_repeated_measurements() (Formulae 17-20).
Unlike the removed 2017 engineering method (which defers to ISO/PAS 20065), the
detailed 2007/2009 Annex C algorithm is self-contained and has worked numeric
examples: conformance is anchored on Annex C.5 (ΔLta and Kt) and Annex G.2 (the
combined uncertainty u = 2.18 dB) -- three new checks (115/115, byte-stable).
The raw-FFT tone-detection pipeline is intentionally out of scope: the standard
gives no raw input spectra, so it could not be verified. 25 unit tests; docs
(GitHub + site EN/ES) get a new levels section with a figure and API rows.
* test: anchor ISO 1996-2 residual/gaussian/uncertainty tests on hand-computed values
Review found four tests re-derived their expected value from the same
arithmetic expression as the implementation, so a transcription error would
go undetected. Replace them with independent hand-computed literals (residual
correction Formula 16, Gaussian residual I.1/I.2, residual-correction
uncertainty F.9, repeated-measurement mean/uncertainty 18/20).
* fix: avoid assert for type narrowing in gaussian_residual_level (bandit B101)
The CI quality job runs bandit -r src, which flags assert statements (stripped
under python -O). Replace the mypy-narrowing assert with an elif/else-raise so
the branch is both bandit-clean and correctly narrowed.
Audit pass 7: bilingual EN/ES consistency (#116)
* docs: bilingual EN/ES consistency batch
Audit batch 7 - the Spanish side catches up with the English one:
Content drift: the five per-architecture filter figures join the ES
filter-banks guide; the missing outdoor-propagation card joins the ES
index (7 cards on both sides); the sound-power card says five methods
in both languages (it said three since before ISO 3745/9614-3).
Decimals: the four prose dot-decimals and four code-comment
comma-decimals fixed per convention, and the systematic LaTeX {,}
migration lands - 146 conversions across 16 ES files, every hit
manually reviewed (clause numbers, standard designations, def.:
literals and code identifiers deliberately untouched; zero \d.\d
remain in ES math). The theory band-edge table keeps points (it
mirrors a code snippet's printed output); the why-phonometry report
tables convert to commas (prose).
Terminology unified: fonios, diezmado (including the divergent
multirate alt pair), umbral de audicion, banda atenuada, nivel de
evaluacion (including the rendered NT ACOU 112 ES diagram - the
generator string is fixed and only that SVG pair regenerated),
sharpness (agudeza), and clausula/apartado unified per file majority
(theory: clausula; api: apartado).
Code comments: 22 English comments translated across gum-uncertainty,
hearing-threshold, noise-induced-hearing-loss, room-noise and
occupational-exposure ES (the occupational f-string outputs were
re-executed and match the echoed comments byte-for-byte).
* docs: 'atenuación en banda atenuada' in the ES filter guide
The glossary fix produced a clumsy repetition ('atenuación de banda
atenuada'); the natural phrasing for stopband attenuation is 'en'
(Copilot).
* docs: the sound-power cards enumerate the guide's five routes exactly
The card grouped ISO 9614-2/3 into one item, making 'five routes' read
as four groups (or six standards); it now mirrors the guide's canonical
five-item enumeration, in both languages (CodeRabbit).