From bc7c9ee0f63883197e3a6a5004026eafdb018521 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20M=2E=20Requena=20Plens?= Date: Sun, 2 Aug 2026 15:52:10 +0200 Subject: [PATCH 1/5] Rename environmental to environment and give it three subgroups The name was the last of the three that did not match: the code said environmental, the generated reference said environment and the guides said Environment. Now they all say the same thing. The three subgroups follow how an environmental study is written. What the source emits is environment.sources: CNOSSOS road and rail, and the wind turbine. What the path does to it is environment.propagation: ISO 9613-1 and -2, ground effect and barriers, refraction. What the law makes of the result at the receiver is environment.assessment: ISO 1996-1 and -2, impulsive prominence, RD 1367/2007. Two modules lose a word their subgroup now says: atmospheric_refraction is propagation.refraction, and spanish_regulation is assessment.spain, which is the shape the next national code will take. The reference loses a cross-package exception with the rename. The wind turbine was documented under Aircraft and wind energy because that is where its audience was looking, and it lives with the environmental rating code; with a sources subgroup it can be documented where it lives. That section is Aircraft noise now and draws from one package, which leaves two sections in the whole taxonomy that deliberately span two parents. Nothing moves in the namespace: from phonometry import environment exports the same 165 names. Compatibility needed one thing the earlier splits did not: the package itself is an alias, so the shim of a renamed package has to serve the modules that moved out of it as well as the names that stayed. Twelve module paths and the package prefix keep working until 5.0. --- .github/labeler.yml | 4 +- CHANGELOG.md | 24 ++ README.md | 2 +- README_PYPI.md | 2 +- docs/api-reference.md | 6 +- docs/atmospheric-refraction.md | 2 +- docs/environmental-levels.md | 26 +- docs/ground-barriers.md | 2 +- docs/impulse-prominence.md | 38 +-- docs/outdoor-propagation.md | 44 ++-- docs/reverberation-prediction.md | 12 +- docs/spanish-noise-regulation.md | 104 ++++---- docs/wind-turbine-noise.md | 14 +- llms-full.txt | 18 +- llms.txt | 6 +- scripts/api_taxonomy.py | 40 ++- scripts/conformance_report.py | 4 +- scripts/generate_api_docs.py | 2 +- scripts/generate_graphs.py | 6 +- scripts/generate_reports.py | 2 +- site/public/llms/llms-levels-weighting.txt | 2 +- site/public/llms/llms-outdoor-sound.txt | 2 +- site/public/llms/llms-simulation.txt | 2 +- .../docs/es/guides/atmospheric-refraction.mdx | 2 +- .../docs/es/guides/environmental-levels.mdx | 28 +- .../docs/es/guides/ground-barriers.mdx | 2 +- .../docs/es/guides/impulse-prominence.mdx | 40 +-- .../docs/es/guides/outdoor-propagation.mdx | 44 ++-- .../es/guides/reverberation-prediction.mdx | 12 +- .../es/guides/spanish-noise-regulation.mdx | 104 ++++---- .../docs/es/guides/wind-turbine-noise.mdx | 16 +- .../docs/guides/atmospheric-refraction.mdx | 2 +- .../docs/guides/environmental-levels.mdx | 28 +- .../content/docs/guides/ground-barriers.mdx | 2 +- .../docs/guides/impulse-prominence.mdx | 40 +-- .../docs/guides/outdoor-propagation.mdx | 44 ++-- .../docs/guides/reverberation-prediction.mdx | 12 +- .../docs/guides/spanish-noise-regulation.mdx | 104 ++++---- .../docs/guides/wind-turbine-noise.mdx | 16 +- .../api/building/spanish-building-code.md | 2 +- .../api/environment/air-absorption.md | 2 +- .../reference/api/environment/cnossos-rail.md | 2 +- .../reference/api/environment/cnossos-road.md | 4 +- .../api/environment/ground-barriers.md | 4 +- .../api/environment/impulse-prominence.md | 2 +- .../api/environment/impulsive-sound.md | 2 +- .../reference/api/environment/measurement.md | 4 +- .../api/environment/outdoor-propagation.md | 4 +- .../docs/reference/api/environment/rating.md | 2 +- ...tmospheric-refraction.md => refraction.md} | 26 +- .../{spanish-regulation.md => spain.md} | 28 +- .../wind-turbine.md} | 20 +- site/src/content/docs/reference/api/index.md | 28 +- .../api/psychoacoustics/tone-audibility.md | 2 +- site/src/generated/api-sidebar.mjs | 10 +- src/phonometry/__init__.py | 188 +++++++------- src/phonometry/_compat.py | 58 ++++- src/phonometry/_plot/environmental.py | 64 ++--- src/phonometry/_plot/geometry.py | 4 +- src/phonometry/_report/iec61400.py | 6 +- src/phonometry/_report/iso1996_impulse.py | 6 +- src/phonometry/_report/iso1996_tone.py | 4 +- src/phonometry/_report/iso9613.py | 20 +- src/phonometry/_report/rd1367.py | 6 +- .../aircraft/atmospheric_absorption.py | 2 +- src/phonometry/aircraft/rotorcraft_noise.py | 4 +- .../building/spanish_building_code.py | 2 +- .../__init__.py | 245 +++++++++--------- .../environment/assessment/__init__.py | 134 ++++++++++ .../assessment}/impulse_prominence.py | 12 +- .../assessment}/impulsive_sound.py | 12 +- .../assessment}/measurement.py | 8 +- .../assessment}/rating.py | 0 .../assessment/spain.py} | 26 +- .../environment/propagation/__init__.py | 84 ++++++ .../propagation}/air_absorption.py | 8 +- .../propagation}/ground_barriers.py | 28 +- .../propagation}/outdoor_propagation.py | 14 +- .../propagation/refraction.py} | 20 +- .../environment/sources/__init__.py | 158 +++++++++++ .../sources}/cnossos_rail.py | 8 +- .../sources}/cnossos_road.py | 6 +- .../sources/wind_turbine.py} | 16 +- .../psychoacoustics/tone_audibility.py | 6 +- .../test_aircraft_atmospheric_absorption.py | 2 +- tests/aircraft/test_rotorcraft_noise.py | 4 +- .../assessment}/test_impulse_prominence.py | 4 +- .../test_impulse_prominence_report.py | 2 +- .../assessment}/test_impulsive_sound.py | 4 +- .../assessment/test_measurement.py} | 4 +- .../assessment/test_rating.py} | 0 .../assessment}/test_rd1367_report.py | 2 +- .../assessment/test_spain.py} | 4 +- .../propagation}/test_air_absorption.py | 2 +- .../propagation}/test_ground_barriers.py | 0 .../propagation}/test_outdoor_propagation.py | 4 +- .../test_outdoor_propagation_report.py | 0 .../propagation/test_refraction.py} | 4 +- .../sources}/test_cnossos_rail.py | 2 +- .../sources}/test_cnossos_road.py | 6 +- .../sources/test_wind_turbine.py} | 2 +- .../test_wind_turbine_tonality_report.py | 2 +- .../test_environment_plot_i18n.py} | 8 +- tests/reference_data.py | 2 +- tests/test_deprecated_aliases.py | 16 +- tests/test_package_architecture.py | 4 +- 106 files changed, 1341 insertions(+), 888 deletions(-) rename site/src/content/docs/reference/api/environment/{atmospheric-refraction.md => refraction.md} (92%) rename site/src/content/docs/reference/api/environment/{spanish-regulation.md => spain.md} (95%) rename site/src/content/docs/reference/api/{aeroacoustics/wind-turbine-noise.md => environment/wind-turbine.md} (90%) rename src/phonometry/{environmental => environment}/__init__.py (93%) create mode 100644 src/phonometry/environment/assessment/__init__.py rename src/phonometry/{environmental => environment/assessment}/impulse_prominence.py (97%) rename src/phonometry/{environmental => environment/assessment}/impulsive_sound.py (98%) rename src/phonometry/{environmental => environment/assessment}/measurement.py (99%) rename src/phonometry/{environmental => environment/assessment}/rating.py (100%) rename src/phonometry/{environmental/spanish_regulation.py => environment/assessment/spain.py} (98%) create mode 100644 src/phonometry/environment/propagation/__init__.py rename src/phonometry/{environmental => environment/propagation}/air_absorption.py (98%) rename src/phonometry/{environmental => environment/propagation}/ground_barriers.py (97%) rename src/phonometry/{environmental => environment/propagation}/outdoor_propagation.py (99%) rename src/phonometry/{environmental/atmospheric_refraction.py => environment/propagation/refraction.py} (98%) create mode 100644 src/phonometry/environment/sources/__init__.py rename src/phonometry/{environmental => environment/sources}/cnossos_rail.py (99%) rename src/phonometry/{environmental => environment/sources}/cnossos_road.py (99%) rename src/phonometry/{environmental/wind_turbine_noise.py => environment/sources/wind_turbine.py} (97%) rename tests/{environmental => environment/assessment}/test_impulse_prominence.py (97%) rename tests/{environmental => environment/assessment}/test_impulse_prominence_report.py (99%) rename tests/{environmental => environment/assessment}/test_impulsive_sound.py (98%) rename tests/{environmental/test_environmental_measurement.py => environment/assessment/test_measurement.py} (99%) rename tests/{environmental/test_environmental.py => environment/assessment/test_rating.py} (100%) rename tests/{environmental => environment/assessment}/test_rd1367_report.py (99%) rename tests/{environmental/test_spanish_regulation.py => environment/assessment/test_spain.py} (99%) rename tests/{environmental => environment/propagation}/test_air_absorption.py (99%) rename tests/{environmental => environment/propagation}/test_ground_barriers.py (100%) rename tests/{environmental => environment/propagation}/test_outdoor_propagation.py (99%) rename tests/{environmental => environment/propagation}/test_outdoor_propagation_report.py (100%) rename tests/{environmental/test_atmospheric_refraction.py => environment/propagation/test_refraction.py} (99%) rename tests/{environmental => environment/sources}/test_cnossos_rail.py (99%) rename tests/{environmental => environment/sources}/test_cnossos_road.py (99%) rename tests/{environmental/test_wind_turbine_noise.py => environment/sources/test_wind_turbine.py} (99%) rename tests/{environmental => environment/sources}/test_wind_turbine_tonality_report.py (99%) rename tests/{environmental/test_environmental_plot_i18n.py => environment/test_environment_plot_i18n.py} (92%) diff --git a/.github/labeler.yml b/.github/labeler.yml index 0f367fe6f..acb35543f 100644 --- a/.github/labeler.yml +++ b/.github/labeler.yml @@ -143,9 +143,9 @@ ci: - changed-files: - any-glob-to-any-file: ["src/phonometry/emission/**", "tests/emission/**"] -"area: environmental": +"area: environment": - changed-files: - - any-glob-to-any-file: ["src/phonometry/environmental/**", "tests/environmental/**"] + - any-glob-to-any-file: ["src/phonometry/environment/**", "tests/environment/**"] "area: filters": - changed-files: diff --git a/CHANGELOG.md b/CHANGELOG.md index 8a5fe34ad..45d0766d5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1005,6 +1005,30 @@ and this project adheres to [Semantic Versioning](https://semver.org/). validator says the same thing in the form the whole package uses, rejects infinities too, and names the parameter that failed instead of both at once. +- `phonometry.environmental` is `phonometry.environment`, with three + subgroups that follow how an environmental study is written: what the source + emits (`environment.sources`: CNOSSOS road and rail, wind turbines), what the + path does to it (`environment.propagation`: ISO 9613-1 and -2, ground effect + and barriers, refraction) and what the law makes of the result at the + receiver (`environment.assessment`: ISO 1996-1 and -2, impulsive prominence, + RD 1367/2007). Two modules lose a word the subgroup now says: + `atmospheric_refraction` is `propagation.refraction` and + `spanish_regulation` is `assessment.spain`, which is the shape the next + national code will take. + + The name was the last of the three that did not match: the code said + `environmental`, the reference said `environment` and the guides said + Environment. Now they all say the same thing, and the generated reference + loses a cross-package exception: the wind turbine used to be documented + under Aircraft and wind energy because that is where its audience was + looking, and with the sources subgroup it can be documented where it lives. + That section is now Aircraft noise and draws from one package, which leaves + two sections in the whole taxonomy that deliberately span two parents. + + Nothing moves in the namespace: `from phonometry import environment` exports + the same 165 names, and `environmental` keeps working as a package alias + until 5.0, along with all twelve of its module paths. + - `phonometry.vibration` has three families, by who reads them. `vibration.structural` is the structural acoustics that feeds building prediction: mobility (ISO 7626), plate junctions, radiation efficiency, diff --git a/README.md b/README.md index 2d5eb7fab..4b8cb8e48 100644 --- a/README.md +++ b/README.md @@ -109,7 +109,7 @@ tl = underwater.transmission_loss(...) | `building` | Field airborne, impact and façade insulation with R′w/DnT,w/L′nT,w/D2m,nT,w and C/Ctr/CI (ISO 16283-1/2/3, ISO 717-1/2); laboratory R/Ln (ISO 10140) and survey method (ISO 10052); insulation by intensity (ISO 15186); flanking transmission measurement and prediction (ISO 10848, EN 12354-1/2) and façade/outdoor radiation (EN 12354-3/4); measurement uncertainty (ISO 12999-1); panel transmission theory (mass law, coincidence, double walls, slits and apertures); floor-covering improvement (ISO 16251-1); reception-plate power (EN 15657) and installed structure-borne prediction (EN 12354-5); dynamic stiffness (EN 29052-1) | | `materials` | Absorption ratings αw with classes (ISO 11654) and uncertainty (ISO 12999-2); impedance-tube absorption, impedance and transmission loss (ISO 10534-1/2, ASTM E2611) plus a virtual FDTD tube; porous and multilayer absorber models (Delany-Bazley, Miki, JCA, TMM with MPP and membranes); slow-sound metamaterial absorbers at critical coupling; scattering and diffusion coefficients (ISO 17497-1/2); Schroeder diffuser design and far-field prediction; deep-subwavelength metadiffusers; in-situ road-surface absorption (ISO 13472-1/2); airflow resistance (ISO 9053-1/2) | | `emission` | Sound power by enveloping surface (ISO 3744/3746), reverberation room (ISO 3741), precision anechoic rooms (ISO 3745) and intensity scanning with field indicators and grades (ISO 9614-2/3); two-microphone p-p intensity (IEC 61043, ISO 9614-1); sound power from surface vibration (ISO/TS 7849); noise-emission declarations (ISO 4871) | -| `environmental` | Rating levels, Lden/Ldn and adjustments (ISO 1996-1/2); impulsive-sound prominence (NT ACOU 112); atmospheric absorption (ISO 9613-1) and the ISO 9613-2 general method with per-term octave breakdown; spherical ground effect and wave-theoretic barriers; refraction ray tracing and the GFPE; wind-turbine apparent sound power and tonal audibility (IEC 61400-11) | +| `environment` | Sources: CNOSSOS-EU road and rail emission, wind-turbine apparent sound power and tonal audibility (IEC 61400-11). Propagation: atmospheric absorption (ISO 9613-1) and the ISO 9613-2 general method with per-term octave breakdown, spherical ground effect and wave-theoretic barriers, refraction ray tracing and the GFPE. Assessment: rating levels, Lden/Ldn and adjustments (ISO 1996-1/2), impulsive-sound prominence (NT ACOU 112) and the Spanish RD 1367/2007 | | `aircraft` | EPNL certification chain (ICAO Annex 16) with IEC 61265 verification and SAE ARP 5534 absorption; airport noise contours (ECAC Doc 29) with the EASA ANP fleet database; rotorcraft hemisphere method (ECAC Doc 32) | | `underwater` | Levels re 1 µPa (ISO 18405); ship radiated noise (ISO 17208-1/2); pile driving (ISO 18406); ship-traffic source levels (JOMOPANS-ECHO) and Wenz ambient noise; sonar equation and detection range; sound speed and seabed reflection; transmission loss from spreading laws and Weston's shallow-water regimes to normal-mode, ray and parabolic-equation solvers; marine-mammal audiograms and regulatory auditory weighting (NMFS 2024/2018, Southall et al. 2019) | | `vibration` | Human vibration: weightings (ISO 8041-1), whole-body metrics and buildings (ISO 2631-1/2), multiple shocks (ISO 2631-5), hand-arm and A(8) (ISO 5349); mobility and the FRF family (ISO 7626); isolator transfer stiffness (ISO 10846); plate-junction transmission and Kij; radiation efficiency and point mobilities | diff --git a/README_PYPI.md b/README_PYPI.md index 76d234b79..b9a85cbe2 100644 --- a/README_PYPI.md +++ b/README_PYPI.md @@ -116,7 +116,7 @@ tl = underwater.transmission_loss(...) | `building` | Field airborne, impact and façade insulation with R′w/DnT,w/L′nT,w/D2m,nT,w and C/Ctr/CI (ISO 16283-1/2/3, ISO 717-1/2); laboratory R/Ln (ISO 10140) and survey method (ISO 10052); insulation by intensity (ISO 15186); flanking transmission measurement and prediction (ISO 10848, EN 12354-1/2) and façade/outdoor radiation (EN 12354-3/4); measurement uncertainty (ISO 12999-1); panel transmission theory (mass law, coincidence, double walls, slits and apertures); floor-covering improvement (ISO 16251-1); reception-plate power (EN 15657) and installed structure-borne prediction (EN 12354-5); dynamic stiffness (EN 29052-1) | | `materials` | Absorption ratings αw with classes (ISO 11654) and uncertainty (ISO 12999-2); impedance-tube absorption, impedance and transmission loss (ISO 10534-1/2, ASTM E2611) plus a virtual FDTD tube; porous and multilayer absorber models (Delany-Bazley, Miki, JCA, TMM with MPP and membranes); slow-sound metamaterial absorbers at critical coupling; scattering and diffusion coefficients (ISO 17497-1/2); Schroeder diffuser design and far-field prediction; deep-subwavelength metadiffusers; in-situ road-surface absorption (ISO 13472-1/2); airflow resistance (ISO 9053-1/2) | | `emission` | Sound power by enveloping surface (ISO 3744/3746), reverberation room (ISO 3741), precision anechoic rooms (ISO 3745) and intensity scanning with field indicators and grades (ISO 9614-2/3); two-microphone p-p intensity (IEC 61043, ISO 9614-1); sound power from surface vibration (ISO/TS 7849); noise-emission declarations (ISO 4871) | -| `environmental` | Rating levels, Lden/Ldn and adjustments (ISO 1996-1/2); impulsive-sound prominence (NT ACOU 112); atmospheric absorption (ISO 9613-1) and the ISO 9613-2 general method with per-term octave breakdown; spherical ground effect and wave-theoretic barriers; refraction ray tracing and the GFPE; wind-turbine apparent sound power and tonal audibility (IEC 61400-11) | +| `environment` | Sources: CNOSSOS-EU road and rail emission, wind-turbine apparent sound power and tonal audibility (IEC 61400-11). Propagation: atmospheric absorption (ISO 9613-1) and the ISO 9613-2 general method with per-term octave breakdown, spherical ground effect and wave-theoretic barriers, refraction ray tracing and the GFPE. Assessment: rating levels, Lden/Ldn and adjustments (ISO 1996-1/2), impulsive-sound prominence (NT ACOU 112) and the Spanish RD 1367/2007 | | `aircraft` | EPNL certification chain (ICAO Annex 16) with IEC 61265 verification and SAE ARP 5534 absorption; airport noise contours (ECAC Doc 29) with the EASA ANP fleet database; rotorcraft hemisphere method (ECAC Doc 32) | | `underwater` | Levels re 1 µPa (ISO 18405); ship radiated noise (ISO 17208-1/2); pile driving (ISO 18406); ship-traffic source levels (JOMOPANS-ECHO) and Wenz ambient noise; sonar equation and detection range; sound speed and seabed reflection; transmission loss from spreading laws and Weston's shallow-water regimes to normal-mode, ray and parabolic-equation solvers; marine-mammal audiograms and regulatory auditory weighting (NMFS 2024/2018, Southall et al. 2019) | | `vibration` | Human vibration: weightings (ISO 8041-1), whole-body metrics and buildings (ISO 2631-1/2), multiple shocks (ISO 2631-5), hand-arm and A(8) (ISO 5349); mobility and the FRF family (ISO 7626); isolator transfer stiffness (ISO 10846); plate-junction transmission and Kij; radiation efficiency and point mobilities | diff --git a/docs/api-reference.md b/docs/api-reference.md index 9c245ef1b..ee95dacab 100644 --- a/docs/api-reference.md +++ b/docs/api-reference.md @@ -35,7 +35,7 @@ contour = aircraft.noise_contour(path, powers, distances, sel, lmax, x=gx, y=gy) | `phonometry.materials` | Absorption (ISO 354/11654), impedance tube, airflow resistance, scattering/diffusion, road absorption, dynamic stiffness | | `phonometry.building` | Sound insulation measurement and prediction (EN 12354, ISO 717/16283/10140/15186/10052/10848), structure-borne sound | | `phonometry.vibration` | Three families: `structural` (mobility ISO 7626, plate junctions, radiation, experimental SEA, transfer stiffness ISO 10846), `human` (ISO 2631/5349/8041 exposure and multiple shocks) and `machinery` (fault frequencies and condition monitoring) | -| `phonometry.environmental` | Rating levels, ISO 1996-2 measurement, outdoor propagation (ISO 9613), atmospheric refraction (ray tracing and the parabolic equation), wind-turbine noise, impulsive prominence | +| `phonometry.environment` | Three subgroups: `sources` (CNOSSOS road and rail emission, wind-turbine apparent sound power), `propagation` (ISO 9613-1/-2, ground effect and barriers, refraction by ray tracing and the parabolic equation) and `assessment` (ISO 1996-1/-2 rating and measurement, impulsive prominence, RD 1367/2007) | | `phonometry.aircraft` | EPNL (ICAO Annex 16), SAE ARP 5534 absorption, airport contours (ECAC Doc 29), rotorcraft (ECAC Doc 32) | | `phonometry.underwater` | ISO 18405/17208/18406 levels, propagation and Weston regimes, sound speed, sonar equation and detection range, seabed, ambient and ship-traffic noise, marine-mammal audiograms and regulatory auditory weighting, numerical solvers | | `phonometry.electroacoustics` | Distortion (IEC 60268-3 / AES17), transfer function and coherence, radiating piston | @@ -46,8 +46,8 @@ contour = aircraft.noise_contour(path, powers, distances, sel, lmax, x=gx, y=gy) Every name in the table below is also re-exported at the top level, so `from phonometry import ` works for every row. Two generations of module paths are still importable and warn on use: the pre-3.2 flat ones (for example -`phonometry.insulation`), removed in 4.0, and the pre-4.0 ones that the split -of `metrology` moved (for example `phonometry.metrology.levels`, now +`phonometry.building.insulation`), removed in 4.0, and the pre-4.0 ones that the split +of `metrology` moved (for example `phonometry.signals.levels`, now `phonometry.signals.levels`), removed in 5.0. Reading a moved name from the namespace it left (`metrology.leq`) warns and delegates as well. diff --git a/docs/atmospheric-refraction.md b/docs/atmospheric-refraction.md index f779edae6..fcf1a6f13 100644 --- a/docs/atmospheric-refraction.md +++ b/docs/atmospheric-refraction.md @@ -7,7 +7,7 @@ The [ISO 9613-2 method](outdoor-propagation.md) and the atmosphere. In reality the sound speed changes with height, because temperature and wind change with height, and this **refracts** sound: rays curve, and over a few hundred metres the received level can swing by tens of decibels. This page -covers `phonometry.environmental.atmospheric_refraction`, the +covers `phonometry.environment.atmospheric_refraction`, the refracting-atmosphere counterpart of the ocean solvers in [`phonometry.underwater.numerical_propagation`](underwater-solvers.md): a **ray model** and a **parabolic-equation (PE)** solver, both clean-room from diff --git a/docs/environmental-levels.md b/docs/environmental-levels.md index f9ab038e3..991715abd 100644 --- a/docs/environmental-levels.md +++ b/docs/environmental-levels.md @@ -30,12 +30,12 @@ and `composite_rating_level()` the general whole-day composite of clause 6.5 +6 dB prominent tones): ```python -from phonometry import environmental +from phonometry import environment -l = environmental.lden(63.2, 58.1, 51.4) # from LAeq per period -r = environmental.composite_rating_level([(63.2, 12, 0.0), # day +l = environment.lden(63.2, 58.1, 51.4) # from LAeq per period +r = environment.composite_rating_level([(63.2, 12, 0.0), # day (58.1, 4, 5.0), # evening (+5) - (51.4, 8, 10.0)]) # night (+10) == environmental.lden + (51.4, 8, 10.0)]) # night (+10) == environment.lden ``` Synthetic 24-hour urban LAeq profile with day, evening and night bands, the +5 and +10 dB weighted period levels and the resulting Lden @@ -46,7 +46,7 @@ r = environmental.composite_rating_level([(63.2, 12, 0.0), # day ```python import numpy as np import matplotlib.pyplot as plt -from phonometry import environmental +from phonometry import environment # Synthetic hourly LAeq of an urban road (dB), hours 00 to 23 laeq_h = np.array([48, 46, 45, 45, 46, 50, 56, 64, 66, 65, 63, 63, @@ -58,7 +58,7 @@ def period_leq(idx): ld = period_leq(np.arange(7, 19)) # day 07-19 le = period_leq(np.arange(19, 23)) # evening 19-23 ln_ = period_leq(np.r_[23, np.arange(0, 7)]) # night 23-07 -l_den = environmental.lden(ld, le, ln_) +l_den = environment.lden(ld, le, ln_) print(f"Lden = {l_den:.1f} dB") # Lden = 64.3 dB fig, ax = plt.subplots() @@ -124,10 +124,10 @@ $K_t$ (Table J.1). ```python import matplotlib.pyplot as plt -from phonometry import environmental +from phonometry import environment # ISO 1996-2:2007 Annex C.5, Example 2 (two tones near 400 Hz): -res = environmental.assess_tonal_audibility(tone_level=54.1, masking_noise_level=45.2, +res = environment.assess_tonal_audibility(tone_level=54.1, masking_noise_level=45.2, centre_frequency=430.0) print(res.audibility, res.adjustment) # ΔLta ≈ 11.1 dB -> Kt = 6 dB res.plot() @@ -154,20 +154,20 @@ reported alongside as `approximate_uncertainty` and a warning when the levels spread beyond 3 dB, where the substitute grossly inflates. ```python -from phonometry import environmental +from phonometry import environment # Tonal adjustment for a prominent tone: -tonal = environmental.assess_tonal_audibility(54.1, 45.2, 430.0) # TonalAssessmentResult +tonal = environment.assess_tonal_audibility(54.1, 45.2, 430.0) # TonalAssessmentResult kt = tonal.adjustment # 6 dB tonal.plot() # this audibility on the Kt curve, as in the figure above # Subtract residual (background) noise from a measured level: -corr = environmental.residual_sound_correction(measured_level=58.0, residual_level=50.0) +corr = environment.residual_sound_correction(measured_level=58.0, residual_level=50.0) corr.corrected_level, corr.reliable # Combine an uncertainty budget and expand to 95 %: -u = environmental.combined_standard_uncertainty([0.59, 0.3, 2.0, 0.40, 0.38]) # 2.18 dB (G.2) -environmental.expanded_uncertainty(u) # 4.36 dB (k = 2) +u = environment.combined_standard_uncertainty([0.59, 0.3, 2.0, 0.40, 0.38]) # 2.18 dB (G.2) +environment.expanded_uncertainty(u) # 4.36 dB (k = 2) ``` ## Quick answers diff --git a/docs/ground-barriers.md b/docs/ground-barriers.md index 94fcf4c14..530c51bf5 100644 --- a/docs/ground-barriers.md +++ b/docs/ground-barriers.md @@ -4,7 +4,7 @@ The [ISO 9613-2 general method](outdoor-propagation.md) folds the ground and barrier terms into tabulated, energy-based corrections. This page covers the -underlying wave acoustics in `phonometry.environmental.ground_barriers`: the +underlying wave acoustics in `phonometry.environment.ground_barriers`: the **spherical-wave reflection coefficient** of a finite-impedance ground (Weyl-Van der Pol) and the **wave-theoretic diffraction** of a screen, both in a homogeneous (non-refracting, non-turbulent) atmosphere. These are the physical diff --git a/docs/impulse-prominence.md b/docs/impulse-prominence.md index 483cbd527..60a22ec2d 100644 --- a/docs/impulse-prominence.md +++ b/docs/impulse-prominence.md @@ -36,10 +36,10 @@ $K_I$ (the adjustment is 0 dB when no event qualifies). [Watch the high-resolution video (WebM)](https://raw.githubusercontent.com/jmrplens/phonometry/main/.github/images/anim_onset_detection.webm) ```python -from phonometry import environmental +from phonometry import environment # Three candidate impulses: (onset rate dB/s, level difference dB). -result = environmental.impulse_prominence([1200.0, 300.0, 60.0], [32.0, 18.0, 11.0]) +result = environment.impulse_prominence([1200.0, 300.0, 60.0], [32.0, 18.0, 11.0]) print(result.per_impulse.round(2)) # [12.25 9.94 7.42] print(round(result.prominence, 2)) # 12.25 (the governing impulse) print(round(result.adjustment, 2)) # 13.05 dB @@ -50,10 +50,10 @@ result.plot() # the KI(P) curve of section 2 with these impulses marked A single impulse can be evaluated directly with `predicted_prominence`: ```python -from phonometry import environmental +from phonometry import environment # P = 3*lg(1000) + 2*lg(30) = 9 + 2.95 = 11.95. -print(round(environmental.predicted_prominence(1000.0, 30.0), 4)) # 11.9542 +print(round(environment.predicted_prominence(1000.0, 30.0), 4)) # 11.9542 ``` ## 2. Adjustment to LAeq (clause 8) @@ -66,10 +66,10 @@ K_I = 1.8\,(P - 5)\ \text{dB} \quad (P > 5), \qquad K_I = 0 \quad (P \le 5). $$ ```python -from phonometry import environmental +from phonometry import environment -print(float(environmental.impulse_adjustment(10.0))) # 9.0 dB -print(float(environmental.impulse_adjustment(5.0))) # 0.0 dB (at the threshold) +print(float(environment.impulse_adjustment(10.0))) # 9.0 dB +print(float(environment.impulse_adjustment(5.0))) # 0.0 dB (at the threshold) ``` The adjustment is applied to $L_{Aeq,30\text{min}}$ from the single event with the @@ -82,10 +82,10 @@ L_{Ar,T} = 10\,\log_{10}\!\left(\frac{1}{T}\sum_N \Delta t_N\, $$ ```python -from phonometry import environmental +from phonometry import environment # Two 30-min periods: one impulsive (KI = 7.6 dB), one quiet. -print(round(environmental.rating_level([72.0, 66.0], [7.6, 0.0], [30.0, 30.0], 60.0), 2)) +print(round(environment.rating_level([72.0, 66.0], [7.6, 0.0], [30.0, 30.0], 60.0), 2)) # 76.78 dB ``` @@ -102,16 +102,16 @@ measurement of ISO 1996-2. ```python import numpy as np import matplotlib.pyplot as plt -from phonometry import environmental +from phonometry import environment # One line for the adjustment curve with the impulses marked: -environmental.impulse_prominence([1200.0, 300.0, 60.0], [32.0, 18.0, 11.0]).plot() +environment.impulse_prominence([1200.0, 300.0, 60.0], [32.0, 18.0, 11.0]).plot() plt.show() # By hand, the left panel — P vs onset rate for three level differences: orate = np.logspace(1, 4, 200) for ld in (5.0, 15.0, 30.0): - plt.plot(orate, environmental.predicted_prominence(orate, np.full_like(orate, ld)), + plt.plot(orate, environment.predicted_prominence(orate, np.full_like(orate, ld)), label=f"LD = {ld:g} dB") plt.xscale("log"); plt.legend(); plt.show() ``` @@ -139,7 +139,7 @@ The onset detection can be exercised on a level history directly with ```python import numpy as np -from phonometry import environmental +from phonometry import environment # An LpAF history sampled every 20 ms: quiet, a 30 dB rise over 0.30 s, steady. dt = 0.02 @@ -148,7 +148,7 @@ levels = np.concatenate([ 40.0 + 30.0 * np.arange(1, 16) / 15, # a straight 100 dB/s onset to 70 dB np.full(15, 70.0), ]) -onset = environmental.detect_onsets(levels, dt)[0] +onset = environment.detect_onsets(levels, dt)[0] print(round(onset.onset_rate), round(onset.level_difference)) # 100 30 print(round(onset.prominence, 2)) # 8.95 ``` @@ -156,7 +156,7 @@ print(round(onset.prominence, 2)) # 8.95 From a calibrated time signal (in pascal) the whole chain runs end to end: ```python -result = environmental.impulsive_sound_adjustment(signal, fs) +result = environment.impulsive_sound_adjustment(signal, fs) print(result.category) # e.g. 'highly impulsive' print(round(result.adjustment, 1)) # KI in dB (0.0 to about 9 dB in typical cases) print(round(result.adjusted_laeq, 1)) # LAeq + KI @@ -172,7 +172,7 @@ result.plot() # the LpAF history with the detected onsets (needs matplotlib) ```python import numpy as np import matplotlib.pyplot as plt -from phonometry import environmental +from phonometry import environment # Three hammer strikes over a 55 dB(A) background, 6 s at 48 kHz. fs = 48000 @@ -188,7 +188,7 @@ for onset_time in (1.0, 2.6, 4.2): strike *= 2e-5 * 10 ** (95 / 20) / np.sqrt(np.mean(strike[window] ** 2)) signal += strike -res = environmental.impulsive_sound_adjustment(signal, fs) +res = environment.impulsive_sound_adjustment(signal, fs) print(res.category, round(res.prominence, 2), round(res.adjustment, 2)) # highly impulsive 11.34 11.42 @@ -264,10 +264,10 @@ pass `language="es"` for a Spanish fiche (translated fixed strings and a comma decimal separator), e.g. `res.report("impulse_fiche_es.pdf", language="es")`. ```python -from phonometry import environmental, ReportMetadata +from phonometry import environment, ReportMetadata # The three-impulse pile-driving set (onset rate dB/s, level difference dB). -res = environmental.impulse_prominence([1200.0, 300.0, 60.0], [32.0, 18.0, 11.0]) +res = environment.impulse_prominence([1200.0, 300.0, 60.0], [32.0, 18.0, 11.0]) res.report( "impulse_fiche.pdf", metadata=ReportMetadata( diff --git a/docs/outdoor-propagation.md b/docs/outdoor-propagation.md index 7407f558e..c4f2fdb1a 100644 --- a/docs/outdoor-propagation.md +++ b/docs/outdoor-propagation.md @@ -49,12 +49,12 @@ curves overtake it below ~200 Hz: the relaxation signature.* ```python import matplotlib.pyplot as plt import numpy as np -from phonometry import environmental +from phonometry import environment freqs = np.geomspace(50.0, 10000.0, 400) fig, ax = plt.subplots() for temp, rh in [(20.0, 50.0), (20.0, 10.0), (0.0, 70.0), (30.0, 80.0)]: - ax.loglog(freqs, environmental.air_attenuation(freqs, temp, rh) * 1000.0, + ax.loglog(freqs, environment.air_attenuation(freqs, temp, rh) * 1000.0, label=f"{temp:g} °C, {rh:g} % RH") ax.set_xlabel("Frequency [Hz]") ax.set_ylabel("Attenuation coefficient alpha [dB/km]") @@ -66,21 +66,21 @@ plt.show() ```python import numpy as np -from phonometry import environmental +from phonometry import environment bands = [63, 125, 250, 500, 1000, 2000, 4000, 8000] # octave-band centres [Hz] # Pure-tone attenuation coefficient alpha [dB/m] at 20 °C, 50 % RH, one atmosphere -alpha = environmental.air_attenuation(bands, temperature=20.0, relative_humidity=50.0) +alpha = environment.air_attenuation(bands, temperature=20.0, relative_humidity=50.0) print(np.round(alpha * 1000.0, 2)) # in dB/km, as Table 1 tabulates # [ 0.12 0.44 1.31 2.73 4.66 9.89 29.67 105.29] # Reproduce an ISO 9613-1 Table 1 cell exactly (10 °C, 70 %, 1 kHz) -cell = environmental.air_attenuation(1000.0, 10.0, 70.0, exact_midband=True) * 1000.0 +cell = environment.air_attenuation(1000.0, 10.0, 70.0, exact_midband=True) * 1000.0 print(round(float(cell), 2)) # 3.66 (dB/km, Table 1) # Feed real conditions into the ISO 354 power attenuation coefficient m [1/m] -m = environmental.air_attenuation_m([1000.0, 4000.0], temperature=20.0, relative_humidity=50.0) +m = environment.air_attenuation_m([1000.0, 4000.0], temperature=20.0, relative_humidity=50.0) print(np.round(m, 5)) # [0.00107 0.00683] ``` @@ -123,9 +123,9 @@ Table 1 unit, on a linear ordinate over a logarithmic frequency axis). Passing a that path as `total_attenuation`, the ISO 9613-2 $A_{atm}$ of Eq. (8). ```python -from phonometry import environmental +from phonometry import environment -res = environmental.atmospheric_attenuation( +res = environment.atmospheric_attenuation( [63, 125, 250, 500, 1000, 2000, 4000, 8000], temperature=20.0, relative_humidity=50.0, ) @@ -144,10 +144,10 @@ res.plot() # alpha in dB/km against frequency (needs matplotlib) ```python import matplotlib.pyplot as plt import numpy as np -from phonometry import environmental +from phonometry import environment # One line: the coefficient curve straight from the result. -res = environmental.atmospheric_attenuation( +res = environment.atmospheric_attenuation( np.geomspace(50.0, 10000.0, 400), temperature=20.0, relative_humidity=50.0, ) res.plot() @@ -156,7 +156,7 @@ plt.show() # Or by hand from air_attenuation (dB/m, so scale by 1000 for dB/km): freqs = np.geomspace(50.0, 10000.0, 400) fig, ax = plt.subplots() -ax.semilogx(freqs, environmental.air_attenuation(freqs, 20.0, 50.0) * 1000.0) +ax.semilogx(freqs, environment.air_attenuation(freqs, 20.0, 50.0) * 1000.0) ax.set_xlabel("Frequency [Hz]") ax.set_ylabel("Attenuation coefficient alpha [dB/km]") plt.show() @@ -234,11 +234,11 @@ mid bands. ```python import matplotlib.pyplot as plt import numpy as np -from phonometry import environmental +from phonometry import environment bands = np.array([63.0, 125.0, 250.0, 500.0, 1000.0, 2000.0, 4000.0, 8000.0]) -barrier = environmental.Barrier(source_to_edge=101.0, edge_to_receiver=101.0) -att = environmental.outdoor_propagation_attenuation( +barrier = environment.Barrier(source_to_edge=101.0, edge_to_receiver=101.0) +att = environment.outdoor_propagation_attenuation( 200.0, 1.5, 1.5, bands, ground_source=1.0, ground_middle=1.0, ground_receiver=1.0, barrier=barrier, temperature=15.0, relative_humidity=70.0, @@ -275,15 +275,15 @@ plt.show() ```python import numpy as np -from phonometry import environmental +from phonometry import environment bands = [63, 125, 250, 500, 1000, 2000, 4000, 8000] # octave-band centres [Hz] # A point source and receiver 1.5 m high, 200 m apart over porous ground # (G = 1), screened midway by a barrier that raises the path over its top edge # (dss = dsr ~ 101 m). Geometry feeds the pathlength-difference equations. -barrier = environmental.Barrier(source_to_edge=101.0, edge_to_receiver=101.0) -att = environmental.outdoor_propagation_attenuation( +barrier = environment.Barrier(source_to_edge=101.0, edge_to_receiver=101.0) +att = environment.outdoor_propagation_attenuation( 200.0, source_height=1.5, receiver_height=1.5, frequencies=bands, ground_source=1.0, ground_middle=1.0, ground_receiver=1.0, barrier=barrier, temperature=15.0, relative_humidity=70.0, @@ -296,7 +296,7 @@ att.plot() # the stacked breakdown above (needs matplotli # Predicted receiver level from an octave-band sound power Lw = 95 dB lw = np.full(len(bands), 95.0) -lp = environmental.predicted_receiver_level( +lp = environment.predicted_receiver_level( lw, 200.0, 1.5, 1.5, bands, 1.0, 1.0, 1.0, barrier=barrier, temperature=15.0, relative_humidity=70.0, ) @@ -322,14 +322,14 @@ general per-region method of 7.3.1); combine them by hand when the alternative method is appropriate. ```python -from phonometry import environmental +from phonometry import environment # Alternative ground term (mean path height hm = 2 m, d = 200 m) -print(round(environmental.ground_attenuation_alternative(200.0, 2.0), 2)) # 4.43 dB +print(round(environment.ground_attenuation_alternative(200.0, 2.0), 2)) # 4.43 dB # Its companion solid-angle index (add to Dc when using Eq. (10)) -print(round(environmental.directivity_omega(1.5, 1.5, 200.0), 2)) # 3.01 dB +print(round(environment.directivity_omega(1.5, 1.5, 200.0), 2)) # 3.01 dB # Long-term meteorological correction (C0 = 2 dB) to subtract from LAT(DW) -print(round(environmental.meteorological_correction(200.0, 1.5, 1.5, 2.0), 2)) # 1.7 dB +print(round(environment.meteorological_correction(200.0, 1.5, 1.5, 2.0), 2)) # 1.7 dB ``` ### The image source behind the ground effect diff --git a/docs/reverberation-prediction.md b/docs/reverberation-prediction.md index 0b3d4992e..3ea1c03b5 100644 --- a/docs/reverberation-prediction.md +++ b/docs/reverberation-prediction.md @@ -32,7 +32,7 @@ $c_0 = 343\ \mathrm{m/s}$) and the air-absorption term $4mV$. ```python import matplotlib.pyplot as plt -from phonometry import environmental, room +from phonometry import environment, room # A 10 x 7 x 3.5 m room: hard end walls, lightly treated side walls and a # very absorptive floor/ceiling pair (carpet plus an acoustic ceiling). @@ -40,7 +40,7 @@ bands = [125.0, 250.0, 500.0, 1000.0, 2000.0, 4000.0] alpha_x = [0.06, 0.07, 0.08, 0.09, 0.10, 0.10] alpha_y = [0.12, 0.14, 0.16, 0.18, 0.20, 0.20] alpha_z = [0.30, 0.50, 0.65, 0.78, 0.82, 0.80] -m = environmental.air_attenuation_m(bands, 20.0, 50.0) # air at 20 C / 50 % RH +m = environment.air_attenuation_m(bands, 20.0, 50.0) # air at 20 C / 50 % RH res = room.reverberation_time_models((10.0, 7.0, 3.5), (alpha_x, alpha_y, alpha_z), air_attenuation=m, frequencies=bands) @@ -84,9 +84,9 @@ power-attenuation coefficient $m$ (in neper per metre, from the ISO 9613-1 [atmospheric absorption](outdoor-propagation.md)): ```python -from phonometry import environmental, room +from phonometry import environment, room -m = environmental.air_attenuation_m(2000.0, temperature=20.0, relative_humidity=50.0) +m = environment.air_attenuation_m(2000.0, temperature=20.0, relative_humidity=50.0) surfaces = [(40.0, 0.3), (40.0, 0.3), (24.0, 0.3), (24.0, 0.3), (15.0, 0.3), (15.0, 0.3)] print(round(room.eyring_reverberation_time(120.0, surfaces, air_attenuation=m), 3)) @@ -163,9 +163,9 @@ res.plot() # the five model curves per band (the figure above) ```python import matplotlib.pyplot as plt -from phonometry import environmental, room +from phonometry import environment, room -m = environmental.air_attenuation_m([125.0, 250.0, 500.0, 1000.0, 2000.0, 4000.0], 20.0, 50.0) +m = environment.air_attenuation_m([125.0, 250.0, 500.0, 1000.0, 2000.0, 4000.0], 20.0, 50.0) room.reverberation_time_models( (10.0, 7.0, 3.5), ( diff --git a/docs/spanish-noise-regulation.md b/docs/spanish-noise-regulation.md index d6ada9fae..28fc7e917 100644 --- a/docs/spanish-noise-regulation.md +++ b/docs/spanish-noise-regulation.md @@ -40,20 +40,20 @@ $K_t$ is evaluated band by band and, when several emergent tones are present, the largest of the resulting $K_t$ governs (step d): ```python -from phonometry import environmental +from phonometry import environment freqs = [100, 125, 160, 200, 250, 315, 400, 500, 630, 800, 1000] levels = [58.0, 60.0, 59.0, 61.0, 72.0, 62.0, 60.0, 58.0, 56.0, 54.0, 52.0] -kt = environmental.tonal_correction(levels, freqs) +kt = environment.tonal_correction(levels, freqs) kt.correction # 6 dB kt.governing_frequency # 250.0 Hz kt.differences[4] # Lt = 10.5 dB above the mean of the neighbours -environmental.low_frequency_correction(lceq=76.0, laeq=63.0) # Lf = 13 dB -> Kf = 3 dB -environmental.impulsive_correction(laieq=68.0, laeq=63.0) # Li = 5 dB -> Ki = 0 dB -environmental.corrected_level(63.0, kt=6, kf=3, ki=0) # LKeq = 72.0 dB -environmental.total_correction(kt=6, kf=6, ki=3) # 9.0 dB: the cap +environment.low_frequency_correction(lceq=76.0, laeq=63.0) # Lf = 13 dB -> Kf = 3 dB +environment.impulsive_correction(laieq=68.0, laeq=63.0) # Li = 5 dB -> Ki = 0 dB +environment.corrected_level(63.0, kt=6, kf=3, ki=0) # LKeq = 72.0 dB +environment.total_correction(kt=6, kf=6, ki=3) # 9.0 dB: the cap ``` ## Relationship with the ISO 1996 procedures @@ -100,20 +100,20 @@ own rounding: add 0.5 dB to the result and take the integer part, which is what `round_reported_level()` does. ```python -from phonometry import environmental +from phonometry import environment -environmental.RD1367_PERIOD_CLOCK_LIMITS["day"] # (7, 19) -environmental.RD1367_PERIOD_HOURS["day"] # 12.0 h +environment.RD1367_PERIOD_CLOCK_LIMITS["day"] # (7, 19) +environment.RD1367_PERIOD_HOURS["day"] # 12.0 h -day = [environmental.NoisePhase(2, 0.0, label="cerrada"), - environmental.NoisePhase(6, 50.0, kt=6, kf=3), - environmental.NoisePhase(4, 48.0, kt=3, kf=3)] +day = [environment.NoisePhase(2, 0.0, label="cerrada"), + environment.NoisePhase(6, 50.0, kt=6, kf=3), + environment.NoisePhase(4, 48.0, kt=3, kf=3)] -lkeq_d = environmental.evaluation_period_level(day) # 56.82 dB, unrounded -environmental.round_reported_level(lkeq_d) # 57 dB +lkeq_d = environment.evaluation_period_level(day) # 56.82 dB, unrounded +environment.round_reported_level(lkeq_d) # 57 dB -lk_d = environmental.long_term_corrected_level([57.0, 0.0], weights=[303, 62]) -environmental.round_reported_level(lk_d) # 56 dB (303 days open, 62 closed) +lk_d = environment.long_term_corrected_level([57.0, 0.0], weights=[303, 62]) +environment.round_reported_level(lk_d) # 56 dB (303 days open, 62 closed) ``` ## Acoustic quality objectives and immission limit values @@ -141,16 +141,16 @@ And in areas urbanised after the regulation entered into force (24 October `urbanisation="new"` applies. ```python -from phonometry import environmental - -environmental.outdoor_quality_objectives("a") # 65 / 65 / 55 dB -environmental.outdoor_quality_objectives("a", urbanisation="new") # 60 / 60 / 50 dB -environmental.indoor_quality_objectives("residential", "bedrooms") # 40 / 40 / 30 dB -environmental.infrastructure_limits("a") # 60 / 60 / 50 dB -environmental.max_infrastructure_limit("a") # 85 dB (LAmax) -environmental.activity_limits("a") # 55 / 55 / 45 dB -environmental.adjacent_premises_limits("residential", "bedrooms") # 35 / 35 / 25 dB -environmental.vibration_quality_objective("residential") # 75 dB +from phonometry import environment + +environment.outdoor_quality_objectives("a") # 65 / 65 / 55 dB +environment.outdoor_quality_objectives("a", urbanisation="new") # 60 / 60 / 50 dB +environment.indoor_quality_objectives("residential", "bedrooms") # 40 / 40 / 30 dB +environment.infrastructure_limits("a") # 60 / 60 / 50 dB +environment.max_infrastructure_limit("a") # 85 dB (LAmax) +environment.activity_limits("a") # 55 / 55 / 45 dB +environment.adjacent_premises_limits("residential", "bedrooms") # 35 / 35 / 25 dB +environment.vibration_quality_objective("residential") # 75 dB ``` Meeting an immission limit value is not simply staying below the number in the @@ -183,16 +183,16 @@ each $L_{Keq,Ti}$, 58 dB on the daily $L_{Keq,x}$ and 55 dB on the annual $L_{K,x}$: ```python -from phonometry import environmental +from phonometry import environment -day = [environmental.NoisePhase(2, 0.0, label="cerrada"), - environmental.NoisePhase(6, 50.0, kt=6, kf=3), - environmental.NoisePhase(4, 48.0, kt=3, kf=3)] -evening = [environmental.NoisePhase(2, 48.0, kt=3, kf=3), - environmental.NoisePhase(2, 0.0, label="cerrada")] +day = [environment.NoisePhase(2, 0.0, label="cerrada"), + environment.NoisePhase(6, 50.0, kt=6, kf=3), + environment.NoisePhase(4, 48.0, kt=3, kf=3)] +evening = [environment.NoisePhase(2, 48.0, kt=3, kf=3), + environment.NoisePhase(2, 0.0, label="cerrada")] -limits = environmental.activity_limits("a") # 55 / 55 / 45 dB -verdict = environmental.assess_activity( +limits = environment.activity_limits("a") # 55 / 55 / 45 dB +verdict = environment.assess_activity( {"day": day, "evening": evening}, limits, operating_days=303) verdict.periods[0].reported_level # 57 dB (LKeq,d) @@ -207,17 +207,17 @@ verdict.complies # False: LK,d exceeds 55 dB ```python import matplotlib.pyplot as plt -from phonometry import environmental +from phonometry import environment -day = [environmental.NoisePhase(2, 0.0, label="cerrada"), - environmental.NoisePhase(6, 50.0, kt=6, kf=3), - environmental.NoisePhase(4, 48.0, kt=3, kf=3)] -evening = [environmental.NoisePhase(2, 48.0, kt=3, kf=3), - environmental.NoisePhase(2, 0.0, label="cerrada")] +day = [environment.NoisePhase(2, 0.0, label="cerrada"), + environment.NoisePhase(6, 50.0, kt=6, kf=3), + environment.NoisePhase(4, 48.0, kt=3, kf=3)] +evening = [environment.NoisePhase(2, 48.0, kt=3, kf=3), + environment.NoisePhase(2, 0.0, label="cerrada")] -verdict = environmental.assess_activity( +verdict = environment.assess_activity( {"day": day, "evening": evening}, - environmental.activity_limits("a"), + environment.activity_limits("a"), operating_days=303, ) verdict.plot() @@ -248,15 +248,15 @@ applicable limit values and the boxed verdict. The default language is Spanish, the language of the regulation; `language="en"` translates it. ```python -from phonometry import environmental - -verdict = environmental.assess_activity( - {"day": [environmental.NoisePhase(2, 0.0, label="cerrada"), - environmental.NoisePhase(6, 50.0, kt=6, kf=3), - environmental.NoisePhase(4, 48.0, kt=3, kf=3)], - "evening": [environmental.NoisePhase(2, 48.0, kt=3, kf=3), - environmental.NoisePhase(2, 0.0, label="cerrada")]}, - environmental.activity_limits("a"), +from phonometry import environment + +verdict = environment.assess_activity( + {"day": [environment.NoisePhase(2, 0.0, label="cerrada"), + environment.NoisePhase(6, 50.0, kt=6, kf=3), + environment.NoisePhase(4, 48.0, kt=3, kf=3)], + "evening": [environment.NoisePhase(2, 48.0, kt=3, kf=3), + environment.NoisePhase(2, 0.0, label="cerrada")]}, + environment.activity_limits("a"), operating_days=303, ) @@ -302,7 +302,7 @@ exceeds 9 dB. - [Impulsive-sound prominence](impulse-prominence.md): the ISO/PAS 1996-3 impulsive adjustment, the relative of $K_i$ that is not interchangeable with it. -- API reference: [`environmental.spanish_regulation`](https://jmrplens.github.io/phonometry/reference/api/environment/spanish-regulation/). +- API reference: [`environmental.spanish_regulation`](https://jmrplens.github.io/phonometry/reference/api/environment/spain/). ## References diff --git a/docs/wind-turbine-noise.md b/docs/wind-turbine-noise.md index 5ca38fbaa..c40239ab5 100644 --- a/docs/wind-turbine-noise.md +++ b/docs/wind-turbine-noise.md @@ -26,12 +26,12 @@ pressure doubling. Side view of a horizontal-axis wind turbine with hub height H and rotor diameter D, a microphone lying on a flat ground board downwind at the horizontal distance R0 = H + D/2 from the tower centreline, the slant distance R1 from the rotor centre to the microphone with the board inclination angle phi between 25 and 40 degrees, and a met mast measuring wind speed and direction; a plan-view inset shows the Figure 3 pattern with the reference position downwind and three optional positions at plus and minus 60 degrees and upwind, and the annotations give R1 equals the square root of H squared plus R0 squared and the apparent sound power formula LWA,i = Lp,i minus 6 plus 10 lg(4 pi R1 squared over S0) ```python -from phonometry import environmental +from phonometry import environment # Background-corrected A-weighted one-third-octave band levels L_p,i (dB). band_levels = [55.0, 58.0, 60.0, 57.0, 54.0] -r1 = environmental.slant_distance(hub_height=80.0, rotor_diameter=100.0) -lwa = environmental.apparent_sound_power_level(band_levels, r1) # dB re 1 pW +r1 = environment.slant_distance(hub_height=80.0, rotor_diameter=100.0) +lwa = environment.apparent_sound_power_level(band_levels, r1) # dB re 1 pW ``` ### Why "apparent" @@ -119,20 +119,20 @@ $\Delta L_a > 0$. ```python import numpy as np -from phonometry import environmental +from phonometry import environment df = 2.0 freqs = np.arange(50.0, 400.0 + df, df) levels = 42.0 - 6.0 * np.log10(freqs / 100.0) levels[int(np.argmin(np.abs(freqs - 200.0)))] += 22.0 # blade-passing-style tone -environmental.wind_turbine_tonality(levels, freqs, tone_frequency=200.0).plot() +environment.wind_turbine_tonality(levels, freqs, tone_frequency=200.0).plot() ``` ```python import numpy as np -from phonometry import environmental +from phonometry import environment # A uniformly-spaced narrowband spectrum (2 Hz resolution): a flat 30 dB floor # with a discrete 60 dB tone at 500 Hz. @@ -140,7 +140,7 @@ frequencies = np.arange(440.0, 562.0, 2.0) levels = np.full(frequencies.size, 30.0) levels[np.argmin(np.abs(frequencies - 500.0))] = 60.0 -res = environmental.wind_turbine_tonality(levels, frequencies) +res = environment.wind_turbine_tonality(levels, frequencies) print(res.tone_frequency, res.tonality, res.tonal_audibility, res.is_audible) res.plot() # spectrum + critical band + masking level (needs matplotlib) ``` diff --git a/llms-full.txt b/llms-full.txt index b36b30c7f..41a872c0f 100644 --- a/llms-full.txt +++ b/llms-full.txt @@ -257,7 +257,6 @@ The generated API reference, one page per module. Fetch these only when a specif - [aeroacoustics/anp-fleet](https://jmrplens.github.io/phonometry/reference/api/aeroacoustics/anp-fleet/) - [aeroacoustics/atmospheric-absorption](https://jmrplens.github.io/phonometry/reference/api/aeroacoustics/atmospheric-absorption/) - [aeroacoustics/rotorcraft-noise](https://jmrplens.github.io/phonometry/reference/api/aeroacoustics/rotorcraft-noise/) -- [aeroacoustics/wind-turbine-noise](https://jmrplens.github.io/phonometry/reference/api/aeroacoustics/wind-turbine-noise/) - [broadcast/program-loudness](https://jmrplens.github.io/phonometry/reference/api/broadcast/program-loudness/) - [building/aperture-transmission](https://jmrplens.github.io/phonometry/reference/api/building/aperture-transmission/) - [building/building-prediction](https://jmrplens.github.io/phonometry/reference/api/building/building-prediction/) @@ -286,7 +285,6 @@ The generated API reference, one page per module. Fetch these only when a specif - [electroacoustics/sound-reinforcement](https://jmrplens.github.io/phonometry/reference/api/electroacoustics/sound-reinforcement/) - [electroacoustics/swept-sine](https://jmrplens.github.io/phonometry/reference/api/electroacoustics/swept-sine/) - [environment/air-absorption](https://jmrplens.github.io/phonometry/reference/api/environment/air-absorption/) -- [environment/atmospheric-refraction](https://jmrplens.github.io/phonometry/reference/api/environment/atmospheric-refraction/) - [environment/cnossos-rail](https://jmrplens.github.io/phonometry/reference/api/environment/cnossos-rail/) - [environment/cnossos-road](https://jmrplens.github.io/phonometry/reference/api/environment/cnossos-road/) - [environment/ground-barriers](https://jmrplens.github.io/phonometry/reference/api/environment/ground-barriers/) @@ -295,7 +293,9 @@ The generated API reference, one page per module. Fetch these only when a specif - [environment/measurement](https://jmrplens.github.io/phonometry/reference/api/environment/measurement/) - [environment/outdoor-propagation](https://jmrplens.github.io/phonometry/reference/api/environment/outdoor-propagation/) - [environment/rating](https://jmrplens.github.io/phonometry/reference/api/environment/rating/) -- [environment/spanish-regulation](https://jmrplens.github.io/phonometry/reference/api/environment/spanish-regulation/) +- [environment/refraction](https://jmrplens.github.io/phonometry/reference/api/environment/refraction/) +- [environment/spain](https://jmrplens.github.io/phonometry/reference/api/environment/spain/) +- [environment/wind-turbine](https://jmrplens.github.io/phonometry/reference/api/environment/wind-turbine/) - [filters/compliance](https://jmrplens.github.io/phonometry/reference/api/filters/compliance/) - [filters/core](https://jmrplens.github.io/phonometry/reference/api/filters/core/) - [filters/equalizer](https://jmrplens.github.io/phonometry/reference/api/filters/equalizer/) @@ -2248,7 +2248,7 @@ contour = aircraft.noise_contour(path, powers, distances, sel, lmax, x=gx, y=gy) | `phonometry.materials` | Absorption (ISO 354/11654), impedance tube, airflow resistance, scattering/diffusion, road absorption, dynamic stiffness | | `phonometry.building` | Sound insulation measurement and prediction (EN 12354, ISO 717/16283/10140/15186/10052/10848), structure-borne sound | | `phonometry.vibration` | Three families: `structural` (mobility ISO 7626, plate junctions, radiation, experimental SEA, transfer stiffness ISO 10846), `human` (ISO 2631/5349/8041 exposure and multiple shocks) and `machinery` (fault frequencies and condition monitoring) | -| `phonometry.environmental` | Rating levels, ISO 1996-2 measurement, outdoor propagation (ISO 9613), atmospheric refraction (ray tracing and the parabolic equation), wind-turbine noise, impulsive prominence | +| `phonometry.environment` | Rating levels, ISO 1996-2 measurement, outdoor propagation (ISO 9613), atmospheric refraction (ray tracing and the parabolic equation), wind-turbine noise, impulsive prominence | | `phonometry.aircraft` | EPNL (ICAO Annex 16), SAE ARP 5534 absorption, airport contours (ECAC Doc 29), rotorcraft (ECAC Doc 32) | | `phonometry.underwater` | ISO 18405/17208/18406 levels, propagation and Weston regimes, sound speed, sonar equation and detection range, seabed, ambient and ship-traffic noise, marine-mammal audiograms and regulatory auditory weighting, numerical solvers | | `phonometry.electroacoustics` | Distortion (IEC 60268-3 / AES17), transfer function and coherence, radiating piston | @@ -2259,8 +2259,8 @@ contour = aircraft.noise_contour(path, powers, distances, sel, lmax, x=gx, y=gy) Every name in the table below is also re-exported at the top level, so `from phonometry import ` works for every row. Two generations of module paths are still importable and warn on use: the pre-3.2 flat ones (for example -`phonometry.insulation`), removed in 4.0, and the pre-4.0 ones that the split -of `metrology` moved (for example `phonometry.metrology.levels`, now +`phonometry.building.insulation`), removed in 4.0, and the pre-4.0 ones that the split +of `metrology` moved (for example `phonometry.signals.levels`, now `phonometry.signals.levels`), removed in 5.0. Reading a moved name from the namespace it left (`metrology.leq`) warns and delegates as well. @@ -3474,7 +3474,7 @@ The [ISO 9613-2 method](https://jmrplens.github.io/phonometry/guides/outdoor-pro atmosphere. In reality the sound speed changes with height, because temperature and wind change with height, and this **refracts** sound: rays curve, and over a few hundred metres the received level can swing by tens of decibels. This page -covers `phonometry.environmental.atmospheric_refraction`, the +covers `phonometry.environment.atmospheric_refraction`, the refracting-atmosphere counterpart of the ocean solvers in [`phonometry.underwater.numerical_propagation`](https://jmrplens.github.io/phonometry/guides/underwater-solvers/): a **ray model** and a **parabolic-equation (PE)** solver, both clean-room from @@ -12517,7 +12517,7 @@ Source: https://jmrplens.github.io/phonometry/guides/ground-barriers/ The [ISO 9613-2 general method](https://jmrplens.github.io/phonometry/guides/outdoor-propagation/) folds the ground and barrier terms into tabulated, energy-based corrections. This page covers the -underlying wave acoustics in `phonometry.environmental.ground_barriers`: the +underlying wave acoustics in `phonometry.environment.ground_barriers`: the **spherical-wave reflection coefficient** of a finite-impedance ground (Weyl-Van der Pol) and the **wave-theoretic diffraction** of a screen, both in a homogeneous (non-refracting, non-turbulent) atmosphere. These are the physical @@ -34531,7 +34531,7 @@ exceeds 9 dB. - [Impulsive-sound prominence](https://jmrplens.github.io/phonometry/guides/impulse-prominence/): the ISO/PAS 1996-3 impulsive adjustment, the relative of $K_i$ that is not interchangeable with it. -- API reference: [`environmental.spanish_regulation`](https://jmrplens.github.io/phonometry/reference/api/environment/spanish-regulation/). +- API reference: [`environmental.spanish_regulation`](https://jmrplens.github.io/phonometry/reference/api/environment/spain/). ## References diff --git a/llms.txt b/llms.txt index 24636c4fb..27e4dcd64 100644 --- a/llms.txt +++ b/llms.txt @@ -257,7 +257,6 @@ The generated API reference, one page per module. Fetch these only when a specif - [aeroacoustics/anp-fleet](https://jmrplens.github.io/phonometry/reference/api/aeroacoustics/anp-fleet/) - [aeroacoustics/atmospheric-absorption](https://jmrplens.github.io/phonometry/reference/api/aeroacoustics/atmospheric-absorption/) - [aeroacoustics/rotorcraft-noise](https://jmrplens.github.io/phonometry/reference/api/aeroacoustics/rotorcraft-noise/) -- [aeroacoustics/wind-turbine-noise](https://jmrplens.github.io/phonometry/reference/api/aeroacoustics/wind-turbine-noise/) - [broadcast/program-loudness](https://jmrplens.github.io/phonometry/reference/api/broadcast/program-loudness/) - [building/aperture-transmission](https://jmrplens.github.io/phonometry/reference/api/building/aperture-transmission/) - [building/building-prediction](https://jmrplens.github.io/phonometry/reference/api/building/building-prediction/) @@ -286,7 +285,6 @@ The generated API reference, one page per module. Fetch these only when a specif - [electroacoustics/sound-reinforcement](https://jmrplens.github.io/phonometry/reference/api/electroacoustics/sound-reinforcement/) - [electroacoustics/swept-sine](https://jmrplens.github.io/phonometry/reference/api/electroacoustics/swept-sine/) - [environment/air-absorption](https://jmrplens.github.io/phonometry/reference/api/environment/air-absorption/) -- [environment/atmospheric-refraction](https://jmrplens.github.io/phonometry/reference/api/environment/atmospheric-refraction/) - [environment/cnossos-rail](https://jmrplens.github.io/phonometry/reference/api/environment/cnossos-rail/) - [environment/cnossos-road](https://jmrplens.github.io/phonometry/reference/api/environment/cnossos-road/) - [environment/ground-barriers](https://jmrplens.github.io/phonometry/reference/api/environment/ground-barriers/) @@ -295,7 +293,9 @@ The generated API reference, one page per module. Fetch these only when a specif - [environment/measurement](https://jmrplens.github.io/phonometry/reference/api/environment/measurement/) - [environment/outdoor-propagation](https://jmrplens.github.io/phonometry/reference/api/environment/outdoor-propagation/) - [environment/rating](https://jmrplens.github.io/phonometry/reference/api/environment/rating/) -- [environment/spanish-regulation](https://jmrplens.github.io/phonometry/reference/api/environment/spanish-regulation/) +- [environment/refraction](https://jmrplens.github.io/phonometry/reference/api/environment/refraction/) +- [environment/spain](https://jmrplens.github.io/phonometry/reference/api/environment/spain/) +- [environment/wind-turbine](https://jmrplens.github.io/phonometry/reference/api/environment/wind-turbine/) - [filters/compliance](https://jmrplens.github.io/phonometry/reference/api/filters/compliance/) - [filters/core](https://jmrplens.github.io/phonometry/reference/api/filters/core/) - [filters/equalizer](https://jmrplens.github.io/phonometry/reference/api/filters/equalizer/) diff --git a/scripts/api_taxonomy.py b/scripts/api_taxonomy.py index c30defe9b..fce7ad082 100644 --- a/scripts/api_taxonomy.py +++ b/scripts/api_taxonomy.py @@ -17,11 +17,9 @@ - every module appears in exactly one section; - each section only contains modules from the subpackages declared for it in - ``_SECTION_SUBPACKAGES``. Three sections deliberately span more than one + ``_SECTION_SUBPACKAGES``. Two sections deliberately span more than one parent: ``filters`` adds the package top level (``phonometry`` itself), - ``aeroacoustics`` includes ``environmental.wind_turbine_noise`` because the - section groups by audience (aircraft and wind energy) while the module - lives with the other environmental-rating code, and ``power`` includes + and ``power`` includes ``metrology.intensity_compliance`` (the IEC 61043 class checker sits with the other instrument-conformance code but documents the intensity chain the rest of the section measures with). @@ -218,30 +216,30 @@ class Section: label_en="Environmental acoustics", label_es="Acústica ambiental", modules=( - "phonometry.environmental.outdoor_propagation", - "phonometry.environmental.cnossos_road", - "phonometry.environmental.ground_barriers", - "phonometry.environmental.atmospheric_refraction", - "phonometry.environmental.air_absorption", - "phonometry.environmental.cnossos_rail", - "phonometry.environmental.impulse_prominence", - "phonometry.environmental.impulsive_sound", - "phonometry.environmental.rating", - "phonometry.environmental.measurement", - "phonometry.environmental.spanish_regulation", + "phonometry.environment.propagation.outdoor_propagation", + "phonometry.environment.sources.cnossos_road", + "phonometry.environment.propagation.ground_barriers", + "phonometry.environment.propagation.refraction", + "phonometry.environment.propagation.air_absorption", + "phonometry.environment.sources.cnossos_rail", + "phonometry.environment.assessment.impulse_prominence", + "phonometry.environment.assessment.impulsive_sound", + "phonometry.environment.assessment.rating", + "phonometry.environment.sources.wind_turbine", + "phonometry.environment.assessment.measurement", + "phonometry.environment.assessment.spain", ), ), Section( key="aeroacoustics", - label_en="Aircraft and wind energy", - label_es="Aeronaves y energía eólica", + label_en="Aircraft noise", + label_es="Ruido de aeronaves", modules=( "phonometry.aircraft.aircraft_noise", "phonometry.aircraft.atmospheric_absorption", "phonometry.aircraft.airport_noise", "phonometry.aircraft.anp_fleet", "phonometry.aircraft.rotorcraft_noise", - "phonometry.environmental.wind_turbine_noise", ), ), Section( @@ -340,8 +338,8 @@ class Section: "building": ("building",), "materials": ("materials",), "vibration": ("vibration",), - "environment": ("environmental",), - "aeroacoustics": ("aircraft", "environmental"), + "environment": ("environment",), + "aeroacoustics": ("aircraft",), "underwater": ("underwater",), "power": ("emission", "metrology"), "electroacoustics": ("electroacoustics",), @@ -381,7 +379,7 @@ class Section: "plot_transmission_tube_geometry": "phonometry.materials.impedance_tube", "plot_silencer_geometry": "phonometry.noise_control.silencers", "plot_plenum_geometry": "phonometry.noise_control.hvac", - "plot_barrier_geometry": "phonometry.environmental.ground_barriers", + "plot_barrier_geometry": "phonometry.environment.propagation.ground_barriers", "plot_microphone_positions": "phonometry.emission.sound_power", "plot_aperture_geometry": "phonometry.building.aperture_transmission", "plot_piston_geometry": "phonometry.electroacoustics.piston", diff --git a/scripts/conformance_report.py b/scripts/conformance_report.py index 7f314905e..bc3b94fd7 100644 --- a/scripts/conformance_report.py +++ b/scripts/conformance_report.py @@ -4578,7 +4578,7 @@ def _iso1996_3_ramp_onset() -> Any: """Detected onset of a 30 dB LpAF ramp over 0.30 s (dt = 20 ms).""" import numpy as _np - from phonometry.environmental.impulsive_sound import detect_onsets + from phonometry.environment.assessment.impulsive_sound import detect_onsets dt = 0.02 pre = _np.full(round(0.2 / dt), 40.0) @@ -7350,7 +7350,7 @@ def _chk_cnossos_a_weighting() -> Outcome: "Critical bandwidth about a 500 Hz tone, Hz", ) def _chk_wt_critical_bandwidth() -> Outcome: - from phonometry.environmental.wind_turbine_noise import critical_bandwidth + from phonometry.environment.sources.wind_turbine import critical_bandwidth expected = 25.0 + 75.0 * (1.0 + 1.4 * (500.0 / 1000.0) ** 2) ** 0.69 return numeric(expected, critical_bandwidth(500.0), 1e-6, unit="Hz", places=3) diff --git a/scripts/generate_api_docs.py b/scripts/generate_api_docs.py index b62fd790c..7d6869160 100644 --- a/scripts/generate_api_docs.py +++ b/scripts/generate_api_docs.py @@ -797,7 +797,7 @@ def build_model() -> tuple[list[ModuleDoc], dict[str, str], list[str]]: "phonometry.aircraft.atmospheric_absorption" ), "phonometry.environmental_measurement": ( - "phonometry.environmental.measurement" + "phonometry.environment.assessment.measurement" ), "phonometry.underwater_acoustics": "phonometry.underwater.acoustics", "phonometry.underwater_propagation": "phonometry.underwater.propagation", diff --git a/scripts/generate_graphs.py b/scripts/generate_graphs.py index 399d46b4e..643326888 100644 --- a/scripts/generate_graphs.py +++ b/scripts/generate_graphs.py @@ -6219,7 +6219,7 @@ def generate_wind_turbine_tonality(output_dir: str) -> None: """IEC 61400-11 wind-turbine tonal audibility: narrowband spectrum + masking.""" print("Generating wind_turbine_tonality...") from phonometry import wind_turbine_tonality - from phonometry.environmental.wind_turbine_noise import _critical_band_edges + from phonometry.environment.sources.wind_turbine import _critical_band_edges # A narrowband spectrum: a shaped broadband floor with a blade-passing-style # tone near 200 Hz, at 2 Hz resolution. @@ -10571,7 +10571,9 @@ def generate_impulse_prominence(output_dir: str) -> None: impulse_prominence, predicted_prominence, ) - from phonometry.environmental.impulse_prominence import ADJUSTMENT_THRESHOLD + from phonometry.environment.assessment.impulse_prominence import ( + ADJUSTMENT_THRESHOLD, + ) _fig, (ax_p, ax_k) = plt.subplots(1, 2, figsize=(12.5, 5.4)) diff --git a/scripts/generate_reports.py b/scripts/generate_reports.py index 955b7e561..d38a9d093 100644 --- a/scripts/generate_reports.py +++ b/scripts/generate_reports.py @@ -1142,7 +1142,7 @@ def _impulse_prominence_example() -> tuple[object, ReportMetadata, str]: requirement is a plausible maximum governing prominence the example exceeds, so the optional verdict FAILs. """ - result = ph.environmental.impulse_prominence( + result = ph.environment.assessment.impulse_prominence( [1200.0, 300.0, 60.0], [32.0, 18.0, 11.0] ) metadata = ReportMetadata( diff --git a/site/public/llms/llms-levels-weighting.txt b/site/public/llms/llms-levels-weighting.txt index 6a2a44cab..85c62868d 100644 --- a/site/public/llms/llms-levels-weighting.txt +++ b/site/public/llms/llms-levels-weighting.txt @@ -1962,7 +1962,7 @@ exceeds 9 dB. - [Impulsive-sound prominence](https://jmrplens.github.io/phonometry/guides/impulse-prominence/): the ISO/PAS 1996-3 impulsive adjustment, the relative of $K_i$ that is not interchangeable with it. -- API reference: [`environmental.spanish_regulation`](https://jmrplens.github.io/phonometry/reference/api/environment/spanish-regulation/). +- API reference: [`environmental.spanish_regulation`](https://jmrplens.github.io/phonometry/reference/api/environment/spain/). ## References diff --git a/site/public/llms/llms-outdoor-sound.txt b/site/public/llms/llms-outdoor-sound.txt index 10cd2f6c2..041585dc5 100644 --- a/site/public/llms/llms-outdoor-sound.txt +++ b/site/public/llms/llms-outdoor-sound.txt @@ -456,7 +456,7 @@ Source: https://jmrplens.github.io/phonometry/guides/ground-barriers/ The [ISO 9613-2 general method](https://jmrplens.github.io/phonometry/guides/outdoor-propagation/) folds the ground and barrier terms into tabulated, energy-based corrections. This page covers the -underlying wave acoustics in `phonometry.environmental.ground_barriers`: the +underlying wave acoustics in `phonometry.environment.ground_barriers`: the **spherical-wave reflection coefficient** of a finite-impedance ground (Weyl-Van der Pol) and the **wave-theoretic diffraction** of a screen, both in a homogeneous (non-refracting, non-turbulent) atmosphere. These are the physical diff --git a/site/public/llms/llms-simulation.txt b/site/public/llms/llms-simulation.txt index a9311991d..0716a3246 100644 --- a/site/public/llms/llms-simulation.txt +++ b/site/public/llms/llms-simulation.txt @@ -14,7 +14,7 @@ The [ISO 9613-2 method](https://jmrplens.github.io/phonometry/guides/outdoor-pro atmosphere. In reality the sound speed changes with height, because temperature and wind change with height, and this **refracts** sound: rays curve, and over a few hundred metres the received level can swing by tens of decibels. This page -covers `phonometry.environmental.atmospheric_refraction`, the +covers `phonometry.environment.atmospheric_refraction`, the refracting-atmosphere counterpart of the ocean solvers in [`phonometry.underwater.numerical_propagation`](https://jmrplens.github.io/phonometry/guides/underwater-solvers/): a **ray model** and a **parabolic-equation (PE)** solver, both clean-room from diff --git a/site/src/content/docs/es/guides/atmospheric-refraction.mdx b/site/src/content/docs/es/guides/atmospheric-refraction.mdx index e0fdbd907..fa2b532c4 100644 --- a/site/src/content/docs/es/guides/atmospheric-refraction.mdx +++ b/site/src/content/docs/es/guides/atmospheric-refraction.mdx @@ -28,7 +28,7 @@ ambos una atmósfera **homogénea**. En realidad la velocidad del sonido cambia con la altura, porque la temperatura y el viento cambian con la altura, y esto **refracta** el sonido: los rayos se curvan y, en unos cientos de metros, el nivel recibido puede variar en decenas de decibelios. Esta página cubre -`phonometry.environmental.atmospheric_refraction`, el homólogo para atmósfera +`phonometry.environment.atmospheric_refraction`, el homólogo para atmósfera refractante de los solvers oceánicos de [`phonometry.underwater.numerical_propagation`](/phonometry/es/guides/underwater-solvers/): un **modelo de rayos** y un solver de **ecuación parabólica (PE)**. diff --git a/site/src/content/docs/es/guides/environmental-levels.mdx b/site/src/content/docs/es/guides/environmental-levels.mdx index a3ea24f35..65b9ddf21 100644 --- a/site/src/content/docs/es/guides/environmental-levels.mdx +++ b/site/src/content/docs/es/guides/environmental-levels.mdx @@ -55,12 +55,12 @@ carácter (Tabla A.1: p. ej. +5 dB impulsivo regular, +12 dB altamente impulsivo, +3 a +6 dB tonos prominentes): ```python -from phonometry import environmental +from phonometry import environment -l = environmental.lden(63.2, 58.1, 51.4) # desde LAeq por periodo -r = environmental.composite_rating_level([(63.2, 12, 0.0), # día +l = environment.lden(63.2, 58.1, 51.4) # desde LAeq por periodo +r = environment.composite_rating_level([(63.2, 12, 0.0), # día (58.1, 4, 5.0), # tarde (+5) - (51.4, 8, 10.0)]) # noche (+10) == environmental.lden + (51.4, 8, 10.0)]) # noche (+10) == environment.lden ``` @@ -71,7 +71,7 @@ r = environmental.composite_rating_level([(63.2, 12, 0.0), # día ```python import numpy as np import matplotlib.pyplot as plt -from phonometry import environmental +from phonometry import environment # LAeq horario sintético de una vía urbana (dB), horas 00 a 23 laeq_h = np.array([48, 46, 45, 45, 46, 50, 56, 64, 66, 65, 63, 63, @@ -83,7 +83,7 @@ def period_leq(idx): ld = period_leq(np.arange(7, 19)) # día 07-19 le = period_leq(np.arange(19, 23)) # tarde 19-23 ln_ = period_leq(np.r_[23, np.arange(0, 7)]) # noche 23-07 -l_den = environmental.lden(ld, le, ln_) +l_den = environment.lden(ld, le, ln_) print(f"Lden = {l_den:.1f} dB") # Lden = 64.3 dB fig, ax = plt.subplots() @@ -150,10 +150,10 @@ a $K_t$ (Tabla J.1). ```python import matplotlib.pyplot as plt -from phonometry import environmental +from phonometry import environment # ISO 1996-2:2007 Anexo C.5, Ejemplo 2 (dos tonos cerca de 400 Hz): -res = environmental.assess_tonal_audibility(tone_level=54.1, masking_noise_level=45.2, +res = environment.assess_tonal_audibility(tone_level=54.1, masking_noise_level=45.2, centre_frequency=430.0) print(res.audibility, res.adjustment) # ΔLta ≈ 11.1 dB -> Kt = 6 dB res.plot(language="es") @@ -182,21 +182,21 @@ cuando los niveles se dispersan más de 3 dB, donde el sustituto se infla groseramente. ```python -from phonometry import environmental +from phonometry import environment -tonal = environmental.assess_tonal_audibility(54.1, 45.2, 430.0) # TonalAssessmentResult +tonal = environment.assess_tonal_audibility(54.1, 45.2, 430.0) # TonalAssessmentResult kt = tonal.adjustment # 6 dB tonal.plot() # esta audibilidad sobre la curva de Kt, como en la figura de arriba -corr = environmental.residual_sound_correction(measured_level=58.0, residual_level=50.0) -u = environmental.combined_standard_uncertainty([0.59, 0.3, 2.0, 0.40, 0.38]) # 2,18 dB (G.2) -environmental.expanded_uncertainty(u) # 4,36 dB (k = 2) +corr = environment.residual_sound_correction(measured_level=58.0, residual_level=50.0) +u = environment.combined_standard_uncertainty([0.59, 0.3, 2.0, 0.40, 0.38]) # 2,18 dB (G.2) +environment.expanded_uncertainty(u) # 4,36 dB (k = 2) ``` ## Qué cubre esta guía **Cubierto.** El $L_{den}$, el $L_{dn}$ y el nivel de valoración compuesto de la ISO 1996-1:2016 (apartado 6.5, -`environmental.lden`/`ldn`/`composite_rating_level`); y el ajuste tonal +`environment.lden`/`ldn`/`composite_rating_level`); y el ajuste tonal (anexo C), la corrección de ruido residual (apartado 10.4) y el balance de incertidumbre de medida (apartado 4, anexo F) de la ISO 1996-2:2017. diff --git a/site/src/content/docs/es/guides/ground-barriers.mdx b/site/src/content/docs/es/guides/ground-barriers.mdx index 6afd44f62..739983e26 100644 --- a/site/src/content/docs/es/guides/ground-barriers.mdx +++ b/site/src/content/docs/es/guides/ground-barriers.mdx @@ -51,7 +51,7 @@ import ThemeImage from '../../../../components/ThemeImage.astro'; El [método general ISO 9613-2](/phonometry/es/guides/outdoor-propagation/) pliega los términos de suelo y barrera en correcciones tabuladas basadas en energía. Esta página cubre la acústica ondulatoria subyacente en -`phonometry.environmental.ground_barriers`: el **coeficiente de reflexión de +`phonometry.environment.ground_barriers`: el **coeficiente de reflexión de onda esférica** de un suelo de impedancia finita (Weyl-Van der Pol) y la **difracción por teoría ondulatoria** de una pantalla, ambos en una atmósfera homogénea (sin refracción ni turbulencia). Son el núcleo físico de los modelos diff --git a/site/src/content/docs/es/guides/impulse-prominence.mdx b/site/src/content/docs/es/guides/impulse-prominence.mdx index 186adb741..8a45a23fe 100644 --- a/site/src/content/docs/es/guides/impulse-prominence.mdx +++ b/site/src/content/docs/es/guides/impulse-prominence.mdx @@ -73,10 +73,10 @@ evento cualifica). /> ```python -from phonometry import environmental +from phonometry import environment # Tres impulsos candidatos: (tasa de crecimiento dB/s, diferencia de nivel dB). -result = environmental.impulse_prominence([1200.0, 300.0, 60.0], [32.0, 18.0, 11.0]) +result = environment.impulse_prominence([1200.0, 300.0, 60.0], [32.0, 18.0, 11.0]) print(result.per_impulse.round(2)) # [12.25 9.94 7.42] print(round(result.prominence, 2)) # 12.25 (el impulso determinante) print(round(result.adjustment, 2)) # 13.05 dB @@ -87,10 +87,10 @@ result.plot() # la curva KI(P) de la sección 2 con estos impulsos marcados Un solo impulso se evalúa directamente con `predicted_prominence`: ```python -from phonometry import environmental +from phonometry import environment # P = 3*lg(1000) + 2*lg(30) = 9 + 2.95 = 11.95. -print(round(environmental.predicted_prominence(1000.0, 30.0), 4)) # 11.9542 +print(round(environment.predicted_prominence(1000.0, 30.0), 4)) # 11.9542 ``` ## 2. Ajuste al LAeq (cláusula 8) @@ -103,10 +103,10 @@ K_I = 1{,}8\,(P - 5)\ \text{dB} \quad (P > 5), \qquad K_I = 0 \quad (P \le 5). $$ ```python -from phonometry import environmental +from phonometry import environment -print(float(environmental.impulse_adjustment(10.0))) # 9.0 dB -print(float(environmental.impulse_adjustment(5.0))) # 0.0 dB (en el umbral) +print(float(environment.impulse_adjustment(10.0))) # 9.0 dB +print(float(environment.impulse_adjustment(5.0))) # 0.0 dB (en el umbral) ``` El ajuste se aplica al $L_{Aeq,30\text{min}}$ del evento con la prominencia más alta. El @@ -119,10 +119,10 @@ L_{Ar,T} = 10\,\log_{10}\!\left(\frac{1}{T}\sum_N \Delta t_N\, $$ ```python -from phonometry import environmental +from phonometry import environment # Dos periodos de 30 min: uno impulsivo (KI = 7.6 dB), otro tranquilo. -print(round(environmental.rating_level([72.0, 66.0], [7.6, 0.0], [30.0, 30.0], 60.0), 2)) +print(round(environment.rating_level([72.0, 66.0], [7.6, 0.0], [30.0, 30.0], 60.0), 2)) # 76.78 dB ``` @@ -143,16 +143,16 @@ impulsos marcados. El método es un complemento a la medida de ruido ambiental d ```python import numpy as np import matplotlib.pyplot as plt -from phonometry import environmental +from phonometry import environment # Una línea para la curva de ajuste con los impulsos marcados: -environmental.impulse_prominence([1200.0, 300.0, 60.0], [32.0, 18.0, 11.0]).plot(language="es") +environment.impulse_prominence([1200.0, 300.0, 60.0], [32.0, 18.0, 11.0]).plot(language="es") plt.show() # A mano, el panel izquierdo — P frente a la tasa de crecimiento para tres LD: orate = np.logspace(1, 4, 200) for ld in (5.0, 15.0, 30.0): - plt.plot(orate, environmental.predicted_prominence(orate, np.full_like(orate, ld)), + plt.plot(orate, environment.predicted_prominence(orate, np.full_like(orate, ld)), label=f"LD = {ld:g} dB") plt.xscale("log"); plt.legend(); plt.show() ``` @@ -179,7 +179,7 @@ nivel con `detect_onsets`, cómodo para sonómetros que ya registran $L_{pAF}$: ```python import numpy as np -from phonometry import environmental +from phonometry import environment # Historial LpAF muestreado cada 20 ms: silencio, subida de 30 dB en 0,30 s, estable. dt = 0.02 @@ -188,7 +188,7 @@ levels = np.concatenate([ 40.0 + 30.0 * np.arange(1, 16) / 15, # arranque recto de 100 dB/s hasta 70 dB np.full(15, 70.0), ]) -onset = environmental.detect_onsets(levels, dt)[0] +onset = environment.detect_onsets(levels, dt)[0] print(round(onset.onset_rate), round(onset.level_difference)) # 100 30 print(round(onset.prominence, 2)) # 8.95 ``` @@ -197,7 +197,7 @@ Desde una señal temporal calibrada (en pascales) la cadena completa se ejecuta de principio a fin: ```python -result = environmental.impulsive_sound_adjustment(signal, fs) +result = environment.impulsive_sound_adjustment(signal, fs) print(result.category) # p. ej. 'highly impulsive' print(round(result.adjustment, 1)) # KI en dB (de 0,0 a unos 9 dB en casos típicos) print(round(result.adjusted_laeq, 1)) # LAeq + KI @@ -217,7 +217,7 @@ result.plot() # la historia de LpAF con los inicios detectados (necesita matpl ```python import numpy as np import matplotlib.pyplot as plt -from phonometry import environmental +from phonometry import environment # Tres golpes de martillo sobre un fondo de 55 dB(A), 6 s a 48 kHz. fs = 48000 @@ -233,7 +233,7 @@ for onset_time in (1.0, 2.6, 4.2): strike *= 2e-5 * 10 ** (95 / 20) / np.sqrt(np.mean(strike[window] ** 2)) signal += strike -res = environmental.impulsive_sound_adjustment(signal, fs) +res = environment.impulsive_sound_adjustment(signal, fs) print(res.category, round(res.prominence, 2), round(res.adjustment, 2)) # highly impulsive 11.34 11.42 @@ -313,10 +313,10 @@ genera en inglés por defecto; pase `language="es"` para una ficha en español `res.report("impulse_fiche_es.pdf", language="es")`. ```python -from phonometry import environmental, ReportMetadata +from phonometry import environment, ReportMetadata # El conjunto de tres impulsos de hincado del §1 (tasa de inicio dB/s, LD dB). -res = environmental.impulse_prominence([1200.0, 300.0, 60.0], [32.0, 18.0, 11.0]) +res = environment.impulse_prominence([1200.0, 300.0, 60.0], [32.0, 18.0, 11.0]) res.report( "impulse_fiche.pdf", metadata=ReportMetadata( @@ -364,5 +364,5 @@ sustituye esta medición, no como una función propia. ## Véase también -- Referencia de la API: [`environmental.impulse_prominence`](/phonometry/es/reference/api/environment/impulse-prominence/). +- Referencia de la API: [`environment.impulse_prominence`](/phonometry/es/reference/api/environment/impulse-prominence/). - Referencia de la API: [`environmental.impulsive_sound`](/phonometry/es/reference/api/environment/impulsive-sound/). diff --git a/site/src/content/docs/es/guides/outdoor-propagation.mdx b/site/src/content/docs/es/guides/outdoor-propagation.mdx index 1d89bf758..bb8b44ebf 100644 --- a/site/src/content/docs/es/guides/outdoor-propagation.mdx +++ b/site/src/content/docs/es/guides/outdoor-propagation.mdx @@ -113,12 +113,12 @@ húmedas la superan por debajo de ~200 Hz: la firma de la relajación.* ```python import matplotlib.pyplot as plt import numpy as np -from phonometry import environmental +from phonometry import environment freqs = np.geomspace(50.0, 10000.0, 400) fig, ax = plt.subplots() for temp, rh in [(20.0, 50.0), (20.0, 10.0), (0.0, 70.0), (30.0, 80.0)]: - ax.loglog(freqs, environmental.air_attenuation(freqs, temp, rh) * 1000.0, + ax.loglog(freqs, environment.air_attenuation(freqs, temp, rh) * 1000.0, label=f"{temp:g} °C, {rh:g} % HR") ax.set_xlabel("Frecuencia [Hz]") ax.set_ylabel("Coeficiente de atenuación alpha [dB/km]") @@ -130,21 +130,21 @@ plt.show() ```python import numpy as np -from phonometry import environmental +from phonometry import environment bands = [63, 125, 250, 500, 1000, 2000, 4000, 8000] # centros de banda de octava [Hz] # Coeficiente de atenuación de tono puro alpha [dB/m] a 20 °C, 50 % HR, una atmósfera -alpha = environmental.air_attenuation(bands, temperature=20.0, relative_humidity=50.0) +alpha = environment.air_attenuation(bands, temperature=20.0, relative_humidity=50.0) print(np.round(alpha * 1000.0, 2)) # en dB/km, como tabula la Tabla 1 # [ 0.12 0.44 1.31 2.73 4.66 9.89 29.67 105.29] # Reproduce exactamente una celda de la Tabla 1 de la ISO 9613-1 (10 °C, 70 %, 1 kHz) -cell = environmental.air_attenuation(1000.0, 10.0, 70.0, exact_midband=True) * 1000.0 +cell = environment.air_attenuation(1000.0, 10.0, 70.0, exact_midband=True) * 1000.0 print(round(float(cell), 2)) # 3.66 (dB/km, Tabla 1) # Alimenta condiciones reales al coeficiente de atenuación de potencia m [1/m] de la ISO 354 -m = environmental.air_attenuation_m([1000.0, 4000.0], temperature=20.0, relative_humidity=50.0) +m = environment.air_attenuation_m([1000.0, 4000.0], temperature=20.0, relative_humidity=50.0) print(np.round(m, 5)) # [0.00107 0.00683] ``` @@ -190,9 +190,9 @@ total $A = \alpha\,d$, en decibelios, sobre ese trayecto como `total_attenuation el $A_{atm}$ de la ISO 9613-2 (Ec. (8)). ```python -from phonometry import environmental +from phonometry import environment -res = environmental.atmospheric_attenuation( +res = environment.atmospheric_attenuation( [63, 125, 250, 500, 1000, 2000, 4000, 8000], temperature=20.0, relative_humidity=50.0, ) @@ -212,10 +212,10 @@ hasta alrededor de 1 kHz y luego sube con fuerza, superando 20 dB/km cerca de ```python import matplotlib.pyplot as plt import numpy as np -from phonometry import environmental +from phonometry import environment # Una línea: la curva del coeficiente directamente desde el resultado. -res = environmental.atmospheric_attenuation( +res = environment.atmospheric_attenuation( np.geomspace(50.0, 10000.0, 400), temperature=20.0, relative_humidity=50.0, ) res.plot(language="es") @@ -224,7 +224,7 @@ plt.show() # O a mano desde air_attenuation (dB/m, escala por 1000 para dB/km): freqs = np.geomspace(50.0, 10000.0, 400) fig, ax = plt.subplots() -ax.semilogx(freqs, environmental.air_attenuation(freqs, 20.0, 50.0) * 1000.0) +ax.semilogx(freqs, environment.air_attenuation(freqs, 20.0, 50.0) * 1000.0) ax.set_xlabel("Frecuencia [Hz]") ax.set_ylabel("Coeficiente de atenuación alpha [dB/km]") plt.show() @@ -306,11 +306,11 @@ bandas medias. ```python import matplotlib.pyplot as plt import numpy as np -from phonometry import environmental +from phonometry import environment bands = np.array([63.0, 125.0, 250.0, 500.0, 1000.0, 2000.0, 4000.0, 8000.0]) -barrier = environmental.Barrier(source_to_edge=101.0, edge_to_receiver=101.0) -att = environmental.outdoor_propagation_attenuation( +barrier = environment.Barrier(source_to_edge=101.0, edge_to_receiver=101.0) +att = environment.outdoor_propagation_attenuation( 200.0, 1.5, 1.5, bands, ground_source=1.0, ground_middle=1.0, ground_receiver=1.0, barrier=barrier, temperature=15.0, relative_humidity=70.0, @@ -348,7 +348,7 @@ plt.show() ```python import numpy as np -from phonometry import environmental +from phonometry import environment bands = [63, 125, 250, 500, 1000, 2000, 4000, 8000] # centros de banda de octava [Hz] @@ -356,8 +356,8 @@ bands = [63, 125, 250, 500, 1000, 2000, 4000, 8000] # centros de banda de octa # (G = 1), apantallados a media distancia por una barrera que eleva el recorrido # sobre su borde superior (dss = dsr ~ 101 m). La geometría alimenta las # ecuaciones de diferencia de recorrido. -barrier = environmental.Barrier(source_to_edge=101.0, edge_to_receiver=101.0) -att = environmental.outdoor_propagation_attenuation( +barrier = environment.Barrier(source_to_edge=101.0, edge_to_receiver=101.0) +att = environment.outdoor_propagation_attenuation( 200.0, source_height=1.5, receiver_height=1.5, frequencies=bands, ground_source=1.0, ground_middle=1.0, ground_receiver=1.0, barrier=barrier, temperature=15.0, relative_humidity=70.0, @@ -370,7 +370,7 @@ att.plot() # el desglose apilado de arriba (necesita matp # Nivel predicho en el receptor a partir de una potencia acústica por bandas Lw = 95 dB lw = np.full(len(bands), 95.0) -lp = environmental.predicted_receiver_level( +lp = environment.predicted_receiver_level( lw, 200.0, 1.5, 1.5, bands, 1.0, 1.0, 1.0, barrier=barrier, temperature=15.0, relative_humidity=70.0, ) @@ -397,14 +397,14 @@ general por regiones del 7.3.1); combínalos a mano cuando proceda el método alternativo. ```python -from phonometry import environmental +from phonometry import environment # Término de suelo alternativo (altura media del recorrido hm = 2 m, d = 200 m) -print(round(environmental.ground_attenuation_alternative(200.0, 2.0), 2)) # 4.43 dB +print(round(environment.ground_attenuation_alternative(200.0, 2.0), 2)) # 4.43 dB # Su índice de ángulo sólido asociado (súmalo a Dc al usar la Ec. (10)) -print(round(environmental.directivity_omega(1.5, 1.5, 200.0), 2)) # 3.01 dB +print(round(environment.directivity_omega(1.5, 1.5, 200.0), 2)) # 3.01 dB # Corrección meteorológica a largo plazo (C0 = 2 dB) a restar de LAT(DW) -print(round(environmental.meteorological_correction(200.0, 1.5, 1.5, 2.0), 2)) # 1.7 dB +print(round(environment.meteorological_correction(200.0, 1.5, 1.5, 2.0), 2)) # 1.7 dB ``` ### La fuente imagen que explica el efecto del suelo diff --git a/site/src/content/docs/es/guides/reverberation-prediction.mdx b/site/src/content/docs/es/guides/reverberation-prediction.mdx index 18cace178..1231f4afe 100644 --- a/site/src/content/docs/es/guides/reverberation-prediction.mdx +++ b/site/src/content/docs/es/guides/reverberation-prediction.mdx @@ -130,7 +130,7 @@ $c_0 = 343\ \mathrm{m/s}$) y el término de absorción del aire $4mV$. ```python import matplotlib.pyplot as plt -from phonometry import environmental, room +from phonometry import environment, room # Una sala de 10 x 7 x 3.5 m: paredes frontales duras, paredes laterales con # tratamiento ligero y un par suelo/techo muy absorbente (moqueta y techo acústico). @@ -138,7 +138,7 @@ bands = [125.0, 250.0, 500.0, 1000.0, 2000.0, 4000.0] alpha_x = [0.06, 0.07, 0.08, 0.09, 0.10, 0.10] alpha_y = [0.12, 0.14, 0.16, 0.18, 0.20, 0.20] alpha_z = [0.30, 0.50, 0.65, 0.78, 0.82, 0.80] -m = environmental.air_attenuation_m(bands, 20.0, 50.0) # aire a 20 C / 50 % HR +m = environment.air_attenuation_m(bands, 20.0, 50.0) # aire a 20 C / 50 % HR res = room.reverberation_time_models((10.0, 7.0, 3.5), (alpha_x, alpha_y, alpha_z), air_attenuation=m, frequencies=bands) @@ -183,9 +183,9 @@ atenuación de potencia $m$ (en neper por metro, de la ISO 9613-1): ```python -from phonometry import environmental, room +from phonometry import environment, room -m = environmental.air_attenuation_m(2000.0, temperature=20.0, relative_humidity=50.0) +m = environment.air_attenuation_m(2000.0, temperature=20.0, relative_humidity=50.0) surfaces = [(40.0, 0.3), (40.0, 0.3), (24.0, 0.3), (24.0, 0.3), (15.0, 0.3), (15.0, 0.3)] print(round(room.eyring_reverberation_time(120.0, surfaces, air_attenuation=m), 3)) @@ -268,9 +268,9 @@ res.plot() # las curvas de los cinco modelos por banda (la figura de arriba) ```python import matplotlib.pyplot as plt -from phonometry import environmental, room +from phonometry import environment, room -m = environmental.air_attenuation_m([125.0, 250.0, 500.0, 1000.0, 2000.0, 4000.0], 20.0, 50.0) +m = environment.air_attenuation_m([125.0, 250.0, 500.0, 1000.0, 2000.0, 4000.0], 20.0, 50.0) room.reverberation_time_models( (10.0, 7.0, 3.5), ( diff --git a/site/src/content/docs/es/guides/spanish-noise-regulation.mdx b/site/src/content/docs/es/guides/spanish-noise-regulation.mdx index c07880d00..3dac0e7c3 100644 --- a/site/src/content/docs/es/guides/spanish-noise-regulation.mdx +++ b/site/src/content/docs/es/guides/spanish-noise-regulation.mdx @@ -65,20 +65,20 @@ Para $K_t$ se evalúa banda a banda y, cuando hay varios tonos emergentes, gobierna el mayor de los $K_t$ obtenidos (paso d): ```python -from phonometry import environmental +from phonometry import environment freqs = [100, 125, 160, 200, 250, 315, 400, 500, 630, 800, 1000] levels = [58.0, 60.0, 59.0, 61.0, 72.0, 62.0, 60.0, 58.0, 56.0, 54.0, 52.0] -kt = environmental.tonal_correction(levels, freqs) +kt = environment.tonal_correction(levels, freqs) kt.correction # 6 dB kt.governing_frequency # 250,0 Hz kt.differences[4] # Lt = 10,5 dB sobre la media de las vecinas -environmental.low_frequency_correction(lceq=76.0, laeq=63.0) # Lf = 13 dB -> Kf = 3 dB -environmental.impulsive_correction(laieq=68.0, laeq=63.0) # Li = 5 dB -> Ki = 0 dB -environmental.corrected_level(63.0, kt=6, kf=3, ki=0) # LKeq = 72,0 dB -environmental.total_correction(kt=6, kf=6, ki=3) # 9,0 dB: el tope +environment.low_frequency_correction(lceq=76.0, laeq=63.0) # Lf = 13 dB -> Kf = 3 dB +environment.impulsive_correction(laieq=68.0, laeq=63.0) # Li = 5 dB -> Ki = 0 dB +environment.corrected_level(63.0, kt=6, kf=3, ki=0) # LKeq = 72,0 dB +environment.total_correction(kt=6, kf=6, ki=3) # 9,0 dB: el tope ``` ## Relación con los procedimientos de ISO 1996 @@ -125,20 +125,20 @@ fija además su propio redondeo: se incrementa el resultado en 0,5 dB y se toma la parte entera, que es lo que hace `round_reported_level()`. ```python -from phonometry import environmental +from phonometry import environment -environmental.RD1367_PERIOD_CLOCK_LIMITS["day"] # (7, 19) -environmental.RD1367_PERIOD_HOURS["day"] # 12,0 h +environment.RD1367_PERIOD_CLOCK_LIMITS["day"] # (7, 19) +environment.RD1367_PERIOD_HOURS["day"] # 12,0 h -day = [environmental.NoisePhase(2, 0.0, label="cerrada"), - environmental.NoisePhase(6, 50.0, kt=6, kf=3), - environmental.NoisePhase(4, 48.0, kt=3, kf=3)] +day = [environment.NoisePhase(2, 0.0, label="cerrada"), + environment.NoisePhase(6, 50.0, kt=6, kf=3), + environment.NoisePhase(4, 48.0, kt=3, kf=3)] -lkeq_d = environmental.evaluation_period_level(day) # 56,82 dB sin redondear -environmental.round_reported_level(lkeq_d) # 57 dB +lkeq_d = environment.evaluation_period_level(day) # 56,82 dB sin redondear +environment.round_reported_level(lkeq_d) # 57 dB -lk_d = environmental.long_term_corrected_level([57.0, 0.0], weights=[303, 62]) -environmental.round_reported_level(lk_d) # 56 dB (303 días abierta, 62 cerrada) +lk_d = environment.long_term_corrected_level([57.0, 0.0], weights=[303, 62]) +environment.round_reported_level(lk_d) # 56 dB (303 días abierta, 62 cerrada) ``` ## Objetivos de calidad acústica y valores límite de inmisión @@ -166,16 +166,16 @@ entrada en vigor del reglamento (24 de octubre de 2007) el objetivo es 5 dB más estricto (artículo 14.2), que es lo que aplica `urbanisation="new"`. ```python -from phonometry import environmental - -environmental.outdoor_quality_objectives("a") # 65 / 65 / 55 dB -environmental.outdoor_quality_objectives("a", urbanisation="new") # 60 / 60 / 50 dB -environmental.indoor_quality_objectives("residential", "bedrooms") # 40 / 40 / 30 dB -environmental.infrastructure_limits("a") # 60 / 60 / 50 dB -environmental.max_infrastructure_limit("a") # 85 dB (LAmax) -environmental.activity_limits("a") # 55 / 55 / 45 dB -environmental.adjacent_premises_limits("residential", "bedrooms") # 35 / 35 / 25 dB -environmental.vibration_quality_objective("residential") # 75 dB +from phonometry import environment + +environment.outdoor_quality_objectives("a") # 65 / 65 / 55 dB +environment.outdoor_quality_objectives("a", urbanisation="new") # 60 / 60 / 50 dB +environment.indoor_quality_objectives("residential", "bedrooms") # 40 / 40 / 30 dB +environment.infrastructure_limits("a") # 60 / 60 / 50 dB +environment.max_infrastructure_limit("a") # 85 dB (LAmax) +environment.activity_limits("a") # 55 / 55 / 45 dB +environment.adjacent_premises_limits("residential", "bedrooms") # 35 / 35 / 25 dB +environment.vibration_quality_objective("residential") # 75 dB ``` Cumplir un valor límite de inmisión no es simplemente quedar por debajo del @@ -209,16 +209,16 @@ $L_{Keq,Ti}$, 58 dB sobre el $L_{Keq,x}$ diario y 55 dB sobre el $L_{K,x}$ anual: ```python -from phonometry import environmental +from phonometry import environment -day = [environmental.NoisePhase(2, 0.0, label="cerrada"), - environmental.NoisePhase(6, 50.0, kt=6, kf=3), - environmental.NoisePhase(4, 48.0, kt=3, kf=3)] -evening = [environmental.NoisePhase(2, 48.0, kt=3, kf=3), - environmental.NoisePhase(2, 0.0, label="cerrada")] +day = [environment.NoisePhase(2, 0.0, label="cerrada"), + environment.NoisePhase(6, 50.0, kt=6, kf=3), + environment.NoisePhase(4, 48.0, kt=3, kf=3)] +evening = [environment.NoisePhase(2, 48.0, kt=3, kf=3), + environment.NoisePhase(2, 0.0, label="cerrada")] -limits = environmental.activity_limits("a") # 55 / 55 / 45 dB -verdict = environmental.assess_activity( +limits = environment.activity_limits("a") # 55 / 55 / 45 dB +verdict = environment.assess_activity( {"day": day, "evening": evening}, limits, operating_days=303) verdict.periods[0].reported_level # 57 dB (LKeq,d) @@ -233,17 +233,17 @@ verdict.complies # False: LK,d supera los 55 dB ```python import matplotlib.pyplot as plt -from phonometry import environmental +from phonometry import environment -day = [environmental.NoisePhase(2, 0.0, label="cerrada"), - environmental.NoisePhase(6, 50.0, kt=6, kf=3), - environmental.NoisePhase(4, 48.0, kt=3, kf=3)] -evening = [environmental.NoisePhase(2, 48.0, kt=3, kf=3), - environmental.NoisePhase(2, 0.0, label="cerrada")] +day = [environment.NoisePhase(2, 0.0, label="cerrada"), + environment.NoisePhase(6, 50.0, kt=6, kf=3), + environment.NoisePhase(4, 48.0, kt=3, kf=3)] +evening = [environment.NoisePhase(2, 48.0, kt=3, kf=3), + environment.NoisePhase(2, 0.0, label="cerrada")] -verdict = environmental.assess_activity( +verdict = environment.assess_activity( {"day": day, "evening": evening}, - environmental.activity_limits("a"), + environment.activity_limits("a"), operating_days=303, ) verdict.plot(language="es") @@ -275,15 +275,15 @@ idioma por defecto es el español, el del reglamento; `language="en"` la traduce. ```python -from phonometry import environmental - -verdict = environmental.assess_activity( - {"day": [environmental.NoisePhase(2, 0.0, label="cerrada"), - environmental.NoisePhase(6, 50.0, kt=6, kf=3), - environmental.NoisePhase(4, 48.0, kt=3, kf=3)], - "evening": [environmental.NoisePhase(2, 48.0, kt=3, kf=3), - environmental.NoisePhase(2, 0.0, label="cerrada")]}, - environmental.activity_limits("a"), +from phonometry import environment + +verdict = environment.assess_activity( + {"day": [environment.NoisePhase(2, 0.0, label="cerrada"), + environment.NoisePhase(6, 50.0, kt=6, kf=3), + environment.NoisePhase(4, 48.0, kt=3, kf=3)], + "evening": [environment.NoisePhase(2, 48.0, kt=3, kf=3), + environment.NoisePhase(2, 0.0, label="cerrada")]}, + environment.activity_limits("a"), operating_days=303, ) @@ -314,7 +314,7 @@ donde termina el sonómetro. - [Niveles integrados y estadísticos](/phonometry/es/guides/levels/): los niveles $L_{Aeq}$, $L_{Ceq}$ y $L_{AIeq}$ por fase que alimentan las tres correcciones. - [Código Técnico de la Edificación (CTE DB-HR)](/phonometry/es/guides/spanish-building-code/): la otra mitad de la normativa española, que comparte con esta el índice de ruido día $L_d$ del emplazamiento. - [Prominencia de sonidos impulsivos](/phonometry/es/guides/impulse-prominence/): el ajuste por impulsividad de ISO/PAS 1996-3, el pariente del $K_i$ que no es intercambiable con él. -- Referencia de la API: [`environmental.spanish_regulation`](/phonometry/es/reference/api/environment/spanish-regulation/). +- Referencia de la API: [`environmental.spanish_regulation`](/phonometry/es/reference/api/environment/spain/). ## Respuestas rápidas diff --git a/site/src/content/docs/es/guides/wind-turbine-noise.mdx b/site/src/content/docs/es/guides/wind-turbine-noise.mdx index d8b70d3af..b8719441c 100644 --- a/site/src/content/docs/es/guides/wind-turbine-noise.mdx +++ b/site/src/content/docs/es/guides/wind-turbine-noise.mdx @@ -46,12 +46,12 @@ dan cuenta de la duplicación de presión en la placa del suelo. ```python -from phonometry import environmental +from phonometry import environment # Niveles de banda de tercio de octava ponderados A y corregidos de fondo L_p,i (dB). band_levels = [55.0, 58.0, 60.0, 57.0, 54.0] -r1 = environmental.slant_distance(hub_height=80.0, rotor_diameter=100.0) -lwa = environmental.apparent_sound_power_level(band_levels, r1) # dB re 1 pW +r1 = environment.slant_distance(hub_height=80.0, rotor_diameter=100.0) +lwa = environment.apparent_sound_power_level(band_levels, r1) # dB re 1 pW ``` ### Por qué "aparente" @@ -133,20 +133,20 @@ $\Delta L_a \ge -3\ \text{dB}$ y audible cuando $\Delta L_a > 0$. ```python import numpy as np -from phonometry import environmental +from phonometry import environment df = 2.0 freqs = np.arange(50.0, 400.0 + df, df) levels = 42.0 - 6.0 * np.log10(freqs / 100.0) levels[int(np.argmin(np.abs(freqs - 200.0)))] += 22.0 # tono estilo paso de pala -environmental.wind_turbine_tonality(levels, freqs, tone_frequency=200.0).plot(language="es") +environment.wind_turbine_tonality(levels, freqs, tone_frequency=200.0).plot(language="es") ``` ```python import numpy as np -from phonometry import environmental +from phonometry import environment # Un espectro de banda estrecha uniformemente espaciado (resolución de 2 Hz): # un suelo plano de 30 dB con un tono discreto de 60 dB en 500 Hz. @@ -154,7 +154,7 @@ frequencies = np.arange(440.0, 562.0, 2.0) levels = np.full(frequencies.size, 30.0) levels[np.argmin(np.abs(frequencies - 500.0))] = 60.0 -res = environmental.wind_turbine_tonality(levels, frequencies) +res = environment.wind_turbine_tonality(levels, frequencies) print(res.tone_frequency, res.tonality, res.tonal_audibility, res.is_audible) res.plot() # espectro + banda crítica + nivel de enmascaramiento (requiere matplotlib) ``` @@ -177,4 +177,4 @@ $K_T$, pasa la audibilidad media al `tonal_adjustment` de la ISO 1996-2. ## Véase también -- Referencia de la API: [`environmental.wind_turbine_noise`](/phonometry/es/reference/api/aeroacoustics/wind-turbine-noise/). +- Referencia de la API: [`environmental.wind_turbine_noise`](/phonometry/es/reference/api/environment/wind-turbine/). diff --git a/site/src/content/docs/guides/atmospheric-refraction.mdx b/site/src/content/docs/guides/atmospheric-refraction.mdx index f649ad131..f5a12dd6d 100644 --- a/site/src/content/docs/guides/atmospheric-refraction.mdx +++ b/site/src/content/docs/guides/atmospheric-refraction.mdx @@ -28,7 +28,7 @@ a **homogeneous** atmosphere. In reality the sound speed changes with height, because temperature and wind change with height, and this **refracts** sound: rays curve, and over a few hundred metres the received level can swing by tens of decibels. This page covers -`phonometry.environmental.atmospheric_refraction`, the refracting-atmosphere +`phonometry.environment.atmospheric_refraction`, the refracting-atmosphere counterpart of the ocean solvers in [`phonometry.underwater.numerical_propagation`](/phonometry/guides/underwater-solvers/): a **ray model** and a **parabolic-equation (PE)** solver. diff --git a/site/src/content/docs/guides/environmental-levels.mdx b/site/src/content/docs/guides/environmental-levels.mdx index 112c8d705..46b670166 100644 --- a/site/src/content/docs/guides/environmental-levels.mdx +++ b/site/src/content/docs/guides/environmental-levels.mdx @@ -55,12 +55,12 @@ and `composite_rating_level()` the general whole-day composite of clause 6.5 +6 dB prominent tones): ```python -from phonometry import environmental +from phonometry import environment -l = environmental.lden(63.2, 58.1, 51.4) # from LAeq per period -r = environmental.composite_rating_level([(63.2, 12, 0.0), # day +l = environment.lden(63.2, 58.1, 51.4) # from LAeq per period +r = environment.composite_rating_level([(63.2, 12, 0.0), # day (58.1, 4, 5.0), # evening (+5) - (51.4, 8, 10.0)]) # night (+10) == environmental.lden + (51.4, 8, 10.0)]) # night (+10) == environment.lden ``` @@ -71,7 +71,7 @@ r = environmental.composite_rating_level([(63.2, 12, 0.0), # day ```python import numpy as np import matplotlib.pyplot as plt -from phonometry import environmental +from phonometry import environment # Synthetic hourly LAeq of an urban road (dB), hours 00 to 23 laeq_h = np.array([48, 46, 45, 45, 46, 50, 56, 64, 66, 65, 63, 63, @@ -83,7 +83,7 @@ def period_leq(idx): ld = period_leq(np.arange(7, 19)) # day 07-19 le = period_leq(np.arange(19, 23)) # evening 19-23 ln_ = period_leq(np.r_[23, np.arange(0, 7)]) # night 23-07 -l_den = environmental.lden(ld, le, ln_) +l_den = environment.lden(ld, le, ln_) print(f"Lden = {l_den:.1f} dB") # Lden = 64.3 dB fig, ax = plt.subplots() @@ -149,10 +149,10 @@ $K_t$ (Table J.1). ```python import matplotlib.pyplot as plt -from phonometry import environmental +from phonometry import environment # ISO 1996-2:2007 Annex C.5, Example 2 (two tones near 400 Hz): -res = environmental.assess_tonal_audibility(tone_level=54.1, masking_noise_level=45.2, +res = environment.assess_tonal_audibility(tone_level=54.1, masking_noise_level=45.2, centre_frequency=430.0) print(res.audibility, res.adjustment) # ΔLta ≈ 11.1 dB -> Kt = 6 dB res.plot() @@ -179,20 +179,20 @@ reported alongside as `approximate_uncertainty` and a warning when the levels spread beyond 3 dB, where the substitute grossly inflates. ```python -from phonometry import environmental +from phonometry import environment -tonal = environmental.assess_tonal_audibility(54.1, 45.2, 430.0) # TonalAssessmentResult +tonal = environment.assess_tonal_audibility(54.1, 45.2, 430.0) # TonalAssessmentResult kt = tonal.adjustment # 6 dB tonal.plot() # this audibility on the Kt curve, as in the figure above -corr = environmental.residual_sound_correction(measured_level=58.0, residual_level=50.0) -u = environmental.combined_standard_uncertainty([0.59, 0.3, 2.0, 0.40, 0.38]) # 2.18 dB (G.2) -environmental.expanded_uncertainty(u) # 4.36 dB (k = 2) +corr = environment.residual_sound_correction(measured_level=58.0, residual_level=50.0) +u = environment.combined_standard_uncertainty([0.59, 0.3, 2.0, 0.40, 0.38]) # 2.18 dB (G.2) +environment.expanded_uncertainty(u) # 4.36 dB (k = 2) ``` ## What this guide covers **Covered.** ISO 1996-1:2016's $L_{den}$, $L_{dn}$ and composite rating level (clause -6.5, `environmental.lden`/`ldn`/`composite_rating_level`); and ISO +6.5, `environment.lden`/`ldn`/`composite_rating_level`); and ISO 1996-2:2017's tonal adjustment (Annex C), residual-noise correction (Clause 10.4) and measurement uncertainty budget (Clause 4, Annex F). diff --git a/site/src/content/docs/guides/ground-barriers.mdx b/site/src/content/docs/guides/ground-barriers.mdx index 3d877e04f..243892d86 100644 --- a/site/src/content/docs/guides/ground-barriers.mdx +++ b/site/src/content/docs/guides/ground-barriers.mdx @@ -51,7 +51,7 @@ import ThemeImage from '../../../components/ThemeImage.astro'; The [ISO 9613-2 general method](/phonometry/guides/outdoor-propagation/) folds the ground and barrier terms into tabulated, energy-based corrections. This page covers the underlying wave acoustics in -`phonometry.environmental.ground_barriers`: the **spherical-wave reflection +`phonometry.environment.ground_barriers`: the **spherical-wave reflection coefficient** of a finite-impedance ground (Weyl-Van der Pol) and the **wave-theoretic diffraction** of a screen, both in a homogeneous (non-refracting, non-turbulent) atmosphere. These are the physical core of the diff --git a/site/src/content/docs/guides/impulse-prominence.mdx b/site/src/content/docs/guides/impulse-prominence.mdx index c9a93a2d1..5d84b6d95 100644 --- a/site/src/content/docs/guides/impulse-prominence.mdx +++ b/site/src/content/docs/guides/impulse-prominence.mdx @@ -72,10 +72,10 @@ $K_I$ (the adjustment is 0 dB when no event qualifies). /> ```python -from phonometry import environmental +from phonometry import environment # Three candidate impulses: (onset rate dB/s, level difference dB). -result = environmental.impulse_prominence([1200.0, 300.0, 60.0], [32.0, 18.0, 11.0]) +result = environment.impulse_prominence([1200.0, 300.0, 60.0], [32.0, 18.0, 11.0]) print(result.per_impulse.round(2)) # [12.25 9.94 7.42] print(round(result.prominence, 2)) # 12.25 (the governing impulse) print(round(result.adjustment, 2)) # 13.05 dB @@ -86,10 +86,10 @@ result.plot() # the KI(P) curve of section 2 with these impulses marked A single impulse can be evaluated directly with `predicted_prominence`: ```python -from phonometry import environmental +from phonometry import environment # P = 3*lg(1000) + 2*lg(30) = 9 + 2.95 = 11.95. -print(round(environmental.predicted_prominence(1000.0, 30.0), 4)) # 11.9542 +print(round(environment.predicted_prominence(1000.0, 30.0), 4)) # 11.9542 ``` ## 2. Adjustment to LAeq (clause 8) @@ -102,10 +102,10 @@ K_I = 1.8\,(P - 5)\ \text{dB} \quad (P > 5), \qquad K_I = 0 \quad (P \le 5). $$ ```python -from phonometry import environmental +from phonometry import environment -print(float(environmental.impulse_adjustment(10.0))) # 9.0 dB -print(float(environmental.impulse_adjustment(5.0))) # 0.0 dB (at the threshold) +print(float(environment.impulse_adjustment(10.0))) # 9.0 dB +print(float(environment.impulse_adjustment(5.0))) # 0.0 dB (at the threshold) ``` The adjustment is applied to $L_{Aeq,30\text{min}}$ from the single event with the @@ -118,10 +118,10 @@ L_{Ar,T} = 10\,\log_{10}\left(\frac{1}{T}\sum_N \Delta t_N\, $$ ```python -from phonometry import environmental +from phonometry import environment # Two 30-min periods: one impulsive (KI = 7.6 dB), one quiet. -print(round(environmental.rating_level([72.0, 66.0], [7.6, 0.0], [30.0, 30.0], 60.0), 2)) +print(round(environment.rating_level([72.0, 66.0], [7.6, 0.0], [30.0, 30.0], 60.0), 2)) # 76.78 dB ``` @@ -142,16 +142,16 @@ measurement of [ISO 1996-2](/phonometry/guides/environmental-levels/). ```python import numpy as np import matplotlib.pyplot as plt -from phonometry import environmental +from phonometry import environment # One line for the adjustment curve with the impulses marked: -environmental.impulse_prominence([1200.0, 300.0, 60.0], [32.0, 18.0, 11.0]).plot() +environment.impulse_prominence([1200.0, 300.0, 60.0], [32.0, 18.0, 11.0]).plot() plt.show() # By hand, the left panel — P vs onset rate for three level differences: orate = np.logspace(1, 4, 200) for ld in (5.0, 15.0, 30.0): - plt.plot(orate, environmental.predicted_prominence(orate, np.full_like(orate, ld)), + plt.plot(orate, environment.predicted_prominence(orate, np.full_like(orate, ld)), label=f"LD = {ld:g} dB") plt.xscale("log"); plt.legend(); plt.show() ``` @@ -179,7 +179,7 @@ The onset detection can be exercised on a level history directly with ```python import numpy as np -from phonometry import environmental +from phonometry import environment # An LpAF history sampled every 20 ms: quiet, a 30 dB rise over 0.30 s, steady. dt = 0.02 @@ -188,7 +188,7 @@ levels = np.concatenate([ 40.0 + 30.0 * np.arange(1, 16) / 15, # a straight 100 dB/s onset to 70 dB np.full(15, 70.0), ]) -onset = environmental.detect_onsets(levels, dt)[0] +onset = environment.detect_onsets(levels, dt)[0] print(round(onset.onset_rate), round(onset.level_difference)) # 100 30 print(round(onset.prominence, 2)) # 8.95 ``` @@ -196,7 +196,7 @@ print(round(onset.prominence, 2)) # 8.95 From a calibrated time signal (in pascal) the whole chain runs end to end: ```python -result = environmental.impulsive_sound_adjustment(signal, fs) +result = environment.impulsive_sound_adjustment(signal, fs) print(result.category) # e.g. 'highly impulsive' print(round(result.adjustment, 1)) # KI in dB (0.0 to about 9 dB in typical cases) print(round(result.adjusted_laeq, 1)) # LAeq + KI @@ -216,7 +216,7 @@ result.plot() # the LpAF history with the detected onsets (needs matplotlib) ```python import numpy as np import matplotlib.pyplot as plt -from phonometry import environmental +from phonometry import environment # Three hammer strikes over a 55 dB(A) background, 6 s at 48 kHz. fs = 48000 @@ -232,7 +232,7 @@ for onset_time in (1.0, 2.6, 4.2): strike *= 2e-5 * 10 ** (95 / 20) / np.sqrt(np.mean(strike[window] ** 2)) signal += strike -res = environmental.impulsive_sound_adjustment(signal, fs) +res = environment.impulsive_sound_adjustment(signal, fs) print(res.category, round(res.prominence, 2), round(res.adjustment, 2)) # highly impulsive 11.34 11.42 @@ -308,10 +308,10 @@ pass `language="es"` for a Spanish fiche (translated fixed strings and a comma decimal separator), e.g. `res.report("impulse_fiche_es.pdf", language="es")`. ```python -from phonometry import environmental, ReportMetadata +from phonometry import environment, ReportMetadata # The three-impulse pile-driving set from §1 (onset rate dB/s, level diff. dB). -res = environmental.impulse_prominence([1200.0, 300.0, 60.0], [32.0, 18.0, 11.0]) +res = environment.impulse_prominence([1200.0, 300.0, 60.0], [32.0, 18.0, 11.0]) res.report( "impulse_fiche.pdf", metadata=ReportMetadata( @@ -355,5 +355,5 @@ measurement replaces, not as a function of their own. ## See also -- API reference: [`environmental.impulse_prominence`](/phonometry/reference/api/environment/impulse-prominence/). +- API reference: [`environment.impulse_prominence`](/phonometry/reference/api/environment/impulse-prominence/). - API reference: [`environmental.impulsive_sound`](/phonometry/reference/api/environment/impulsive-sound/). diff --git a/site/src/content/docs/guides/outdoor-propagation.mdx b/site/src/content/docs/guides/outdoor-propagation.mdx index 3642b8840..2b21aae49 100644 --- a/site/src/content/docs/guides/outdoor-propagation.mdx +++ b/site/src/content/docs/guides/outdoor-propagation.mdx @@ -110,12 +110,12 @@ curves overtake it below ~200 Hz: the relaxation signature.* ```python import matplotlib.pyplot as plt import numpy as np -from phonometry import environmental +from phonometry import environment freqs = np.geomspace(50.0, 10000.0, 400) fig, ax = plt.subplots() for temp, rh in [(20.0, 50.0), (20.0, 10.0), (0.0, 70.0), (30.0, 80.0)]: - ax.loglog(freqs, environmental.air_attenuation(freqs, temp, rh) * 1000.0, + ax.loglog(freqs, environment.air_attenuation(freqs, temp, rh) * 1000.0, label=f"{temp:g} °C, {rh:g} % RH") ax.set_xlabel("Frequency [Hz]") ax.set_ylabel("Attenuation coefficient alpha [dB/km]") @@ -127,21 +127,21 @@ plt.show() ```python import numpy as np -from phonometry import environmental +from phonometry import environment bands = [63, 125, 250, 500, 1000, 2000, 4000, 8000] # octave-band centres [Hz] # Pure-tone attenuation coefficient alpha [dB/m] at 20 °C, 50 % RH, one atmosphere -alpha = environmental.air_attenuation(bands, temperature=20.0, relative_humidity=50.0) +alpha = environment.air_attenuation(bands, temperature=20.0, relative_humidity=50.0) print(np.round(alpha * 1000.0, 2)) # in dB/km, as Table 1 tabulates # [ 0.12 0.44 1.31 2.73 4.66 9.89 29.67 105.29] # Reproduce an ISO 9613-1 Table 1 cell exactly (10 °C, 70 %, 1 kHz) -cell = environmental.air_attenuation(1000.0, 10.0, 70.0, exact_midband=True) * 1000.0 +cell = environment.air_attenuation(1000.0, 10.0, 70.0, exact_midband=True) * 1000.0 print(round(float(cell), 2)) # 3.66 (dB/km, Table 1) # Feed real conditions into the ISO 354 power attenuation coefficient m [1/m] -m = environmental.air_attenuation_m([1000.0, 4000.0], temperature=20.0, relative_humidity=50.0) +m = environment.air_attenuation_m([1000.0, 4000.0], temperature=20.0, relative_humidity=50.0) print(np.round(m, 5)) # [0.00107 0.00683] ``` @@ -184,9 +184,9 @@ Table 1 unit, on a linear ordinate over a logarithmic frequency axis). Passing a that path as `total_attenuation`, the ISO 9613-2 $A_{atm}$ of Eq. (8). ```python -from phonometry import environmental +from phonometry import environment -res = environmental.atmospheric_attenuation( +res = environment.atmospheric_attenuation( [63, 125, 250, 500, 1000, 2000, 4000, 8000], temperature=20.0, relative_humidity=50.0, ) @@ -205,10 +205,10 @@ res.plot() # alpha in dB/km against frequency (needs matplotlib) ```python import matplotlib.pyplot as plt import numpy as np -from phonometry import environmental +from phonometry import environment # One line: the coefficient curve straight from the result. -res = environmental.atmospheric_attenuation( +res = environment.atmospheric_attenuation( np.geomspace(50.0, 10000.0, 400), temperature=20.0, relative_humidity=50.0, ) res.plot() @@ -217,7 +217,7 @@ plt.show() # Or by hand from air_attenuation (dB/m, so scale by 1000 for dB/km): freqs = np.geomspace(50.0, 10000.0, 400) fig, ax = plt.subplots() -ax.semilogx(freqs, environmental.air_attenuation(freqs, 20.0, 50.0) * 1000.0) +ax.semilogx(freqs, environment.air_attenuation(freqs, 20.0, 50.0) * 1000.0) ax.set_xlabel("Frequency [Hz]") ax.set_ylabel("Attenuation coefficient alpha [dB/km]") plt.show() @@ -295,11 +295,11 @@ mid bands. ```python import matplotlib.pyplot as plt import numpy as np -from phonometry import environmental +from phonometry import environment bands = np.array([63.0, 125.0, 250.0, 500.0, 1000.0, 2000.0, 4000.0, 8000.0]) -barrier = environmental.Barrier(source_to_edge=101.0, edge_to_receiver=101.0) -att = environmental.outdoor_propagation_attenuation( +barrier = environment.Barrier(source_to_edge=101.0, edge_to_receiver=101.0) +att = environment.outdoor_propagation_attenuation( 200.0, 1.5, 1.5, bands, ground_source=1.0, ground_middle=1.0, ground_receiver=1.0, barrier=barrier, temperature=15.0, relative_humidity=70.0, @@ -336,15 +336,15 @@ plt.show() ```python import numpy as np -from phonometry import environmental +from phonometry import environment bands = [63, 125, 250, 500, 1000, 2000, 4000, 8000] # octave-band centres [Hz] # A point source and receiver 1.5 m high, 200 m apart over porous ground # (G = 1), screened midway by a barrier that raises the path over its top edge # (dss = dsr ~ 101 m). Geometry feeds the pathlength-difference equations. -barrier = environmental.Barrier(source_to_edge=101.0, edge_to_receiver=101.0) -att = environmental.outdoor_propagation_attenuation( +barrier = environment.Barrier(source_to_edge=101.0, edge_to_receiver=101.0) +att = environment.outdoor_propagation_attenuation( 200.0, source_height=1.5, receiver_height=1.5, frequencies=bands, ground_source=1.0, ground_middle=1.0, ground_receiver=1.0, barrier=barrier, temperature=15.0, relative_humidity=70.0, @@ -357,7 +357,7 @@ att.plot() # the stacked breakdown above (needs matplotli # Predicted receiver level from an octave-band sound power Lw = 95 dB lw = np.full(len(bands), 95.0) -lp = environmental.predicted_receiver_level( +lp = environment.predicted_receiver_level( lw, 200.0, 1.5, 1.5, bands, 1.0, 1.0, 1.0, barrier=barrier, temperature=15.0, relative_humidity=70.0, ) @@ -383,14 +383,14 @@ general per-region method of 7.3.1); combine them by hand when the alternative method is appropriate. ```python -from phonometry import environmental +from phonometry import environment # Alternative ground term (mean path height hm = 2 m, d = 200 m) -print(round(environmental.ground_attenuation_alternative(200.0, 2.0), 2)) # 4.43 dB +print(round(environment.ground_attenuation_alternative(200.0, 2.0), 2)) # 4.43 dB # Its companion solid-angle index (add to Dc when using Eq. (10)) -print(round(environmental.directivity_omega(1.5, 1.5, 200.0), 2)) # 3.01 dB +print(round(environment.directivity_omega(1.5, 1.5, 200.0), 2)) # 3.01 dB # Long-term meteorological correction (C0 = 2 dB) to subtract from LAT(DW) -print(round(environmental.meteorological_correction(200.0, 1.5, 1.5, 2.0), 2)) # 1.7 dB +print(round(environment.meteorological_correction(200.0, 1.5, 1.5, 2.0), 2)) # 1.7 dB ``` ### The image source behind the ground effect diff --git a/site/src/content/docs/guides/reverberation-prediction.mdx b/site/src/content/docs/guides/reverberation-prediction.mdx index 37183e1be..01195bded 100644 --- a/site/src/content/docs/guides/reverberation-prediction.mdx +++ b/site/src/content/docs/guides/reverberation-prediction.mdx @@ -130,7 +130,7 @@ $c_0 = 343\ \mathrm{m/s}$) and the air-absorption term $4mV$. ```python import matplotlib.pyplot as plt -from phonometry import environmental, room +from phonometry import environment, room # A 10 x 7 x 3.5 m room: hard end walls, lightly treated side walls and a # very absorptive floor/ceiling pair (carpet plus an acoustic ceiling). @@ -138,7 +138,7 @@ bands = [125.0, 250.0, 500.0, 1000.0, 2000.0, 4000.0] alpha_x = [0.06, 0.07, 0.08, 0.09, 0.10, 0.10] alpha_y = [0.12, 0.14, 0.16, 0.18, 0.20, 0.20] alpha_z = [0.30, 0.50, 0.65, 0.78, 0.82, 0.80] -m = environmental.air_attenuation_m(bands, 20.0, 50.0) # air at 20 C / 50 % RH +m = environment.air_attenuation_m(bands, 20.0, 50.0) # air at 20 C / 50 % RH res = room.reverberation_time_models((10.0, 7.0, 3.5), (alpha_x, alpha_y, alpha_z), air_attenuation=m, frequencies=bands) @@ -182,9 +182,9 @@ metre, from the ISO 9613-1 [atmospheric absorption](/phonometry/guides/outdoor-propagation/)): ```python -from phonometry import environmental, room +from phonometry import environment, room -m = environmental.air_attenuation_m(2000.0, temperature=20.0, relative_humidity=50.0) +m = environment.air_attenuation_m(2000.0, temperature=20.0, relative_humidity=50.0) surfaces = [(40.0, 0.3), (40.0, 0.3), (24.0, 0.3), (24.0, 0.3), (15.0, 0.3), (15.0, 0.3)] print(round(room.eyring_reverberation_time(120.0, surfaces, air_attenuation=m), 3)) @@ -265,9 +265,9 @@ res.plot() # the five model curves per band (the figure above) ```python import matplotlib.pyplot as plt -from phonometry import environmental, room +from phonometry import environment, room -m = environmental.air_attenuation_m([125.0, 250.0, 500.0, 1000.0, 2000.0, 4000.0], 20.0, 50.0) +m = environment.air_attenuation_m([125.0, 250.0, 500.0, 1000.0, 2000.0, 4000.0], 20.0, 50.0) room.reverberation_time_models( (10.0, 7.0, 3.5), ( diff --git a/site/src/content/docs/guides/spanish-noise-regulation.mdx b/site/src/content/docs/guides/spanish-noise-regulation.mdx index 9781b7850..5895aa85b 100644 --- a/site/src/content/docs/guides/spanish-noise-regulation.mdx +++ b/site/src/content/docs/guides/spanish-noise-regulation.mdx @@ -65,20 +65,20 @@ $K_t$ is evaluated band by band and, when several emergent tones are present, the largest of the resulting $K_t$ governs (step d): ```python -from phonometry import environmental +from phonometry import environment freqs = [100, 125, 160, 200, 250, 315, 400, 500, 630, 800, 1000] levels = [58.0, 60.0, 59.0, 61.0, 72.0, 62.0, 60.0, 58.0, 56.0, 54.0, 52.0] -kt = environmental.tonal_correction(levels, freqs) +kt = environment.tonal_correction(levels, freqs) kt.correction # 6 dB kt.governing_frequency # 250.0 Hz kt.differences[4] # Lt = 10.5 dB above the mean of the neighbours -environmental.low_frequency_correction(lceq=76.0, laeq=63.0) # Lf = 13 dB -> Kf = 3 dB -environmental.impulsive_correction(laieq=68.0, laeq=63.0) # Li = 5 dB -> Ki = 0 dB -environmental.corrected_level(63.0, kt=6, kf=3, ki=0) # LKeq = 72.0 dB -environmental.total_correction(kt=6, kf=6, ki=3) # 9.0 dB: the cap +environment.low_frequency_correction(lceq=76.0, laeq=63.0) # Lf = 13 dB -> Kf = 3 dB +environment.impulsive_correction(laieq=68.0, laeq=63.0) # Li = 5 dB -> Ki = 0 dB +environment.corrected_level(63.0, kt=6, kf=3, ki=0) # LKeq = 72.0 dB +environment.total_correction(kt=6, kf=6, ki=3) # 9.0 dB: the cap ``` ## Relationship with the ISO 1996 procedures @@ -125,20 +125,20 @@ own rounding: add 0.5 dB to the result and take the integer part, which is what `round_reported_level()` does. ```python -from phonometry import environmental +from phonometry import environment -environmental.RD1367_PERIOD_CLOCK_LIMITS["day"] # (7, 19) -environmental.RD1367_PERIOD_HOURS["day"] # 12.0 h +environment.RD1367_PERIOD_CLOCK_LIMITS["day"] # (7, 19) +environment.RD1367_PERIOD_HOURS["day"] # 12.0 h -day = [environmental.NoisePhase(2, 0.0, label="cerrada"), - environmental.NoisePhase(6, 50.0, kt=6, kf=3), - environmental.NoisePhase(4, 48.0, kt=3, kf=3)] +day = [environment.NoisePhase(2, 0.0, label="cerrada"), + environment.NoisePhase(6, 50.0, kt=6, kf=3), + environment.NoisePhase(4, 48.0, kt=3, kf=3)] -lkeq_d = environmental.evaluation_period_level(day) # 56.82 dB, unrounded -environmental.round_reported_level(lkeq_d) # 57 dB +lkeq_d = environment.evaluation_period_level(day) # 56.82 dB, unrounded +environment.round_reported_level(lkeq_d) # 57 dB -lk_d = environmental.long_term_corrected_level([57.0, 0.0], weights=[303, 62]) -environmental.round_reported_level(lk_d) # 56 dB (303 days open, 62 closed) +lk_d = environment.long_term_corrected_level([57.0, 0.0], weights=[303, 62]) +environment.round_reported_level(lk_d) # 56 dB (303 days open, 62 closed) ``` ## Acoustic quality objectives and immission limit values @@ -166,16 +166,16 @@ And in areas urbanised after the regulation entered into force (24 October `urbanisation="new"` applies. ```python -from phonometry import environmental - -environmental.outdoor_quality_objectives("a") # 65 / 65 / 55 dB -environmental.outdoor_quality_objectives("a", urbanisation="new") # 60 / 60 / 50 dB -environmental.indoor_quality_objectives("residential", "bedrooms") # 40 / 40 / 30 dB -environmental.infrastructure_limits("a") # 60 / 60 / 50 dB -environmental.max_infrastructure_limit("a") # 85 dB (LAmax) -environmental.activity_limits("a") # 55 / 55 / 45 dB -environmental.adjacent_premises_limits("residential", "bedrooms") # 35 / 35 / 25 dB -environmental.vibration_quality_objective("residential") # 75 dB +from phonometry import environment + +environment.outdoor_quality_objectives("a") # 65 / 65 / 55 dB +environment.outdoor_quality_objectives("a", urbanisation="new") # 60 / 60 / 50 dB +environment.indoor_quality_objectives("residential", "bedrooms") # 40 / 40 / 30 dB +environment.infrastructure_limits("a") # 60 / 60 / 50 dB +environment.max_infrastructure_limit("a") # 85 dB (LAmax) +environment.activity_limits("a") # 55 / 55 / 45 dB +environment.adjacent_premises_limits("residential", "bedrooms") # 35 / 35 / 25 dB +environment.vibration_quality_objective("residential") # 75 dB ``` Meeting an immission limit value is not simply staying below the number in the @@ -208,16 +208,16 @@ each $L_{Keq,Ti}$, 58 dB on the daily $L_{Keq,x}$ and 55 dB on the annual $L_{K,x}$: ```python -from phonometry import environmental +from phonometry import environment -day = [environmental.NoisePhase(2, 0.0, label="cerrada"), - environmental.NoisePhase(6, 50.0, kt=6, kf=3), - environmental.NoisePhase(4, 48.0, kt=3, kf=3)] -evening = [environmental.NoisePhase(2, 48.0, kt=3, kf=3), - environmental.NoisePhase(2, 0.0, label="cerrada")] +day = [environment.NoisePhase(2, 0.0, label="cerrada"), + environment.NoisePhase(6, 50.0, kt=6, kf=3), + environment.NoisePhase(4, 48.0, kt=3, kf=3)] +evening = [environment.NoisePhase(2, 48.0, kt=3, kf=3), + environment.NoisePhase(2, 0.0, label="cerrada")] -limits = environmental.activity_limits("a") # 55 / 55 / 45 dB -verdict = environmental.assess_activity( +limits = environment.activity_limits("a") # 55 / 55 / 45 dB +verdict = environment.assess_activity( {"day": day, "evening": evening}, limits, operating_days=303) verdict.periods[0].reported_level # 57 dB (LKeq,d) @@ -232,17 +232,17 @@ verdict.complies # False: LK,d exceeds 55 dB ```python import matplotlib.pyplot as plt -from phonometry import environmental +from phonometry import environment -day = [environmental.NoisePhase(2, 0.0, label="cerrada"), - environmental.NoisePhase(6, 50.0, kt=6, kf=3), - environmental.NoisePhase(4, 48.0, kt=3, kf=3)] -evening = [environmental.NoisePhase(2, 48.0, kt=3, kf=3), - environmental.NoisePhase(2, 0.0, label="cerrada")] +day = [environment.NoisePhase(2, 0.0, label="cerrada"), + environment.NoisePhase(6, 50.0, kt=6, kf=3), + environment.NoisePhase(4, 48.0, kt=3, kf=3)] +evening = [environment.NoisePhase(2, 48.0, kt=3, kf=3), + environment.NoisePhase(2, 0.0, label="cerrada")] -verdict = environmental.assess_activity( +verdict = environment.assess_activity( {"day": day, "evening": evening}, - environmental.activity_limits("a"), + environment.activity_limits("a"), operating_days=303, ) verdict.plot() @@ -273,15 +273,15 @@ applicable limit values and the boxed verdict. The default language is Spanish, the language of the regulation; `language="en"` translates it. ```python -from phonometry import environmental - -verdict = environmental.assess_activity( - {"day": [environmental.NoisePhase(2, 0.0, label="cerrada"), - environmental.NoisePhase(6, 50.0, kt=6, kf=3), - environmental.NoisePhase(4, 48.0, kt=3, kf=3)], - "evening": [environmental.NoisePhase(2, 48.0, kt=3, kf=3), - environmental.NoisePhase(2, 0.0, label="cerrada")]}, - environmental.activity_limits("a"), +from phonometry import environment + +verdict = environment.assess_activity( + {"day": [environment.NoisePhase(2, 0.0, label="cerrada"), + environment.NoisePhase(6, 50.0, kt=6, kf=3), + environment.NoisePhase(4, 48.0, kt=3, kf=3)], + "evening": [environment.NoisePhase(2, 48.0, kt=3, kf=3), + environment.NoisePhase(2, 0.0, label="cerrada")]}, + environment.activity_limits("a"), operating_days=303, ) @@ -310,7 +310,7 @@ not implemented either: this page starts where the sound level meter ends. - [Integrated and Statistical Levels](/phonometry/guides/levels/): the per-phase $L_{Aeq}$, $L_{Ceq}$ and $L_{AIeq}$ levels that feed the three corrections. - [Spanish Building Code (CTE DB-HR)](/phonometry/guides/spanish-building-code/): the other half of the Spanish framework, which shares the site's day noise index $L_d$ with this page. - [Impulsive-sound prominence](/phonometry/guides/impulse-prominence/): the ISO/PAS 1996-3 impulsive adjustment, the relative of $K_i$ that is not interchangeable with it. -- API reference: [`environmental.spanish_regulation`](/phonometry/reference/api/environment/spanish-regulation/). +- API reference: [`environmental.spanish_regulation`](/phonometry/reference/api/environment/spain/). ## Quick answers diff --git a/site/src/content/docs/guides/wind-turbine-noise.mdx b/site/src/content/docs/guides/wind-turbine-noise.mdx index 994572a87..57e90e651 100644 --- a/site/src/content/docs/guides/wind-turbine-noise.mdx +++ b/site/src/content/docs/guides/wind-turbine-noise.mdx @@ -45,12 +45,12 @@ ground-board pressure doubling. ```python -from phonometry import environmental +from phonometry import environment # Background-corrected A-weighted one-third-octave band levels L_p,i (dB). band_levels = [55.0, 58.0, 60.0, 57.0, 54.0] -r1 = environmental.slant_distance(hub_height=80.0, rotor_diameter=100.0) -lwa = environmental.apparent_sound_power_level(band_levels, r1) # dB re 1 pW +r1 = environment.slant_distance(hub_height=80.0, rotor_diameter=100.0) +lwa = environment.apparent_sound_power_level(band_levels, r1) # dB re 1 pW ``` ### Why "apparent" @@ -127,20 +127,20 @@ $\Delta L_a \ge -3\ \text{dB}$ and audible when $\Delta L_a > 0$. ```python import numpy as np -from phonometry import environmental +from phonometry import environment df = 2.0 freqs = np.arange(50.0, 400.0 + df, df) levels = 42.0 - 6.0 * np.log10(freqs / 100.0) levels[int(np.argmin(np.abs(freqs - 200.0)))] += 22.0 # blade-passing-style tone -environmental.wind_turbine_tonality(levels, freqs, tone_frequency=200.0).plot() +environment.wind_turbine_tonality(levels, freqs, tone_frequency=200.0).plot() ``` ```python import numpy as np -from phonometry import environmental +from phonometry import environment # A uniformly-spaced narrowband spectrum (2 Hz resolution): a flat 30 dB floor # with a discrete 60 dB tone at 500 Hz. @@ -148,7 +148,7 @@ frequencies = np.arange(440.0, 562.0, 2.0) levels = np.full(frequencies.size, 30.0) levels[np.argmin(np.abs(frequencies - 500.0))] = 60.0 -res = environmental.wind_turbine_tonality(levels, frequencies) +res = environment.wind_turbine_tonality(levels, frequencies) print(res.tone_frequency, res.tonality, res.tonal_audibility, res.is_audible) res.plot() # spectrum + critical band + masking level (needs matplotlib) ``` @@ -169,4 +169,4 @@ $K_T$, pass the mean audibility to the ISO 1996-2 `tonal_adjustment`. ## See also -- API reference: [`environmental.wind_turbine_noise`](/phonometry/reference/api/aeroacoustics/wind-turbine-noise/). +- API reference: [`environmental.wind_turbine_noise`](/phonometry/reference/api/environment/wind-turbine/). diff --git a/site/src/content/docs/reference/api/building/spanish-building-code.md b/site/src/content/docs/reference/api/building/spanish-building-code.md index f27fe9f70..0934d5926 100644 --- a/site/src/content/docs/reference/api/building/spanish-building-code.md +++ b/site/src/content/docs/reference/api/building/spanish-building-code.md @@ -74,7 +74,7 @@ about 1,8 m2 specimens; larger windows insulate less, and the CEC corrects :::note `"sanitary"` does not mean the same thing here as in -[`phonometry.environmental.spanish_regulation`](/phonometry/reference/api/environment/spanish-regulation/). In Table 2.1 of DB-HR +[`phonometry.environment.assessment.spain`](/phonometry/reference/api/environment/spain/). In Table 2.1 of DB-HR it is the *non-hospital* ambulatory health use of footnote (1) (medical practices, consulting rooms), deliberately distinct from `"hospital"`; in the RD 1367/2007 tables it is the *hospitalario* use itself. Both names diff --git a/site/src/content/docs/reference/api/environment/air-absorption.md b/site/src/content/docs/reference/api/environment/air-absorption.md index 85ea51a3f..f38ae927f 100644 --- a/site/src/content/docs/reference/api/environment/air-absorption.md +++ b/site/src/content/docs/reference/api/environment/air-absorption.md @@ -1,5 +1,5 @@ --- -title: "environmental.air_absorption" +title: "environment.propagation.air_absorption" description: "Atmospheric absorption of sound: ISO 9613-1:1993." sidebar: label: "air_absorption" diff --git a/site/src/content/docs/reference/api/environment/cnossos-rail.md b/site/src/content/docs/reference/api/environment/cnossos-rail.md index 4bff40b63..a5996ee46 100644 --- a/site/src/content/docs/reference/api/environment/cnossos-rail.md +++ b/site/src/content/docs/reference/api/environment/cnossos-rail.md @@ -1,5 +1,5 @@ --- -title: "environmental.cnossos_rail" +title: "environment.sources.cnossos_rail" description: "CNOSSOS-EU railway source emission (Directive 2002/49/EC Annex II, 2.3)." sidebar: label: "cnossos_rail" diff --git a/site/src/content/docs/reference/api/environment/cnossos-road.md b/site/src/content/docs/reference/api/environment/cnossos-road.md index 0ab71b6c8..6683abce6 100644 --- a/site/src/content/docs/reference/api/environment/cnossos-road.md +++ b/site/src/content/docs/reference/api/environment/cnossos-road.md @@ -1,5 +1,5 @@ --- -title: "environmental.cnossos_road" +title: "environment.sources.cnossos_road" description: "CNOSSOS-EU road traffic source emission (Directive 2002/49/EC Annex II, 2.2)." sidebar: label: "cnossos_road" @@ -102,7 +102,7 @@ methodology". Only the per-metre line power is defined by the method. ## ROAD_COEFFICIENTS -*Constant* (`phonometry.environmental.cnossos_road.RoadEmissionCoefficients`). +*Constant* (`phonometry.environment.sources.cnossos_road.RoadEmissionCoefficients`). ## ROAD_OCTAVE_BANDS diff --git a/site/src/content/docs/reference/api/environment/ground-barriers.md b/site/src/content/docs/reference/api/environment/ground-barriers.md index 06d5a11e6..d21dbd509 100644 --- a/site/src/content/docs/reference/api/environment/ground-barriers.md +++ b/site/src/content/docs/reference/api/environment/ground-barriers.md @@ -1,5 +1,5 @@ --- -title: "environmental.ground_barriers" +title: "environment.propagation.ground_barriers" description: "Spherical-wave ground effect and advanced barrier diffraction." sidebar: label: "ground_barriers" @@ -8,7 +8,7 @@ sidebar: Spherical-wave ground effect and advanced barrier diffraction. This module extends the tabulated ground and barrier terms of ISO 9613-2 (see -[`phonometry.environmental.outdoor_propagation`](/phonometry/reference/api/environment/outdoor-propagation/)) with the underlying wave +[`phonometry.environment.propagation.outdoor_propagation`](/phonometry/reference/api/environment/outdoor-propagation/)) with the underlying wave acoustics: the spherical-wave reflection coefficient of a finite-impedance ground and the wave-theoretic diffraction of a screen, both in a homogeneous (non-refracting, non-turbulent) atmosphere. diff --git a/site/src/content/docs/reference/api/environment/impulse-prominence.md b/site/src/content/docs/reference/api/environment/impulse-prominence.md index 07e8ca7d3..850030b63 100644 --- a/site/src/content/docs/reference/api/environment/impulse-prominence.md +++ b/site/src/content/docs/reference/api/environment/impulse-prominence.md @@ -1,5 +1,5 @@ --- -title: "environmental.impulse_prominence" +title: "environment.assessment.impulse_prominence" description: "Prominence of impulsive sounds and the LAeq adjustment (NT ACOU 112:2002)." sidebar: label: "impulse_prominence" diff --git a/site/src/content/docs/reference/api/environment/impulsive-sound.md b/site/src/content/docs/reference/api/environment/impulsive-sound.md index 17e7aaee3..320f212a0 100644 --- a/site/src/content/docs/reference/api/environment/impulsive-sound.md +++ b/site/src/content/docs/reference/api/environment/impulsive-sound.md @@ -1,5 +1,5 @@ --- -title: "environmental.impulsive_sound" +title: "environment.assessment.impulsive_sound" description: "Objective prominence of impulsive sounds and the LAeq adjustment (ISO/PAS 1996-3:2022)." sidebar: label: "impulsive_sound" diff --git a/site/src/content/docs/reference/api/environment/measurement.md b/site/src/content/docs/reference/api/environment/measurement.md index 7f96e7820..807c9b611 100644 --- a/site/src/content/docs/reference/api/environment/measurement.md +++ b/site/src/content/docs/reference/api/environment/measurement.md @@ -1,5 +1,5 @@ --- -title: "environmental.measurement" +title: "environment.assessment.measurement" description: "Determination of environmental-noise sound pressure levels (ISO 1996-2:2017)." sidebar: label: "measurement" @@ -8,7 +8,7 @@ sidebar: Determination of environmental-noise sound pressure levels (ISO 1996-2:2017). The measurement companion of the ISO 1996-1 descriptors in -`phonometry.environmental`. ISO 1996-2 covers *how* the levels that feed +`phonometry.environment`. ISO 1996-2 covers *how* the levels that feed those descriptors are obtained: the tonal adjustment for prominent tones, the residual-noise correction, and the measurement-uncertainty budget. diff --git a/site/src/content/docs/reference/api/environment/outdoor-propagation.md b/site/src/content/docs/reference/api/environment/outdoor-propagation.md index 9a2f1d3eb..5373b07c9 100644 --- a/site/src/content/docs/reference/api/environment/outdoor-propagation.md +++ b/site/src/content/docs/reference/api/environment/outdoor-propagation.md @@ -1,5 +1,5 @@ --- -title: "environmental.outdoor_propagation" +title: "environment.propagation.outdoor_propagation" description: "Outdoor sound propagation: ISO 9613-2:1996 general method of calculation." sidebar: label: "outdoor_propagation" @@ -30,7 +30,7 @@ Implemented here are the four general terms of clause 7: * `Adiv` geometrical divergence, $20 \log_{10}(d/d_0) + 11$ (Eq. (7)); * `Aatm` atmospheric absorption, $\alpha d$ (Eq. (8)) with `alpha` - the ISO 9613-1 coefficient supplied by [`phonometry.environmental.air_absorption`](/phonometry/reference/api/environment/air-absorption/); + the ISO 9613-1 coefficient supplied by [`phonometry.environment.propagation.air_absorption`](/phonometry/reference/api/environment/air-absorption/); * `Agr` ground effect, both the general per-region method of 7.3.1 with the Table 3 functions `a'/b'/c'/d'` (Eq. (9)) and the alternative simplified method of 7.3.2 (Eq. (10)); diff --git a/site/src/content/docs/reference/api/environment/rating.md b/site/src/content/docs/reference/api/environment/rating.md index 3d4a0baa1..5cdb43fef 100644 --- a/site/src/content/docs/reference/api/environment/rating.md +++ b/site/src/content/docs/reference/api/environment/rating.md @@ -1,5 +1,5 @@ --- -title: "environmental.rating" +title: "environment.assessment.rating" description: "Environmental noise descriptors per ISO 1996-1:2016." sidebar: label: "rating" diff --git a/site/src/content/docs/reference/api/environment/atmospheric-refraction.md b/site/src/content/docs/reference/api/environment/refraction.md similarity index 92% rename from site/src/content/docs/reference/api/environment/atmospheric-refraction.md rename to site/src/content/docs/reference/api/environment/refraction.md index a9b83d9e6..04b86b8ad 100644 --- a/site/src/content/docs/reference/api/environment/atmospheric-refraction.md +++ b/site/src/content/docs/reference/api/environment/refraction.md @@ -1,8 +1,8 @@ --- -title: "environmental.atmospheric_refraction" +title: "environment.propagation.refraction" description: "Atmospheric refraction: ray tracing and the parabolic equation (PE)." sidebar: - label: "atmospheric_refraction" + label: "refraction" --- Atmospheric refraction: ray tracing and the parabolic equation (PE). @@ -17,13 +17,13 @@ Attenborough & Van Renterghem, *Predicting Outdoor Sound* (2e, CRC, 2021, Ch. 11), and it is the refracting-atmosphere counterpart of the range-independent ocean solvers in [`phonometry.underwater.numerical_propagation`](/phonometry/reference/api/underwater/numerical-propagation/): -* [`atmospheric_ray_paths`](/phonometry/reference/api/environment/atmospheric-refraction/#atmospheric_ray_paths) -- geometrical acoustics. Integrates Snell's law +* [`atmospheric_ray_paths`](/phonometry/reference/api/environment/refraction/#atmospheric_ray_paths) -- geometrical acoustics. Integrates Snell's law for sound rays (Salomons Eq. (4.3)) with a fixed-step Runge-Kutta scheme, returning the curved ray paths, their turning points, travel times and ground reflections. It shares its ray core with the ocean `ray_trace`: the atmospheric version reflects at the ground ($z = 0$) instead of the sea surface and marches an upward-open half space. -* [`atmospheric_parabolic_equation`](/phonometry/reference/api/environment/atmospheric-refraction/#atmospheric_parabolic_equation) -- the Green's Function Parabolic +* [`atmospheric_parabolic_equation`](/phonometry/reference/api/environment/refraction/#atmospheric_parabolic_equation) -- the Green's Function Parabolic Equation (GFPE, Salomons Appendix H). Marches the one-way wave equation in range with the split-step Fourier algorithm (the same range-marching family as the ocean `parabolic_equation`), a Gaussian starter (Salomons @@ -34,16 +34,16 @@ ocean solvers in [`phonometry.underwater.numerical_propagation`](/phonometry/ref the relative sound level (dB re free field) over the range-height plane. For a linear effective sound-speed profile the ray paths are exact circular -arcs of radius [`ray_curvature_radius`](/phonometry/reference/api/environment/atmospheric-refraction/#ray_curvature_radius), and an upward-refracting linear -profile has a closed-form [`shadow_zone_distance`](/phonometry/reference/api/environment/atmospheric-refraction/#shadow_zone_distance); both anchor the ray +arcs of radius [`ray_curvature_radius`](/phonometry/reference/api/environment/refraction/#ray_curvature_radius), and an upward-refracting linear +profile has a closed-form [`shadow_zone_distance`](/phonometry/reference/api/environment/refraction/#shadow_zone_distance); both anchor the ray model. The PE is anchored against the exact spherical-wave ground effect -([`phonometry.environmental.ground_effect`](/phonometry/reference/api/environment/ground-barriers/#ground_effect)) in the homogeneous limit +([`phonometry.environment.ground_effect`](/phonometry/reference/api/environment/ground-barriers/#ground_effect)) in the homogeneous limit (gradient zero), which it reproduces to a few tenths of a dB on the default grid (finer `height_step` converges it further). The ground impedance is taken in the $e^{-i \omega t}$ convention of Salomons (a passive ground has $\operatorname{Im}(Z) > 0$), shared with -[`phonometry.environmental.ground_barriers`](/phonometry/reference/api/environment/ground-barriers/). The porous models of +[`phonometry.environment.propagation.ground_barriers`](/phonometry/reference/api/environment/ground-barriers/). The porous models of `phonometry.materials` work in the opposite $e^{+j \omega t}$ convention ($\operatorname{Im}(Z) < 0$), so an impedance derived from them (`flow_resistivity=` or a @@ -120,7 +120,7 @@ convention. | `height_step` | Vertical grid spacing `dz`, in metres. Default (`None`): a tenth of a wavelength (Salomons Sec. G.2). | | `air_density` | Air density `rho`, in kg/m3 (for the porous model). | -**Returns:** An [`AtmosphericPEResult`](/phonometry/reference/api/environment/atmospheric-refraction/#atmosphericperesult). +**Returns:** An [`AtmosphericPEResult`](/phonometry/reference/api/environment/refraction/#atmosphericperesult). **Raises** @@ -159,13 +159,13 @@ $dt/dr = 1/(\xi c^2)$. | Name | Description | | :--- | :--- | -| `profile` | The effective sound-speed profile (see [`linear_sound_speed_profile`](/phonometry/reference/api/environment/atmospheric-refraction/#linear_sound_speed_profile) / [`log_linear_sound_speed_profile`](/phonometry/reference/api/environment/atmospheric-refraction/#log_linear_sound_speed_profile)). | +| `profile` | The effective sound-speed profile (see [`linear_sound_speed_profile`](/phonometry/reference/api/environment/refraction/#linear_sound_speed_profile) / [`log_linear_sound_speed_profile`](/phonometry/reference/api/environment/refraction/#log_linear_sound_speed_profile)). | | `source_height` | Source height `zs`, in metres (>= 0). | | `launch_angles_deg` | Launch angles from the horizontal, in degrees (positive upward), within `(-90, 90)`. | | `max_range` | Maximum horizontal range to trace, in metres. | | `n_steps` | Number of range steps per ray (>= 2). | -**Returns:** An [`AtmosphericRayResult`](/phonometry/reference/api/environment/atmospheric-refraction/#atmosphericrayresult). +**Returns:** An [`AtmosphericRayResult`](/phonometry/reference/api/environment/refraction/#atmosphericrayresult). **Raises** @@ -334,7 +334,7 @@ and creates an acoustic shadow near the ground (Salomons Sec. 4.2). | `ground_speed` | Sound speed `c0` at the ground, in m/s. | | `max_height` | Top of the sampled profile, in metres. | -**Returns:** A two-point [`EffectiveSoundSpeedProfile`](/phonometry/reference/api/environment/atmospheric-refraction/#effectivesoundspeedprofile). +**Returns:** A two-point [`EffectiveSoundSpeedProfile`](/phonometry/reference/api/environment/refraction/#effectivesoundspeedprofile). **Raises** @@ -374,7 +374,7 @@ near the ground is resolved by sampling the height grid logarithmically. | `max_height` | Top of the sampled profile, in metres. | | `n_points` | Number of samples of the height grid (>= 2). | -**Returns:** An [`EffectiveSoundSpeedProfile`](/phonometry/reference/api/environment/atmospheric-refraction/#effectivesoundspeedprofile). +**Returns:** An [`EffectiveSoundSpeedProfile`](/phonometry/reference/api/environment/refraction/#effectivesoundspeedprofile). **Raises** diff --git a/site/src/content/docs/reference/api/environment/spanish-regulation.md b/site/src/content/docs/reference/api/environment/spain.md similarity index 95% rename from site/src/content/docs/reference/api/environment/spanish-regulation.md rename to site/src/content/docs/reference/api/environment/spain.md index 83be07077..bc27c9596 100644 --- a/site/src/content/docs/reference/api/environment/spanish-regulation.md +++ b/site/src/content/docs/reference/api/environment/spain.md @@ -1,8 +1,8 @@ --- -title: "environmental.spanish_regulation" +title: "environment.assessment.spain" description: "Spanish noise regulation: the corrected level LKeq (Real Decreto 1367/2007)." sidebar: - label: "spanish_regulation" + label: "spain" --- Spanish noise regulation: the corrected level LKeq (Real Decreto 1367/2007). @@ -110,7 +110,7 @@ area they sit in (Article 24.1). | :--- | :--- | | `area_type` | Acoustic area type letter, or an alias. | -**Returns:** The applicable [`RegulationLimits`](/phonometry/reference/api/environment/spanish-regulation/#regulationlimits) for the index `LK,x`. +**Returns:** The applicable [`RegulationLimits`](/phonometry/reference/api/environment/spain/#regulationlimits) for the index `LK,x`. **Raises** @@ -216,7 +216,7 @@ the street is not. | `building_use` | `"residential"`, `"office"` (alias `"administrative"`), `"sanitary"` or `"educational"`. | | `room_type` | `"living"`, `"bedrooms"`, `"professional_offices"`, `"offices"`, `"classrooms"` or `"reading_rooms"`. | -**Returns:** The applicable [`RegulationLimits`](/phonometry/reference/api/environment/spanish-regulation/#regulationlimits) for the index `LK,x`. +**Returns:** The applicable [`RegulationLimits`](/phonometry/reference/api/environment/spain/#regulationlimits) for the index `LK,x`. **Raises** @@ -260,15 +260,15 @@ derived from `operating_days`: the reported daily level then represents | Name | Description | | :--- | :--- | | `measurements` | Noise phases keyed by evaluation period (`"day"`, `"evening"`, `"night"`). Periods that are absent are not assessed. | -| `limits` | The applicable limit row, from [`activity_limits`](/phonometry/reference/api/environment/spanish-regulation/#activity_limits) or [`adjacent_premises_limits`](/phonometry/reference/api/environment/spanish-regulation/#adjacent_premises_limits). | +| `limits` | The applicable limit row, from [`activity_limits`](/phonometry/reference/api/environment/spain/#activity_limits) or [`adjacent_premises_limits`](/phonometry/reference/api/environment/spain/#adjacent_premises_limits). | | `long_term_levels` | Annual `LK,x` per period, in dB, when known. | | `operating_days` | Number of days a year the activity operates; used to derive `LK,x` from the daily `LKeq,x` when `long_term_levels` is not given. | | `year_days` | Days in the year considered (default 365). | | `closed_level` | Level, in dB, representing a day on which the activity does not operate (default 0 dB, as in the Spanish worked literature). | | `new_activity` | Whether the annual criterion applies. | -| `period_hours` | Period durations `T`, in hours, overriding [`RD1367_PERIOD_HOURS`](/phonometry/reference/api/environment/spanish-regulation/#rd1367_period_hours). | +| `period_hours` | Period durations `T`, in hours, overriding [`RD1367_PERIOD_HOURS`](/phonometry/reference/api/environment/spain/#rd1367_period_hours). | -**Returns:** An [`ActivityAssessment`](/phonometry/reference/api/environment/spanish-regulation/#activityassessment). +**Returns:** An [`ActivityAssessment`](/phonometry/reference/api/environment/spain/#activityassessment). **Raises** @@ -326,7 +326,7 @@ Evaluation-period level `LKeq,T` from its noise phases. $L_{Keq,T} = 10 \log_{10}\left[ (1/T) \sum_i T_i \cdot 10^{L_{Keq,Ti}/10} \right]$ (Annex IV A.3.4.2 b): the duration-weighted energy mean of the phase levels. The -returned value is **not** rounded; apply [`round_reported_level`](/phonometry/reference/api/environment/spanish-regulation/#round_reported_level) for the value +returned value is **not** rounded; apply [`round_reported_level`](/phonometry/reference/api/environment/spain/#round_reported_level) for the value the regulation asks to report. **Parameters** @@ -363,7 +363,7 @@ This is the classic sound-level-meter route. The onset-rate method of [`impulsive_sound_adjustment`](/phonometry/reference/api/environment/impulsive-sound/#impulsive_sound_adjustment) (ISO/PAS 1996-3) is a different, signal-based procedure and its `KI` is not interchangeable with this `Ki`. The same misprint as in -[`low_frequency_correction`](/phonometry/reference/api/environment/spanish-regulation/#low_frequency_correction) affects the 3 dB row of the printed +[`low_frequency_correction`](/phonometry/reference/api/environment/spain/#low_frequency_correction) affects the 3 dB row of the printed table. ::: @@ -404,7 +404,7 @@ cultural buildings. | `building_use` | `"residential"`, `"sanitary"` (alias `"hospital"`) or `"educational"`. | | `room_type` | `"living"` (living areas), `"bedrooms"`, `"classrooms"` or `"reading_rooms"`. | -**Returns:** The applicable [`RegulationLimits`](/phonometry/reference/api/environment/spanish-regulation/#regulationlimits) for the index `Lx`. +**Returns:** The applicable [`RegulationLimits`](/phonometry/reference/api/environment/spain/#regulationlimits) for the index `Lx`. **Raises** @@ -430,7 +430,7 @@ values (Article 23.1). | :--- | :--- | | `area_type` | Acoustic area type letter, or an alias. | -**Returns:** The applicable [`RegulationLimits`](/phonometry/reference/api/environment/spanish-regulation/#regulationlimits) for the index `Lx`. +**Returns:** The applicable [`RegulationLimits`](/phonometry/reference/api/environment/spain/#regulationlimits) for the index `Lx`. **Raises** @@ -612,7 +612,7 @@ numeric objective: footnote (2) refers it to the adjoining areas. | `area_type` | Acoustic area type: the letter `"e"`, `"a"`, `"d"`, `"c"` or `"b"`, or an alias such as `"residential"`. | | `urbanisation` | `"existing"` (default) for areas already urbanised on 24 October 2007, or `"new"` for the rest (5 dB stricter). | -**Returns:** The applicable [`RegulationLimits`](/phonometry/reference/api/environment/spanish-regulation/#regulationlimits) for the index `Lx`. +**Returns:** The applicable [`RegulationLimits`](/phonometry/reference/api/environment/spain/#regulationlimits) for the index `Lx`. **Raises** @@ -815,7 +815,7 @@ arithmetic mean with 8/5/3 dB thresholds and grades the result 0/3/6 dB. | `levels` | Unweighted one-third-octave band levels, in dB. | | `frequencies` | Band centre frequencies, in Hz, in ascending order (one per level). | -**Returns:** A [`TonalCorrectionResult`](/phonometry/reference/api/environment/spanish-regulation/#tonalcorrectionresult). +**Returns:** A [`TonalCorrectionResult`](/phonometry/reference/api/environment/spain/#tonalcorrectionresult). **Raises** @@ -883,7 +883,7 @@ correction of, say, 4.5 dB is not a reading the regulation can produce. | `kf` | Low-frequency correction, in dB (0, 3 or 6). | | `ki` | Impulsive correction, in dB (0, 3 or 6). | -**Returns:** The summed correction, in dB, never above [`RD1367_MAX_CORRECTION`](/phonometry/reference/api/environment/spanish-regulation/#rd1367_max_correction). +**Returns:** The summed correction, in dB, never above [`RD1367_MAX_CORRECTION`](/phonometry/reference/api/environment/spain/#rd1367_max_correction). **Raises** diff --git a/site/src/content/docs/reference/api/aeroacoustics/wind-turbine-noise.md b/site/src/content/docs/reference/api/environment/wind-turbine.md similarity index 90% rename from site/src/content/docs/reference/api/aeroacoustics/wind-turbine-noise.md rename to site/src/content/docs/reference/api/environment/wind-turbine.md index e89c13aee..094f76ffc 100644 --- a/site/src/content/docs/reference/api/aeroacoustics/wind-turbine-noise.md +++ b/site/src/content/docs/reference/api/environment/wind-turbine.md @@ -1,26 +1,26 @@ --- -title: "environmental.wind_turbine_noise" +title: "environment.sources.wind_turbine" description: "Wind-turbine acoustic noise (IEC 61400-11:2012+A1:2018)." sidebar: - label: "wind_turbine_noise" + label: "wind_turbine" --- Wind-turbine acoustic noise (IEC 61400-11:2012+A1:2018). Two closed-form quantities of the standard: -* [`apparent_sound_power_level`](/phonometry/reference/api/aeroacoustics/wind-turbine-noise/#apparent_sound_power_level) -- the A-weighted apparent sound power +* [`apparent_sound_power_level`](/phonometry/reference/api/environment/wind-turbine/#apparent_sound_power_level) -- the A-weighted apparent sound power level `L_WA` referred to the equivalent point source at the rotor centre, from the ground-board sound pressure level and the slant distance - ([`slant_distance`](/phonometry/reference/api/aeroacoustics/wind-turbine-noise/#slant_distance)), + ([`slant_distance`](/phonometry/reference/api/environment/wind-turbine/#slant_distance)), $L_{WA} = L_p - 6 + 10 \cdot \log_{10}(4\pi R_1^2/S_0)$ (Formula 26). -* [`wind_turbine_tonality`](/phonometry/reference/api/aeroacoustics/wind-turbine-noise/#wind_turbine_tonality) -- the tonal-audibility chain (Formulae 30-34): +* [`wind_turbine_tonality`](/phonometry/reference/api/environment/wind-turbine/#wind_turbine_tonality) -- the tonal-audibility chain (Formulae 30-34): the critical bandwidth ([`critical_bandwidth`](/phonometry/reference/api/environment/measurement/#critical_bandwidth)), the masking-noise level, the tonality and the audibility criterion, giving the tonal audibility `ΔL_a` that decides whether a tone is audible. The tonal-audibility formula itself is the ISO 1996-2 Annex C one already in -[`phonometry.environmental.measurement`](/phonometry/reference/api/environment/measurement/); what is specific to IEC 61400-11 is +[`phonometry.environment.assessment.measurement`](/phonometry/reference/api/environment/measurement/); what is specific to IEC 61400-11 is how the tone and masking-noise levels and the (Zwicker) critical band are determined from the narrowband spectrum. The rating adjustment `K_T` is the ISO 1996-2 [`tonal_adjustment`](/phonometry/reference/api/environment/measurement/#tonal_adjustment). The @@ -49,7 +49,7 @@ pressure doubling; $S_0 = 1$ m². | Name | Description | | :--- | :--- | -| `band_levels` | Background-corrected A-weighted band sound pressure levels `L_p,i`, in dB (scalar or per band). The 61400-11-specific background correction (Formula 23 subtraction with the 3-6 dB asterisk marking and the \<= 3 dB not-reported rule, subclause 9.3) is out of scope here and must be applied beforehand; note its rule set differs from the ISO 1996-2 correction in [`phonometry.environmental.measurement.residual_sound_correction`](/phonometry/reference/api/environment/measurement/#residual_sound_correction). | +| `band_levels` | Background-corrected A-weighted band sound pressure levels `L_p,i`, in dB (scalar or per band). The 61400-11-specific background correction (Formula 23 subtraction with the 3-6 dB asterisk marking and the \<= 3 dB not-reported rule, subclause 9.3) is out of scope here and must be applied beforehand; note its rule set differs from the ISO 1996-2 correction in [`phonometry.environment.assessment.measurement.residual_sound_correction`](/phonometry/reference/api/environment/measurement/#residual_sound_correction). | | `r1` | Slant distance `R1` to the rotor centre, in m. | **Returns:** The apparent sound power level `L_WA`, in dB re 1 pW. @@ -125,11 +125,11 @@ that highest line (9.5.4), which also anchors `L_a`. When the candidate fails the 9.5.2 screening or no line classifies as "tone", the result carries `has_identified_tone = False`: its numeric fields are non-standard fallbacks and the spectrum must be excluded from the 9.5.1 -bin averaging (see [`WindTurbineTonalityResult`](/phonometry/reference/api/aeroacoustics/wind-turbine-noise/#windturbinetonalityresult)). +bin averaging (see [`WindTurbineTonalityResult`](/phonometry/reference/api/environment/wind-turbine/#windturbinetonalityresult)). Spectra must extend over the whole critical band: a truncated band leaves the masking average over the surviving lines while Formula 31 still scales -to the full bandwidth, so a [`WindTurbineNoiseWarning`](/phonometry/reference/api/aeroacoustics/wind-turbine-noise/#windturbinenoisewarning) is issued. +to the full bandwidth, so a [`WindTurbineNoiseWarning`](/phonometry/reference/api/environment/wind-turbine/#windturbinenoisewarning) is issued. Candidates below 20 Hz are outside the standard's analysis range (the critical band would extend to negative frequencies) and are rejected. @@ -141,7 +141,7 @@ critical band would extend to negative frequencies) and are rejected. | `frequencies` | Line frequencies, in Hz (uniform spacing). | | `tone_frequency` | Candidate tone frequency, in Hz; if `None` the highest-level line is used. | -**Returns:** A [`WindTurbineTonalityResult`](/phonometry/reference/api/aeroacoustics/wind-turbine-noise/#windturbinetonalityresult). +**Returns:** A [`WindTurbineTonalityResult`](/phonometry/reference/api/environment/wind-turbine/#windturbinetonalityresult). **Raises** diff --git a/site/src/content/docs/reference/api/index.md b/site/src/content/docs/reference/api/index.md index 18c644235..3d5f47caf 100644 --- a/site/src/content/docs/reference/api/index.md +++ b/site/src/content/docs/reference/api/index.md @@ -167,19 +167,20 @@ La referencia de la API se genera a partir de los docstrings del código (en ing | Module | Summary | | :--- | :--- | -| [`environmental.outdoor_propagation`](/phonometry/reference/api/environment/outdoor-propagation/) | Outdoor sound propagation: ISO 9613-2:1996 general method of calculation. | -| [`environmental.cnossos_road`](/phonometry/reference/api/environment/cnossos-road/) | CNOSSOS-EU road traffic source emission (Directive 2002/49/EC Annex II, 2.2). | -| [`environmental.ground_barriers`](/phonometry/reference/api/environment/ground-barriers/) | Spherical-wave ground effect and advanced barrier diffraction. | -| [`environmental.atmospheric_refraction`](/phonometry/reference/api/environment/atmospheric-refraction/) | Atmospheric refraction: ray tracing and the parabolic equation (PE). | -| [`environmental.air_absorption`](/phonometry/reference/api/environment/air-absorption/) | Atmospheric absorption of sound: ISO 9613-1:1993. | -| [`environmental.cnossos_rail`](/phonometry/reference/api/environment/cnossos-rail/) | CNOSSOS-EU railway source emission (Directive 2002/49/EC Annex II, 2.3). | -| [`environmental.impulse_prominence`](/phonometry/reference/api/environment/impulse-prominence/) | Prominence of impulsive sounds and the LAeq adjustment (NT ACOU 112:2002). | -| [`environmental.impulsive_sound`](/phonometry/reference/api/environment/impulsive-sound/) | Objective prominence of impulsive sounds and the `LAeq` adjustment (ISO/PAS 1996-3:2022). | -| [`environmental.rating`](/phonometry/reference/api/environment/rating/) | Environmental noise descriptors per ISO 1996-1:2016. | -| [`environmental.measurement`](/phonometry/reference/api/environment/measurement/) | Determination of environmental-noise sound pressure levels (ISO 1996-2:2017). | -| [`environmental.spanish_regulation`](/phonometry/reference/api/environment/spanish-regulation/) | Spanish noise regulation: the corrected level LKeq (Real Decreto 1367/2007). | - -## Aircraft and wind energy +| [`environment.propagation.outdoor_propagation`](/phonometry/reference/api/environment/outdoor-propagation/) | Outdoor sound propagation: ISO 9613-2:1996 general method of calculation. | +| [`environment.sources.cnossos_road`](/phonometry/reference/api/environment/cnossos-road/) | CNOSSOS-EU road traffic source emission (Directive 2002/49/EC Annex II, 2.2). | +| [`environment.propagation.ground_barriers`](/phonometry/reference/api/environment/ground-barriers/) | Spherical-wave ground effect and advanced barrier diffraction. | +| [`environment.propagation.refraction`](/phonometry/reference/api/environment/refraction/) | Atmospheric refraction: ray tracing and the parabolic equation (PE). | +| [`environment.propagation.air_absorption`](/phonometry/reference/api/environment/air-absorption/) | Atmospheric absorption of sound: ISO 9613-1:1993. | +| [`environment.sources.cnossos_rail`](/phonometry/reference/api/environment/cnossos-rail/) | CNOSSOS-EU railway source emission (Directive 2002/49/EC Annex II, 2.3). | +| [`environment.assessment.impulse_prominence`](/phonometry/reference/api/environment/impulse-prominence/) | Prominence of impulsive sounds and the LAeq adjustment (NT ACOU 112:2002). | +| [`environment.assessment.impulsive_sound`](/phonometry/reference/api/environment/impulsive-sound/) | Objective prominence of impulsive sounds and the `LAeq` adjustment (ISO/PAS 1996-3:2022). | +| [`environment.assessment.rating`](/phonometry/reference/api/environment/rating/) | Environmental noise descriptors per ISO 1996-1:2016. | +| [`environment.sources.wind_turbine`](/phonometry/reference/api/environment/wind-turbine/) | Wind-turbine acoustic noise (IEC 61400-11:2012+A1:2018). | +| [`environment.assessment.measurement`](/phonometry/reference/api/environment/measurement/) | Determination of environmental-noise sound pressure levels (ISO 1996-2:2017). | +| [`environment.assessment.spain`](/phonometry/reference/api/environment/spain/) | Spanish noise regulation: the corrected level LKeq (Real Decreto 1367/2007). | + +## Aircraft noise | Module | Summary | | :--- | :--- | @@ -188,7 +189,6 @@ La referencia de la API se genera a partir de los docstrings del código (en ing | [`aircraft.airport_noise`](/phonometry/reference/api/aeroacoustics/airport-noise/) | Noise-Power-Distance (NPD) event-level interpolation (ECAC Doc 29). | | [`aircraft.anp_fleet`](/phonometry/reference/api/aeroacoustics/anp-fleet/) | EASA ANP fleet database bridge for the ECAC Doc 29 airport-noise chain. | | [`aircraft.rotorcraft_noise`](/phonometry/reference/api/aeroacoustics/rotorcraft-noise/) | Rotorcraft noise by the hemisphere method (ECAC Doc 32 / NORAH2). | -| [`environmental.wind_turbine_noise`](/phonometry/reference/api/aeroacoustics/wind-turbine-noise/) | Wind-turbine acoustic noise (IEC 61400-11:2012+A1:2018). | ## Underwater acoustics diff --git a/site/src/content/docs/reference/api/psychoacoustics/tone-audibility.md b/site/src/content/docs/reference/api/psychoacoustics/tone-audibility.md index 0fb483904..a11899fd1 100644 --- a/site/src/content/docs/reference/api/psychoacoustics/tone-audibility.md +++ b/site/src/content/docs/reference/api/psychoacoustics/tone-audibility.md @@ -9,7 +9,7 @@ Objective audibility of tones in noise -- engineering method (ISO/PAS 20065:2016 ISO/PAS 20065 is the detailed engineering method that ISO 1996-2:2017 defers to for the audibility of prominent tones; the simplified 2007/2009 Annex C method -lives in [`phonometry.environmental.measurement`](/phonometry/reference/api/environment/measurement/). The audibility of a tone +lives in [`phonometry.environment.assessment.measurement`](/phonometry/reference/api/environment/measurement/). The audibility of a tone is the amount, in decibels, by which its tone level rises above the masking threshold of the surrounding noise. diff --git a/site/src/generated/api-sidebar.mjs b/site/src/generated/api-sidebar.mjs index 2d0ef65f1..f2553d54c 100644 --- a/site/src/generated/api-sidebar.mjs +++ b/site/src/generated/api-sidebar.mjs @@ -175,19 +175,20 @@ export const apiSidebar = { 'reference/api/environment/outdoor-propagation', 'reference/api/environment/cnossos-road', 'reference/api/environment/ground-barriers', - 'reference/api/environment/atmospheric-refraction', + 'reference/api/environment/refraction', 'reference/api/environment/air-absorption', 'reference/api/environment/cnossos-rail', 'reference/api/environment/impulse-prominence', 'reference/api/environment/impulsive-sound', 'reference/api/environment/rating', + 'reference/api/environment/wind-turbine', 'reference/api/environment/measurement', - 'reference/api/environment/spanish-regulation', + 'reference/api/environment/spain', ], }, { - label: 'Aircraft and wind energy', - translations: { es: 'Aeronaves y energía eólica' }, + label: 'Aircraft noise', + translations: { es: 'Ruido de aeronaves' }, collapsed: true, items: [ 'reference/api/aeroacoustics/aircraft-noise', @@ -195,7 +196,6 @@ export const apiSidebar = { 'reference/api/aeroacoustics/airport-noise', 'reference/api/aeroacoustics/anp-fleet', 'reference/api/aeroacoustics/rotorcraft-noise', - 'reference/api/aeroacoustics/wind-turbine-noise', ], }, { diff --git a/src/phonometry/__init__.py b/src/phonometry/__init__.py index 8f75ca0eb..7f0132c64 100644 --- a/src/phonometry/__init__.py +++ b/src/phonometry/__init__.py @@ -497,14 +497,103 @@ velocity_level, velocity_level_from_acceleration, ) -from .environmental.air_absorption import ( +from .environment.assessment.impulse_prominence import ( + ImpulseProminenceResult, + ImpulseProminenceWarning, + impulse_adjustment, + impulse_prominence, + predicted_prominence, + rating_level, +) +from .environment.assessment.impulsive_sound import ( + ImpulseOnset, + ImpulsiveSoundResult, + ImpulsiveSoundWarning, + detect_onsets, + impulsive_sound_adjustment, + sound_pressure_level_history, +) +from .environment.assessment.measurement import ( + RepeatedMeasurementResult, + ResidualCorrectionResult, + TonalAssessmentResult, + assess_tonal_audibility, + combined_standard_uncertainty, + critical_bandwidth, + gaussian_residual_level, + residual_correction_uncertainty, + residual_sound_correction, + tonal_adjustment, + tonal_adjustment_from_mean_audibility, + tonal_audibility, + tonal_seeking_survey, + uncertainty_from_repeated_measurements, +) +from .environment.assessment.measurement import ( + expanded_uncertainty as environmental_expanded_uncertainty, +) +from .environment.assessment.rating import composite_rating_level, lden, ldn +from .environment.assessment.spain import ( + ACOUSTIC_AREA_TYPES, + RD1367_CORRECTION_VALUES, + RD1367_EVALUATION_PERIODS, + RD1367_MAX_CORRECTION, + RD1367_PERIOD_CLOCK_LIMITS, + RD1367_PERIOD_HOURS, + ActivityAssessment, + NoisePhase, + PeriodAssessment, + RegulationLimits, + TonalCorrectionResult, + activity_limits, + adjacent_premises_limits, + assess_activity, + corrected_level, + evaluation_period_level, + impulsive_correction, + indoor_quality_objectives, + infrastructure_limits, + long_term_corrected_level, + low_frequency_correction, + max_infrastructure_limit, + outdoor_quality_objectives, + round_reported_level, + tonal_correction, + total_correction, + vibration_quality_objective, +) +from .environment.propagation.air_absorption import ( AtmosphericAbsorptionWarning, AtmosphericAttenuation, air_attenuation, air_attenuation_m, atmospheric_attenuation, ) -from .environmental.atmospheric_refraction import ( +from .environment.propagation.ground_barriers import ( + BarrierInsertionLoss, + SphericalGroundResult, + barrier_insertion_loss, + fresnel_number, + ground_effect, + kurze_anderson_attenuation, + spherical_reflection_coefficient, +) +from .environment.propagation.outdoor_propagation import ( + DEFAULT_FREQUENCIES, + Barrier, + OutdoorAttenuation, + SourceEmission, + atmospheric_absorption, + barrier_attenuation, + directivity_omega, + geometric_divergence, + ground_attenuation, + ground_attenuation_alternative, + meteorological_correction, + outdoor_propagation_attenuation, + predicted_receiver_level, +) +from .environment.propagation.refraction import ( AtmosphericPEResult, AtmosphericRayResult, EffectiveSoundSpeedProfile, @@ -515,7 +604,7 @@ ray_curvature_radius, shadow_zone_distance, ) -from .environmental.cnossos_rail import ( +from .environment.sources.cnossos_rail import ( AERODYNAMIC_REFERENCE_SPEED, AERODYNAMIC_THRESHOLD_SPEED, RAILWAY_MINIMUM_SPEED, @@ -565,7 +654,7 @@ wheel_roughness, wheel_transfer, ) -from .environmental.cnossos_road import ( +from .environment.sources.cnossos_road import ( CNOSSOS_A_WEIGHTING, ROAD_COEFFICIENTS, ROAD_OCTAVE_BANDS, @@ -586,96 +675,7 @@ road_surface_coefficients, road_vehicle_sound_power, ) -from .environmental.ground_barriers import ( - BarrierInsertionLoss, - SphericalGroundResult, - barrier_insertion_loss, - fresnel_number, - ground_effect, - kurze_anderson_attenuation, - spherical_reflection_coefficient, -) -from .environmental.impulse_prominence import ( - ImpulseProminenceResult, - ImpulseProminenceWarning, - impulse_adjustment, - impulse_prominence, - predicted_prominence, - rating_level, -) -from .environmental.impulsive_sound import ( - ImpulseOnset, - ImpulsiveSoundResult, - ImpulsiveSoundWarning, - detect_onsets, - impulsive_sound_adjustment, - sound_pressure_level_history, -) -from .environmental.measurement import ( - RepeatedMeasurementResult, - ResidualCorrectionResult, - TonalAssessmentResult, - assess_tonal_audibility, - combined_standard_uncertainty, - critical_bandwidth, - gaussian_residual_level, - residual_correction_uncertainty, - residual_sound_correction, - tonal_adjustment, - tonal_adjustment_from_mean_audibility, - tonal_audibility, - tonal_seeking_survey, - uncertainty_from_repeated_measurements, -) -from .environmental.measurement import ( - expanded_uncertainty as environmental_expanded_uncertainty, -) -from .environmental.outdoor_propagation import ( - DEFAULT_FREQUENCIES, - Barrier, - OutdoorAttenuation, - SourceEmission, - atmospheric_absorption, - barrier_attenuation, - directivity_omega, - geometric_divergence, - ground_attenuation, - ground_attenuation_alternative, - meteorological_correction, - outdoor_propagation_attenuation, - predicted_receiver_level, -) -from .environmental.rating import composite_rating_level, lden, ldn -from .environmental.spanish_regulation import ( - ACOUSTIC_AREA_TYPES, - RD1367_CORRECTION_VALUES, - RD1367_EVALUATION_PERIODS, - RD1367_MAX_CORRECTION, - RD1367_PERIOD_CLOCK_LIMITS, - RD1367_PERIOD_HOURS, - ActivityAssessment, - NoisePhase, - PeriodAssessment, - RegulationLimits, - TonalCorrectionResult, - activity_limits, - adjacent_premises_limits, - assess_activity, - corrected_level, - evaluation_period_level, - impulsive_correction, - indoor_quality_objectives, - infrastructure_limits, - long_term_corrected_level, - low_frequency_correction, - max_infrastructure_limit, - outdoor_quality_objectives, - round_reported_level, - tonal_correction, - total_correction, - vibration_quality_objective, -) -from .environmental.wind_turbine_noise import ( +from .environment.sources.wind_turbine import ( WindTurbineNoiseWarning, WindTurbineTonalityResult, apparent_sound_power_level, diff --git a/src/phonometry/_compat.py b/src/phonometry/_compat.py index 8392a9d16..0d68f99e8 100644 --- a/src/phonometry/_compat.py +++ b/src/phonometry/_compat.py @@ -11,9 +11,10 @@ subpackages into domain ones (``phonometry.metrology`` into ``phonometry.filters``, ``phonometry.signals`` and a narrowed ``phonometry.metrology``; the speech intelligibility of - ``phonometry.hearing`` into ``phonometry.speech``), and gives the large - domains a second level (``phonometry.vibration`` into ``structural``, - ``human`` and ``machinery``). Removed in 5.0. + ``phonometry.hearing`` into ``phonometry.speech``), gives the large domains + a second level (``phonometry.vibration`` into ``structural``, ``human`` and + ``machinery``) and renames ``phonometry.environmental`` to + ``phonometry.environment``. Removed in 5.0. Every public module path that moved stays importable through the shims registered here: ``import phonometry.`` and ``from phonometry. @@ -106,11 +107,16 @@ "phonometry.vibration.human.multiple_shock", "phonometry.transfer_stiffness": "phonometry.vibration.structural.transfer_stiffness", - "phonometry.air_absorption": "phonometry.environmental.air_absorption", - "phonometry.environmental_measurement": "phonometry.environmental.measurement", - "phonometry.impulse_prominence": "phonometry.environmental.impulse_prominence", - "phonometry.outdoor_propagation": "phonometry.environmental.outdoor_propagation", - "phonometry.wind_turbine_noise": "phonometry.environmental.wind_turbine_noise", + "phonometry.air_absorption": + "phonometry.environment.propagation.air_absorption", + "phonometry.environmental_measurement": + "phonometry.environment.assessment.measurement", + "phonometry.impulse_prominence": + "phonometry.environment.assessment.impulse_prominence", + "phonometry.outdoor_propagation": + "phonometry.environment.propagation.outdoor_propagation", + "phonometry.wind_turbine_noise": + "phonometry.environment.sources.wind_turbine", "phonometry.aircraft_atmospheric_absorption": "phonometry.aircraft.atmospheric_absorption", "phonometry.aircraft_noise": "phonometry.aircraft.aircraft_noise", "phonometry.airport_noise": "phonometry.aircraft.airport_noise", @@ -185,6 +191,32 @@ "phonometry.vibration.human.multiple_shock", "phonometry.vibration.machine_diagnostics": "phonometry.vibration.machinery.diagnostics", + # The package itself was renamed, so the whole prefix is an alias. + "phonometry.environmental": "phonometry.environment", + "phonometry.environmental.outdoor_propagation": + "phonometry.environment.propagation.outdoor_propagation", + "phonometry.environmental.air_absorption": + "phonometry.environment.propagation.air_absorption", + "phonometry.environmental.ground_barriers": + "phonometry.environment.propagation.ground_barriers", + "phonometry.environmental.atmospheric_refraction": + "phonometry.environment.propagation.refraction", + "phonometry.environmental.cnossos_road": + "phonometry.environment.sources.cnossos_road", + "phonometry.environmental.cnossos_rail": + "phonometry.environment.sources.cnossos_rail", + "phonometry.environmental.wind_turbine_noise": + "phonometry.environment.sources.wind_turbine", + "phonometry.environmental.rating": + "phonometry.environment.assessment.rating", + "phonometry.environmental.measurement": + "phonometry.environment.assessment.measurement", + "phonometry.environmental.impulsive_sound": + "phonometry.environment.assessment.impulsive_sound", + "phonometry.environmental.impulse_prominence": + "phonometry.environment.assessment.impulse_prominence", + "phonometry.environmental.spanish_regulation": + "phonometry.environment.assessment.spain", } #: The two generations, each with the release that deprecated it and the one @@ -206,6 +238,13 @@ def __getattr__(name: str) -> Any: try: attr = getattr(target, name) except AttributeError: + # A renamed package keeps serving the modules that moved out of + # it: ``environmental.wind_turbine_noise`` is not an attribute of + # ``environment`` any more, it is an alias of its own. The alias + # carries the notice, so returning it here is silent. + alias = sys.modules.get(f"{old}.{name}") + if alias is not None: + return alias raise AttributeError( f"module {old!r} has no attribute {name!r}" ) from None @@ -218,7 +257,8 @@ def __getattr__(name: str) -> Any: return attr def __dir__() -> list[str]: - return dir(import_module(new)) + names = set(dir(import_module(new))) | set(_alias_modules(old)) + return sorted(names) shim.__getattr__ = __getattr__ # type: ignore[method-assign] shim.__dir__ = __dir__ # type: ignore[method-assign] diff --git a/src/phonometry/_plot/environmental.py b/src/phonometry/_plot/environmental.py index e24b3776e..ebf055139 100644 --- a/src/phonometry/_plot/environmental.py +++ b/src/phonometry/_plot/environmental.py @@ -26,26 +26,26 @@ if TYPE_CHECKING: from matplotlib.axes import Axes - from ..environmental.air_absorption import AtmosphericAttenuation - from ..environmental.atmospheric_refraction import ( - AtmosphericPEResult, - AtmosphericRayResult, - EffectiveSoundSpeedProfile, + from ..environment.assessment.impulse_prominence import ImpulseProminenceResult + from ..environment.assessment.measurement import TonalAssessmentResult + from ..environment.assessment.spain import ( + ActivityAssessment, + TonalCorrectionResult, ) - from ..environmental.cnossos_rail import RailwayEmissionResult - from ..environmental.cnossos_road import RoadEmissionResult - from ..environmental.ground_barriers import ( + from ..environment.propagation.air_absorption import AtmosphericAttenuation + from ..environment.propagation.ground_barriers import ( BarrierInsertionLoss, SphericalGroundResult, ) - from ..environmental.impulse_prominence import ImpulseProminenceResult - from ..environmental.measurement import TonalAssessmentResult - from ..environmental.outdoor_propagation import OutdoorAttenuation - from ..environmental.spanish_regulation import ( - ActivityAssessment, - TonalCorrectionResult, + from ..environment.propagation.outdoor_propagation import OutdoorAttenuation + from ..environment.propagation.refraction import ( + AtmosphericPEResult, + AtmosphericRayResult, + EffectiveSoundSpeedProfile, ) - from ..environmental.wind_turbine_noise import WindTurbineTonalityResult + from ..environment.sources.cnossos_rail import RailwayEmissionResult + from ..environment.sources.cnossos_road import RoadEmissionResult + from ..environment.sources.wind_turbine import WindTurbineTonalityResult #: Spanish translations of the fixed strings rendered by the environmental #: ``.plot()`` renderers, keyed by their verbatim English text. ``_t`` @@ -148,7 +148,7 @@ def plot_atmospheric_attenuation( humidity-dependent relaxation roll-off. :param result: An - :class:`~phonometry.environmental.air_absorption.AtmosphericAttenuation`. + :class:`~phonometry.environment.propagation.air_absorption.AtmosphericAttenuation`. :param ax: Existing axes, or ``None`` to create a figure. :param language: Label language, ``"en"`` (default) or ``"es"``. :param kwargs: Forwarded to the ``alpha`` curve ``plot`` call. @@ -185,14 +185,14 @@ def plot_wind_turbine_tonality( ) -> Axes: """Narrowband spectrum with the critical band, masking level and the tone. - :param result: A :class:`~phonometry.environmental.wind_turbine_noise.WindTurbineTonalityResult`. + :param result: A :class:`~phonometry.environment.sources.wind_turbine.WindTurbineTonalityResult`. :param ax: Existing axes, or ``None`` to create a figure. :param language: Label language, ``"en"`` (default) or ``"es"``. :param kwargs: Forwarded to the spectrum ``plot`` call. :return: The axes. """ from .._i18n import format_number, localize_axes - from ..environmental.wind_turbine_noise import _critical_band_edges + from ..environment.sources.wind_turbine import _critical_band_edges ax = ax if ax is not None else _new_axes() freqs = np.asarray(result.frequencies, dtype=np.float64) @@ -219,14 +219,14 @@ def plot_impulse_prominence( ) -> Axes: """Adjustment curve ``KI(P)`` with the candidate impulses marked. - :param result: An :class:`~phonometry.environmental.impulse_prominence.ImpulseProminenceResult`. + :param result: An :class:`~phonometry.environment.assessment.impulse_prominence.ImpulseProminenceResult`. :param ax: Existing axes, or ``None`` to create a figure. :param language: Label language, ``"en"`` (default) or ``"es"``. :param kwargs: Forwarded to the impulses ``scatter``. :return: The axes. """ from .._i18n import decimal_comma, format_number, localize_axes - from ..environmental.impulse_prominence import ( + from ..environment.assessment.impulse_prominence import ( ADJUSTMENT_THRESHOLD, impulse_adjustment, ) @@ -264,14 +264,14 @@ def plot_tonal_adjustment( """Tonal adjustment curve ``Kt(ΔLta)`` with the assessed tone marked. :param result: A - :class:`~phonometry.environmental.measurement.TonalAssessmentResult`. + :class:`~phonometry.environment.assessment.measurement.TonalAssessmentResult`. :param ax: Existing axes, or ``None`` to create a figure. :param language: Label language, ``"en"`` (default) or ``"es"``. :param kwargs: Forwarded to the assessed-tone ``scatter``. :return: The axes. """ from .._i18n import format_number, localize_axes - from ..environmental.measurement import tonal_adjustment + from ..environment.assessment.measurement import tonal_adjustment ax = ax if ax is not None else _new_axes() top = max(result.audibility, 12.0) + 1.0 @@ -309,7 +309,7 @@ def plot_outdoor_attenuation( as the primary marker line on top. :param result: An - :class:`~phonometry.environmental.outdoor_propagation.OutdoorAttenuation`. + :class:`~phonometry.environment.propagation.outdoor_propagation.OutdoorAttenuation`. :param ax: Existing axes, or ``None`` to create a figure. :param language: Label language, ``"en"`` (default) or ``"es"``. :param kwargs: Forwarded to the total-attenuation ``plot`` call. @@ -366,7 +366,7 @@ def plot_cnossos_rail_emission( are read directly off the chart. :param result: A - :class:`~phonometry.environmental.cnossos_rail.RailwayEmissionResult`. + :class:`~phonometry.environment.sources.cnossos_rail.RailwayEmissionResult`. :param ax: Existing axes, or ``None`` to create a figure. :param language: Label language, ``"en"`` (default) or ``"es"``. :param kwargs: Forwarded to the total-line-power ``bar`` call. @@ -417,7 +417,7 @@ def plot_spherical_ground( ground-effect dip and any surface-wave enhancement are both visible. :param result: A - :class:`~phonometry.environmental.ground_barriers.SphericalGroundResult`. + :class:`~phonometry.environment.propagation.ground_barriers.SphericalGroundResult`. :param ax: Existing axes, or ``None`` to create a figure. :param language: Label language, ``"en"`` (default) or ``"es"``. :param kwargs: Forwarded to the ``dL`` ``plot`` call. @@ -450,7 +450,7 @@ def plot_barrier_insertion_loss( """Barrier insertion loss versus frequency. :param result: A - :class:`~phonometry.environmental.ground_barriers.BarrierInsertionLoss`. + :class:`~phonometry.environment.propagation.ground_barriers.BarrierInsertionLoss`. :param ax: Existing axes, or ``None`` to create a figure. :param language: Label language, ``"en"`` (default) or ``"es"``. :param kwargs: Forwarded to the insertion-loss ``plot`` call. @@ -484,7 +484,7 @@ def plot_sound_speed_profile( """Effective sound-speed profile ``c_eff(z)`` (height on the vertical axis). :param profile: An - :class:`~phonometry.environmental.atmospheric_refraction.EffectiveSoundSpeedProfile`. + :class:`~phonometry.environment.propagation.refraction.EffectiveSoundSpeedProfile`. :param ax: Existing axes, or ``None`` to create a figure. :param language: Label language, ``"en"`` (default) or ``"es"``. :param kwargs: Forwarded to the profile ``plot`` call. @@ -513,7 +513,7 @@ def plot_atmospheric_rays( """Curved sound-ray paths over the ground (height on the vertical axis). :param result: An - :class:`~phonometry.environmental.atmospheric_refraction.AtmosphericRayResult`. + :class:`~phonometry.environment.propagation.refraction.AtmosphericRayResult`. :param ax: Existing axes, or ``None`` to create a figure. :param language: Label language, ``"en"`` (default) or ``"es"``. :param kwargs: Forwarded to each ray ``plot`` call. @@ -552,7 +552,7 @@ def plot_atmospheric_pe( ``phonometry_field_dark`` on a dark one -- and ``cmap`` overrides it. :param result: An - :class:`~phonometry.environmental.atmospheric_refraction.AtmosphericPEResult`. + :class:`~phonometry.environment.propagation.refraction.AtmosphericPEResult`. :param ax: Existing axes, or ``None`` to create a figure. :param language: Label language, ``"en"`` (default) or ``"es"``. :param kwargs: Forwarded to ``imshow``. @@ -603,7 +603,7 @@ def plot_tonal_correction_rd1367( highlighted. :param result: A - :class:`~phonometry.environmental.spanish_regulation.TonalCorrectionResult`. + :class:`~phonometry.environment.assessment.spain.TonalCorrectionResult`. :param ax: Existing axes, or ``None`` to create a figure. :param language: Label language, ``"en"`` (default) or ``"es"``. :param kwargs: Forwarded to the band-level ``bar`` call. @@ -660,7 +660,7 @@ def plot_activity_assessment( that exceeds its own limit is outlined in the exceedance colour. :param result: An - :class:`~phonometry.environmental.spanish_regulation.ActivityAssessment`. + :class:`~phonometry.environment.assessment.spain.ActivityAssessment`. :param ax: Existing axes, or ``None`` to create a figure. :param language: Label language, ``"en"`` (default) or ``"es"``. :param kwargs: Forwarded to the ``LKeq,x`` ``bar`` call. @@ -789,7 +789,7 @@ def plot_cnossos_road_emission( governs the source is read directly off the chart. :param result: A - :class:`~phonometry.environmental.cnossos_road.RoadEmissionResult`. + :class:`~phonometry.environment.sources.cnossos_road.RoadEmissionResult`. :param ax: Existing axes, or ``None`` to create a figure. :param language: Label language, ``"en"`` (default) or ``"es"``. :param kwargs: Forwarded to the total-line-power ``bar`` call. diff --git a/src/phonometry/_plot/geometry.py b/src/phonometry/_plot/geometry.py index b659938e5..9e164b406 100644 --- a/src/phonometry/_plot/geometry.py +++ b/src/phonometry/_plot/geometry.py @@ -45,7 +45,7 @@ from ..building.panel_transmission import SoundReductionResult from ..electroacoustics.piston import RadiatingPistonResult from ..emission.intensity import IntensityResult - from ..environmental.ground_barriers import BarrierInsertionLoss + from ..environment.propagation.ground_barriers import BarrierInsertionLoss from ..materials.diffuser_design import DiffuserPolarResponse from ..materials.impedance_tube import ImpedanceTubeResult, TransferMatrix from ..materials.metadiffuser import MetadiffuserResult @@ -1784,7 +1784,7 @@ def plot_barrier_geometry( Ground line, thin (or thick) screen, the direct path cut by the screen and the diffracted path over the top edge(s), with the path-length difference annotated. Distances follow - :func:`~phonometry.environmental.barrier_insertion_loss`: + :func:`~phonometry.environment.barrier_insertion_loss`: ``receiver_distance`` is horizontal from the source. :param ax: Existing axes, or ``None`` to create a figure. diff --git a/src/phonometry/_report/iec61400.py b/src/phonometry/_report/iec61400.py index 14b3f875a..1ca45205a 100644 --- a/src/phonometry/_report/iec61400.py +++ b/src/phonometry/_report/iec61400.py @@ -2,7 +2,7 @@ """Wind-turbine tonal audibility assessment fiche (reportlab renderer). Renders a -:class:`~phonometry.environmental.wind_turbine_noise.WindTurbineTonalityResult` +:class:`~phonometry.environment.sources.wind_turbine.WindTurbineTonalityResult` to a one-page PDF laid out like a wind-turbine tonality-assessment report of an environmental-noise laboratory. IEC 61400-11:2012+A1:2018 decides whether a wind turbine emits an audible tone: from the narrow-band spectrum it forms the @@ -66,7 +66,7 @@ from .metadata import ReportMetadata if TYPE_CHECKING: - from ..environmental.wind_turbine_noise import WindTurbineTonalityResult + from ..environment.sources.wind_turbine import WindTurbineTonalityResult def _fmt(value: float, language: str, decimals: int = 1) -> str: @@ -274,7 +274,7 @@ def render_wind_turbine_tonality_report( """Render a wind-turbine tonal audibility assessment fiche to a PDF at ``path``. :param result: A - :class:`~phonometry.environmental.wind_turbine_noise.WindTurbineTonalityResult` + :class:`~phonometry.environment.sources.wind_turbine.WindTurbineTonalityResult` carrying the tone/masking levels, the critical band and the tonal audibility. :param path: Destination path of the PDF file. diff --git a/src/phonometry/_report/iso1996_impulse.py b/src/phonometry/_report/iso1996_impulse.py index b37ea7cc6..1bb7f3aee 100644 --- a/src/phonometry/_report/iso1996_impulse.py +++ b/src/phonometry/_report/iso1996_impulse.py @@ -2,7 +2,7 @@ """Impulsive-sound prominence assessment fiche (reportlab renderer). Renders a -:class:`~phonometry.environmental.impulse_prominence.ImpulseProminenceResult` +:class:`~phonometry.environment.assessment.impulse_prominence.ImpulseProminenceResult` to a one-page PDF laid out like an impulsive-sound assessment report of an environmental-noise laboratory. NT ACOU 112:2002 (carried into ISO/PAS 1996-3:2022) measures how *prominent* an impulse is from its onset rate @@ -66,7 +66,7 @@ from .metadata import ReportMetadata if TYPE_CHECKING: - from ..environmental.impulse_prominence import ImpulseProminenceResult + from ..environment.assessment.impulse_prominence import ImpulseProminenceResult #: Minimum onset rate, in dB/s, for a level rise to qualify as an impulse #: (NT ACOU 112:2002, clause 4.5). Kept here so the renderer does not import @@ -362,7 +362,7 @@ def render_impulse_prominence_report( """Render an impulsive-sound prominence assessment fiche to a PDF at ``path``. :param result: An - :class:`~phonometry.environmental.impulse_prominence.ImpulseProminenceResult` + :class:`~phonometry.environment.assessment.impulse_prominence.ImpulseProminenceResult` carrying the per-impulse onset rates, level differences and prominences. :param path: Destination path of the PDF file. :param metadata: Optional :class:`ReportMetadata` supplying the header diff --git a/src/phonometry/_report/iso1996_tone.py b/src/phonometry/_report/iso1996_tone.py index 6aff703a8..ed0e7fcb3 100644 --- a/src/phonometry/_report/iso1996_tone.py +++ b/src/phonometry/_report/iso1996_tone.py @@ -81,7 +81,9 @@ def _tonal_adjustment(audibility: float) -> int: Imported lazily from the environmental-measurement domain module (the fiche layer references domain code only at call time, never at import time). """ - from ..environmental.measurement import tonal_adjustment_from_mean_audibility + from ..environment.assessment.measurement import ( + tonal_adjustment_from_mean_audibility, + ) return tonal_adjustment_from_mean_audibility(audibility) diff --git a/src/phonometry/_report/iso9613.py b/src/phonometry/_report/iso9613.py index 670ea2fac..e9f57b565 100644 --- a/src/phonometry/_report/iso9613.py +++ b/src/phonometry/_report/iso9613.py @@ -5,7 +5,7 @@ reports (clearly labelled predictions, never measurement certificates): * :func:`render_outdoor_attenuation_report` for an - :class:`~phonometry.environmental.outdoor_propagation.OutdoorAttenuation`: + :class:`~phonometry.environment.propagation.outdoor_propagation.OutdoorAttenuation`: the ISO 9613-2:1996 octave-band attenuation breakdown (geometrical divergence ``Adiv``, atmospheric absorption ``Aatm``, ground effect ``Agr`` and screening ``Abar``) and, from the source sound power the result was composed @@ -14,7 +14,7 @@ wide per-band term table above the attenuation-breakdown plot. * :func:`render_barrier_insertion_loss_report` for a - :class:`~phonometry.environmental.ground_barriers.BarrierInsertionLoss`: the + :class:`~phonometry.environment.propagation.ground_barriers.BarrierInsertionLoss`: the per-band barrier insertion loss ``IL`` and its mean over the octave bands. A two-panel layout: the per-band table beside the insertion-loss spectrum. Its diffraction model is the wave-theoretic rigid-screen solution (``method @@ -59,8 +59,8 @@ if TYPE_CHECKING: from numpy.typing import NDArray - from ..environmental.ground_barriers import BarrierInsertionLoss - from ..environmental.outdoor_propagation import ( + from ..environment.propagation.ground_barriers import BarrierInsertionLoss + from ..environment.propagation.outdoor_propagation import ( OutdoorAttenuation, SourceEmission, ) @@ -80,7 +80,7 @@ class _AttenuationLevels(NamedTuple): """The per-band source power ``Lw``, downwind level ``LfT`` and A-weighting. Bundles the display quantities the attenuation fiche derives from a - :class:`~phonometry.environmental.outdoor_propagation.SourceEmission`, so + :class:`~phonometry.environment.propagation.outdoor_propagation.SourceEmission`, so the table and the boxed A-weighted level share one computation. """ @@ -282,11 +282,11 @@ def _resolve_levels( """Compose the per-band levels the fiche boxes from a source emission. Calls the shared - :func:`~phonometry.environmental.outdoor_propagation._compose_receiver_level` + :func:`~phonometry.environment.propagation.outdoor_propagation._compose_receiver_level` so the report reuses the domain's Eq. (3) composition rather than reimplementing it. """ - from ..environmental.outdoor_propagation import _compose_receiver_level + from ..environment.propagation.outdoor_propagation import _compose_receiver_level freqs = np.asarray(result.frequencies, dtype=np.float64) lw = np.atleast_1d(np.asarray(emission.sound_power_level, dtype=np.float64)) @@ -318,7 +318,7 @@ def render_outdoor_attenuation_report( """Render an ISO 9613-2 outdoor-propagation prediction fiche to ``path``. :param result: An - :class:`~phonometry.environmental.outdoor_propagation.OutdoorAttenuation` + :class:`~phonometry.environment.propagation.outdoor_propagation.OutdoorAttenuation` (the per-band attenuation breakdown). :param path: Destination path of the PDF file. :param metadata: Optional :class:`ReportMetadata`; a ``requirement`` is the @@ -328,7 +328,7 @@ def render_outdoor_attenuation_report( table adds the A-weighted band level. :param language: ``"en"`` (default) or ``"es"``. :param source_emission: Optional - :class:`~phonometry.environmental.outdoor_propagation.SourceEmission`; + :class:`~phonometry.environment.propagation.outdoor_propagation.SourceEmission`; when supplied the fiche boxes the A-weighted downwind level at the receiver, otherwise it boxes the range of the total attenuation. :return: The written ``path`` as a :class:`str`. @@ -547,7 +547,7 @@ def render_barrier_insertion_loss_report( """Render a barrier insertion-loss prediction fiche to ``path``. :param result: A - :class:`~phonometry.environmental.ground_barriers.BarrierInsertionLoss`. + :class:`~phonometry.environment.propagation.ground_barriers.BarrierInsertionLoss`. :param path: Destination path of the PDF file. :param metadata: Optional :class:`ReportMetadata`; a ``requirement`` is the minimum required mean insertion loss (a higher insertion loss is better). diff --git a/src/phonometry/_report/rd1367.py b/src/phonometry/_report/rd1367.py index 77c80779c..c7ba1d397 100644 --- a/src/phonometry/_report/rd1367.py +++ b/src/phonometry/_report/rd1367.py @@ -2,7 +2,7 @@ """Activity noise inspection fiche (reportlab renderer, RD 1367/2007). Renders a -:class:`~phonometry.environmental.spanish_regulation.ActivityAssessment` to a +:class:`~phonometry.environment.assessment.spain.ActivityAssessment` to a one-page PDF laid out like a Spanish *informe de ensayo acustico* / *acta de inspeccion acustica* of an activity. @@ -76,7 +76,7 @@ ) if TYPE_CHECKING: - from ..environmental.spanish_regulation import ( + from ..environment.assessment.spain import ( ActivityAssessment, PeriodAssessment, ) @@ -363,7 +363,7 @@ def render_activity_report( """Render an RD 1367/2007 activity inspection fiche to a PDF at ``path``. :param result: An - :class:`~phonometry.environmental.spanish_regulation.ActivityAssessment`. + :class:`~phonometry.environment.assessment.spain.ActivityAssessment`. :param path: Destination path of the PDF file. :param metadata: Optional :class:`ReportMetadata` supplying the header identity (``specimen`` the activity, ``client`` its holder, diff --git a/src/phonometry/aircraft/atmospheric_absorption.py b/src/phonometry/aircraft/atmospheric_absorption.py index a74d2a56f..5037bb0d9 100644 --- a/src/phonometry/aircraft/atmospheric_absorption.py +++ b/src/phonometry/aircraft/atmospheric_absorption.py @@ -28,7 +28,7 @@ import numpy as np from .._internal.validation import require_non_negative, require_positive_array -from ..environmental.air_absorption import air_attenuation +from ..environment.propagation.air_absorption import air_attenuation if TYPE_CHECKING: from matplotlib.axes import Axes diff --git a/src/phonometry/aircraft/rotorcraft_noise.py b/src/phonometry/aircraft/rotorcraft_noise.py index 842606154..fe435474f 100644 --- a/src/phonometry/aircraft/rotorcraft_noise.py +++ b/src/phonometry/aircraft/rotorcraft_noise.py @@ -23,7 +23,7 @@ * :func:`atmospheric_adjustment` -- :math:`\Delta L_a = -\alpha(f) \cdot (r - 60)` with the ISO 9613-1 pure-tone coefficient (Eq. 26/27), reusing - :func:`~phonometry.environmental.air_absorption.air_attenuation`. + :func:`~phonometry.environment.propagation.air_absorption.air_attenuation`. * :func:`ground_effect_adjustment` -- ``ΔLg`` for a point source over an impedance plane (Chien-Soroka, Eq. 28-35) with the Delany-Bazley one-parameter impedance and the CNOSSOS flow-resistivity classes. @@ -1444,7 +1444,7 @@ def _absorption_coefficient( """ import warnings - from ..environmental.air_absorption import ( + from ..environment.propagation.air_absorption import ( AtmosphericAbsorptionWarning, air_attenuation, ) diff --git a/src/phonometry/building/spanish_building_code.py b/src/phonometry/building/spanish_building_code.py index 0f5be449b..0855160bc 100644 --- a/src/phonometry/building/spanish_building_code.py +++ b/src/phonometry/building/spanish_building_code.py @@ -69,7 +69,7 @@ .. note:: ``"sanitary"`` does not mean the same thing here as in - :mod:`phonometry.environmental.spanish_regulation`. In Table 2.1 of DB-HR + :mod:`phonometry.environment.assessment.spain`. In Table 2.1 of DB-HR it is the *non-hospital* ambulatory health use of footnote (1) (medical practices, consulting rooms), deliberately distinct from ``"hospital"``; in the RD 1367/2007 tables it is the *hospitalario* use itself. Both names diff --git a/src/phonometry/environmental/__init__.py b/src/phonometry/environment/__init__.py similarity index 93% rename from src/phonometry/environmental/__init__.py rename to src/phonometry/environment/__init__.py index f1745d106..3e32fd30a 100644 --- a/src/phonometry/environmental/__init__.py +++ b/src/phonometry/environment/__init__.py @@ -1,47 +1,147 @@ # Copyright (c) 2026. Jose Manuel Requena Plens -"""environmental domain of phonometry (see module docstrings).""" +"""environment domain of phonometry (see module docstrings). + +Renamed from ``environmental`` in 4.0 and given three subgroups, which is how +an environmental study is written: what the source emits +(:mod:`~phonometry.environment.sources`), what the path does to it +(:mod:`~phonometry.environment.propagation`) and what the law makes of the +result at the receiver (:mod:`~phonometry.environment.assessment`). Every +public name is still exported here, so ``from phonometry import environment`` +reads as the old package did. +""" from __future__ import annotations from .._plot.geometry import ( plot_barrier_geometry, ) -from .air_absorption import ( +from .assessment import ( + ACOUSTIC_AREA_TYPES, + RD1367_CORRECTION_VALUES, + RD1367_EVALUATION_PERIODS, + RD1367_MAX_CORRECTION, + RD1367_PERIOD_CLOCK_LIMITS, + RD1367_PERIOD_HOURS, + ActivityAssessment, + EnvironmentalMeasurementWarning, + ImpulseOnset, + ImpulseProminenceResult, + ImpulseProminenceWarning, + ImpulsiveSoundResult, + ImpulsiveSoundWarning, + NoisePhase, + PeriodAssessment, + RegulationLimits, + RepeatedMeasurementResult, + ResidualCorrectionResult, + TonalAssessmentResult, + TonalCorrectionResult, + activity_limits, + adjacent_premises_limits, + assess_activity, + assess_tonal_audibility, + combined_standard_uncertainty, + composite_rating_level, + corrected_level, + critical_bandwidth, + detect_onsets, + evaluation_period_level, + expanded_uncertainty, + gaussian_residual_level, + impulse_adjustment, + impulse_prominence, + impulsive_correction, + impulsive_sound_adjustment, + indoor_quality_objectives, + infrastructure_limits, + lden, + ldn, + long_term_corrected_level, + low_frequency_correction, + max_infrastructure_limit, + outdoor_quality_objectives, + predicted_prominence, + rating_level, + residual_correction_uncertainty, + residual_sound_correction, + round_reported_level, + sound_pressure_level_history, + tonal_adjustment, + tonal_adjustment_from_mean_audibility, + tonal_audibility, + tonal_correction, + tonal_seeking_survey, + total_correction, + uncertainty_from_repeated_measurements, + vibration_quality_objective, +) +from .propagation import ( + DEFAULT_FREQUENCIES, AtmosphericAbsorptionWarning, AtmosphericAttenuation, - air_attenuation, - air_attenuation_m, - atmospheric_attenuation, -) -from .atmospheric_refraction import ( AtmosphericPEResult, AtmosphericRayResult, + Barrier, + BarrierInsertionLoss, EffectiveSoundSpeedProfile, + OutdoorAttenuation, + SourceEmission, + SphericalGroundResult, + air_attenuation, + air_attenuation_m, + atmospheric_absorption, + atmospheric_attenuation, atmospheric_parabolic_equation, atmospheric_ray_paths, + barrier_attenuation, + barrier_insertion_loss, + directivity_omega, + fresnel_number, + geometric_divergence, + ground_attenuation, + ground_attenuation_alternative, + ground_effect, + kurze_anderson_attenuation, linear_sound_speed_profile, log_linear_sound_speed_profile, + meteorological_correction, + outdoor_propagation_attenuation, + predicted_receiver_level, ray_curvature_radius, shadow_zone_distance, + spherical_reflection_coefficient, ) -from .cnossos_rail import ( +from .sources import ( AERODYNAMIC_REFERENCE_SPEED, AERODYNAMIC_THRESHOLD_SPEED, + CNOSSOS_A_WEIGHTING, RAILWAY_MINIMUM_SPEED, RAILWAY_OCTAVE_BANDS, RAILWAY_SOURCE_HEIGHTS, RAILWAY_THIRD_OCTAVE_BANDS, REFERENCE_JOINT_DENSITY, + ROAD_COEFFICIENTS, + ROAD_OCTAVE_BANDS, + ROAD_REFERENCE_SPEED, + ROAD_REFERENCE_TEMPERATURE, + ROAD_SOURCE_HEIGHT, TRAM_MINIMUM_SPEED, BrakeType, BridgeType, ContactFilter, DirectivityEdition, + JunctionType, RailPad, RailRoughnessClass, RailwayEmissionResult, RailwayTrack, RailwayVehicle, + RoadEmissionCoefficients, + RoadEmissionResult, + RoadSurface, + RoadSurfaceCoefficients, + RoadTraffic, + RoadVehicleCategory, RollingStock, RoughnessInterpolation, RunningCondition, @@ -54,18 +154,28 @@ VehicleType, WheelDiameter, WheelMeasure, + WindTurbineNoiseWarning, + WindTurbineTonalityResult, aerodynamic_sound_power, + apparent_sound_power_level, bridge_transfer, contact_filter, curve_squeal_excess, horizontal_directivity, impact_roughness, impact_roughness_single, + line_source_segment_power, octave_bands_from_third_octaves, rail_roughness, railway_source_power, + road_propulsion_noise, + road_rolling_noise, + road_source_power, + road_surface_coefficients, + road_vehicle_sound_power, rolling_sound_power, roughness_to_frequency, + slant_distance, superstructure_transfer, total_effective_roughness, track_transfer, @@ -73,125 +183,6 @@ vertical_directivity, wheel_roughness, wheel_transfer, -) -from .cnossos_road import ( - CNOSSOS_A_WEIGHTING, - ROAD_COEFFICIENTS, - ROAD_OCTAVE_BANDS, - ROAD_REFERENCE_SPEED, - ROAD_REFERENCE_TEMPERATURE, - ROAD_SOURCE_HEIGHT, - JunctionType, - RoadEmissionCoefficients, - RoadEmissionResult, - RoadSurface, - RoadSurfaceCoefficients, - RoadTraffic, - RoadVehicleCategory, - line_source_segment_power, - road_propulsion_noise, - road_rolling_noise, - road_source_power, - road_surface_coefficients, - road_vehicle_sound_power, -) -from .ground_barriers import ( - BarrierInsertionLoss, - SphericalGroundResult, - barrier_insertion_loss, - fresnel_number, - ground_effect, - kurze_anderson_attenuation, - spherical_reflection_coefficient, -) -from .impulse_prominence import ( - ImpulseProminenceResult, - ImpulseProminenceWarning, - impulse_adjustment, - impulse_prominence, - predicted_prominence, - rating_level, -) -from .impulsive_sound import ( - ImpulseOnset, - ImpulsiveSoundResult, - ImpulsiveSoundWarning, - detect_onsets, - impulsive_sound_adjustment, - sound_pressure_level_history, -) -from .measurement import ( - EnvironmentalMeasurementWarning, - RepeatedMeasurementResult, - ResidualCorrectionResult, - TonalAssessmentResult, - assess_tonal_audibility, - combined_standard_uncertainty, - critical_bandwidth, - expanded_uncertainty, - gaussian_residual_level, - residual_correction_uncertainty, - residual_sound_correction, - tonal_adjustment, - tonal_adjustment_from_mean_audibility, - tonal_audibility, - tonal_seeking_survey, - uncertainty_from_repeated_measurements, -) -from .outdoor_propagation import ( - DEFAULT_FREQUENCIES, - Barrier, - OutdoorAttenuation, - SourceEmission, - atmospheric_absorption, - barrier_attenuation, - directivity_omega, - geometric_divergence, - ground_attenuation, - ground_attenuation_alternative, - meteorological_correction, - outdoor_propagation_attenuation, - predicted_receiver_level, -) -from .rating import ( - composite_rating_level, - lden, - ldn, -) -from .spanish_regulation import ( - ACOUSTIC_AREA_TYPES, - RD1367_CORRECTION_VALUES, - RD1367_EVALUATION_PERIODS, - RD1367_MAX_CORRECTION, - RD1367_PERIOD_CLOCK_LIMITS, - RD1367_PERIOD_HOURS, - ActivityAssessment, - NoisePhase, - PeriodAssessment, - RegulationLimits, - TonalCorrectionResult, - activity_limits, - adjacent_premises_limits, - assess_activity, - corrected_level, - evaluation_period_level, - impulsive_correction, - indoor_quality_objectives, - infrastructure_limits, - long_term_corrected_level, - low_frequency_correction, - max_infrastructure_limit, - outdoor_quality_objectives, - round_reported_level, - tonal_correction, - total_correction, - vibration_quality_objective, -) -from .wind_turbine_noise import ( - WindTurbineNoiseWarning, - WindTurbineTonalityResult, - apparent_sound_power_level, - slant_distance, wind_turbine_tonality, ) diff --git a/src/phonometry/environment/assessment/__init__.py b/src/phonometry/environment/assessment/__init__.py new file mode 100644 index 000000000..c260502fd --- /dev/null +++ b/src/phonometry/environment/assessment/__init__.py @@ -0,0 +1,134 @@ +# Copyright (c) 2026. Jose Manuel Requena Plens +"""environment.assessment subdomain of phonometry: rating and assessment of environmental noise (ISO 1996, national codes).""" + +from __future__ import annotations + +from .impulse_prominence import ( + ImpulseProminenceResult, + ImpulseProminenceWarning, + impulse_adjustment, + impulse_prominence, + predicted_prominence, + rating_level, +) +from .impulsive_sound import ( + ImpulseOnset, + ImpulsiveSoundResult, + ImpulsiveSoundWarning, + detect_onsets, + impulsive_sound_adjustment, + sound_pressure_level_history, +) +from .measurement import ( + EnvironmentalMeasurementWarning, + RepeatedMeasurementResult, + ResidualCorrectionResult, + TonalAssessmentResult, + assess_tonal_audibility, + combined_standard_uncertainty, + critical_bandwidth, + expanded_uncertainty, + gaussian_residual_level, + residual_correction_uncertainty, + residual_sound_correction, + tonal_adjustment, + tonal_adjustment_from_mean_audibility, + tonal_audibility, + tonal_seeking_survey, + uncertainty_from_repeated_measurements, +) +from .rating import ( + composite_rating_level, + lden, + ldn, +) +from .spain import ( + ACOUSTIC_AREA_TYPES, + RD1367_CORRECTION_VALUES, + RD1367_EVALUATION_PERIODS, + RD1367_MAX_CORRECTION, + RD1367_PERIOD_CLOCK_LIMITS, + RD1367_PERIOD_HOURS, + ActivityAssessment, + NoisePhase, + PeriodAssessment, + RegulationLimits, + TonalCorrectionResult, + activity_limits, + adjacent_premises_limits, + assess_activity, + corrected_level, + evaluation_period_level, + impulsive_correction, + indoor_quality_objectives, + infrastructure_limits, + long_term_corrected_level, + low_frequency_correction, + max_infrastructure_limit, + outdoor_quality_objectives, + round_reported_level, + tonal_correction, + total_correction, + vibration_quality_objective, +) + +__all__ = [ + "ACOUSTIC_AREA_TYPES", + "RD1367_CORRECTION_VALUES", + "RD1367_EVALUATION_PERIODS", + "RD1367_MAX_CORRECTION", + "RD1367_PERIOD_CLOCK_LIMITS", + "RD1367_PERIOD_HOURS", + "ActivityAssessment", + "EnvironmentalMeasurementWarning", + "ImpulseOnset", + "ImpulseProminenceResult", + "ImpulseProminenceWarning", + "ImpulsiveSoundResult", + "ImpulsiveSoundWarning", + "NoisePhase", + "PeriodAssessment", + "RegulationLimits", + "RepeatedMeasurementResult", + "ResidualCorrectionResult", + "TonalAssessmentResult", + "TonalCorrectionResult", + "activity_limits", + "adjacent_premises_limits", + "assess_activity", + "assess_tonal_audibility", + "combined_standard_uncertainty", + "composite_rating_level", + "corrected_level", + "critical_bandwidth", + "detect_onsets", + "evaluation_period_level", + "expanded_uncertainty", + "gaussian_residual_level", + "impulse_adjustment", + "impulse_prominence", + "impulsive_correction", + "impulsive_sound_adjustment", + "indoor_quality_objectives", + "infrastructure_limits", + "lden", + "ldn", + "long_term_corrected_level", + "low_frequency_correction", + "max_infrastructure_limit", + "outdoor_quality_objectives", + "predicted_prominence", + "rating_level", + "residual_correction_uncertainty", + "residual_sound_correction", + "round_reported_level", + "sound_pressure_level_history", + "tonal_adjustment", + "tonal_adjustment_from_mean_audibility", + "tonal_audibility", + "tonal_correction", + "tonal_seeking_survey", + "total_correction", + "uncertainty_from_repeated_measurements", + "vibration_quality_objective", +] diff --git a/src/phonometry/environmental/impulse_prominence.py b/src/phonometry/environment/assessment/impulse_prominence.py similarity index 97% rename from src/phonometry/environmental/impulse_prominence.py rename to src/phonometry/environment/assessment/impulse_prominence.py index 20565a922..b24c8b279 100644 --- a/src/phonometry/environmental/impulse_prominence.py +++ b/src/phonometry/environment/assessment/impulse_prominence.py @@ -37,12 +37,12 @@ import numpy as np -from .._internal.warnings import PhonometryWarning +from ..._internal.warnings import PhonometryWarning if TYPE_CHECKING: from matplotlib.axes import Axes - from .._report.metadata import ReportMetadata + from ..._report.metadata import ReportMetadata from numpy.typing import ArrayLike @@ -106,8 +106,8 @@ def plot(self, ax: Axes | None = None, *, language: str = "en", **kwargs: Any) - Requires matplotlib (``pip install phonometry[plot]``); returns the :class:`~matplotlib.axes.Axes`. """ - from .._i18n import check_language - from .._plot.environmental import plot_impulse_prominence + from ..._i18n import check_language + from ..._plot.environmental import plot_impulse_prominence return plot_impulse_prominence(self, ax=ax, language=check_language(language), **kwargs) @@ -151,14 +151,14 @@ def report( (``pip install phonometry[report]``), or matplotlib is missing for the embedded figure (``pip install phonometry[plot]``). """ - from .._i18n import check_language + from ..._i18n import check_language check_language(language) if engine != "reportlab": raise ValueError( f"Unknown report engine {engine!r}; only 'reportlab' is supported." ) - from .._report.iso1996_impulse import render_impulse_prominence_report + from ..._report.iso1996_impulse import render_impulse_prominence_report return render_impulse_prominence_report( self, diff --git a/src/phonometry/environmental/impulsive_sound.py b/src/phonometry/environment/assessment/impulsive_sound.py similarity index 98% rename from src/phonometry/environmental/impulsive_sound.py rename to src/phonometry/environment/assessment/impulsive_sound.py index 1c16f7ccb..938768034 100644 --- a/src/phonometry/environmental/impulsive_sound.py +++ b/src/phonometry/environment/assessment/impulsive_sound.py @@ -6,7 +6,7 @@ ISO/PAS 1996-3 objectively categorises a source by how prominently its impulsive sound is perceived and derives an adjustment ``KI`` (typically in the range 0.0 dB to 9.0 dB) that is added to ``LAeq``. Unlike the closed-form -:mod:`~phonometry.environmental.impulse_prominence` helpers (NT ACOU 112, which +:mod:`~phonometry.environment.assessment.impulse_prominence` helpers (NT ACOU 112, which take the onset rate and level difference as inputs), this module implements the *objective measurement chain* that reads those quantities directly from a calibrated time signal. @@ -35,7 +35,7 @@ The prominence and adjustment formulae are shared with NT ACOU 112 (both derive from Pedersen's method) and are reused from -:mod:`~phonometry.environmental.impulse_prominence`. The method for determining +:mod:`~phonometry.environment.assessment.impulse_prominence`. The method for determining ``KI`` is not sensitive to the absolute calibration of the equipment (Clause 8): onset rate and level difference are level *differences*, so the adjustment is unchanged by a constant offset. Only the reported ``LAeq`` and the @@ -51,7 +51,7 @@ import numpy as np from numpy.typing import ArrayLike -from .._internal.warnings import PhonometryWarning +from ..._internal.warnings import PhonometryWarning from .impulse_prominence import impulse_adjustment, predicted_prominence if TYPE_CHECKING: @@ -204,7 +204,7 @@ def sound_pressure_level_history( slightly from ``dt`` because it is an integer number of samples. :raises ValueError: for a non-positive ``fs`` or ``dt`` outside 10-25 ms. """ - from ..filters.weighting import time_weighting, weighting_filter + from ...filters.weighting import time_weighting, weighting_filter x = np.asarray(signal, dtype=np.float64).ravel() if fs <= 0.0: @@ -240,7 +240,7 @@ def _equivalent_level( signal: np.ndarray, fs: float, reference_pressure: float, calibration_offset: float ) -> float: """A-weighted equivalent continuous level ``LAeq`` of the interval, in dB.""" - from ..filters.weighting import weighting_filter + from ...filters.weighting import weighting_filter weighted = np.asarray(weighting_filter(signal, round(fs), curve="A"), dtype=np.float64) mean_square = float(np.mean(weighted**2)) @@ -513,7 +513,7 @@ def _plot_impulsive_sound( language: str = "en", **kwargs: Any, ) -> Axes: - from .._i18n import check_language + from ..._i18n import check_language lang = check_language(language) labels = _PLOT_LABELS[lang] diff --git a/src/phonometry/environmental/measurement.py b/src/phonometry/environment/assessment/measurement.py similarity index 99% rename from src/phonometry/environmental/measurement.py rename to src/phonometry/environment/assessment/measurement.py index 2b5d4a07a..093fd11d9 100644 --- a/src/phonometry/environmental/measurement.py +++ b/src/phonometry/environment/assessment/measurement.py @@ -3,7 +3,7 @@ Determination of environmental-noise sound pressure levels (ISO 1996-2:2017). The measurement companion of the ISO 1996-1 descriptors in -:mod:`phonometry.environmental`. ISO 1996-2 covers *how* the levels that feed +:mod:`phonometry.environment`. ISO 1996-2 covers *how* the levels that feed those descriptors are obtained: the tonal adjustment for prominent tones, the residual-noise correction, and the measurement-uncertainty budget. @@ -54,7 +54,7 @@ import numpy as np -from .._internal.warnings import PhonometryWarning +from ..._internal.warnings import PhonometryWarning if TYPE_CHECKING: from matplotlib.axes import Axes @@ -191,8 +191,8 @@ class TonalAssessmentResult: def plot(self, ax: Axes | None = None, *, language: str = "en", **kwargs: Any) -> Axes: """Plot the ``Kt(ΔLta)`` adjustment curve with this tone marked.""" - from .._i18n import check_language - from .._plot.environmental import plot_tonal_adjustment + from ..._i18n import check_language + from ..._plot.environmental import plot_tonal_adjustment return plot_tonal_adjustment(self, ax=ax, language=check_language(language), **kwargs) diff --git a/src/phonometry/environmental/rating.py b/src/phonometry/environment/assessment/rating.py similarity index 100% rename from src/phonometry/environmental/rating.py rename to src/phonometry/environment/assessment/rating.py diff --git a/src/phonometry/environmental/spanish_regulation.py b/src/phonometry/environment/assessment/spain.py similarity index 98% rename from src/phonometry/environmental/spanish_regulation.py rename to src/phonometry/environment/assessment/spain.py index 1e0af56f1..bf1e493df 100644 --- a/src/phonometry/environmental/spanish_regulation.py +++ b/src/phonometry/environment/assessment/spain.py @@ -30,17 +30,17 @@ *relatives, not the same procedure*, so this module implements the RD's own variants rather than delegating: -* :func:`~phonometry.environmental.measurement.tonal_audibility` / - :func:`~phonometry.environmental.measurement.tonal_adjustment` are the +* :func:`~phonometry.environment.assessment.measurement.tonal_audibility` / + :func:`~phonometry.environment.assessment.measurement.tonal_adjustment` are the ISO 1996-2 Annex C engineering method: a critical-band audibility ``ΔLta`` mapped to a *continuous* ``Kt`` in 0 to 6 dB. The RD works on one-third-octave band differences and yields 0/3/6 dB only. - :func:`~phonometry.environmental.measurement.tonal_seeking_survey` is the + :func:`~phonometry.environment.assessment.measurement.tonal_seeking_survey` is the closest relative (ISO 1996-2:2017 Annex K also splits the spectrum at 125/400 Hz), but it requires the band to exceed *both* neighbours by 15/8/5 dB and returns a boolean flag, whereas the RD compares against the *mean* of the neighbours with 8/5/3 dB thresholds and grades the result. -* :func:`~phonometry.environmental.impulsive_sound.impulsive_sound_adjustment` +* :func:`~phonometry.environment.assessment.impulsive_sound.impulsive_sound_adjustment` is the ISO/PAS 1996-3 onset-rate method on a calibrated time signal. The RD's ``Ki`` is the classic :math:`L_{AIeq} - L_{Aeq}` impulse-vs-fast difference read @@ -90,7 +90,7 @@ if TYPE_CHECKING: from matplotlib.axes import Axes - from .._report.metadata import ReportMetadata + from ..._report.metadata import ReportMetadata __all__ = [ "ACOUSTIC_AREA_TYPES", @@ -366,8 +366,8 @@ class TonalCorrectionResult: def plot(self, ax: Axes | None = None, *, language: str = "en", **kwargs: Any) -> Axes: """Plot the band spectrum with the emergent-tone differences ``Lt``.""" - from .._i18n import check_language - from .._plot.environmental import plot_tonal_correction_rd1367 + from ..._i18n import check_language + from ..._plot.environmental import plot_tonal_correction_rd1367 return plot_tonal_correction_rd1367( self, ax=ax, language=check_language(language), **kwargs @@ -405,7 +405,7 @@ def tonal_correction( .. note:: This is *not* the ISO 1996-2 tonal adjustment. The closest ISO relative is the Annex K survey method - (:func:`~phonometry.environmental.measurement.tonal_seeking_survey`), + (:func:`~phonometry.environment.assessment.measurement.tonal_seeking_survey`), which compares the band against *both* neighbours with 15/8/5 dB thresholds and only flags prominence; the RD compares against their arithmetic mean with 8/5/3 dB thresholds and grades the result 0/3/6 dB. @@ -522,7 +522,7 @@ def impulsive_correction(laieq: float, laeq: float) -> float: .. note:: This is the classic sound-level-meter route. The onset-rate method of - :func:`~phonometry.environmental.impulsive_sound.impulsive_sound_adjustment` + :func:`~phonometry.environment.assessment.impulsive_sound.impulsive_sound_adjustment` (ISO/PAS 1996-3) is a different, signal-based procedure and its ``KI`` is not interchangeable with this ``Ki``. The same misprint as in :func:`low_frequency_correction` affects the 3 dB row of the printed @@ -1044,8 +1044,8 @@ def complies(self) -> bool: def plot(self, ax: Axes | None = None, *, language: str = "en", **kwargs: Any) -> Axes: """Plot the per-period indices against their RD 1367/2007 limits.""" - from .._i18n import check_language - from .._plot.environmental import plot_activity_assessment + from ..._i18n import check_language + from ..._plot.environmental import plot_activity_assessment return plot_activity_assessment( self, ax=ax, language=check_language(language), **kwargs @@ -1077,8 +1077,8 @@ def report( :raises ImportError: If reportlab (or, for the figure, matplotlib) is not installed. """ - from .._i18n import check_language - from .._report.rd1367 import render_activity_report + from ..._i18n import check_language + from ..._report.rd1367 import render_activity_report return render_activity_report( self, diff --git a/src/phonometry/environment/propagation/__init__.py b/src/phonometry/environment/propagation/__init__.py new file mode 100644 index 000000000..f4cdc90e6 --- /dev/null +++ b/src/phonometry/environment/propagation/__init__.py @@ -0,0 +1,84 @@ +# Copyright (c) 2026. Jose Manuel Requena Plens +"""environment.propagation subdomain of phonometry: outdoor sound propagation (ISO 9613, refraction, barriers).""" + +from __future__ import annotations + +from .air_absorption import ( + AtmosphericAbsorptionWarning, + AtmosphericAttenuation, + air_attenuation, + air_attenuation_m, + atmospheric_attenuation, +) +from .ground_barriers import ( + BarrierInsertionLoss, + SphericalGroundResult, + barrier_insertion_loss, + fresnel_number, + ground_effect, + kurze_anderson_attenuation, + spherical_reflection_coefficient, +) +from .outdoor_propagation import ( + DEFAULT_FREQUENCIES, + Barrier, + OutdoorAttenuation, + SourceEmission, + atmospheric_absorption, + barrier_attenuation, + directivity_omega, + geometric_divergence, + ground_attenuation, + ground_attenuation_alternative, + meteorological_correction, + outdoor_propagation_attenuation, + predicted_receiver_level, +) +from .refraction import ( + AtmosphericPEResult, + AtmosphericRayResult, + EffectiveSoundSpeedProfile, + atmospheric_parabolic_equation, + atmospheric_ray_paths, + linear_sound_speed_profile, + log_linear_sound_speed_profile, + ray_curvature_radius, + shadow_zone_distance, +) + +__all__ = [ + "DEFAULT_FREQUENCIES", + "AtmosphericAbsorptionWarning", + "AtmosphericAttenuation", + "AtmosphericPEResult", + "AtmosphericRayResult", + "Barrier", + "BarrierInsertionLoss", + "EffectiveSoundSpeedProfile", + "OutdoorAttenuation", + "SourceEmission", + "SphericalGroundResult", + "air_attenuation", + "air_attenuation_m", + "atmospheric_absorption", + "atmospheric_attenuation", + "atmospheric_parabolic_equation", + "atmospheric_ray_paths", + "barrier_attenuation", + "barrier_insertion_loss", + "directivity_omega", + "fresnel_number", + "geometric_divergence", + "ground_attenuation", + "ground_attenuation_alternative", + "ground_effect", + "kurze_anderson_attenuation", + "linear_sound_speed_profile", + "log_linear_sound_speed_profile", + "meteorological_correction", + "outdoor_propagation_attenuation", + "predicted_receiver_level", + "ray_curvature_radius", + "shadow_zone_distance", + "spherical_reflection_coefficient", +] diff --git a/src/phonometry/environmental/air_absorption.py b/src/phonometry/environment/propagation/air_absorption.py similarity index 98% rename from src/phonometry/environmental/air_absorption.py rename to src/phonometry/environment/propagation/air_absorption.py index 9b81d7747..6567f737d 100644 --- a/src/phonometry/environmental/air_absorption.py +++ b/src/phonometry/environment/propagation/air_absorption.py @@ -74,8 +74,8 @@ import numpy as np from numpy.typing import ArrayLike, NDArray -from .._internal.warnings import PhonometryWarning -from ..materials.sound_absorption import attenuation_from_alpha +from ..._internal.warnings import PhonometryWarning +from ...materials.sound_absorption import attenuation_from_alpha if TYPE_CHECKING: # pragma: no cover - typing only from matplotlib.axes import Axes @@ -369,8 +369,8 @@ def plot( :param kwargs: Forwarded to the ``alpha`` curve ``plot`` call. :return: The axes. """ - from .._i18n import check_language - from .._plot.environmental import plot_atmospheric_attenuation + from ..._i18n import check_language + from ..._plot.environmental import plot_atmospheric_attenuation check_language(language) return plot_atmospheric_attenuation(self, ax=ax, language=language, **kwargs) diff --git a/src/phonometry/environmental/ground_barriers.py b/src/phonometry/environment/propagation/ground_barriers.py similarity index 97% rename from src/phonometry/environmental/ground_barriers.py rename to src/phonometry/environment/propagation/ground_barriers.py index 778baa96e..3fea5e42d 100644 --- a/src/phonometry/environmental/ground_barriers.py +++ b/src/phonometry/environment/propagation/ground_barriers.py @@ -3,7 +3,7 @@ Spherical-wave ground effect and advanced barrier diffraction. This module extends the tabulated ground and barrier terms of ISO 9613-2 (see -:mod:`phonometry.environmental.outdoor_propagation`) with the underlying wave +:mod:`phonometry.environment.propagation.outdoor_propagation`) with the underlying wave acoustics: the spherical-wave reflection coefficient of a finite-impedance ground and the wave-theoretic diffraction of a screen, both in a homogeneous (non-refracting, non-turbulent) atmosphere. @@ -106,14 +106,14 @@ from numpy.typing import ArrayLike, NDArray from scipy.special import fresnel, wofz -from .._internal.validation import require_positive, require_positive_array -from ..materials.porous_absorber import delany_bazley, miki +from ..._internal.validation import require_positive, require_positive_array +from ...materials.porous_absorber import delany_bazley, miki if TYPE_CHECKING: from matplotlib.axes import Axes - from .._report.metadata import ReportMetadata - from ..materials.porous_absorber import PorousMediumResult + from ..._report.metadata import ReportMetadata + from ...materials.porous_absorber import PorousMediumResult #: Default speed of sound ``c`` in air, in m/s (matches the materials domain). _C_SOUND = 343.0 @@ -155,7 +155,7 @@ def _normalized_ground_impedance( scalar/array is taken as already :math:`e^{-i \omega t}` and passed through. """ - from ..materials.porous_absorber import PorousMediumResult + from ...materials.porous_absorber import PorousMediumResult if (impedance is None) == (flow_resistivity is None): raise ValueError( @@ -244,8 +244,8 @@ def plot(self, ax: Axes | None = None, *, language: str = "en", **kwargs: Any) - Requires matplotlib (``pip install phonometry[plot]``); returns the :class:`~matplotlib.axes.Axes`. """ - from .._i18n import check_language - from .._plot.environmental import plot_spherical_ground + from ..._i18n import check_language + from ..._plot.environmental import plot_spherical_ground return plot_spherical_ground(self, ax=ax, language=check_language(language), **kwargs) @@ -585,8 +585,8 @@ def plot(self, ax: Axes | None = None, *, language: str = "en", **kwargs: Any) - Requires matplotlib (``pip install phonometry[plot]``); returns the :class:`~matplotlib.axes.Axes`. """ - from .._i18n import check_language - from .._plot.environmental import plot_barrier_insertion_loss + from ..._i18n import check_language + from ..._plot.environmental import plot_barrier_insertion_loss return plot_barrier_insertion_loss(self, ax=ax, language=check_language(language), **kwargs) @@ -601,8 +601,8 @@ def plot_geometry( :raises ValueError: If the result does not retain its geometry. """ - from .._i18n import check_language - from .._plot.geometry import plot_barrier_result_geometry + from ..._i18n import check_language + from ..._plot.geometry import plot_barrier_result_geometry check_language(language) return plot_barrier_result_geometry( @@ -648,14 +648,14 @@ def report( :raises ImportError: If reportlab or matplotlib is not installed (``pip install "phonometry[report,plot]"``). """ - from .._i18n import check_language + from ..._i18n import check_language check_language(language) if engine != "reportlab": raise ValueError( f"Unknown report engine {engine!r}; only 'reportlab' is supported." ) - from .._report.iso9613 import render_barrier_insertion_loss_report + from ..._report.iso9613 import render_barrier_insertion_loss_report return render_barrier_insertion_loss_report( self, path, metadata=metadata, verbose=verbose, language=language diff --git a/src/phonometry/environmental/outdoor_propagation.py b/src/phonometry/environment/propagation/outdoor_propagation.py similarity index 99% rename from src/phonometry/environmental/outdoor_propagation.py rename to src/phonometry/environment/propagation/outdoor_propagation.py index facc98eec..dfc0f93d2 100644 --- a/src/phonometry/environmental/outdoor_propagation.py +++ b/src/phonometry/environment/propagation/outdoor_propagation.py @@ -25,7 +25,7 @@ * ``Adiv`` geometrical divergence, :math:`20 \log_{10}(d/d_0) + 11` (Eq. (7)); * ``Aatm`` atmospheric absorption, :math:`\alpha d` (Eq. (8)) with ``alpha`` - the ISO 9613-1 coefficient supplied by :mod:`phonometry.environmental.air_absorption`; + the ISO 9613-1 coefficient supplied by :mod:`phonometry.environment.propagation.air_absorption`; * ``Agr`` ground effect, both the general per-region method of 7.3.1 with the Table 3 functions ``a'/b'/c'/d'`` (Eq. (9)) and the alternative simplified method of 7.3.2 (Eq. (10)); @@ -50,13 +50,13 @@ import numpy as np from numpy.typing import ArrayLike, NDArray -from .._internal.warnings import _warn_renamed +from ..._internal.warnings import _warn_renamed from .air_absorption import air_attenuation if TYPE_CHECKING: from matplotlib.axes import Axes - from .._report.metadata import ReportMetadata + from ..._report.metadata import ReportMetadata #: Reference distance ``d0`` in the divergence term (ISO 9613-2:1996, Eq. (7)), m. _D0 = 1.0 @@ -199,8 +199,8 @@ def plot(self, ax: Axes | None = None, *, language: str = "en", **kwargs: Any) - Requires matplotlib (``pip install phonometry[plot]``); returns the :class:`~matplotlib.axes.Axes`. """ - from .._i18n import check_language - from .._plot.environmental import plot_outdoor_attenuation + from ..._i18n import check_language + from ..._plot.environmental import plot_outdoor_attenuation return plot_outdoor_attenuation(self, ax=ax, language=check_language(language), **kwargs) @@ -257,14 +257,14 @@ def report( :raises ImportError: If reportlab or matplotlib is not installed (``pip install "phonometry[report,plot]"``). """ - from .._i18n import check_language + from ..._i18n import check_language check_language(language) if engine != "reportlab": raise ValueError( f"Unknown report engine {engine!r}; only 'reportlab' is supported." ) - from .._report.iso9613 import render_outdoor_attenuation_report + from ..._report.iso9613 import render_outdoor_attenuation_report return render_outdoor_attenuation_report( self, path, metadata=metadata, verbose=verbose, language=language, diff --git a/src/phonometry/environmental/atmospheric_refraction.py b/src/phonometry/environment/propagation/refraction.py similarity index 98% rename from src/phonometry/environmental/atmospheric_refraction.py rename to src/phonometry/environment/propagation/refraction.py index 671a02d82..85d877b9d 100644 --- a/src/phonometry/environmental/atmospheric_refraction.py +++ b/src/phonometry/environment/propagation/refraction.py @@ -32,13 +32,13 @@ arcs of radius :func:`ray_curvature_radius`, and an upward-refracting linear profile has a closed-form :func:`shadow_zone_distance`; both anchor the ray model. The PE is anchored against the exact spherical-wave ground effect -(:func:`phonometry.environmental.ground_effect`) in the homogeneous limit +(:func:`phonometry.environment.ground_effect`) in the homogeneous limit (gradient zero), which it reproduces to a few tenths of a dB on the default grid (finer ``height_step`` converges it further). The ground impedance is taken in the :math:`e^{-i \omega t}` convention of Salomons (a passive ground has :math:`\operatorname{Im}(Z) > 0`), shared with -:mod:`phonometry.environmental.ground_barriers`. The porous models of +:mod:`phonometry.environment.propagation.ground_barriers`. The porous models of :mod:`phonometry.materials` work in the opposite :math:`e^{+j \omega t}` convention (:math:`\operatorname{Im}(Z) < 0`), so an impedance derived from them (``flow_resistivity=`` or a @@ -55,13 +55,13 @@ import numpy as np from numpy.typing import ArrayLike -from .._internal.validation import require_positive +from ..._internal.validation import require_positive if TYPE_CHECKING: from matplotlib.axes import Axes from numpy.typing import NDArray - from ..materials.porous_absorber import PorousMediumResult + from ...materials.porous_absorber import PorousMediumResult #: Default reference speed of sound ``c`` in air at the ground, in m/s (matches #: the environmental domain). @@ -99,8 +99,8 @@ def speed_at(self, height: ArrayLike) -> NDArray[np.float64]: def plot(self, ax: Axes | None = None, *, language: str = "en", **kwargs: Any) -> Axes: """Plot the effective sound-speed profile (height on the vertical axis).""" - from .._i18n import check_language - from .._plot.environmental import plot_sound_speed_profile + from ..._i18n import check_language + from ..._plot.environmental import plot_sound_speed_profile return plot_sound_speed_profile(self, ax=ax, language=check_language(language), **kwargs) @@ -320,8 +320,8 @@ class AtmosphericRayResult: def plot(self, ax: Axes | None = None, *, language: str = "en", **kwargs: Any) -> Axes: """Plot the curved ray paths (height on the vertical axis).""" - from .._i18n import check_language - from .._plot.environmental import plot_atmospheric_rays + from ..._i18n import check_language + from ..._plot.environmental import plot_atmospheric_rays return plot_atmospheric_rays(self, ax=ax, language=check_language(language), **kwargs) @@ -470,8 +470,8 @@ def level_at_height(self, height: float) -> NDArray[np.float64]: def plot(self, ax: Axes | None = None, *, language: str = "en", **kwargs: Any) -> Axes: """Plot the relative-level field over the range-height plane.""" - from .._i18n import check_language - from .._plot.environmental import plot_atmospheric_pe + from ..._i18n import check_language + from ..._plot.environmental import plot_atmospheric_pe return plot_atmospheric_pe(self, ax=ax, language=check_language(language), **kwargs) diff --git a/src/phonometry/environment/sources/__init__.py b/src/phonometry/environment/sources/__init__.py new file mode 100644 index 000000000..d6907611e --- /dev/null +++ b/src/phonometry/environment/sources/__init__.py @@ -0,0 +1,158 @@ +# Copyright (c) 2026. Jose Manuel Requena Plens +"""environment.sources subdomain of phonometry: environmental source emission (CNOSSOS, wind turbines).""" + +from __future__ import annotations + +from .cnossos_rail import ( + AERODYNAMIC_REFERENCE_SPEED, + AERODYNAMIC_THRESHOLD_SPEED, + RAILWAY_MINIMUM_SPEED, + RAILWAY_OCTAVE_BANDS, + RAILWAY_SOURCE_HEIGHTS, + RAILWAY_THIRD_OCTAVE_BANDS, + REFERENCE_JOINT_DENSITY, + TRAM_MINIMUM_SPEED, + BrakeType, + BridgeType, + ContactFilter, + DirectivityEdition, + RailPad, + RailRoughnessClass, + RailwayEmissionResult, + RailwayTrack, + RailwayVehicle, + RollingStock, + RoughnessInterpolation, + RunningCondition, + TrackBase, + TrackCurvature, + TrackDescriptor, + TrackTransferClass, + TractionVehicle, + VehicleDescriptor, + VehicleType, + WheelDiameter, + WheelMeasure, + aerodynamic_sound_power, + bridge_transfer, + contact_filter, + curve_squeal_excess, + horizontal_directivity, + impact_roughness, + impact_roughness_single, + octave_bands_from_third_octaves, + rail_roughness, + railway_source_power, + rolling_sound_power, + roughness_to_frequency, + superstructure_transfer, + total_effective_roughness, + track_transfer, + traction_sound_power, + vertical_directivity, + wheel_roughness, + wheel_transfer, +) +from .cnossos_road import ( + CNOSSOS_A_WEIGHTING, + ROAD_COEFFICIENTS, + ROAD_OCTAVE_BANDS, + ROAD_REFERENCE_SPEED, + ROAD_REFERENCE_TEMPERATURE, + ROAD_SOURCE_HEIGHT, + JunctionType, + RoadEmissionCoefficients, + RoadEmissionResult, + RoadSurface, + RoadSurfaceCoefficients, + RoadTraffic, + RoadVehicleCategory, + line_source_segment_power, + road_propulsion_noise, + road_rolling_noise, + road_source_power, + road_surface_coefficients, + road_vehicle_sound_power, +) +from .wind_turbine import ( + WindTurbineNoiseWarning, + WindTurbineTonalityResult, + apparent_sound_power_level, + slant_distance, + wind_turbine_tonality, +) + +__all__ = [ + "AERODYNAMIC_REFERENCE_SPEED", + "AERODYNAMIC_THRESHOLD_SPEED", + "CNOSSOS_A_WEIGHTING", + "RAILWAY_MINIMUM_SPEED", + "RAILWAY_OCTAVE_BANDS", + "RAILWAY_SOURCE_HEIGHTS", + "RAILWAY_THIRD_OCTAVE_BANDS", + "REFERENCE_JOINT_DENSITY", + "ROAD_COEFFICIENTS", + "ROAD_OCTAVE_BANDS", + "ROAD_REFERENCE_SPEED", + "ROAD_REFERENCE_TEMPERATURE", + "ROAD_SOURCE_HEIGHT", + "TRAM_MINIMUM_SPEED", + "BrakeType", + "BridgeType", + "ContactFilter", + "DirectivityEdition", + "JunctionType", + "RailPad", + "RailRoughnessClass", + "RailwayEmissionResult", + "RailwayTrack", + "RailwayVehicle", + "RoadEmissionCoefficients", + "RoadEmissionResult", + "RoadSurface", + "RoadSurfaceCoefficients", + "RoadTraffic", + "RoadVehicleCategory", + "RollingStock", + "RoughnessInterpolation", + "RunningCondition", + "TrackBase", + "TrackCurvature", + "TrackDescriptor", + "TrackTransferClass", + "TractionVehicle", + "VehicleDescriptor", + "VehicleType", + "WheelDiameter", + "WheelMeasure", + "WindTurbineNoiseWarning", + "WindTurbineTonalityResult", + "aerodynamic_sound_power", + "apparent_sound_power_level", + "bridge_transfer", + "contact_filter", + "curve_squeal_excess", + "horizontal_directivity", + "impact_roughness", + "impact_roughness_single", + "line_source_segment_power", + "octave_bands_from_third_octaves", + "rail_roughness", + "railway_source_power", + "road_propulsion_noise", + "road_rolling_noise", + "road_source_power", + "road_surface_coefficients", + "road_vehicle_sound_power", + "rolling_sound_power", + "roughness_to_frequency", + "slant_distance", + "superstructure_transfer", + "total_effective_roughness", + "track_transfer", + "traction_sound_power", + "vertical_directivity", + "wheel_roughness", + "wheel_transfer", + "wind_turbine_tonality", +] diff --git a/src/phonometry/environmental/cnossos_rail.py b/src/phonometry/environment/sources/cnossos_rail.py similarity index 99% rename from src/phonometry/environmental/cnossos_rail.py rename to src/phonometry/environment/sources/cnossos_rail.py index 39eae8722..37851a6c5 100644 --- a/src/phonometry/environmental/cnossos_rail.py +++ b/src/phonometry/environment/sources/cnossos_rail.py @@ -67,7 +67,7 @@ This is the **emission** stage only. Splitting a source line into equivalent point sources is explicitly outside the scope of the method (2.5.3), and the CNOSSOS propagation model is not ISO 9613-2, so the hand-off to -:mod:`~phonometry.environmental.outdoor_propagation` mixes two methods and is a +:mod:`~phonometry.environment.propagation.outdoor_propagation` mixes two methods and is a convenience, not a normative chain. """ @@ -1299,7 +1299,7 @@ class RollingStock: Every field is the spectrum the method needs, so a Member State substitutes its own database simply by building this object from its own tables rather - than from the :mod:`~phonometry.environmental.cnossos_rail` look-ups. + than from the :mod:`~phonometry.environment.sources.cnossos_rail` look-ups. :ivar axles: Number of axles per vehicle ``N_a``. :ivar wheel_roughness: ``(wavelengths in mm, levels in dB)`` of Table G-1a. @@ -1401,8 +1401,8 @@ class RailwayEmissionResult: def plot(self, ax: Axes | None = None, *, language: str = "en", **kwargs: Any) -> Axes: """Plot the per-metre line power of the two equivalent source heights.""" - from .._i18n import check_language - from .._plot.environmental import plot_cnossos_rail_emission + from ..._i18n import check_language + from ..._plot.environmental import plot_cnossos_rail_emission return plot_cnossos_rail_emission( self, ax=ax, language=check_language(language), **kwargs diff --git a/src/phonometry/environmental/cnossos_road.py b/src/phonometry/environment/sources/cnossos_road.py similarity index 99% rename from src/phonometry/environmental/cnossos_road.py rename to src/phonometry/environment/sources/cnossos_road.py index 3cd9fedc9..5aa8648e3 100644 --- a/src/phonometry/environmental/cnossos_road.py +++ b/src/phonometry/environment/sources/cnossos_road.py @@ -43,7 +43,7 @@ explicitly outside the scope of the method (2.5.3), so :func:`line_source_segment_power` is offered as plain arithmetic, clearly labelled as such. The CNOSSOS propagation model itself is *not* ISO 9613-2; the -hand-off to :func:`~phonometry.environmental.outdoor_propagation.predicted_receiver_level` +hand-off to :func:`~phonometry.environment.propagation.outdoor_propagation.predicted_receiver_level` mixes two methods and is a convenience, not a normative chain. """ @@ -810,8 +810,8 @@ def a_weighted_line_power(self) -> float: def plot(self, ax: Axes | None = None, *, language: str = "en", **kwargs: Any) -> Axes: """Plot the per-metre line power spectrum and its category breakdown.""" - from .._i18n import check_language - from .._plot.environmental import plot_cnossos_road_emission + from ..._i18n import check_language + from ..._plot.environmental import plot_cnossos_road_emission return plot_cnossos_road_emission( self, ax=ax, language=check_language(language), **kwargs diff --git a/src/phonometry/environmental/wind_turbine_noise.py b/src/phonometry/environment/sources/wind_turbine.py similarity index 97% rename from src/phonometry/environmental/wind_turbine_noise.py rename to src/phonometry/environment/sources/wind_turbine.py index 527db77cf..dc79eb71a 100644 --- a/src/phonometry/environmental/wind_turbine_noise.py +++ b/src/phonometry/environment/sources/wind_turbine.py @@ -15,7 +15,7 @@ ``ΔL_a`` that decides whether a tone is audible. The tonal-audibility formula itself is the ISO 1996-2 Annex C one already in -:mod:`phonometry.environmental.measurement`; what is specific to IEC 61400-11 is +:mod:`phonometry.environment.assessment.measurement`; what is specific to IEC 61400-11 is how the tone and masking-noise levels and the (Zwicker) critical band are determined from the narrowband spectrum. The rating adjustment ``K_T`` is the ISO 1996-2 :func:`~phonometry.environmental_measurement.tonal_adjustment`. The @@ -31,13 +31,13 @@ import numpy as np -from .._internal.warnings import PhonometryWarning +from ..._internal.warnings import PhonometryWarning if TYPE_CHECKING: from matplotlib.axes import Axes from numpy.typing import NDArray - from .._report.metadata import ReportMetadata + from ..._report.metadata import ReportMetadata class WindTurbineNoiseWarning(PhonometryWarning): @@ -109,7 +109,7 @@ def apparent_sound_power_level( asterisk marking and the <= 3 dB not-reported rule, subclause 9.3) is out of scope here and must be applied beforehand; note its rule set differs from the ISO 1996-2 correction in - :func:`phonometry.environmental.measurement.residual_sound_correction`. + :func:`phonometry.environment.assessment.measurement.residual_sound_correction`. :param r1: Slant distance ``R1`` to the rotor centre, in m. :return: The apparent sound power level ``L_WA``, in dB re 1 pW. :raises ValueError: If the inputs are invalid. @@ -213,8 +213,8 @@ class WindTurbineTonalityResult: def plot(self, ax: Axes | None = None, *, language: str = "en", **kwargs: Any) -> Axes: """Plot the narrowband spectrum with the critical band and masking level.""" - from .._i18n import check_language - from .._plot.environmental import plot_wind_turbine_tonality + from ..._i18n import check_language + from ..._plot.environmental import plot_wind_turbine_tonality return plot_wind_turbine_tonality(self, ax=ax, language=check_language(language), **kwargs) @@ -260,14 +260,14 @@ def report( (``pip install phonometry[report]``), or matplotlib is missing for the embedded figure (``pip install phonometry[plot]``). """ - from .._i18n import check_language + from ..._i18n import check_language check_language(language) if engine != "reportlab": raise ValueError( f"Unknown report engine {engine!r}; only 'reportlab' is supported." ) - from .._report.iec61400 import render_wind_turbine_tonality_report + from ..._report.iec61400 import render_wind_turbine_tonality_report return render_wind_turbine_tonality_report( self, path, metadata=metadata, verbose=verbose, language=language diff --git a/src/phonometry/psychoacoustics/tone_audibility.py b/src/phonometry/psychoacoustics/tone_audibility.py index bfe912a09..1fe9b52c6 100644 --- a/src/phonometry/psychoacoustics/tone_audibility.py +++ b/src/phonometry/psychoacoustics/tone_audibility.py @@ -4,7 +4,7 @@ ISO/PAS 20065 is the detailed engineering method that ISO 1996-2:2017 defers to for the audibility of prominent tones; the simplified 2007/2009 Annex C method -lives in :mod:`phonometry.environmental.measurement`. The audibility of a tone +lives in :mod:`phonometry.environment.assessment.measurement`. The audibility of a tone is the amount, in decibels, by which its tone level rises above the masking threshold of the surrounding noise. @@ -153,7 +153,7 @@ def critical_bandwidth_engineering(tone_frequency: float) -> float: :math:`\Delta f_c = 25.0 + 75.0 (1.0 + 1.4 (f_T/1000)^2)^{0.69}` Hz. This is the continuous ISO/PAS 20065 engineering-method bandwidth, distinct from the - stepped ISO 1996-2 Annex C :func:`~phonometry.environmental.measurement. + stepped ISO 1996-2 Annex C :func:`~phonometry.environment.assessment.measurement. critical_bandwidth` (100 Hz / 20 %). :param tone_frequency: Tone frequency ``fT``, in Hz. @@ -1169,7 +1169,7 @@ def report( # Module named ``tone_audibility`` (distinct from the ISO 1996-2 Annex C -# ``tonal_audibility`` in :mod:`phonometry.environmental.measurement`). +# ``tonal_audibility`` in :mod:`phonometry.environment.assessment.measurement`). def tone_audibility( diff --git a/tests/aircraft/test_aircraft_atmospheric_absorption.py b/tests/aircraft/test_aircraft_atmospheric_absorption.py index c37f17d28..035fa825f 100644 --- a/tests/aircraft/test_aircraft_atmospheric_absorption.py +++ b/tests/aircraft/test_aircraft_atmospheric_absorption.py @@ -19,7 +19,7 @@ AircraftBandAttenuation, sae_band_attenuation, ) -from phonometry.environmental.air_absorption import air_attenuation +from phonometry.environment.propagation.air_absorption import air_attenuation # Published SAE-Method constants (ARP 5534 §3.2.2), for the independent oracle. _A, _B, _C, _D, _E, _F, _G = 0.867942, 0.111761, 0.95824, 0.008191, 1.6, 9.2, 0.765 diff --git a/tests/aircraft/test_rotorcraft_noise.py b/tests/aircraft/test_rotorcraft_noise.py index e4f81b13e..82bc121a2 100644 --- a/tests/aircraft/test_rotorcraft_noise.py +++ b/tests/aircraft/test_rotorcraft_noise.py @@ -75,7 +75,9 @@ def test_atmospheric_low_bands_do_not_warn() -> None: assert np.all(la <= 0.0) # Above the 10 kHz top of the NORAH grid alpha is large and extrapolated: # the advisory warning must still propagate there. - from phonometry.environmental.air_absorption import AtmosphericAbsorptionWarning + from phonometry.environment.propagation.air_absorption import ( + AtmosphericAbsorptionWarning, + ) with pytest.warns(AtmosphericAbsorptionWarning): atmospheric_adjustment([16000.0], 1060.0) diff --git a/tests/environmental/test_impulse_prominence.py b/tests/environment/assessment/test_impulse_prominence.py similarity index 97% rename from tests/environmental/test_impulse_prominence.py rename to tests/environment/assessment/test_impulse_prominence.py index ba40c3713..70b947f3d 100644 --- a/tests/environmental/test_impulse_prominence.py +++ b/tests/environment/assessment/test_impulse_prominence.py @@ -1,5 +1,5 @@ # Copyright (c) 2026. Jose Manuel Requena Plens -"""Tests for :mod:`phonometry.environmental.impulse_prominence` (prominence of impulsive sounds). +"""Tests for :mod:`phonometry.environment.assessment.impulse_prominence` (prominence of impulsive sounds). Validated against the formulae of NT ACOU 112:2002: the predicted prominence ``P = 3*lg(onset_rate) + 2*lg(level_difference)`` (clause 7, Formula 1), the @@ -18,7 +18,7 @@ # The module is shadowed in the package namespace by the function of the same # name, so it must be imported through the import system directly. -nt = importlib.import_module("phonometry.environmental.impulse_prominence") +nt = importlib.import_module("phonometry.environment.assessment.impulse_prominence") def test_predicted_prominence_formula_1() -> None: diff --git a/tests/environmental/test_impulse_prominence_report.py b/tests/environment/assessment/test_impulse_prominence_report.py similarity index 99% rename from tests/environmental/test_impulse_prominence_report.py rename to tests/environment/assessment/test_impulse_prominence_report.py index 1f4cb5beb..05a26bc8d 100644 --- a/tests/environmental/test_impulse_prominence_report.py +++ b/tests/environment/assessment/test_impulse_prominence_report.py @@ -26,7 +26,7 @@ # The module is shadowed in the package namespace by the function of the same # name, so it must be imported through the import system directly. -nt = importlib.import_module("phonometry.environmental.impulse_prominence") +nt = importlib.import_module("phonometry.environment.assessment.impulse_prominence") _PDF_MAGIC = b"%PDF" diff --git a/tests/environmental/test_impulsive_sound.py b/tests/environment/assessment/test_impulsive_sound.py similarity index 98% rename from tests/environmental/test_impulsive_sound.py rename to tests/environment/assessment/test_impulsive_sound.py index 0a329be88..739710787 100644 --- a/tests/environmental/test_impulsive_sound.py +++ b/tests/environment/assessment/test_impulsive_sound.py @@ -1,5 +1,5 @@ # Copyright (c) 2026. Jose Manuel Requena Plens -"""Tests for :mod:`phonometry.environmental.impulsive_sound` (ISO/PAS 1996-3:2022). +"""Tests for :mod:`phonometry.environment.assessment.impulsive_sound` (ISO/PAS 1996-3:2022). ISO/PAS 1996-3 gives no worked numeric example, so the oracle is derived by hand from the standard's own definitions and reproduced here (clean-room from @@ -40,7 +40,7 @@ ISO1996_3_RAMP_PROMINENCE, ) -iso = importlib.import_module("phonometry.environmental.impulsive_sound") +iso = importlib.import_module("phonometry.environment.assessment.impulsive_sound") # --------------------------------------------------------------------------- diff --git a/tests/environmental/test_environmental_measurement.py b/tests/environment/assessment/test_measurement.py similarity index 99% rename from tests/environmental/test_environmental_measurement.py rename to tests/environment/assessment/test_measurement.py index a3622070c..65f79547c 100644 --- a/tests/environmental/test_environmental_measurement.py +++ b/tests/environment/assessment/test_measurement.py @@ -27,7 +27,9 @@ tonal_seeking_survey, uncertainty_from_repeated_measurements, ) -from phonometry.environmental.measurement import EnvironmentalMeasurementWarning +from phonometry.environment.assessment.measurement import ( + EnvironmentalMeasurementWarning, +) # --------------------------------------------------------------------------- diff --git a/tests/environmental/test_environmental.py b/tests/environment/assessment/test_rating.py similarity index 100% rename from tests/environmental/test_environmental.py rename to tests/environment/assessment/test_rating.py diff --git a/tests/environmental/test_rd1367_report.py b/tests/environment/assessment/test_rd1367_report.py similarity index 99% rename from tests/environmental/test_rd1367_report.py rename to tests/environment/assessment/test_rd1367_report.py index e10338837..9b6d7b7d7 100644 --- a/tests/environmental/test_rd1367_report.py +++ b/tests/environment/assessment/test_rd1367_report.py @@ -23,7 +23,7 @@ pytest.importorskip("reportlab") from phonometry import ReportMetadata -from phonometry.environmental import spanish_regulation as rd +from phonometry.environment.assessment import spain as rd _PDF_MAGIC = b"%PDF" diff --git a/tests/environmental/test_spanish_regulation.py b/tests/environment/assessment/test_spain.py similarity index 99% rename from tests/environmental/test_spanish_regulation.py rename to tests/environment/assessment/test_spain.py index 5c22d9284..d399f418c 100644 --- a/tests/environmental/test_spanish_regulation.py +++ b/tests/environment/assessment/test_spain.py @@ -1,5 +1,5 @@ # Copyright (c) 2026. Jose Manuel Requena Plens -"""Tests for :mod:`phonometry.environmental.spanish_regulation` (RD 1367/2007). +"""Tests for :mod:`phonometry.environment.assessment.spain` (RD 1367/2007). Two independent oracle families anchor this module. @@ -27,7 +27,7 @@ import numpy as np import pytest -from phonometry.environmental import spanish_regulation as rd +from phonometry.environment.assessment import spain as rd # --------------------------------------------------------------------------- # # Manual Ejemplo 3.1: the two measured noise phases diff --git a/tests/environmental/test_air_absorption.py b/tests/environment/propagation/test_air_absorption.py similarity index 99% rename from tests/environmental/test_air_absorption.py rename to tests/environment/propagation/test_air_absorption.py index 83ae9b328..5d33cc6f4 100644 --- a/tests/environmental/test_air_absorption.py +++ b/tests/environment/propagation/test_air_absorption.py @@ -71,7 +71,7 @@ def test_table1_agreement_is_tight() -> None: # --- Reference conditions (clause 4.2 / Annex B) ---------------------------- def test_reference_condition_constants() -> None: - from phonometry.environmental.air_absorption import _PR, _T0, _T01 + from phonometry.environment.propagation.air_absorption import _PR, _T0, _T01 assert _T0 == 293.15 # 20 degC assert _PR == 101.325 # one standard atmosphere, kPa diff --git a/tests/environmental/test_ground_barriers.py b/tests/environment/propagation/test_ground_barriers.py similarity index 100% rename from tests/environmental/test_ground_barriers.py rename to tests/environment/propagation/test_ground_barriers.py diff --git a/tests/environmental/test_outdoor_propagation.py b/tests/environment/propagation/test_outdoor_propagation.py similarity index 99% rename from tests/environmental/test_outdoor_propagation.py rename to tests/environment/propagation/test_outdoor_propagation.py index 5fdfd74d0..eb9acb217 100644 --- a/tests/environmental/test_outdoor_propagation.py +++ b/tests/environment/propagation/test_outdoor_propagation.py @@ -21,7 +21,7 @@ Primary oracles: the exact closed forms above (Adiv digit-exact points, the Table 3 function limits, the barrier caps and C3 transition) and consistency of -Aatm with ``phonometry.environmental.air_absorption``. +Aatm with ``phonometry.environment.propagation.air_absorption``. """ from __future__ import annotations @@ -265,7 +265,7 @@ def test_deep_clearance_tends_to_zero(self) -> None: def test_c3_transition_single_to_triple(self) -> None: # Eq. (15): e -> 0 gives C3 = 1 (matches single); e >> lambda gives C3 -> 3. - from phonometry.environmental.outdoor_propagation import _c3_double + from phonometry.environment.propagation.outdoor_propagation import _c3_double lam = 340.0 / 500.0 assert _c3_double(lam, 1e-6) == pytest.approx(1.0, abs=1e-3) diff --git a/tests/environmental/test_outdoor_propagation_report.py b/tests/environment/propagation/test_outdoor_propagation_report.py similarity index 100% rename from tests/environmental/test_outdoor_propagation_report.py rename to tests/environment/propagation/test_outdoor_propagation_report.py diff --git a/tests/environmental/test_atmospheric_refraction.py b/tests/environment/propagation/test_refraction.py similarity index 99% rename from tests/environmental/test_atmospheric_refraction.py rename to tests/environment/propagation/test_refraction.py index 9baa8296c..5ea874bb1 100644 --- a/tests/environmental/test_atmospheric_refraction.py +++ b/tests/environment/propagation/test_refraction.py @@ -30,7 +30,8 @@ import numpy as np import pytest -from phonometry.environmental.atmospheric_refraction import ( +from phonometry.environment.propagation.ground_barriers import ground_effect +from phonometry.environment.propagation.refraction import ( AtmosphericPEResult, AtmosphericRayResult, EffectiveSoundSpeedProfile, @@ -41,7 +42,6 @@ ray_curvature_radius, shadow_zone_distance, ) -from phonometry.environmental.ground_barriers import ground_effect C0 = 343.0 # A representative grassland surface impedance (normalized, e^{-i omega t}: diff --git a/tests/environmental/test_cnossos_rail.py b/tests/environment/sources/test_cnossos_rail.py similarity index 99% rename from tests/environmental/test_cnossos_rail.py rename to tests/environment/sources/test_cnossos_rail.py index 0070fccf3..bbb60b082 100644 --- a/tests/environmental/test_cnossos_rail.py +++ b/tests/environment/sources/test_cnossos_rail.py @@ -35,7 +35,7 @@ import pytest import reference_data as ref -from phonometry.environmental.cnossos_rail import ( +from phonometry.environment.sources.cnossos_rail import ( AERODYNAMIC_REFERENCE_SPEED, RAILWAY_MINIMUM_SPEED, RAILWAY_OCTAVE_BANDS, diff --git a/tests/environmental/test_cnossos_road.py b/tests/environment/sources/test_cnossos_road.py similarity index 99% rename from tests/environmental/test_cnossos_road.py rename to tests/environment/sources/test_cnossos_road.py index 3823f7284..eb0636df3 100644 --- a/tests/environmental/test_cnossos_road.py +++ b/tests/environment/sources/test_cnossos_road.py @@ -37,7 +37,7 @@ import pytest import reference_data as ref -from phonometry.environmental.cnossos_road import ( +from phonometry.environment.sources.cnossos_road import ( CNOSSOS_A_WEIGHTING, ROAD_COEFFICIENTS, ROAD_OCTAVE_BANDS, @@ -556,7 +556,9 @@ def test_segment_power_is_the_line_power_plus_ten_log_length() -> None: def test_segment_power_feeds_the_propagation_stage() -> None: """The emission output is an octave-band ``L_W`` on the 63 Hz - 8 kHz grid.""" - from phonometry.environmental.outdoor_propagation import predicted_receiver_level + from phonometry.environment.propagation.outdoor_propagation import ( + predicted_receiver_level, + ) result = road_source_power( [ diff --git a/tests/environmental/test_wind_turbine_noise.py b/tests/environment/sources/test_wind_turbine.py similarity index 99% rename from tests/environmental/test_wind_turbine_noise.py rename to tests/environment/sources/test_wind_turbine.py index b9821ad04..bdbe60fcd 100644 --- a/tests/environmental/test_wind_turbine_noise.py +++ b/tests/environment/sources/test_wind_turbine.py @@ -14,7 +14,7 @@ import numpy as np import pytest -from phonometry.environmental.wind_turbine_noise import ( +from phonometry.environment.sources.wind_turbine import ( WindTurbineNoiseWarning, apparent_sound_power_level, critical_bandwidth, diff --git a/tests/environmental/test_wind_turbine_tonality_report.py b/tests/environment/sources/test_wind_turbine_tonality_report.py similarity index 99% rename from tests/environmental/test_wind_turbine_tonality_report.py rename to tests/environment/sources/test_wind_turbine_tonality_report.py index b5e37ed5f..ac2d5b24a 100644 --- a/tests/environmental/test_wind_turbine_tonality_report.py +++ b/tests/environment/sources/test_wind_turbine_tonality_report.py @@ -20,7 +20,7 @@ pytest.importorskip("reportlab") from phonometry import ReportMetadata -from phonometry.environmental.wind_turbine_noise import ( +from phonometry.environment.sources.wind_turbine import ( wind_turbine_tonality, ) diff --git a/tests/environmental/test_environmental_plot_i18n.py b/tests/environment/test_environment_plot_i18n.py similarity index 92% rename from tests/environmental/test_environmental_plot_i18n.py rename to tests/environment/test_environment_plot_i18n.py index e99c91e84..b7d8e2c66 100644 --- a/tests/environmental/test_environmental_plot_i18n.py +++ b/tests/environment/test_environment_plot_i18n.py @@ -10,11 +10,11 @@ import numpy as np import pytest -from phonometry.environmental.ground_barriers import ground_effect -from phonometry.environmental.outdoor_propagation import ( +from phonometry.environment.propagation.ground_barriers import ground_effect +from phonometry.environment.propagation.outdoor_propagation import ( outdoor_propagation_attenuation, ) -from phonometry.environmental.wind_turbine_noise import wind_turbine_tonality +from phonometry.environment.sources.wind_turbine import wind_turbine_tonality def _result() -> object: @@ -75,7 +75,7 @@ def test_plot_spherical_ground_spanish_xaxis_label() -> None: def test_plot_cnossos_road_emission_spanish_labels() -> None: # The road-emission renderer localises its own title, ylabel and category # legend, and takes the shared ``_band_axis`` for the frequency axis. - from phonometry.environmental.cnossos_road import ( + from phonometry.environment.sources.cnossos_road import ( RoadTraffic, RoadVehicleCategory, road_source_power, diff --git a/tests/reference_data.py b/tests/reference_data.py index be38e7a04..f1b4ba0cd 100644 --- a/tests/reference_data.py +++ b/tests/reference_data.py @@ -2798,7 +2798,7 @@ # comment: Commission Delegated Directive (EU) 2021/1226 (OJ L 269, 28.7.2021) # where it replaced the table, Commission Directive (EU) 2015/996 (OJ L 168, # 1.7.2015) where it did not. These constants pin the tables shipped in -# ``phonometry.environmental.cnossos_rail``. +# ``phonometry.environment.sources.cnossos_rail``. # --------------------------------------------------------------------------- #: Wavelength grid of Tables G-1b, G-2 and G-4 as replaced by (EU) 2021/1226, mm. diff --git a/tests/test_deprecated_aliases.py b/tests/test_deprecated_aliases.py index 216e9853d..f276be547 100644 --- a/tests/test_deprecated_aliases.py +++ b/tests/test_deprecated_aliases.py @@ -305,7 +305,7 @@ def test_renamed_attribute_shims_reject_unknown_names() -> None: "phonometry.distortion", "phonometry.dynamic_stiffness", "phonometry.enclosed_space_absorption", - "phonometry.environmental", + "phonometry.environment", "phonometry.environmental_measurement", "phonometry.facade_prediction", "phonometry.filter_design", @@ -431,6 +431,20 @@ def test_pre_move_module_path_still_imports(path: str) -> None: "phonometry.vibration.point_mobility", "phonometry.vibration.radiation_efficiency", "phonometry.vibration.transfer_stiffness", + # The package itself was renamed, so the prefix is an alias as well. + "phonometry.environmental", + "phonometry.environmental.air_absorption", + "phonometry.environmental.atmospheric_refraction", + "phonometry.environmental.cnossos_rail", + "phonometry.environmental.cnossos_road", + "phonometry.environmental.ground_barriers", + "phonometry.environmental.impulse_prominence", + "phonometry.environmental.impulsive_sound", + "phonometry.environmental.measurement", + "phonometry.environmental.outdoor_propagation", + "phonometry.environmental.rating", + "phonometry.environmental.spanish_regulation", + "phonometry.environmental.wind_turbine_noise", ] diff --git a/tests/test_package_architecture.py b/tests/test_package_architecture.py index 98f5cee22..0464a530d 100644 --- a/tests/test_package_architecture.py +++ b/tests/test_package_architecture.py @@ -27,8 +27,8 @@ #: `* -> _internal` and `* -> TOOLBOX`. "root" = modules still at the top #: level of the package (shrinks to the facade set as the migration proceeds). ALLOWED_EDGES: set[tuple[str, str]] = { - ("environmental", "materials"), # air_absorption -> ISO 354 helpers - ("aircraft", "environmental"), # atmospheric absorption reuse + ("environment", "materials"), # air_absorption -> ISO 354 helpers + ("aircraft", "environment"), # atmospheric absorption reuse ("vibration", "hearing"), # multiple-shock SEXES tables # swept-sine distortion reuses the ISO 18233 sweep / Farina # inverse-filter machinery of room_ir From 4890e9ec8be88ecee20841745d914413cc200ce0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20M=2E=20Requena=20Plens?= Date: Sun, 2 Aug 2026 16:53:44 +0200 Subject: [PATCH 2/5] Answer the review of the environment rename Eight findings, the first of which forked the package in two. A renamed package is an alias module, and the alias delegated every missing attribute to the target, `__path__` included. That handed the import machinery the real package's search path, so `import phonometry.environmental.propagation` built a second, independent copy of every submodule: same source, distinct classes, isinstance failing across the two spellings, pickles not round tripping, and no notice at all on the way in, since that route never reaches the delegating hook. It also made `from phonometry.environmental import lden` warn three times, because the from-import probes `__path__` first. The alias now refuses that one attribute, which is enough: it serves the modules registered for it and nothing else, and there is a test that says so. The generated llms dumps were a regeneration behind and still taught `from phonometry import environmental`. Two guides named `phonometry.environment.atmospheric_refraction` and `.ground_barriers`, paths that never existed: the sweep had renamed the package and not the subgroup. Eighteen pages had their API link targets updated and their labels left naming the old module. The figure generator kept one namespace import in the old spelling, the last one in the file. Twelve prose citations pointed at files this rename moved, including four source links in the errata page that the transplant carries into both site pages, and the evidence table of "Why phonometry". Nine more were already broken before this branch, from the 3.2 modularization, and are fixed with them. The migration note in the curated API table had been mangled by two successive global renames until it said a path moved to itself and called a current path deprecated. It is a table now, one row per generation, which is harder to corrupt and easier to read. Also: `_plot/environmental.py` is `_plot/environment.py`, since every other renderer matches its domain; the frozen 3.2 snapshot gets its `environmental` row back, because a frozen list of pre-3.2 paths must name the paths that existed then; and the duplication exclusion that still named `metrology/core.py`, gone since the metrology split, names `filters/core.py`. --- CHANGELOG.md | 8 + docs/ERRATA.md | 8 +- docs/api-reference.md | 17 +- docs/atmospheric-refraction.md | 2 +- docs/environmental-levels.md | 4 +- docs/ground-barriers.md | 2 +- docs/references.md | 2 +- docs/spanish-noise-regulation.md | 2 +- docs/why-phonometry.md | 2 +- llms-full.txt | 265 +++++++-------- scripts/conformance_report.py | 2 +- scripts/generate_graphs.py | 4 +- scripts/generate_reports.py | 4 +- site/public/llms/llms-aircraft-wind.txt | 58 ++-- site/public/llms/llms-levels-weighting.txt | 134 ++++---- site/public/llms/llms-outdoor-sound.txt | 40 +-- site/public/llms/llms-room-acoustics.txt | 12 +- site/public/llms/llms-simulation.txt | 2 +- site/public/llms/llms-start.txt | 2 +- .../docs/es/guides/atmospheric-refraction.mdx | 2 +- .../docs/es/guides/cnossos-rail-emission.mdx | 2 +- .../docs/es/guides/cnossos-road-emission.mdx | 2 +- .../docs/es/guides/environmental-levels.mdx | 2 +- .../docs/es/guides/ground-barriers.mdx | 2 +- .../docs/es/guides/impulse-prominence.mdx | 2 +- .../docs/es/guides/outdoor-propagation.mdx | 2 +- .../es/guides/reverberation-prediction.mdx | 2 +- .../es/guides/spanish-noise-regulation.mdx | 2 +- .../docs/es/guides/wind-turbine-noise.mdx | 2 +- site/src/content/docs/es/reference/errata.md | 8 +- .../docs/es/reference/why-phonometry.mdx | 2 +- .../docs/guides/atmospheric-refraction.mdx | 2 +- .../docs/guides/cnossos-rail-emission.mdx | 2 +- .../docs/guides/cnossos-road-emission.mdx | 2 +- .../docs/guides/environmental-levels.mdx | 2 +- .../content/docs/guides/ground-barriers.mdx | 2 +- .../docs/guides/impulse-prominence.mdx | 2 +- .../docs/guides/outdoor-propagation.mdx | 2 +- .../docs/guides/reverberation-prediction.mdx | 2 +- .../docs/guides/spanish-noise-regulation.mdx | 2 +- .../docs/guides/wind-turbine-noise.mdx | 2 +- site/src/content/docs/reference/errata.md | 8 +- .../content/docs/reference/why-phonometry.mdx | 2 +- sonar-project.properties | 2 +- src/phonometry/_compat.py | 8 + .../{environmental.py => environment.py} | 0 src/phonometry/_plotting.py | 2 +- src/phonometry/_report/rd1367.py | 2 +- .../assessment/impulse_prominence.py | 2 +- .../environment/assessment/measurement.py | 2 +- .../environment/assessment/spain.py | 310 ++++++++++++------ .../environment/propagation/air_absorption.py | 2 +- .../propagation/ground_barriers.py | 4 +- .../propagation/outdoor_propagation.py | 2 +- .../environment/propagation/refraction.py | 6 +- .../environment/sources/cnossos_rail.py | 2 +- .../environment/sources/cnossos_road.py | 2 +- .../environment/sources/wind_turbine.py | 2 +- tests/data/cnossos/README.md | 4 +- .../test_impulse_prominence_report.py | 2 +- .../assessment/test_measurement.py | 3 +- .../assessment/test_rd1367_report.py | 2 +- tests/environment/assessment/test_spain.py | 56 ++-- .../test_wind_turbine_tonality_report.py | 2 +- tests/reference_data.py | 16 +- tests/test_deprecated_aliases.py | 21 +- 66 files changed, 630 insertions(+), 454 deletions(-) rename src/phonometry/_plot/{environmental.py => environment.py} (100%) diff --git a/CHANGELOG.md b/CHANGELOG.md index 45d0766d5..294f0e062 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1005,6 +1005,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/). validator says the same thing in the form the whole package uses, rejects infinities too, and names the parameter that failed instead of both at once. +- The RD 1367 assessment reads as the article it implements. Its two longest + functions were doing four jobs each: `assess_activity` now validates the + annual parameters, resolves the period durations, assesses one period and + derives the annual index in four named steps, and `tonal_correction` + separates the spectrum it rejects from the band-by-band procedure of + Annex IV A.3.3. No number changes: the conformance report, the fiches and + the tests are identical either side of the refactor. + - `phonometry.environmental` is `phonometry.environment`, with three subgroups that follow how an environmental study is written: what the source emits (`environment.sources`: CNOSSOS road and rail, wind turbines), what the diff --git a/docs/ERRATA.md b/docs/ERRATA.md index a7cb050f1..01f8d4a5d 100644 --- a/docs/ERRATA.md +++ b/docs/ERRATA.md @@ -1114,7 +1114,7 @@ which is the check that enforces the rule; see printed with them. - **Library behaviour:** implements the spelled-out reading, weighting the onset rate by 3 and the level difference by 2 (`predicted_prominence` in - [`impulse_prominence.py`](../src/phonometry/environmental/impulse_prominence.py)), + [`impulse_prominence.py`](../src/phonometry/environment/assessment/impulse_prominence.py)), which is also the NT ACOU 112:2002 form the PAS carries over. - **Status:** unreported. @@ -1136,7 +1136,7 @@ which is the check that enforces the rule; see 1 kHz being both). - **Library behaviour:** unaffected. The library never reads Table 2: it computes $A_\text{atm}$ from the ISO 9613-1 formula directly - ([`air_absorption.py`](../src/phonometry/environmental/air_absorption.py)), + ([`air_absorption.py`](../src/phonometry/environment/propagation/air_absorption.py)), so it yields 4,15 dB/km for this condition. - **Status:** unreported. @@ -1969,7 +1969,7 @@ which is the check that enforces the rule; see in the autonomous-community noise regulations that transpose this Annex print `10 < Lf <= 15`. - **Library behaviour:** - [`low_frequency_correction`](../src/phonometry/environmental/spanish_regulation.py) + [`low_frequency_correction`](../src/phonometry/environment/assessment/spain.py) and `impulsive_correction` implement $10 < L \le 15$, with a regression test pinning the three branches at the 10 dB and 15 dB boundaries. - **Status:** unreported (national regulation, not a standards body). @@ -1995,7 +1995,7 @@ which is the check that enforces the rule; see from 125 Hz to 4 kHz", read: "each octave band i from 63 Hz to 8 kHz"'. The same corrigendum also adds "octave bands" to the frequency range of 2.1.1. - **Library behaviour:** - [`cnossos_road`](../src/phonometry/environmental/cnossos_road.py) works over + [`cnossos_road`](../src/phonometry/environment/sources/cnossos_road.py) works over the corrected 63 Hz to 8 kHz grid (`ROAD_OCTAVE_BANDS`), pinned by `test_octave_bands_are_the_corrected_range` and by the workbook cases, whose published levels cover all eight bands. diff --git a/docs/api-reference.md b/docs/api-reference.md index ee95dacab..751e2112f 100644 --- a/docs/api-reference.md +++ b/docs/api-reference.md @@ -45,11 +45,18 @@ contour = aircraft.noise_contour(path, powers, distances, sel, lmax, x=gx, y=gy) Every name in the table below is also re-exported at the top level, so `from phonometry import ` works for every row. Two generations of module -paths are still importable and warn on use: the pre-3.2 flat ones (for example -`phonometry.building.insulation`), removed in 4.0, and the pre-4.0 ones that the split -of `metrology` moved (for example `phonometry.signals.levels`, now -`phonometry.signals.levels`), removed in 5.0. Reading a moved name from the -namespace it left (`metrology.leq`) warns and delegates as well. +paths are still importable and warn on use. The pre-3.2 flat ones, written +without a subpackage, are removed in 4.0; the pre-4.0 ones that the taxonomy +moved are removed in 5.0. Reading a moved name from the namespace it left +warns and delegates as well. + +| Deprecated path | Resolves to | Removed in | +| :--- | :--- | :--- | +| `phonometry.insulation` | `phonometry.building.insulation` | 4.0 | +| `phonometry.metrology.levels` | `phonometry.signals.levels` | 5.0 | +| `phonometry.hearing.sti` | `phonometry.speech.sti` | 5.0 | +| `phonometry.vibration.human_vibration` | `phonometry.vibration.human.exposure` | 5.0 | +| `phonometry.environmental` | `phonometry.environment` | 5.0 | | Name | Type | Description (Inputs) | Usage Snippet (Outputs) | | :--- | :--- | :--- | :--- | diff --git a/docs/atmospheric-refraction.md b/docs/atmospheric-refraction.md index fcf1a6f13..9bd79690e 100644 --- a/docs/atmospheric-refraction.md +++ b/docs/atmospheric-refraction.md @@ -7,7 +7,7 @@ The [ISO 9613-2 method](outdoor-propagation.md) and the atmosphere. In reality the sound speed changes with height, because temperature and wind change with height, and this **refracts** sound: rays curve, and over a few hundred metres the received level can swing by tens of decibels. This page -covers `phonometry.environment.atmospheric_refraction`, the +covers `phonometry.environment.propagation.refraction`, the refracting-atmosphere counterpart of the ocean solvers in [`phonometry.underwater.numerical_propagation`](underwater-solvers.md): a **ray model** and a **parabolic-equation (PE)** solver, both clean-room from diff --git a/docs/environmental-levels.md b/docs/environmental-levels.md index 991715abd..01dc64d4e 100644 --- a/docs/environmental-levels.md +++ b/docs/environmental-levels.md @@ -192,8 +192,8 @@ $L_{dn}$ (3.6.5) keeps only the +10 dB night penalty. - [Occupational Noise Exposure](occupational-exposure.md): the workplace counterpart, from task samples to the daily exposure level with its uncertainty budget. -- API reference: [`environmental.measurement`](https://jmrplens.github.io/phonometry/reference/api/environment/measurement/) - and [`environmental.rating`](https://jmrplens.github.io/phonometry/reference/api/environment/rating/). +- API reference: [`environment.assessment.measurement`](https://jmrplens.github.io/phonometry/reference/api/environment/measurement/) + and [`environment.assessment.rating`](https://jmrplens.github.io/phonometry/reference/api/environment/rating/). ## References diff --git a/docs/ground-barriers.md b/docs/ground-barriers.md index 530c51bf5..8b7c494b3 100644 --- a/docs/ground-barriers.md +++ b/docs/ground-barriers.md @@ -4,7 +4,7 @@ The [ISO 9613-2 general method](outdoor-propagation.md) folds the ground and barrier terms into tabulated, energy-based corrections. This page covers the -underlying wave acoustics in `phonometry.environment.ground_barriers`: the +underlying wave acoustics in `phonometry.environment.propagation.ground_barriers`: the **spherical-wave reflection coefficient** of a finite-impedance ground (Weyl-Van der Pol) and the **wave-theoretic diffraction** of a screen, both in a homogeneous (non-refracting, non-turbulent) atmosphere. These are the physical diff --git a/docs/references.md b/docs/references.md index 7bd37f1be..3a0a14313 100644 --- a/docs/references.md +++ b/docs/references.md @@ -812,7 +812,7 @@ it; the list grows as guides gain their References sections. for adjustment of LAeq* (ISO/PAS 1996-3:2022). [iso.org catalogue](https://www.iso.org/standard/77035.html). The objective onset-analysis method implemented by - `environmental.impulsive_sound`: the level history LpAF (Clause 4), the onset + `environment.assessment.impulsive_sound`: the level history LpAF (Clause 4), the onset detection and least-squares onset rate (Clauses 3.3-3.5), the prominence (Clause 5, Formula 2) and the adjustment KI (Clause 6, Formula 3). Cited by [Impulsive-sound prominence](impulse-prominence.md). diff --git a/docs/spanish-noise-regulation.md b/docs/spanish-noise-regulation.md index 28fc7e917..12c2e97b9 100644 --- a/docs/spanish-noise-regulation.md +++ b/docs/spanish-noise-regulation.md @@ -302,7 +302,7 @@ exceeds 9 dB. - [Impulsive-sound prominence](impulse-prominence.md): the ISO/PAS 1996-3 impulsive adjustment, the relative of $K_i$ that is not interchangeable with it. -- API reference: [`environmental.spanish_regulation`](https://jmrplens.github.io/phonometry/reference/api/environment/spain/). +- API reference: [`environment.assessment.spain`](https://jmrplens.github.io/phonometry/reference/api/environment/spain/). ## References diff --git a/docs/why-phonometry.md b/docs/why-phonometry.md index af4b051b7..8a2ddc16d 100644 --- a/docs/why-phonometry.md +++ b/docs/why-phonometry.md @@ -130,7 +130,7 @@ sample from the metrology core: | ISO 7196:1995 Table 2 | G weighting (infrasound) at every nominal response value, 0.25–315 Hz | `tests/filters/test_g_weighting.py` | | ISO 226:2023 Table 1 and Annex B | Equal-loudness contours and loudness levels against the Annex B tables, hearing threshold against the Table 1 $T_f$ parameters | `tests/psychoacoustics/test_loudness_contours.py` | | ECMA-418-1:2024 | TNR/PR tone prominence: critical bandwidths, proximity spacing and prominence criteria against the worked examples in clauses 10–12 | `tests/psychoacoustics/test_tonality.py` | -| ISO 1996-1:2016 | `lden()`, `ldn()` and `composite_rating_level()` against hand-computed formula values | `tests/environmental/test_environmental.py` | +| ISO 1996-1:2016 | `lden()`, `ldn()` and `composite_rating_level()` against hand-computed formula values | `tests/environment/assessment/test_rating.py` | | IEC 60942:2017 Table 2 | Calibrator short-term stability limits (frequency-dependent, class 1) in `sensitivity()` | `tests/metrology/test_calibration_validation.py` | The same discipline applies far beyond the metrology core: today the suite runs diff --git a/llms-full.txt b/llms-full.txt index 41a872c0f..47c5654b7 100644 --- a/llms-full.txt +++ b/llms-full.txt @@ -2248,7 +2248,7 @@ contour = aircraft.noise_contour(path, powers, distances, sel, lmax, x=gx, y=gy) | `phonometry.materials` | Absorption (ISO 354/11654), impedance tube, airflow resistance, scattering/diffusion, road absorption, dynamic stiffness | | `phonometry.building` | Sound insulation measurement and prediction (EN 12354, ISO 717/16283/10140/15186/10052/10848), structure-borne sound | | `phonometry.vibration` | Three families: `structural` (mobility ISO 7626, plate junctions, radiation, experimental SEA, transfer stiffness ISO 10846), `human` (ISO 2631/5349/8041 exposure and multiple shocks) and `machinery` (fault frequencies and condition monitoring) | -| `phonometry.environment` | Rating levels, ISO 1996-2 measurement, outdoor propagation (ISO 9613), atmospheric refraction (ray tracing and the parabolic equation), wind-turbine noise, impulsive prominence | +| `phonometry.environment` | Three subgroups: `sources` (CNOSSOS road and rail emission, wind-turbine apparent sound power), `propagation` (ISO 9613-1/-2, ground effect and barriers, refraction by ray tracing and the parabolic equation) and `assessment` (ISO 1996-1/-2 rating and measurement, impulsive prominence, RD 1367/2007) | | `phonometry.aircraft` | EPNL (ICAO Annex 16), SAE ARP 5534 absorption, airport contours (ECAC Doc 29), rotorcraft (ECAC Doc 32) | | `phonometry.underwater` | ISO 18405/17208/18406 levels, propagation and Weston regimes, sound speed, sonar equation and detection range, seabed, ambient and ship-traffic noise, marine-mammal audiograms and regulatory auditory weighting, numerical solvers | | `phonometry.electroacoustics` | Distortion (IEC 60268-3 / AES17), transfer function and coherence, radiating piston | @@ -2258,11 +2258,18 @@ contour = aircraft.noise_contour(path, powers, distances, sel, lmax, x=gx, y=gy) Every name in the table below is also re-exported at the top level, so `from phonometry import ` works for every row. Two generations of module -paths are still importable and warn on use: the pre-3.2 flat ones (for example -`phonometry.building.insulation`), removed in 4.0, and the pre-4.0 ones that the split -of `metrology` moved (for example `phonometry.signals.levels`, now -`phonometry.signals.levels`), removed in 5.0. Reading a moved name from the -namespace it left (`metrology.leq`) warns and delegates as well. +paths are still importable and warn on use. The pre-3.2 flat ones, written +without a subpackage, are removed in 4.0; the pre-4.0 ones that the taxonomy +moved are removed in 5.0. Reading a moved name from the namespace it left +warns and delegates as well. + +| Deprecated path | Resolves to | Removed in | +| :--- | :--- | :--- | +| `phonometry.insulation` | `phonometry.building.insulation` | 4.0 | +| `phonometry.metrology.levels` | `phonometry.signals.levels` | 5.0 | +| `phonometry.hearing.sti` | `phonometry.speech.sti` | 5.0 | +| `phonometry.vibration.human_vibration` | `phonometry.vibration.human.exposure` | 5.0 | +| `phonometry.environmental` | `phonometry.environment` | 5.0 | | Name | Type | Description (Inputs) | Usage Snippet (Outputs) | | :--- | :--- | :--- | :--- | @@ -3474,7 +3481,7 @@ The [ISO 9613-2 method](https://jmrplens.github.io/phonometry/guides/outdoor-pro atmosphere. In reality the sound speed changes with height, because temperature and wind change with height, and this **refracts** sound: rays curve, and over a few hundred metres the received level can swing by tens of decibels. This page -covers `phonometry.environment.atmospheric_refraction`, the +covers `phonometry.environment.propagation.refraction`, the refracting-atmosphere counterpart of the ocean solvers in [`phonometry.underwater.numerical_propagation`](https://jmrplens.github.io/phonometry/guides/underwater-solvers/): a **ray model** and a **parabolic-equation (PE)** solver, both clean-room from @@ -9442,12 +9449,12 @@ and `composite_rating_level()` the general whole-day composite of clause 6.5 +6 dB prominent tones): ```python -from phonometry import environmental +from phonometry import environment -l = environmental.lden(63.2, 58.1, 51.4) # from LAeq per period -r = environmental.composite_rating_level([(63.2, 12, 0.0), # day +l = environment.lden(63.2, 58.1, 51.4) # from LAeq per period +r = environment.composite_rating_level([(63.2, 12, 0.0), # day (58.1, 4, 5.0), # evening (+5) - (51.4, 8, 10.0)]) # night (+10) == environmental.lden + (51.4, 8, 10.0)]) # night (+10) == environment.lden ``` Synthetic 24-hour urban LAeq profile with day, evening and night bands, the +5 and +10 dB weighted period levels and the resulting Lden @@ -9458,7 +9465,7 @@ r = environmental.composite_rating_level([(63.2, 12, 0.0), # day ```python import numpy as np import matplotlib.pyplot as plt -from phonometry import environmental +from phonometry import environment # Synthetic hourly LAeq of an urban road (dB), hours 00 to 23 laeq_h = np.array([48, 46, 45, 45, 46, 50, 56, 64, 66, 65, 63, 63, @@ -9470,7 +9477,7 @@ def period_leq(idx): ld = period_leq(np.arange(7, 19)) # day 07-19 le = period_leq(np.arange(19, 23)) # evening 19-23 ln_ = period_leq(np.r_[23, np.arange(0, 7)]) # night 23-07 -l_den = environmental.lden(ld, le, ln_) +l_den = environment.lden(ld, le, ln_) print(f"Lden = {l_den:.1f} dB") # Lden = 64.3 dB fig, ax = plt.subplots() @@ -9536,10 +9543,10 @@ $K_t$ (Table J.1). ```python import matplotlib.pyplot as plt -from phonometry import environmental +from phonometry import environment # ISO 1996-2:2007 Annex C.5, Example 2 (two tones near 400 Hz): -res = environmental.assess_tonal_audibility(tone_level=54.1, masking_noise_level=45.2, +res = environment.assess_tonal_audibility(tone_level=54.1, masking_noise_level=45.2, centre_frequency=430.0) print(res.audibility, res.adjustment) # ΔLta ≈ 11.1 dB -> Kt = 6 dB res.plot() @@ -9566,20 +9573,20 @@ reported alongside as `approximate_uncertainty` and a warning when the levels spread beyond 3 dB, where the substitute grossly inflates. ```python -from phonometry import environmental +from phonometry import environment # Tonal adjustment for a prominent tone: -tonal = environmental.assess_tonal_audibility(54.1, 45.2, 430.0) # TonalAssessmentResult +tonal = environment.assess_tonal_audibility(54.1, 45.2, 430.0) # TonalAssessmentResult kt = tonal.adjustment # 6 dB tonal.plot() # this audibility on the Kt curve, as in the figure above # Subtract residual (background) noise from a measured level: -corr = environmental.residual_sound_correction(measured_level=58.0, residual_level=50.0) +corr = environment.residual_sound_correction(measured_level=58.0, residual_level=50.0) corr.corrected_level, corr.reliable # Combine an uncertainty budget and expand to 95 %: -u = environmental.combined_standard_uncertainty([0.59, 0.3, 2.0, 0.40, 0.38]) # 2.18 dB (G.2) -environmental.expanded_uncertainty(u) # 4.36 dB (k = 2) +u = environment.combined_standard_uncertainty([0.59, 0.3, 2.0, 0.40, 0.38]) # 2.18 dB (G.2) +environment.expanded_uncertainty(u) # 4.36 dB (k = 2) ``` ## Quick answers @@ -9604,8 +9611,8 @@ $L_{dn}$ (3.6.5) keeps only the +10 dB night penalty. - [Occupational Noise Exposure](https://jmrplens.github.io/phonometry/guides/occupational-exposure/): the workplace counterpart, from task samples to the daily exposure level with its uncertainty budget. -- API reference: [`environmental.measurement`](https://jmrplens.github.io/phonometry/reference/api/environment/measurement/) - and [`environmental.rating`](https://jmrplens.github.io/phonometry/reference/api/environment/rating/). +- API reference: [`environment.assessment.measurement`](https://jmrplens.github.io/phonometry/reference/api/environment/measurement/) + and [`environment.assessment.rating`](https://jmrplens.github.io/phonometry/reference/api/environment/rating/). ## References @@ -12517,7 +12524,7 @@ Source: https://jmrplens.github.io/phonometry/guides/ground-barriers/ The [ISO 9613-2 general method](https://jmrplens.github.io/phonometry/guides/outdoor-propagation/) folds the ground and barrier terms into tabulated, energy-based corrections. This page covers the -underlying wave acoustics in `phonometry.environment.ground_barriers`: the +underlying wave acoustics in `phonometry.environment.propagation.ground_barriers`: the **spherical-wave reflection coefficient** of a finite-impedance ground (Weyl-Van der Pol) and the **wave-theoretic diffraction** of a screen, both in a homogeneous (non-refracting, non-turbulent) atmosphere. These are the physical @@ -14909,10 +14916,10 @@ $K_I$ (the adjustment is 0 dB when no event qualifies). [Watch the high-resolution video (WebM)](https://raw.githubusercontent.com/jmrplens/phonometry/main/.github/images/anim_onset_detection.webm) ```python -from phonometry import environmental +from phonometry import environment # Three candidate impulses: (onset rate dB/s, level difference dB). -result = environmental.impulse_prominence([1200.0, 300.0, 60.0], [32.0, 18.0, 11.0]) +result = environment.impulse_prominence([1200.0, 300.0, 60.0], [32.0, 18.0, 11.0]) print(result.per_impulse.round(2)) # [12.25 9.94 7.42] print(round(result.prominence, 2)) # 12.25 (the governing impulse) print(round(result.adjustment, 2)) # 13.05 dB @@ -14923,10 +14930,10 @@ result.plot() # the KI(P) curve of section 2 with these impulses marked A single impulse can be evaluated directly with `predicted_prominence`: ```python -from phonometry import environmental +from phonometry import environment # P = 3*lg(1000) + 2*lg(30) = 9 + 2.95 = 11.95. -print(round(environmental.predicted_prominence(1000.0, 30.0), 4)) # 11.9542 +print(round(environment.predicted_prominence(1000.0, 30.0), 4)) # 11.9542 ``` ## 2. Adjustment to LAeq (clause 8) @@ -14939,10 +14946,10 @@ K_I = 1.8\,(P - 5)\ \text{dB} \quad (P > 5), \qquad K_I = 0 \quad (P \le 5). $$ ```python -from phonometry import environmental +from phonometry import environment -print(float(environmental.impulse_adjustment(10.0))) # 9.0 dB -print(float(environmental.impulse_adjustment(5.0))) # 0.0 dB (at the threshold) +print(float(environment.impulse_adjustment(10.0))) # 9.0 dB +print(float(environment.impulse_adjustment(5.0))) # 0.0 dB (at the threshold) ``` The adjustment is applied to $L_{Aeq,30\text{min}}$ from the single event with the @@ -14955,10 +14962,10 @@ L_{Ar,T} = 10\,\log_{10}\!\left(\frac{1}{T}\sum_N \Delta t_N\, $$ ```python -from phonometry import environmental +from phonometry import environment # Two 30-min periods: one impulsive (KI = 7.6 dB), one quiet. -print(round(environmental.rating_level([72.0, 66.0], [7.6, 0.0], [30.0, 30.0], 60.0), 2)) +print(round(environment.rating_level([72.0, 66.0], [7.6, 0.0], [30.0, 30.0], 60.0), 2)) # 76.78 dB ``` @@ -14975,16 +14982,16 @@ measurement of ISO 1996-2. ```python import numpy as np import matplotlib.pyplot as plt -from phonometry import environmental +from phonometry import environment # One line for the adjustment curve with the impulses marked: -environmental.impulse_prominence([1200.0, 300.0, 60.0], [32.0, 18.0, 11.0]).plot() +environment.impulse_prominence([1200.0, 300.0, 60.0], [32.0, 18.0, 11.0]).plot() plt.show() # By hand, the left panel — P vs onset rate for three level differences: orate = np.logspace(1, 4, 200) for ld in (5.0, 15.0, 30.0): - plt.plot(orate, environmental.predicted_prominence(orate, np.full_like(orate, ld)), + plt.plot(orate, environment.predicted_prominence(orate, np.full_like(orate, ld)), label=f"LD = {ld:g} dB") plt.xscale("log"); plt.legend(); plt.show() ``` @@ -15012,7 +15019,7 @@ The onset detection can be exercised on a level history directly with ```python import numpy as np -from phonometry import environmental +from phonometry import environment # An LpAF history sampled every 20 ms: quiet, a 30 dB rise over 0.30 s, steady. dt = 0.02 @@ -15021,7 +15028,7 @@ levels = np.concatenate([ 40.0 + 30.0 * np.arange(1, 16) / 15, # a straight 100 dB/s onset to 70 dB np.full(15, 70.0), ]) -onset = environmental.detect_onsets(levels, dt)[0] +onset = environment.detect_onsets(levels, dt)[0] print(round(onset.onset_rate), round(onset.level_difference)) # 100 30 print(round(onset.prominence, 2)) # 8.95 ``` @@ -15029,7 +15036,7 @@ print(round(onset.prominence, 2)) # 8.95 From a calibrated time signal (in pascal) the whole chain runs end to end: ```python -result = environmental.impulsive_sound_adjustment(signal, fs) +result = environment.impulsive_sound_adjustment(signal, fs) print(result.category) # e.g. 'highly impulsive' print(round(result.adjustment, 1)) # KI in dB (0.0 to about 9 dB in typical cases) print(round(result.adjusted_laeq, 1)) # LAeq + KI @@ -15045,7 +15052,7 @@ result.plot() # the LpAF history with the detected onsets (needs matplotlib) ```python import numpy as np import matplotlib.pyplot as plt -from phonometry import environmental +from phonometry import environment # Three hammer strikes over a 55 dB(A) background, 6 s at 48 kHz. fs = 48000 @@ -15061,7 +15068,7 @@ for onset_time in (1.0, 2.6, 4.2): strike *= 2e-5 * 10 ** (95 / 20) / np.sqrt(np.mean(strike[window] ** 2)) signal += strike -res = environmental.impulsive_sound_adjustment(signal, fs) +res = environment.impulsive_sound_adjustment(signal, fs) print(res.category, round(res.prominence, 2), round(res.adjustment, 2)) # highly impulsive 11.34 11.42 @@ -15137,10 +15144,10 @@ pass `language="es"` for a Spanish fiche (translated fixed strings and a comma decimal separator), e.g. `res.report("impulse_fiche_es.pdf", language="es")`. ```python -from phonometry import environmental, ReportMetadata +from phonometry import environment, ReportMetadata # The three-impulse pile-driving set (onset rate dB/s, level difference dB). -res = environmental.impulse_prominence([1200.0, 300.0, 60.0], [32.0, 18.0, 11.0]) +res = environment.impulse_prominence([1200.0, 300.0, 60.0], [32.0, 18.0, 11.0]) res.report( "impulse_fiche.pdf", metadata=ReportMetadata( @@ -23796,12 +23803,12 @@ curves overtake it below ~200 Hz: the relaxation signature.* ```python import matplotlib.pyplot as plt import numpy as np -from phonometry import environmental +from phonometry import environment freqs = np.geomspace(50.0, 10000.0, 400) fig, ax = plt.subplots() for temp, rh in [(20.0, 50.0), (20.0, 10.0), (0.0, 70.0), (30.0, 80.0)]: - ax.loglog(freqs, environmental.air_attenuation(freqs, temp, rh) * 1000.0, + ax.loglog(freqs, environment.air_attenuation(freqs, temp, rh) * 1000.0, label=f"{temp:g} °C, {rh:g} % RH") ax.set_xlabel("Frequency [Hz]") ax.set_ylabel("Attenuation coefficient alpha [dB/km]") @@ -23813,21 +23820,21 @@ plt.show() ```python import numpy as np -from phonometry import environmental +from phonometry import environment bands = [63, 125, 250, 500, 1000, 2000, 4000, 8000] # octave-band centres [Hz] # Pure-tone attenuation coefficient alpha [dB/m] at 20 °C, 50 % RH, one atmosphere -alpha = environmental.air_attenuation(bands, temperature=20.0, relative_humidity=50.0) +alpha = environment.air_attenuation(bands, temperature=20.0, relative_humidity=50.0) print(np.round(alpha * 1000.0, 2)) # in dB/km, as Table 1 tabulates # [ 0.12 0.44 1.31 2.73 4.66 9.89 29.67 105.29] # Reproduce an ISO 9613-1 Table 1 cell exactly (10 °C, 70 %, 1 kHz) -cell = environmental.air_attenuation(1000.0, 10.0, 70.0, exact_midband=True) * 1000.0 +cell = environment.air_attenuation(1000.0, 10.0, 70.0, exact_midband=True) * 1000.0 print(round(float(cell), 2)) # 3.66 (dB/km, Table 1) # Feed real conditions into the ISO 354 power attenuation coefficient m [1/m] -m = environmental.air_attenuation_m([1000.0, 4000.0], temperature=20.0, relative_humidity=50.0) +m = environment.air_attenuation_m([1000.0, 4000.0], temperature=20.0, relative_humidity=50.0) print(np.round(m, 5)) # [0.00107 0.00683] ``` @@ -23870,9 +23877,9 @@ Table 1 unit, on a linear ordinate over a logarithmic frequency axis). Passing a that path as `total_attenuation`, the ISO 9613-2 $A_{atm}$ of Eq. (8). ```python -from phonometry import environmental +from phonometry import environment -res = environmental.atmospheric_attenuation( +res = environment.atmospheric_attenuation( [63, 125, 250, 500, 1000, 2000, 4000, 8000], temperature=20.0, relative_humidity=50.0, ) @@ -23891,10 +23898,10 @@ res.plot() # alpha in dB/km against frequency (needs matplotlib) ```python import matplotlib.pyplot as plt import numpy as np -from phonometry import environmental +from phonometry import environment # One line: the coefficient curve straight from the result. -res = environmental.atmospheric_attenuation( +res = environment.atmospheric_attenuation( np.geomspace(50.0, 10000.0, 400), temperature=20.0, relative_humidity=50.0, ) res.plot() @@ -23903,7 +23910,7 @@ plt.show() # Or by hand from air_attenuation (dB/m, so scale by 1000 for dB/km): freqs = np.geomspace(50.0, 10000.0, 400) fig, ax = plt.subplots() -ax.semilogx(freqs, environmental.air_attenuation(freqs, 20.0, 50.0) * 1000.0) +ax.semilogx(freqs, environment.air_attenuation(freqs, 20.0, 50.0) * 1000.0) ax.set_xlabel("Frequency [Hz]") ax.set_ylabel("Attenuation coefficient alpha [dB/km]") plt.show() @@ -23981,11 +23988,11 @@ mid bands. ```python import matplotlib.pyplot as plt import numpy as np -from phonometry import environmental +from phonometry import environment bands = np.array([63.0, 125.0, 250.0, 500.0, 1000.0, 2000.0, 4000.0, 8000.0]) -barrier = environmental.Barrier(source_to_edge=101.0, edge_to_receiver=101.0) -att = environmental.outdoor_propagation_attenuation( +barrier = environment.Barrier(source_to_edge=101.0, edge_to_receiver=101.0) +att = environment.outdoor_propagation_attenuation( 200.0, 1.5, 1.5, bands, ground_source=1.0, ground_middle=1.0, ground_receiver=1.0, barrier=barrier, temperature=15.0, relative_humidity=70.0, @@ -24022,15 +24029,15 @@ plt.show() ```python import numpy as np -from phonometry import environmental +from phonometry import environment bands = [63, 125, 250, 500, 1000, 2000, 4000, 8000] # octave-band centres [Hz] # A point source and receiver 1.5 m high, 200 m apart over porous ground # (G = 1), screened midway by a barrier that raises the path over its top edge # (dss = dsr ~ 101 m). Geometry feeds the pathlength-difference equations. -barrier = environmental.Barrier(source_to_edge=101.0, edge_to_receiver=101.0) -att = environmental.outdoor_propagation_attenuation( +barrier = environment.Barrier(source_to_edge=101.0, edge_to_receiver=101.0) +att = environment.outdoor_propagation_attenuation( 200.0, source_height=1.5, receiver_height=1.5, frequencies=bands, ground_source=1.0, ground_middle=1.0, ground_receiver=1.0, barrier=barrier, temperature=15.0, relative_humidity=70.0, @@ -24043,7 +24050,7 @@ att.plot() # the stacked breakdown above (needs matplotli # Predicted receiver level from an octave-band sound power Lw = 95 dB lw = np.full(len(bands), 95.0) -lp = environmental.predicted_receiver_level( +lp = environment.predicted_receiver_level( lw, 200.0, 1.5, 1.5, bands, 1.0, 1.0, 1.0, barrier=barrier, temperature=15.0, relative_humidity=70.0, ) @@ -24069,14 +24076,14 @@ general per-region method of 7.3.1); combine them by hand when the alternative method is appropriate. ```python -from phonometry import environmental +from phonometry import environment # Alternative ground term (mean path height hm = 2 m, d = 200 m) -print(round(environmental.ground_attenuation_alternative(200.0, 2.0), 2)) # 4.43 dB +print(round(environment.ground_attenuation_alternative(200.0, 2.0), 2)) # 4.43 dB # Its companion solid-angle index (add to Dc when using Eq. (10)) -print(round(environmental.directivity_omega(1.5, 1.5, 200.0), 2)) # 3.01 dB +print(round(environment.directivity_omega(1.5, 1.5, 200.0), 2)) # 3.01 dB # Long-term meteorological correction (C0 = 2 dB) to subtract from LAT(DW) -print(round(environmental.meteorological_correction(200.0, 1.5, 1.5, 2.0), 2)) # 1.7 dB +print(round(environment.meteorological_correction(200.0, 1.5, 1.5, 2.0), 2)) # 1.7 dB ``` ### The image source behind the ground effect @@ -27615,7 +27622,7 @@ it; the list grows as guides gain their References sections. for adjustment of LAeq* (ISO/PAS 1996-3:2022). [iso.org catalogue](https://www.iso.org/standard/77035.html). The objective onset-analysis method implemented by - `environmental.impulsive_sound`: the level history LpAF (Clause 4), the onset + `environment.assessment.impulsive_sound`: the level history LpAF (Clause 4), the onset detection and least-squares onset rate (Clauses 3.3-3.5), the prominence (Clause 5, Formula 2) and the adjustment KI (Clause 6, Formula 3). Cited by [Impulsive-sound prominence](https://jmrplens.github.io/phonometry/guides/impulse-prominence/). @@ -28559,7 +28566,7 @@ $c_0 = 343\ \mathrm{m/s}$) and the air-absorption term $4mV$. ```python import matplotlib.pyplot as plt -from phonometry import environmental, room +from phonometry import environment, room # A 10 x 7 x 3.5 m room: hard end walls, lightly treated side walls and a # very absorptive floor/ceiling pair (carpet plus an acoustic ceiling). @@ -28567,7 +28574,7 @@ bands = [125.0, 250.0, 500.0, 1000.0, 2000.0, 4000.0] alpha_x = [0.06, 0.07, 0.08, 0.09, 0.10, 0.10] alpha_y = [0.12, 0.14, 0.16, 0.18, 0.20, 0.20] alpha_z = [0.30, 0.50, 0.65, 0.78, 0.82, 0.80] -m = environmental.air_attenuation_m(bands, 20.0, 50.0) # air at 20 C / 50 % RH +m = environment.air_attenuation_m(bands, 20.0, 50.0) # air at 20 C / 50 % RH res = room.reverberation_time_models((10.0, 7.0, 3.5), (alpha_x, alpha_y, alpha_z), air_attenuation=m, frequencies=bands) @@ -28611,9 +28618,9 @@ power-attenuation coefficient $m$ (in neper per metre, from the ISO 9613-1 [atmospheric absorption](https://jmrplens.github.io/phonometry/guides/outdoor-propagation/)): ```python -from phonometry import environmental, room +from phonometry import environment, room -m = environmental.air_attenuation_m(2000.0, temperature=20.0, relative_humidity=50.0) +m = environment.air_attenuation_m(2000.0, temperature=20.0, relative_humidity=50.0) surfaces = [(40.0, 0.3), (40.0, 0.3), (24.0, 0.3), (24.0, 0.3), (15.0, 0.3), (15.0, 0.3)] print(round(room.eyring_reverberation_time(120.0, surfaces, air_attenuation=m), 3)) @@ -28690,9 +28697,9 @@ res.plot() # the five model curves per band (the figure above) ```python import matplotlib.pyplot as plt -from phonometry import environmental, room +from phonometry import environment, room -m = environmental.air_attenuation_m([125.0, 250.0, 500.0, 1000.0, 2000.0, 4000.0], 20.0, 50.0) +m = environment.air_attenuation_m([125.0, 250.0, 500.0, 1000.0, 2000.0, 4000.0], 20.0, 50.0) room.reverberation_time_models( (10.0, 7.0, 3.5), ( @@ -34269,20 +34276,20 @@ $K_t$ is evaluated band by band and, when several emergent tones are present, the largest of the resulting $K_t$ governs (step d): ```python -from phonometry import environmental +from phonometry import environment freqs = [100, 125, 160, 200, 250, 315, 400, 500, 630, 800, 1000] levels = [58.0, 60.0, 59.0, 61.0, 72.0, 62.0, 60.0, 58.0, 56.0, 54.0, 52.0] -kt = environmental.tonal_correction(levels, freqs) +kt = environment.tonal_correction(levels, freqs) kt.correction # 6 dB kt.governing_frequency # 250.0 Hz kt.differences[4] # Lt = 10.5 dB above the mean of the neighbours -environmental.low_frequency_correction(lceq=76.0, laeq=63.0) # Lf = 13 dB -> Kf = 3 dB -environmental.impulsive_correction(laieq=68.0, laeq=63.0) # Li = 5 dB -> Ki = 0 dB -environmental.corrected_level(63.0, kt=6, kf=3, ki=0) # LKeq = 72.0 dB -environmental.total_correction(kt=6, kf=6, ki=3) # 9.0 dB: the cap +environment.low_frequency_correction(lceq=76.0, laeq=63.0) # Lf = 13 dB -> Kf = 3 dB +environment.impulsive_correction(laieq=68.0, laeq=63.0) # Li = 5 dB -> Ki = 0 dB +environment.corrected_level(63.0, kt=6, kf=3, ki=0) # LKeq = 72.0 dB +environment.total_correction(kt=6, kf=6, ki=3) # 9.0 dB: the cap ``` ## Relationship with the ISO 1996 procedures @@ -34329,20 +34336,20 @@ own rounding: add 0.5 dB to the result and take the integer part, which is what `round_reported_level()` does. ```python -from phonometry import environmental +from phonometry import environment -environmental.RD1367_PERIOD_CLOCK_LIMITS["day"] # (7, 19) -environmental.RD1367_PERIOD_HOURS["day"] # 12.0 h +environment.RD1367_PERIOD_CLOCK_LIMITS["day"] # (7, 19) +environment.RD1367_PERIOD_HOURS["day"] # 12.0 h -day = [environmental.NoisePhase(2, 0.0, label="cerrada"), - environmental.NoisePhase(6, 50.0, kt=6, kf=3), - environmental.NoisePhase(4, 48.0, kt=3, kf=3)] +day = [environment.NoisePhase(2, 0.0, label="cerrada"), + environment.NoisePhase(6, 50.0, kt=6, kf=3), + environment.NoisePhase(4, 48.0, kt=3, kf=3)] -lkeq_d = environmental.evaluation_period_level(day) # 56.82 dB, unrounded -environmental.round_reported_level(lkeq_d) # 57 dB +lkeq_d = environment.evaluation_period_level(day) # 56.82 dB, unrounded +environment.round_reported_level(lkeq_d) # 57 dB -lk_d = environmental.long_term_corrected_level([57.0, 0.0], weights=[303, 62]) -environmental.round_reported_level(lk_d) # 56 dB (303 days open, 62 closed) +lk_d = environment.long_term_corrected_level([57.0, 0.0], weights=[303, 62]) +environment.round_reported_level(lk_d) # 56 dB (303 days open, 62 closed) ``` ## Acoustic quality objectives and immission limit values @@ -34370,16 +34377,16 @@ And in areas urbanised after the regulation entered into force (24 October `urbanisation="new"` applies. ```python -from phonometry import environmental +from phonometry import environment -environmental.outdoor_quality_objectives("a") # 65 / 65 / 55 dB -environmental.outdoor_quality_objectives("a", urbanisation="new") # 60 / 60 / 50 dB -environmental.indoor_quality_objectives("residential", "bedrooms") # 40 / 40 / 30 dB -environmental.infrastructure_limits("a") # 60 / 60 / 50 dB -environmental.max_infrastructure_limit("a") # 85 dB (LAmax) -environmental.activity_limits("a") # 55 / 55 / 45 dB -environmental.adjacent_premises_limits("residential", "bedrooms") # 35 / 35 / 25 dB -environmental.vibration_quality_objective("residential") # 75 dB +environment.outdoor_quality_objectives("a") # 65 / 65 / 55 dB +environment.outdoor_quality_objectives("a", urbanisation="new") # 60 / 60 / 50 dB +environment.indoor_quality_objectives("residential", "bedrooms") # 40 / 40 / 30 dB +environment.infrastructure_limits("a") # 60 / 60 / 50 dB +environment.max_infrastructure_limit("a") # 85 dB (LAmax) +environment.activity_limits("a") # 55 / 55 / 45 dB +environment.adjacent_premises_limits("residential", "bedrooms") # 35 / 35 / 25 dB +environment.vibration_quality_objective("residential") # 75 dB ``` Meeting an immission limit value is not simply staying below the number in the @@ -34412,16 +34419,16 @@ each $L_{Keq,Ti}$, 58 dB on the daily $L_{Keq,x}$ and 55 dB on the annual $L_{K,x}$: ```python -from phonometry import environmental +from phonometry import environment -day = [environmental.NoisePhase(2, 0.0, label="cerrada"), - environmental.NoisePhase(6, 50.0, kt=6, kf=3), - environmental.NoisePhase(4, 48.0, kt=3, kf=3)] -evening = [environmental.NoisePhase(2, 48.0, kt=3, kf=3), - environmental.NoisePhase(2, 0.0, label="cerrada")] +day = [environment.NoisePhase(2, 0.0, label="cerrada"), + environment.NoisePhase(6, 50.0, kt=6, kf=3), + environment.NoisePhase(4, 48.0, kt=3, kf=3)] +evening = [environment.NoisePhase(2, 48.0, kt=3, kf=3), + environment.NoisePhase(2, 0.0, label="cerrada")] -limits = environmental.activity_limits("a") # 55 / 55 / 45 dB -verdict = environmental.assess_activity( +limits = environment.activity_limits("a") # 55 / 55 / 45 dB +verdict = environment.assess_activity( {"day": day, "evening": evening}, limits, operating_days=303) verdict.periods[0].reported_level # 57 dB (LKeq,d) @@ -34436,17 +34443,17 @@ verdict.complies # False: LK,d exceeds 55 dB ```python import matplotlib.pyplot as plt -from phonometry import environmental +from phonometry import environment -day = [environmental.NoisePhase(2, 0.0, label="cerrada"), - environmental.NoisePhase(6, 50.0, kt=6, kf=3), - environmental.NoisePhase(4, 48.0, kt=3, kf=3)] -evening = [environmental.NoisePhase(2, 48.0, kt=3, kf=3), - environmental.NoisePhase(2, 0.0, label="cerrada")] +day = [environment.NoisePhase(2, 0.0, label="cerrada"), + environment.NoisePhase(6, 50.0, kt=6, kf=3), + environment.NoisePhase(4, 48.0, kt=3, kf=3)] +evening = [environment.NoisePhase(2, 48.0, kt=3, kf=3), + environment.NoisePhase(2, 0.0, label="cerrada")] -verdict = environmental.assess_activity( +verdict = environment.assess_activity( {"day": day, "evening": evening}, - environmental.activity_limits("a"), + environment.activity_limits("a"), operating_days=303, ) verdict.plot() @@ -34477,15 +34484,15 @@ applicable limit values and the boxed verdict. The default language is Spanish, the language of the regulation; `language="en"` translates it. ```python -from phonometry import environmental +from phonometry import environment -verdict = environmental.assess_activity( - {"day": [environmental.NoisePhase(2, 0.0, label="cerrada"), - environmental.NoisePhase(6, 50.0, kt=6, kf=3), - environmental.NoisePhase(4, 48.0, kt=3, kf=3)], - "evening": [environmental.NoisePhase(2, 48.0, kt=3, kf=3), - environmental.NoisePhase(2, 0.0, label="cerrada")]}, - environmental.activity_limits("a"), +verdict = environment.assess_activity( + {"day": [environment.NoisePhase(2, 0.0, label="cerrada"), + environment.NoisePhase(6, 50.0, kt=6, kf=3), + environment.NoisePhase(4, 48.0, kt=3, kf=3)], + "evening": [environment.NoisePhase(2, 48.0, kt=3, kf=3), + environment.NoisePhase(2, 0.0, label="cerrada")]}, + environment.activity_limits("a"), operating_days=303, ) @@ -34531,7 +34538,7 @@ exceeds 9 dB. - [Impulsive-sound prominence](https://jmrplens.github.io/phonometry/guides/impulse-prominence/): the ISO/PAS 1996-3 impulsive adjustment, the relative of $K_i$ that is not interchangeable with it. -- API reference: [`environmental.spanish_regulation`](https://jmrplens.github.io/phonometry/reference/api/environment/spain/). +- API reference: [`environment.assessment.spain`](https://jmrplens.github.io/phonometry/reference/api/environment/spain/). ## References @@ -43144,7 +43151,7 @@ sample from the metrology core: | ISO 7196:1995 Table 2 | G weighting (infrasound) at every nominal response value, 0.25–315 Hz | `tests/filters/test_g_weighting.py` | | ISO 226:2023 Table 1 and Annex B | Equal-loudness contours and loudness levels against the Annex B tables, hearing threshold against the Table 1 $T_f$ parameters | `tests/psychoacoustics/test_loudness_contours.py` | | ECMA-418-1:2024 | TNR/PR tone prominence: critical bandwidths, proximity spacing and prominence criteria against the worked examples in clauses 10–12 | `tests/psychoacoustics/test_tonality.py` | -| ISO 1996-1:2016 | `lden()`, `ldn()` and `composite_rating_level()` against hand-computed formula values | `tests/environmental/test_environmental.py` | +| ISO 1996-1:2016 | `lden()`, `ldn()` and `composite_rating_level()` against hand-computed formula values | `tests/environment/assessment/test_rating.py` | | IEC 60942:2017 Table 2 | Calibrator short-term stability limits (frequency-dependent, class 1) in `sensitivity()` | `tests/metrology/test_calibration_validation.py` | The same discipline applies far beyond the metrology core: today the suite runs @@ -43263,12 +43270,12 @@ pressure doubling. Side view of a horizontal-axis wind turbine with hub height H and rotor diameter D, a microphone lying on a flat ground board downwind at the horizontal distance R0 = H + D/2 from the tower centreline, the slant distance R1 from the rotor centre to the microphone with the board inclination angle phi between 25 and 40 degrees, and a met mast measuring wind speed and direction; a plan-view inset shows the Figure 3 pattern with the reference position downwind and three optional positions at plus and minus 60 degrees and upwind, and the annotations give R1 equals the square root of H squared plus R0 squared and the apparent sound power formula LWA,i = Lp,i minus 6 plus 10 lg(4 pi R1 squared over S0) ```python -from phonometry import environmental +from phonometry import environment # Background-corrected A-weighted one-third-octave band levels L_p,i (dB). band_levels = [55.0, 58.0, 60.0, 57.0, 54.0] -r1 = environmental.slant_distance(hub_height=80.0, rotor_diameter=100.0) -lwa = environmental.apparent_sound_power_level(band_levels, r1) # dB re 1 pW +r1 = environment.slant_distance(hub_height=80.0, rotor_diameter=100.0) +lwa = environment.apparent_sound_power_level(band_levels, r1) # dB re 1 pW ``` ### Why "apparent" @@ -43356,20 +43363,20 @@ $\Delta L_a > 0$. ```python import numpy as np -from phonometry import environmental +from phonometry import environment df = 2.0 freqs = np.arange(50.0, 400.0 + df, df) levels = 42.0 - 6.0 * np.log10(freqs / 100.0) levels[int(np.argmin(np.abs(freqs - 200.0)))] += 22.0 # blade-passing-style tone -environmental.wind_turbine_tonality(levels, freqs, tone_frequency=200.0).plot() +environment.wind_turbine_tonality(levels, freqs, tone_frequency=200.0).plot() ``` ```python import numpy as np -from phonometry import environmental +from phonometry import environment # A uniformly-spaced narrowband spectrum (2 Hz resolution): a flat 30 dB floor # with a discrete 60 dB tone at 500 Hz. @@ -43377,7 +43384,7 @@ frequencies = np.arange(440.0, 562.0, 2.0) levels = np.full(frequencies.size, 30.0) levels[np.argmin(np.abs(frequencies - 500.0))] = 60.0 -res = environmental.wind_turbine_tonality(levels, frequencies) +res = environment.wind_turbine_tonality(levels, frequencies) print(res.tone_frequency, res.tonality, res.tonal_audibility, res.is_audible) res.plot() # spectrum + critical band + masking level (needs matplotlib) ``` diff --git a/scripts/conformance_report.py b/scripts/conformance_report.py index bc3b94fd7..9f8ab72b8 100644 --- a/scripts/conformance_report.py +++ b/scripts/conformance_report.py @@ -1795,7 +1795,7 @@ def _chk_iso18233_sweep_deconvolution() -> Outcome: 20.0 * np.log10(np.abs(h_est[mask])) - 20.0 * np.log10(np.abs(h_true[mask])) ))) # Linear deconvolution is exact in-band up to windowing/regularisation - # leakage; 0.1 dB is the demonstrated in-band bound (tests/test_room_ir.py) + # leakage; 0.1 dB is the demonstrated in-band bound (tests/room/test_room_ir.py) # with the same 300-1500 Hz evaluation band, well inside the sweep edges. return numeric(0.0, worst, 0.1, unit="dB", places=4, expected_label="0 dB in-band error (+/-0.1 dB)") diff --git a/scripts/generate_graphs.py b/scripts/generate_graphs.py index 643326888..e4fb1475a 100644 --- a/scripts/generate_graphs.py +++ b/scripts/generate_graphs.py @@ -12738,7 +12738,7 @@ def generate_impulsive_sound_onsets(output_dir: str) -> None: print("Generating impulsive_sound_onsets...") import warnings as _warnings - from phonometry import environmental + from phonometry import environment # Three hammer strikes over a 55 dB(A) background, 6 s at 48 kHz: the # objective chain samples LpAF, detects the onsets and rates the source. @@ -12757,7 +12757,7 @@ def generate_impulsive_sound_onsets(output_dir: str) -> None: with _warnings.catch_warnings(): # The synthetic interval is shorter than the assessment period. _warnings.simplefilter("ignore") - res = environmental.impulsive_sound_adjustment(signal, fs) + res = environment.impulsive_sound_adjustment(signal, fs) _fig, ax = plt.subplots(figsize=(10, 6)) res.plot(ax=ax, language=_LANG) diff --git a/scripts/generate_reports.py b/scripts/generate_reports.py index d38a9d093..c0e377c14 100644 --- a/scripts/generate_reports.py +++ b/scripts/generate_reports.py @@ -2915,7 +2915,7 @@ def _outdoor_attenuation_example() -> tuple[object, ReportMetadata, str]: (source-edge = edge-receiver = 105 m, so the diffracted path exceeds the direct one). The divergence, atmospheric, ground and barrier terms come from the tested clause-7 functions (see - tests/environmental/test_outdoor_propagation.py). + tests/environment/propagation/test_outdoor_propagation.py). """ freqs = np.array([63, 125, 250, 500, 1000, 2000, 4000, 8000], dtype=float) lw = np.array([95, 100, 103, 105, 104, 101, 95, 88], dtype=float) @@ -2952,7 +2952,7 @@ def _barrier_insertion_loss_example() -> tuple[object, ReportMetadata, str]: A 4 m thin noise barrier 50 m from a source (1 m high), the receiver 1.5 m high at 100 m, in the free field. The per-band insertion loss comes from the tested wave-theoretic rigid-screen model (see - tests/environmental/test_ground_barriers.py). + tests/environment/propagation/test_ground_barriers.py). """ freqs = np.array([63, 125, 250, 500, 1000, 2000, 4000, 8000], dtype=float) result = ph.barrier_insertion_loss(freqs, 1.0, 50.0, 4.0, 100.0, 1.5) diff --git a/site/public/llms/llms-aircraft-wind.txt b/site/public/llms/llms-aircraft-wind.txt index 71b0d096b..7edf1ad62 100644 --- a/site/public/llms/llms-aircraft-wind.txt +++ b/site/public/llms/llms-aircraft-wind.txt @@ -1028,12 +1028,12 @@ pressure doubling. Side view of a horizontal-axis wind turbine with hub height H and rotor diameter D, a microphone lying on a flat ground board downwind at the horizontal distance R0 = H + D/2 from the tower centreline, the slant distance R1 from the rotor centre to the microphone with the board inclination angle phi between 25 and 40 degrees, and a met mast measuring wind speed and direction; a plan-view inset shows the Figure 3 pattern with the reference position downwind and three optional positions at plus and minus 60 degrees and upwind, and the annotations give R1 equals the square root of H squared plus R0 squared and the apparent sound power formula LWA,i = Lp,i minus 6 plus 10 lg(4 pi R1 squared over S0) ```python -from phonometry import environmental +from phonometry import environment # Background-corrected A-weighted one-third-octave band levels L_p,i (dB). band_levels = [55.0, 58.0, 60.0, 57.0, 54.0] -r1 = environmental.slant_distance(hub_height=80.0, rotor_diameter=100.0) -lwa = environmental.apparent_sound_power_level(band_levels, r1) # dB re 1 pW +r1 = environment.slant_distance(hub_height=80.0, rotor_diameter=100.0) +lwa = environment.apparent_sound_power_level(band_levels, r1) # dB re 1 pW ``` ### Why "apparent" @@ -1121,20 +1121,20 @@ $\Delta L_a > 0$. ```python import numpy as np -from phonometry import environmental +from phonometry import environment df = 2.0 freqs = np.arange(50.0, 400.0 + df, df) levels = 42.0 - 6.0 * np.log10(freqs / 100.0) levels[int(np.argmin(np.abs(freqs - 200.0)))] += 22.0 # blade-passing-style tone -environmental.wind_turbine_tonality(levels, freqs, tone_frequency=200.0).plot() +environment.wind_turbine_tonality(levels, freqs, tone_frequency=200.0).plot() ``` ```python import numpy as np -from phonometry import environmental +from phonometry import environment # A uniformly-spaced narrowband spectrum (2 Hz resolution): a flat 30 dB floor # with a discrete 60 dB tone at 500 Hz. @@ -1142,7 +1142,7 @@ frequencies = np.arange(440.0, 562.0, 2.0) levels = np.full(frequencies.size, 30.0) levels[np.argmin(np.abs(frequencies - 500.0))] = 60.0 -res = environmental.wind_turbine_tonality(levels, frequencies) +res = environment.wind_turbine_tonality(levels, frequencies) print(res.tone_frequency, res.tonality, res.tonal_audibility, res.is_audible) res.plot() # spectrum + critical band + masking level (needs matplotlib) ``` @@ -1249,12 +1249,12 @@ curves overtake it below ~200 Hz: the relaxation signature.* ```python import matplotlib.pyplot as plt import numpy as np -from phonometry import environmental +from phonometry import environment freqs = np.geomspace(50.0, 10000.0, 400) fig, ax = plt.subplots() for temp, rh in [(20.0, 50.0), (20.0, 10.0), (0.0, 70.0), (30.0, 80.0)]: - ax.loglog(freqs, environmental.air_attenuation(freqs, temp, rh) * 1000.0, + ax.loglog(freqs, environment.air_attenuation(freqs, temp, rh) * 1000.0, label=f"{temp:g} °C, {rh:g} % RH") ax.set_xlabel("Frequency [Hz]") ax.set_ylabel("Attenuation coefficient alpha [dB/km]") @@ -1266,21 +1266,21 @@ plt.show() ```python import numpy as np -from phonometry import environmental +from phonometry import environment bands = [63, 125, 250, 500, 1000, 2000, 4000, 8000] # octave-band centres [Hz] # Pure-tone attenuation coefficient alpha [dB/m] at 20 °C, 50 % RH, one atmosphere -alpha = environmental.air_attenuation(bands, temperature=20.0, relative_humidity=50.0) +alpha = environment.air_attenuation(bands, temperature=20.0, relative_humidity=50.0) print(np.round(alpha * 1000.0, 2)) # in dB/km, as Table 1 tabulates # [ 0.12 0.44 1.31 2.73 4.66 9.89 29.67 105.29] # Reproduce an ISO 9613-1 Table 1 cell exactly (10 °C, 70 %, 1 kHz) -cell = environmental.air_attenuation(1000.0, 10.0, 70.0, exact_midband=True) * 1000.0 +cell = environment.air_attenuation(1000.0, 10.0, 70.0, exact_midband=True) * 1000.0 print(round(float(cell), 2)) # 3.66 (dB/km, Table 1) # Feed real conditions into the ISO 354 power attenuation coefficient m [1/m] -m = environmental.air_attenuation_m([1000.0, 4000.0], temperature=20.0, relative_humidity=50.0) +m = environment.air_attenuation_m([1000.0, 4000.0], temperature=20.0, relative_humidity=50.0) print(np.round(m, 5)) # [0.00107 0.00683] ``` @@ -1323,9 +1323,9 @@ Table 1 unit, on a linear ordinate over a logarithmic frequency axis). Passing a that path as `total_attenuation`, the ISO 9613-2 $A_{atm}$ of Eq. (8). ```python -from phonometry import environmental +from phonometry import environment -res = environmental.atmospheric_attenuation( +res = environment.atmospheric_attenuation( [63, 125, 250, 500, 1000, 2000, 4000, 8000], temperature=20.0, relative_humidity=50.0, ) @@ -1344,10 +1344,10 @@ res.plot() # alpha in dB/km against frequency (needs matplotlib) ```python import matplotlib.pyplot as plt import numpy as np -from phonometry import environmental +from phonometry import environment # One line: the coefficient curve straight from the result. -res = environmental.atmospheric_attenuation( +res = environment.atmospheric_attenuation( np.geomspace(50.0, 10000.0, 400), temperature=20.0, relative_humidity=50.0, ) res.plot() @@ -1356,7 +1356,7 @@ plt.show() # Or by hand from air_attenuation (dB/m, so scale by 1000 for dB/km): freqs = np.geomspace(50.0, 10000.0, 400) fig, ax = plt.subplots() -ax.semilogx(freqs, environmental.air_attenuation(freqs, 20.0, 50.0) * 1000.0) +ax.semilogx(freqs, environment.air_attenuation(freqs, 20.0, 50.0) * 1000.0) ax.set_xlabel("Frequency [Hz]") ax.set_ylabel("Attenuation coefficient alpha [dB/km]") plt.show() @@ -1434,11 +1434,11 @@ mid bands. ```python import matplotlib.pyplot as plt import numpy as np -from phonometry import environmental +from phonometry import environment bands = np.array([63.0, 125.0, 250.0, 500.0, 1000.0, 2000.0, 4000.0, 8000.0]) -barrier = environmental.Barrier(source_to_edge=101.0, edge_to_receiver=101.0) -att = environmental.outdoor_propagation_attenuation( +barrier = environment.Barrier(source_to_edge=101.0, edge_to_receiver=101.0) +att = environment.outdoor_propagation_attenuation( 200.0, 1.5, 1.5, bands, ground_source=1.0, ground_middle=1.0, ground_receiver=1.0, barrier=barrier, temperature=15.0, relative_humidity=70.0, @@ -1475,15 +1475,15 @@ plt.show() ```python import numpy as np -from phonometry import environmental +from phonometry import environment bands = [63, 125, 250, 500, 1000, 2000, 4000, 8000] # octave-band centres [Hz] # A point source and receiver 1.5 m high, 200 m apart over porous ground # (G = 1), screened midway by a barrier that raises the path over its top edge # (dss = dsr ~ 101 m). Geometry feeds the pathlength-difference equations. -barrier = environmental.Barrier(source_to_edge=101.0, edge_to_receiver=101.0) -att = environmental.outdoor_propagation_attenuation( +barrier = environment.Barrier(source_to_edge=101.0, edge_to_receiver=101.0) +att = environment.outdoor_propagation_attenuation( 200.0, source_height=1.5, receiver_height=1.5, frequencies=bands, ground_source=1.0, ground_middle=1.0, ground_receiver=1.0, barrier=barrier, temperature=15.0, relative_humidity=70.0, @@ -1496,7 +1496,7 @@ att.plot() # the stacked breakdown above (needs matplotli # Predicted receiver level from an octave-band sound power Lw = 95 dB lw = np.full(len(bands), 95.0) -lp = environmental.predicted_receiver_level( +lp = environment.predicted_receiver_level( lw, 200.0, 1.5, 1.5, bands, 1.0, 1.0, 1.0, barrier=barrier, temperature=15.0, relative_humidity=70.0, ) @@ -1522,14 +1522,14 @@ general per-region method of 7.3.1); combine them by hand when the alternative method is appropriate. ```python -from phonometry import environmental +from phonometry import environment # Alternative ground term (mean path height hm = 2 m, d = 200 m) -print(round(environmental.ground_attenuation_alternative(200.0, 2.0), 2)) # 4.43 dB +print(round(environment.ground_attenuation_alternative(200.0, 2.0), 2)) # 4.43 dB # Its companion solid-angle index (add to Dc when using Eq. (10)) -print(round(environmental.directivity_omega(1.5, 1.5, 200.0), 2)) # 3.01 dB +print(round(environment.directivity_omega(1.5, 1.5, 200.0), 2)) # 3.01 dB # Long-term meteorological correction (C0 = 2 dB) to subtract from LAT(DW) -print(round(environmental.meteorological_correction(200.0, 1.5, 1.5, 2.0), 2)) # 1.7 dB +print(round(environment.meteorological_correction(200.0, 1.5, 1.5, 2.0), 2)) # 1.7 dB ``` ### The image source behind the ground effect diff --git a/site/public/llms/llms-levels-weighting.txt b/site/public/llms/llms-levels-weighting.txt index 85c62868d..82ff760e3 100644 --- a/site/public/llms/llms-levels-weighting.txt +++ b/site/public/llms/llms-levels-weighting.txt @@ -1469,12 +1469,12 @@ and `composite_rating_level()` the general whole-day composite of clause 6.5 +6 dB prominent tones): ```python -from phonometry import environmental +from phonometry import environment -l = environmental.lden(63.2, 58.1, 51.4) # from LAeq per period -r = environmental.composite_rating_level([(63.2, 12, 0.0), # day +l = environment.lden(63.2, 58.1, 51.4) # from LAeq per period +r = environment.composite_rating_level([(63.2, 12, 0.0), # day (58.1, 4, 5.0), # evening (+5) - (51.4, 8, 10.0)]) # night (+10) == environmental.lden + (51.4, 8, 10.0)]) # night (+10) == environment.lden ``` Synthetic 24-hour urban LAeq profile with day, evening and night bands, the +5 and +10 dB weighted period levels and the resulting Lden @@ -1485,7 +1485,7 @@ r = environmental.composite_rating_level([(63.2, 12, 0.0), # day ```python import numpy as np import matplotlib.pyplot as plt -from phonometry import environmental +from phonometry import environment # Synthetic hourly LAeq of an urban road (dB), hours 00 to 23 laeq_h = np.array([48, 46, 45, 45, 46, 50, 56, 64, 66, 65, 63, 63, @@ -1497,7 +1497,7 @@ def period_leq(idx): ld = period_leq(np.arange(7, 19)) # day 07-19 le = period_leq(np.arange(19, 23)) # evening 19-23 ln_ = period_leq(np.r_[23, np.arange(0, 7)]) # night 23-07 -l_den = environmental.lden(ld, le, ln_) +l_den = environment.lden(ld, le, ln_) print(f"Lden = {l_den:.1f} dB") # Lden = 64.3 dB fig, ax = plt.subplots() @@ -1563,10 +1563,10 @@ $K_t$ (Table J.1). ```python import matplotlib.pyplot as plt -from phonometry import environmental +from phonometry import environment # ISO 1996-2:2007 Annex C.5, Example 2 (two tones near 400 Hz): -res = environmental.assess_tonal_audibility(tone_level=54.1, masking_noise_level=45.2, +res = environment.assess_tonal_audibility(tone_level=54.1, masking_noise_level=45.2, centre_frequency=430.0) print(res.audibility, res.adjustment) # ΔLta ≈ 11.1 dB -> Kt = 6 dB res.plot() @@ -1593,20 +1593,20 @@ reported alongside as `approximate_uncertainty` and a warning when the levels spread beyond 3 dB, where the substitute grossly inflates. ```python -from phonometry import environmental +from phonometry import environment # Tonal adjustment for a prominent tone: -tonal = environmental.assess_tonal_audibility(54.1, 45.2, 430.0) # TonalAssessmentResult +tonal = environment.assess_tonal_audibility(54.1, 45.2, 430.0) # TonalAssessmentResult kt = tonal.adjustment # 6 dB tonal.plot() # this audibility on the Kt curve, as in the figure above # Subtract residual (background) noise from a measured level: -corr = environmental.residual_sound_correction(measured_level=58.0, residual_level=50.0) +corr = environment.residual_sound_correction(measured_level=58.0, residual_level=50.0) corr.corrected_level, corr.reliable # Combine an uncertainty budget and expand to 95 %: -u = environmental.combined_standard_uncertainty([0.59, 0.3, 2.0, 0.40, 0.38]) # 2.18 dB (G.2) -environmental.expanded_uncertainty(u) # 4.36 dB (k = 2) +u = environment.combined_standard_uncertainty([0.59, 0.3, 2.0, 0.40, 0.38]) # 2.18 dB (G.2) +environment.expanded_uncertainty(u) # 4.36 dB (k = 2) ``` ## Quick answers @@ -1631,8 +1631,8 @@ $L_{dn}$ (3.6.5) keeps only the +10 dB night penalty. - [Occupational Noise Exposure](https://jmrplens.github.io/phonometry/guides/occupational-exposure/): the workplace counterpart, from task samples to the daily exposure level with its uncertainty budget. -- API reference: [`environmental.measurement`](https://jmrplens.github.io/phonometry/reference/api/environment/measurement/) - and [`environmental.rating`](https://jmrplens.github.io/phonometry/reference/api/environment/rating/). +- API reference: [`environment.assessment.measurement`](https://jmrplens.github.io/phonometry/reference/api/environment/measurement/) + and [`environment.assessment.rating`](https://jmrplens.github.io/phonometry/reference/api/environment/rating/). ## References @@ -1700,20 +1700,20 @@ $K_t$ is evaluated band by band and, when several emergent tones are present, the largest of the resulting $K_t$ governs (step d): ```python -from phonometry import environmental +from phonometry import environment freqs = [100, 125, 160, 200, 250, 315, 400, 500, 630, 800, 1000] levels = [58.0, 60.0, 59.0, 61.0, 72.0, 62.0, 60.0, 58.0, 56.0, 54.0, 52.0] -kt = environmental.tonal_correction(levels, freqs) +kt = environment.tonal_correction(levels, freqs) kt.correction # 6 dB kt.governing_frequency # 250.0 Hz kt.differences[4] # Lt = 10.5 dB above the mean of the neighbours -environmental.low_frequency_correction(lceq=76.0, laeq=63.0) # Lf = 13 dB -> Kf = 3 dB -environmental.impulsive_correction(laieq=68.0, laeq=63.0) # Li = 5 dB -> Ki = 0 dB -environmental.corrected_level(63.0, kt=6, kf=3, ki=0) # LKeq = 72.0 dB -environmental.total_correction(kt=6, kf=6, ki=3) # 9.0 dB: the cap +environment.low_frequency_correction(lceq=76.0, laeq=63.0) # Lf = 13 dB -> Kf = 3 dB +environment.impulsive_correction(laieq=68.0, laeq=63.0) # Li = 5 dB -> Ki = 0 dB +environment.corrected_level(63.0, kt=6, kf=3, ki=0) # LKeq = 72.0 dB +environment.total_correction(kt=6, kf=6, ki=3) # 9.0 dB: the cap ``` ## Relationship with the ISO 1996 procedures @@ -1760,20 +1760,20 @@ own rounding: add 0.5 dB to the result and take the integer part, which is what `round_reported_level()` does. ```python -from phonometry import environmental +from phonometry import environment -environmental.RD1367_PERIOD_CLOCK_LIMITS["day"] # (7, 19) -environmental.RD1367_PERIOD_HOURS["day"] # 12.0 h +environment.RD1367_PERIOD_CLOCK_LIMITS["day"] # (7, 19) +environment.RD1367_PERIOD_HOURS["day"] # 12.0 h -day = [environmental.NoisePhase(2, 0.0, label="cerrada"), - environmental.NoisePhase(6, 50.0, kt=6, kf=3), - environmental.NoisePhase(4, 48.0, kt=3, kf=3)] +day = [environment.NoisePhase(2, 0.0, label="cerrada"), + environment.NoisePhase(6, 50.0, kt=6, kf=3), + environment.NoisePhase(4, 48.0, kt=3, kf=3)] -lkeq_d = environmental.evaluation_period_level(day) # 56.82 dB, unrounded -environmental.round_reported_level(lkeq_d) # 57 dB +lkeq_d = environment.evaluation_period_level(day) # 56.82 dB, unrounded +environment.round_reported_level(lkeq_d) # 57 dB -lk_d = environmental.long_term_corrected_level([57.0, 0.0], weights=[303, 62]) -environmental.round_reported_level(lk_d) # 56 dB (303 days open, 62 closed) +lk_d = environment.long_term_corrected_level([57.0, 0.0], weights=[303, 62]) +environment.round_reported_level(lk_d) # 56 dB (303 days open, 62 closed) ``` ## Acoustic quality objectives and immission limit values @@ -1801,16 +1801,16 @@ And in areas urbanised after the regulation entered into force (24 October `urbanisation="new"` applies. ```python -from phonometry import environmental - -environmental.outdoor_quality_objectives("a") # 65 / 65 / 55 dB -environmental.outdoor_quality_objectives("a", urbanisation="new") # 60 / 60 / 50 dB -environmental.indoor_quality_objectives("residential", "bedrooms") # 40 / 40 / 30 dB -environmental.infrastructure_limits("a") # 60 / 60 / 50 dB -environmental.max_infrastructure_limit("a") # 85 dB (LAmax) -environmental.activity_limits("a") # 55 / 55 / 45 dB -environmental.adjacent_premises_limits("residential", "bedrooms") # 35 / 35 / 25 dB -environmental.vibration_quality_objective("residential") # 75 dB +from phonometry import environment + +environment.outdoor_quality_objectives("a") # 65 / 65 / 55 dB +environment.outdoor_quality_objectives("a", urbanisation="new") # 60 / 60 / 50 dB +environment.indoor_quality_objectives("residential", "bedrooms") # 40 / 40 / 30 dB +environment.infrastructure_limits("a") # 60 / 60 / 50 dB +environment.max_infrastructure_limit("a") # 85 dB (LAmax) +environment.activity_limits("a") # 55 / 55 / 45 dB +environment.adjacent_premises_limits("residential", "bedrooms") # 35 / 35 / 25 dB +environment.vibration_quality_objective("residential") # 75 dB ``` Meeting an immission limit value is not simply staying below the number in the @@ -1843,16 +1843,16 @@ each $L_{Keq,Ti}$, 58 dB on the daily $L_{Keq,x}$ and 55 dB on the annual $L_{K,x}$: ```python -from phonometry import environmental +from phonometry import environment -day = [environmental.NoisePhase(2, 0.0, label="cerrada"), - environmental.NoisePhase(6, 50.0, kt=6, kf=3), - environmental.NoisePhase(4, 48.0, kt=3, kf=3)] -evening = [environmental.NoisePhase(2, 48.0, kt=3, kf=3), - environmental.NoisePhase(2, 0.0, label="cerrada")] +day = [environment.NoisePhase(2, 0.0, label="cerrada"), + environment.NoisePhase(6, 50.0, kt=6, kf=3), + environment.NoisePhase(4, 48.0, kt=3, kf=3)] +evening = [environment.NoisePhase(2, 48.0, kt=3, kf=3), + environment.NoisePhase(2, 0.0, label="cerrada")] -limits = environmental.activity_limits("a") # 55 / 55 / 45 dB -verdict = environmental.assess_activity( +limits = environment.activity_limits("a") # 55 / 55 / 45 dB +verdict = environment.assess_activity( {"day": day, "evening": evening}, limits, operating_days=303) verdict.periods[0].reported_level # 57 dB (LKeq,d) @@ -1867,17 +1867,17 @@ verdict.complies # False: LK,d exceeds 55 dB ```python import matplotlib.pyplot as plt -from phonometry import environmental +from phonometry import environment -day = [environmental.NoisePhase(2, 0.0, label="cerrada"), - environmental.NoisePhase(6, 50.0, kt=6, kf=3), - environmental.NoisePhase(4, 48.0, kt=3, kf=3)] -evening = [environmental.NoisePhase(2, 48.0, kt=3, kf=3), - environmental.NoisePhase(2, 0.0, label="cerrada")] +day = [environment.NoisePhase(2, 0.0, label="cerrada"), + environment.NoisePhase(6, 50.0, kt=6, kf=3), + environment.NoisePhase(4, 48.0, kt=3, kf=3)] +evening = [environment.NoisePhase(2, 48.0, kt=3, kf=3), + environment.NoisePhase(2, 0.0, label="cerrada")] -verdict = environmental.assess_activity( +verdict = environment.assess_activity( {"day": day, "evening": evening}, - environmental.activity_limits("a"), + environment.activity_limits("a"), operating_days=303, ) verdict.plot() @@ -1908,15 +1908,15 @@ applicable limit values and the boxed verdict. The default language is Spanish, the language of the regulation; `language="en"` translates it. ```python -from phonometry import environmental - -verdict = environmental.assess_activity( - {"day": [environmental.NoisePhase(2, 0.0, label="cerrada"), - environmental.NoisePhase(6, 50.0, kt=6, kf=3), - environmental.NoisePhase(4, 48.0, kt=3, kf=3)], - "evening": [environmental.NoisePhase(2, 48.0, kt=3, kf=3), - environmental.NoisePhase(2, 0.0, label="cerrada")]}, - environmental.activity_limits("a"), +from phonometry import environment + +verdict = environment.assess_activity( + {"day": [environment.NoisePhase(2, 0.0, label="cerrada"), + environment.NoisePhase(6, 50.0, kt=6, kf=3), + environment.NoisePhase(4, 48.0, kt=3, kf=3)], + "evening": [environment.NoisePhase(2, 48.0, kt=3, kf=3), + environment.NoisePhase(2, 0.0, label="cerrada")]}, + environment.activity_limits("a"), operating_days=303, ) @@ -1962,7 +1962,7 @@ exceeds 9 dB. - [Impulsive-sound prominence](https://jmrplens.github.io/phonometry/guides/impulse-prominence/): the ISO/PAS 1996-3 impulsive adjustment, the relative of $K_i$ that is not interchangeable with it. -- API reference: [`environmental.spanish_regulation`](https://jmrplens.github.io/phonometry/reference/api/environment/spain/). +- API reference: [`environment.assessment.spain`](https://jmrplens.github.io/phonometry/reference/api/environment/spain/). ## References diff --git a/site/public/llms/llms-outdoor-sound.txt b/site/public/llms/llms-outdoor-sound.txt index 041585dc5..5de0f2667 100644 --- a/site/public/llms/llms-outdoor-sound.txt +++ b/site/public/llms/llms-outdoor-sound.txt @@ -456,7 +456,7 @@ Source: https://jmrplens.github.io/phonometry/guides/ground-barriers/ The [ISO 9613-2 general method](https://jmrplens.github.io/phonometry/guides/outdoor-propagation/) folds the ground and barrier terms into tabulated, energy-based corrections. This page covers the -underlying wave acoustics in `phonometry.environment.ground_barriers`: the +underlying wave acoustics in `phonometry.environment.propagation.ground_barriers`: the **spherical-wave reflection coefficient** of a finite-impedance ground (Weyl-Van der Pol) and the **wave-theoretic diffraction** of a screen, both in a homogeneous (non-refracting, non-turbulent) atmosphere. These are the physical @@ -1112,10 +1112,10 @@ $K_I$ (the adjustment is 0 dB when no event qualifies). [Watch the high-resolution video (WebM)](https://raw.githubusercontent.com/jmrplens/phonometry/main/.github/images/anim_onset_detection.webm) ```python -from phonometry import environmental +from phonometry import environment # Three candidate impulses: (onset rate dB/s, level difference dB). -result = environmental.impulse_prominence([1200.0, 300.0, 60.0], [32.0, 18.0, 11.0]) +result = environment.impulse_prominence([1200.0, 300.0, 60.0], [32.0, 18.0, 11.0]) print(result.per_impulse.round(2)) # [12.25 9.94 7.42] print(round(result.prominence, 2)) # 12.25 (the governing impulse) print(round(result.adjustment, 2)) # 13.05 dB @@ -1126,10 +1126,10 @@ result.plot() # the KI(P) curve of section 2 with these impulses marked A single impulse can be evaluated directly with `predicted_prominence`: ```python -from phonometry import environmental +from phonometry import environment # P = 3*lg(1000) + 2*lg(30) = 9 + 2.95 = 11.95. -print(round(environmental.predicted_prominence(1000.0, 30.0), 4)) # 11.9542 +print(round(environment.predicted_prominence(1000.0, 30.0), 4)) # 11.9542 ``` ## 2. Adjustment to LAeq (clause 8) @@ -1142,10 +1142,10 @@ K_I = 1.8\,(P - 5)\ \text{dB} \quad (P > 5), \qquad K_I = 0 \quad (P \le 5). $$ ```python -from phonometry import environmental +from phonometry import environment -print(float(environmental.impulse_adjustment(10.0))) # 9.0 dB -print(float(environmental.impulse_adjustment(5.0))) # 0.0 dB (at the threshold) +print(float(environment.impulse_adjustment(10.0))) # 9.0 dB +print(float(environment.impulse_adjustment(5.0))) # 0.0 dB (at the threshold) ``` The adjustment is applied to $L_{Aeq,30\text{min}}$ from the single event with the @@ -1158,10 +1158,10 @@ L_{Ar,T} = 10\,\log_{10}\!\left(\frac{1}{T}\sum_N \Delta t_N\, $$ ```python -from phonometry import environmental +from phonometry import environment # Two 30-min periods: one impulsive (KI = 7.6 dB), one quiet. -print(round(environmental.rating_level([72.0, 66.0], [7.6, 0.0], [30.0, 30.0], 60.0), 2)) +print(round(environment.rating_level([72.0, 66.0], [7.6, 0.0], [30.0, 30.0], 60.0), 2)) # 76.78 dB ``` @@ -1178,16 +1178,16 @@ measurement of ISO 1996-2. ```python import numpy as np import matplotlib.pyplot as plt -from phonometry import environmental +from phonometry import environment # One line for the adjustment curve with the impulses marked: -environmental.impulse_prominence([1200.0, 300.0, 60.0], [32.0, 18.0, 11.0]).plot() +environment.impulse_prominence([1200.0, 300.0, 60.0], [32.0, 18.0, 11.0]).plot() plt.show() # By hand, the left panel — P vs onset rate for three level differences: orate = np.logspace(1, 4, 200) for ld in (5.0, 15.0, 30.0): - plt.plot(orate, environmental.predicted_prominence(orate, np.full_like(orate, ld)), + plt.plot(orate, environment.predicted_prominence(orate, np.full_like(orate, ld)), label=f"LD = {ld:g} dB") plt.xscale("log"); plt.legend(); plt.show() ``` @@ -1215,7 +1215,7 @@ The onset detection can be exercised on a level history directly with ```python import numpy as np -from phonometry import environmental +from phonometry import environment # An LpAF history sampled every 20 ms: quiet, a 30 dB rise over 0.30 s, steady. dt = 0.02 @@ -1224,7 +1224,7 @@ levels = np.concatenate([ 40.0 + 30.0 * np.arange(1, 16) / 15, # a straight 100 dB/s onset to 70 dB np.full(15, 70.0), ]) -onset = environmental.detect_onsets(levels, dt)[0] +onset = environment.detect_onsets(levels, dt)[0] print(round(onset.onset_rate), round(onset.level_difference)) # 100 30 print(round(onset.prominence, 2)) # 8.95 ``` @@ -1232,7 +1232,7 @@ print(round(onset.prominence, 2)) # 8.95 From a calibrated time signal (in pascal) the whole chain runs end to end: ```python -result = environmental.impulsive_sound_adjustment(signal, fs) +result = environment.impulsive_sound_adjustment(signal, fs) print(result.category) # e.g. 'highly impulsive' print(round(result.adjustment, 1)) # KI in dB (0.0 to about 9 dB in typical cases) print(round(result.adjusted_laeq, 1)) # LAeq + KI @@ -1248,7 +1248,7 @@ result.plot() # the LpAF history with the detected onsets (needs matplotlib) ```python import numpy as np import matplotlib.pyplot as plt -from phonometry import environmental +from phonometry import environment # Three hammer strikes over a 55 dB(A) background, 6 s at 48 kHz. fs = 48000 @@ -1264,7 +1264,7 @@ for onset_time in (1.0, 2.6, 4.2): strike *= 2e-5 * 10 ** (95 / 20) / np.sqrt(np.mean(strike[window] ** 2)) signal += strike -res = environmental.impulsive_sound_adjustment(signal, fs) +res = environment.impulsive_sound_adjustment(signal, fs) print(res.category, round(res.prominence, 2), round(res.adjustment, 2)) # highly impulsive 11.34 11.42 @@ -1340,10 +1340,10 @@ pass `language="es"` for a Spanish fiche (translated fixed strings and a comma decimal separator), e.g. `res.report("impulse_fiche_es.pdf", language="es")`. ```python -from phonometry import environmental, ReportMetadata +from phonometry import environment, ReportMetadata # The three-impulse pile-driving set (onset rate dB/s, level difference dB). -res = environmental.impulse_prominence([1200.0, 300.0, 60.0], [32.0, 18.0, 11.0]) +res = environment.impulse_prominence([1200.0, 300.0, 60.0], [32.0, 18.0, 11.0]) res.report( "impulse_fiche.pdf", metadata=ReportMetadata( diff --git a/site/public/llms/llms-room-acoustics.txt b/site/public/llms/llms-room-acoustics.txt index a3289cb62..eeb2108d2 100644 --- a/site/public/llms/llms-room-acoustics.txt +++ b/site/public/llms/llms-room-acoustics.txt @@ -1581,7 +1581,7 @@ $c_0 = 343\ \mathrm{m/s}$) and the air-absorption term $4mV$. ```python import matplotlib.pyplot as plt -from phonometry import environmental, room +from phonometry import environment, room # A 10 x 7 x 3.5 m room: hard end walls, lightly treated side walls and a # very absorptive floor/ceiling pair (carpet plus an acoustic ceiling). @@ -1589,7 +1589,7 @@ bands = [125.0, 250.0, 500.0, 1000.0, 2000.0, 4000.0] alpha_x = [0.06, 0.07, 0.08, 0.09, 0.10, 0.10] alpha_y = [0.12, 0.14, 0.16, 0.18, 0.20, 0.20] alpha_z = [0.30, 0.50, 0.65, 0.78, 0.82, 0.80] -m = environmental.air_attenuation_m(bands, 20.0, 50.0) # air at 20 C / 50 % RH +m = environment.air_attenuation_m(bands, 20.0, 50.0) # air at 20 C / 50 % RH res = room.reverberation_time_models((10.0, 7.0, 3.5), (alpha_x, alpha_y, alpha_z), air_attenuation=m, frequencies=bands) @@ -1633,9 +1633,9 @@ power-attenuation coefficient $m$ (in neper per metre, from the ISO 9613-1 [atmospheric absorption](https://jmrplens.github.io/phonometry/guides/outdoor-propagation/)): ```python -from phonometry import environmental, room +from phonometry import environment, room -m = environmental.air_attenuation_m(2000.0, temperature=20.0, relative_humidity=50.0) +m = environment.air_attenuation_m(2000.0, temperature=20.0, relative_humidity=50.0) surfaces = [(40.0, 0.3), (40.0, 0.3), (24.0, 0.3), (24.0, 0.3), (15.0, 0.3), (15.0, 0.3)] print(round(room.eyring_reverberation_time(120.0, surfaces, air_attenuation=m), 3)) @@ -1712,9 +1712,9 @@ res.plot() # the five model curves per band (the figure above) ```python import matplotlib.pyplot as plt -from phonometry import environmental, room +from phonometry import environment, room -m = environmental.air_attenuation_m([125.0, 250.0, 500.0, 1000.0, 2000.0, 4000.0], 20.0, 50.0) +m = environment.air_attenuation_m([125.0, 250.0, 500.0, 1000.0, 2000.0, 4000.0], 20.0, 50.0) room.reverberation_time_models( (10.0, 7.0, 3.5), ( diff --git a/site/public/llms/llms-simulation.txt b/site/public/llms/llms-simulation.txt index 0716a3246..534a802c5 100644 --- a/site/public/llms/llms-simulation.txt +++ b/site/public/llms/llms-simulation.txt @@ -14,7 +14,7 @@ The [ISO 9613-2 method](https://jmrplens.github.io/phonometry/guides/outdoor-pro atmosphere. In reality the sound speed changes with height, because temperature and wind change with height, and this **refracts** sound: rays curve, and over a few hundred metres the received level can swing by tens of decibels. This page -covers `phonometry.environment.atmospheric_refraction`, the +covers `phonometry.environment.propagation.refraction`, the refracting-atmosphere counterpart of the ocean solvers in [`phonometry.underwater.numerical_propagation`](https://jmrplens.github.io/phonometry/guides/underwater-solvers/): a **ray model** and a **parabolic-equation (PE)** solver, both clean-room from diff --git a/site/public/llms/llms-start.txt b/site/public/llms/llms-start.txt index 04599dc7a..063e4b7e1 100644 --- a/site/public/llms/llms-start.txt +++ b/site/public/llms/llms-start.txt @@ -299,7 +299,7 @@ sample from the metrology core: | ISO 7196:1995 Table 2 | G weighting (infrasound) at every nominal response value, 0.25–315 Hz | `tests/filters/test_g_weighting.py` | | ISO 226:2023 Table 1 and Annex B | Equal-loudness contours and loudness levels against the Annex B tables, hearing threshold against the Table 1 $T_f$ parameters | `tests/psychoacoustics/test_loudness_contours.py` | | ECMA-418-1:2024 | TNR/PR tone prominence: critical bandwidths, proximity spacing and prominence criteria against the worked examples in clauses 10–12 | `tests/psychoacoustics/test_tonality.py` | -| ISO 1996-1:2016 | `lden()`, `ldn()` and `composite_rating_level()` against hand-computed formula values | `tests/environmental/test_environmental.py` | +| ISO 1996-1:2016 | `lden()`, `ldn()` and `composite_rating_level()` against hand-computed formula values | `tests/environment/assessment/test_rating.py` | | IEC 60942:2017 Table 2 | Calibrator short-term stability limits (frequency-dependent, class 1) in `sensitivity()` | `tests/metrology/test_calibration_validation.py` | The same discipline applies far beyond the metrology core: today the suite runs diff --git a/site/src/content/docs/es/guides/atmospheric-refraction.mdx b/site/src/content/docs/es/guides/atmospheric-refraction.mdx index fa2b532c4..5aab81110 100644 --- a/site/src/content/docs/es/guides/atmospheric-refraction.mdx +++ b/site/src/content/docs/es/guides/atmospheric-refraction.mdx @@ -28,7 +28,7 @@ ambos una atmósfera **homogénea**. En realidad la velocidad del sonido cambia con la altura, porque la temperatura y el viento cambian con la altura, y esto **refracta** el sonido: los rayos se curvan y, en unos cientos de metros, el nivel recibido puede variar en decenas de decibelios. Esta página cubre -`phonometry.environment.atmospheric_refraction`, el homólogo para atmósfera +`phonometry.environment.propagation.refraction`, el homólogo para atmósfera refractante de los solvers oceánicos de [`phonometry.underwater.numerical_propagation`](/phonometry/es/guides/underwater-solvers/): un **modelo de rayos** y un solver de **ecuación parabólica (PE)**. diff --git a/site/src/content/docs/es/guides/cnossos-rail-emission.mdx b/site/src/content/docs/es/guides/cnossos-rail-emission.mdx index 25928f490..6da82b458 100644 --- a/site/src/content/docs/es/guides/cnossos-rail-emission.mdx +++ b/site/src/content/docs/es/guides/cnossos-rail-emission.mdx @@ -500,7 +500,7 @@ depósitos, estaciones y megafonía, se tratan por el método industrial. ## Véase también -- Referencia de la API: [`environmental.cnossos_rail`](/phonometry/es/reference/api/environment/cnossos-rail/). +- Referencia de la API: [`environment.sources.cnossos_rail`](/phonometry/es/reference/api/environment/cnossos-rail/). - [Propagación del sonido en exteriores](/phonometry/es/guides/outdoor-propagation/): la cadena ISO 9613-2 que lleva una potencia de fuente hasta un receptor. - [Niveles de ruido ambiental](/phonometry/es/guides/environmental-levels/): los diff --git a/site/src/content/docs/es/guides/cnossos-road-emission.mdx b/site/src/content/docs/es/guides/cnossos-road-emission.mdx index e593201aa..8a28ad4a4 100644 --- a/site/src/content/docs/es/guides/cnossos-road-emission.mdx +++ b/site/src/content/docs/es/guides/cnossos-road-emission.mdx @@ -379,7 +379,7 @@ fuente en fuentes puntuales lo declara fuera de alcance el propio método. ## Véase también -- Referencia de la API: [`environmental.cnossos_road`](/phonometry/es/reference/api/environment/cnossos-road/). +- Referencia de la API: [`environment.sources.cnossos_road`](/phonometry/es/reference/api/environment/cnossos-road/). - [Propagación del sonido en exteriores](/phonometry/es/guides/outdoor-propagation/): la cadena ISO 9613-2 que lleva una potencia de fuente hasta un receptor. - [Niveles de ruido ambiental](/phonometry/es/guides/environmental-levels/): los diff --git a/site/src/content/docs/es/guides/environmental-levels.mdx b/site/src/content/docs/es/guides/environmental-levels.mdx index 65b9ddf21..aefacfab4 100644 --- a/site/src/content/docs/es/guides/environmental-levels.mdx +++ b/site/src/content/docs/es/guides/environmental-levels.mdx @@ -212,7 +212,7 @@ aplicados. - [Audibilidad objetiva de tonos en ruido (ISO/PAS 20065)](/phonometry/es/guides/tone-audibility/): la audibilidad tonal cuyo valor medio se traduce en el ajuste $K_t$ (Tabla J.1). - [Tonos discretos prominentes](/phonometry/es/guides/tone-prominence/): los veredictos de relación tono-ruido y de prominencia de ECMA-418-1, cribado de emisión complementario para la cuestión tonal. - [Exposición laboral (ISO 9612)](/phonometry/es/guides/occupational-exposure/): la contraparte en el puesto de trabajo, de las muestras por tarea al nivel de exposición diario con su balance de incertidumbre. -- Referencia de la API: [`environmental.measurement`](/phonometry/es/reference/api/environment/measurement/) y [`environmental.rating`](/phonometry/es/reference/api/environment/rating/). +- Referencia de la API: [`environment.assessment.measurement`](/phonometry/es/reference/api/environment/measurement/) y [`environment.assessment.rating`](/phonometry/es/reference/api/environment/rating/). ## Respuestas rápidas diff --git a/site/src/content/docs/es/guides/ground-barriers.mdx b/site/src/content/docs/es/guides/ground-barriers.mdx index 739983e26..67f5a3e19 100644 --- a/site/src/content/docs/es/guides/ground-barriers.mdx +++ b/site/src/content/docs/es/guides/ground-barriers.mdx @@ -51,7 +51,7 @@ import ThemeImage from '../../../../components/ThemeImage.astro'; El [método general ISO 9613-2](/phonometry/es/guides/outdoor-propagation/) pliega los términos de suelo y barrera en correcciones tabuladas basadas en energía. Esta página cubre la acústica ondulatoria subyacente en -`phonometry.environment.ground_barriers`: el **coeficiente de reflexión de +`phonometry.environment.propagation.ground_barriers`: el **coeficiente de reflexión de onda esférica** de un suelo de impedancia finita (Weyl-Van der Pol) y la **difracción por teoría ondulatoria** de una pantalla, ambos en una atmósfera homogénea (sin refracción ni turbulencia). Son el núcleo físico de los modelos diff --git a/site/src/content/docs/es/guides/impulse-prominence.mdx b/site/src/content/docs/es/guides/impulse-prominence.mdx index 8a45a23fe..0046d93db 100644 --- a/site/src/content/docs/es/guides/impulse-prominence.mdx +++ b/site/src/content/docs/es/guides/impulse-prominence.mdx @@ -365,4 +365,4 @@ sustituye esta medición, no como una función propia. ## Véase también - Referencia de la API: [`environment.impulse_prominence`](/phonometry/es/reference/api/environment/impulse-prominence/). -- Referencia de la API: [`environmental.impulsive_sound`](/phonometry/es/reference/api/environment/impulsive-sound/). +- Referencia de la API: [`environment.assessment.impulsive_sound`](/phonometry/es/reference/api/environment/impulsive-sound/). diff --git a/site/src/content/docs/es/guides/outdoor-propagation.mdx b/site/src/content/docs/es/guides/outdoor-propagation.mdx index bb8b44ebf..74609952b 100644 --- a/site/src/content/docs/es/guides/outdoor-propagation.mdx +++ b/site/src/content/docs/es/guides/outdoor-propagation.mdx @@ -703,4 +703,4 @@ coherente. ## Véase también -- Referencia de la API: [`environmental.outdoor_propagation`](/phonometry/es/reference/api/environment/outdoor-propagation/) y [`environmental.air_absorption`](/phonometry/es/reference/api/environment/air-absorption/). +- Referencia de la API: [`environment.propagation.outdoor_propagation`](/phonometry/es/reference/api/environment/outdoor-propagation/) y [`environment.propagation.air_absorption`](/phonometry/es/reference/api/environment/air-absorption/). diff --git a/site/src/content/docs/es/guides/reverberation-prediction.mdx b/site/src/content/docs/es/guides/reverberation-prediction.mdx index 1231f4afe..00c7125a4 100644 --- a/site/src/content/docs/es/guides/reverberation-prediction.mdx +++ b/site/src/content/docs/es/guides/reverberation-prediction.mdx @@ -452,4 +452,4 @@ de una predicción estadística. ## Véase también -- Referencia de la API: [`room.reverberation_prediction`](/phonometry/es/reference/api/rooms/reverberation-prediction/) y [`environmental.air_absorption`](/phonometry/es/reference/api/environment/air-absorption/). +- Referencia de la API: [`room.reverberation_prediction`](/phonometry/es/reference/api/rooms/reverberation-prediction/) y [`environment.propagation.air_absorption`](/phonometry/es/reference/api/environment/air-absorption/). diff --git a/site/src/content/docs/es/guides/spanish-noise-regulation.mdx b/site/src/content/docs/es/guides/spanish-noise-regulation.mdx index 3dac0e7c3..b6001ce53 100644 --- a/site/src/content/docs/es/guides/spanish-noise-regulation.mdx +++ b/site/src/content/docs/es/guides/spanish-noise-regulation.mdx @@ -314,7 +314,7 @@ donde termina el sonómetro. - [Niveles integrados y estadísticos](/phonometry/es/guides/levels/): los niveles $L_{Aeq}$, $L_{Ceq}$ y $L_{AIeq}$ por fase que alimentan las tres correcciones. - [Código Técnico de la Edificación (CTE DB-HR)](/phonometry/es/guides/spanish-building-code/): la otra mitad de la normativa española, que comparte con esta el índice de ruido día $L_d$ del emplazamiento. - [Prominencia de sonidos impulsivos](/phonometry/es/guides/impulse-prominence/): el ajuste por impulsividad de ISO/PAS 1996-3, el pariente del $K_i$ que no es intercambiable con él. -- Referencia de la API: [`environmental.spanish_regulation`](/phonometry/es/reference/api/environment/spain/). +- Referencia de la API: [`environment.assessment.spain`](/phonometry/es/reference/api/environment/spain/). ## Respuestas rápidas diff --git a/site/src/content/docs/es/guides/wind-turbine-noise.mdx b/site/src/content/docs/es/guides/wind-turbine-noise.mdx index b8719441c..3409a005f 100644 --- a/site/src/content/docs/es/guides/wind-turbine-noise.mdx +++ b/site/src/content/docs/es/guides/wind-turbine-noise.mdx @@ -177,4 +177,4 @@ $K_T$, pasa la audibilidad media al `tonal_adjustment` de la ISO 1996-2. ## Véase también -- Referencia de la API: [`environmental.wind_turbine_noise`](/phonometry/es/reference/api/environment/wind-turbine/). +- Referencia de la API: [`environment.sources.wind_turbine`](/phonometry/es/reference/api/environment/wind-turbine/). diff --git a/site/src/content/docs/es/reference/errata.md b/site/src/content/docs/es/reference/errata.md index bccc4530d..d37a3af1b 100644 --- a/site/src/content/docs/es/reference/errata.md +++ b/site/src/content/docs/es/reference/errata.md @@ -1149,7 +1149,7 @@ which is the check that enforces the rule; see printed with them. - **Library behaviour:** implements the spelled-out reading, weighting the onset rate by 3 and the level difference by 2 (`predicted_prominence` in - [`impulse_prominence.py`](https://github.com/jmrplens/phonometry/blob/main/src/phonometry/environmental/impulse_prominence.py)), + [`impulse_prominence.py`](https://github.com/jmrplens/phonometry/blob/main/src/phonometry/environment/assessment/impulse_prominence.py)), which is also the NT ACOU 112:2002 form the PAS carries over. - **Status:** unreported. @@ -1171,7 +1171,7 @@ which is the check that enforces the rule; see 1 kHz being both). - **Library behaviour:** unaffected. The library never reads Table 2: it computes $A_\text{atm}$ from the ISO 9613-1 formula directly - ([`air_absorption.py`](https://github.com/jmrplens/phonometry/blob/main/src/phonometry/environmental/air_absorption.py)), + ([`air_absorption.py`](https://github.com/jmrplens/phonometry/blob/main/src/phonometry/environment/propagation/air_absorption.py)), so it yields 4,15 dB/km for this condition. - **Status:** unreported. @@ -2004,7 +2004,7 @@ which is the check that enforces the rule; see in the autonomous-community noise regulations that transpose this Annex print `10 < Lf <= 15`. - **Library behaviour:** - [`low_frequency_correction`](https://github.com/jmrplens/phonometry/blob/main/src/phonometry/environmental/spanish_regulation.py) + [`low_frequency_correction`](https://github.com/jmrplens/phonometry/blob/main/src/phonometry/environment/assessment/spain.py) and `impulsive_correction` implement $10 < L \le 15$, with a regression test pinning the three branches at the 10 dB and 15 dB boundaries. - **Status:** unreported (national regulation, not a standards body). @@ -2030,7 +2030,7 @@ which is the check that enforces the rule; see from 125 Hz to 4 kHz", read: "each octave band i from 63 Hz to 8 kHz"'. The same corrigendum also adds "octave bands" to the frequency range of 2.1.1. - **Library behaviour:** - [`cnossos_road`](https://github.com/jmrplens/phonometry/blob/main/src/phonometry/environmental/cnossos_road.py) works over + [`cnossos_road`](https://github.com/jmrplens/phonometry/blob/main/src/phonometry/environment/sources/cnossos_road.py) works over the corrected 63 Hz to 8 kHz grid (`ROAD_OCTAVE_BANDS`), pinned by `test_octave_bands_are_the_corrected_range` and by the workbook cases, whose published levels cover all eight bands. diff --git a/site/src/content/docs/es/reference/why-phonometry.mdx b/site/src/content/docs/es/reference/why-phonometry.mdx index f8e772818..2cdbe8ee9 100644 --- a/site/src/content/docs/es/reference/why-phonometry.mdx +++ b/site/src/content/docs/es/reference/why-phonometry.mdx @@ -107,7 +107,7 @@ metrología: | ISO 7196:1995 Tabla 2 | Ponderación G (infrasonidos) en todos los valores nominales de respuesta, 0,25–315 Hz | `tests/filters/test_g_weighting.py` | | ISO 226:2023 Tabla 1 y Anexo B | Líneas isofónicas y niveles de sonoridad frente a las tablas del Anexo B, umbral de audición frente a los parámetros $T_f$ de la Tabla 1 | `tests/psychoacoustics/test_loudness_contours.py` | | ECMA-418-1:2024 | Prominencia tonal TNR/PR: anchos de banda críticos, separación de proximidad y criterios de prominencia frente a los ejemplos resueltos de los apartados 10–12 | `tests/psychoacoustics/test_tonality.py` | -| ISO 1996-1:2016 | `lden()`, `ldn()` y `composite_rating_level()` frente a valores de las fórmulas calculados a mano | `tests/environmental/test_environmental.py` | +| ISO 1996-1:2016 | `lden()`, `ldn()` y `composite_rating_level()` frente a valores de las fórmulas calculados a mano | `tests/environment/assessment/test_rating.py` | | IEC 60942:2017 Tabla 2 | Límites de estabilidad a corto plazo del calibrador (dependientes de la frecuencia, clase 1) en `sensitivity()` | `tests/metrology/test_calibration_validation.py` | La misma disciplina se aplica mucho más allá del núcleo de metrología: hoy la diff --git a/site/src/content/docs/guides/atmospheric-refraction.mdx b/site/src/content/docs/guides/atmospheric-refraction.mdx index f5a12dd6d..3b708ef81 100644 --- a/site/src/content/docs/guides/atmospheric-refraction.mdx +++ b/site/src/content/docs/guides/atmospheric-refraction.mdx @@ -28,7 +28,7 @@ a **homogeneous** atmosphere. In reality the sound speed changes with height, because temperature and wind change with height, and this **refracts** sound: rays curve, and over a few hundred metres the received level can swing by tens of decibels. This page covers -`phonometry.environment.atmospheric_refraction`, the refracting-atmosphere +`phonometry.environment.propagation.refraction`, the refracting-atmosphere counterpart of the ocean solvers in [`phonometry.underwater.numerical_propagation`](/phonometry/guides/underwater-solvers/): a **ray model** and a **parabolic-equation (PE)** solver. diff --git a/site/src/content/docs/guides/cnossos-rail-emission.mdx b/site/src/content/docs/guides/cnossos-rail-emission.mdx index 509495d31..60f6a6d1b 100644 --- a/site/src/content/docs/guides/cnossos-rail-emission.mdx +++ b/site/src/content/docs/guides/cnossos-rail-emission.mdx @@ -485,7 +485,7 @@ such as depots, stations and loudspeakers, are treated by the industrial method. ## See also -- API reference: [`environmental.cnossos_rail`](/phonometry/reference/api/environment/cnossos-rail/). +- API reference: [`environment.sources.cnossos_rail`](/phonometry/reference/api/environment/cnossos-rail/). - [Outdoor Sound Propagation](/phonometry/guides/outdoor-propagation/): the ISO 9613-2 chain that carries a source power to a receiver. - [Environmental noise levels](/phonometry/guides/environmental-levels/): the diff --git a/site/src/content/docs/guides/cnossos-road-emission.mdx b/site/src/content/docs/guides/cnossos-road-emission.mdx index 058eebc0b..d25f35679 100644 --- a/site/src/content/docs/guides/cnossos-road-emission.mdx +++ b/site/src/content/docs/guides/cnossos-road-emission.mdx @@ -368,7 +368,7 @@ split into point sources is declared out of scope by the method itself. ## See also -- API reference: [`environmental.cnossos_road`](/phonometry/reference/api/environment/cnossos-road/). +- API reference: [`environment.sources.cnossos_road`](/phonometry/reference/api/environment/cnossos-road/). - [Outdoor Sound Propagation](/phonometry/guides/outdoor-propagation/): the ISO 9613-2 chain that carries a source power to a receiver. - [Environmental noise levels](/phonometry/guides/environmental-levels/): the diff --git a/site/src/content/docs/guides/environmental-levels.mdx b/site/src/content/docs/guides/environmental-levels.mdx index 46b670166..51f216548 100644 --- a/site/src/content/docs/guides/environmental-levels.mdx +++ b/site/src/content/docs/guides/environmental-levels.mdx @@ -207,7 +207,7 @@ implemented, only the arithmetic that follows once you have applied them. - [Objective audibility of tones in noise (ISO/PAS 20065)](/phonometry/guides/tone-audibility/): the tonal audibility whose mean value maps to the $K_t$ adjustment (Table J.1). - [Prominent Discrete Tones](/phonometry/guides/tone-prominence/): the ECMA-418-1 tone-to-noise and prominence-ratio verdicts, complementary emission screening for the tonal question. - [Occupational exposure (ISO 9612)](/phonometry/guides/occupational-exposure/): the workplace counterpart, from task samples to the daily exposure level with its uncertainty budget. -- API reference: [`environmental.measurement`](/phonometry/reference/api/environment/measurement/) and [`environmental.rating`](/phonometry/reference/api/environment/rating/). +- API reference: [`environment.assessment.measurement`](/phonometry/reference/api/environment/measurement/) and [`environment.assessment.rating`](/phonometry/reference/api/environment/rating/). ## Quick answers diff --git a/site/src/content/docs/guides/ground-barriers.mdx b/site/src/content/docs/guides/ground-barriers.mdx index 243892d86..5f4450301 100644 --- a/site/src/content/docs/guides/ground-barriers.mdx +++ b/site/src/content/docs/guides/ground-barriers.mdx @@ -51,7 +51,7 @@ import ThemeImage from '../../../components/ThemeImage.astro'; The [ISO 9613-2 general method](/phonometry/guides/outdoor-propagation/) folds the ground and barrier terms into tabulated, energy-based corrections. This page covers the underlying wave acoustics in -`phonometry.environment.ground_barriers`: the **spherical-wave reflection +`phonometry.environment.propagation.ground_barriers`: the **spherical-wave reflection coefficient** of a finite-impedance ground (Weyl-Van der Pol) and the **wave-theoretic diffraction** of a screen, both in a homogeneous (non-refracting, non-turbulent) atmosphere. These are the physical core of the diff --git a/site/src/content/docs/guides/impulse-prominence.mdx b/site/src/content/docs/guides/impulse-prominence.mdx index 5d84b6d95..83be86432 100644 --- a/site/src/content/docs/guides/impulse-prominence.mdx +++ b/site/src/content/docs/guides/impulse-prominence.mdx @@ -356,4 +356,4 @@ measurement replaces, not as a function of their own. ## See also - API reference: [`environment.impulse_prominence`](/phonometry/reference/api/environment/impulse-prominence/). -- API reference: [`environmental.impulsive_sound`](/phonometry/reference/api/environment/impulsive-sound/). +- API reference: [`environment.assessment.impulsive_sound`](/phonometry/reference/api/environment/impulsive-sound/). diff --git a/site/src/content/docs/guides/outdoor-propagation.mdx b/site/src/content/docs/guides/outdoor-propagation.mdx index 2b21aae49..91936c3e4 100644 --- a/site/src/content/docs/guides/outdoor-propagation.mdx +++ b/site/src/content/docs/guides/outdoor-propagation.mdx @@ -667,4 +667,4 @@ same result against the exact half-plane and coherent-ground models. ## See also -- API reference: [`environmental.outdoor_propagation`](/phonometry/reference/api/environment/outdoor-propagation/) and [`environmental.air_absorption`](/phonometry/reference/api/environment/air-absorption/). +- API reference: [`environment.propagation.outdoor_propagation`](/phonometry/reference/api/environment/outdoor-propagation/) and [`environment.propagation.air_absorption`](/phonometry/reference/api/environment/air-absorption/). diff --git a/site/src/content/docs/guides/reverberation-prediction.mdx b/site/src/content/docs/guides/reverberation-prediction.mdx index 01195bded..a85265bdd 100644 --- a/site/src/content/docs/guides/reverberation-prediction.mdx +++ b/site/src/content/docs/guides/reverberation-prediction.mdx @@ -433,4 +433,4 @@ rather than a statistical prediction. ## See also -- API reference: [`room.reverberation_prediction`](/phonometry/reference/api/rooms/reverberation-prediction/) and [`environmental.air_absorption`](/phonometry/reference/api/environment/air-absorption/). +- API reference: [`room.reverberation_prediction`](/phonometry/reference/api/rooms/reverberation-prediction/) and [`environment.propagation.air_absorption`](/phonometry/reference/api/environment/air-absorption/). diff --git a/site/src/content/docs/guides/spanish-noise-regulation.mdx b/site/src/content/docs/guides/spanish-noise-regulation.mdx index 5895aa85b..4bf87c635 100644 --- a/site/src/content/docs/guides/spanish-noise-regulation.mdx +++ b/site/src/content/docs/guides/spanish-noise-regulation.mdx @@ -310,7 +310,7 @@ not implemented either: this page starts where the sound level meter ends. - [Integrated and Statistical Levels](/phonometry/guides/levels/): the per-phase $L_{Aeq}$, $L_{Ceq}$ and $L_{AIeq}$ levels that feed the three corrections. - [Spanish Building Code (CTE DB-HR)](/phonometry/guides/spanish-building-code/): the other half of the Spanish framework, which shares the site's day noise index $L_d$ with this page. - [Impulsive-sound prominence](/phonometry/guides/impulse-prominence/): the ISO/PAS 1996-3 impulsive adjustment, the relative of $K_i$ that is not interchangeable with it. -- API reference: [`environmental.spanish_regulation`](/phonometry/reference/api/environment/spain/). +- API reference: [`environment.assessment.spain`](/phonometry/reference/api/environment/spain/). ## Quick answers diff --git a/site/src/content/docs/guides/wind-turbine-noise.mdx b/site/src/content/docs/guides/wind-turbine-noise.mdx index 57e90e651..4a21ce760 100644 --- a/site/src/content/docs/guides/wind-turbine-noise.mdx +++ b/site/src/content/docs/guides/wind-turbine-noise.mdx @@ -169,4 +169,4 @@ $K_T$, pass the mean audibility to the ISO 1996-2 `tonal_adjustment`. ## See also -- API reference: [`environmental.wind_turbine_noise`](/phonometry/reference/api/environment/wind-turbine/). +- API reference: [`environment.sources.wind_turbine`](/phonometry/reference/api/environment/wind-turbine/). diff --git a/site/src/content/docs/reference/errata.md b/site/src/content/docs/reference/errata.md index ed78fd8be..15bf38569 100644 --- a/site/src/content/docs/reference/errata.md +++ b/site/src/content/docs/reference/errata.md @@ -1140,7 +1140,7 @@ which is the check that enforces the rule; see printed with them. - **Library behaviour:** implements the spelled-out reading, weighting the onset rate by 3 and the level difference by 2 (`predicted_prominence` in - [`impulse_prominence.py`](https://github.com/jmrplens/phonometry/blob/main/src/phonometry/environmental/impulse_prominence.py)), + [`impulse_prominence.py`](https://github.com/jmrplens/phonometry/blob/main/src/phonometry/environment/assessment/impulse_prominence.py)), which is also the NT ACOU 112:2002 form the PAS carries over. - **Status:** unreported. @@ -1162,7 +1162,7 @@ which is the check that enforces the rule; see 1 kHz being both). - **Library behaviour:** unaffected. The library never reads Table 2: it computes $A_\text{atm}$ from the ISO 9613-1 formula directly - ([`air_absorption.py`](https://github.com/jmrplens/phonometry/blob/main/src/phonometry/environmental/air_absorption.py)), + ([`air_absorption.py`](https://github.com/jmrplens/phonometry/blob/main/src/phonometry/environment/propagation/air_absorption.py)), so it yields 4,15 dB/km for this condition. - **Status:** unreported. @@ -1995,7 +1995,7 @@ which is the check that enforces the rule; see in the autonomous-community noise regulations that transpose this Annex print `10 < Lf <= 15`. - **Library behaviour:** - [`low_frequency_correction`](https://github.com/jmrplens/phonometry/blob/main/src/phonometry/environmental/spanish_regulation.py) + [`low_frequency_correction`](https://github.com/jmrplens/phonometry/blob/main/src/phonometry/environment/assessment/spain.py) and `impulsive_correction` implement $10 < L \le 15$, with a regression test pinning the three branches at the 10 dB and 15 dB boundaries. - **Status:** unreported (national regulation, not a standards body). @@ -2021,7 +2021,7 @@ which is the check that enforces the rule; see from 125 Hz to 4 kHz", read: "each octave band i from 63 Hz to 8 kHz"'. The same corrigendum also adds "octave bands" to the frequency range of 2.1.1. - **Library behaviour:** - [`cnossos_road`](https://github.com/jmrplens/phonometry/blob/main/src/phonometry/environmental/cnossos_road.py) works over + [`cnossos_road`](https://github.com/jmrplens/phonometry/blob/main/src/phonometry/environment/sources/cnossos_road.py) works over the corrected 63 Hz to 8 kHz grid (`ROAD_OCTAVE_BANDS`), pinned by `test_octave_bands_are_the_corrected_range` and by the workbook cases, whose published levels cover all eight bands. diff --git a/site/src/content/docs/reference/why-phonometry.mdx b/site/src/content/docs/reference/why-phonometry.mdx index 6c4f86200..49bfe45b2 100644 --- a/site/src/content/docs/reference/why-phonometry.mdx +++ b/site/src/content/docs/reference/why-phonometry.mdx @@ -103,7 +103,7 @@ sample from the metrology core: | ISO 7196:1995 Table 2 | G weighting (infrasound) at every nominal response value, 0.25–315 Hz | `tests/filters/test_g_weighting.py` | | ISO 226:2023 Table 1 and Annex B | Equal-loudness contours and loudness levels against the Annex B tables, hearing threshold against the Table 1 $T_f$ parameters | `tests/psychoacoustics/test_loudness_contours.py` | | ECMA-418-1:2024 | TNR/PR tone prominence: critical bandwidths, proximity spacing and prominence criteria against the worked examples in clauses 10–12 | `tests/psychoacoustics/test_tonality.py` | -| ISO 1996-1:2016 | `lden()`, `ldn()` and `composite_rating_level()` against hand-computed formula values | `tests/environmental/test_environmental.py` | +| ISO 1996-1:2016 | `lden()`, `ldn()` and `composite_rating_level()` against hand-computed formula values | `tests/environment/assessment/test_rating.py` | | IEC 60942:2017 Table 2 | Calibrator short-term stability limits (frequency-dependent, class 1) in `sensitivity()` | `tests/metrology/test_calibration_validation.py` | The same discipline applies far beyond the metrology core: today the suite diff --git a/sonar-project.properties b/sonar-project.properties index 1a668878f..09bd13cb5 100644 --- a/sonar-project.properties +++ b/sonar-project.properties @@ -19,7 +19,7 @@ sonar.python.coverage.reportPaths=coverage.xml sonar.exclusions=**/__pycache__/**, **/*.png, **/*.md sonar.test.inclusions=tests/**/test_*.py # Exclude overload-heavy type-stub files from duplication analysis -sonar.cpd.exclusions=src/phonometry/__init__.py,src/phonometry/metrology/core.py +sonar.cpd.exclusions=src/phonometry/__init__.py,src/phonometry/filters/core.py # Increase authorized parameters for scientific APIs. event_level and # noise_contour mirror the ECAC Doc 29 single-event parameter set (NPD tables diff --git a/src/phonometry/_compat.py b/src/phonometry/_compat.py index 0d68f99e8..1934127de 100644 --- a/src/phonometry/_compat.py +++ b/src/phonometry/_compat.py @@ -234,6 +234,14 @@ def _make_shim(old: str, new: str, since: str, removed_in: str) -> types.ModuleT ) def __getattr__(name: str) -> Any: + if name == "__path__": + # Never proxy the import machinery's own attribute. A renamed + # package would otherwise hand out the real package's search path + # and let ``import phonometry.environmental.propagation`` build a + # second, independent copy of every submodule: same code, distinct + # classes, failing isinstance and pickles. The alias serves the + # modules registered for it and nothing else. + raise AttributeError(f"module {old!r} has no attribute {name!r}") target = import_module(new) try: attr = getattr(target, name) diff --git a/src/phonometry/_plot/environmental.py b/src/phonometry/_plot/environment.py similarity index 100% rename from src/phonometry/_plot/environmental.py rename to src/phonometry/_plot/environment.py diff --git a/src/phonometry/_plotting.py b/src/phonometry/_plotting.py index e3ef172f8..7004882ff 100644 --- a/src/phonometry/_plotting.py +++ b/src/phonometry/_plotting.py @@ -41,7 +41,7 @@ plot_sound_power, plot_vibration_sound_power, ) -from ._plot.environmental import ( +from ._plot.environment import ( plot_impulse_prominence, plot_outdoor_attenuation, plot_tonal_adjustment, diff --git a/src/phonometry/_report/rd1367.py b/src/phonometry/_report/rd1367.py index c7ba1d397..7658b406d 100644 --- a/src/phonometry/_report/rd1367.py +++ b/src/phonometry/_report/rd1367.py @@ -420,7 +420,7 @@ def render_activity_report( flow.append(Spacer(1, 6)) def _assessment_plot(ax: Any = None, language: str = "en", **kwargs: Any) -> Any: - from .._plot.environmental import plot_activity_assessment + from .._plot.environment import plot_activity_assessment return plot_activity_assessment(result, ax=ax, language=language, **kwargs) diff --git a/src/phonometry/environment/assessment/impulse_prominence.py b/src/phonometry/environment/assessment/impulse_prominence.py index b24c8b279..f3cec7739 100644 --- a/src/phonometry/environment/assessment/impulse_prominence.py +++ b/src/phonometry/environment/assessment/impulse_prominence.py @@ -107,7 +107,7 @@ def plot(self, ax: Axes | None = None, *, language: str = "en", **kwargs: Any) - :class:`~matplotlib.axes.Axes`. """ from ..._i18n import check_language - from ..._plot.environmental import plot_impulse_prominence + from ..._plot.environment import plot_impulse_prominence return plot_impulse_prominence(self, ax=ax, language=check_language(language), **kwargs) diff --git a/src/phonometry/environment/assessment/measurement.py b/src/phonometry/environment/assessment/measurement.py index 093fd11d9..0bd3c4ad8 100644 --- a/src/phonometry/environment/assessment/measurement.py +++ b/src/phonometry/environment/assessment/measurement.py @@ -192,7 +192,7 @@ class TonalAssessmentResult: def plot(self, ax: Axes | None = None, *, language: str = "en", **kwargs: Any) -> Axes: """Plot the ``Kt(ΔLta)`` adjustment curve with this tone marked.""" from ..._i18n import check_language - from ..._plot.environmental import plot_tonal_adjustment + from ..._plot.environment import plot_tonal_adjustment return plot_tonal_adjustment(self, ax=ax, language=check_language(language), **kwargs) diff --git a/src/phonometry/environment/assessment/spain.py b/src/phonometry/environment/assessment/spain.py index bf1e493df..c9ef65212 100644 --- a/src/phonometry/environment/assessment/spain.py +++ b/src/phonometry/environment/assessment/spain.py @@ -367,7 +367,7 @@ class TonalCorrectionResult: def plot(self, ax: Axes | None = None, *, language: str = "en", **kwargs: Any) -> Axes: """Plot the band spectrum with the emergent-tone differences ``Lt``.""" from ..._i18n import check_language - from ..._plot.environmental import plot_tonal_correction_rd1367 + from ..._plot.environment import plot_tonal_correction_rd1367 return plot_tonal_correction_rd1367( self, ax=ax, language=check_language(language), **kwargs @@ -385,41 +385,13 @@ def _kt_thresholds(frequency: float) -> tuple[float, float] | None: return None -def tonal_correction( - levels: Sequence[float] | np.ndarray, - frequencies: Sequence[float] | np.ndarray, -) -> TonalCorrectionResult: - r"""Tonal correction ``Kt`` from a one-third-octave spectrum (Annex IV A.3.3). - - The spectrum must be **unweighted** (no frequency weighting applied, as - required by step a). For every interior band ``f`` the procedure forms - :math:`L_t = L_f - L_s` with ``Ls`` the arithmetic mean of the levels of - the bands - immediately above and below (step b), and reads ``Kt`` off the table of - step c: with 20 Hz to 125 Hz bands :math:`L_t < 8` gives 0 dB, - :math:`8 \le L_t \le 12` - gives 3 dB and :math:`L_t > 12` gives 6 dB; the thresholds are 5/8 dB over - 160 Hz to 400 Hz and 3/5 dB over 500 Hz to 10 kHz. With more than one - emergent tone the largest ``Kt`` governs (step d). +def _validate_kt_spectrum(band_levels: np.ndarray, freqs: np.ndarray) -> None: + """Reject a spectrum the Annex IV A.3.3 procedure cannot read. - .. note:: - This is *not* the ISO 1996-2 tonal adjustment. The closest ISO relative - is the Annex K survey method - (:func:`~phonometry.environment.assessment.measurement.tonal_seeking_survey`), - which compares the band against *both* neighbours with 15/8/5 dB - thresholds and only flags prominence; the RD compares against their - arithmetic mean with 8/5/3 dB thresholds and grades the result 0/3/6 dB. - - :param levels: Unweighted one-third-octave band levels, in dB. - :param frequencies: Band centre frequencies, in Hz, in ascending order - (one per level). - :return: A :class:`TonalCorrectionResult`. - :raises ValueError: If fewer than three bands are given, the shapes differ, - the frequencies are not positive and strictly ascending, or any value - is not finite. + :param band_levels: Unweighted one-third-octave band levels, dB. + :param freqs: Band centre frequencies, Hz, strictly ascending. + :raises ValueError: For a shape, a value or an order the procedure needs. """ - band_levels = np.asarray(levels, dtype=np.float64) - freqs = np.asarray(frequencies, dtype=np.float64) if band_levels.ndim != 1 or freqs.ndim != 1: raise ValueError("'levels' and 'frequencies' must be one-dimensional.") if band_levels.shape != freqs.shape: @@ -439,6 +411,20 @@ def tonal_correction( if np.any(np.diff(freqs) <= 0.0): raise ValueError("'frequencies' must be strictly ascending.") + +def _band_tonal_corrections( + band_levels: np.ndarray, freqs: np.ndarray +) -> tuple[np.ndarray, np.ndarray]: + """Steps b and c of Annex IV A.3.3, band by band. + + Interior bands only: the first and the last have no pair of neighbours to + average, and a band outside the tabulated ranges has no threshold, so both + stay ``nan`` in each output. + + :param band_levels: Unweighted one-third-octave band levels, dB. + :param freqs: Band centre frequencies, Hz. + :return: ``(differences, band_corrections)``, both aligned to the input. + """ differences = np.full(band_levels.shape, np.nan, dtype=np.float64) band_kt = np.full(band_levels.shape, np.nan, dtype=np.float64) for i in range(1, band_levels.size - 1): @@ -455,6 +441,46 @@ def tonal_correction( band_kt[i] = 3.0 else: band_kt[i] = 6.0 + return differences, band_kt + + +def tonal_correction( + levels: Sequence[float] | np.ndarray, + frequencies: Sequence[float] | np.ndarray, +) -> TonalCorrectionResult: + r"""Tonal correction ``Kt`` from a one-third-octave spectrum (Annex IV A.3.3). + + The spectrum must be **unweighted** (no frequency weighting applied, as + required by step a). For every interior band ``f`` the procedure forms + :math:`L_t = L_f - L_s` with ``Ls`` the arithmetic mean of the levels of + the bands + immediately above and below (step b), and reads ``Kt`` off the table of + step c: with 20 Hz to 125 Hz bands :math:`L_t < 8` gives 0 dB, + :math:`8 \le L_t \le 12` + gives 3 dB and :math:`L_t > 12` gives 6 dB; the thresholds are 5/8 dB over + 160 Hz to 400 Hz and 3/5 dB over 500 Hz to 10 kHz. With more than one + emergent tone the largest ``Kt`` governs (step d). + + .. note:: + This is *not* the ISO 1996-2 tonal adjustment. The closest ISO relative + is the Annex K survey method + (:func:`~phonometry.environment.assessment.measurement.tonal_seeking_survey`), + which compares the band against *both* neighbours with 15/8/5 dB + thresholds and only flags prominence; the RD compares against their + arithmetic mean with 8/5/3 dB thresholds and grades the result 0/3/6 dB. + + :param levels: Unweighted one-third-octave band levels, in dB. + :param frequencies: Band centre frequencies, in Hz, in ascending order + (one per level). + :return: A :class:`TonalCorrectionResult`. + :raises ValueError: If fewer than three bands are given, the shapes differ, + the frequencies are not positive and strictly ascending, or any value + is not finite. + """ + band_levels = np.asarray(levels, dtype=np.float64) + freqs = np.asarray(frequencies, dtype=np.float64) + _validate_kt_spectrum(band_levels, freqs) + differences, band_kt = _band_tonal_corrections(band_levels, freqs) if np.all(np.isnan(band_kt)): correction = 0.0 @@ -1045,7 +1071,7 @@ def complies(self) -> bool: def plot(self, ax: Axes | None = None, *, language: str = "en", **kwargs: Any) -> Axes: """Plot the per-period indices against their RD 1367/2007 limits.""" from ..._i18n import check_language - from ..._plot.environmental import plot_activity_assessment + from ..._plot.environment import plot_activity_assessment return plot_activity_assessment( self, ax=ax, language=check_language(language), **kwargs @@ -1089,6 +1115,139 @@ def report( ) +def _period_durations( + period_hours: Mapping[str, float] | None, +) -> dict[str, float]: + """The period durations ``T``, with any caller override applied. + + :param period_hours: Durations in hours keyed by period, or ``None``. + :return: A duration for every RD 1367 evaluation period. + :raises ValueError: For an unknown period key or a non-positive duration. + """ + durations = dict(RD1367_PERIOD_HOURS) + if period_hours is not None: + for name, value in period_hours.items(): + durations[_check_period(name)] = _positive(value, "period_hours") + return durations + + +def _validate_annual_inputs( + operating_days: int | None, year_days: int, closed_level: float +) -> None: + """Reject the annual parameters the Article 25.1 b i criterion cannot use. + + :param operating_days: Days a year the activity operates, or ``None``. + :param year_days: Days in the year considered. + :param closed_level: Level representing a day with no operation, dB. + :raises ValueError: For a fractional or out-of-range count of days. + """ + if int(year_days) != year_days or int(year_days) <= 0: + raise ValueError( + f"'year_days' must be a positive whole number; got {year_days!r}." + ) + if operating_days is None: + return + if int(operating_days) != operating_days: + raise ValueError( + "'operating_days' must be a whole number of days; got " + f"{operating_days!r}." + ) + if not 0 < int(operating_days) <= int(year_days): + raise ValueError( + "'operating_days' must be a positive integer no larger than " + f"'year_days'; got {operating_days!r} of {year_days!r}." + ) + _finite(closed_level, "closed_level") + + +def _annual_index( + reported: int, + period: str, + *, + long_term_levels: Mapping[str, float] | None, + operating_days: int | None, + year_days: int, + closed_level: float, +) -> float | None: + """The annual index ``LK,x`` of a period: given, derived, or unavailable. + + :param reported: The reported daily ``LKeq,x`` of the period, dB. + :param period: Evaluation period name. + :param long_term_levels: Annual index per period when the caller knows it. + :param operating_days: Days a year the activity operates, or ``None``. + :param year_days: Days in the year considered. + :param closed_level: Level of a day with no operation, dB. + :return: The annual index in dB, or ``None`` when neither input is given. + """ + if long_term_levels is not None and period in long_term_levels: + return _finite(long_term_levels[period], "long_term_levels") + if operating_days is None: + return None + closed = int(year_days) - int(operating_days) + weights = [float(operating_days)] + ([float(closed)] if closed else []) + values = [float(reported)] + ([closed_level] if closed else []) + return long_term_corrected_level(values, weights=weights) + + +def _assess_period( + name: str, + phases: tuple[NoisePhase, ...], + duration: float, + limit: float, + *, + long_term_levels: Mapping[str, float] | None, + operating_days: int | None, + year_days: int, + closed_level: float, + new_activity: bool, +) -> PeriodAssessment: + """One evaluation period against the three criteria of Article 25.1 b. + + :param name: Evaluation period name. + :param phases: The noise phases measured in the period. + :param duration: Period duration ``T``, hours. + :param limit: The applicable limit value, dB. + :param long_term_levels: Annual index per period when the caller knows it. + :param operating_days: Days a year the activity operates, or ``None``. + :param year_days: Days in the year considered. + :param closed_level: Level of a day with no operation, dB. + :param new_activity: Whether the annual criterion applies. + :return: The assessment of the period. + :raises ValueError: If the period carries no noise phases. + """ + if not phases: + raise ValueError(f"Period {name!r} has no noise phases.") + level = evaluation_period_level(phases, hours=duration) + reported = round_reported_level(level) + annual = _annual_index( + reported, + name, + long_term_levels=long_term_levels, + operating_days=operating_days, + year_days=year_days, + closed_level=closed_level, + ) + max_phase = max(p.lkeq for p in phases) + return PeriodAssessment( + period=name, + phases=phases, + duration_hours=duration, + evaluation_period_level=level, + reported_level=reported, + long_term_corrected_level=annual, + reported_long_term=None if annual is None else round_reported_level(annual), + limit=limit, + max_phase_level=max_phase, + phase_pass=bool(max_phase <= limit + _PHASE_ALLOWANCE + 1e-9), + daily_pass=bool(reported <= limit + _DAILY_ALLOWANCE + 1e-9), + long_term_pass=( + None + if annual is None or not new_activity + else bool(round_reported_level(annual) <= limit + 1e-9) + ), + ) + + def assess_activity( measurements: Mapping[str, Sequence[NoisePhase]], limits: RegulationLimits, @@ -1136,71 +1295,24 @@ def assess_activity( """ if not measurements: raise ValueError("At least one evaluation period must be supplied.") - durations = dict(RD1367_PERIOD_HOURS) - if period_hours is not None: - for name, value in period_hours.items(): - durations[_check_period(name)] = _positive(value, "period_hours") - if int(year_days) != year_days or int(year_days) <= 0: - raise ValueError( - f"'year_days' must be a positive whole number; got {year_days!r}." - ) - if operating_days is not None: - if int(operating_days) != operating_days: - raise ValueError( - "'operating_days' must be a whole number of days; got " - f"{operating_days!r}." - ) - days = int(operating_days) - total_days = int(year_days) - if not 0 < days <= total_days: - raise ValueError( - "'operating_days' must be a positive integer no larger than " - f"'year_days'; got {operating_days!r} of {year_days!r}." - ) - _finite(closed_level, "closed_level") - - assessments: list[PeriodAssessment] = [] - for name in RD1367_EVALUATION_PERIODS: - if name not in measurements: - continue - phases = tuple(measurements[name]) - if not phases: - raise ValueError(f"Period {name!r} has no noise phases.") - duration = durations[name] - level = evaluation_period_level(phases, hours=duration) - reported = round_reported_level(level) - limit = limits[name] - - annual: float | None = None - if long_term_levels is not None and name in long_term_levels: - annual = _finite(long_term_levels[name], "long_term_levels") - elif operating_days is not None: - closed = int(year_days) - int(operating_days) - weights = [float(operating_days)] + ([float(closed)] if closed else []) - values = [float(reported)] + ([closed_level] if closed else []) - annual = long_term_corrected_level(values, weights=weights) - - max_phase = max(p.lkeq for p in phases) - assessments.append( - PeriodAssessment( - period=name, - phases=phases, - duration_hours=duration, - evaluation_period_level=level, - reported_level=reported, - long_term_corrected_level=annual, - reported_long_term=None if annual is None else round_reported_level(annual), - limit=limit, - max_phase_level=max_phase, - phase_pass=bool(max_phase <= limit + _PHASE_ALLOWANCE + 1e-9), - daily_pass=bool(reported <= limit + _DAILY_ALLOWANCE + 1e-9), - long_term_pass=( - None - if annual is None or not new_activity - else bool(round_reported_level(annual) <= limit + 1e-9) - ), - ) + durations = _period_durations(period_hours) + _validate_annual_inputs(operating_days, year_days, closed_level) + + assessments = [ + _assess_period( + name, + tuple(measurements[name]), + durations[name], + limits[name], + long_term_levels=long_term_levels, + operating_days=operating_days, + year_days=year_days, + closed_level=closed_level, + new_activity=new_activity, ) + for name in RD1367_EVALUATION_PERIODS + if name in measurements + ] if not assessments: raise ValueError( "None of the supplied keys is an evaluation period; expected " diff --git a/src/phonometry/environment/propagation/air_absorption.py b/src/phonometry/environment/propagation/air_absorption.py index 6567f737d..385ad0331 100644 --- a/src/phonometry/environment/propagation/air_absorption.py +++ b/src/phonometry/environment/propagation/air_absorption.py @@ -370,7 +370,7 @@ def plot( :return: The axes. """ from ..._i18n import check_language - from ..._plot.environmental import plot_atmospheric_attenuation + from ..._plot.environment import plot_atmospheric_attenuation check_language(language) return plot_atmospheric_attenuation(self, ax=ax, language=language, **kwargs) diff --git a/src/phonometry/environment/propagation/ground_barriers.py b/src/phonometry/environment/propagation/ground_barriers.py index 3fea5e42d..099c92dd9 100644 --- a/src/phonometry/environment/propagation/ground_barriers.py +++ b/src/phonometry/environment/propagation/ground_barriers.py @@ -245,7 +245,7 @@ def plot(self, ax: Axes | None = None, *, language: str = "en", **kwargs: Any) - :class:`~matplotlib.axes.Axes`. """ from ..._i18n import check_language - from ..._plot.environmental import plot_spherical_ground + from ..._plot.environment import plot_spherical_ground return plot_spherical_ground(self, ax=ax, language=check_language(language), **kwargs) @@ -586,7 +586,7 @@ def plot(self, ax: Axes | None = None, *, language: str = "en", **kwargs: Any) - :class:`~matplotlib.axes.Axes`. """ from ..._i18n import check_language - from ..._plot.environmental import plot_barrier_insertion_loss + from ..._plot.environment import plot_barrier_insertion_loss return plot_barrier_insertion_loss(self, ax=ax, language=check_language(language), **kwargs) diff --git a/src/phonometry/environment/propagation/outdoor_propagation.py b/src/phonometry/environment/propagation/outdoor_propagation.py index dfc0f93d2..5756e9a8c 100644 --- a/src/phonometry/environment/propagation/outdoor_propagation.py +++ b/src/phonometry/environment/propagation/outdoor_propagation.py @@ -200,7 +200,7 @@ def plot(self, ax: Axes | None = None, *, language: str = "en", **kwargs: Any) - :class:`~matplotlib.axes.Axes`. """ from ..._i18n import check_language - from ..._plot.environmental import plot_outdoor_attenuation + from ..._plot.environment import plot_outdoor_attenuation return plot_outdoor_attenuation(self, ax=ax, language=check_language(language), **kwargs) diff --git a/src/phonometry/environment/propagation/refraction.py b/src/phonometry/environment/propagation/refraction.py index 85d877b9d..b30889175 100644 --- a/src/phonometry/environment/propagation/refraction.py +++ b/src/phonometry/environment/propagation/refraction.py @@ -100,7 +100,7 @@ def speed_at(self, height: ArrayLike) -> NDArray[np.float64]: def plot(self, ax: Axes | None = None, *, language: str = "en", **kwargs: Any) -> Axes: """Plot the effective sound-speed profile (height on the vertical axis).""" from ..._i18n import check_language - from ..._plot.environmental import plot_sound_speed_profile + from ..._plot.environment import plot_sound_speed_profile return plot_sound_speed_profile(self, ax=ax, language=check_language(language), **kwargs) @@ -321,7 +321,7 @@ class AtmosphericRayResult: def plot(self, ax: Axes | None = None, *, language: str = "en", **kwargs: Any) -> Axes: """Plot the curved ray paths (height on the vertical axis).""" from ..._i18n import check_language - from ..._plot.environmental import plot_atmospheric_rays + from ..._plot.environment import plot_atmospheric_rays return plot_atmospheric_rays(self, ax=ax, language=check_language(language), **kwargs) @@ -471,7 +471,7 @@ def level_at_height(self, height: float) -> NDArray[np.float64]: def plot(self, ax: Axes | None = None, *, language: str = "en", **kwargs: Any) -> Axes: """Plot the relative-level field over the range-height plane.""" from ..._i18n import check_language - from ..._plot.environmental import plot_atmospheric_pe + from ..._plot.environment import plot_atmospheric_pe return plot_atmospheric_pe(self, ax=ax, language=check_language(language), **kwargs) diff --git a/src/phonometry/environment/sources/cnossos_rail.py b/src/phonometry/environment/sources/cnossos_rail.py index 37851a6c5..e7585f6bf 100644 --- a/src/phonometry/environment/sources/cnossos_rail.py +++ b/src/phonometry/environment/sources/cnossos_rail.py @@ -1402,7 +1402,7 @@ class RailwayEmissionResult: def plot(self, ax: Axes | None = None, *, language: str = "en", **kwargs: Any) -> Axes: """Plot the per-metre line power of the two equivalent source heights.""" from ..._i18n import check_language - from ..._plot.environmental import plot_cnossos_rail_emission + from ..._plot.environment import plot_cnossos_rail_emission return plot_cnossos_rail_emission( self, ax=ax, language=check_language(language), **kwargs diff --git a/src/phonometry/environment/sources/cnossos_road.py b/src/phonometry/environment/sources/cnossos_road.py index 5aa8648e3..a6d163394 100644 --- a/src/phonometry/environment/sources/cnossos_road.py +++ b/src/phonometry/environment/sources/cnossos_road.py @@ -811,7 +811,7 @@ def a_weighted_line_power(self) -> float: def plot(self, ax: Axes | None = None, *, language: str = "en", **kwargs: Any) -> Axes: """Plot the per-metre line power spectrum and its category breakdown.""" from ..._i18n import check_language - from ..._plot.environmental import plot_cnossos_road_emission + from ..._plot.environment import plot_cnossos_road_emission return plot_cnossos_road_emission( self, ax=ax, language=check_language(language), **kwargs diff --git a/src/phonometry/environment/sources/wind_turbine.py b/src/phonometry/environment/sources/wind_turbine.py index dc79eb71a..eb946c1f3 100644 --- a/src/phonometry/environment/sources/wind_turbine.py +++ b/src/phonometry/environment/sources/wind_turbine.py @@ -214,7 +214,7 @@ class WindTurbineTonalityResult: def plot(self, ax: Axes | None = None, *, language: str = "en", **kwargs: Any) -> Axes: """Plot the narrowband spectrum with the critical band and masking level.""" from ..._i18n import check_language - from ..._plot.environmental import plot_wind_turbine_tonality + from ..._plot.environment import plot_wind_turbine_tonality return plot_wind_turbine_tonality(self, ax=ax, language=check_language(language), **kwargs) diff --git a/tests/data/cnossos/README.md b/tests/data/cnossos/README.md index ed65e2125..415e9a441 100644 --- a/tests/data/cnossos/README.md +++ b/tests/data/cnossos/README.md @@ -70,7 +70,7 @@ vehicles, 17 280 cases at both source heights, to 0,0055 dB. ## Purpose and scope of use -`tests/environmental/test_cnossos_rail.py` feeds the shipped equations of +`tests/environment/sources/test_cnossos_rail.py` feeds the shipped equations of Annex II 2.3 the 2015 coefficient set and requires the workbook's levels back, band by band, at both source heights. That is the only oracle that pins the *equations*: the Directive itself prints no worked example anywhere in 2.3 or @@ -124,7 +124,7 @@ The coefficients of the current text, Tables F-1 to F-4 as they stand after machine-transcribed from the Official Journal, and pin the tables shipped in `phonometry.environmental.cnossos_road`. -`tests/environmental/test_cnossos_road.py` feeds the shipped equations of +`tests/environment/sources/test_cnossos_road.py` feeds the shipped equations of Annex II 2.2 the superseded 2015 coefficient set and requires the workbook's levels back, band by band. That is the only oracle that pins the *equations*: the Directive itself prints no worked example anywhere in 2.2 or Appendix F. diff --git a/tests/environment/assessment/test_impulse_prominence_report.py b/tests/environment/assessment/test_impulse_prominence_report.py index 05a26bc8d..56f6cb090 100644 --- a/tests/environment/assessment/test_impulse_prominence_report.py +++ b/tests/environment/assessment/test_impulse_prominence_report.py @@ -8,7 +8,7 @@ verdict renders both ways, and the boxed governing prominence ``P``, the derived ``LAeq`` adjustment ``KI`` and the metadata appear in the extracted text. The prominence and adjustment maths itself is validated against the NT ACOU 112:2002 -formulae elsewhere (tests/environmental/test_impulse_prominence.py); this fiche +formulae elsewhere (tests/environment/assessment/test_impulse_prominence.py); this fiche test anchors its numbers to the documented three-impulse pile-driving set, whose governing prominence and adjustment are derived from Formula 1 and Formula 2. """ diff --git a/tests/environment/assessment/test_measurement.py b/tests/environment/assessment/test_measurement.py index 65f79547c..422ec7528 100644 --- a/tests/environment/assessment/test_measurement.py +++ b/tests/environment/assessment/test_measurement.py @@ -113,7 +113,8 @@ def test_survey_thresholds() -> None: flags = tonal_seeking_survey(levels, freqs) assert flags[2] # 125 Hz assert flags[6] # 630 Hz - assert not flags[0] and not flags[-1] # end bands never flagged + assert not flags[0] # end bands never flagged + assert not flags[-1] def test_survey_below_threshold_not_flagged() -> None: diff --git a/tests/environment/assessment/test_rd1367_report.py b/tests/environment/assessment/test_rd1367_report.py index 9b6d7b7d7..5547f4013 100644 --- a/tests/environment/assessment/test_rd1367_report.py +++ b/tests/environment/assessment/test_rd1367_report.py @@ -8,7 +8,7 @@ renders both ways, XML specials in metadata survive reportlab, and a fiche with all three evaluation periods still fits one page. The assessment maths itself is validated against the regulation and the published worked examples elsewhere -(tests/environmental/test_spanish_regulation.py). +(tests/environment/assessment/test_spain.py). The case rendered here is the worked example of Aviles Lopez & Perera Martin, Manual de acustica ambiental y arquitectonica, Ejemplos 3.1 to 3.3: an activity diff --git a/tests/environment/assessment/test_spain.py b/tests/environment/assessment/test_spain.py index d399f418c..6b3e80bbb 100644 --- a/tests/environment/assessment/test_spain.py +++ b/tests/environment/assessment/test_spain.py @@ -236,8 +236,9 @@ def test_long_term_level_unweighted_is_the_plain_energy_mean() -> None: def test_evaluation_period_rejects_durations_that_do_not_sum_to_the_period() -> None: """Annex IV A.3.4.2 b requires "La suma de los Ti = T".""" + phases = [rd.NoisePhase(2.0, 50.0)] with pytest.raises(ValueError, match="sum Ti = T"): - rd.evaluation_period_level([rd.NoisePhase(2.0, 50.0)], hours=12.0) + rd.evaluation_period_level(phases, hours=12.0) # --------------------------------------------------------------------------- # @@ -273,7 +274,10 @@ def test_outdoor_quality_objectives_annex_ii_table_a( ) -> None: """Annex II Table A as amended by RD 1038/2012, every area type.""" limits = rd.outdoor_quality_objectives(area) - assert (limits.day, limits.evening, limits.night) == expected + day, evening, night = expected + assert limits.day == day + assert limits.evening == evening + assert limits.night == night assert limits.index == "Lx" @@ -302,7 +306,10 @@ def test_indoor_quality_objectives_annex_ii_table_b( ) -> None: """Annex II Table B, every row of the indoor quality objectives.""" limits = rd.indoor_quality_objectives(use, room) - assert (limits.day, limits.evening, limits.night) == expected + day, evening, night = expected + assert limits.day == day + assert limits.evening == evening + assert limits.night == night @pytest.mark.parametrize( @@ -331,7 +338,10 @@ def test_infrastructure_limits_annex_iii_table_a1( ) -> None: """Annex III Table A1: new road, rail and airport infrastructure.""" limits = rd.infrastructure_limits(area) - assert (limits.day, limits.evening, limits.night) == expected + day, evening, night = expected + assert limits.day == day + assert limits.evening == evening + assert limits.night == night @pytest.mark.parametrize( @@ -364,7 +374,10 @@ def test_activity_limits_annex_iii_table_b1( its activity against (its Tabla 3.7, printed page 173). """ limits = rd.activity_limits(area) - assert (limits.day, limits.evening, limits.night) == expected + day, evening, night = expected + assert limits.day == day + assert limits.evening == evening + assert limits.night == night assert limits.index == "LK,x" @@ -386,7 +399,10 @@ def test_adjacent_premises_limits_annex_iii_table_b2( ) -> None: """Annex III Table B2, every row of the noise transmitted to adjacent premises.""" limits = rd.adjacent_premises_limits(use, room) - assert (limits.day, limits.evening, limits.night) == expected + day, evening, night = expected + assert limits.day == day + assert limits.evening == evening + assert limits.night == night def test_area_type_aliases_and_catalogue() -> None: @@ -471,8 +487,9 @@ def test_new_activity_without_annual_information_is_refused() -> None: would assert something the assessment has not shown, so the missing input is an error rather than a silently omitted check. """ + periods, limits = _example_periods(), rd.activity_limits("a") with pytest.raises(ValueError, match="annual index"): - rd.assess_activity(_example_periods(), rd.activity_limits("a")) + rd.assess_activity(periods, limits) def test_operating_activity_without_annual_information_is_allowed() -> None: @@ -575,8 +592,9 @@ def test_tonal_correction_validation() -> None: rd.tonal_correction([60.0, 60.0, 60.0], [160.0, 125.0, 100.0]) with pytest.raises(ValueError, match="positive"): rd.tonal_correction([60.0, 60.0, 60.0], [0.0, 125.0, 160.0]) + two_dimensional = np.zeros((2, 3)) with pytest.raises(ValueError, match="one-dimensional"): - rd.tonal_correction(np.zeros((2, 3)), np.zeros((2, 3))) + rd.tonal_correction(two_dimensional, two_dimensional) def test_limit_lookup_validation() -> None: @@ -598,33 +616,29 @@ def test_assess_activity_validation() -> None: limits = rd.activity_limits("a") with pytest.raises(ValueError, match="At least one evaluation period"): rd.assess_activity({}, limits) + empty_day: dict[str, list[rd.NoisePhase]] = {"day": []} with pytest.raises(ValueError, match="no noise phases"): - rd.assess_activity({"day": []}, limits) + rd.assess_activity(empty_day, limits) + afternoon = {"afternoon": [rd.NoisePhase(1.0, 50.0)]} with pytest.raises(ValueError, match="evaluation period"): - rd.assess_activity({"afternoon": [rd.NoisePhase(1.0, 50.0)]}, limits) + rd.assess_activity(afternoon, limits) + day = {"day": [rd.NoisePhase(12.0, 50.0)]} with pytest.raises(ValueError, match="operating_days"): - rd.assess_activity( - {"day": [rd.NoisePhase(12.0, 50.0)]}, limits, operating_days=400 - ) + rd.assess_activity(day, limits, operating_days=400) with pytest.raises(ValueError, match="year_days"): - rd.assess_activity( - {"day": [rd.NoisePhase(12.0, 50.0)]}, - limits, - operating_days=10, - year_days=0, - ) + rd.assess_activity(day, limits, operating_days=10, year_days=0) # 'year_days' is validated even when it is the only annual input given, # and a fractional count of days is rejected rather than truncated. with pytest.raises(ValueError, match="year_days"): rd.assess_activity( - {"day": [rd.NoisePhase(12.0, 50.0)]}, + day, limits, long_term_levels={"day": 50.0}, year_days=365.5, # type: ignore[arg-type] ) with pytest.raises(ValueError, match="whole number"): rd.assess_activity( - {"day": [rd.NoisePhase(12.0, 50.0)]}, + day, limits, operating_days=10.5, # type: ignore[arg-type] ) diff --git a/tests/environment/sources/test_wind_turbine_tonality_report.py b/tests/environment/sources/test_wind_turbine_tonality_report.py index ac2d5b24a..f36c89f54 100644 --- a/tests/environment/sources/test_wind_turbine_tonality_report.py +++ b/tests/environment/sources/test_wind_turbine_tonality_report.py @@ -8,7 +8,7 @@ and the boxed tonal audibility ``ΔL_a``, the tone frequency and the audibility decision appear in the extracted text. The tonality algorithm itself is validated against the IEC 61400-11 oracle elsewhere -(tests/environmental/test_wind_turbine_noise.py); this fiche test reuses the +(tests/environment/sources/test_wind_turbine.py); this fiche test reuses the same hand-derived synthetic tone so its numbers are documented. """ diff --git a/tests/reference_data.py b/tests/reference_data.py index f1b4ba0cd..c9a41a5fb 100644 --- a/tests/reference_data.py +++ b/tests/reference_data.py @@ -388,7 +388,7 @@ # (S = 10 m2, A = 0,16 * 50 / 0,8 = 10 m2), so R = L1 - L2 = the reference. The # 32 dB unfavourable-deviation allowance then permits a 2 dB upward shift of the # reference (32 dB / 16 bands), giving Rw = curve@500 Hz (52) + 2 = 54 dB - the -# analytic +2-shift anchor (mirrors tests/test_lab_insulation.py). +# analytic +2-shift anchor (mirrors tests/building/test_lab_insulation.py). # --------------------------------------------------------------------------- ISO10140_2_REF_AIRBORNE_R: list[float] = [ 33, 36, 39, 42, 45, 48, 51, 52, 53, 54, 55, 56, 56, 56, 56, 56, @@ -401,7 +401,7 @@ # contributes an Ff/Fd/Df triplet (12 flanking paths), which with the direct # Dd path make 13 transmission paths. Energy summation (Formula (26)) gives # R'w = 52,2 dB -> 52 dB. Row = (label, Rw_flanking, KFf, KFd=KDf, coupling -# length lf). Mirrors tests/test_building_prediction.py (_annex_h_paths). +# length lf). Mirrors tests/building/test_building_prediction.py (_annex_h_paths). # --------------------------------------------------------------------------- EN12354_1_ANNEX_H3_R_DIRECT = 57.0 EN12354_1_ANNEX_H3_SEPARATING_AREA = 11.5 @@ -670,7 +670,7 @@ # workers) and F (full-day forklift drivers) use the Table C.4 sampling budget. # Task tuples are (samples, duration_hours, duration_range) so the conformance # report can rebuild the Task objects (Task is not importable here — this module -# is stdlib-only). Mirrors tests/test_occupational_exposure.py. +# is stdlib-only). Mirrors tests/hearing/test_occupational_exposure.py. # --------------------------------------------------------------------------- ISO9612_ANNEX_D_TASKS: tuple[tuple, ...] = ( ((70.0,), 1.5, None), @@ -693,7 +693,7 @@ # of Annex A. Both use the same practical-coefficient spectrum except at 500 Hz; # A.1 gives alpha_w = 0,60 with no shape indicator, A.2 (500 Hz raised to 1,00) # gives alpha_w = 0,60(M). Bands are 250/500/1000/2000/4000 Hz. Mirrors -# tests/test_absorption_rating.py. +# tests/materials/test_absorption_rating.py. # --------------------------------------------------------------------------- ISO11654_ANNEX_A1_ALPHA_P: tuple[float, ...] = (0.35, 0.70, 0.65, 0.60, 0.55) ISO11654_ANNEX_A1_ALPHA_W = 0.60 @@ -709,7 +709,7 @@ # 100 mm gives V = 7,854e-4 m3 and S = 0,0471 m2; with the IEC 61094-2:2009 air # properties at 23 C and f = 2 Hz the standard prints b = 1,83e-3 m and the # heat-conduction-corrected kappa' = kappa*0,978 = 1,370. Mirrors -# tests/test_airflow_resistance.py. +# tests/materials/test_airflow_resistance.py. # --------------------------------------------------------------------------- ISO9053_2_ANNEX_A_SURFACE = 0.0471 # S (m2) ISO9053_2_ANNEX_A_VOLUME = 7.854e-4 # V (m3) @@ -731,7 +731,7 @@ # reference speed of sound c = 343,2 m/s at 20 C. The synthetic worked chain # (T1..T4 = 8,0/6,0/7,5/5,0 s, V/S from V = 200 m3, S = 10 m2) exercises the # Sabine absorptions Eq (1)/(4) and the scattering Eq (5). Mirrors -# tests/test_scattering_diffusion.py. +# tests/materials/test_scattering_diffusion.py. # --------------------------------------------------------------------------- ISO17497_1_SPEED_OF_SOUND_20C = 343.2 # Eq (2) reference condition (m/s) ISO17497_1_CHAIN_V = 200.0 # chamber volume V (m3) @@ -852,7 +852,7 @@ # ds = 1,25 m, dm = 0,25 m gives the geometrical-spreading factor Kr = 2/3 # (Clause 4.2). The Annex A worked example (c = 340 m/s, 5 ms flat window) # gives a maximum-sampled-area radius r ~ 1,34 m. Mirrors -# tests/test_road_absorption.py. +# tests/materials/test_road_absorption.py. # --------------------------------------------------------------------------- ISO13472_1_KR = 2.0 / 3.0 # geometrical-spreading factor ISO13472_1_MSA_WINDOW = 5.0e-3 # reflected-wave window width Tw (s) @@ -873,7 +873,7 @@ # expanded uncertainty U = 4,1 dB. The K1 background correction floors at # 1,26 dB (>= 6 dB signal-to-noise edge bands). The meteorological correction # C1 at the 23 C, ps0 reference is 5*lg(296/314) = -0,128 dB. Mirrors -# tests/test_sound_power_precision.py. +# tests/emission/test_sound_power_precision.py. # --------------------------------------------------------------------------- ISO3745_U_SIGMA_R0 = 0.5 # reproducibility standard deviation (dB) ISO3745_U_SIGMA_OMC = 2.0 # operating/mounting/... std. deviation (dB) diff --git a/tests/test_deprecated_aliases.py b/tests/test_deprecated_aliases.py index f276be547..95cc6ac3c 100644 --- a/tests/test_deprecated_aliases.py +++ b/tests/test_deprecated_aliases.py @@ -305,7 +305,7 @@ def test_renamed_attribute_shims_reject_unknown_names() -> None: "phonometry.distortion", "phonometry.dynamic_stiffness", "phonometry.enclosed_space_absorption", - "phonometry.environment", + "phonometry.environmental", "phonometry.environmental_measurement", "phonometry.facade_prediction", "phonometry.filter_design", @@ -461,6 +461,25 @@ def test_pre_split_module_path_still_imports(path: str) -> None: assert public, f"{path} imports but exposes no public names" +def test_a_renamed_package_alias_is_not_a_package() -> None: + """The alias must not hand out the real package's search path. + + Proxying ``__path__`` would let ``import phonometry.environmental.`` + build a second, independent copy of every submodule: same source, distinct + classes, failing isinstance and pickles, and with no notice on the way in. + """ + import importlib + + from phonometry import environmental # noqa: F401 - the alias under test + + assert not hasattr(sys.modules["phonometry.environmental"], "__path__") + with pytest.raises(ModuleNotFoundError): + importlib.import_module("phonometry.environmental.propagation") + # The modules the table names still resolve, and to the same object. + alias = importlib.import_module("phonometry.environmental.rating") + assert alias is sys.modules["phonometry.environmental.rating"] + + def test_a_module_only_split_still_serves_the_dotted_read() -> None: """``vibration.human_vibration.x`` after the import, not only the import. From 25c752ef7982ba68e4c178e6f7c9662d41486bef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20M=2E=20Requena=20Plens?= Date: Sun, 2 Aug 2026 17:27:23 +0200 Subject: [PATCH 3/5] Name the repeated labels in the environment renderers SonarCloud's duplicated-literal rule on the moved file: the axis labels the renderers share become module constants keyed the same way in the translation table, and the two legend placements join the two that already live in the shared plotting module. --- src/phonometry/_plot/common.py | 2 ++ src/phonometry/_plot/environment.py | 50 +++++++++++++++++------------ 2 files changed, 32 insertions(+), 20 deletions(-) diff --git a/src/phonometry/_plot/common.py b/src/phonometry/_plot/common.py index 6ef01089a..25f80e846 100644 --- a/src/phonometry/_plot/common.py +++ b/src/phonometry/_plot/common.py @@ -51,6 +51,8 @@ _LEGEND_UPPER_RIGHT: Final = "upper right" #: Legend placement for the range-axis figures, whose curves fall to the right. _LEGEND_LOWER_LEFT: Final = "lower left" +#: Legend placement for the figures whose curves fall to the left instead. +_LEGEND_UPPER_LEFT: Final = "upper left" # --------------------------------------------------------------------------- # Shared artist colors (matplotlib "tab10" hues plus neutral greys). diff --git a/src/phonometry/_plot/environment.py b/src/phonometry/_plot/environment.py index ebf055139..9d4525f77 100644 --- a/src/phonometry/_plot/environment.py +++ b/src/phonometry/_plot/environment.py @@ -15,6 +15,7 @@ _C_REFERENCE, _C_SECONDARY, _C_TERTIARY, + _LEGEND_UPPER_LEFT, _LEGEND_UPPER_RIGHT, _band_axis, _field_cmap, @@ -52,12 +53,21 @@ #: returns the English key unchanged for any language other than ``"es"``, #: so the English output is byte-for-byte identical to the pre-i18n #: renderers. +#: Axis labels the renderers repeat; the Spanish table is keyed by the same +#: constants, so a label is written once. +_FREQ_LABEL = "Frequency [Hz]" +_HEIGHT_LABEL = "Height [m]" +_RANGE_LABEL = "Range [m]" +_TOTAL_A_LABEL = r"$A$ — total" +_FREE_FIELD_LABEL = "Level re free field [dB]" +_LT_LABEL = r"$L_t$ [dB]" + _STRINGS: dict[str, str] = { "Narrowband spectrum": "Espectro de banda estrecha", "Critical band": "Banda crítica", "Masking level": "Nivel de enmascaramiento", "Tone": "Tono", - "Frequency [Hz]": "Frecuencia [Hz]", + _FREQ_LABEL: "Frecuencia [Hz]", "Level [dB]": "Nivel [dB]", "IEC 61400-11 tonal audibility": "Audibilidad tonal IEC 61400-11", "threshold": "umbral", @@ -86,7 +96,7 @@ "Excess attenuation $\\Delta L$": "Atenuación en exceso $\\Delta L$", "Free field (0 dB)": "Campo libre (0 dB)", "Hard-ground limit (+6 dB)": "Límite de suelo duro (+6 dB)", - "Level re free field [dB]": "Nivel re campo libre [dB]", + _FREE_FIELD_LABEL: "Nivel re campo libre [dB]", "Spherical-wave ground effect (Weyl-Van der Pol)": "Efecto de suelo de onda esférica (Weyl-Van der Pol)", "Insertion loss": "Pérdida por inserción", "ground": "suelo", @@ -94,8 +104,8 @@ "Insertion loss [dB]": "Pérdida por inserción [dB]", "Barrier insertion loss": "Pérdida por inserción de barrera", "Effective sound speed [m/s]": "Velocidad efectiva del sonido [m/s]", - "Height [m]": "Altura [m]", - "Range [m]": "Distancia [m]", + _HEIGHT_LABEL: "Altura [m]", + _RANGE_LABEL: "Distancia [m]", "Effective sound-speed profile": "Perfil de velocidad efectiva del sonido", "Source": "Fuente", "Atmospheric ray paths": "Trayectorias de rayos atmosféricos", @@ -170,11 +180,11 @@ def plot_atmospheric_attenuation( fmin, fmax = float(freqs.min()), float(freqs.max()) ax.set_xlim(fmin, fmax) format_frequency_axis(ax, fmin, fmax) - ax.set_xlabel(_t("Frequency [Hz]", language)) + ax.set_xlabel(_t(_FREQ_LABEL, language)) ax.set_ylabel(_t(r"Attenuation coefficient $\alpha$ [dB/km]", language)) ax.set_title(_t("ISO 9613-1 atmospheric attenuation", language)) ax.grid(True, which="both", alpha=0.3) - ax.legend(loc="upper left", fontsize="small") + ax.legend(loc=_LEGEND_UPPER_LEFT, fontsize="small") localize_axes(ax, language) return ax @@ -205,7 +215,7 @@ def plot_wind_turbine_tonality( label=f"{_t('Masking level', language)} ({format_number(result.masking_level, language)} dB)") ax.plot([fc], [result.tone_level], "o", color=_C_REFERENCE, label=f"{_t('Tone', language)} ({format_number(result.tone_level, language)} dB)") - ax.set_xlabel(_t("Frequency [Hz]", language)) + ax.set_xlabel(_t(_FREQ_LABEL, language)) ax.set_ylabel(_t("Level [dB]", language)) ax.set_title(f"{_t('IEC 61400-11 tonal audibility', language)} ΔLₐ = {format_number(result.tonal_audibility, language)} dB") ax.grid(True, alpha=0.3) @@ -252,7 +262,7 @@ def plot_impulse_prominence( ax.set_ylabel(_t("Adjustment $K_I$ [dB]", language)) ax.set_title(_t("NT ACOU 112 — impulse adjustment to $L_{Aeq}$", language)) ax.set_ylim(bottom=0.0) - ax.legend(loc="upper left", fontsize="small") + ax.legend(loc=_LEGEND_UPPER_LEFT, fontsize="small") ax.grid(True, alpha=0.3) localize_axes(ax, language) return ax @@ -292,7 +302,7 @@ def plot_tonal_adjustment( ax.set_ylabel(_t("Tonal adjustment $K_t$ [dB]", language)) ax.set_title(_t("ISO 1996-2 tonal adjustment", language)) ax.set_ylim(bottom=0.0) - ax.legend(loc="upper left", fontsize="small") + ax.legend(loc=_LEGEND_UPPER_LEFT, fontsize="small") ax.grid(True, alpha=0.3) localize_axes(ax, language) return ax @@ -435,7 +445,7 @@ def plot_spherical_ground( ax.axhline(6.0, color=_C_REFERENCE, ls="--", lw=0.9, label=_t("Hard-ground limit (+6 dB)", language)) _freq_axis(ax, freqs, language=language) - ax.set_ylabel(_t("Level re free field [dB]", language)) + ax.set_ylabel(_t(_FREE_FIELD_LABEL, language)) ax.set_title(_t("Spherical-wave ground effect (Weyl-Van der Pol)", language)) ax.legend(loc="best", fontsize="small") ax.grid(True, which="both", alpha=0.3) @@ -498,7 +508,7 @@ def plot_sound_speed_profile( label = profile.description or "c_eff(z)" ax.plot(c, z, **{"color": _C_PRIMARY, "lw": 1.4, "label": label, **kwargs}) ax.set_xlabel(_t("Effective sound speed [m/s]", language)) - ax.set_ylabel(_t("Height [m]", language)) + ax.set_ylabel(_t(_HEIGHT_LABEL, language)) ax.set_title(_t("Effective sound-speed profile", language)) ax.grid(True, alpha=0.3) ax.legend(loc="best", fontsize="small") @@ -528,12 +538,12 @@ def plot_atmospheric_rays( ax.plot(r[i], z[i], **{"color": _C_PRIMARY, "lw": 0.7, "alpha": 0.7, **kwargs}) ax.plot([0.0], [result.source_height], "o", color=_C_REFERENCE, label=_t("Source", language)) ax.axhline(0.0, color=_C_MUTED, lw=1.0) - ax.set_xlabel(_t("Range [m]", language)) - ax.set_ylabel(_t("Height [m]", language)) + ax.set_xlabel(_t(_RANGE_LABEL, language)) + ax.set_ylabel(_t(_HEIGHT_LABEL, language)) ax.set_ylim(bottom=0.0) ax.set_title(_t("Atmospheric ray paths", language)) ax.grid(True, alpha=0.3) - ax.legend(loc="upper right", fontsize="small") + ax.legend(loc=_LEGEND_UPPER_RIGHT, fontsize="small") localize_axes(ax, language) return ax @@ -581,12 +591,12 @@ def plot_atmospheric_pe( **kwargs, }, ) - ax.figure.colorbar(img, ax=ax, label=_t("Level re free field [dB]", language)) + ax.figure.colorbar(img, ax=ax, label=_t(_FREE_FIELD_LABEL, language)) ax.plot([0.0], [result.source_height], "o", color="k", ms=4.0, label=_t("Source", language)) - ax.set_xlabel(_t("Range [m]", language)) - ax.set_ylabel(_t("Height [m]", language)) + ax.set_xlabel(_t(_RANGE_LABEL, language)) + ax.set_ylabel(_t(_HEIGHT_LABEL, language)) ax.set_title(f"{_t('GFPE relative sound level', language)} ({format_number(result.frequency, language, decimals=0)} Hz)") - ax.legend(loc="upper right", fontsize="small") + ax.legend(loc=_LEGEND_UPPER_RIGHT, fontsize="small") localize_axes(ax, language) return ax @@ -640,7 +650,7 @@ def plot_tonal_correction_rd1367( handles, labels = ax.get_legend_handles_labels() extra_handles, extra_labels = twin.get_legend_handles_labels() ax.legend(handles + extra_handles, labels + extra_labels, - loc="upper left", fontsize="small") + loc=_LEGEND_UPPER_LEFT, fontsize="small") ax.grid(True, axis="y", alpha=0.3) localize_axes(ax, language) localize_axes(twin, language) @@ -748,7 +758,7 @@ def plot_activity_assessment( ax.set_ylabel(_t("Corrected level [dB]", language)) ax.set_title(_t("RD 1367/2007 assessment vs limit values", language)) - ax.legend(loc="upper right", fontsize="small", ncol=2) + ax.legend(loc=_LEGEND_UPPER_RIGHT, fontsize="small", ncol=2) ax.grid(True, axis="y", alpha=0.3) localize_axes(ax, language) return ax From 1d2e16a02e068b65fb01f5d4194132ff819c366b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20M=2E=20Requena=20Plens?= Date: Sun, 2 Aug 2026 17:46:37 +0200 Subject: [PATCH 4/5] Name the last two repeated labels The constants were written as raw strings and the call sites were not, so two of the eight duplications survived the first pass. Neither string has an escape, so both spellings collapse into one constant. --- src/phonometry/_plot/environment.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/phonometry/_plot/environment.py b/src/phonometry/_plot/environment.py index 9d4525f77..27a2b3e92 100644 --- a/src/phonometry/_plot/environment.py +++ b/src/phonometry/_plot/environment.py @@ -58,9 +58,9 @@ _FREQ_LABEL = "Frequency [Hz]" _HEIGHT_LABEL = "Height [m]" _RANGE_LABEL = "Range [m]" -_TOTAL_A_LABEL = r"$A$ — total" +_TOTAL_A_LABEL = "$A$ — total" _FREE_FIELD_LABEL = "Level re free field [dB]" -_LT_LABEL = r"$L_t$ [dB]" +_LT_LABEL = "$L_t$ [dB]" _STRINGS: dict[str, str] = { "Narrowband spectrum": "Espectro de banda estrecha", @@ -84,7 +84,7 @@ "$A_{atm}$ — atmospheric": "$A_{atm}$ — atmosférica", "$A_{gr}$ — ground": "$A_{gr}$ — suelo", "$A_{bar}$ — barrier": "$A_{bar}$ — barrera", - "$A$ — total": "$A$ — total", + _TOTAL_A_LABEL: _TOTAL_A_LABEL, "Attenuation A [dB]": "Atenuación A [dB]", "ISO 9613-2 attenuation breakdown": "Desglose de atenuación ISO 9613-2", "CNOSSOS-EU railway source line power": ( @@ -116,7 +116,7 @@ "Band level": "Nivel de banda", "$L_t$ vs neighbour mean": "$L_t$ frente a la media de contiguas", "Band level [dB]": "Nivel de banda [dB]", - "$L_t$ [dB]": "$L_t$ [dB]", + _LT_LABEL: _LT_LABEL, "RD 1367/2007 tonal correction $K_t$ = {kt} dB": "Corrección tonal $K_t$ = {kt} dB (RD 1367/2007)", "max $L_{Keq,Ti}$": "máx. $L_{Keq,Ti}$", @@ -352,7 +352,7 @@ def plot_outdoor_attenuation( kwargs.setdefault("color", _C_REFERENCE) kwargs.setdefault("marker", "D") - kwargs.setdefault("label", _t("$A$ — total", language)) + kwargs.setdefault("label", _t(_TOTAL_A_LABEL, language)) ax.plot(positions, np.asarray(result.a_total, dtype=np.float64), zorder=4, **kwargs) ax.axhline(0.0, color=_C_MUTED, lw=0.8) @@ -639,7 +639,7 @@ def plot_tonal_correction_rd1367( index = int(np.argmin(np.abs(freqs - result.governing_frequency))) twin.plot([positions[index]], [lt[index]], "*", color=_C_REFERENCE, ms=14.0, zorder=5) - twin.set_ylabel(_t("$L_t$ [dB]", language)) + twin.set_ylabel(_t(_LT_LABEL, language)) twin.axhline(0.0, color=_C_MUTED, lw=0.8) ax.set_title( From dce4311aab0c5544bd290e76d33cd3d28a7eb905 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20M=2E=20Requena=20Plens?= Date: Sun, 2 Aug 2026 18:07:24 +0200 Subject: [PATCH 5/5] Name the subgroup in the last two link labels Two impulse-prominence bullets printed environment.impulse_prominence, which is a module of environment.assessment. Every backticked module label in the guides now resolves as an import. --- site/src/content/docs/es/guides/impulse-prominence.mdx | 2 +- site/src/content/docs/guides/impulse-prominence.mdx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/site/src/content/docs/es/guides/impulse-prominence.mdx b/site/src/content/docs/es/guides/impulse-prominence.mdx index 0046d93db..7f7790729 100644 --- a/site/src/content/docs/es/guides/impulse-prominence.mdx +++ b/site/src/content/docs/es/guides/impulse-prominence.mdx @@ -364,5 +364,5 @@ sustituye esta medición, no como una función propia. ## Véase también -- Referencia de la API: [`environment.impulse_prominence`](/phonometry/es/reference/api/environment/impulse-prominence/). +- Referencia de la API: [`environment.assessment.impulse_prominence`](/phonometry/es/reference/api/environment/impulse-prominence/). - Referencia de la API: [`environment.assessment.impulsive_sound`](/phonometry/es/reference/api/environment/impulsive-sound/). diff --git a/site/src/content/docs/guides/impulse-prominence.mdx b/site/src/content/docs/guides/impulse-prominence.mdx index 83be86432..b2cec4c2a 100644 --- a/site/src/content/docs/guides/impulse-prominence.mdx +++ b/site/src/content/docs/guides/impulse-prominence.mdx @@ -355,5 +355,5 @@ measurement replaces, not as a function of their own. ## See also -- API reference: [`environment.impulse_prominence`](/phonometry/reference/api/environment/impulse-prominence/). +- API reference: [`environment.assessment.impulse_prominence`](/phonometry/reference/api/environment/impulse-prominence/). - API reference: [`environment.assessment.impulsive_sound`](/phonometry/reference/api/environment/impulsive-sound/).