Skip to content

Commit 935ab12

Browse files
committed
add paris fomula
1 parent 5ffce44 commit 935ab12

File tree

5 files changed

+136
-4
lines changed

5 files changed

+136
-4
lines changed

docs/api_reference.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ Modules
1616
.. toctree::
1717
:maxdepth: 1
1818

19-
modules/imkar
19+
modules/imkar.utils
2020

2121

2222
.. _examples gallery: https://pyfar-gallery.readthedocs.io/en/latest/examples_gallery.html
Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
imkar
2-
=====
1+
imkar.utils
2+
===========
33

4-
.. automodule:: imkar
4+
.. automodule:: imkar.utils
55
:members:
66
:undoc-members:
77
:show-inheritance:

imkar/__init__.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,10 @@
55
__author__ = """The pyfar developers"""
66
__email__ = ''
77
__version__ = '0.1.0'
8+
9+
10+
from . import utils
11+
12+
__all__ = [
13+
'utils',
14+
]

imkar/utils.py

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
"""Utilities for imkar."""
2+
import numpy as np
3+
import pyfar as pf
4+
5+
6+
def paris_formula(coefficients, incident_directions):
7+
r"""
8+
Calculate the random-incidence coefficient
9+
according to Paris formula.
10+
11+
The implementation follows the Equation 2.53 from [#]_ and is
12+
discretized as:
13+
14+
.. math::
15+
c_{rand} = \sum_{\Omega_S} c(\Omega_S) \cdot |\Omega_S \cdot n| \cdot w
16+
17+
with the ``coefficients`` :math:`c`, and the
18+
area weights :math:`w` from the ``incident_directions``.
19+
:math:`|\Omega_S \cdot n|` represent the cosine of the angle between the
20+
surface normal and the incident direction.
21+
22+
.. note::
23+
The incident directions should be
24+
equally distributed to get a valid result.
25+
26+
Parameters
27+
----------
28+
coefficients : pyfar.FrequencyData
29+
coefficients for different incident directions. Its cshape
30+
needs to be (..., n_incident_directions)
31+
incident_directions : pyfar.Coordinates
32+
Defines the incidence directions of each `coefficients` in a
33+
Coordinates object. Its cshape needs to be (n_incident_directions). In
34+
sperical coordinates the radii needs to be constant. The weights need
35+
to reflect the area weights.
36+
37+
Returns
38+
-------
39+
random_coefficient : pyfar.FrequencyData
40+
The random-incidence scattering coefficient.
41+
42+
References
43+
----------
44+
.. [#] H. Kuttruff, Room acoustics, Sixth edition. Boca Raton:
45+
CRC Press/Taylor & Francis Group, 2017.
46+
"""
47+
if not isinstance(coefficients, pf.FrequencyData):
48+
raise ValueError("coefficients has to be FrequencyData")
49+
if not isinstance(incident_directions, pf.Coordinates):
50+
raise ValueError("incident_directions have to be None or Coordinates")
51+
if incident_directions.cshape[0] != coefficients.cshape[-1]:
52+
raise ValueError(
53+
"the last dimension of coefficients needs be same as "
54+
"the incident_directions.cshape.")
55+
56+
theta = incident_directions.colatitude
57+
weight = np.cos(theta) * incident_directions.weights
58+
norm = np.sum(weight)
59+
coefficients_freq = np.swapaxes(coefficients.freq, -1, -2)
60+
random_coefficient = pf.FrequencyData(
61+
np.sum(coefficients_freq*weight/norm, axis=-1),
62+
coefficients.frequencies,
63+
comment='random-incidence coefficient',
64+
)
65+
return random_coefficient

tests/test_utils.py

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import pytest
2+
import numpy as np
3+
import pyfar as pf
4+
5+
from imkar import utils
6+
7+
8+
@pytest.mark.parametrize("c_value", [
9+
(0), (0.2), (0.5), (0.8), (1)])
10+
@pytest.mark.parametrize("frequencies", [
11+
([100, 200]), ([100])])
12+
def test_random_constant_coefficient(
13+
c_value, frequencies):
14+
incident_directions = pf.Coordinates.from_spherical_colatitude(
15+
0, np.linspace(0, np.pi/2, 10), 1)
16+
incident_directions.weights = np.sin(incident_directions.colatitude)
17+
shape = np.append(incident_directions.cshape, len(frequencies))
18+
coefficient = pf.FrequencyData(np.zeros(shape)+c_value, frequencies)
19+
c_rand = utils.paris_formula(coefficient, incident_directions)
20+
np.testing.assert_allclose(c_rand.freq, c_value)
21+
assert c_rand.comment == 'random-incidence coefficient'
22+
23+
24+
def test_random_non_constant_coefficient():
25+
incident_directions = pf.samplings.sph_gaussian(10)
26+
incident_directions = incident_directions[incident_directions.z >= 0]
27+
incident_cshape = incident_directions.cshape
28+
s_value = np.arange(
29+
incident_cshape[0]).reshape(incident_cshape) / incident_cshape[0]
30+
theta = incident_directions.colatitude
31+
actual_weight = np.cos(theta) * incident_directions.weights
32+
actual_weight /= np.sum(actual_weight)
33+
coefficient = pf.FrequencyData(s_value.reshape((50, 1)), [100])
34+
c_rand = utils.paris_formula(coefficient, incident_directions)
35+
desired = np.sum(s_value*actual_weight)
36+
np.testing.assert_allclose(c_rand.freq, desired)
37+
38+
39+
def test_paris_formula_coefficients_type_error():
40+
incident_directions = pf.samplings.sph_gaussian(10)
41+
with pytest.raises(
42+
ValueError, match="coefficients has to be FrequencyData"):
43+
utils.paris_formula(np.zeros(10), incident_directions)
44+
45+
46+
def test_paris_formula_incident_directions_type_error():
47+
coefficients = pf.FrequencyData(np.zeros((10, 1)), [100])
48+
with pytest.raises(
49+
ValueError,
50+
match="incident_directions have to be None or Coordinates"):
51+
utils.paris_formula(coefficients, None)
52+
53+
54+
def test_paris_formula_shape_mismatch_error():
55+
incident_directions = pf.samplings.sph_gaussian(10)
56+
coefficients = pf.FrequencyData(np.zeros((5, 1)), [100])
57+
with pytest.raises(
58+
ValueError,
59+
match="the last dimension of coefficients needs be same as"):
60+
utils.paris_formula(coefficients, incident_directions)

0 commit comments

Comments
 (0)