From f75b2f9a6000de5084fa41423c8d082aa0eeb371 Mon Sep 17 00:00:00 2001 From: Ali Cetin Date: Wed, 9 Apr 2025 23:35:40 +0200 Subject: [PATCH 1/8] init --- src/waveresponse/_core.py | 29 ++++++++++++++++++++++++++++- tests/test_core.py | 13 +++++++++++++ 2 files changed, 41 insertions(+), 1 deletion(-) diff --git a/src/waveresponse/_core.py b/src/waveresponse/_core.py index a9cf236..5d62d7d 100644 --- a/src/waveresponse/_core.py +++ b/src/waveresponse/_core.py @@ -4,8 +4,9 @@ from numbers import Number import numpy as np -from scipy.integrate import trapezoid +from scipy.integrate import trapezoid, quad from scipy.interpolate import RegularGridInterpolator as RGI +from scipy.optimize import root_scalar from scipy.special import gamma from ._utils import _robust_modulus, complex_to_polar, polar_to_complex @@ -2412,6 +2413,32 @@ def _spread_fun(self, omega, theta): """ raise NotImplementedError() + def discrete_directions(self, n, direction_offset=0.0): + if self._degrees: + x_lb = -180.0 + x_ub = 180.0 + periodicity = 360.0 + else: + x_lb = -np.pi + x_ub = np.pi + periodicity = 2.0 * np.pi + + total_area = quad(lambda theta: self(None, theta), x_lb, x_ub)[0] + half_bin_edges = np.empty(2 * n - 1) + + x_prev = x_lb + for i in range(1, 2 * n): + target_area = total_area * i / (2 * n) + res = root_scalar( + lambda x: quad(lambda theta: self(None, theta), x_lb, x)[0] + - target_area, + bracket=[x_prev, x_ub], + ) + x_prev = res.root + half_bin_edges[i - 1] = x_prev + + return _robust_modulus(half_bin_edges[::2] + direction_offset, periodicity) + class CosineHalfSpreading(BaseSpreading): """ diff --git a/tests/test_core.py b/tests/test_core.py index fcc51e5..f1c600c 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -5994,3 +5994,16 @@ def test__call__degrees(self, f, d, s, spread_expect): def test__call__radians(self, f, d, s, spread_expect): spreading = CosineHalfSpreading(s, degrees=False) assert spreading(f, d) == pytest.approx(spread_expect) + + @pytest.mark.parametrize("n,dirs_expect", [ + [2, np.array([-13.2, 13.2])], + [3, np.array([-18.8, 0.0, 18.8])], + [4, np.array([-22.3, -6.3, 6.3, 22.3])], + [5, np.array([-24.8, -10.3, 0.0, 10.3, 24.8])] + ]) + def test_discrete_directions_no_offset(self , n, dirs_expect): + spreading = CosineHalfSpreading(4, degrees=True) + np.testing.assert_allclose((spreading.discrete_directions(n) + 1e-8) % 360.0, dirs_expect % 360.0, rtol=0.0, atol=0.1) + + spreading = CosineHalfSpreading(4, degrees=False) + np.testing.assert_allclose(np.degrees(spreading.discrete_directions(n) + 1e-8) % 360.0, dirs_expect % 360.0, rtol=0.0, atol=0.1) \ No newline at end of file From a35eee4d40e59dc5135d1ac073543420b08be903 Mon Sep 17 00:00:00 2001 From: Ali Cetin Date: Wed, 9 Apr 2025 23:40:58 +0200 Subject: [PATCH 2/8] add tests --- tests/test_core.py | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/tests/test_core.py b/tests/test_core.py index f1c600c..3b94256 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -6006,4 +6006,30 @@ def test_discrete_directions_no_offset(self , n, dirs_expect): np.testing.assert_allclose((spreading.discrete_directions(n) + 1e-8) % 360.0, dirs_expect % 360.0, rtol=0.0, atol=0.1) spreading = CosineHalfSpreading(4, degrees=False) - np.testing.assert_allclose(np.degrees(spreading.discrete_directions(n) + 1e-8) % 360.0, dirs_expect % 360.0, rtol=0.0, atol=0.1) \ No newline at end of file + np.testing.assert_allclose(np.degrees(spreading.discrete_directions(n) + 1e-8) % 360.0, dirs_expect % 360.0, rtol=0.0, atol=0.1) + + @pytest.mark.parametrize("n,dirs_expect", [ + [2, np.array([-3.2, 23.2])], + [3, np.array([-8.8, 10.0, 28.8])], + [4, np.array([-12.3, 3.7, 16.3, 32.3])], + [5, np.array([-14.8, -0.3, 10.0, 20.3, 34.8])] + ]) + def test_discrete_directions_offset(self , n, dirs_expect): + spreading = CosineHalfSpreading(4, degrees=True) + np.testing.assert_allclose((spreading.discrete_directions(n, 10.0) + 1e-8) % 360.0, dirs_expect % 360.0, rtol=0.0, atol=0.1) + + spreading = CosineHalfSpreading(4, degrees=False) + np.testing.assert_allclose(np.degrees(spreading.discrete_directions(n, np.radians(10)) + 1e-8) % 360.0, dirs_expect % 360.0, rtol=0.0, atol=0.1) + + @pytest.mark.parametrize("n,dirs_expect", [ + [2, np.array([-23.2, 3.2])], + [3, np.array([-28.8, -10.0, 8.8])], + [4, np.array([-32.3, -16.3, -3.7, 12.3])], + [5, np.array([-34.8, -20.3, -10.0, 0.3, 14.8])] + ]) + def test_discrete_directions_neg_offset(self , n, dirs_expect): + spreading = CosineHalfSpreading(4, degrees=True) + np.testing.assert_allclose((spreading.discrete_directions(n, -10.0) + 1e-8) % 360.0, dirs_expect % 360.0, rtol=0.0, atol=0.1) + + spreading = CosineHalfSpreading(4, degrees=False) + np.testing.assert_allclose(np.degrees(spreading.discrete_directions(n, np.radians(-10)) + 1e-8) % 360.0, dirs_expect % 360.0, rtol=0.0, atol=0.1) \ No newline at end of file From f31a68cb9a3c624c4a77b65d8e66b0de2b429b8e Mon Sep 17 00:00:00 2001 From: petter-indrevoll-4ss Date: Thu, 10 Apr 2025 15:43:22 +0200 Subject: [PATCH 3/8] Fix: added docstring, added tests, added raising error, compared with OF --- src/waveresponse/_core.py | 40 +++++- tests/test_core.py | 283 ++++++++++++++++++++++++++++++++++---- 2 files changed, 288 insertions(+), 35 deletions(-) diff --git a/src/waveresponse/_core.py b/src/waveresponse/_core.py index 5d62d7d..b35e0b0 100644 --- a/src/waveresponse/_core.py +++ b/src/waveresponse/_core.py @@ -2414,6 +2414,25 @@ def _spread_fun(self, omega, theta): raise NotImplementedError() def discrete_directions(self, n, direction_offset=0.0): + """ + Find the equal energy directions for a given direction and spreading function. + The function uses the spreading function to find the directions + needed to use an equal energy approach similarly to how OrcaFlex does it. + + Parameters + ---------- + n : int + Number of equal energy directions to find.e + direction_offset : float + Direction of the peak wave energy. + degrees : bool, default False + If True, dirp is in degrees. If False, dirp is in radians. + + Returns + ------- + np.array + Numpy array of equal energy directions in radians or degrees (same convention as input). + """ if self._degrees: x_lb = -180.0 x_ub = 180.0 @@ -2423,17 +2442,26 @@ def discrete_directions(self, n, direction_offset=0.0): x_ub = np.pi periodicity = 2.0 * np.pi - total_area = quad(lambda theta: self(None, theta), x_lb, x_ub)[0] + try: + total_area = quad(lambda theta: self(None, theta), x_lb, x_ub)[0] + except Exception as e: + raise RuntimeError( + f"Failed to calculate total area under the spreading function: {e}" + ) + half_bin_edges = np.empty(2 * n - 1) x_prev = x_lb for i in range(1, 2 * n): target_area = total_area * i / (2 * n) - res = root_scalar( - lambda x: quad(lambda theta: self(None, theta), x_lb, x)[0] - - target_area, - bracket=[x_prev, x_ub], - ) + try: + res = root_scalar( + lambda x: quad(lambda theta: self(None, theta), x_lb, x)[0] + - target_area, + bracket=[x_prev, x_ub], + ) + except Exception as e: + raise RuntimeError(f"Failed to calculate root for direction {i}: {e}") x_prev = res.root half_bin_edges[i - 1] = x_prev diff --git a/tests/test_core.py b/tests/test_core.py index 3b94256..619c4e2 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -5879,6 +5879,138 @@ def test_independent_of_frequency(self): assert len(np.unique(np.array(spread_out_list))) == 1 + @pytest.mark.parametrize( + "n,dirs_expect", + [ + [2, np.array([-26.4, 26.4])], + [3, np.array([-37.6, 0.0, 37.6])], + [4, np.array([-44.6, -12.5, 12.5, 44.6])], + [5, np.array([-49.5, -20.5, 0.0, 20.5, 49.5])], + ], + ) + def test_discrete_directions_no_offset_full(self, n, dirs_expect): + spreading = CosineFullSpreading(4, degrees=True) + np.testing.assert_allclose( + (spreading.discrete_directions(n) + 1e-8) % 360.0, + dirs_expect % 360.0, + rtol=0.0, + atol=0.1, + ) + + spreading = CosineFullSpreading(4, degrees=False) + np.testing.assert_allclose( + np.degrees(spreading.discrete_directions(n) + 1e-8) % 360.0, + dirs_expect % 360.0, + rtol=0.0, + atol=0.1, + ) + + @pytest.mark.parametrize( + "n,dirs_expect", + [ + [2, np.array([-16.4, 36.4])], + [3, np.array([-27.6, 10.0, 47.6])], + [4, np.array([-34.6, -2.5, 22.5, 54.6])], + [5, np.array([-39.5, -10.5, 10.0, 30.5, 59.5])], + ], + ) + def test_discrete_directions_offset_full(self, n, dirs_expect): + spreading = CosineFullSpreading(4, degrees=True) + np.testing.assert_allclose( + (spreading.discrete_directions(n, 10.0) + 1e-8) % 360.0, + dirs_expect % 360.0, + rtol=0.0, + atol=0.1, + ) + + spreading = CosineFullSpreading(4, degrees=False) + np.testing.assert_allclose( + np.degrees(spreading.discrete_directions(n, np.radians(10)) + 1e-8) % 360.0, + dirs_expect % 360.0, + rtol=0.0, + atol=0.1, + ) + + @pytest.mark.parametrize( + "n,dirs_expect", + [ + [2, np.array([-36.4, 16.4])], + [3, np.array([-47.6, -10.0, 27.6])], + [4, np.array([-54.6, -22.5, 2.5, 34.6])], + [5, np.array([-59.5, -30.5, -10.0, 10.5, 39.5])], + ], + ) + def test_discrete_directions_neg_offset_full(self, n, dirs_expect): + spreading = CosineFullSpreading(4, degrees=True) + np.testing.assert_allclose( + (spreading.discrete_directions(n, -10.0) + 1e-8) % 360.0, + dirs_expect % 360.0, + rtol=0.0, + atol=0.1, + ) + + spreading = CosineFullSpreading(4, degrees=False) + np.testing.assert_allclose( + np.degrees(spreading.discrete_directions(n, np.radians(-10)) + 1e-8) + % 360.0, + dirs_expect % 360.0, + rtol=0.0, + atol=0.1, + ) + + @pytest.mark.parametrize( + "n,dirs_expect", + [ + [2, np.array([332.6, 385.4])], + [3, np.array([321.4, 359.0, 396.6])], + [4, np.array([314.4, 346.5, 371.5, 403.6])], + [5, np.array([309.5, 338.5, 359.0, 379.5, 408.5])], + ], + ) + def test_discrete_directions_large_offset_full(self, n, dirs_expect): + spreading = CosineFullSpreading(4, degrees=True) + np.testing.assert_allclose( + (spreading.discrete_directions(n, 359) + 1e-8) % 360.0, + dirs_expect % 360.0, + rtol=0.0, + atol=0.1, + ) + + spreading = CosineFullSpreading(4, degrees=False) + np.testing.assert_allclose( + np.degrees(spreading.discrete_directions(n, np.radians(359)) + 1e-8) + % 360.0, + dirs_expect % 360.0, + rtol=0.0, + atol=0.1, + ) + + @pytest.mark.parametrize( + "n,dirs_expect", + [ + [2, np.array([-11.0, 31.0])], + [3, np.array([-20.0, 10.0, 40.0])], + [4, np.array([-25.6, 0.1, 19.9, 45.6])], + [5, np.array([-29.5, -6.3, 10.0, 26.3, 49.5])], + ], + ) + def test_discrete_directions_other_s_full(self, n, dirs_expect): + spreading = CosineFullSpreading(6.5, degrees=True) + np.testing.assert_allclose( + (spreading.discrete_directions(n, 10) + 1e-8) % 360.0, + dirs_expect % 360.0, + rtol=0.0, + atol=0.1, + ) + + spreading = CosineFullSpreading(6.5, degrees=False) + np.testing.assert_allclose( + np.degrees(spreading.discrete_directions(n, np.radians(10)) + 1e-8) % 360.0, + dirs_expect % 360.0, + rtol=0.0, + atol=0.1, + ) + class Test_CosineHalfSpreading: def test__init__(self): @@ -5995,41 +6127,134 @@ def test__call__radians(self, f, d, s, spread_expect): spreading = CosineHalfSpreading(s, degrees=False) assert spreading(f, d) == pytest.approx(spread_expect) - @pytest.mark.parametrize("n,dirs_expect", [ - [2, np.array([-13.2, 13.2])], - [3, np.array([-18.8, 0.0, 18.8])], - [4, np.array([-22.3, -6.3, 6.3, 22.3])], - [5, np.array([-24.8, -10.3, 0.0, 10.3, 24.8])] - ]) - def test_discrete_directions_no_offset(self , n, dirs_expect): + @pytest.mark.parametrize( + "n,dirs_expect", + [ + [2, np.array([-13.2, 13.2])], + [3, np.array([-18.8, 0.0, 18.8])], + [4, np.array([-22.3, -6.3, 6.3, 22.3])], + [5, np.array([-24.8, -10.3, 0.0, 10.3, 24.8])], + ], + ) + def test_discrete_directions_no_offset(self, n, dirs_expect): spreading = CosineHalfSpreading(4, degrees=True) - np.testing.assert_allclose((spreading.discrete_directions(n) + 1e-8) % 360.0, dirs_expect % 360.0, rtol=0.0, atol=0.1) + np.testing.assert_allclose( + (spreading.discrete_directions(n) + 1e-8) % 360.0, + dirs_expect % 360.0, + rtol=0.0, + atol=0.1, + ) spreading = CosineHalfSpreading(4, degrees=False) - np.testing.assert_allclose(np.degrees(spreading.discrete_directions(n) + 1e-8) % 360.0, dirs_expect % 360.0, rtol=0.0, atol=0.1) - - @pytest.mark.parametrize("n,dirs_expect", [ - [2, np.array([-3.2, 23.2])], - [3, np.array([-8.8, 10.0, 28.8])], - [4, np.array([-12.3, 3.7, 16.3, 32.3])], - [5, np.array([-14.8, -0.3, 10.0, 20.3, 34.8])] - ]) - def test_discrete_directions_offset(self , n, dirs_expect): + np.testing.assert_allclose( + np.degrees(spreading.discrete_directions(n) + 1e-8) % 360.0, + dirs_expect % 360.0, + rtol=0.0, + atol=0.1, + ) + + @pytest.mark.parametrize( + "n,dirs_expect", + [ + [2, np.array([-3.2, 23.2])], + [3, np.array([-8.8, 10.0, 28.8])], + [4, np.array([-12.3, 3.7, 16.3, 32.3])], + [5, np.array([-14.8, -0.3, 10.0, 20.3, 34.8])], + ], + ) + def test_discrete_directions_offset(self, n, dirs_expect): + spreading = CosineHalfSpreading(4, degrees=True) + np.testing.assert_allclose( + (spreading.discrete_directions(n, 10.0) + 1e-8) % 360.0, + dirs_expect % 360.0, + rtol=0.0, + atol=0.1, + ) + + spreading = CosineHalfSpreading(4, degrees=False) + np.testing.assert_allclose( + np.degrees(spreading.discrete_directions(n, np.radians(10)) + 1e-8) % 360.0, + dirs_expect % 360.0, + rtol=0.0, + atol=0.1, + ) + + @pytest.mark.parametrize( + "n,dirs_expect", + [ + [2, np.array([-23.2, 3.2])], + [3, np.array([-28.8, -10.0, 8.8])], + [4, np.array([-32.3, -16.3, -3.7, 12.3])], + [5, np.array([-34.8, -20.3, -10.0, 0.3, 14.8])], + ], + ) + def test_discrete_directions_neg_offset(self, n, dirs_expect): spreading = CosineHalfSpreading(4, degrees=True) - np.testing.assert_allclose((spreading.discrete_directions(n, 10.0) + 1e-8) % 360.0, dirs_expect % 360.0, rtol=0.0, atol=0.1) + np.testing.assert_allclose( + (spreading.discrete_directions(n, -10.0) + 1e-8) % 360.0, + dirs_expect % 360.0, + rtol=0.0, + atol=0.1, + ) spreading = CosineHalfSpreading(4, degrees=False) - np.testing.assert_allclose(np.degrees(spreading.discrete_directions(n, np.radians(10)) + 1e-8) % 360.0, dirs_expect % 360.0, rtol=0.0, atol=0.1) - - @pytest.mark.parametrize("n,dirs_expect", [ - [2, np.array([-23.2, 3.2])], - [3, np.array([-28.8, -10.0, 8.8])], - [4, np.array([-32.3, -16.3, -3.7, 12.3])], - [5, np.array([-34.8, -20.3, -10.0, 0.3, 14.8])] - ]) - def test_discrete_directions_neg_offset(self , n, dirs_expect): + np.testing.assert_allclose( + np.degrees(spreading.discrete_directions(n, np.radians(-10)) + 1e-8) + % 360.0, + dirs_expect % 360.0, + rtol=0.0, + atol=0.1, + ) + + @pytest.mark.parametrize( + "n,dirs_expect", + [ + [2, np.array([345.8, 372.2])], + [3, np.array([340.2, 359, 377.8])], + [4, np.array([336.7, 352.7, 365.3, 381.3])], + [5, np.array([334.2, 348.7, 359.0, 369.3, 383.8])], + ], + ) + def test_discrete_directions_large_offset(self, n, dirs_expect): spreading = CosineHalfSpreading(4, degrees=True) - np.testing.assert_allclose((spreading.discrete_directions(n, -10.0) + 1e-8) % 360.0, dirs_expect % 360.0, rtol=0.0, atol=0.1) + np.testing.assert_allclose( + (spreading.discrete_directions(n, 359) + 1e-8) % 360.0, + dirs_expect % 360.0, + rtol=0.0, + atol=0.1, + ) spreading = CosineHalfSpreading(4, degrees=False) - np.testing.assert_allclose(np.degrees(spreading.discrete_directions(n, np.radians(-10)) + 1e-8) % 360.0, dirs_expect % 360.0, rtol=0.0, atol=0.1) \ No newline at end of file + np.testing.assert_allclose( + np.degrees(spreading.discrete_directions(n, np.radians(359)) + 1e-8) + % 360.0, + dirs_expect % 360.0, + rtol=0.0, + atol=0.1, + ) + + @pytest.mark.parametrize( + "n,dirs_expect", + [ + [2, np.array([-0.5, 20.5])], + [3, np.array([-5.0, 10.0, 25.0])], + [4, np.array([-7.8, 5.0, 15.0, 27.8])], + [5, np.array([-9.8, 1.8, 10.0, 18.2, 29.8])], + ], + ) + def test_discrete_directions_other_s(self, n, dirs_expect): + spreading = CosineHalfSpreading(6.5, degrees=True) + np.testing.assert_allclose( + (spreading.discrete_directions(n, 10) + 1e-8) % 360.0, + dirs_expect % 360.0, + rtol=0.0, + atol=0.1, + ) + + spreading = CosineHalfSpreading(6.5, degrees=False) + np.testing.assert_allclose( + np.degrees(spreading.discrete_directions(n, np.radians(10)) + 1e-8) % 360.0, + dirs_expect % 360.0, + rtol=0.0, + atol=0.1, + ) From 0fe98ef77d84dbf5c40f3fbc6a87ed39e8bdbf55 Mon Sep 17 00:00:00 2001 From: petter-indrevoll-4ss Date: Thu, 10 Apr 2025 15:46:24 +0200 Subject: [PATCH 4/8] black + isort --- src/waveresponse/_core.py | 2 +- tests/test_core.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/waveresponse/_core.py b/src/waveresponse/_core.py index b35e0b0..fb62b82 100644 --- a/src/waveresponse/_core.py +++ b/src/waveresponse/_core.py @@ -4,7 +4,7 @@ from numbers import Number import numpy as np -from scipy.integrate import trapezoid, quad +from scipy.integrate import quad, trapezoid from scipy.interpolate import RegularGridInterpolator as RGI from scipy.optimize import root_scalar from scipy.special import gamma diff --git a/tests/test_core.py b/tests/test_core.py index 619c4e2..e1c5323 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -9,9 +9,9 @@ from scipy.interpolate import RegularGridInterpolator as RGI import waveresponse as wr -from waveresponse import ( # BinGrid, +from waveresponse import ( RAO, - CosineFullSpreading, + CosineFullSpreading, # BinGrid, CosineHalfSpreading, DirectionalBinSpectrum, DirectionalSpectrum, From 42a8d1c6651c0c0704f060ed0970ab8bbb1a17b4 Mon Sep 17 00:00:00 2001 From: Ali Cetin Date: Mon, 14 Apr 2025 09:02:03 +0200 Subject: [PATCH 5/8] remove comment --- tests/test_core.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_core.py b/tests/test_core.py index e1c5323..82b9c88 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -11,7 +11,7 @@ import waveresponse as wr from waveresponse import ( RAO, - CosineFullSpreading, # BinGrid, + CosineFullSpreading, CosineHalfSpreading, DirectionalBinSpectrum, DirectionalSpectrum, From 1a6f98ccafabd786c26de706b87c717928b6c4b5 Mon Sep 17 00:00:00 2001 From: Ali Cetin Date: Mon, 14 Apr 2025 09:36:45 +0200 Subject: [PATCH 6/8] some adjustments --- src/waveresponse/_core.py | 56 +++++++++++++++++++++------------------ 1 file changed, 30 insertions(+), 26 deletions(-) diff --git a/src/waveresponse/_core.py b/src/waveresponse/_core.py index fb62b82..d867862 100644 --- a/src/waveresponse/_core.py +++ b/src/waveresponse/_core.py @@ -1533,7 +1533,7 @@ def from_spectrum1d( 1-D array of grid frequency coordinates. Positive and monotonically increasing. dirs : array-like 1-D array of grid direction coordinates. Positive and monotonically increasing. - Must cover the directional range [0, 360) degrees (or [0, 2 * numpy.pi) radians). + Must cover the directional range [0, 360) degrees (or [0, 2 * pi) radians). spectrum1d : array-like 1-D array of non-directional spectrum density values. These 1-D spectrum values will be scaled according to the spreading function, and distributed @@ -2415,23 +2415,25 @@ def _spread_fun(self, omega, theta): def discrete_directions(self, n, direction_offset=0.0): """ - Find the equal energy directions for a given direction and spreading function. - The function uses the spreading function to find the directions - needed to use an equal energy approach similarly to how OrcaFlex does it. + Split the spreading function into discrete direction bins with + "equal energy", i.e. equal area under the curve. The direcitons + representing the bins are chosen to have equal area under the curve on + each side within the bin. Parameters ---------- n : int - Number of equal energy directions to find.e - direction_offset : float - Direction of the peak wave energy. - degrees : bool, default False - If True, dirp is in degrees. If False, dirp is in radians. + Number of discrete directions. + direction_offset : float, default + A offset to add to the discrete directions. Units should be + according to the `degrees` flag given during initialization. Returns ------- - np.array - Numpy array of equal energy directions in radians or degrees (same convention as input). + ndarray + A sequence of direction representing "equal energy" bins with range + wrapped to [0, 360) degrees or [0, 2 * numpy.pi) radians according + to the `degrees` flag given during initialization. """ if self._degrees: x_lb = -180.0 @@ -2442,30 +2444,32 @@ def discrete_directions(self, n, direction_offset=0.0): x_ub = np.pi periodicity = 2.0 * np.pi - try: - total_area = quad(lambda theta: self(None, theta), x_lb, x_ub)[0] - except Exception as e: - raise RuntimeError( - f"Failed to calculate total area under the spreading function: {e}" - ) + total_area, _ = quad( + lambda theta: self(None, theta), x_lb, x_ub, epsabs=1.0e-6, epsrel=0.0 + ) half_bin_edges = np.empty(2 * n - 1) x_prev = x_lb for i in range(1, 2 * n): target_area = total_area * i / (2 * n) - try: - res = root_scalar( - lambda x: quad(lambda theta: self(None, theta), x_lb, x)[0] - - target_area, - bracket=[x_prev, x_ub], - ) - except Exception as e: - raise RuntimeError(f"Failed to calculate root for direction {i}: {e}") + res = root_scalar( + lambda x: quad( + lambda theta: self(None, theta), x_lb, x, epsabs=1.0e-6, epsrel=0.0 + )[0] + - target_area, + bracket=[x_prev, x_ub], + ) + + if not res.converged: + raise RuntimeError(f"Failed find the directions: {res.flag}") + x_prev = res.root half_bin_edges[i - 1] = x_prev - return _robust_modulus(half_bin_edges[::2] + direction_offset, periodicity) + return np.round( + _robust_modulus(half_bin_edges[::2] + direction_offset, periodicity), 5 + ) class CosineHalfSpreading(BaseSpreading): From 5c35661f48d0cbb8e0885c9286f98bf2a0b0f8d6 Mon Sep 17 00:00:00 2001 From: Ali Cetin Date: Mon, 14 Apr 2025 09:37:17 +0200 Subject: [PATCH 7/8] remove round --- src/waveresponse/_core.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/waveresponse/_core.py b/src/waveresponse/_core.py index d867862..48aa2c3 100644 --- a/src/waveresponse/_core.py +++ b/src/waveresponse/_core.py @@ -2467,9 +2467,7 @@ def discrete_directions(self, n, direction_offset=0.0): x_prev = res.root half_bin_edges[i - 1] = x_prev - return np.round( - _robust_modulus(half_bin_edges[::2] + direction_offset, periodicity), 5 - ) + return _robust_modulus(half_bin_edges[::2] + direction_offset, periodicity) class CosineHalfSpreading(BaseSpreading): From 67cc6eb67a63c33ca4508abfaafa4fe0ca5b16c8 Mon Sep 17 00:00:00 2001 From: Ali Cetin Date: Mon, 14 Apr 2025 09:39:41 +0200 Subject: [PATCH 8/8] docstring --- src/waveresponse/_core.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/waveresponse/_core.py b/src/waveresponse/_core.py index 48aa2c3..c4fbea7 100644 --- a/src/waveresponse/_core.py +++ b/src/waveresponse/_core.py @@ -2432,7 +2432,7 @@ def discrete_directions(self, n, direction_offset=0.0): ------- ndarray A sequence of direction representing "equal energy" bins with range - wrapped to [0, 360) degrees or [0, 2 * numpy.pi) radians according + wrapped to [0, 360) degrees or [0, 2 * pi) radians according to the `degrees` flag given during initialization. """ if self._degrees: