Ir al contenido
Esta documentación describe la versión 4.0.0, todavía sin publicar. La versión actual en PyPI es la 3.3.0 y no incluye todo lo que se describe aquí.

Actualizar de la 3.3 a la 4.0

La 4.0 rompe duro y no lleva capa de compatibilidad. Casi nada se ha borrado: de los 885 nombres que la raíz exportaba en la 3.3.0, diez no existen ya en ningún sitio y el resto simplemente vive en una dirección con nombre en vez de en una plana. Así que el trabajo es sobre todo un barrido de imports, y esta página es sobre todo un mapa.

Con las dos primeras secciones arreglas los imports. La tercera léela aunque tengas prisa, porque en tres sitios el arreglo evidente es el equivocado, y uno de esos tres cambia un número sin levantar nada.

En la 3.3.0 la raíz reexportaba la biblioteca entera, así que from phonometry import laeq funcionaba para casi cualquier nombre. Hoy la raíz exporta veinticinco cosas: los veintiún subpaquetes, las tres clases que no pertenecen a ningún dominio concreto (Signal, ReportMetadata, PhonometryWarning) y __version__.

import phonometry
print(len(phonometry.__all__)) # 25
print("laeq" in phonometry.__all__) # False
from phonometry.signals import laeq # esta es la grafía nueva

Medido sobre la superficie entera, 872 de aquellos 885 nombres de la raíz los publica hoy exactamente un subpaquete, ninguno dos, y ninguno necesita una ruta más profunda que phonometry.<subpaquete>. Otros tres siguen en la raíz, donde siempre estuvieron. Así que el cambio no tiene ambigüedad: busca el subpaquete e importa de él.

La forma de atributo no se retiró nunca, que es el cambio más pequeño posible para el código que tiraba de la raíz:

from phonometry import filters
_, _, _, labels = filters.nominal_frequencies(1)
print(labels)

En vez de buscar cada uno en la referencia, pregúntaselo al paquete instalado:

import importlib
import pkgutil
import phonometry
def whereis(name):
"""Cada módulo público que publica `name`, del menos profundo al que más."""
found = ["phonometry"] if name in phonometry.__all__ else []
for attribute in phonometry.__all__:
package = getattr(phonometry, attribute)
if not hasattr(package, "__path__"):
continue
modules = [package.__name__]
modules += [m.name for m in pkgutil.walk_packages(package.__path__, package.__name__ + ".")]
for module_name in modules:
if any(part.startswith("_") for part in module_name.split(".")):
continue
try:
module = importlib.import_module(module_name)
except ImportError:
continue
if name in getattr(module, "__all__", ()):
found.append(module_name)
return sorted(found, key=lambda path: path.count("."))
print(whereis("laeq")[0]) # phonometry.signals
print(whereis("reverberation_time")[0]) # phonometry.room
print(whereis("sensitivity")[0]) # phonometry.metrology
print(whereis("Signal")[0]) # phonometry

Recorre el árbol en unos dos segundos. Cuando devuelve varias rutas, la menos profunda es el import canónico: un subpaquete reexporta lo que publican sus propios módulos, y esa ruta corta es la que usan las guías y los tests. Los cuatro nombres que la raíz sigue publicando por su cuenta, Signal, PhonometryWarning, ReportMetadata y __version__, vuelven como phonometry a secas. Una lista vacía significa que el nombre está en la última sección de esta página.

Cinco de los treinta módulos que cambiaron de ruta no se mudaron a una dirección nueva. Se dividieron, y quien siga sólo la primera mitad de la partición se lleva un ImportError por el resto. Merece la pena revisarlos uno a uno, y el paquete privado de renderizado va con ellos porque le pasó lo mismo:

Módulo en 3.3.0Dónde está hoy su contenido
phonometry.impedance_tubematerials.absorbers.impedance_tube (función de transferencia de dos micrófonos), materials.absorbers.four_microphone (TransferMatrix, transfer_matrix_one_load, wave_decomposition, face_quantities, air_layer_transfer_matrix), materials.absorbers.standing_wave y fluids
phonometry.scattering_diffusionmaterials.diffusers.reverberation_room_scattering para la parte de cámara reverberante (ScatteringResult, ScatteringUncertainty, BASE_PLATE_BANDS y el resto), materials.diffusers.scattering_diffusion para los nueve nombres de campo libre
phonometry.materials.porous_absorbermaterials.absorbers.porous para los modelos de material en masa, materials.absorbers.layered para toda la API multicapa (layered_absorber, AirLayer, MembraneLayer, PerforatedPlateLayer, MicroperforatedPlateLayer, LayeredAbsorberResult)
phonometry.metrology.spectrasignals.spectra, más signals.multitaper (multitaper_psd) y signals.windows (window_metrics)
phonometry.compliancefilters.compliance (FilterComplianceResult, class_limits, verify_filter_class), filters.weighting_compliance (verify_weighting_class, weighting_class_limits), aircraft.measurement_system (verify_aircraft_noise_system)
phonometry._plottingphonometry._plot.<dominio>: los 82 renderizadores se repartieron por dominio, así que plot_airborne_insulation está en _plot.building y plot_age_threshold en _plot.hearing. Privado en ambos casos, y la vía soportada es el .plot() del resultado

Los otros veinticinco se mudaron enteros, y el resolutor de arriba los encuentra sin leer esta tabla. Cada uno de los tres primeros era alcanzable por dos rutas en la 3.3.0, la plana y la de dentro de su paquete, y las dos han desaparecido.

Tres sitios donde el arreglo evidente es el equivocado

Sección titulada «Tres sitios donde el arreglo evidente es el equivocado»

Todo lo demás de esta página se anuncia en cuanto lo ejecutas, y el arreglo es el que parece. Estos tres no son así.

Los auxiliares del tubo de impedancia tomaban kelvin

Sección titulada «Los auxiliares del tubo de impedancia tomaban kelvin»

air_density_iso y speed_of_sound_iso documentaban y consumían su temperatura en kelvin. Sus sucesores toman grados Celsius, como el resto de la biblioteca. El nombre cambió, así que el import falla y te enteras, pero el problema es el argumento que arrastras: 293,15 es un número de aspecto legítimo en cualquiera de las dos unidades.

from phonometry.materials import speed_of_sound_iso10534
print(round(float(speed_of_sound_iso10534(temperature_c=20.0)), 5)) # 343.28784
print(round(float(speed_of_sound_iso10534(temperature_c=293.15)), 5)) # 477.13003

343,29 m/s frente a 477,13 m/s: un 39 % de error, sin excepción ninguna, y cada coeficiente de absorción calculado a partir de ahí sale mal en una cantidad de aspecto plausible. Convierte el valor, no te limites a renombrar la palabra clave.

3.3.04.0
impedance_tube.air_density_iso(T_kelvin, p_kPa)materials.air_density_iso10534(temperature_c=T_kelvin - 273.15, atmospheric_pressure_kpa=p_kPa)
impedance_tube.speed_of_sound_iso(T_kelvin)materials.speed_of_sound_iso10534(temperature_c=T_kelvin - 273.15)

Con la conversión aplicada, los dos devuelven exactamente lo que devolvían en la 3.3.0, bit a bit.

scattering_diffusion.speed_of_sound tomaba Celsius, y su sustituto responde distinto

Sección titulada «scattering_diffusion.speed_of_sound tomaba Celsius, y su sustituto responde distinto»

El auxiliar de ISO 17497-1 que se llamaba igual tomaba grados Celsius, así que ahí no hay conversión que hacer. La trampa es el sustituto. fluids.air() usa un modelo del medio más completo que aquella fórmula de una línea de la ISO 17497-1, y a 20 °C responde 343,99 m/s donde el auxiliar viejo respondía 343,20 m/s. Es poco, y no es nada despreciable si estás reproduciendo una medición hecha con el número antiguo. El apartado 8 de la ISO 17497-1 imprime la fórmula si necesitas ese apartado exacto.

transmission_loss pasó a propagation_loss, cosa que el import detecta, y con él se renombró el campo de dentro del resultado. Ese no levanta nada: un PropagationLossResult no tiene .tl, así que el AttributeError aparece allí donde lo leas, que puede quedar lejos de la llamada.

from phonometry.underwater.propagation.closed_form import propagation_loss
result = propagation_loss([1000.0], 1000.0)
print(round(float(result.pl[0]), 5)) # 60.06013, el número que guardaba .tl

El mismo renombrado llega a underwater.sonar_equation, cuyo parámetro transmission_loss es ahora propagation_loss.

Cinco puntos de entrada ya no toman las condiciones ambientales como números sueltos. Ahí no basta con renombrar la palabra clave, porque el parámetro ha desaparecido del todo:

3.3.04.0
airport_noise.event_level(..., temperature=15.0, pressure=101.325)event_level(..., atmosphere=AerodromeAtmosphere(temperature_c=15.0, atmospheric_pressure_kpa=101.325))
airport_noise.noise_contour(...)lo mismo, con AerodromeAtmosphere
rotorcraft_noise.rotorcraft_event_level(..., temperature=15.0, relative_humidity=70.0)rotorcraft_event_level(..., atmosphere=RotorcraftAtmosphere(temperature_c=15.0, relative_humidity_percent=70.0))
rotorcraft_noise.rotorcraft_noise_contour(...)lo mismo, con RotorcraftAtmosphere
outdoor_propagation.predicted_receiver_level(..., humidity=70.0)predicted_receiver_level(..., atmosphere=AtmosphericConditions(relative_humidity_percent=70.0))

Las sustituciones punto a punto del rotorcraft fueron por el mismo camino: bank_angle y path_angle son ahora RotorcraftTrackState(bank_angle_deg=..., path_angle_deg=...).

from phonometry.aircraft.airport_noise import AerodromeAtmosphere
atmosphere = AerodromeAtmosphere(temperature_c=15.0, atmospheric_pressure_kpa=101.325)
print(atmosphere.temperature_c, atmosphere.atmospheric_pressure_kpa)

Un parámetro que nombra una magnitud lleva su unidad

Sección titulada «Un parámetro que nombra una magnitud lleva su unidad»

En todo lo demás, las condiciones ambientales conservaron su sitio y ganaron un sufijo. La regla es que un parámetro público que nombra una magnitud dimensional termina en la unidad que teclea quien llama, porque el número es donde se pierde la unidad y ninguna guardia puede cazarlo por magnitud: 101,325 y 101325 son las dos presiones legítimas en esta biblioteca.

3.3.04.0
temperature=temperature_c=
pressure=, static_pressure=, barometric_pressure=, ambient_pressure=, atmospheric_pressure=atmospheric_pressure_kpa=, o _pa= donde el apartado esté escrito en pascales
humidity=, relative_humidity=relative_humidity_percent=
diameter= y sus trece compuestosdiameter_m=
angle=, angles=, bank_angle=, path_angle=, grazing_angle=, launch_angles=_deg= o _rad=, según lo que lea la función
runway_gradient=runway_gradient_ratio=
sound_speed_gradient=sound_speed_gradient_per_s=

Un caso merece mención propia porque era una trampa y no una incomodidad. critical_angle era un campo en radianes en un resultado y un parámetro en grados en las dos funciones que estaban una pantalla más abajo, o sea la misma palabra para dos cosas separadas por un factor 57. Ahora es critical_angle_rad en el resultado y critical_angle_deg en los parámetros.

transmission_loss= pasó a propagation_loss= sólo en los resultados de sónar y de propagación numérica submarina. ReactiveSilencerResult y SoundReductionResult siguen llamando al suyo transmission_loss=, y con razón: es como lo llaman las normas que hay detrás.

Las funciones de la EN 29052-1 de materials siguen la misma regla, con la unidad de cada magnitud al final de su nombre:

3.3.04.0
apparent_dynamic_stiffness(resonant_frequency=, total_mass_per_area=)resonant_frequency_hz=, total_mass_per_area_kg_m2=
enclosed_gas_stiffness(thickness=)thickness_m=
installed_dynamic_stiffness(apparent_stiffness, airflow_resistivity, gas_stiffness=)apparent_stiffness_n_m3, airflow_resistivity_kpa_s_m2= sólo por nombre, gas_stiffness_n_m3=
natural_frequency(dynamic_stiffness=, mass_per_area=)dynamic_stiffness_n_m3=, mass_per_area_kg_m2=
floating_floor_resonance(resonant_frequency=, total_mass_per_area=, floor_mass_per_area=, airflow_resistivity=, thickness=)resonant_frequency_hz=, total_mass_per_area_kg_m2=, floor_mass_per_area_kg_m2=, airflow_resistivity_kpa_s_m2=, thickness_m=

La resistividad al flujo es la que se gana la palabra clave. La EN 29052-1 pone sus umbrales en kPa·s/m², y todas las demás resistividades al flujo de la biblioteca van en Pa·s/m², una unidad mil veces menor, así que una cifra en Pa·s/m² pasada por posición caía sin aviso en la rama equivocada del apartado 8.2; ahora se escribe por su nombre. Y por debajo de 100 kPa·s/m², installed_dynamic_stiffness ya no toma como cero un término de gas ausente: la 3.3.0 devolvía con installed_dynamic_stiffness(20e6, 50.0), la Fórmula 6 sin su segundo término, y la 4.0 lanza un error y pide .

Las condiciones y las banderas se escriben por su nombre

Sección titulada «Las condiciones y las banderas se escriben por su nombre»

Dos reglas dejaron 37 firmas keyword-only. Una condición o una opción que lleva valor por defecto se escribe por su nombre, y toda bandera booleana pública es keyword-only. Las dos existen porque el sitio donde se pierde el significado es la llamada: air_attenuation(freqs, 20.0, 50.0, 101.325) son una temperatura, una humedad y una presión en un orden que no recuerda nadie, y bank.filter(x, True) no dice cuál de cinco banderas se ha activado.

El fallo es ruidoso, que es de lo que se trata:

import numpy as np
from phonometry.signals import leq
x = np.random.default_rng(1).standard_normal(4800) * 0.01
try:
leq(x, 48000)
except TypeError as error:
print(error) # leq() takes 1 positional argument but 2 were given

Esa llamada merece una segunda mirada. leq nunca tuvo fs, porque una media energética no lo necesita, así que en la 3.3.0 el 48000 se ligaba en silencio a calibration_factor y la respuesta salía 93,6 dB alta sin una queja. Ahora es un TypeError, que es lo que siempre debió ser.

print(round(float(leq(x, dbfs=True)), 3))

Los nombres públicos se habían ido repartiendo entre varias grafías de la misma magnitud, y la 4.0 se queda con la que ya usaba la mayoría. La regla de las magnitudes que se cuentan en plural es la que seguía casi toda la API: un campo o un parámetro que guarda un array es frequencies, times, distances o levels, y uno que guarda un solo valor va en singular. Un argumento vectorizado que admite un número o un array conserva su nombre.

3.3.0 o 4.0.0rc14.0
sound_speed (válvulas de control, medición de silenciadores, regímenes de Weston, ruido ambiente, FluidSeabed, SoundSpeedProfile, ShipSourceLevelResult)speed_of_sound
c= y rho= en sound_intensity, c= en las dos conversiones de desajuste de fase, phase_mismatch(), absorption_coefficient_uncertainty y monopole_source_levelspeed_of_sound=, density=
speed_of_sound_m_s= en thickness_critical_frequency_productspeed_of_sound=
sample_rate en el SignalSource de FDTD, STOIResult e impact_force_exposure_levelfs
freq= en linkwitz_riley y adaptation_term_kcfrequency= y frequencies=
.frequency en los resultados que llevan un eje de frecuencias: IntensityResult, FieldIndicators, IntensityInstrumentComplianceResult, RoomAcousticsResult, los cinco resultados de auditorio, ImpedanceTubeResult, PorousMediumResult, LayeredAbsorberResult, DiffuseFieldAbsorptionResult, BiotWavesResult, SlitResonatorAbsorberResult, MetadiffuserResult, TransferMatrix, los dos resultados de ruido de válvulas, AircraftBandAttenuation, AmbientNoiseResult, ShipTrafficSpectrum.frequencies
frequency= con un array: las funciones de transmisión por flancos, internal_spectrum, pipe_transmission_loss, expander_noise, transfer_matrix_one_load, transfer_matrix_two_load, TransferMatrix.plotfrequencies=
.time en ZwickerLoudness, EcmaLoudness, MooreGlasbergTimeVaryingLoudness, los resultados de tonalidad, aspereza y fuerza de fluctuación de ECMA-418-2, DecayCurve, QuasiPeakResult.times
.level en DecayCurve, NpdLevelResult, NoiseContourResult, RotorcraftNoiseContourResult, LowFrequencyResult, DuctPathStage, LateLateralResult, TransferStiffnessResult.levels
.distance en NpdLevelResult y RotorcraftEventResult.distances
level=, measured_level=, operational_level= en sti_from_impulse_response, stipa, sti_adjusted_for_levels y STIResult.adjusted_for_levelslevels=, measured_levels=, operational_levels=
.passed en AircraftSystemComplianceResult, QuasiPeakDynamicsResult, HeavyImpactSourceCheck, RigidMassCalibrationResult.passes, como en todos los demás verificadores
MicrophoneNoise(weighting="CCIR")MicrophoneNoise(weighting="468"), la grafía que usan weighting_filter y weighted_thd para la curva UIT-R BS.468-4

times, levels = decay_curve(ir, fs) sigue desempaquetando la curva de caída, en el mismo orden. Los solvers FDTD conservan c y rho, que nombran los mapas en los que están escritas sus ecuaciones.

El .plot() de cada resultado se lee ahora plot(ax=None, *, ...): primero los ejes, y todo lo demás por su nombre, language incluido. Tres no empezaban por los ejes en la 3.3.0, así que plot(ax) les entregaba los ejes al parámetro equivocado:

3.3.04.0
LoudspeakerCharacteristics.plot("impedance", ax).plot(ax, quantity="impedance")
MicrophoneCharacteristics.plot("noise", ax).plot(ax, quantity="noise")
TransferMatrix.plot(f, rho_c, ax).plot(ax, frequencies=f, characteristic_impedance=rho_c)

Seis resultados posteriores a la 3.3.0 tomaban language por posición en la 4.0.0rc1: las cuatro valoraciones de protectores auditivos y los dos resultados de intensimetría en baja frecuencia. plot(ax, "es") sobre ellos es ahora plot(ax, language="es"), como en todos los demás.

Los veredictos son objetos de resultado, no diccionarios

Sección titulada «Los veredictos son objetos de resultado, no diccionarios»

Las cinco funciones verify_* devolvían dict[str, Any] y ahora devuelven una dataclase congelada, como cualquier otro resultado de la biblioteca. Dos envoltorios públicos que existían sólo para empaquetar ese diccionario se fueron con el cambio, ya que el verificador devuelve ahora el objeto él mismo.

3.3.04.0
verify_filter_class(bank)["overall_class"]verify_filter_class(bank).overall_class
verify_weighting_class(wf)["overall_class"]verify_weighting_class(wf).overall_class
verify_intensity_class(...)["bands"]verify_intensity_class(...).bands
verify_quasi_peak_dynamics()["passed"]verify_quasi_peak_dynamics().passes
verify_aircraft_noise_system(...)["passed"]verify_aircraft_noise_system(...).passes
filter_class_compliance(bank)verify_filter_class(bank), que devuelve el mismo FilterComplianceResult
from phonometry.filters import OctaveFilterBank, verify_filter_class
verdict = verify_filter_class(OctaveFilterBank(48000, fraction=1, limits=[125, 4000]))
print(verdict.overall_class, len(verdict.bands))

verify_running_rms_decay es posterior a la 3.3.0, y en la 4.0.0rc1 devolvía un bool desnudo, que tiraba el intervalo impreso y el tiempo medido que había juzgado. Ahora devuelve un RunningRmsDecayVerification que conserva los dos: el veredicto es .passes, junto a .measured_time_s, .printed_time_s, .tolerance_s y el intervalo como .lower_time_s y .upper_time_s. El objeto se niega a leerse como valor de verdad, así que un if verify_running_rms_decay(...): escrito contra la versión candidata levanta TypeError en vez de dar por buenos todos los vibrómetros.

El banco de filtros devuelve un resultado, no una tupla

Sección titulada «El banco de filtros devuelve un resultado, no una tupla»

octave_filter() y OctaveFilterBank.filter() devolvían dos elementos normalmente y tres cuando sigbands=True pedía las señales por banda. La longitud de la respuesta dependía de una palabra clave, que es la razón por la que ese par necesitaba doce declaraciones @overload para poder tiparse, y por la que _, _, bands = bank.filter(...) era una línea en la que había que contar comas.

Ahora devuelven un OctaveFilterResult con campos con nombre, como cualquier otro cálculo de la biblioteca.

3.3.04.0
spl, freq = octave_filter(x, fs)r = octave_filter(x, fs), y luego r.levels y r.frequencies
spl, _ = octave_filter(x, fs)octave_filter(x, fs).levels
_, freq = octave_filter(x, fs)octave_filter(x, fs).frequencies
_, _, bands = bank.filter(x, sigbands=True)bank.filter(x, sigbands=True).require_bands()
import numpy as np
from phonometry import filters
result = filters.octave_filter(np.zeros(8000) + 1e-6, 8000, fraction=3)
print(len(result.frequencies), result.bands)
# 25 None

bands es None salvo que la llamada haya pedido sigbands=True, y levels es None en una llamada hecha con calculate_level=False. El código que necesita cualquiera de los dos puede decirlo con require_bands() o require_levels(), que devuelven el valor o fallan nombrando el argumento que faltaba, en vez de entregar un None que revienta unas líneas más adelante.

El resultado también se dibuja solo, así que el espectro de bandas es una llamada:

ax = result.plot()

Las formas de onda dentro de los resultados vuelven como el Signal del que salieron

Sección titulada «Las formas de onda dentro de los resultados vuelven como el Signal del que salieron»

Una transformación a la que se le entrega un Signal devuelve un Signal desde que se escribió el contrato, pero cinco objetos de resultado todavía guardaban su forma de onda como un array desnudo junto a un fs suelto. Ahora conservan el tipo con el que llegó la entrada:

CampoEs un Signal cuando la entrada lo era
envelope(...).signala la frecuencia de muestreo de la entrada
time_synchronous_average(...).period_waveforma la frecuencia de muestreo de la entrada
resample_signal(...).signala fs_new, que entonces tiene que ser un número entero de hercios
align_impulse_responses(...).aligned, .referencecada uno si su propia entrada lo era
underwater.pile_strike_metrics(...).pressurea la frecuencia de muestreo de la entrada

Un array desnudo sigue devolviendo un array desnudo, así que el código que pasa arrays no lo nota. El código que pasa un Signal y hace aritmética con uno de estos campos pasa por np.asarray, como con cualquier Signal, y una entrada calibrada vuelve con calibration_factor=1.0, así que el campo alimenta a la función siguiente en pascales sin que el factor se aplique otra vez.

io.read_blocks es posterior a la 3.3.0, y en la 4.0.0rc1 entregaba arrays float64 desnudos. Ahora entrega un Signal por bloque, con la frecuencia de muestreo, la calibración, las etiquetas de canal y la procedencia que da io.read, y toma el mismo calibration_factor= y lee el mismo sidecar. Para un bucle escrito contra la versión candidata cambian dos cosas. Un bloque de un archivo calibrado llega a los filtros y a las funciones de nivel en pascales, así que un calibration_factor aplicado a mano encima cuenta dos veces. Y la aritmética sobre un bloque, o sobre lo que un filtro devuelve para él, pasa por np.asarray, como con cualquier Signal.

Toda tabla que la biblioteca publica a nivel de módulo es de sólo lectura en la 4.0. En la 3.3.0 las tablas publicadas eran diccionarios corrientes y los arrays publicados admitían aritmética sobre el propio array, así que una línea como REFERENCE_CURVE[500] = 0.9 o BAND_IMPORTANCE *= 2 cambiaba el número para todo lo que se ejecutara después en el mismo proceso, sin que nada lo avisara. Los diccionarios son ahora types.MappingProxyType, también los que van anidados dentro de otra tabla, y los arrays tienen desactivada la bandera writeable. Leer no cambia; escribir lanza TypeError en una tabla y ValueError en un array.

El código que retocaba una tabla publicada tiene que copiarla antes:

from phonometry.materials.absorbers import REFERENCE_CURVE
from phonometry.speech.sii import BAND_IMPORTANCE
curve = dict(REFERENCE_CURVE)
curve[4000] = 1.0
weights = BAND_IMPORTANCE.copy()
weights[0] = 0.0
print(REFERENCE_CURVE[4000], curve[4000], weights[0], BAND_IMPORTANCE[0])
# 0.9 1.0 0.0 0.0083

Una tabla que lleva tablas dentro necesita copiar cada nivel, como en {area: dict(rows) for area, rows in GUIDE_VALUES.items()}. La misma copia hace falta antes de serializar una tabla con pickle, copiarla en profundidad o escribirla como JSON, porque un mapeo de sólo lectura no admite ninguna de las tres cosas.

Un resultado guarda una copia propia de los arrays que recibe

Sección titulada «Un resultado guarda una copia propia de los arrays que recibe»

En la 3.3.0 y en la 4.0.0rc1 un resultado guardaba el mismo array que se le pasaba siempre que ese array ya era del tipo que lee la función, float64 para niveles y frecuencias, porque en ese caso np.asarray devuelve su argumento sin tocarlo. Cambiar después el array de quien llamaba, levels[0] = 0.0 o frequencies *= 2, cambiaba un resultado ya calculado y quizá ya impreso. En la 4.0 todo array que una función o un método de la biblioteca guarda de los argumentos que recibe es una copia propia, de sólo lectura como las tablas publicadas, y el array de quien llama queda como estaba. Lo mismo vale para los objetos que guardan arrays para entregarlos después: la rejilla de frecuencias de una noise_control.SilencerChain, los ejes y las trayectorias con los que se construye una aircraft.AnpDatabase y los mapas del medio de simulation.FDTD2D y simulation.ElasticFDTD2D, cuyos c, rho, c_p y c_s siguen siendo ahora aquellos con los que se calculó el paso de tiempo. Una Signal también guarda una copia propia de sus muestras, que sigue siendo escribible para procesarla en su sitio, como siempre lo fueron las muestras de un fichero leído con io.read. Un registro de resultado construido a mano, llamando directamente a su clase, hace la misma copia: todo registro público que puede guardar un array copia los arrays que recibe al construirse, lo construya quien lo construya, también dentro de una tupla, una lista o cualquier diccionario. Un registro guardado dentro de otro se guarda entero y responde de sus propios arrays, y una Signal no es una excepción: los siete resultados que devuelven una forma de onda como Signal (filters.OctaveFilterResult, signals.EnvelopeResult, signals.ResampledSignalResult, signals.AlignedImpulseResponseResult, signals.SynchronousAverageResult, environment.DirectSoundSubtraction y underwater.PileStrikeResult) guardan una propia cuando los construye la biblioteca, cuyas muestras siguen siendo escribibles como las de cualquier Signal, y guardan la de quien llama cuando se construyen a mano con ella. Todos los demás arrays que guarda un resultado rechazan las escrituras en su sitio, tanto los que calculó como los que recibió, cuando en la 3.3.0 la mayoría de los calculados las aceptaban. Escribir en uno lanza ahora ValueError, así que el código que retocaba uno en su sitio tiene que copiarlo antes:

import numpy as np
from phonometry import io, room
levels = np.array([60.0, 58.0, 55.0, 52.0, 50.0, 48.0, 45.0, 42.0, 40.0, 38.0])
nc = room.noise_criterion(levels)
levels[0] = 0.0
samples = np.zeros(4)
signal = io.Signal(samples, fs=48_000)
samples[0] = 1.0
measured = np.array([1.0, 0.5, 0.25, 0.125])
response = room.ImpulseResponseResult(ir=measured, fs=48_000, method="farina")
measured[0] = 0.0
edited = nc.levels.copy()
edited[0] = 62.0
print(nc.levels[0], nc.levels.flags.writeable, response.ir[0], response.ir.flags.writeable)
print(signal.data[0, 0], signal.data.flags.writeable)
# 60.0 False 1.0 False
# 0.0 True

Una fila de catálogo dice lo que su fuente afirma de cada celda

Sección titulada «Una fila de catálogo dice lo que su fuente afirma de cada celda»

Los catálogos publicados de materiales son nuevos en la 4.0: ni la 3.3.0 ni la 4.0.0rc1 los tenían. Cambiaron de forma una vez de camino a la versión final, y el código escrito contra la rama principal entretanto necesita los cambios de abajo. Todas las filas de todos los catálogos comparten ahora una base, io.CatalogueRow (con io.BandedRow para una fila que imprime un valor por banda), y las mismas tres preguntas tienen la misma respuesta en cualquiera de ellas.

Antes4.0
SolidMaterial.estimated, OrthotropicWood.estimatedrow.basis, que asigna "estimated" a cada campo con la nota al pie
SolidMaterial.is_estimate(field), OrthotropicWood.is_estimated(field)row.basis_of(field) == "estimated"
row.is_derived(field) en un valor que la página da en una unidad que la fila no guarda (los °F y psi de la Tabla 14.1 de Ver & Beranek, los sabines de la Tabla 7.1 de Long)field in row.converted; row.converted[field] es la cifra de la página y su unidad, como ("3e5", "psi")
row.is_derived(field) en un valor que la página da por referencia a otra de sus filas: una celda en blanco bajo un bloque (Tabla 8.7 de Ver & Beranek, Tabla 30 de ASHRAE) o una descripción que dice «Parecido al anterior» (capítulo 32 de Harris)field in row.carried; row.carried[field] nombra la fila
OrthotropicWood(...) y PlateauMaterial(...) con sus propios campos por posicióntodos los campos por nombre, como en cualquier otra fila
from phonometry._internal.catalogue import CatalogueRowfrom phonometry.io import CatalogueRow

is_derived responde ahora sólo por lo que calcula la biblioteca y podría volver a calcular con las celdas de la propia fila, como una velocidad de barra obtenida de un módulo y una densidad. Un valor que la página da en otra unidad sigue siendo el número de la página, y también lo es uno que da por referencia a otra de sus filas, así que ninguno de los dos es ya derivado. basis_of responde con la entrada del propio campo, si no con la de la fila, y si no con una cadena vacía, que quiere decir que la fuente no dice cómo se obtuvo el número; las cinco cosas que puede afirmar una fuente están en io.CATALOGUE_BASES.

from phonometry import solids
board = solids.PUBLISHED_SOLIDS["hopkins-2007-table-a2/plasterboard_natural_gypsum"]
ear = solids.damping_named("EAR C-1002")[0]
print(board.basis_of("poisson_ratio"))
# estimated
print(ear.converted["youngs_modulus_max_pa"], ear.is_derived("youngs_modulus_max_pa"))
# ('3e5', 'psi') False

Las capas elásticas pasaron a ser filas de catálogo en el mismo cambio. Una ResilientLayer es una io.CatalogueRow como cualquier otra fila, con la clave y la atribución de las demás:

Antes4.0
PUBLISHED_RESILIENT_LAYERS["mineral_wool_rock_60_30"], resilient_layer("mineral_wool_rock_60_30")"hopkins-2007-table-a3/mineral_wool_rock_60_30", la clave "<tabla>/<fila>" de todos los catálogos
layer.attributed_to, una cadenalayer.attributed_to["row"], un mapeo como en todas las filas
ResilientLayer(...) con la rigidez, la densidad y el espesor obligatoriostodas las magnitudes opcionales, y apparent_dynamic_stiffness_n_m3 junto a dynamic_stiffness_n_m3 para una fuente que sólo da la aparente
layer.natural_frequency(mass_per_area=...)layer.natural_frequency(mass_per_area_kg_m2=...); una capa que sólo guarda toma además airflow_resistivity_pa_s_m2= y gas_stiffness_n_m3=, y sin el primero lanza io.CatalogueError

La Tabla A3 de Hopkins imprime , la rigidez de la capa instalada tal como la define la lista de símbolos del propio libro, así que sus quince filas siguen guardando dynamic_stiffness_n_m3 y devuelven las frecuencias naturales que devolvían. Cuatro de ellas guardan una densidad que la página imprime una sola vez para su bloque y deja en blanco en su propia línea, y layer.carried["density_kg_m3"] nombra ahora la fila que la imprime; la densidad es la misma.

from phonometry import materials
rebond = materials.resilient_layer("hopkins-2007-table-a3/rebond_foam_64_20")
print(rebond.attributed_to["row"], round(rebond.natural_frequency(mass_per_area_kg_m2=120.0), 1))
# Hopkins and Hall (2006) 43.6

Una fila de catálogo se comprueba al construirse

Sección titulada «Una fila de catálogo se comprueba al construirse»

Toda fila de catálogo se comprueba en su constructor, la construya la biblioteca a partir de una tabla empaquetada o quien llama a mano, y una celda que nada de lo que viene después podría leer lanza io.CatalogueError, nombrando la fila y el campo. El código escrito entretanto contra la rama principal que contara con que se aceptara algo de lo siguiente tiene que cambiar:

Antes se aceptaba4.0
NaN, un infinito, True o un texto como "0,97" en un campo numéricoCatalogueError; una celda que la página deja vacía es None
una fracción en un campo entero (year), 1 en un campo booleano (has_section_drawing), un número en un campo de textoCatalogueError
un name o un source vacíos, o un matiz cuyo texto está vacío, como misprinted={"porosity": ""}CatalogueError; why_missing respondía "" para esa celda, lo que escondía el matiz
un matiz con la clave de un campo que la clase no tiene, como en ranges={"no_such_field": ...}, o un matiz sobre un número (ranges, approximate, unquantified, …) con la clave de un campo de textoCatalogueError, que nombra el matiz y la clave
bounded_above o bounded_below sobre un campo sin intervalo; un intervalo, o uno de los intervalos de las lecturas de reported, con un extremo que no es finito o con el extremo bajo por encima del alto; una lista de reported sin nada dentro; una uncertainty negativaCatalogueError
un valor junto a misprinted, unquantified, not_derivable o reported en el mismo campo; una entrada de converted o carried en un campo que no guarda nadaCatalogueError: los cuatro primeros dicen que no hay valor que servir, y los dos últimos no tienen nada que describir
un valor negativo en un campo cuyo nombre acaba en _kg_m3, _kg_m2, _kg, _kg_mol, _m_s, _pa, _pa_s_m2, _pa_s_m, _n_m3, _mm, _um, _m, _m2, _m_hz o _per_cm (una densidad, una masa por unidad de superficie, una masa, una masa molar, una velocidad, una presión o un módulo, una resistividad al flujo, una resistencia al flujo específica, una rigidez por unidad de superficie, una longitud, un área, un producto espesor por frecuencia, una cuenta por centímetro); una porosidad fuera de 0 a 1; shot_content_percent, binder_content_percent o adhered_area_percent fuera de 0 a 100CatalogueError; una temperatura en grados Celsius, una tasa de decaimiento por metro y un nivel en decibelios siguen pudiendo ser negativos
approximate=[...] guardado como la lista que se le dio, un mapeo guardado como el diccionarioun frozenset, y todo mapeo congelado hasta el fondo, con sus pares en tuplas
un campo de una subclase anotado con un tipo para el que el contrato no tiene comprobación, un float o un int a secas incluidosTypeError la primera vez que se construye la clase; un campo es float | None (Optional[float] es lo mismo), int | None, bool, str, frozenset[str] o un Mapping[str, ...] de ellos, porque cualquier celda de una fila puede faltar
PorousMaterial.frame_constants() sobre una fila sin structural_loss_factor, leída como un esqueleto que no disipa nadaValueError, que dice qué tenía la página en esa celda
GroundSurface.porosity con valores de 26,9 a 58,1 en seis filas de la Tabla 6.7 de Cox y D’Antonio, que la página imprime en tanto por ciento en una columna de fracciones que no dice su unidadNone, con misprinted["porosity"] citando la cifra que imprime la página; why_missing("porosity") y printed("porosity") lo dicen

Todas las demás filas empaquetadas cumplían ya el contrato, así que esas seis porosidades son las únicas celdas publicadas que cambian, y todas las filas porosas publicadas con los dos módulos imprimen su factor de pérdidas.

from phonometry import io, materials
try:
materials.PorousMaterial(name="Panel core", source="a datasheet", porosity=97.0)
except io.CatalogueError as error:
print(error)
# 'Panel core': porosity is 97.0, and a porosity is a fraction from 0 to 1
carpet = materials.Carpet(
name="Loop pile", source="a datasheet", pile_height_mm=6.0, approximate=["pile_height_mm"]
)
print(carpet.approximate)
# frozenset({'pile_height_mm'})

La clase de un banco de filtros lee todos los requisitos de IEC 61260-1

Sección titulada «La clase de un banco de filtros lee todos los requisitos de IEC 61260-1»

verify_filter_class calificaba solo la máscara de la Tabla 1. Para la edición de 2014 califica ahora también la desviación del ancho de banda efectivo del 5.12 y la suma de las señales de salida del 5.16, calculadas como las calcula IEC 61260-2:2016, y la class de una banda y la overall_class del banco son la clase más estricta que se cumple en los tres. Para la edición de 1995 califica la respuesta integrada del filtro del 4.5.3 y la suma de las señales de salida del 4.9 junto a la Tabla 1, como las ensaya el capítulo 5 de esa edición. En las dos ediciones cada banda se califica a la frecuencia de entrada del banco hasta su mitad, así que una banda diezmada se califica sobre las imágenes que su diezmado pliega sobre ella, y el diezmador del banco las deja a más de 125 dB por debajo.

Antes4.0
cada banda de un banco diezmado se diezmaba hasta que su frecuencia de Nyquist de proceso era 1,25 veces su borde superior de bandase diezma sólo hasta dejarla en dieciséis veces ese borde, así que la transformación bilineal ya no deforma la banda: el nivel de una banda se mueve hasta 0,07 dB con ruido blanco, sobre todo en las bandas más bajas, y el banco de octavas, que sumaría sus salidas hasta +0,94 dB y leería clase 2 en el 5.16, es clase 1 en todos los requisitos
la class de una banda leída de margin_class{c}_dbclass sobre todos los requisitos; margin_class{c}_db sigue siendo el margen de la Tabla 1, junto a bandwidth_margin_class{c}_db y summation_margin_class{c}_db, que vale None en las dos bandas de los extremos
un solo requisito por el que preguntarrequirements, requirement_class(name) y binding_margin_db(name, cls) leen cada uno por separado, y plot(requirement="summation") lo dibuja
un veredicto con edition="1995" solo sobre la Tabla 1la Tabla 1, "effective_bandwidth" (la respuesta integrada del filtro del 4.5.3) y "summation" (4.9, recorrida de la frecuencia central más baja a la más alta, así que las bandas de los extremos también la llevan), las dos con la S que el 5.3.3 sube en pasos de 12 hasta que la respuesta integrada se estabiliza, que el points_per_bandwidth del resultado indica (36 para el banco de octavas por defecto)
una banda diezmada calificada hasta la frecuencia de Nyquist de su frecuencia diezmada, con checked_to_omega parado ahícalificada a la frecuencia de entrada hasta su mitad, imágenes incluidas; checked_to_omega es la mitad de la frecuencia de entrada entre la frecuencia central, y range_limited marca una máscara de la Tabla 1 que sigue más allá
el filtro antialiasing que scipy.signal.resample_poly diseña por defecto, que dejaba las imágenes del banco de octavas a 8, 16 y 32 kHz 68,9 dB por debajo, sin llegar a los 70 dB de la clase 1un filtro de la misma longitud dimensionado para una banda atenuada de 120 dB, con sus imágenes al menos 125,4 dB por debajo; el nivel de una banda se mueve menos de 0,01 dB

Los bancos de octavas y de tercios de octava siguen siendo clase 1.

from phonometry import filters
octave = filters.verify_filter_class(
filters.OctaveFilterBank(48000, fraction=1, limits=[125, 4000]))
print(octave.overall_class, octave.requirement_class("relative_attenuation"))
# 1 1

Un veredicto lee de la norma el límite que esta imprime

Sección titulada «Un veredicto lee de la norma el límite que esta imprime»

Un veredicto llevaba antes como campo del constructor el límite con el que se le juzgaba. En 3.3.0 lo hacían tres: ToneAssessment.criterion_db, ExposureAssessment.action_value y limit_value, y RigidMassCalibrationResult.tolerance. Otros seis, posteriores a 3.3.0, lo hacían en 4.0.0rc1, y el resto de la tabla llegó después de 4.0.0rc1. Cualquier código que construyera uno a mano, o que lo reescribiera con dataclasses.replace, podía juzgar una medición contra cualquier número y seguir poniendo el nombre de la norma encima del resultado. En 4.0 todo límite que imprime una norma es una propiedad de solo lectura, leída de los campos que la seleccionan: las bandas, la condición, la fuente, la clase, la sala. Un veredicto que se guardaba junto a su límite se lee también de los campos medidos, o se contrasta con ellos al construir el resultado, de modo que no puede contradecir el límite. Las funciones que devuelven estos veredictos reciben los mismos argumentos y dan las mismas respuestas, y cada nombre de la tabla sigue leyendo el mismo valor; solo cambia el código que construye un veredicto directamente o que sustituye uno de estos campos. La última columna indica en qué versión apareció cada veredicto.

VeredictoYa no son campos del constructorCampos que conserva o ganaDesde
building.PositionSpreadChecklimit_db, stage, spread_db, action, next_positions, corner_standard_deviation_dblevels_db, del que se leen todosdespués de 4.0.0rc1
building.HeavyImpactSourceCheckfrequencies, nominal, tolerance, deviation, within_tolerance, passessource, measured4.0.0rc1
noise_control.BandFlatnessChecklimit_db, satisfiedoctave_centres_hz, spread_dbdespués de 4.0.0rc1
noise_control.SourcePositionCheckrequired_positions, satisfied, exceeds_maximumpositions_used, max_octave_spread_dbdespués de 4.0.0rc1
noise_control.TestEnvironmentApplicabilityenvironmental_correction_limit_db, background_margin_limit_db, required_area_ratio, applicablebase_standard, actual_area_ratio, mean_absorption_coefficientdespués de 4.0.0rc1
environment.ReflectionGridChecknominal_m, deviations_m, withincheck, path_differences_mdespués de 4.0.0rc1
emission.HardWalledRoomChecklimit_db, minimum_volume_m3, box_dimension_limit_m, minimum_microphone_distance_mlas bandas, la sala y la caja de referenciadespués de 4.0.0rc1
emission.ReferenceSoundSourceVerdicttodos los *_limit_db, adjacent_step_db, core_range_db, extended_range_db, frequency_range_met, not_judgedlas bandas, los niveles y las cifras medidasdespués de 4.0.0rc1
emission.ReferenceSourceDriftResult, emission.SpecialRoomSuitabilityChecklimit_dblas bandasdespués de 4.0.0rc1
emission.SpecialRoomReverberationChecklower_limit, upper_limitlas bandasdespués de 4.0.0rc1
emission.IntensityInstrumentComplianceResultlimit_class1, limit_class2, spacing_offset_dbdevice, spacing; las filas por banda se leen de ellos, véase la sección siguiente4.0.0rc1
emission.InverseSquareLawResult, emission.SourceDirectionalityResulttolerance_dblo selecciona room; los radios cualificados deben ser los que alcanzan los nivelesdespués de 4.0.0rc1
hearing.AmbientNoiseChecklimits_db, table_bands_hzgana earphone_attenuation_db y allowed_threshold_shift_dbdespués de 4.0.0rc1
hearing.FixtureIsolationCheckrequired_dblas bandasdespués de 4.0.0rc1
hearing.ReatSoundFieldCheckallowable_variation_dbgana free_field_rejection_dbdespués de 4.0.0rc1
hearing.DiffuseSoundFieldCheckallowable_variation_dbgana front_to_random_index_dbdespués de 4.0.0rc1
hearing.FreeSoundFieldCheckaxis_offset_m, inverse_distance_difference_dblos seleccionan field y loudspeaker_distance_mdespués de 4.0.0rc1
electroacoustics.ProgrammeSignalCheckoffset_db, relative_levels_db, tolerance_plus_db, tolerance_minus_dbfrequencies_hz, band_levels_dbdespués de 4.0.0rc1
electroacoustics.NeckLoopVerificationlimitslo selecciona neck_loop_typedespués de 4.0.0rc1
psychoacoustics.ToneAssessmentcriterion_db, prominentgana method, "tone_to_noise_ratio" o "prominence_ratio"3.3.0
underwater.WeightedExposureResultcriteriagana impulsive4.0.0rc1
vibration.ExposureAssessmentaction_value, limit_value, exceeds_action, exceeds_limit, zonevalue, kind, metric3.3.0
vibration.PhaseVerification, vibration.WeightingVerificationtolerance_deg (fase), within_tolerancela ponderación y las frecuencias4.0.0rc1
vibration.RunningRmsDecayVerificationprinted_time_s, tolerance_smethod, integration_time_s, measured_time_sdespués de 4.0.0rc1
vibration.SignalBurstVerificationprinted, deviation_percent, tolerance_percent, within_tolerancela fila de la tabla, las columnas y measured4.0.0rc1
vibration.VibrationMeterVerificationlower_percent, upper_percent, within_tolerancegana measured_responsedespués de 4.0.0rc1
vibration.AssessmentVelocityguide_value_mm_slo selecciona building_classdespués de 4.0.0rc1
vibration.MountingCheckacceptable, frequency_limit_hz, peak_acceleration_limit_m_s2, devicegana peak_acceleration_m_s2 y upper_frequency_hzdespués de 4.0.0rc1
vibration.DamageAssessmentguideline_mm_sgana massive_structure, solo por nombre4.0.0rc1
vibration.RigidMassCalibrationResultexpected, deviation, within_tolerance, passes, tolerancefrequencies, measured, mass, quantity3.3.0
vibration.LevelDifferenceChecklimit_dblo selecciona conditiondespués de 4.0.0rc1
vibration.OutputMassCheckmass_limit_kggana output_apparent_mass_kg, la fuerza de salida medida entre la aceleración de salidadespués de 4.0.0rc1

También cambian dos llamadas. rigid_mass_calibration_check ya no recibe tolerance: el apartado 7.5.2 de ISO 7626-2:2015 dice que el bloque «shall agree within ±5 %», así que una aceptación interna más estricta se lee de .deviation. Y HeavyImpactSourceCheck y RigidMassCalibrationResult, cuyo passes era un campo almacenado, se niegan a leerse como valor de verdad igual que los demás veredictos: if check: lanza TypeError y el veredicto es .passes.

import dataclasses
from phonometry import vibration
check = vibration.check_blocked_output([100.0, 200.0], [90.0, 95.0], [60.0, 70.0])
print(check.limit_db, check.passes)
# 20.0 True
try:
dataclasses.replace(check, limit_db=10.0)
except TypeError as error:
print(type(error).__name__)
# TypeError
calibration = vibration.rigid_mass_calibration_check([0.104, 0.093], [50.0, 500.0], 10.0)
print(calibration.tolerance, calibration.within_tolerance.tolist())
# 0.05 [True, False]

Los límites que una norma deja en manos del usuario siguen siendo campos: los límites de rugosidad y de tasa de decaimiento «por defecto» de ISO 3095, la tolerancia que ISO/DIS 16032 7.6 no imprime para «aproximadamente 30 s», el periodo de curado que fija la especificación del producto según ISO 10140-1 G.4, el desplazamiento de umbral que ISO 8253-1 deja aceptar al examinador, y los límites de especificación, de caso de ensayo y de fila de límites que se pasan a las comprobaciones de conformidad, de ISO 17534-1 y del RD 1367/2007. Los registros de requisitos de ISO 8297 y de IEC 60118-4, PlantRequirement y LoopRequirement, también conservan como campo el límite de cada fila: un mismo tipo de registro reúne requisitos cuyos límites mezclan cifras impresas con la geometría de la planta, la intensidad de campo especificada o el ruido medido con el bucle apagado.

Un veredicto se lee de los valores que guarda

Sección titulada «Un veredicto se lee de los valores que guarda»

La sección anterior sacó de los constructores los límites impresos. Las banderas del veredicto se quedaron dentro: passes, complies, qualified, todos los *_met y *_ok, los avisos y la clase de cada banda de un veredicto de filtro, de ponderación o de instrumento de intensidad. La función que construía el resultado las calculaba y las guardaba junto a las lecturas de las que salían, así que un resultado construido a mano, o reescrito con dataclasses.replace, podía guardar lecturas que no cumplen y una bandera que dice que sí. En 4.0 todas son propiedades de solo lectura, leídas de los valores que guarda el resultado y del límite que imprime la norma. El resultado conserva aquello de lo que se lee su veredicto: las lecturas, los márgenes, las posiciones, las cifras medidas. Una cifra que solo alimentaba el veredicto, como el nivel redondeado de un periodo del RD 1367/2007 o la fracción de gotas de lluvia dentro de su tolerancia, también es una propiedad. Las funciones que devuelven estos resultados reciben los mismos argumentos y dan los mismos veredictos, y cada nombre de la tabla sigue leyendo el mismo valor; solo cambia el código que construye uno de estos resultados directamente o que sustituye uno de los campos de la lista. «Solo por nombre» marca un campo que solo se pasa por nombre.

ResultadoYa no son campos del constructorSe lee deDesde
building.JointTestElementChecklength_ok, width_ok, passesjoint_length_m, joint_width_mm, window_or_door_gapdespués de 4.0.0rc1
building.GapWidthCheckgap_width_mm, spread_mm, enough_positions, uniform, passesreadings_mmdespués de 4.0.0rc1
building.JointGapSeriesChecknominal_measured, minimum_measured, working_range_measured, passesgap_widths_mm, nominal_gap_mm, minimum_gap_mmdespués de 4.0.0rc1
building.LiningCuringCheckcured, lag_within_third, passescuring_time_days, time_lag_days, required_curing_daysdespués de 4.0.0rc1
building.RainGeneratorVerificationrate_deviation_mm_h, rate_ok, drop_share, drops_ok, velocity_share, velocities_ok, passesrain_type, rainfall_rate_mm_h, drop_diameters_mm, fall_velocities_m_sdespués de 4.0.0rc1
building.ServiceEquipmentPositionCheckseparation_m, surface_distance_m, source_distance_m, heights_m, corner_height_m, corner_wall_distances_m y los seis *_okla sala, la esquina y las posicionesdespués de 4.0.0rc1
building.DbHrCheckreported, margin, compliesrequirement, value4.0.0rc1
building.LowFrequencyIntensityResult, building.LowFrequencyElementResultqualifiedsurface_pressure_intensity_indicator, frente al límite de 6.4.2 que selecciona absorbing_specimen_surface4.0.0rc1
electroacoustics.BackgroundNoiseAssessmentreference_signal_to_noise_ratio_db, category, report_requirednoise_levels_db, noise_is_tonaldespués de 4.0.0rc1
electroacoustics.FeedbackStabilityResultis_stableloop_gain, stability_margin4.0.0rc1
emission.FreeFieldCheckroom, frequencies_hz, band_radius_m, maximum_qualified_radius_m, todos los *_met, full_frequency_range, conforming_range_hz, conforming_radius_m, not_judgedlos argumentos de check_free_field: gana source_directionality, reflecting_plane_absorption_coefficient, reflecting_plane_margin_m, paths_in_working_area, speed_of_sounddespués de 4.0.0rc1
emission.IntensityInstrumentComplianceResultbands, overall_class, range_limitedfrequencies, residual_index, device, spacing4.0.0rc1
emission.ReferenceSourceCalibrationsound_power_level_db, intensity_bands, intensity_agreement, room_qualifiedgana intensity_sound_power_level_db y room_qualificationdespués de 4.0.0rc1
emission.HardWalledSoundPowerResultbackground_requirement_met, upper_boundgana background_margin_db, background_margin_ref_dbdespués de 4.0.0rc1
emission.InSituSoundPowerResultbackground_requirement_met, upper_bound, grade, sigma_r0, sigma_tot, expanded_uncertaintygana background_margin_db, background_margin_ref_db, los menores de todas las posiciones, y excess_levels_db, directivity_range_db, los dos indicadores con los que la tabla 2 asigna el grado4.0.0rc1
emission.SpecialRoomSoundPowerResultbackground_requirement_met, background_requirement_met_agana background_margin_db, background_margin_ref_db, background_margin_a_dbdespués de 4.0.0rc1
emission.EmissionPressureResultupper_boundgana background_margin_db, solo por nombre4.0.0rc1
emission.PrecisionCriteriade criterion_1 a criterion_5, qualifiedgana las lecturas: indicators, scan_intensity_level_1, scan_intensity_level_2, frequencies, repeatability_limit_db, pressure_residual_index_db, field_nonuniformity_1, field_nonuniformity_23.3.0
emission.DiscretePointIntensityResulttodo salvo los indicadores y el grado: partial_power, sound_power, sound_power_level, achieved_grade, los dos criterios, las cifras con ponderación A y el restogana normal_intensity, segment_areas_m2, pressure_residual_index_db, band_type4.0.0rc1
emission.TurbineTestEnvironmentCheckcorrection_ok, wind_ok, windscreen_advisedenvironmental_correction_db, ratio, wind_speed_m_sdespués de 4.0.0rc1
environment.ImpulseOnsetlevel_difference, prominence, qualifieslevel_start, level_end, onset_rate3.3.0
environment.ImpulseProminenceResultper_impulse, qualifies, prominence, adjustmentonset_rates, level_differences, assessment_period_min3.3.0
environment.ResidualCorrectionResultreliablemargin3.3.0
environment.PeriodAssessmentreported_level, reported_long_term, max_phase_level, phase_pass, daily_pass, long_term_passphases, los dos niveles y limit; gana new_activity, solo por nombre4.0.0rc1
environment.ModulationPeriodmodulation_depths_db, fundamental_frequencies_hz, valid_blocks, rated, rating_db, mean_modulation_frequency_hz, mode_modulation_frequency_hzblocksdespués de 4.0.0rc1
environment.SoundRelevantTurbinesrelevant, total_level_db, relevant_level_dbpredicted_levels_dbdespués de 4.0.0rc1
environment.WindShearProfiletypicalshear_exponentdespués de 4.0.0rc1
environment.WindTurbineTonalityResultis_audibletonal_audibility, has_identified_tone, que también es de solo lectura (sección siguiente)3.3.0
filters.FilterComplianceResultbands, overall_class, range_limitedgana band_margins, las filas sin su clase3.3.0
filters.WeightingComplianceResultbands, overall_class, range_limitedgana band_margins, las filas sin su clase4.0.0rc1
hearing.AscendingThresholdResultthreshold_db, determined, series_exhaustedascent_levels_db, shorteneddespués de 4.0.0rc1
hearing.TaskContributionspread_advisoryn_samples, sample_range_db3.3.0
hearing.ExposureResultsampling_advisorygana sample_range_db, n_workers, sample_duration_hours, solo por nombre3.3.0
hearing.AttenuationDifferenceResultdifference_db, criterion_db, significantlas dos medias y sus incertidumbres expandidasdespués de 4.0.0rc1
materials.CeilingSpecimenCheckarea_error_m2, deflection_ok, substructure_ok, fixture_ok, supports_ok, humidity_ok, ce_marking_depth, satisfiedgana las cifras medidas: deflection_mm, substructure_width_mm, substructure_height_mm, fixture_density_kg_m2, support_width_mm, support_height_mm, support_centre_distance_m, lowest_relative_humidity_percentdespués de 4.0.0rc1
metrology.TrendTestResulttrend_free, boundsstatistic, frente a la región que dan values, method, alpha y median3.3.0
metrology.StationarityTestResultstationary, boundscount, frente a la región que dan segment_values, method y alpha3.3.0
metrology.CouplerCheckratio_recommended, approximation_valid, full_solution_advised, conditions_validgana heat_conduction_method, temperature_c, static_pressure_pa, relative_humidity_percent, in_air, solo por nombredespués de 4.0.0rc1
metrology.FreeFieldArrangementCheckdistances_ok, support_ok, annex_a_range, attenuation_accuracygana air_attenuationdespués de 4.0.0rc1
noise_control.CabinUncertaintyratio_satisfied, stateable, stated_band_range_hz, increased_uncertainty_band_range_hz, excess_standard_deviation_dbmethod, volume_ratiodespués de 4.0.0rc1
room.RoomAcousticsResultedt_valid, t20_valid, t30_validdynamic_range y los tiempos de caída3.3.0
room.BackgroundMarginCheckneeds_correction, unusable, satisfiedmargins_dbdespués de 4.0.0rc1
room.DetailVerdictsatisfied, room_ok, fittings_ok, sources_okgana room_detail, fitting_detail, source_detaildespués de 4.0.0rc1
underwater.WeightedExposureResultexceeds_injury, exceeds_ttssel_margin, peak_margin, tts_margin, tts_peak_margin4.0.0rc1
vibration.PeopleAssessmentcomplies, criterion, within_uncertaintykb_fmax, kb_ftr, guide, source; gana rare_short_events, solo por nombredespués de 4.0.0rc1
vibration.RailwayChangecomplies, kb_fmax_met, kb_ftr_met, kb_fmax_increase_percent, kb_ftr_increase_percentgana kb_fmax_before, kb_fmax_after, kb_ftr_before, kb_ftr_afterdespués de 4.0.0rc1
vibration.TransferStiffnessResultvalidgana transmissibility, el |T| del método indirecto3.3.0
vibration.DrivingPointStiffnessResultadequategana blocked_output_difference_db, unwanted_input_difference_dbdespués de 4.0.0rc1
aircraft.AircraftSystemComplianceResultpasses, checksgana directional, frequency_response, linearity, resolution, los argumentos de verify_aircraft_noise_system4.0.0rc1
broadcast.QuasiPeakDynamicsResultpasses, worst_margin_db, worst_deviation_db, stimuligana readings_percent, las once lecturas en el orden de las tablas 2 y 34.0.0rc1
building.FloorCoveringImprovementResultlimitedgana background_margin_db, el menor margen de todas las posiciones y de los dos forjados3.1.0
building.VibrationReductionResultsingle_number, bracketedfrequencies, k_ij; gana modal_overlap, band_type3.1.0
building.ServiceEquipmentBackgroundResultdifference_db, correction_db, corrected_db, regimemeasured_db, background_dbdespués de 4.0.0rc1
building.ServiceEquipmentResultstandardizablefrequencies_hz, banddespués de 4.0.0rc1
emission.SoundPowerIntensityResultpartial_power_level, sound_power, sound_power_level, negative_band, negative_partial_power_index, dynamic_capability_index, achieved_grade, a_weighting_omitted_bands, sound_power_level_apartial_power y los indicadores; gana pressure_residual_index_db, band_type, repeatability_limit_db3.1.0
emission.PrecisionIntensityResultsound_power, sound_power_level, sound_power_level_normalized, not_applicable_band, sound_power_level_apartial_power; gana temperature_c, barometric_pressure_pa, solo por nombre3.1.0
emission.InDuctSoundPowerResultinformation_only_bandfrequencies, flow_velocity4.0.0rc1
emission.SourceLocationPlanspectral_character, a_weighted_spectral_characterstandard, standard_deviation_db, a_weighted_standard_deviation_dbdespués de 4.0.0rc1
environment.ImpulsiveSoundResultprominence, adjustment, category, adjusted_laeqonsets, laeq3.3.0
environment.ModulationBlockstatusprominence; gana excluded, solo por nombredespués de 4.0.0rc1
environment.ReferenceTrackCheckconditionsla rugosidad, las tasas de decaimiento y los límites; gana curve_radius_m, track_gradient_ratio, solo por nombredespués de 4.0.0rc1

FilterComplianceResult y WeightingComplianceResult guardan sus filas por banda como band_margins: las filas que guardaba bands, sin la clave "class". Una fila que declara una clase se rechaza, y la propiedad bands añade a cada fila la clase que ganan sus márgenes, de modo que result.bands[i]["class"] se lee como antes. IntensityInstrumentComplianceResult ya no recibe filas por banda: guarda las frecuencias y el índice presión-intensidad residual medido, y lee de ellos, del dispositivo y de la separación de los micrófonos las filas, la clase y range_limited.

Las filas de las que se lee un veredicto son de solo lectura. band_margins y between_nominals se copian en diccionarios de solo lectura al construir el resultado, de modo que escribir en una lanza TypeError y escribir después en los diccionarios con los que se construyó no mueve la clase; para cambiar una fila, cópiela con dict(row), porque copy.deepcopy no puede copiar un diccionario de solo lectura. Las filas de AircraftSystemComplianceResult.checks, QuasiPeakDynamicsResult.stimuli y ReferenceTrackCheck.conditions se construyen, cada vez que se leen, a partir de lo que guarda el resultado y de las tablas de la norma.

import dataclasses
from phonometry import building, filters
gap = building.check_gap_width([5.0, 5.1, 5.2, 5.1])
print(gap.passes, round(gap.spread_mm, 1))
# True 0.2
try:
dataclasses.replace(gap, passes=False)
except TypeError as error:
print(type(error).__name__)
# TypeError
wider = dataclasses.replace(gap, readings_mm=(5.0, 5.1, 5.6, 5.1))
print(wider.uniform, wider.passes)
# False False
bank = filters.OctaveFilterBank(fs=48000, fraction=1, order=6, limits=[125, 4000])
result = filters.verify_filter_class(bank)
print(result.overall_class, result.bands[0]["class"], "class" in result.band_margins[0])
# 1 1 False

Una bandera sigue siendo un campo cuando no es un veredicto contra un límite y nada de lo que guarda el resultado la decide: una opción que fija quien llama, como small_room o noise_is_tonal; una rama que eligió quien llama con lo que pasó, como InverseSquareLawResult.origin_fitted; un hecho de una entrada que el resultado no guarda, como TimeWeightedEnvelope.calibrated; una propiedad del fichero leído; si un resolvedor iterativo convergió; y una columna de una fila de una tabla publicada. CriticalCouplingResult.converged conserva el desenlace de su resolución, que el resultado no puede repetir, pero ya no puede declarar la convergencia junto a una absorción que no supera 0,999, la tercera condición de la bandera. Un régimen que el resultado podría leer de sus propios valores es un veredicto, y la sección siguiente enumera los que se han movido. PlantRequirement y LoopRequirement no cambian: sus filas conservan cada límite como campo por la razón que da la sección anterior, y PlantRequirement.holds sigue junto a él. TrackCondition conserva su holds como fila del informe, pero ReferenceTrackCheck construye sus condiciones a partir de la vía que guarda, así que ningún veredicto lee una construida en otro sitio. También se quedan los checks de sí o no de SoundLevelMeterPeriodicRequirement: son lo que vio quien ensaya, una sobrecarga indicada o un indicador enclavado, no un límite juzgado.

Un resultado lee su régimen de los valores que guarda

Sección titulada «Un resultado lee su régimen de los valores que guarda»

Algunos resultados guardaban un régimen o una clasificación junto a los valores de los que se leía: el régimen de una válvula, si un martillo rebota, si el terreno apantalla un trayecto, si se encontró un tono, si un promediado síncrono tuvo que interpolar. La función comparaba valores que el resultado guarda, o podría guardar, con una constante o con un umbral que imprime su fuente, y guardaba la respuesta, así que un resultado construido a mano podía guardar la rigidez de un martillo en régimen subcrítico y decir que es sobrecrítico. En 4.0 cada una es una propiedad de solo lectura, leída de los valores que conserva el resultado, y un resultado que no conservaba lo que lee la prueba ahora lo conserva, con la unidad en el nombre. «Solo por nombre» marca un campo que solo se pasa por nombre.

Un valor justo en un límite cae donde lo pone la fuente. Hopkins imprime el caso sobrecrítico como K m ≥ 4 Zdp², así que el amortiguamiento crítico es sobrecrítico, e IEC TS 61400-11-2 pide un total «al menos 3 dB» por encima del fondo para la resta logarítmica, así que una diferencia de 3 dB la recibe. IEC 60534-8-4 no zanja su umbral de cavitación en un solo sentido: la prueba de 5.1 no deja el umbral mismo de ningún lado, la región de la Ecuación (9) y la Ecuación (19b) lo ponen con los puntos con cavitación, y 4.1, la Ecuación (18b) y 6.3 con los turbulentos. La biblioteca lo lee como turbulento, donde la Ecuación (9) devuelve cero y las dos ramas se encuentran sin escalón.

Las funciones reciben los mismos argumentos y devuelven los mismos números. Tiene que cambiar el código que construye uno de estos resultados directamente o que sustituye uno de los campos de los que se lee su régimen, y también el que lee band_type de vibration_reduction_index (véase debajo de la tabla).

ResultadoYa no es campo del constructorSe lee deDesde
building.TappingForceResultover_critical, K m ≥ 4 Zdp² (Hopkins, ecuación 3.95)contact_stiffness, impedance; gana mass_kg, solo por nombre4.0.0rc1
noise_control.HydrodynamicValveNoiseregime, con cavitación cuando Δp supera x_Fzp1 (p1 - pv) (IEC 60534-8-4, 5.1)differential, corrected_ratio; gana inlet_pressure_pa, vapour_pressure_pa4.0.0rc1
noise_control.AerodynamicValveNoiseregime, los cinco intervalos de IEC 60534-8-3, 5.2pressure_ratio, boundaries; gana specific_heat_ratio, pressure_recovery, efficiency_correction, solo por nombre4.0.0rc1
aircraft.TerrainScreeningResultscreened, terreno estrictamente por encima de la línea de visiónsource, receiver, distances, heights3.3.0
hearing.AutomaticThresholdResultis_peak, retained (ISO 8253-1, 6.3.5 a))reversal_levels_dbdespués de 4.0.0rc1
hearing.SweepThresholdResultis_peakreversal_levels_dbdespués de 4.0.0rc1
metrology.WaveMotionCorrectioninterpolatedfrequencies_hz, speed_of_sound_ratiodespués de 4.0.0rc1
noise_control.CabinInsulationResultapparent (ISO 11957, 3.6)methoddespués de 4.0.0rc1
environment.WindTurbineTonalityResulthas_identified_tone (IEC 61400-11, 9.5.2 a 9.5.4)levels, frequencies; gana candidate_frequency_hz, solo por nombre3.3.0
building.LabJointInsulationResultregime (ISO 10140-1, J.1)r_s_measured_db, r_s_max_db; gana limit_at_maximum, solo por nombredespués de 4.0.0rc1
environment.TurbineSoundLevelsregimes (IEC TS 61400-11-2, 11.7)total_levels_db, background_levels_dbdespués de 4.0.0rc1
underwater.WestonPropagationResultregimerange_m, boundaries4.0.0rc1
signals.SynchronousAverageResultinterpolated, f_s T no es un número entero de muestrasfs, period_s4.0.0rc1

building.VibrationReductionResult.band_type sigue siendo un campo, como el conjunto de bandas que declara quien llama: octave_bands() declara "octave", porque una sola banda de octava no se distingue de una de tercio de octava por su espaciado. vibration_reduction_index ya no lo rellena con el conjunto de bandas que leía de las frecuencias; lo deja en None, y el resultado lee el espaciado cada vez que calcula la media del anexo A, que da la misma media que antes.

Los números que elige un régimen quedan atados a él, así que un resultado construido a mano ya no puede llevar un régimen junto a los valores de otro. Al construirse, cada uno de estos resultados rechaza:

  • TappingForceResult: una frecuencia de corte, una frecuencia límite, un espectro de fuerza o una potencia inyectada que no sean los que dan K, Zdp y m en su régimen, y unos límites de las ecuaciones (3.99) y (3.100) que no estén a un factor 2 el uno del otro.
  • HydrodynamicValveNoise: una presión de vapor igual o superior a la presión de entrada; un x_F o un x_Fzp1 que no sean los que dan la Ecuación (1) o la (3c) a partir de los campos de al lado; un punto en la evaporación o más allá, x_F = 1; campos de cavitación en un punto turbulento, o ninguno en uno con cavitación.
  • AerodynamicValveNoise: unos límites que no sean los de su gas y su guarnición, y un número de Mach o una eficiencia acústica de otro régimen.
  • TerrainScreeningResult: una fuente o un receptor que no sean un punto finito, una fuente en el receptor o más allá, una sección que no vaya de uno al otro, y una diferencia de camino o unas aristas de difracción que no sean las de la goma elástica sobre esa sección.
  • AutomaticThresholdResult y SweepThresholdResult: inversiones que no se alternan, un trazado que no conserva ningún pico o ningún valle, y una media, un umbral o un umbral continuo que no sean los que dan las inversiones.
  • WaveMotionCorrection: una frecuencia más allá de la última fila de la tabla C.3, y una corrección que la tabla no da en su frecuencia.
  • CabinInsulationResult: un aislamiento ponderado A con cualquier método que no sea el del ruido real.
  • WindTurbineTonalityResult: una candidata que no sea una línea del espectro a 20 Hz o más, y una cadena de las fórmulas 30 a 34 que no sea la que da el espectro en torno a ella.
  • LabJointInsulationResult: un índice corregido por otra regla de J.1.
  • TurbineSoundLevels: una diferencia de niveles que no sea el total menos el fondo, un nivel del aerogenerador por otra regla de 11.7, y una incertidumbre en un intervalo cuyo nivel no se puede determinar.
  • WestonPropagationResult: una pérdida compuesta que no sea la ley del régimen vigente.
  • SynchronousAverageResult: una rejilla de periodo de más o menos muestras que round(fs * period_s).
import dataclasses
import numpy as np
from phonometry import building
bands = np.array([100.0, 200.0, 400.0, 800.0])
force = building.tapping_force_spectrum(bands, 1.0e6, 3.0e4)
print(force.over_critical)
# False
print(building.tapping_force_spectrum(bands, 1.0e6, 100.0).over_critical)
# True
try:
dataclasses.replace(force, impedance=100.0)
except ValueError as error:
print("cut_off_frequency" in str(error))
# True

Todo lo demás se movió. Estos diez no existen en ningún módulo público:

Ya no estáQué escribir en su lugar
octavefilter, getansifrequencies, normalizedfreqfilters.octave_filter, filters.nominal_frequencies, filters.normalized_frequencies. Eran las grafías originales de PyOctaveBand, renombradas en la 3.1 y conservadas como alias hasta la 4.0
calculate_sensitivitymetrology.sensitivity, el mismo renombrado de la 3.1
filter_class_compliancefilters.verify_filter_class
air_density_iso, speed_of_sound_isomaterials.air_density_iso10534, materials.speed_of_sound_iso10534, y lee el aviso de los kelvin de más arriba
speed_of_soundfluids.air(temperature_c=...).speed_of_sound para el medio. Usa un modelo más completo que aquella fórmula de una línea de la ISO 17497-1, así que el número se mueve: 343,99 m/s frente a 343,20 m/s a 20 °C
depth_to_pressurefluids.depth_to_gauge_pressure_mpa(depth_m=...), que devuelve el mismo número, o fluids.depth_to_absolute_pressure_pa para la absoluta. Las dos son keyword-only y las dos toman un latitude_deg opcional
TransmissionLossResultPropagationLossResult, con .tl renombrado a .pl

Los números. El informe de conformidad son las mismas 995 comprobaciones contra las mismas normas con los mismos valores esperados que tenía antes de la reorganización, y de cada renombrado de esta página se comprobó que devuelve lo que devolvía su predecesor. Si un resultado se mueve después de que termines de actualizar, eso es un defecto y merece la pena avisar.

Un veredicto es la excepción, y la sección sobre las clases de filtro de más arriba explica cómo: verify_filter_class califica el ancho de banda efectivo del 5.12 y la suma de salidas del 5.16 además de la máscara de la Tabla 1 para la edición de 2014, y la respuesta integrada del filtro del 4.5.3 y la suma del 4.9 con edition="1995", así que la clase de un banco es la más estricta que cumple en los tres. Cada banda se califica a la frecuencia de entrada del banco hasta la mitad de ella, así que los márgenes de la Tabla 1 de una banda diezmada recogen las imágenes que su diezmado pliega sobre ella.

Los bancos de filtros diezmados son la otra: cada banda conserva ahora su frecuencia de Nyquist de proceso en dieciséis veces su borde superior y diezma con un filtro antialiasing dimensionado para una banda atenuada de 120 dB, así que el nivel de una banda se mueve hasta 0,07 dB con ruido blanco, como dice la misma sección.

Lo que dibuja cada .plot() está intacto, y también lo que imprime cada .report() salvo una etiqueta: la ficha del micrófono escribe dB(468) donde escribía dB(CCIR). El contrato de Signal es el que era, ahora cumplido en dos sitios más, los bloques de read_blocks y las formas de onda dentro de cinco resultados. Cada fórmula está intacta también. La 4.0 recolocó los muebles y puso la unidad en la etiqueta; salvo esas dos, no recalculó nada.