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í.

Aire húmedo

Normas aplicables: IEC 61094

Una calibración por reciprocidad necesita conocer el aire en el que se hace con cinco o seis cifras, y por eso la norma que define esa calibración lleva también el modelo de aire húmedo mejor enunciado de la literatura acústica. El Anexo F es ese modelo, y esta página es lo que la biblioteca hace con él.

from phonometry import fluids
f = fluids.air(
temperature_c=23.0,
static_pressure_pa=101325.0,
relative_humidity_percent=50.0,
)
print(f.density) # 1.1860847889882964 kg/m3
print(f.speed_of_sound) # 345.86651725321 m/s
print(f.characteristic_impedance) # 410.2270151343906 Pa.s/m

Ésas son las condiciones de la primera fila de la Tabla F.1, y los números son los que imprime el anexo. Cada una de las diez cifras que tabula vuelve dentro del redondeo de su última posición impresa.

Diagrama de flujo del modelo de aire húmedo del anexo, leído de arriba abajo. Arriba, cuatro cajas de entrada: la temperatura t en grados Celsius, válida de 15 a 27 grados; la presión estática ps en pascales, válida de 60 a 110 kilopascales; la humedad relativa H, válida del 10 al 90 por ciento; y, en una caja discontinua, la fracción molar de dióxido de carbono xc, 0,000 4 si no se mide, con una línea debajo que dice que entra en la densidad, la velocidad del sonido y la relación de calores específicos. Cada condición medida llega solo a los pasos que nombra su propia expresión, con una línea para cada una: la temperatura baja a la presión de vapor de saturación psv de la izquierda, una exponencial en la temperatura en kelvin con cuatro coeficientes de la Tabla F.2, y cruza hasta el factor de realce f de la derecha, con tres; la presión estática cruza también hasta f y baja a la fracción molar de vapor de agua xw, entre ambos; y la humedad relativa baja solo a xw. psv y f apuntan a xw desde cada lado. De xw sale una línea que baja a las filas de debajo, rotulada con las siete magnitudes en las que entra. En la columna izquierda, el factor de compresibilidad Z, con nueve coeficientes, apunta a la densidad rho en el centro, y debajo la conductividad térmica ka apunta a la difusividad térmica alfa t, que toma además la densidad desde arriba y el calor específico a presión constante Cp desde abajo. La columna derecha reúne la velocidad del sonido a frecuencia cero c0 y la relación de calores específicos kappa, con dieciséis coeficientes cada una, y la viscosidad eta, con seis. Las cinco propiedades que tabula la Tabla F.1 llevan borde verde y sus valores para 23 grados, 101 325 pascales y 50 por ciento: una densidad de 1,186 084 8 kilogramos por metro cúbico, una velocidad del sonido de 345,866 52 metros por segundo, una relación de 1,400 757 3, una viscosidad de 1,826 566 por diez a la menos cinco pascales segundo y una difusividad de 2,115 317 por diez a la menos cinco metros cuadrados por segundo. La conductividad y el calor específico llevan borde rojo discontinuo: vuelven de la misma llamada, pero el apartado F.6 solo las escribe y ninguna columna de la Tabla F.1 imprime un valor para ellas. Una leyenda nombra los tres bordes. Una caja al pie escribe la ecuación de la densidad, el factor de compresibilidad, la fracción molar de vapor de agua y alfa t como ka entre rho por Cp. Tres líneas debajo dicen qué cajas toman la temperatura en kelvin; dan las incertidumbres típicas relativas que da el anexo para sus ecuaciones, 22 partes por millón para la densidad, 3 diezmilésimas para c0 y 3,2 diezmilésimas para kappa; y señalan que la dispersión mueve la velocidad del sonido menos que esa incertidumbreDiagrama de flujo del modelo de aire húmedo del anexo, leído de arriba abajo. Arriba, cuatro cajas de entrada: la temperatura t en grados Celsius, válida de 15 a 27 grados; la presión estática ps en pascales, válida de 60 a 110 kilopascales; la humedad relativa H, válida del 10 al 90 por ciento; y, en una caja discontinua, la fracción molar de dióxido de carbono xc, 0,000 4 si no se mide, con una línea debajo que dice que entra en la densidad, la velocidad del sonido y la relación de calores específicos. Cada condición medida llega solo a los pasos que nombra su propia expresión, con una línea para cada una: la temperatura baja a la presión de vapor de saturación psv de la izquierda, una exponencial en la temperatura en kelvin con cuatro coeficientes de la Tabla F.2, y cruza hasta el factor de realce f de la derecha, con tres; la presión estática cruza también hasta f y baja a la fracción molar de vapor de agua xw, entre ambos; y la humedad relativa baja solo a xw. psv y f apuntan a xw desde cada lado. De xw sale una línea que baja a las filas de debajo, rotulada con las siete magnitudes en las que entra. En la columna izquierda, el factor de compresibilidad Z, con nueve coeficientes, apunta a la densidad rho en el centro, y debajo la conductividad térmica ka apunta a la difusividad térmica alfa t, que toma además la densidad desde arriba y el calor específico a presión constante Cp desde abajo. La columna derecha reúne la velocidad del sonido a frecuencia cero c0 y la relación de calores específicos kappa, con dieciséis coeficientes cada una, y la viscosidad eta, con seis. Las cinco propiedades que tabula la Tabla F.1 llevan borde verde y sus valores para 23 grados, 101 325 pascales y 50 por ciento: una densidad de 1,186 084 8 kilogramos por metro cúbico, una velocidad del sonido de 345,866 52 metros por segundo, una relación de 1,400 757 3, una viscosidad de 1,826 566 por diez a la menos cinco pascales segundo y una difusividad de 2,115 317 por diez a la menos cinco metros cuadrados por segundo. La conductividad y el calor específico llevan borde rojo discontinuo: vuelven de la misma llamada, pero el apartado F.6 solo las escribe y ninguna columna de la Tabla F.1 imprime un valor para ellas. Una leyenda nombra los tres bordes. Una caja al pie escribe la ecuación de la densidad, el factor de compresibilidad, la fracción molar de vapor de agua y alfa t como ka entre rho por Cp. Tres líneas debajo dicen qué cajas toman la temperatura en kelvin; dan las incertidumbres típicas relativas que da el anexo para sus ecuaciones, 22 partes por millón para la densidad, 3 diezmilésimas para c0 y 3,2 diezmilésimas para kappa; y señalan que la dispersión mueve la velocidad del sonido menos que esa incertidumbre

La cadena que recorre la llamada de arriba: el vapor de agua va primero y todas las magnitudes posteriores dependen de él, y las cinco con valor impreso llevan el que da la Tabla F.1 para esas condiciones.

La biblioteca pide la temperatura y supone las otras dos. No es pereza en una dirección y rigor en la otra: se sigue de cuánto vale cada una. Barriendo cada condición por su rango plausible, con las propias ecuaciones del anexo:

CondiciónBarrida entreMueve la densidad un
Presión estática80 y 105 kPa25 %
Temperatura15 y 27 °C4,5 %
Humedad relativa0 y 100 %1,05 %

La presión domina, y además es la que la gente cree saber ya. Un emplazamiento de ensayo a 1000 metros está a unos 90 kPa. Eso es un 11 % por debajo de la atmósfera estándar que la biblioteca supondría, y deja la densidad un 13 % alta, que en un nivel de potencia sonora es medio decibelio sin nada en la página que lo diga.

La humedad es el caso contrario. Es la que menos mueve la densidad, y casi nadie conoce la suya sin medirla.

Así que las dos se suponen y las dos se anuncian, una sola vez, en el mismo aviso:

import warnings
from phonometry import fluids
with warnings.catch_warnings(record=True) as caught:
warnings.simplefilter("always")
fluids.air(temperature_c=20.0)
print(caught[0].category.__name__) # FluidAssumptionWarning
print(str(caught[0].message))
# air() assumed 101325 Pa and 50 % relative humidity. The pressure is the one
# worth measuring: a site 1000 m up sits near 90 kPa, and taking it for one
# standard atmosphere puts the density about 13 % high. The whole span of
# humidity, 0 % to 100 %, is worth about 1 % of the density. Pass the
# conditions to silence this.

Un aviso y no dos, porque quien no pasó ninguna de las dos tiene una cosa que arreglar, no dos.

Pasar las dos es silencioso, que es la propiedad que debe recibir quien midió su aire. Python muestra el aviso una vez por sitio de llamada, así que explorar en un intérprete no queda ahogado en él, y cuelga de PhonometryWarning, de modo que una sola regla lo convierte en error para quien prefiera que sus ejecuciones fallen en vez de avisar:

import warnings
from phonometry import PhonometryWarning
warnings.filterwarnings("error", category=PhonometryWarning)

La temperatura no tiene valor por defecto en absoluto. Es la única condición que quien llama ha medido de verdad, y no hay ningún valor defendible que inventarle.

La fracción de dióxido de carbono sí se pone por defecto, en silencio, a 0,000 4. Eso no es una suposición sobre el aire de nadie: el apartado F.2 la nombra como el valor a usar en condiciones de laboratorio a falta de una medida, así que la biblioteca está citando el anexo y no rellenando un hueco.

La Tabla F.1 imprime cinco magnitudes. El apartado F.6 da expresiones para dos más, y tres se siguen por identidad de las anteriores:

f.density # impresa en la Tabla F.1
f.speed_of_sound # impresa, a frecuencia cero
f.heat_capacity_ratio # impresa
f.viscosity # impresa
f.thermal_diffusivity # impresa
f.thermal_conductivity # expresión del apartado F.6
f.specific_heat_capacity # expresión del apartado F.6
f.characteristic_impedance # rho c
f.prandtl_number # eta / (rho alpha_t)
f.kinematic_viscosity # eta / rho

Una magnitud que este modelo no determinó lanza en vez de devolver un número que nadie imprimió. El error nombra el modelo, lo que sí determina y las dos salidas:

from phonometry.fluids import Fluid, FluidPropertyUnavailable
medido = Fluid(
temperature_c=10.0,
static_pressure_pa=1.1e6,
composition={"salinity_psu": 35.0},
model="a density and a sound speed, measured",
validity="",
properties={"density": 1027.0, "speed_of_sound": 1491.0},
)
try:
medido.heat_capacity_ratio
except FluidPropertyUnavailable as exc:
print(exc)
# 'a density and a sound speed, measured' does not determine
# 'heat_capacity_ratio'; it determines density, speed_of_sound. Supply the
# quantity yourself, or use a model that prints it.

Con un solo fluido en la biblioteca eso parece pedantería, y deja de parecerlo con dos: el agua de mar no tiene relación de calores específicos que dar, y un Fluid que se la inventara estaría entregando un número con la forma de una medida y nada de su sustancia.

Tres atributos no son magnitudes y nunca lanzan. Son el registro de dónde salieron los números:

f.properties # exactamente lo que este modelo determinó
f.model # 'IEC 61094-2:2009 Annex F (CIPM-2007)'
f.composition # la humedad y el CO2 con los que se calculó

El dominio impreso, y la diferencia entre un ajuste y un hecho

Sección titulada «El dominio impreso, y la diferencia entre un ajuste y un hecho»

El Anexo F enuncia, en su propia página, dónde se validaron sus ecuaciones: 15 °C a 27 °C, 60 kPa a 110 kPa y 10 % a 90 % de humedad relativa. No enuncia ningún rango para la fracción de dióxido de carbono.

Fuera de esa caja la biblioteca avisa y sigue respondiendo:

import warnings
from phonometry import fluids
with warnings.catch_warnings(record=True) as caught:
warnings.simplefilter("always")
hot = fluids.air(temperature_c=60.0, static_pressure_pa=101325.0,
relative_humidity_percent=50.0)
print(caught[0].category.__name__) # FluidWarning
print("temperature 60 degC (stated 15 to 27)" in str(caught[0].message)) # True
print(round(hot.speed_of_sound, 2)) # 371.52

La distinción importa y es la regla que sigue toda la biblioteca. El aire a 60 °C dentro de un conducto existe; un ajuste más allá de su rango validado sigue siendo aritmética, y rechazarlo sería rechazar una medida real. Lo que sí se rechaza es un estado que no puede darse, y esa es la lista completa: una temperatura igual o por debajo del cero absoluto, una presión no positiva, una humedad relativa fuera del 0 % al 100 %, una fracción molar de dióxido de carbono fuera de 0 a 1, y un valor no finito en cualquiera de las cuatro. Cada una lanza ValueError nombrando el argumento.

El oráculo es la Tabla F.1: cinco magnitudes en dos conjuntos de condiciones, diez cifras en total. La tolerancia no se elige. Un valor está enunciado con media unidad de la última posición hasta la que se imprimió, así que eso es lo que significa reproducirlo, y el test deriva esa cota de la cadena impresa en vez de llevar una constante que alguien escogió. Lo que fija la cota es esa posición, no un recuento de cifras significativas: 1,186 084 8 tiene ocho y 2,115 317 x 10^-5 siete, y la misma regla lee las dos:

La tolerancia, derivada de lo impreso
from decimal import Decimal
def half_of_the_last_printed_figure(printed: str) -> float:
"""Media unidad de la última posición que imprimió el anexo."""
exponent = int(Decimal(printed).as_tuple().exponent)
return float(Decimal(5) * Decimal(10) ** (exponent - 1))
half_of_the_last_printed_figure("1.1860848") # 5e-08
half_of_the_last_printed_figure("2.115317e-5") # 5e-12

Nadie puede aflojarla sin darse cuenta, y una edición futura que cite más cifras la aprieta sola.

Tres filas de conformidad registran cuánto de ese margen consume cada conjunto: un 55 % el primero, un 96 % el segundo y nada la identidad que ata la conductividad térmica y el calor específico a la difusividad impresa entre las dos. El 96 % es la densidad del segundo conjunto, y es el redondeo del propio anexo y no holgura de la biblioteca. Publicarlo como porcentaje significa que si algún cambio lo empuja fuera, se verá venir.

Pasar un Fluid a una función de medida es asunto de los paquetes que lo consumen, y viene con una regla que conviene enunciar aquí: donde una norma fija el aire de su propio procedimiento, usar un aire mejor es apartarse de la norma, y la biblioteca lo dice en vez de dejar que una medida deje de reproducir en silencio el documento que cita.

Por eso las fórmulas de aire simplificadas de ISO 10534-2, ASTM E2611 e ISO 17497-1 se quedan en sus módulos con sus cláusulas, y por eso el número de Prandtl 0,71 de Johnson-Champoux-Allard sigue siendo 0,71 aunque el aire en ese estado tenga 0,728.

Dos estados de ese aire se publican como constantes, porque no tiene sentido que quien llama los escriba dos veces. materials.ANNEX_A_AIR es el estado de referencia que fija el anexo A.3 de la ISO 9053-2:2020 para una medida de resistencia al flujo, 23 °C y 50 % de humedad relativa a una atmósfera estándar, evaluado con el mismo modelo CIPM-2007 que cualquier otro aire. simulation.SIMULATION_AIR es el medio por defecto del solver, y a propósito no es eso: 343 m/s y 1,2 kg/m³ planos, el par redondo con el que se monta una malla de diferencias finitas, llevado como Fluid para que una simulación diga en qué aire se hizo en lugar de dejar dos floats sueltos en una llamada.