Repository: pyfar/pyfar
Pull request #732 (2024-12-13 13:44:15)

speed of sound calculation


Patch:
diff --git a/docs/api_reference.rst b/docs/api_reference.rst
index b354b1ed..037d4986 100644
--- a/docs/api_reference.rst
+++ b/docs/api_reference.rst
@@ -29,6 +29,7 @@ Modules
 .. toctree::
    :maxdepth: 1
 
+   modules/pyfar.constants
    modules/pyfar.dsp
    modules/pyfar.dsp.fft
    modules/pyfar.dsp.filter
diff --git a/docs/modules/pyfar.constants.rst b/docs/modules/pyfar.constants.rst
new file mode 100644
index 00000000..d75577f0
--- /dev/null
+++ b/docs/modules/pyfar.constants.rst
@@ -0,0 +1,7 @@
+pyfar.constants
+===============
+
+.. automodule:: pyfar.constants
+   :members:
+   :undoc-members:
+   :show-inheritance:
diff --git a/pyfar/__init__.py b/pyfar/__init__.py
index 06680a4e..5c105220 100644
--- a/pyfar/__init__.py
+++ b/pyfar/__init__.py
@@ -22,6 +22,7 @@ from . import io
 from . import dsp
 from . import signals
 from . import utils
+from . import constants
 
 
 __all__ = [
@@ -48,6 +49,7 @@ __all__ = [
     'dsp',
     'signals',
     'utils',
+    'constants',
     'dot',
     'cross',
     ]
diff --git a/pyfar/constants/__init__.py b/pyfar/constants/__init__.py
new file mode 100644
index 00000000..09be3565
--- /dev/null
+++ b/pyfar/constants/__init__.py
@@ -0,0 +1,10 @@
+"""Module for constants and physical properties of air."""
+
+
+from .speed_of_sound import (
+    speed_of_sound_simple,
+)
+
+__all__ = [
+    'speed_of_sound_simple',
+]
diff --git a/pyfar/constants/speed_of_sound.py b/pyfar/constants/speed_of_sound.py
new file mode 100644
index 00000000..c428c167
--- /dev/null
+++ b/pyfar/constants/speed_of_sound.py
@@ -0,0 +1,45 @@
+"""File containing all speed of sound calculation functions."""
+import numpy as np
+
+
+def speed_of_sound_simple(temperature):
+    r"""
+    Calculate the speed of sound in air using a simplified version
+    of the ideal gas law based on the temperature.
+
+    The calculation follows ISO 9613-1 [#]_ (Formula A.5).
+
+    .. math::
+
+        c = 343.2 \cdot \sqrt{\frac{t + 273.15}{t_0 + 273.15}} \mathrm{m/s}
+
+    where:
+        - :math:`t` is the air temperature (°C)
+        - :math:`t_0=20\mathrm{°C}` is the reference air temperature (°C)
+
+    Parameters
+    ----------
+    temperature : float, array_like
+        Temperature in degree Celsius from -20°C to +50°C.
+
+    Returns
+    -------
+    speed_of_sound : float, array_like
+        Speed of sound in air in (m/s).
+
+    References
+    ----------
+    .. [#] ISO 9613-1:1993, Acoustics -- Attenuation of sound during
+           propagation outdoors -- Part 1: Calculation of the absorption of
+           sound by the atmosphere.
+    """
+    # input validation
+    if np.any(np.array(temperature) < -20) or np.any(
+            np.array(temperature) > 50):
+        raise ValueError("Temperature must be between -20°C and +50°C.")
+    # convert to numpy array if necessary
+    temperature = np.array(temperature, dtype=float) if isinstance(
+        temperature, list) else temperature
+
+    t_0 = 20
+    return 343.2*np.sqrt((temperature+273.15)/(t_0+273.15))


Test patch:
diff --git a/tests/test_constants_speed_of_sound.py b/tests/test_constants_speed_of_sound.py
new file mode 100644
index 00000000..2a4d0e7c
--- /dev/null
+++ b/tests/test_constants_speed_of_sound.py
@@ -0,0 +1,36 @@
+import pytest
+import numpy as np
+import pyfar as pf
+import numpy.testing as npt
+
+
+def test_speed_of_sound_simple_scalar():
+    temperature = 20
+    expected = 343.2 * np.sqrt((temperature + 273.15) / 293.15)
+    result = pf.constants.speed_of_sound_simple(temperature)
+    npt.assert_array_equal(result, expected)
+
+@pytest.mark.parametrize("temperature", [
+        np.array([0, 10, 20, 30]),
+        [0, 10, 20, 30],
+])
+def test_speed_of_sound_simple_array(temperature):
+    expected = 343.2 * np.sqrt((np.array(temperature) + 273.15) / 293.15)
+    result = pf.constants.speed_of_sound_simple(temperature)
+    npt.assert_array_equal(result, expected)
+
+def test_speed_of_sound_simple_below_range():
+    with pytest.raises(
+            ValueError, match="Temperature must be between"):
+        pf.constants.speed_of_sound_simple(-21)
+
+def test_speed_of_sound_simple_above_range():
+    with pytest.raises(
+            ValueError, match="Temperature must be between"):
+        pf.constants.speed_of_sound_simple(51)
+
+def test_speed_of_sound_simple_edge_cases():
+    temperatures = [-20, 50]
+    expected = 343.2 * np.sqrt((np.array(temperatures) + 273.15) / 293.15)
+    result = pf.constants.speed_of_sound_simple(temperatures)
+    npt.assert_array_equal(result, expected)