-
Notifications
You must be signed in to change notification settings - Fork 2
Feature: add paris fomula to utils #35
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,7 +1,7 @@ | ||
| imkar | ||
| ===== | ||
| imkar.utils | ||
| =========== | ||
|
|
||
| .. automodule:: imkar | ||
| .. automodule:: imkar.utils | ||
| :members: | ||
| :undoc-members: | ||
| :show-inheritance: |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,62 @@ | ||
| """Utilities for imkar.""" | ||
| import numpy as np | ||
| import pyfar as pf | ||
|
|
||
|
|
||
| def paris_formula(coefficients, colatitude_rad, area_weights): | ||
| r""" | ||
| Calculate the random-incidence coefficient | ||
| according to the Paris formula. | ||
|
|
||
| The Paris formula computes the random-incidence coefficient based on | ||
| directional coefficients. It is valid for scattering and absorption | ||
| coefficients and requires equally distributed incident directions | ||
| to get a valid result. | ||
|
|
||
| The implementation follows the Equation 2.53 from [#]_ and is | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can you please add a little general introduction? Something like: 'The Paris formula computes the ... It is valid for... and requires ...'. This is valid for scattering, absorption and generally all direction dependend energetic properties? |
||
| discretized as: | ||
|
|
||
| .. math:: | ||
| c_{rand} = \sum_{\Omega_S} c(\Omega_S) \cdot \cos(\theta) \cdot w | ||
|
|
||
| with the `coefficients` :math:`c`, and the | ||
| area weights :math:`w` from the `incident_directions`. | ||
| :math:`\theta` represents the angle between the | ||
| surface normal and the incident direction. | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is very implicit. Can you please be more verbose here and also add a check for |
||
|
|
||
| Parameters | ||
| ---------- | ||
| coefficients : pyfar.FrequencyData | ||
| coefficients for different incident directions. Its cshape | ||
| needs to be (..., n_incident_directions). | ||
| colatitude_rad : pyfar.Coordinates | ||
| Defines the angle between the surface normal and the sound | ||
| incidence in radiant. Its cshape | ||
| needs to be (n_incident_directions). | ||
| area_weights : np.ndarray | ||
| Area weights for each incident direction. Its cshape | ||
| needs to be (n_incident_directions). | ||
|
|
||
| Returns | ||
| ------- | ||
| random_coefficient : pyfar.FrequencyData | ||
| The random-incidence coefficient. | ||
|
|
||
| References | ||
| ---------- | ||
| .. [#] H. Kuttruff, Room acoustics, Sixth edition. Boca Raton: | ||
| CRC Press/Taylor & Francis Group, 2017. | ||
| """ | ||
| if not isinstance(coefficients, pf.FrequencyData): | ||
| raise ValueError("coefficients has to be FrequencyData") | ||
| if colatitude_rad.shape != area_weights.shape: | ||
| raise ValueError( | ||
| "colatitude_rad and area_weights need to have the same shape.") | ||
|
|
||
| theta = colatitude_rad | ||
| weight = np.cos(theta) * area_weights | ||
| weight = weight[..., np.newaxis] | ||
| norm = np.sum(weight) | ||
| random_coefficient = coefficients*weight/norm | ||
| random_coefficient.freq = np.sum(random_coefficient.freq, axis=-2) | ||
| return random_coefficient | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,60 @@ | ||
| import pytest | ||
| import numpy as np | ||
| import pyfar as pf | ||
|
|
||
| from imkar import utils | ||
|
|
||
|
|
||
| @pytest.mark.parametrize("c_value", [ | ||
| (0), (0.2), (0.5), (0.8), (1)]) | ||
| @pytest.mark.parametrize("frequencies", [ | ||
| ([100, 200]), ([100])]) | ||
| def test_random_constant_coefficient( | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. A little docstring or comments for this test would be nice.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would suggest that all tests start with |
||
| c_value, frequencies): | ||
| incident_directions = pf.Coordinates.from_spherical_colatitude( | ||
| 0, np.linspace(0, np.pi/2, 10), 1) | ||
| incident_directions.weights = np.sin(incident_directions.colatitude) | ||
| shape = np.append(incident_directions.cshape, len(frequencies)) | ||
| coefficient = pf.FrequencyData(np.zeros(shape)+c_value, frequencies) | ||
| c_rand = utils.paris_formula(coefficient, incident_directions) | ||
| np.testing.assert_allclose(c_rand.freq, c_value) | ||
| assert c_rand.comment == 'random-incidence coefficient' | ||
|
|
||
|
|
||
| def test_random_non_constant_coefficient(): | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. docstring and/or comments would be nice. |
||
| incident_directions = pf.samplings.sph_gaussian(10) | ||
| incident_directions = incident_directions[incident_directions.z >= 0] | ||
| incident_cshape = incident_directions.cshape | ||
| s_value = np.arange( | ||
| incident_cshape[0]).reshape(incident_cshape) / incident_cshape[0] | ||
| theta = incident_directions.colatitude | ||
| actual_weight = np.cos(theta) * incident_directions.weights | ||
| actual_weight /= np.sum(actual_weight) | ||
| coefficient = pf.FrequencyData(s_value.reshape((50, 1)), [100]) | ||
| c_rand = utils.paris_formula(coefficient, incident_directions) | ||
| desired = np.sum(s_value*actual_weight) | ||
| np.testing.assert_allclose(c_rand.freq, desired) | ||
|
|
||
|
|
||
| def test_paris_formula_coefficients_type_error(): | ||
| incident_directions = pf.samplings.sph_gaussian(10) | ||
| with pytest.raises( | ||
| ValueError, match="coefficients has to be FrequencyData"): | ||
| utils.paris_formula(np.zeros(10), incident_directions) | ||
|
|
||
|
|
||
| def test_paris_formula_incident_directions_type_error(): | ||
| coefficients = pf.FrequencyData(np.zeros((10, 1)), [100]) | ||
| with pytest.raises( | ||
| ValueError, | ||
| match="incident_directions have to be None or Coordinates"): | ||
| utils.paris_formula(coefficients, None) | ||
|
|
||
|
|
||
| def test_paris_formula_shape_mismatch_error(): | ||
| incident_directions = pf.samplings.sph_gaussian(10) | ||
| coefficients = pf.FrequencyData(np.zeros((5, 1)), [100]) | ||
| with pytest.raises( | ||
| ValueError, | ||
| match="the last dimension of coefficients needs be same as"): | ||
| utils.paris_formula(coefficients, incident_directions) | ||
Uh oh!
There was an error while loading. Please reload this page.