The five verify_* returned a dictionary, and two of them had a twin that wrapped it (#758)
* The five verify_* return a result object, and the two twins that wrapped one are gone
Every verifier in the library returns one of the 194 result dataclasses
except these five, which returned a bare dict[str, Any]. Two of them had a
public twin whose only job was to run the verifier and package what it gave
back: filters.filter_class_compliance and emission.intensity_class_compliance.
The verifier now returns the object itself, so there is one door onto each
computation instead of two, and the rows a caller reads come back on a name
rather than on a string key.
verify_filter_class returns the FilterComplianceResult and
verify_intensity_class the IntensityInstrumentComplianceResult that their
twins used to build, with .plot() and the accredited .report() fiche on them
as before. The two twins are gone.
The other three gain a result class of their own: WeightingComplianceResult,
QuasiPeakDynamicsResult and AircraftSystemComplianceResult. Each one pins the
summary it states against the rows under it, because the summary is the
conjunction, the minimum or the strictest class of a column a reader prints
beside it, and a sheet whose box disagrees with its own table is the one
reading a caller cannot correct.
Everything after the argument the call is about is keyword-only:
verify_filter_class took num_points positionally where its twin did not, and
verify_quasi_peak_dynamics took the ballistics second.
* Pin the three new verdicts against the rows under them, and publish the weighting result
* Say keyword-only where the table says it, and name the verifier in the pages that pointed at its twin
* Every class the edition defines has to be on the row, and on the sweep
Every verdict reads its pass and fail flags, grades and classes from the values it holds (#949)
A result no longer stores a verdict beside the readings it came from. Sixty-two result classes used to keep the flag, grade or class their function had worked out (`passes`, `complies`, `qualified`, the `*_met` and `*_ok` flags, the advisories, the per-band classes of the IEC 61260-1, IEC 61672-1 and IEC 61043 verdicts, the ISO 9614-2 grade, the ISO/PAS 1996-3 category, the IEC TS 61400-11-2 block status and the rest), so a result built by hand or rewritten with `dataclasses.replace` could hold readings that fail and a flag that says they pass. Each of them is now a read-only property, read from the values the result keeps and the limit the standard prints, and so is any figure that only fed it, such as the rounded level of an RD 1367/2007 period or the acceptance region of a trend test.
The filter bank returns a result, not a tuple whose length depends on a keyword (#810)
* The filter bank returns a result, not a tuple whose length depends on a keyword
octave_filter() and OctaveFilterBank.filter() returned two items normally and
three when sigbands=True asked for the band waveforms. That is why the pair
carried twelve @overload declarations to be typable at all, and why
`_, _, bands = bank.filter(...)` was a line a reader had to count commas in.
Both return an OctaveFilterResult now, with levels, frequencies and bands, the
way every other computation in this library already did. require_levels() and
require_bands() return the value or raise naming the argument that was missing,
which beats the None a caller would otherwise meet several lines later. Like
every other result it draws itself, and the band spectrum is in the guide.
* Every snippet that mentions the filter bank runs against the new return
Four the snippet gate caught. The rewriter that converted the guides appended
the accessor to the end of the line rather than the end of the call, so a
trailing comment swallowed it and the variable kept the whole result;
README_PYPI.md is a separate file neither rewriter walked; the snippet that
demonstrates the missing-bands error raised it for real, and now catches it and
prints the message; and the header snippet of llms.txt is written by hand
inside generate_llms.py, where no documentation sweep would ever find it.
* The nominal labels carry the decimal comma, and the stale clip is redrawn
The renderer sets its own tick labels for a nominal spectrum, and
set_xticklabels installs fixed strings that localize_axes never reaches, so a
Spanish nominal plot showed 31.5 where the rest of the corpus writes 31,5. That
was the one way round the decimal-point gate, which reads the generated SVGs.
The bands docstring conflated the type that is preserved with the units it is
in: an uncalibrated Signal comes back as a Signal carrying None, a calibrated
one as a Signal carrying 1.0, and a dBFS bank hands back plain arrays.
One test indexed the result rather than unpacking it, which the AST sweep did
not see because it only looked at tuple targets; a second sweep for subscripts
found no others anywhere in the tree. anim_modulation_transfer is redrawn
because the code that draws it changed.
* The per-channel legend entry is a named constant, shared by the two renderers that use it
* The stateful block-processing snippets read bands through the accessor too
The five verify_* returned a dictionary, and two of them had a twin that wrapped it (#758)
* The five verify_* return a result object, and the two twins that wrapped one are gone
Every verifier in the library returns one of the 194 result dataclasses
except these five, which returned a bare dict[str, Any]. Two of them had a
public twin whose only job was to run the verifier and package what it gave
back: filters.filter_class_compliance and emission.intensity_class_compliance.
The verifier now returns the object itself, so there is one door onto each
computation instead of two, and the rows a caller reads come back on a name
rather than on a string key.
verify_filter_class returns the FilterComplianceResult and
verify_intensity_class the IntensityInstrumentComplianceResult that their
twins used to build, with .plot() and the accredited .report() fiche on them
as before. The two twins are gone.
The other three gain a result class of their own: WeightingComplianceResult,
QuasiPeakDynamicsResult and AircraftSystemComplianceResult. Each one pins the
summary it states against the rows under it, because the summary is the
conjunction, the minimum or the strictest class of a column a reader prints
beside it, and a sheet whose box disagrees with its own table is the one
reading a caller cannot correct.
Everything after the argument the call is about is keyword-only:
verify_filter_class took num_points positionally where its twin did not, and
verify_quasi_peak_dynamics took the ballistics second.
* Pin the three new verdicts against the rows under them, and publish the weighting result
* Say keyword-only where the table says it, and name the verifier in the pages that pointed at its twin
* Every class the edition defines has to be on the row, and on the sweep
Fit each weighting at its own sample rate instead of oversampling around it (#646)
* Fit each weighting at its own sample rate instead of oversampling around it
The frequency weightings were realised by bilinear-transforming the printed
prototype at 3 to 8 times the sample rate and resampling around the result. The
transform's frequency compression is what forced the oversampling, and the
resampling then cost more than the compression it was bought to avoid: its
anti-alias filter sits with its cutoff on the input Nyquist, the signal crosses
it twice, and that is a fixed 1.65 dB at 20 kHz for a 44.1 kHz input however
high the factor goes.
Fitting the prototype at the sample rate removes both. Worst error over the
corpus goes from 18.4 dB to 0.060 dB, and A-weighting at 44.1 kHz from 2.24 dB
out at 20 kHz to 0.002. It also runs 25 to 50 times faster with an eighth of
the memory, because there is no longer an oversampled intermediate: the whole
path is one cascade of second-order sections and nothing else.
The deliverable is the design routine, not coefficients. Every parameter is a
frequency, each factor is Routh-Hurwitz stable by construction so no step can
leave the unit disc, and the residual is in decibels so the Jacobian stays
bounded. The result is bit-identical across processes and BLAS thread counts,
and a test re-derives it rather than pinning an array.
Three published tolerances that used to fail now pass, one of which nothing in
the tree documented: AU at 32 kHz was 13.3 dB outside IEC 61012's only mask and
verified to no class at all. A and C now earn IEC 61672-1 class 1 at every rate
the library supports, where they fell to class 2 at 32 kHz and to no class at
16 kHz.
Also here, because they arrived with it:
The conformance harness emits docs/conformance.json, and the report, the site
page, the Spanish twin, the quoted counts and the PR comment all derive from
it. They used to derive from the rendered markdown: three separate regexes
parsed the report's English headline as a wire format, each telling the reader
to go and update the other two. The published "369 standards" counted citation
strings rather than documents, so one book cited at two equations counted
twice; the artefact separates 114 normative designations from 84 further
sources. Outcome.passed was a numpy.bool_ on nine checks, which no consumer
noticed because the only use was an if.
IEC 651:1979 Table V brings the Type 0 laboratory class to the weightings, the
way IEC 61260:1995 brings class 0 to the band filters. It is worth having for a
reason beyond completeness: class 1 is +2.5/-16 dB at 16 kHz and +3/-inf at
20 kHz, so it cannot see an error at the top of the band, which is exactly
where the old design was wrong. Type 0 is +2/-3 at both. A and C pass it using
seven per cent of the budget.
* Take the seven review findings, and refuse the one that would open a hole
Six are what they look like. The pair matcher closed over its loop variable,
which is a hazard even where the closure is consumed before the next iteration,
so the distances are materialised and the lambda goes; four raises blocks
carried their fixture construction inside, so a failure there would have
satisfied a test about something else; and the determinism check on the
markdown renderer compared one expression against itself, which does call
twice and does test what it claims, but reads as a tautology.
The seventh is refused. The band guard is written `not low < high` and the
suggestion is `low >= high`. Those differ on NaN, which the negated comparison
refuses and the inequality admits, and a band of NaN reaching the fit is how a
silent wrong answer starts. It is spelled out with isfinite instead, which is
what the guard meant all along.
Every verdict reads its pass and fail flags, grades and classes from the values it holds (#949)
A result no longer stores a verdict beside the readings it came from. Sixty-two result classes used to keep the flag, grade or class their function had worked out (`passes`, `complies`, `qualified`, the `*_met` and `*_ok` flags, the advisories, the per-band classes of the IEC 61260-1, IEC 61672-1 and IEC 61043 verdicts, the ISO 9614-2 grade, the ISO/PAS 1996-3 category, the IEC TS 61400-11-2 block status and the rest), so a result built by hand or rewritten with `dataclasses.replace` could hold readings that fail and a flag that says they pass. Each of them is now a read-only property, read from the values the result keeps and the limit the standard prints, and so is any figure that only fed it, such as the rounded level of an RD 1367/2007 period or the acceptance region of a trend test.
The five verify_* returned a dictionary, and two of them had a twin that wrapped it (#758)
* The five verify_* return a result object, and the two twins that wrapped one are gone
Every verifier in the library returns one of the 194 result dataclasses
except these five, which returned a bare dict[str, Any]. Two of them had a
public twin whose only job was to run the verifier and package what it gave
back: filters.filter_class_compliance and emission.intensity_class_compliance.
The verifier now returns the object itself, so there is one door onto each
computation instead of two, and the rows a caller reads come back on a name
rather than on a string key.
verify_filter_class returns the FilterComplianceResult and
verify_intensity_class the IntensityInstrumentComplianceResult that their
twins used to build, with .plot() and the accredited .report() fiche on them
as before. The two twins are gone.
The other three gain a result class of their own: WeightingComplianceResult,
QuasiPeakDynamicsResult and AircraftSystemComplianceResult. Each one pins the
summary it states against the rows under it, because the summary is the
conjunction, the minimum or the strictest class of a column a reader prints
beside it, and a sheet whose box disagrees with its own table is the one
reading a caller cannot correct.
Everything after the argument the call is about is keyword-only:
verify_filter_class took num_points positionally where its twin did not, and
verify_quasi_peak_dynamics took the ballistics second.
* Pin the three new verdicts against the rows under them, and publish the weighting result
* Say keyword-only where the table says it, and name the verifier in the pages that pointed at its twin
* Every class the edition defines has to be on the row, and on the sweep
Say which error each test is waiting for (#610)
* Say which error each test is waiting for
`PT` is selected. The 241 `pytest.raises(ValueError)` calls that named no
message now name one, and the fragment they name is the part that does not
move: the parameter the guard is about, not a bound, not the list of allowed
values, not wording that belongs to numpy.
Reading the guards turned up two `match=` that already existed and pinned
nothing, neither of them reported by the linter because both do pass a
`match=`: one waited on `frequencies`, a word three messages in its module
share, and one waited on `must be positive`, which every positivity guard in
its module shares.
One test could only pin numpy's shape-mismatch text because
`reception_plate_power` broadcast two of its arguments without checking them
first. It has its own guard now, through a new shared helper, so the caller is
told which argument was the wrong length.
Alongside it, 44 `parametrize` decorators take the shape the other 467 use,
and ten `assert a is not None and b is not None` became two assertions.
* Regenerate the reference page for the guard that now names its argument
* Refuse the one-element array the band axis would have stretched
A scalar means the same value in every band and a band-long array means one
value per band. Broadcasting alone also accepted a one-element array and
repeated it across every band, which is the shape a caller lands on after
computing one value where the bands needed several, and four copies of one
measurement read exactly like a measurement of four bands.
The changelog's counts were measured before earlier work moved them: the
suite holds 2075 `pytest.raises`, of which 1795 already named a message. The
39 that still name none are the classes that are their own assertion.
Fit each weighting at its own sample rate instead of oversampling around it (#646)
* Fit each weighting at its own sample rate instead of oversampling around it
The frequency weightings were realised by bilinear-transforming the printed
prototype at 3 to 8 times the sample rate and resampling around the result. The
transform's frequency compression is what forced the oversampling, and the
resampling then cost more than the compression it was bought to avoid: its
anti-alias filter sits with its cutoff on the input Nyquist, the signal crosses
it twice, and that is a fixed 1.65 dB at 20 kHz for a 44.1 kHz input however
high the factor goes.
Fitting the prototype at the sample rate removes both. Worst error over the
corpus goes from 18.4 dB to 0.060 dB, and A-weighting at 44.1 kHz from 2.24 dB
out at 20 kHz to 0.002. It also runs 25 to 50 times faster with an eighth of
the memory, because there is no longer an oversampled intermediate: the whole
path is one cascade of second-order sections and nothing else.
The deliverable is the design routine, not coefficients. Every parameter is a
frequency, each factor is Routh-Hurwitz stable by construction so no step can
leave the unit disc, and the residual is in decibels so the Jacobian stays
bounded. The result is bit-identical across processes and BLAS thread counts,
and a test re-derives it rather than pinning an array.
Three published tolerances that used to fail now pass, one of which nothing in
the tree documented: AU at 32 kHz was 13.3 dB outside IEC 61012's only mask and
verified to no class at all. A and C now earn IEC 61672-1 class 1 at every rate
the library supports, where they fell to class 2 at 32 kHz and to no class at
16 kHz.
Also here, because they arrived with it:
The conformance harness emits docs/conformance.json, and the report, the site
page, the Spanish twin, the quoted counts and the PR comment all derive from
it. They used to derive from the rendered markdown: three separate regexes
parsed the report's English headline as a wire format, each telling the reader
to go and update the other two. The published "369 standards" counted citation
strings rather than documents, so one book cited at two equations counted
twice; the artefact separates 114 normative designations from 84 further
sources. Outcome.passed was a numpy.bool_ on nine checks, which no consumer
noticed because the only use was an if.
IEC 651:1979 Table V brings the Type 0 laboratory class to the weightings, the
way IEC 61260:1995 brings class 0 to the band filters. It is worth having for a
reason beyond completeness: class 1 is +2.5/-16 dB at 16 kHz and +3/-inf at
20 kHz, so it cannot see an error at the top of the band, which is exactly
where the old design was wrong. Type 0 is +2/-3 at both. A and C pass it using
seven per cent of the budget.
* Take the seven review findings, and refuse the one that would open a hole
Six are what they look like. The pair matcher closed over its loop variable,
which is a hazard even where the closure is consumed before the next iteration,
so the distances are materialised and the lambda goes; four raises blocks
carried their fixture construction inside, so a failure there would have
satisfied a test about something else; and the determinism check on the
markdown renderer compared one expression against itself, which does call
twice and does test what it claims, but reads as a tautology.
The seventh is refused. The band guard is written `not low < high` and the
suggestion is `low >= high`. Those differ on NaN, which the negated comparison
refuses and the inequality admits, and a band of NaN reaching the fit is how a
silent wrong answer starts. It is spelled out with isfinite instead, which is
what the guard meant all along.
Fit each weighting at its own sample rate instead of oversampling around it (#646)
* Fit each weighting at its own sample rate instead of oversampling around it
The frequency weightings were realised by bilinear-transforming the printed
prototype at 3 to 8 times the sample rate and resampling around the result. The
transform's frequency compression is what forced the oversampling, and the
resampling then cost more than the compression it was bought to avoid: its
anti-alias filter sits with its cutoff on the input Nyquist, the signal crosses
it twice, and that is a fixed 1.65 dB at 20 kHz for a 44.1 kHz input however
high the factor goes.
Fitting the prototype at the sample rate removes both. Worst error over the
corpus goes from 18.4 dB to 0.060 dB, and A-weighting at 44.1 kHz from 2.24 dB
out at 20 kHz to 0.002. It also runs 25 to 50 times faster with an eighth of
the memory, because there is no longer an oversampled intermediate: the whole
path is one cascade of second-order sections and nothing else.
The deliverable is the design routine, not coefficients. Every parameter is a
frequency, each factor is Routh-Hurwitz stable by construction so no step can
leave the unit disc, and the residual is in decibels so the Jacobian stays
bounded. The result is bit-identical across processes and BLAS thread counts,
and a test re-derives it rather than pinning an array.
Three published tolerances that used to fail now pass, one of which nothing in
the tree documented: AU at 32 kHz was 13.3 dB outside IEC 61012's only mask and
verified to no class at all. A and C now earn IEC 61672-1 class 1 at every rate
the library supports, where they fell to class 2 at 32 kHz and to no class at
16 kHz.
Also here, because they arrived with it:
The conformance harness emits docs/conformance.json, and the report, the site
page, the Spanish twin, the quoted counts and the PR comment all derive from
it. They used to derive from the rendered markdown: three separate regexes
parsed the report's English headline as a wire format, each telling the reader
to go and update the other two. The published "369 standards" counted citation
strings rather than documents, so one book cited at two equations counted
twice; the artefact separates 114 normative designations from 84 further
sources. Outcome.passed was a numpy.bool_ on nine checks, which no consumer
noticed because the only use was an if.
IEC 651:1979 Table V brings the Type 0 laboratory class to the weightings, the
way IEC 61260:1995 brings class 0 to the band filters. It is worth having for a
reason beyond completeness: class 1 is +2.5/-16 dB at 16 kHz and +3/-inf at
20 kHz, so it cannot see an error at the top of the band, which is exactly
where the old design was wrong. Type 0 is +2/-3 at both. A and C pass it using
seven per cent of the budget.
* Take the seven review findings, and refuse the one that would open a hole
Six are what they look like. The pair matcher closed over its loop variable,
which is a hazard even where the closure is consumed before the next iteration,
so the distances are materialised and the lambda goes; four raises blocks
carried their fixture construction inside, so a failure there would have
satisfied a test about something else; and the determinism check on the
markdown renderer compared one expression against itself, which does call
twice and does test what it claims, but reads as a tautology.
The seventh is refused. The band guard is written `not low < high` and the
suggestion is `low >= high`. Those differ on NaN, which the negated comparison
refuses and the inequality admits, and a band of NaN reaching the fit is how a
silent wrong answer starts. It is spelled out with isfinite instead, which is
what the guard meant all along.
The filter bank returns a result, not a tuple whose length depends on a keyword (#810)
* The filter bank returns a result, not a tuple whose length depends on a keyword
octave_filter() and OctaveFilterBank.filter() returned two items normally and
three when sigbands=True asked for the band waveforms. That is why the pair
carried twelve @overload declarations to be typable at all, and why
`_, _, bands = bank.filter(...)` was a line a reader had to count commas in.
Both return an OctaveFilterResult now, with levels, frequencies and bands, the
way every other computation in this library already did. require_levels() and
require_bands() return the value or raise naming the argument that was missing,
which beats the None a caller would otherwise meet several lines later. Like
every other result it draws itself, and the band spectrum is in the guide.
* Every snippet that mentions the filter bank runs against the new return
Four the snippet gate caught. The rewriter that converted the guides appended
the accessor to the end of the line rather than the end of the call, so a
trailing comment swallowed it and the variable kept the whole result;
README_PYPI.md is a separate file neither rewriter walked; the snippet that
demonstrates the missing-bands error raised it for real, and now catches it and
prints the message; and the header snippet of llms.txt is written by hand
inside generate_llms.py, where no documentation sweep would ever find it.
* The nominal labels carry the decimal comma, and the stale clip is redrawn
The renderer sets its own tick labels for a nominal spectrum, and
set_xticklabels installs fixed strings that localize_axes never reaches, so a
Spanish nominal plot showed 31.5 where the rest of the corpus writes 31,5. That
was the one way round the decimal-point gate, which reads the generated SVGs.
The bands docstring conflated the type that is preserved with the units it is
in: an uncalibrated Signal comes back as a Signal carrying None, a calibrated
one as a Signal carrying 1.0, and a dBFS bank hands back plain arrays.
One test indexed the result rather than unpacking it, which the AST sweep did
not see because it only looked at tuple targets; a second sweep for subscripts
found no others anywhere in the tree. anim_modulation_transfer is redrawn
because the code that draws it changed.
* The per-channel legend entry is a named constant, shared by the two renderers that use it
* The stateful block-processing snippets read bands through the accessor too
Band averages for every part of ISO 10846, the driving-point method of ISO 10846-5 and the rig checks of ISO 10846-4 (#876)
* A nominal band frequency below the preferred table is the printed number
The decades below the IEC 61260-1 base table were built by scaling with
0.1, which binary cannot hold, so the 3.15 Hz one-third-octave band came
back as 3.1500000000000004 and the 6.3 Hz band as 6.300000000000001.
Dividing by the power of ten gives the double nearest the printed value.
* Band averages, rig checks and the driving-point method of ISO 10846-4 and -5
band_averaged_stiffness takes the energy mean of the narrow-band stiffness
over each base-ten one-third-octave band, as every part of ISO 10846 asks
(Part 2 Formula (6), Part 3 (7), Part 4 (11), Part 5 (6)); a band holding
fewer than five lines gets no value and a TransferStiffnessWarning, and lines
that fail an adequacy condition are left out. TransferStiffnessResult gains a
per-line validity mask and band_average(); the indirect result marks the
lines with |T| > 0.1, and the indirect method warns with the new class.
check_blocked_output, check_unwanted_input and check_output_mass judge the
20 dB, the 15 dB and the output-mass limit of Part 4 Inequality (3) line by
line and warn where they fail. effective_blocking_mass is Part 4 Formula (6)
with the f3 of Inequality (5), a departure below 40 Hz ignored.
driving_point_stiffness turns the input force and acceleration into k1,1
(Part 5 Formula (3)), finds the upper limiting frequency of 6.2 and averages
only the lines below it, where Formula (7) makes k1,1 stand for k2,1 within
2 dB. driving_point_uncertainty builds the Annex B budget on
metrology.combine_uncertainty with U = 2u, keeping the rectangular terms at
the expressions B.3 prints. Every result has .plot().
* Conformance rows for ISO 10846-4 and ISO 10846-5
Neither part prints a worked example, so the thirteen rows are closed
forms: a band of identical lines averages to itself, lines of 1 to 5 MN/m
to the square root of 11 MN/m, and a band of four lines has no value; a
massless spring gives a flat k1,1 that never reaches f_UL, and a spring
under a 2 kg force plate gives the f_UL of its closed form with every band
below it within the 2 dB of Formula (7); the output mass on the bound of
Part 4 Inequality (3) is 0.6 kg for a 20 dB level difference and biases
the force by the 0.5 dB of NOTE 1; a rigid block has an effective mass
equal to its own, and a block with a spring mode the f3 of its closed
form; Inequalities (1) and (2) hold at equality; and the Annex B budget
at its defaults and at the one-decimal values of Table B.1.
* Document ISO 10846-4 and -5 on the transfer-stiffness guide
The guide gains three sections in both languages: the one-third-octave
band average every part asks for, the checks of the rig read from the
measured spectra (the blocked output, the unidirectional input, the mass
in front of the output force transducers and the effective blocking mass
with its f3), and the driving-point method of Part 5 with its upper
limiting frequency, a figure of k1,1 under a 2 kg force plate and the
Annex B budget. The scope and the section overviews now say what is
computed; only the clause 7.6 linearity criterion is still left to the
operator.
Two printed defects go in the errata: ISO 10846-4:2003 6.2 NOTE 1 prints
the 0,5 dB bias of Inequality (3) as "05 dB", and ISO 10846-5:2008 B.3.6
and Table B.1 round the linearity term 1,5/(2 sqrt 3) = 0,433 dB to
0,5 dB, as ISO 10846-2 does for its own terms.
* The ISO 10846 fiche prints the band levels, and a line on f_UL counts
The transfer-stiffness fiche now carries the one-third-octave band levels
the test report of ISO 10846-2 and -3 presents, prints the line count of a
band short of five valid lines, and reads its low-frequency headline at the
lowest valid line, so an indirect result no longer boxes the resonance
region Inequality (2) rules out. The L_k(f) plot draws the excluded lines
apart.
DrivingPointStiffnessResult.valid keeps a line that sits exactly on the
2 dB threshold, since ISO 10846-5 8.3 states the 2 dB for f <= f_UL. The
band-average and Annex B docstrings attribute the 2 dB to 8.3 and B.3.5 and
describe Table B.1 as the expressions rounded up to one decimal.
The resilient-element figures name their band axis in Spanish, keep the
legend clear of the markers, say so when no band holds five lines, label
the level-difference curve as a difference rather than a verdict, and fit
a figure they draw themselves to its canvas.
* Conformance rows for ISO 10846 cite the linearity test per part and state the NOTE 1 bias exactly
The linearity row cites clause 7.7 of ISO 10846-2 and 7.6 of ISO 10846-3,
the two numbers the parts print. The ISO 10846-4 NOTE 1 row states the
worst-case force-level bias on the Inequality (3) bound, 0,537 dB, as the
0,5 dB the note prints to one decimal. The Formula (7) row covers the bands
at or below f_UL, and the Table B.1 row names its inputs as the B.3
expressions rounded up to one decimal.
* The resilient-element guide, errata and figures say what the standards print
The ISO 10846-2:2008 clause 7.6.1 cross-reference, which sends the
unidirectionality pre-run to the blocked-output inequality of 6.1 instead of
the unwanted-input one of 6.4, is registered in the errata in both
languages. The Annex B entry of ISO 10846-5 is withdrawn: Table B.1 rounds
the three rectangular terms up to one decimal, the conservative rounding an
uncertainty may take, and the guide now says so for Parts 2 and 5.
The guide cites the linearity test per part (7.7 of Parts 2, 4 and 5, 7.6 of
Part 3), puts the preload, creep and temperature conditioning back among
what it leaves to the operator, says that only the unidirectionality
pre-run is judged from the spectra, attributes the 2 dB of Formula (7) to
8.3 and B.3.5, names the input-side mass a force distribution plate with
its own symbol, gives the |T| = 0,1 crossing of the example element as
187,5 Hz, and reproduces the driving-point figure by hand. The fiche section
and example describe the band levels it now prints.
* The fiche keeps a wide sweep on one page, and the guides compare stiffness levels and judge the transmissibility line by line
* The generated pages follow the layers below
* The labels the ISO 10846-4 and -5 plots share are named once
The filter bank returns a result, not a tuple whose length depends on a keyword (#810)
* The filter bank returns a result, not a tuple whose length depends on a keyword
octave_filter() and OctaveFilterBank.filter() returned two items normally and
three when sigbands=True asked for the band waveforms. That is why the pair
carried twelve @overload declarations to be typable at all, and why
`_, _, bands = bank.filter(...)` was a line a reader had to count commas in.
Both return an OctaveFilterResult now, with levels, frequencies and bands, the
way every other computation in this library already did. require_levels() and
require_bands() return the value or raise naming the argument that was missing,
which beats the None a caller would otherwise meet several lines later. Like
every other result it draws itself, and the band spectrum is in the guide.
* Every snippet that mentions the filter bank runs against the new return
Four the snippet gate caught. The rewriter that converted the guides appended
the accessor to the end of the line rather than the end of the call, so a
trailing comment swallowed it and the variable kept the whole result;
README_PYPI.md is a separate file neither rewriter walked; the snippet that
demonstrates the missing-bands error raised it for real, and now catches it and
prints the message; and the header snippet of llms.txt is written by hand
inside generate_llms.py, where no documentation sweep would ever find it.
* The nominal labels carry the decimal comma, and the stale clip is redrawn
The renderer sets its own tick labels for a nominal spectrum, and
set_xticklabels installs fixed strings that localize_axes never reaches, so a
Spanish nominal plot showed 31.5 where the rest of the corpus writes 31,5. That
was the one way round the decimal-point gate, which reads the generated SVGs.
The bands docstring conflated the type that is preserved with the units it is
in: an uncalibrated Signal comes back as a Signal carrying None, a calibrated
one as a Signal carrying 1.0, and a dBFS bank hands back plain arrays.
One test indexed the result rather than unpacking it, which the AST sweep did
not see because it only looked at tuple targets; a second sweep for subscripts
found no others anywhere in the tree. anim_modulation_transfer is redrawn
because the code that draws it changed.
* The per-channel legend entry is a named constant, shared by the two renderers that use it
* The stateful block-processing snippets read bands through the accessor too
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.
Every verdict reads its pass and fail flags, grades and classes from the values it holds (#949)
A result no longer stores a verdict beside the readings it came from. Sixty-two result classes used to keep the flag, grade or class their function had worked out (`passes`, `complies`, `qualified`, the `*_met` and `*_ok` flags, the advisories, the per-band classes of the IEC 61260-1, IEC 61672-1 and IEC 61043 verdicts, the ISO 9614-2 grade, the ISO/PAS 1996-3 category, the IEC TS 61400-11-2 block status and the rest), so a result built by hand or rewritten with `dataclasses.replace` could hold readings that fail and a flag that says they pass. Each of them is now a read-only property, read from the values the result keeps and the limit the standard prints, and so is any figure that only fed it, such as the rounded level of an RD 1367/2007 period or the acceptance region of a trend test.
The filter bank returns a result, not a tuple whose length depends on a keyword (#810)
* The filter bank returns a result, not a tuple whose length depends on a keyword
octave_filter() and OctaveFilterBank.filter() returned two items normally and
three when sigbands=True asked for the band waveforms. That is why the pair
carried twelve @overload declarations to be typable at all, and why
`_, _, bands = bank.filter(...)` was a line a reader had to count commas in.
Both return an OctaveFilterResult now, with levels, frequencies and bands, the
way every other computation in this library already did. require_levels() and
require_bands() return the value or raise naming the argument that was missing,
which beats the None a caller would otherwise meet several lines later. Like
every other result it draws itself, and the band spectrum is in the guide.
* Every snippet that mentions the filter bank runs against the new return
Four the snippet gate caught. The rewriter that converted the guides appended
the accessor to the end of the line rather than the end of the call, so a
trailing comment swallowed it and the variable kept the whole result;
README_PYPI.md is a separate file neither rewriter walked; the snippet that
demonstrates the missing-bands error raised it for real, and now catches it and
prints the message; and the header snippet of llms.txt is written by hand
inside generate_llms.py, where no documentation sweep would ever find it.
* The nominal labels carry the decimal comma, and the stale clip is redrawn
The renderer sets its own tick labels for a nominal spectrum, and
set_xticklabels installs fixed strings that localize_axes never reaches, so a
Spanish nominal plot showed 31.5 where the rest of the corpus writes 31,5. That
was the one way round the decimal-point gate, which reads the generated SVGs.
The bands docstring conflated the type that is preserved with the units it is
in: an uncalibrated Signal comes back as a Signal carrying None, a calibrated
one as a Signal carrying 1.0, and a dBFS bank hands back plain arrays.
One test indexed the result rather than unpacking it, which the AST sweep did
not see because it only looked at tuple targets; a second sweep for subscripts
found no others anywhere in the tree. anim_modulation_transfer is redrawn
because the code that draws it changed.
* The per-channel legend entry is a named constant, shared by the two renderers that use it
* The stateful block-processing snippets read bands through the accessor too
phonometry installs on Python 3.12 and on the numpy, scipy and matplotlib it really needs, and a CI job keeps those floors honest (#956)
phonometry now installs on Python 3.12, 3.13 and 3.14, and the version floors in the package metadata are the oldest releases the library's tests pass with on Python 3.12, each measured with every other floor installed beside it: NumPy 2.0.2, SciPy 1.14.0, matplotlib 3.10.7, reportlab 4.0.4, svglib 0.9.0, numba 0.60.0 and python-soundfile 0.12.0. A new minimum-versions job installs exactly those floors and runs the library's tests, Dependabot moves the pins and leaves the floors, and a one-element array passed where one number is asked for is now refused with a ValueError that names the parameter on every NumPy.
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 filter bank returns a result, not a tuple whose length depends on a keyword (#810)
* The filter bank returns a result, not a tuple whose length depends on a keyword
octave_filter() and OctaveFilterBank.filter() returned two items normally and
three when sigbands=True asked for the band waveforms. That is why the pair
carried twelve @overload declarations to be typable at all, and why
`_, _, bands = bank.filter(...)` was a line a reader had to count commas in.
Both return an OctaveFilterResult now, with levels, frequencies and bands, the
way every other computation in this library already did. require_levels() and
require_bands() return the value or raise naming the argument that was missing,
which beats the None a caller would otherwise meet several lines later. Like
every other result it draws itself, and the band spectrum is in the guide.
* Every snippet that mentions the filter bank runs against the new return
Four the snippet gate caught. The rewriter that converted the guides appended
the accessor to the end of the line rather than the end of the call, so a
trailing comment swallowed it and the variable kept the whole result;
README_PYPI.md is a separate file neither rewriter walked; the snippet that
demonstrates the missing-bands error raised it for real, and now catches it and
prints the message; and the header snippet of llms.txt is written by hand
inside generate_llms.py, where no documentation sweep would ever find it.
* The nominal labels carry the decimal comma, and the stale clip is redrawn
The renderer sets its own tick labels for a nominal spectrum, and
set_xticklabels installs fixed strings that localize_axes never reaches, so a
Spanish nominal plot showed 31.5 where the rest of the corpus writes 31,5. That
was the one way round the decimal-point gate, which reads the generated SVGs.
The bands docstring conflated the type that is preserved with the units it is
in: an uncalibrated Signal comes back as a Signal carrying None, a calibrated
one as a Signal carrying 1.0, and a dBFS bank hands back plain arrays.
One test indexed the result rather than unpacking it, which the AST sweep did
not see because it only looked at tuple targets; a second sweep for subscripts
found no others anywhere in the tree. anim_modulation_transfer is redrawn
because the code that draws it changed.
* The per-channel legend entry is a named constant, shared by the two renderers that use it
* The stateful block-processing snippets read bands through the accessor too
Fit each weighting at its own sample rate instead of oversampling around it (#646)
* Fit each weighting at its own sample rate instead of oversampling around it
The frequency weightings were realised by bilinear-transforming the printed
prototype at 3 to 8 times the sample rate and resampling around the result. The
transform's frequency compression is what forced the oversampling, and the
resampling then cost more than the compression it was bought to avoid: its
anti-alias filter sits with its cutoff on the input Nyquist, the signal crosses
it twice, and that is a fixed 1.65 dB at 20 kHz for a 44.1 kHz input however
high the factor goes.
Fitting the prototype at the sample rate removes both. Worst error over the
corpus goes from 18.4 dB to 0.060 dB, and A-weighting at 44.1 kHz from 2.24 dB
out at 20 kHz to 0.002. It also runs 25 to 50 times faster with an eighth of
the memory, because there is no longer an oversampled intermediate: the whole
path is one cascade of second-order sections and nothing else.
The deliverable is the design routine, not coefficients. Every parameter is a
frequency, each factor is Routh-Hurwitz stable by construction so no step can
leave the unit disc, and the residual is in decibels so the Jacobian stays
bounded. The result is bit-identical across processes and BLAS thread counts,
and a test re-derives it rather than pinning an array.
Three published tolerances that used to fail now pass, one of which nothing in
the tree documented: AU at 32 kHz was 13.3 dB outside IEC 61012's only mask and
verified to no class at all. A and C now earn IEC 61672-1 class 1 at every rate
the library supports, where they fell to class 2 at 32 kHz and to no class at
16 kHz.
Also here, because they arrived with it:
The conformance harness emits docs/conformance.json, and the report, the site
page, the Spanish twin, the quoted counts and the PR comment all derive from
it. They used to derive from the rendered markdown: three separate regexes
parsed the report's English headline as a wire format, each telling the reader
to go and update the other two. The published "369 standards" counted citation
strings rather than documents, so one book cited at two equations counted
twice; the artefact separates 114 normative designations from 84 further
sources. Outcome.passed was a numpy.bool_ on nine checks, which no consumer
noticed because the only use was an if.
IEC 651:1979 Table V brings the Type 0 laboratory class to the weightings, the
way IEC 61260:1995 brings class 0 to the band filters. It is worth having for a
reason beyond completeness: class 1 is +2.5/-16 dB at 16 kHz and +3/-inf at
20 kHz, so it cannot see an error at the top of the band, which is exactly
where the old design was wrong. Type 0 is +2/-3 at both. A and C pass it using
seven per cent of the budget.
* Take the seven review findings, and refuse the one that would open a hole
Six are what they look like. The pair matcher closed over its loop variable,
which is a hazard even where the closure is consumed before the next iteration,
so the distances are materialised and the lambda goes; four raises blocks
carried their fixture construction inside, so a failure there would have
satisfied a test about something else; and the determinism check on the
markdown renderer compared one expression against itself, which does call
twice and does test what it claims, but reads as a tautology.
The seventh is refused. The band guard is written `not low < high` and the
suggestion is `low >= high`. Those differ on NaN, which the negated comparison
refuses and the inequality admits, and a band of NaN reaching the fit is how a
silent wrong answer starts. It is spelled out with isfinite instead, which is
what the guard meant all along.
Band filters are graded on the effective bandwidth, the summation of outputs and the swept test of IEC 61260-1:2014, and a laboratory's IEC 61260-3 periodic results get their verdict (#879)
* Band filters are graded on every requirement IEC 61260-1 sets on their response, and periodic test results get their IEC 61260-3 verdict
verify_filter_class now grades, for the 2014 edition, the effective
bandwidth deviation of 5.12 and the summation of output signals of 5.16
beside the Table 1 mask, computed the way IEC 61260-2:2016 computes them:
the test frequencies of its Formula (1) (points_per_bandwidth, at least
24), the trapezoidal effective bandwidth of Formula (2) and the energy sum
of each band and its two neighbours of Formula (3). A band's class and the
bank's overall_class are the strictest class met on all three, and
requirements, requirement_class, binding_margin_db and
plot(requirement=...) read each one; the fiche tabulates them under its
plot. A bank with no band that has a neighbour on each side is not graded
on the summation at all.
The decimated octave bank sums its adjacent outputs from -1.16 dB to
+0.94 dB about the input and is class 2 where it read class 1; tones run
through the bank itself read the same sums to within 0.01 dB. Designed
with FilterDesign(resample=False) it is class 1, and so is the
one-third-octave bank as designed. The example fiche uses the full-rate
octave bank.
verify_time_invariance runs the exponential sweep of IEC 61260-2 7.4
through the bank, decimation included, at 2 s and 5 s per decade against
Formula (17) of IEC 61260-1, which swept_band_level computes, with the
Annex A uncertainty in swept_level_uncertainty (with the square the printed
Formula (A.2) leaves off).
verify_filter_periodic grades what a laboratory measured in an
IEC 61260-3:2016 periodic test (FilterPeriodicMeasurements), clause by
clause, by the conformance rule of IEC TC 29 with the maxima of
IEC 61260-1:2014 Annex B; it names the results 5.3 makes unusable and
gives the statement of Clause 14, with the caveat of 1.5 when the pattern
approval is not public. periodic_test_frequencies is Formulas (1) and (2)
for any bandwidth and PERIODIC_TEST_ATTENUATION_LIMITS_DB its Table 1.
verify_conformance accepts an acceptance interval open at one end, for a
stop-band minimum.
* The band-filter tests of IEC 61260-2 and IEC 61260-3 are conformance rows
Thirty-six rows: the fifteen test frequencies of IEC 61260-3:2016
Table C.1 and the derivation of C.2, every cell of its Table 1, the worked
examples its Annexes A and B share with IEC 61260-2:2016 (0.057 dB,
0.115 dB and 0.128 dB; 107.97 dB), and the ten examples of the
conformance rule IEC 61260-1:2014 prints in Table C.1. Formulas (2) and (3)
of IEC 61260-2 print no example and are held to closed forms: an ideal band
has no effective bandwidth deviation and an ideal set sums to its input.
The one-third-octave bank is class 1 on the effective bandwidth, the
summation and the swept test, and its swept deviation equals its effective
bandwidth deviation within 0.01 dB, as Annex G of IEC 61260-1 says it
must.
* The filter guides show the effective bandwidth, the summation, the swept test and a periodic verdict, and three defects of the printed pages are registered
Filter Class Verification gains a section on the effective bandwidth and
the summation of outputs, with the figure of Formula (3) on the two
default banks and why the decimated octave bank is class 2 on it; a
section on the swept test of time-invariant operation; and, in the section
on verifying an instrument, how a periodic test of IEC 61260-3 is graded,
with its table of clauses, limits and maximum uncertainties and the
figure of a verdict held back by one unusable reading. Compliance and
Verification no longer says Parts 2 and 3 are out of reach: the design
verifiers compute what Part 2 computes from a response, and
verify_filter_periodic grades a laboratory's Part 3 results without
running the tests. The fiche example is the octave bank designed at the
full rate. English, Spanish and the docs/ mirror say the same, and the
upgrade guide tells a caller of verify_filter_class what moved.
Registered in the errata, with the pages they were read on: Formula (A.2)
of both parts leaves the square off the coefficient of its frequency
terms, which only the squared form reconciles with the 0.057 dB its own
example prints; IEC 61260-3 B.2.6 ends its averaging 6 s to 7 s after
6.3 Hz where the settings of B.2.2 and B.2.3 give 18 s to 19 s; and
Formula (3) and the words of 7.2.4.3 and of IEC 61260-1 5.16 take the
summation difference in opposite directions.
* A periodic verdict checks that the tests cover the filters and frequencies they must, and a filter figure keeps a failing band in view
verify_filter_periodic now reads the Annex B maximum of a 10.2 mid-band result off the attenuation measured, as clause 13 does, so a filter that has drifted past 2 dB at its mid-band fails instead of being set aside as unusable. It lists in `incomplete` a record that measures fewer than the three filters of 11.3 and 13.1, or leaves out a test frequency 13.4 requires, and does not pass it; a new `linearity_midband_frequencies_hz` names the filter of each 11.7 result, and without the frequency labels `coverage_checked` is false and the statement says the coverage was not checked.
A deviation outside its acceptance interval always reads a share of its limit above 1, also below a stop-band minimum. The effective bandwidth, summation and swept-test figures frame their data as well as their limits, the class figure of a band filtered at the full rate stops at the G² breakpoint of Table 1, a clause figure draws a reading 5.3 forbids using as unusable, and a one-sided interval's legend names one limit. The fiche of a full-rate bank no longer credits a multirate stage it does not have. The printed limits of 5.12.2, 5.14.3 and 5.16, the sign Formula (3) is graded with and the inclusive boundaries of Annex B are held by tests.
* The filter guides say what the verifiers grade and how the default banks class, and the two verification diagrams show it
The README and the filter design docstrings no longer call every bank class 1 under IEC 61260-1:2014: the Table 1 mask is class 1, the one-third-octave bank is class 1 on every requirement, and the decimated octave bank is class 2 on the summation of its outputs and class 1 filtered at the full rate. The guide quotes the full-rate octave bank over its default range (from −0.06 dB to +0.69 dB, 0.11 dB to spare) and the decimated Chebyshev II one, the order paragraph of Filter Banks names the summation as what binds past order 4, the periodic example is the record its figure draws, and a paragraph explains the coverage check. The filter, metrology and signals indexes and the guide one-liners name the effective bandwidth, the summation, the swept test and the periodic grading instead of saying they are not implemented.
The class-check diagram moves the Formula (1) grid, 5.12, 5.16 and the sweep into what is computed on the design, and the verification-regimes diagram adds verify_time_invariance and the verify_filter_periodic arrow into Part 3. The errata entries on Formula (A.2) and Formula (3) record that the consolidated IEC 61260-2:2016+AMD1:2017 prints both unchanged.
* The ISO 9295 Formula (A.5) record keeps its status next to the filter records
* The generated pages follow the layers below
* The errata give the unrounded input uncertainty the A.3.5 example derives, the migration guide names the filter verdict among what changed, and the filter plot labels are named once
* The generated pages follow the layers below
Every verdict reads its pass and fail flags, grades and classes from the values it holds (#949)
A result no longer stores a verdict beside the readings it came from. Sixty-two result classes used to keep the flag, grade or class their function had worked out (`passes`, `complies`, `qualified`, the `*_met` and `*_ok` flags, the advisories, the per-band classes of the IEC 61260-1, IEC 61672-1 and IEC 61043 verdicts, the ISO 9614-2 grade, the ISO/PAS 1996-3 category, the IEC TS 61400-11-2 block status and the rest), so a result built by hand or rewritten with `dataclasses.replace` could hold readings that fail and a flag that says they pass. Each of them is now a read-only property, read from the values the result keeps and the limit the standard prints, and so is any figure that only fed it, such as the rounded level of an RD 1367/2007 period or the acceptance region of a trend test.
The five verify_* returned a dictionary, and two of them had a twin that wrapped it (#758)
* The five verify_* return a result object, and the two twins that wrapped one are gone
Every verifier in the library returns one of the 194 result dataclasses
except these five, which returned a bare dict[str, Any]. Two of them had a
public twin whose only job was to run the verifier and package what it gave
back: filters.filter_class_compliance and emission.intensity_class_compliance.
The verifier now returns the object itself, so there is one door onto each
computation instead of two, and the rows a caller reads come back on a name
rather than on a string key.
verify_filter_class returns the FilterComplianceResult and
verify_intensity_class the IntensityInstrumentComplianceResult that their
twins used to build, with .plot() and the accredited .report() fiche on them
as before. The two twins are gone.
The other three gain a result class of their own: WeightingComplianceResult,
QuasiPeakDynamicsResult and AircraftSystemComplianceResult. Each one pins the
summary it states against the rows under it, because the summary is the
conjunction, the minimum or the strictest class of a column a reader prints
beside it, and a sheet whose box disagrees with its own table is the one
reading a caller cannot correct.
Everything after the argument the call is about is keyword-only:
verify_filter_class took num_points positionally where its twin did not, and
verify_quasi_peak_dynamics took the ballistics second.
* Pin the three new verdicts against the rows under them, and publish the weighting result
* Say keyword-only where the table says it, and name the verifier in the pages that pointed at its twin
* Every class the edition defines has to be on the row, and on the sweep