Keep a data sheet's absorption and a laboratory's insulation as rows of their own, each rated again from its bands, and a guide to reading a material datasheet (#890)
* Keep a data sheet's practical and one-third-octave absorption, a laboratory's sound reduction index and a covering's impact improvement as rows of their own, each rated again from its bands
PracticalAbsorptionSpectrum holds the practical coefficients of ISO 11654 from 125 Hz to 4 kHz and ThirdOctaveAbsorptionSpectrum the ISO 354 coefficients from 50 Hz to 10 kHz, both with the weighted coefficient, shape indicators, class, NRC, mounting, thickness and depth of construction the sheet prints. rating() works the weighted coefficient out again from the bands, and practical() turns one-third octaves into practical coefficients, each marked as derived and never borrowed from a neighbouring band.
SoundReductionSpectrum holds the ISO 10140-2 sound reduction index in 21 one-third octaves with Rw, C, Ctr and the Annex B terms of ISO 717-1; ImpactImprovementSpectrum holds the ISO 10140-3 reduction of impact level with Delta Lw, CI,r and CI,Delta. Their rating() calls weighted_rating_extended and the ISO 717-2 improvement functions; the new ImpactImprovementRatingResult carries Delta Lw with both adaptation terms and plots the spectrum.
Reading a catalogue file notes a practical coefficient above 1.00 or off the steps of 0.05, and a printed rating the bands do not give, without changing any cell. weighted_rating, weighted_impact_rating, weighted_impact_improvement and impact_improvement_adaptation_term take a mapping of band to value and refuse a missing band.
* A guide to reading a material datasheet, and the catalogues guide names the row classes that now hold a sheet's absorption and a laboratory's insulation
"Reading a material datasheet" (EN, ES and the docs mirror) sits first among the materials guides and says what each number on a data sheet or a test report is before it is typed: the practical coefficient of ISO 11654 against the one-third-octave coefficient of ISO 354, the steps of 0.05, the cap of 1.00 and the 125 Hz band that is held and not rated; a level declared under CE marking, read from EN 13162, as a bound and never a value; measured against declared, calculated or estimated; the mounting and the depth of construction; the revision and the day it was read; the decimal comma and a corrupt text layer; and the sound reduction index and the impact improvement of a laboratory report. Its examples turn a test report into practical coefficients, read a sheet's octave table from CSV with the notes it earns, and rate a wall and refuse a declared impact improvement a prediction cannot take.
The catalogues guide, the Files overview, the materials overview and the published catalogues page link it, and no longer say that no row class holds these quantities. The guides index counts 146.
* A one-third-octave row's practical coefficients keep a derived text the row already carried on a shared field
* Six coefficients ending in 0 or 5 look like practical ones, and only a sheet that names them says they are
* A negative absorption coefficient is refused rather than read as zero, and a covering's improvement plot keeps a negative band in view
ThirdOctaveAbsorptionSpectrum.rating() refuses a negative one-third-octave
coefficient, naming the row, the band and the value, as
practical_absorption_coefficient does; practical() leaves the octave it
falls in empty and says why in not_derivable, so practical().rating()
refuses the same rating band and a negative band below 200 Hz only empties
the unrated 125 Hz octave. Nothing is floored to 0.00 any more.
A printed absorption class is compared with the one the bands give on its
casefolded word, so "not classified", "c" and "Class C" agree with the
bands that give them. A coefficient above 1 is explained as ISO 354 3.9
NOTE 2 does, by diffraction effects.
ImpactImprovementSpectrum cites the definition of Delta L where it is, ISO
10140-1 Annex H, Equation (H.1), with the levels measured by ISO 10140-3.
The ISO 717-2 improvement plot starts its axis at 0 dB only when no band is
below it, and its title reads "ISO 717-2 Delta Lw (CI,Delta=-9; CI,r=-2) =
15 dB" with the typographic minus of the other rating plots, short enough
for the default figure in both languages. The enlarged range of the ISO
717-1 and ISO 717-2 extended rating plots is shaded with an opaque wash
that stays visible on a dark page.
* The datasheet guide draws each rating it works out, rates a floor covering's report, and cites ISO 10140-1 for the impact improvement
The guide on reading a material datasheet now shows the figure of every
rating its examples work out: the ISO 11654 rating of a test report's
one-third octaves, the ISO 717-1 rating of a laboratory's sound reduction
index over 50 Hz to 5 kHz, and a new example that reads a floor covering's
Delta L from its report and rates it to Delta Lw 15 dB with CI,Delta -9 dB
and CI,r -2 dB. Each figure is the result's own plot() with the code behind
it, in English and Spanish.
Delta L is cited to ISO 10140-1 Annex H, which defines it, with ISO 10140-3
as the measurement; a coefficient above 1 is explained by diffraction, as
ISO 354 does; the glazed partition of the example is a 4 mm pane, which is
where its coincidence dip at 3.15 kHz puts it; the guide says what happens
to a negative coefficient; and two Spanish sentences read as the English
ones do.
* The search check weighs a plain search on two copies of the index that have read the same chunks
On this branch the Spanish search for "ruido" failed the check as if the
standards field had reordered it. Nothing in the field matches "ruido";
the reorder came from how much of the index each side had read. Pagefind
fetches the word index in chunks and keeps them, and a search ranks every
word that begins with what was typed among the chunks it holds, while it
fetches only the chunk that takes in the typed word or its stem. The pages
this branch adds moved a chunk boundary between "ruid" and "ruidos", the
stem of "ruidoso". The weighted copy the check compared against had already
run every designation and held both chunks; the unweighted copy was fresh
and held one, so the two pages whose image text says "ruidoso" rose only on
the weighted side. Two fresh copies at weight 3 and weight 0 return the
same top five, and so do two copies that have both read the second chunk.
The comparison now runs on two fresh copies that make the same searches in
the same order, and a search that returns different pages from them fails
as a difference in what they read, not as a reorder by the field.
loadSearchIndex returns a fresh copy on every call, as it said it did:
Node keeps one module per address, so a second call for the same language
and weight used to get the copy already in use.
* The refusal of a band set and the annex of the enlarged ranges are each named once
The refusal ratings.py gives a band set it does not read is one constant for
its three uses, and the clause the sound reduction sheet cites for its five
enlarged adaptation terms is one. The test of the one-third-octave rating
holds the rating it worked out on the left, as the rest of the file does.
Make a refusal test pin the refusal it is named for (2 of 3) (#638)
* Make a refusal test pin the refusal it is named for (2 of 3)
* Give the three absolute-zero guards the tests they never had
Correcting a comma decimal in seven error messages dropped new-code coverage
to 57.1 percent, and the arithmetic says what the gate was pointing at: four
of the seven guards were already exercised and three were not, and four over
seven is that number exactly.
The three are refused now through their public entry points, and each was
shown to pin its guard by deleting the guard in a scratch tree and watching
the test fail. What the unguarded code returns for a temperature below
absolute zero is worth recording: an air density of minus thirteen kilograms
per cubic metre, a speed of sound of NaN out of a square root of a negative
number, and a sound power level of NaN out of a logarithm of a negative
ratio. None of the three refused anything on its own.
None of the tests pins the number. -273.15 and 0.5 are the library's, and
pinning the very value that was just corrected would be the defect this
branch exists to close, wearing the other face.
A fourth test is here that moves no coverage at all. The Poisson ratio guard
was already reached, but only ever from above: the existing test probes 0.5
and nothing had tried the other half of -1 < nu < 0.5. Both bounds turn out
to be closed, and below the lower one the unguarded frame modulus is exactly
zero, which divides.
Make a refusal test pin the refusal it is named for (2 of 3) (#638)
* Make a refusal test pin the refusal it is named for (2 of 3)
* Give the three absolute-zero guards the tests they never had
Correcting a comma decimal in seven error messages dropped new-code coverage
to 57.1 percent, and the arithmetic says what the gate was pointing at: four
of the seven guards were already exercised and three were not, and four over
seven is that number exactly.
The three are refused now through their public entry points, and each was
shown to pin its guard by deleting the guard in a scratch tree and watching
the test fail. What the unguarded code returns for a temperature below
absolute zero is worth recording: an air density of minus thirteen kilograms
per cubic metre, a speed of sound of NaN out of a square root of a negative
number, and a sound power level of NaN out of a logarithm of a negative
ratio. None of the three refused anything on its own.
None of the tests pins the number. -273.15 and 0.5 are the library's, and
pinning the very value that was just corrected would be the defect this
branch exists to close, wearing the other face.
A fourth test is here that moves no coverage at all. The Poisson ratio guard
was already reached, but only ever from above: the existing test probes 0.5
and nothing had tried the other half of -1 < nu < 0.5. Both bounds turn out
to be closed, and below the lower one the unguarded frame modulus is exactly
zero, which divides.
The unit goes in the name, where the caller writes the number (#752)
* The unit goes in the name, where the caller writes the number
A static pressure of 101 325 and a static pressure of 101.325 are both
legitimate values in this tree: the ISO 3740 family prints kilopascals,
IEC 61094-2 and ISO 15186-3 Annex A print pascals. Nothing separates them
by magnitude, because either reading is a real duty somewhere else, and
the result of the mix-up is still a float.
146 public parameters take their unit as a suffix. No value changes:
every function keeps the unit it already documented, so no oracle, no
printed figure and no conformance row moves.
temperature -> temperature_c across 39 callables, with temperature1_c,
temperature2_c, temperatures_c and temperature_celsius folded into the
same spelling; GasStream.inlet_temperature, which IEC 60534-8-3 states in
kelvin, becomes inlet_temperature_k. relative_humidity and the ISO 354
humidity both become relative_humidity_percent. The ambient pressure of
the propagation and aircraft chains becomes atmospheric_pressure_kpa, the
name air_density_astm and air_density_iso10534 already used; the ISO 3740
family takes static_pressure_kpa and the pascals of ISO 15186-3 Annex A,
ISO 9053-2 and ISO 7235 take static_pressure_pa, matching fluids.air.
Every other member of the family takes _pa.
Acoustic pressure keeps its bare name: a waveform is always in pascals
and a Signal carries its own calibration, so a suffix would restate the
type rather than resolve an ambiguity.
scripts/check_parameter_units.py holds the rule from here on. It walks
the imported package, not the source tree, because ReportMetadata is
published from the root and defined in a private module. Against main it
reports 154 offenders; here it reports none.
* Regenerate the API reference and the llms artifacts after the rewrap
* Pin the renamed argument in the four refusals nothing was reading
The unit goes in the name, where the caller writes the number (#752)
* The unit goes in the name, where the caller writes the number
A static pressure of 101 325 and a static pressure of 101.325 are both
legitimate values in this tree: the ISO 3740 family prints kilopascals,
IEC 61094-2 and ISO 15186-3 Annex A print pascals. Nothing separates them
by magnitude, because either reading is a real duty somewhere else, and
the result of the mix-up is still a float.
146 public parameters take their unit as a suffix. No value changes:
every function keeps the unit it already documented, so no oracle, no
printed figure and no conformance row moves.
temperature -> temperature_c across 39 callables, with temperature1_c,
temperature2_c, temperatures_c and temperature_celsius folded into the
same spelling; GasStream.inlet_temperature, which IEC 60534-8-3 states in
kelvin, becomes inlet_temperature_k. relative_humidity and the ISO 354
humidity both become relative_humidity_percent. The ambient pressure of
the propagation and aircraft chains becomes atmospheric_pressure_kpa, the
name air_density_astm and air_density_iso10534 already used; the ISO 3740
family takes static_pressure_kpa and the pascals of ISO 15186-3 Annex A,
ISO 9053-2 and ISO 7235 take static_pressure_pa, matching fluids.air.
Every other member of the family takes _pa.
Acoustic pressure keeps its bare name: a waveform is always in pascals
and a Signal carries its own calibration, so a suffix would restate the
type rather than resolve an ambiguity.
scripts/check_parameter_units.py holds the rule from here on. It walks
the imported package, not the source tree, because ReportMetadata is
published from the root and defined in a private module. Against main it
reports 154 offenders; here it reports none.
* Regenerate the API reference and the llms artifacts after the rewrap
* Pin the renamed argument in the four refusals nothing was reading
The unit goes in the name, where the caller writes the number (#752)
* The unit goes in the name, where the caller writes the number
A static pressure of 101 325 and a static pressure of 101.325 are both
legitimate values in this tree: the ISO 3740 family prints kilopascals,
IEC 61094-2 and ISO 15186-3 Annex A print pascals. Nothing separates them
by magnitude, because either reading is a real duty somewhere else, and
the result of the mix-up is still a float.
146 public parameters take their unit as a suffix. No value changes:
every function keeps the unit it already documented, so no oracle, no
printed figure and no conformance row moves.
temperature -> temperature_c across 39 callables, with temperature1_c,
temperature2_c, temperatures_c and temperature_celsius folded into the
same spelling; GasStream.inlet_temperature, which IEC 60534-8-3 states in
kelvin, becomes inlet_temperature_k. relative_humidity and the ISO 354
humidity both become relative_humidity_percent. The ambient pressure of
the propagation and aircraft chains becomes atmospheric_pressure_kpa, the
name air_density_astm and air_density_iso10534 already used; the ISO 3740
family takes static_pressure_kpa and the pascals of ISO 15186-3 Annex A,
ISO 9053-2 and ISO 7235 take static_pressure_pa, matching fluids.air.
Every other member of the family takes _pa.
Acoustic pressure keeps its bare name: a waveform is always in pascals
and a Signal carries its own calibration, so a suffix would restate the
type rather than resolve an ambiguity.
scripts/check_parameter_units.py holds the rule from here on. It walks
the imported package, not the source tree, because ReportMetadata is
published from the root and defined in a private module. Against main it
reports 154 offenders; here it reports none.
* Regenerate the API reference and the llms artifacts after the rewrap
* Pin the renamed argument in the four refusals nothing was reading
The unit goes in the name, where the caller writes the number (#752)
* The unit goes in the name, where the caller writes the number
A static pressure of 101 325 and a static pressure of 101.325 are both
legitimate values in this tree: the ISO 3740 family prints kilopascals,
IEC 61094-2 and ISO 15186-3 Annex A print pascals. Nothing separates them
by magnitude, because either reading is a real duty somewhere else, and
the result of the mix-up is still a float.
146 public parameters take their unit as a suffix. No value changes:
every function keeps the unit it already documented, so no oracle, no
printed figure and no conformance row moves.
temperature -> temperature_c across 39 callables, with temperature1_c,
temperature2_c, temperatures_c and temperature_celsius folded into the
same spelling; GasStream.inlet_temperature, which IEC 60534-8-3 states in
kelvin, becomes inlet_temperature_k. relative_humidity and the ISO 354
humidity both become relative_humidity_percent. The ambient pressure of
the propagation and aircraft chains becomes atmospheric_pressure_kpa, the
name air_density_astm and air_density_iso10534 already used; the ISO 3740
family takes static_pressure_kpa and the pascals of ISO 15186-3 Annex A,
ISO 9053-2 and ISO 7235 take static_pressure_pa, matching fluids.air.
Every other member of the family takes _pa.
Acoustic pressure keeps its bare name: a waveform is always in pascals
and a Signal carries its own calibration, so a suffix would restate the
type rather than resolve an ambiguity.
scripts/check_parameter_units.py holds the rule from here on. It walks
the imported package, not the source tree, because ReportMetadata is
published from the root and defined in a private module. Against main it
reports 154 offenders; here it reports none.
* Regenerate the API reference and the llms artifacts after the rewrap
* Pin the renamed argument in the four refusals nothing was reading
Verdicts on a printed limit that read the decimal value, not its last bits (#914)
A quantity computed from decimal readings (a difference, a mean, a ratio, a spread) and judged against a limit a standard prints in decimal is now settled to nine decimal places before it meets that limit, and a half is rounded on the settled value, so 32,3 - 26,3 dB and 32,2 - 26,2 dB are the same 6 dB margin on every machine. One private helper does this for the whole library, and the six local copies of the same nine-decimal rule now go through it. The settled value only decides the verdict; every result keeps the value it computed. Two results change shape at a degenerate input: room.open_plan_metrics returns NaN for a distance when the STI does not fall with distance, and a hammer and floor that are critically damped in decimal get the critically damped pulse instead of NaN.
Every catalogue row has one shape and says what its source claims for each cell (#862)
* Every published catalogue cell is pinned to its last digit
The 1982 rows of the 23 published catalogues are dumped once, every float by
its repr, into tests/data/published_catalogues/baseline.json. A test takes
that baseline through every change listed in tests/catalogue_fingerprint.py
and asserts that the result is, field for field, what the library builds
today. The page generator's check sees the numbers only as the site prints
them; this sees the last digit of a float and a hedge moved from one field to
another, so a change to how a row is built has to say what it moved.
* Every catalogue row has one shape and says what its source claims
The rows of every published catalogue share one base, and phonometry.io now
publishes it: CatalogueRow, BandedRow, CatalogueError and CATALOGUE_BASES.
A row gains basis, what the source claims a value is (measured, declared,
calculated, estimated or extended), read through basis_of. The estimate had
two spellings, SolidMaterial.estimated with is_estimate and
OrthotropicWood.estimated with is_estimated; both are gone, and the 35 cells
Hopkins Table A2 and Rossing Table 15.5 mark as estimates hold "estimated"
in basis.
A value the page prints in another unit is no longer derived: the 116 cells
of Ver & Beranek Table 14.1 in degrees Fahrenheit and psi and the nine of
Long Table 7.1 in sabins keep the printed figure and unit in converted. A
cell the page leaves blank and prints on another row (Ver & Beranek Table
8.7, ASHRAE Table 30, Harris Chapter 32) names that row in carried.
is_derived now answers only for what the library computes. The published
catalogues page marks converted and carried cells as such, with the printed
figure on a converted one, and a test walks every hedged cell of every
catalogue to hold the page to it.
OrthotropicWood and PlateauMaterial take their fields by name only, and no
row class is slotted, because on Python 3.13 a slotted dataclass cannot call
super() without arguments from its own methods. No published value changes:
the fingerprint lists the 54 rows the move touched and nothing else moved.
* A catalogue cell no longer claims a unit or a derivation its page does not print
Long prints the six figures of his musician with no unit. The row now says
so in its note and why they are read as sabins, and `converted` is described
everywhere as the page's figure and its unit rather than as what the page
prints; the published catalogues page words its tooltip the same way. A
figure a packaged table prints with another SI prefix, such as the
megapascals of Rossing Table 15.5, keeps no `converted` entry, and the
docstring says so.
`carried` covers a value the page gives by reference to another row, which
includes the three Harris rows that print "Parecido al anterior" and no row
number, not only a cell left blank under a block. The two ASHRAE rows that
carry a diameter down no longer call it a derivation in their notes, and the
damping paragraph of the catalogues page no longer says converted cells are
marked as worked out.
The fingerprint compares every row as the text the baseline holds, so a
float served as an integer, or a flag served as a number, is a change. The
page generator refuses a cell whose hedges it cannot show, two on one value
or an estimate, a conversion or a carried value on an interval, a list or a
word, instead of publishing it with one of them missing; the test walks
every hedge of every published row to hold it to that.
The older unreleased notes that still named `is_estimated`, the `estimated`
field and cells marked derived now name `basis`, `basis_of` and `converted`.
The scripts and tests that import `CatalogueRow` take it from
`phonometry.io`, and the Spanish pages spell the sabin one way.
* An approximate interval keeps its tilde on the catalogue page
The porosity Cox Table 6.5 prints for granular vermiculite is an interval with
a tilde, and the page showed the bare interval. The range and bound kinds have
no mark of their own, so the tilde now goes into the cell text.
The fingerprint's migration test also requires every field outside the hedges
the step moves to stay equal, basis_of names all five bases, the baseline's
README states the terms that cover it, and the test that probed the
interpreter for the slotted super() failure is gone: Python 3.13.15 carries
the fix and 3.13.5 does not, so it tested the Python patch release, not the
library.
* A list of readings printed with a tilde keeps it on the catalogue page
Like an interval, a cell holding several readings has no mark of its own for
a tilde, so it goes into the text. The generator now refuses an approximate
hedge only on a printed word, which has nowhere to show it. No published cell
changes.
* A catalogue row refuses a basis outside the five words
The catalogue page and every reader of basis_of act on measured, declared,
calculated, estimated and extended only, so a row built with any other word,
such as a misspelt estimate, now raises CatalogueError instead of reaching
the page as a printed number.
Nineteen tables of porous parameters, and the hedges every catalogue row carries (#821)
* Nineteen tables of porous parameters, and the hedges every catalogue row carries
`materials.PUBLISHED_POROUS` is the porous rows of the nineteen parameter
tables Allard & Atalla print across eight chapters, keyed by the table they
came off and the row they are. It replaces the two specimens
`PUBLISHED_POROUS_MATERIALS` held, which are now
`'allard-2009-table-6-1/domisol_coffrage'` and
`'allard-2009-table-11-2/soft_fibrous'`, and it reaches the models the same
way they did.
The key names the table because the book prints one foam in four chapters and
one glass wool in two, sometimes with a column the other page leaves out, so a
flat name would have to pick between two published parameter sets in silence.
`porous_materials_named` gathers every printing of a name and leaves the
choice with the caller: five pages print a "Foam" and they are five different
foams.
Every quantity is optional, because no two of these tables print the same
columns, and a row that answers `None` says why. Table 11.5 prints the word
"model" in three cells of its screen row, because the lengths there were
derived from a measured flow resistance and the tortuosity taken as a
frequency-dependent expression; Table 12.1 prints "variable" for a thickness
its example sweeps. `why_missing` hands those words back, and `printed`
refuses with them rather than returning a float nobody published.
The units are the other half of the work. Table 9.1 prints its characteristic
lengths in millimetres and its thickness in centimetres, Table 13.1 prints its
lengths in metres, the rest are in micrometres, and Table 6.1 prints a complex
shear modulus in N/cm2 where Table 11.8 prints the same specimen's Young's
modulus in pascals. Every conversion is written into the oracle literal and
asserted against the printed digits, and the two glass-wool pages reconcile
through E = 2N(1 + nu), which is also what lets a row that printed one of the
two answer for both, marked as derived.
The fields that say what a cell was when it was not a value move to a shared
row in `_internal/catalogue.py`, so a solid and a porous specimen answer the
same way, and `reported` joins them: a cell that prints several readings from
several studies with no single value, which is how the compiled tables of the
absorber literature print a characteristic length.
Three tools were adjusted rather than worked around. The provenance gate
learns to read a data directory, because a catalogue of one file per published
table cannot list nineteen of them in a comment without that comment becoming
a second copy of the contents. The API reference generator learns to carry an
inherited attribute into the page it publishes, because factoring the shared
row out had silently emptied half of SolidMaterial's attribute table.
Membership there is read from the dataclass fields and not from `hasattr`: a
field with no default and a field built by a factory both leave the class
without an attribute of that name.
The plates, septa and impervious screens these pages also print are not porous
materials and are not in the catalogue; each data file says which of its
page's rows are absent and why.
Four entries join the errata register, found by reading the pages against each
other: a caption that names the wrong figure, two thicknesses the facing prose
contradicts, and a frame modulus three orders of magnitude from the resonance
the text reports for the same specimen. Every row keeps what its page printed.
* Regenerate the llms artefacts for the porous catalogue prose
* The banner reads in order, and the docstrings count what the catalogue holds
A catalogue row is completed by one path, and a banded row reads at any array of bands (#870)
* A catalogue row is completed by one path, and a banded row reads at any array of bands
io.CatalogueRow.from_printed(**cells) is now the one place that works anything out of a row's printed cells. It checks the cells the way the constructor does, converts a figure written under a unit the row class takes as an alias of its own, builds the row, fills what follows from the printed cells and names each filled value in derived. Every packaged loader builds through it: the completions of the solids and of the porous materials are hooks it calls, with the same arithmetic in the same order, and the sabins of Long Table 7.1 are an alias of AbsorptionAreaSpectrum rather than a step of the loader. Which of the two absorption classes a row is stays the loader's choice. Building with Cls(...) stays literal.
row.printed_fields() gives back the cells the page prints, the converted and the carried ones included and the derived ones left out, so type(row).from_printed(**row.printed_fields()) is the row again for every published row. Changing a cell there and building again is how a row is edited; dataclasses.replace copies derived values that no longer follow from the cell that changed, and the docstrings say so.
A figure in another unit is converted on its digits with the exact factor and rounded once. For Long Table 7.1 that moves four cells by one binary digit, the musician's 500, 2000 and 4000 Hz areas and the air's 2000 Hz area (1.0683849600000002 becomes 1.06838496, 0.007545931758530183 becomes 0.007545931758530184), and nothing a page prints.
When the printed cells a derived value rests on, followed back through the values derived first, do not all have one basis, the derived text names the basis of each. In the published catalogues that is the nineteen rows of Hopkins Table A2 whose Poisson ratio the page marks as an estimate: each of their five derived texts now says the value rests on that estimate and on a plate speed and a density whose basis the page does not state. Both published changes are steps of the catalogue fingerprint, which lists every cell they move, and a test checks that the loaders build what they built before apart from those cells.
io.BandedRow.values_at(frequencies_hz) reads a banded row at an array of frequencies, for the functions that take one value per band by position. Each frequency matches the band whose centre is nearest on a logarithmic scale, within a sixth of the spacing between bands, so nominal, exact base-ten and exact base-two centres read the same band, and a band the row does not print is refused with what the page had there. It answers what the per-band accessors answer for every published banded row. The refusal of a band no table prints now reads "an octave band that absorption tables print".
* Cells the completion cannot take are refused by name, and an empty per survives printed_fields
from_printed now refuses printed cells its arithmetic cannot take, with an io.CatalogueError that names the value, the printed cells it would rest on and their values. A modulus of 1 GPa beside a shear modulus of 0.1 GPa gives a Poisson ratio of 4, which no isotropic solid has: without a density it used to be stored as a derived Poisson ratio, and with one it surfaced as a ValueError about a cell nobody printed. A printed Poisson ratio of 0.7 gives a plate speed but no speed in an unbounded solid, and the refusal says which. The arithmetic runs only for a cell it fills, so a row whose cells are not meant to give a value says so in not_derivable and loads.
printed_fields() leaves a field out only when it holds nothing and nothing is also its default. An absorption area whose per is empty used to come back as an area per person after the round trip; it now comes back as it was. A derived passed to the literal constructor is the caller's, and the docstrings and the API table now say that printed_fields() leaves it out with its value.
A figure left empty under one unit alias still speaks for its cell, so a second alias for the same cell is refused as the cell given twice.
The changelog and the fingerprint step now say that Hopkins Table A2 marks twenty-one Poisson ratios as estimates, and that nineteen of those rows derive from them: aircrete and brick print their density as a range. The docstrings of convert_figure and of the figure a float is written as say what repr gives rather than that a float is its decimal.
Tests pin each of these: the Poisson ratio from two moduli against 21/79 worked by hand, every fill site with one estimated input at a time, the refusals, not_derivable, the empty per and the empty alias figure.
* The alias checks of the completion read as two small steps, and a finite number is named once
* An empty figure in a unit alias needs no text field to explain it
* The Spanish catalogues page says which estimate the Hopkins derived values rest on
A catalogue row completes itself from what its table printed (#808)
* A catalogue row completes itself from what its table printed
Four published relations, each filling a hole a published specimen usually
has: the porosity from the two densities and the airflow resistivity from the
bulk density and the fibre diameter, both from Hopkins, and the viscous
characteristic length from the three parameters that are measured routinely or
both lengths from the fibre geometry, both from Allard & Atalla.
Each of them says how good an estimate it is, measured against a printed page
rather than claimed. Hopkins' two equations check each other on his own rock
wool. Eq. (5.25) is held against the twenty-three specimens Allard & Atalla
print with all four columns at once, which is what the docstring's factor of
two is. And the two routes to the viscous length are made to disagree on
purpose, because they are different models.
* The generated reference follows the rebase
* The paragraph that compares the two models sits above the field list, not inside it
* Every assertion in the new test file reads measured first
* The fit comparison reads as a call, so its measured side is unmistakable
* A layer as dense as its own fibre is refused, and two sentences say what they meant
Every catalogue row has one shape and says what its source claims for each cell (#862)
* Every published catalogue cell is pinned to its last digit
The 1982 rows of the 23 published catalogues are dumped once, every float by
its repr, into tests/data/published_catalogues/baseline.json. A test takes
that baseline through every change listed in tests/catalogue_fingerprint.py
and asserts that the result is, field for field, what the library builds
today. The page generator's check sees the numbers only as the site prints
them; this sees the last digit of a float and a hedge moved from one field to
another, so a change to how a row is built has to say what it moved.
* Every catalogue row has one shape and says what its source claims
The rows of every published catalogue share one base, and phonometry.io now
publishes it: CatalogueRow, BandedRow, CatalogueError and CATALOGUE_BASES.
A row gains basis, what the source claims a value is (measured, declared,
calculated, estimated or extended), read through basis_of. The estimate had
two spellings, SolidMaterial.estimated with is_estimate and
OrthotropicWood.estimated with is_estimated; both are gone, and the 35 cells
Hopkins Table A2 and Rossing Table 15.5 mark as estimates hold "estimated"
in basis.
A value the page prints in another unit is no longer derived: the 116 cells
of Ver & Beranek Table 14.1 in degrees Fahrenheit and psi and the nine of
Long Table 7.1 in sabins keep the printed figure and unit in converted. A
cell the page leaves blank and prints on another row (Ver & Beranek Table
8.7, ASHRAE Table 30, Harris Chapter 32) names that row in carried.
is_derived now answers only for what the library computes. The published
catalogues page marks converted and carried cells as such, with the printed
figure on a converted one, and a test walks every hedged cell of every
catalogue to hold the page to it.
OrthotropicWood and PlateauMaterial take their fields by name only, and no
row class is slotted, because on Python 3.13 a slotted dataclass cannot call
super() without arguments from its own methods. No published value changes:
the fingerprint lists the 54 rows the move touched and nothing else moved.
* A catalogue cell no longer claims a unit or a derivation its page does not print
Long prints the six figures of his musician with no unit. The row now says
so in its note and why they are read as sabins, and `converted` is described
everywhere as the page's figure and its unit rather than as what the page
prints; the published catalogues page words its tooltip the same way. A
figure a packaged table prints with another SI prefix, such as the
megapascals of Rossing Table 15.5, keeps no `converted` entry, and the
docstring says so.
`carried` covers a value the page gives by reference to another row, which
includes the three Harris rows that print "Parecido al anterior" and no row
number, not only a cell left blank under a block. The two ASHRAE rows that
carry a diameter down no longer call it a derivation in their notes, and the
damping paragraph of the catalogues page no longer says converted cells are
marked as worked out.
The fingerprint compares every row as the text the baseline holds, so a
float served as an integer, or a flag served as a number, is a change. The
page generator refuses a cell whose hedges it cannot show, two on one value
or an estimate, a conversion or a carried value on an interval, a list or a
word, instead of publishing it with one of them missing; the test walks
every hedge of every published row to hold it to that.
The older unreleased notes that still named `is_estimated`, the `estimated`
field and cells marked derived now name `basis`, `basis_of` and `converted`.
The scripts and tests that import `CatalogueRow` take it from
`phonometry.io`, and the Spanish pages spell the sabin one way.
* An approximate interval keeps its tilde on the catalogue page
The porosity Cox Table 6.5 prints for granular vermiculite is an interval with
a tilde, and the page showed the bare interval. The range and bound kinds have
no mark of their own, so the tilde now goes into the cell text.
The fingerprint's migration test also requires every field outside the hedges
the step moves to stay equal, basis_of names all five bases, the baseline's
README states the terms that cover it, and the test that probed the
interpreter for the slotted super() failure is gone: Python 3.13.15 carries
the fix and 3.13.5 does not, so it tested the Python patch release, not the
library.
* A list of readings printed with a tilde keeps it on the catalogue page
Like an interval, a cell holding several readings has no mark of its own for
a tilde, so it goes into the text. The generator now refuses an approximate
hedge only on a printed word, which has nowhere to show it. No published cell
changes.
* A catalogue row refuses a basis outside the five words
The catalogue page and every reader of basis_of act on measured, declared,
calculated, estimated and extended only, so a row built with any other word,
such as a misspelt estimate, now raises CatalogueError instead of reaching
the page as a printed number.
The same argument for the geometry that had two units on one name (#755)
* The same argument for the geometry that had two units on one name
#752 put the unit in the name where a pressure, a temperature or a
humidity could be read two ways. Measured over the imported API, four
geometric quantities were in the same state, and one of them was two
different dimensions.
diameter was metres in twenty public parameters and millimetres in one,
cnossos_rail.wheel_transfer, where CNOSSOS-EU Table G-3b tabulates 920,
840 and 680. angles was degrees in ten and radians in four while angle
was radians in eleven, so the plural and the singular of one word
disagreed. period was a lattice pitch in metres in the slit and
metadiffuser panels and a repetition time in seconds in the synchronous
average. gradient was a road slope in per cent in CNOSSOS-EU and a
vertical sound-speed gradient in s^-1 in refraction and sound speed.
They become diameter_m and diameter_mm, angle_rad, angles_rad and
angles_deg, period_m and period_s, road_slope_percent and gradient_per_s.
periods, a count of repetitions sitting beside a period that is a length,
becomes repetitions; the two places where it really means evaluation
periods keep the word and are in the guard's exemptions with the reason.
GaussianPulse.width is a half-width in seconds and the perfectly matched
layer of the same module measures its own width in cells. It says
half_width_s now.
No value changes: the conformance report is the same 995 checks with the
same numbers. The guard grows a second tuple, matched against the whole
name, and says in its own comment that the seventy-five compound names
are a second pass rather than an exemption: WestonRegimeBoundaries has
critical_angle in radians one screen above a function that takes it in
degrees. Against main the guard reports 71; here none.
* Re-render the three clips the renamed scenes drew
The unit goes in the name, where the caller writes the number (#752)
* The unit goes in the name, where the caller writes the number
A static pressure of 101 325 and a static pressure of 101.325 are both
legitimate values in this tree: the ISO 3740 family prints kilopascals,
IEC 61094-2 and ISO 15186-3 Annex A print pascals. Nothing separates them
by magnitude, because either reading is a real duty somewhere else, and
the result of the mix-up is still a float.
146 public parameters take their unit as a suffix. No value changes:
every function keeps the unit it already documented, so no oracle, no
printed figure and no conformance row moves.
temperature -> temperature_c across 39 callables, with temperature1_c,
temperature2_c, temperatures_c and temperature_celsius folded into the
same spelling; GasStream.inlet_temperature, which IEC 60534-8-3 states in
kelvin, becomes inlet_temperature_k. relative_humidity and the ISO 354
humidity both become relative_humidity_percent. The ambient pressure of
the propagation and aircraft chains becomes atmospheric_pressure_kpa, the
name air_density_astm and air_density_iso10534 already used; the ISO 3740
family takes static_pressure_kpa and the pascals of ISO 15186-3 Annex A,
ISO 9053-2 and ISO 7235 take static_pressure_pa, matching fluids.air.
Every other member of the family takes _pa.
Acoustic pressure keeps its bare name: a waveform is always in pascals
and a Signal carries its own calibration, so a suffix would restate the
type rather than resolve an ambiguity.
scripts/check_parameter_units.py holds the rule from here on. It walks
the imported package, not the source tree, because ReportMetadata is
published from the root and defined in a private module. Against main it
reports 154 offenders; here it reports none.
* Regenerate the API reference and the llms artifacts after the rewrap
* Pin the renamed argument in the four refusals nothing was reading