feat: reactive silencers, HVAC attenuation, machine enclosures and the radiating piston#232
Conversation
There was a problem hiding this comment.
Sorry @jmrplens, your pull request is larger than the review limit of 150000 diff characters
|
Warning Review limit reached
Next review available in: 17 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Organization UI Review profile: ASSERTIVE Plan: Pro Run ID: ⛔ Files ignored due to path filters (5)
📒 Files selected for processing (35)
📝 WalkthroughWalkthroughAdded rigid-piston radiation analysis and industrial noise-control models for silencers, HVAC systems, and enclosures. The changes include result objects, plotting, public exports, documentation, generated figures, conformance checks, and numerical validation tests. ChangesAcoustics and industrial noise control
Estimated code review effort: 4 (Complex) | ~60 minutes Sequence Diagram(s)sequenceDiagram
participant Caller
participant phonometry
participant NoiseControlModel
participant Result
Caller->>phonometry: invoke acoustic analysis API
phonometry->>NoiseControlModel: validate inputs and compute model
NoiseControlModel-->>Result: return arrays, metadata, and derived values
Result-->>Caller: expose fields and plot()
Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Code Review
This pull request introduces a new 'noise_control' domain to model reactive silencers, HVAC duct acoustics, and machine enclosures, alongside a new 'piston' module under 'electroacoustics' for baffled circular piston radiation and directivity. It includes comprehensive mathematical conformance checks, documentation, and tests. The review feedback suggests vectorizing the directivity index calculation in the piston module, validating that the sum of inlet and outlet extensions does not exceed the chamber length in silencers, and enforcing that internal absorption is at most a 1-D array or scalar in enclosures.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| theta = np.linspace(0.0, 0.5 * np.pi, 2001) | ||
| sin_t = np.sin(theta) | ||
| di = np.empty_like(ka) | ||
| for i, kai in enumerate(ka): | ||
| d = np.asarray(piston_directivity(float(kai), theta), dtype=np.float64) | ||
| integrand = d**2 * sin_t | ||
| integral = float(np.trapezoid(integrand, theta)) | ||
| di[i] = 10.0 * np.log10(2.0 / integral) | ||
| return di |
There was a problem hiding this comment.
The _directivity_index function currently loops over each ka value and performs a 2001-point trapezoidal integration sequentially. This can be significantly optimized by leveraging NumPy's broadcasting capabilities to vectorize the integration across all frequencies at once, avoiding the Python loop entirely.
theta = np.linspace(0.0, 0.5 * np.pi, 2001)
sin_t = np.sin(theta)
d = piston_directivity(ka[:, None], theta[None, :])
integrand = d**2 * sin_t[None, :]
integral = np.trapezoid(integrand, theta, axis=-1)
return 10.0 * np.log10(2.0 / integral)References
- Do not use deprecated or removed NumPy 1.x functions (such as
np.trapz) as fallbacks for NumPy 2.0+ features (such asnp.trapezoid) when the project's minimum NumPy version is pinned to 2.0 or later, as these deprecated functions are removed in NumPy 2.0.
There was a problem hiding this comment.
Applied in ede9056: the directivity is vectorized; verified bit-identical (conformance report unchanged) and faster.
| la = require_non_negative(inlet_extension, "inlet_extension") | ||
| lb = require_non_negative(outlet_extension, "outlet_extension") |
There was a problem hiding this comment.
The sum of the inlet and outlet extensions (inlet_extension + outlet_extension) should not exceed the total chamber length, as they cannot physically overlap or extend past each other inside the chamber. Adding a validation check prevents physically impossible configurations.
| la = require_non_negative(inlet_extension, "inlet_extension") | |
| lb = require_non_negative(outlet_extension, "outlet_extension") | |
| la = require_non_negative(inlet_extension, "inlet_extension") | |
| lb = require_non_negative(outlet_extension, "outlet_extension") | |
| if la + lb > length: | |
| raise ValueError("The sum of inlet and outlet extensions cannot exceed the chamber length.") |
There was a problem hiding this comment.
Applied in ede9056: an inlet+outlet extension exceeding the chamber length is rejected (they would physically overlap).
| alpha = np.asarray(internal_absorption, dtype=np.float64) | ||
| if np.any(alpha <= 0.0) or np.any(alpha >= 1.0) or not np.all(np.isfinite(alpha)): | ||
| raise ValueError("'internal_absorption' must lie strictly in (0, 1).") |
There was a problem hiding this comment.
Enforce that internal_absorption is at most a 1-D array or a scalar. If a multi-dimensional array is passed, it could lead to unexpected broadcasting behavior and shape mismatches when computing the insertion loss.
| alpha = np.asarray(internal_absorption, dtype=np.float64) | |
| if np.any(alpha <= 0.0) or np.any(alpha >= 1.0) or not np.all(np.isfinite(alpha)): | |
| raise ValueError("'internal_absorption' must lie strictly in (0, 1).") | |
| alpha = np.asarray(internal_absorption, dtype=np.float64) | |
| if alpha.ndim > 1: | |
| raise ValueError("'internal_absorption' must be a scalar or a 1-D array.") | |
| if np.any(alpha <= 0.0) or np.any(alpha >= 1.0) or not np.all(np.isfinite(alpha)): | |
| raise ValueError("'internal_absorption' must lie strictly in (0, 1).") |
References
- When validating that numeric arrays or maps contain only strictly positive values, ensure they are also validated as finite (rejecting NaN and infinity), as a simple check like any(x <= 0) will fail to catch non-finite values.
There was a problem hiding this comment.
Applied in ede9056: absorption arrays with more than one dimension are rejected.
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #232 +/- ##
==========================================
- Coverage 96.10% 95.96% -0.14%
==========================================
Files 140 146 +6
Lines 18760 19262 +502
==========================================
+ Hits 18029 18485 +456
- Misses 731 777 +46 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
Numerical conformance report✅ 353/353 conformance checks pass across 44 domains and 224 standards - filters class 1 - weightings within IEC 61672-1 class 1. Each row pins a standard clause to its expected normative value and the value the library computes. Every section below is collapsible and stays collapsed while all of its rows pass; a section with any failing row opens automatically. ✅ Numerical validation - filters & weightings: class showcase (IEC 61260-1 · IEC 61672-1 · ISO 7196)IEC 61260-1:2014 class per filter architecture (order 6, one-third-octave, 100 Hz-10 kHz, fs = 48 kHz). For each architecture the table shows, at its binding band, the measured relative attenuation and the class-1 limit it must clear, so the number and the range it must sit in are both visible. A positive margin means the acceptance limits are met with that much room.
Only Butterworth (the library default) and Chebyshev-II are class-compliant architectures. Chebyshev-I and elliptic trade the mask for passband ripple, and Bessel for a maximally-flat group delay (soft rolloff); they cannot satisfy the IEC 61260-1 Class 1/2 attenuation mask by construction, so they are labelled By design - this is expected, not a failure or regression. Frequency-weighting conformance (A/C: IEC 61672-1 Table 3; G: ISO 7196 A.3). The max deviation from nominal is informational (it falls at a frequency extreme where the tolerance is widest and asymmetric); compliance is judged at the binding frequency - the one with the least headroom - where the deviation, the applicable tolerance band and the headroom are shown together.
✅ Filters & weightings: 100% (7/7)
✅ Levels & dosimetry: 100% (6/6)
✅ Room acoustics: 100% (12/12)
✅ Psychoacoustics: 100% (12/12)
✅ Speech transmission (IEC 60268-16): 100% (9/9)
✅ Intensity & sound power: 100% (4/4)
✅ Room & building acoustics: 100% (50/50)
✅ Building prediction & uncertainty: 100% (15/15)
✅ Outdoor propagation & occupational exposure: 100% (10/10)
✅ Materials: absorption, airflow & impedance: 100% (6/6)
✅ Scattering & diffusion (ISO 17497): 100% (5/5)
✅ In-situ road absorption (ISO 13472): 100% (3/3)
✅ Precision sound power (ISO 3745 / 9614-3): 100% (4/4)
✅ Human vibration (ISO 8041 / 2631 / 5349): 100% (15/15)
✅ Speech intelligibility (ANSI S3.5-1997): 100% (7/7)
✅ Objective intelligibility (STOI / ESTOI): 100% (3/3)
✅ Impulsive-sound prominence (NT ACOU 112): 100% (2/2)
✅ Room noise (ANSI S12.2-2019): 100% (3/3)
✅ Hearing threshold (ISO 7029 / ISO 389-7): 100% (3/3)
✅ Measurement uncertainty (GUM / Supplement 1): 100% (7/7)
✅ Noise-induced hearing loss (ISO 1999): 100% (3/3)
✅ Multiple-shock whole-body vibration (ISO 2631-5): 100% (6/6)
✅ Sound absorption in enclosed spaces (EN 12354-6): 100% (2/2)
✅ Prominent discrete tones (ECMA-418-1): 100% (2/2)
✅ Tonal audibility (ISO/PAS 20065): 100% (11/11)
✅ Psychoacoustic annoyance & fluctuation strength (Fastl & Zwicker): 100% (3/3)
✅ Electroacoustics: distortion & frequency response: 100% (14/14)
✅ Calibrated spectral analysis (Bendat & Piersol): 100% (6/6)
✅ Correlation, time delay and envelope (B&P / Knapp & Carter): 100% (7/7)
✅ Underwater acoustics (ISO 18405/17208/18406): 100% (6/6)
✅ Underwater sound propagation (transmission loss): 100% (15/15)
✅ Underwater numerical propagation (modes / rays / PE): 100% (4/4)
✅ Aircraft noise (ICAO Annex 16 / IEC 61265): 100% (14/14)
✅ Rotorcraft noise (ECAC Doc 32 / NORAH2): 100% (12/12)
✅ Wind-turbine noise (IEC 61400-11): 100% (3/3)
✅ Porous & multilayer absorbers (Mechel / Bies / Cox & D'Antonio): 100% (10/10)
✅ Program loudness (ITU-R BS.1770 / EBU R 128): 100% (8/8)
✅ 2D FDTD wave simulation (Attenborough & Van Renterghem 2021, Ch. 4): 100% (2/2)
✅ Swept-sine distortion & phase utilities (Farina / Novak): 100% (7/7)
✅ Spherical ground & barriers (Attenborough / Salomons / Bies): 100% (6/6)
✅ Panel & aperture sound insulation (Bies / Hopkins / Cremer): 100% (11/11)
✅ Atmospheric refraction (Salomons rays / GFPE): 100% (3/3)
✅ Electroacoustics: 100% (6/6)
✅ Industrial noise control: 100% (9/9)
Tests & coverage — 24360 tests, 0 failures (✅ all green)
Conformance harness: |
There was a problem hiding this comment.
Actionable comments posted: 6
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@docs/noise-control.md`:
- Line 23: Add the `text` language hint to the fenced matrix/formula code blocks
in docs/noise-control.md, including the blocks at the referenced locations,
while preserving their contents unchanged.
In `@scripts/conformance_report.py`:
- Around line 5727-5731: Escape both absolute-value pipe characters in the “Bies
5e Eq. (8.73)” description registered by _NOISE_CONTROL, using &`#124`; or \|, and
update the generated entry in docs/CONFORMANCE.md at line 715 to match.
In `@src/phonometry/_plot/noise_control.py`:
- Line 44: Update all three plotting functions, including the call using
result.transmission_loss and the corresponding calls in plot_hvac_spectrum and
plot_enclosure, so default styling values for lw, marker, and ms are merged into
kwargs before calling Axes.plot rather than passed alongside **kwargs. Preserve
these defaults while allowing caller-supplied kwargs to override them without
duplicate-keyword errors.
- Around line 48-52: Update the resonance plotting loop in the result.resonances
block to track whether an in-range resonance line has already been drawn, rather
than using the array index i. Assign the "Resonance" label to the first
ax.axvline that passes the f-range check, including when earlier resonances are
skipped.
In `@src/phonometry/noise_control/enclosures.py`:
- Around line 8-20: Reformat the equations in the module docstring near the
enclosure noise-control documentation so they use non-indented double-backtick
literals instead of 4-space indented blocks. Preserve the equation text and
surrounding explanatory content, ensuring the generated API Markdown no longer
interprets the formulas as indented code blocks.
In `@src/phonometry/noise_control/silencers.py`:
- Around line 140-155: Update cascade() to validate that every matrix has the
same leading frequency-axis size as matrices[0] before performing any np.matmul
operations; raise a clear ValueError on mismatch, while preserving the existing
multiplication behavior for matching grids.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: 3315983b-950b-494f-96cb-51d1e6cd0c06
⛔ Files ignored due to path filters (5)
.github/images/silencer_expansion_chamber.svgis excluded by!**/*.svg.github/images/silencer_expansion_chamber_dark.svgis excluded by!**/*.svg.github/images/silencer_expansion_chamber_es.svgis excluded by!**/*.svg.github/images/silencer_expansion_chamber_es_dark.svgis excluded by!**/*.svgsite/src/generated/api-sidebar.mjsis excluded by!**/generated/**
📒 Files selected for processing (34)
CHANGELOG.mddocs/CONFORMANCE.mddocs/README.mddocs/api-reference.mddocs/electroacoustics.mddocs/noise-control.mdscripts/api_taxonomy.pyscripts/conformance_report.pyscripts/generate_graphs.pysite/astro.config.mjssite/src/content/docs/es/guides/electroacoustics.mdxsite/src/content/docs/es/guides/noise-control.mdxsite/src/content/docs/guides/electroacoustics.mdxsite/src/content/docs/guides/noise-control.mdxsite/src/content/docs/reference/api/electroacoustics/piston.mdsite/src/content/docs/reference/api/index.mdsite/src/content/docs/reference/api/noise_control/enclosures.mdsite/src/content/docs/reference/api/noise_control/hvac.mdsite/src/content/docs/reference/api/noise_control/silencers.mdsrc/phonometry/__init__.pysrc/phonometry/_plot/electroacoustics.pysrc/phonometry/_plot/noise_control.pysrc/phonometry/electroacoustics/__init__.pysrc/phonometry/electroacoustics/piston.pysrc/phonometry/noise_control/__init__.pysrc/phonometry/noise_control/enclosures.pysrc/phonometry/noise_control/hvac.pysrc/phonometry/noise_control/silencers.pytests/electroacoustics/test_piston.pytests/noise_control/test_enclosures.pytests/noise_control/test_fdtd_crosscheck.pytests/noise_control/test_hvac.pytests/noise_control/test_silencers.pytests/test_package_architecture.py
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
docs/noise-control.md (1)
71-74: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winDisambiguate the resonance formulas.
The quarter-wave expression
c / 4 l_ecan be read as(c / 4) * l_e, notc / (4 l_e). Use explicit grouping for both formulas:Suggested clarification
-A Helmholtz resonator (`helmholtz_resonator`) and a closed **quarter-wave -tube** (`quarter_wave_resonator`) each short the duct at their tuning -frequency, `f_0 = (c / 2 pi) sqrt(S_neck / (l_e V))` (Bies Eq. (8.46)) and -`f = c / 4 l_e` (Eq. (8.44)), giving a sharp transmission-loss spike there. +A Helmholtz resonator (`helmholtz_resonator`) and a closed **quarter-wave +tube** (`quarter_wave_resonator`) each short the duct at their tuning +frequency, `f_0 = c / (2 pi) sqrt(S_neck / (l_e V))` (Bies Eq. (8.46)) and +`f = c / (4 l_e)` (Eq. (8.44)), giving a sharp transmission-loss spike there.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@docs/noise-control.md` around lines 71 - 74, Update the resonance formulas in the documentation paragraph to use explicit denominator grouping, especially the quarter-wave expression as c / (4 l_e), and apply equivalent grouping to the Helmholtz formula so both equations are unambiguous. Preserve the surrounding descriptions and references.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Outside diff comments:
In `@docs/noise-control.md`:
- Around line 71-74: Update the resonance formulas in the documentation
paragraph to use explicit denominator grouping, especially the quarter-wave
expression as c / (4 l_e), and apply equivalent grouping to the Helmholtz
formula so both equations are unambiguous. Preserve the surrounding descriptions
and references.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: 0b4ff04b-e798-46aa-b026-a68f7b7b1eaa
📒 Files selected for processing (8)
docs/CONFORMANCE.mddocs/noise-control.mdscripts/conformance_report.pysrc/phonometry/_plot/noise_control.pysrc/phonometry/electroacoustics/piston.pysrc/phonometry/noise_control/enclosures.pysrc/phonometry/noise_control/silencers.pytests/noise_control/test_fdtd_crosscheck.py
ede9056 to
cb25905
Compare
Add the canonical rigid circular piston radiator (Beranek & Mellow 2e, sections 4.19 and 13.7): the radiation impedance from the piston resistance R1 = 1 - 2 J1(2ka)/2ka and reactance X1 = 2 H1(2ka)/2ka, the low-frequency radiation mass 8 rho a^3 / 3, the far-field directivity 2 J1(ka sin t)/(ka sin t) and the directivity index. radiating_piston returns a frozen RadiatingPistonResult with .plot(); piston_resistance, piston_reactance and piston_directivity are exposed as building blocks. Documented in the electroacoustics guide.
New phonometry.noise_control domain (Bies, Hansen & Howard 5e; Munjal): - Reactive silencers by the four-pole transmission-matrix method: expansion chamber (matches the closed form 10 lg[1 + (1/4)(m - 1/m)^2 sin^2 kL] exactly), Helmholtz and quarter-wave side-branch resonators, and extended-tube chambers, with transmission and insertion loss for configurable source/radiation impedances. The four-pole primitives (duct_matrix, shunt_matrix, cascade, transmission_loss, insertion_loss, helmholtz_impedance, quarter_wave_impedance) are exposed for composition. - HVAC duct methods: end reflection (ASHRAE Table 8.14), bend insertion loss (Table 8.11), plenum transmission loss (Wells' method) and flow-generated noise of straight ducts and mitred bends (VDI 2081). - Machine-enclosure insertion loss IL = R - C, where the panel R is supplied by the caller (array or callable) and the interior correction reuses the room constant of phonometry.room. Validated against the closed forms, digitised table nodes and an independent 2D FDTD cross-check of a single expansion chamber. Documented with a guide in both languages and a concept figure of the expansion-chamber TL.
Re-export the piston and high-level noise-control functions at the top level, add the noise_control API-reference section and the piston module to the taxonomy, and register 13 additive numerical conformance checks (piston resistance/reactance/mass/directivity and the silencer, plenum, end-reflection and enclosure closed forms). Regenerate the conformance report, the API reference pages/sidebar and the quick-reference table, and note the additions in the changelog.
…w-noise constant Adversarial review and a source re-check turned up two numerical errors: - insertion_loss returned the reciprocal of the correct ratio, so a real silencer reported a negative insertion loss. Use IL = 20 lg|(T11 Zr + T12 + Zs Zr T21 + Zs T22)/(Zs + Zr)|, which is 0 for a through connection and equals the transmission loss for the anechoic reference Zs = rho c/S_in, Zr = rho c/S_out. Added a test pinning IL = TL for that reference (the previous identity-only test could not see the sign) and a conformance check. - flow_noise_straight_duct dropped the -2 constant of Bies Eq. (8.251) (VDI 2081-1); added it and pinned the numeric value. Also harden shunt_matrix against the divide-by-zero at an exact lossless resonance, and correct the flow_noise_bend docstring: the radiated power grows as U^6/U^8 (dipole/quadrupole) while only the stream-power efficiency grows as U^3/U^5 (the formula itself already matched Bies Eqs. (8.252)/(8.254)).
…dings Sonar: - S1244 (piston): replace exact float == 0 tests with finite-masking at the removable 0/0 singularities of R1, X1 and the directivity (physics and all oracles unchanged). - S3776 (enclosures): split enclosure_insertion_loss into _resolve_frequencies and _resolve_panel_r helpers with identical semantics. - S5863 (FDTD cross-check test): compare two independently computed runs instead of a self-comparison, keeping the determinism check meaningful. Review bots (verified against Bies / Beranek & Mellow before applying): - Vectorise the piston directivity-index quadrature over frequency (bit-identical results, confirmed by the byte-stable conformance report). - Validate that a silencer chain shares one frequency grid (cascade) and that an extended tube's inlet+outlet extensions do not exceed the chamber length. - Reject a multi-dimensional interior absorption in enclosures. - Use kwargs.setdefault for the plot line styles so a caller-supplied lw/marker/ms no longer raises, and keep the resonance legend label when the first resonance is out of range. - Rephrase the side-branch conformance description with abs(...) so the pipes no longer break the Markdown table, and add a text language hint to the formula code blocks of the noise-control doc. Rejected: converting the enclosures docstring equation blocks to inline literals (the indented-equation-block style is the library-wide docstring convention, used by ~120 modules; changing one would be inconsistent).
cb25905 to
82b8402
Compare
|



Summary
Industrial noise control in a new
phonometry.noise_controldomain, plus the radiating piston in electroacoustics, clean-room from Bies, Hansen and Howard, Ver and Beranek, and Beranek and Mellow.enclosure_insertion_lossfrom a caller-supplied panel transmission loss (measured, from a catalogue, or from the library's own panel prediction) and the interior room constant.radiating_piston: radiation resistance, reactance and directivity of a circular piston in an infinite baffle.The enclosure takes the panel R as an input rather than importing a predictor, so this stands alone; the interior correction reuses the existing room constant.
Validation
The expansion chamber matches its closed form to machine precision (peaks 1.94/6.55/12.18/18.10 dB for area ratios 2/4/8/16), the quarter-wave tuning reproduces the Bies worked example (56.6 Hz), det(T)=1 reciprocity and cascade additivity hold, and IL equals TL for the anechoic reference. The piston matches the closed-form resistance, reactance and directivity, the small- and large-ka limits, the radiation mass and the first directivity null. HVAC nodes hit the ASHRAE and VDI tables. An independent 2D FDTD of one expansion chamber gives a 6.57 dB peak-to-trough ratio against the 6.55 dB closed form. Fourteen new conformance checks (339 total, byte-exact report).
Review found and fixed a real sign inversion in insertion_loss (its identity-only test could not catch it) and a missing constant in the straight-duct flow-noise formula, and corrected a docstring where a reviewer suggestion would have introduced a bug.
Checks
ruff, mypy, bandit, full pytest (3895 passed, no new warnings), conformance byte-exact, figures within tolerance (856, four new variants), api-docs with no drift, site i18n parity and full build clean. Adversarially reviewed in two passes.
Summary by CodeRabbit
New Features
Documentation
Tests