From cd9b42378cd0bef6bbb68fe3c931a3cc2a805b77 Mon Sep 17 00:00:00 2001 From: Ali Cetin Date: Wed, 2 Apr 2025 13:59:55 +0200 Subject: [PATCH 01/10] fix test --- src/waveresponse/__init__.py | 4 +- src/waveresponse/_core.py | 538 ++++---- tests/test_core.py | 2234 ++++++++++++++-------------------- 3 files changed, 1193 insertions(+), 1583 deletions(-) diff --git a/src/waveresponse/__init__.py b/src/waveresponse/__init__.py index be646ea0..85d7711a 100644 --- a/src/waveresponse/__init__.py +++ b/src/waveresponse/__init__.py @@ -1,6 +1,6 @@ from ._core import ( RAO, - BinGrid, + # BinGrid, CosineFullSpreading, CosineHalfSpreading, DirectionalBinSpectrum, @@ -40,7 +40,7 @@ "DirectionalSpectrum", "DirectionalBinSpectrum", "Grid", - "BinGrid", + # "BinGrid", "JONSWAP", "ModifiedPiersonMoskowitz", "OchiHubble", diff --git a/src/waveresponse/_core.py b/src/waveresponse/_core.py index d8f23505..5f97a9ae 100644 --- a/src/waveresponse/_core.py +++ b/src/waveresponse/_core.py @@ -1,4 +1,5 @@ import copy +import warnings from abc import ABC, abstractmethod from numbers import Number @@ -141,6 +142,51 @@ def _sort(dirs, vals): return dirs[sorted_args], vals[:, sorted_args] +class _GridInterpolate: + def __init__(self, freq, dirs, vals, complex_convert="rectangular", **kwargs): + """ + Interpolation function based on ``scipy.interpolate.RegularGridInterpolator``. + """ + xp = np.concatenate((dirs[-1:] - 2 * np.pi, dirs, dirs[:1] + 2.0 * np.pi)) + + yp = freq + zp = np.concatenate( + ( + vals[:, -1:], + vals, + vals[:, :1], + ), + axis=1, + ) + + if np.all(np.isreal(zp)): + self._interpolate = RGI((xp, yp), zp.T, **kwargs) + elif complex_convert.lower() == "polar": + amp, phase = complex_to_polar(zp, phase_degrees=False) + phase_complex = np.cos(phase) + 1j * np.sin(phase) + interp_amp = RGI((xp, yp), amp.T, **kwargs) + interp_phase = RGI((xp, yp), phase_complex.T, **kwargs) + self._interpolate = lambda *args_, **kwargs_: ( + polar_to_complex( + interp_amp(*args_, **kwargs_), + np.angle(interp_phase(*args_, **kwargs_)), + phase_degrees=False, + ) + ) + elif complex_convert.lower() == "rectangular": + interp_real = RGI((xp, yp), np.real(zp.T), **kwargs) + interp_imag = RGI((xp, yp), np.imag(zp.T), **kwargs) + self._interpolate = lambda *args_, **kwargs_: ( + interp_real(*args_, **kwargs_) + 1j * interp_imag(*args_, **kwargs_) + ) + else: + raise ValueError("Unknown 'complex_convert' type") + + def __call__(self, freq, dirs): + dirsnew, freqnew = np.meshgrid(dirs, freq, indexing="ij", sparse=True) + return self._interpolate((dirsnew, freqnew)).T + + def mirror(rao, dof, sym_plane="xz"): """ Mirrors/folds an RAO object about a symmetry plane. @@ -241,7 +287,7 @@ def mirror(rao, dof, sym_plane="xz"): ) -class _BaseGrid: +class Grid: """ Two-dimentional frequency/(wave)direction grid. @@ -301,7 +347,7 @@ def __init__( ) def __repr__(self): - return "_BaseGrid" + return "Grid" @classmethod def from_grid(cls, grid): @@ -538,47 +584,6 @@ def rotate(self, angle, degrees=False): new._dirs, new._vals = _sort(dirs_new, new._vals) return new - def _interpolate_function(self, complex_convert="rectangular", **kw): - """ - Interpolation function based on ``scipy.interpolate.RegularGridInterpolator``. - """ - xp = np.concatenate( - (self._dirs[-1:] - 2 * np.pi, self._dirs, self._dirs[:1] + 2.0 * np.pi) - ) - - yp = self._freq - zp = np.concatenate( - ( - self._vals[:, -1:], - self._vals, - self._vals[:, :1], - ), - axis=1, - ) - - if np.all(np.isreal(zp)): - return RGI((xp, yp), zp.T, **kw) - elif complex_convert.lower() == "polar": - amp, phase = complex_to_polar(zp, phase_degrees=False) - phase_complex = np.cos(phase) + 1j * np.sin(phase) - interp_amp = RGI((xp, yp), amp.T, **kw) - interp_phase = RGI((xp, yp), phase_complex.T, **kw) - return lambda *args, **kwargs: ( - polar_to_complex( - interp_amp(*args, **kwargs), - np.angle(interp_phase(*args, **kwargs)), - phase_degrees=False, - ) - ) - elif complex_convert.lower() == "rectangular": - interp_real = RGI((xp, yp), np.real(zp.T), **kw) - interp_imag = RGI((xp, yp), np.imag(zp.T), **kw) - return lambda *args, **kwargs: ( - interp_real(*args, **kwargs) + 1j * interp_imag(*args, **kwargs) - ) - else: - raise ValueError("Unknown 'complex_convert' type") - def __mul__(self, other): """ Multiply values (element-wise). @@ -696,39 +701,6 @@ def imag(self): new._vals = new._vals.imag return new - -class Grid(_BaseGrid): - """ - A two-dimensional grid with values as a function of frequency and wave direction. - The values are assumed to represent a continuous field. I.e., the values can - be interpolated in the frequency and direction domain. - - Parameters - ---------- - freq : array-like - 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). - vals : array-like (N, M) - Values associated with the grid. Should be a 2-D array of shape (N, M), - such that ``N=len(freq)`` and ``M=len(dirs)``. - freq_hz : bool - If frequency is given in 'Hz'. If ``False``, 'rad/s' is assumed. - degrees : bool - If direction is given in 'degrees'. If ``False``, 'radians' is assumed. - clockwise : bool - If positive directions are defined to be 'clockwise' (``True``) or 'counterclockwise' - (``False``). Clockwise means that the directions follow the right-hand rule - with an axis pointing downwards. - waves_coming_from : bool - If waves are 'coming from' the given directions. If ``False``, 'going towards' - convention is assumed. - """ - - def __repr__(self): - return "Grid" - def interpolate( self, freq, @@ -773,6 +745,12 @@ def interpolate( array : Interpolated grid values. """ + warnings.warn( + "The `Grid.interpolate` method is deprecated and will be removed in a future release. ", + DeprecationWarning, + stacklevel=2, + ) + freq = np.asarray_chkfinite(freq).reshape(-1) dirs = np.asarray_chkfinite(dirs).reshape(-1) @@ -785,15 +763,17 @@ def interpolate( self._check_freq(freq) self._check_dirs(dirs) - interp_fun = self._interpolate_function( + interp_fun = _GridInterpolate( + self._freq, + self._dirs, + self._vals, complex_convert=complex_convert, method="linear", bounds_error=False, fill_value=fill_value, ) - dirsnew, freqnew = np.meshgrid(dirs, freq, indexing="ij", sparse=True) - return interp_fun((dirsnew, freqnew)).T + return interp_fun(freq, dirs) def reshape( self, @@ -838,6 +818,12 @@ def reshape( obj : A copy of the object where the underlying coordinate system is reshaped. """ + warnings.warn( + "The `Grid.reshape` method is deprecated and will be removed in a future release.", + DeprecationWarning, + stacklevel=2, + ) + freq_new = np.asarray_chkfinite(freq).copy() dirs_new = np.asarray_chkfinite(dirs).copy() @@ -850,157 +836,19 @@ def reshape( self._check_freq(freq_new) self._check_dirs(dirs_new) - vals_new = self.interpolate( - freq_new, - dirs_new, - freq_hz=False, - degrees=False, - complex_convert=complex_convert, - fill_value=fill_value, - ) - new = self.copy() - new._freq, new._dirs, new._vals = freq_new, dirs_new, vals_new - return new - - -class BinGrid(_BaseGrid): - """ - A two-dimensional grid with values as a function of frequency and wave direction. - The values are assumed to represent a continuous field along the frequency axis, - but are treated as bins along the direction axis. I.e., the values can only be - interpolated in the frequency domain. - - Parameters - ---------- - freq : array-like - 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). - vals : array-like (N, M) - Values associated with the grid. Should be a 2-D array of shape (N, M), - such that ``N=len(freq)`` and ``M=len(dirs)``. - freq_hz : bool - If frequency is given in 'Hz'. If ``False``, 'rad/s' is assumed. - degrees : bool - If direction is given in 'degrees'. If ``False``, 'radians' is assumed. - clockwise : bool - If positive directions are defined to be 'clockwise' (``True``) or 'counterclockwise' - (``False``). Clockwise means that the directions follow the right-hand rule - with an axis pointing downwards. - waves_coming_from : bool - If waves are 'coming from' the given directions. If ``False``, 'going towards' - convention is assumed. - """ - - def __repr__(self): - return "BinGrid" - - def interpolate( - self, - freq, - freq_hz=False, - complex_convert="rectangular", - fill_value=0.0, - ): - """ - Interpolate (linear) the grid values to match the given frequency coordinates. - - A 'fill value' is used for extrapolation (i.e. `freq` outside the bounds - of the provided 2-D grid). Directions are treated as periodic. - - Parameters - ---------- - freq : array-like - 1-D array of grid frequency coordinates. Positive and monotonically increasing. - freq_hz : bool - If frequency is given in 'Hz'. If ``False``, 'rad/s' is assumed. - complex_convert : str, optional - How to convert complex number grid values before interpolating. Should - be 'rectangular' or 'polar'. If 'rectangular' (default), complex values - are converted to rectangular form (i.e., real and imaginary part) before - interpolating. If 'polar', the values are instead converted to polar - form (i.e., amplitude and phase) before interpolating. The values are - converted back to complex form after interpolation. - fill_value : float or None - The value used for extrapolation (i.e., `freq` outside the bounds of - the provided grid). If ``None``, values outside the frequency domain - are extrapolated via nearest-neighbor extrapolation. Note that directions - are treated as periodic (and will not need extrapolation). - - Returns - ------- - array : - Interpolated grid values. - """ - freq = np.asarray_chkfinite(freq).reshape(-1) - - if freq_hz: - freq = 2.0 * np.pi * freq - - self._check_freq(freq) - - interp_fun = self._interpolate_function( + interp_fun = _GridInterpolate( + self._freq, + self._dirs, + self._vals, complex_convert=complex_convert, method="linear", bounds_error=False, fill_value=fill_value, ) - dirsnew, freqnew = np.meshgrid(self._dirs, freq, indexing="ij", sparse=True) - return interp_fun((dirsnew, freqnew)).T - - def reshape( - self, - freq, - freq_hz=False, - complex_convert="rectangular", - fill_value=0.0, - ): - """ - Reshape the grid to match the given frequency coordinates. Grid - values will be interpolated (linear). - - Parameters - ---------- - freq : array-like - 1-D array of new grid frequency coordinates. Positive and monotonically - increasing. - freq_hz : bool - If frequency is given in 'Hz'. If ``False``, 'rad/s' is assumed. - complex_convert : str, optional - How to convert complex number grid values before interpolating. Should - be 'rectangular' or 'polar'. If 'rectangular' (default), complex values - are converted to rectangular form (i.e., real and imaginary part) before - interpolating. If 'polar', the values are instead converted to polar - form (i.e., amplitude and phase) before interpolating. The values are - converted back to complex form after interpolation. - fill_value : float or None - The value used for extrapolation (i.e., `freq` outside the bounds of - the provided grid). If ``None``, values outside the frequency domain - are extrapolated via nearest-neighbor extrapolation. Note that directions - are treated as periodic (and will not need extrapolation). - - Returns - ------- - obj : - A copy of the object where the underlying coordinate system is reshaped. - """ - freq_new = np.asarray_chkfinite(freq).copy() - - if freq_hz: - freq_new = 2.0 * np.pi * freq_new - - self._check_freq(freq_new) - - vals_new = self.interpolate( - freq_new, - freq_hz=False, - complex_convert=complex_convert, - fill_value=fill_value, - ) + vals_new = interp_fun(freq_new, dirs_new) new = self.copy() - new._freq, new._vals = freq_new, vals_new + new._freq, new._dirs, new._vals = freq_new, dirs_new, vals_new return new @@ -1170,6 +1018,77 @@ def from_amp_phase( rao._phase_leading = phase_leading return rao + def reshape( + self, + freq, + dirs, + freq_hz=False, + degrees=False, + complex_convert="rectangular", + fill_value=0.0, + ): + """ + Reshape the grid to match the given frequency/direction coordinates. Grid + values will be interpolated (linear). + + Parameters + ---------- + freq : array-like + 1-D array of new grid frequency coordinates. Positive and monotonically + increasing. + dirs : array-like + 1-D array of new grid direction coordinates. Positive and monotonically increasing. + Must cover the directional range [0, 360) degrees (or [0, 2 * numpy.pi) radians). + freq_hz : bool + If frequency is given in 'Hz'. If ``False``, 'rad/s' is assumed. + degrees : bool + If direction is given in 'degrees'. If ``False``, 'radians' are assumed. + complex_convert : str, optional + How to convert complex number grid values before interpolating. Should + be 'rectangular' or 'polar'. If 'rectangular' (default), complex values + are converted to rectangular form (i.e., real and imaginary part) before + interpolating. If 'polar', the values are instead converted to polar + form (i.e., amplitude and phase) before interpolating. The values are + converted back to complex form after interpolation. + fill_value : float or None + The value used for extrapolation (i.e., `freq` outside the bounds of + the provided grid). If ``None``, values outside the frequency domain + are extrapolated via nearest-neighbor extrapolation. Note that directions + are treated as periodic (and will not need extrapolation). + + Returns + ------- + obj : + A copy of the object where the underlying coordinate system is reshaped. + """ + + freq_new = np.asarray_chkfinite(freq).copy() + dirs_new = np.asarray_chkfinite(dirs).copy() + + if freq_hz: + freq_new = 2.0 * np.pi * freq_new + + if degrees: + dirs_new = (np.pi / 180.0) * dirs_new + + self._check_freq(freq_new) + self._check_dirs(dirs_new) + + interp_fun = _GridInterpolate( + freq_new, + dirs_new, + self._vals, + complex_convert=complex_convert, + method="linear", + bounds_error=False, + fill_value=fill_value, + ) + + vals_new = interp_fun(freq_new, dirs_new) + new = self.copy() + new._freq, new._dirs, new._vals = freq_new, dirs_new, vals_new + return new + def differentiate(self, n=1): """ Return the nth derivative of the RAO. @@ -1570,6 +1489,17 @@ def _freq_spectrum(self, freq_hz=None): return f, s + @staticmethod + def _full_range_dir(x): + """Add direction range bounds (0.0 and 2.0 * np.pi)""" + range_end = np.nextafter(2.0 * np.pi, 0.0, dtype=type(x[0])) + + if x[0] != 0.0: + x = np.r_[0.0, x] + if x[-1] < range_end: + x = np.r_[x, range_end] + return x + @classmethod def from_spectrum1d( cls, @@ -1686,11 +1616,11 @@ def interpolate( dirs, freq_hz=False, degrees=False, + complex_convert="rectangular", fill_value=0.0, - **kwargs, ): """ - Interpolate (linear) the spectrum values to match the given frequency and direction + Interpolate (linear) the grid values to match the given frequency and direction coordinates. A 'fill value' is used for extrapolation (i.e. `freq` outside the bounds @@ -1706,6 +1636,13 @@ def interpolate( If frequency is given in 'Hz'. If ``False``, 'rad/s' is assumed. degrees : bool If direction is given in 'degrees'. If ``False``, 'radians' is assumed. + complex_convert : str, optional + How to convert complex number grid values before interpolating. Should + be 'rectangular' or 'polar'. If 'rectangular' (default), complex values + are converted to rectangular form (i.e., real and imaginary part) before + interpolating. If 'polar', the values are instead converted to polar + form (i.e., amplitude and phase) before interpolating. The values are + converted back to complex form after interpolation. fill_value : float or None The value used for extrapolation (i.e., `freq` outside the bounds of the provided grid). If ``None``, values outside the frequency domain @@ -1715,14 +1652,21 @@ def interpolate( Returns ------- array : - Interpolated spectrum density values. + Interpolated grid values. """ + warnings.warn( + "The `DirectionalSpectrum.interpolate` method is deprecated and will be removed in a future release." + "Use `DirectionalSpectrum.reshape` method instead.", + DeprecationWarning, + stacklevel=2, + ) vals = super().interpolate( freq, dirs, freq_hz=freq_hz, degrees=degrees, + complex_convert=complex_convert, fill_value=fill_value, ) @@ -1731,22 +1675,87 @@ def interpolate( if degrees: vals *= np.pi / 180.0 - return vals - @staticmethod - def _full_range_dir(x): - """Add direction range bounds (0.0 and 2.0 * np.pi)""" - range_end = np.nextafter(2.0 * np.pi, 0.0, dtype=type(x[0])) + def reshape( + self, + freq, + dirs, + freq_hz=False, + degrees=False, + complex_convert="rectangular", + fill_value=0.0, + ): + """ + Reshape the grid to match the given frequency/direction coordinates. Grid + values will be interpolated (linear). - if x[0] != 0.0: - x = np.r_[0.0, x] - if x[-1] < range_end: - x = np.r_[x, range_end] - return x + Parameters + ---------- + freq : array-like + 1-D array of new grid frequency coordinates. Positive and monotonically + increasing. + dirs : array-like + 1-D array of new grid direction coordinates. Positive and monotonically increasing. + Must cover the directional range [0, 360) degrees (or [0, 2 * numpy.pi) radians). + freq_hz : bool + If frequency is given in 'Hz'. If ``False``, 'rad/s' is assumed. + degrees : bool + If direction is given in 'degrees'. If ``False``, 'radians' are assumed. + complex_convert : str, optional + How to convert complex number grid values before interpolating. Should + be 'rectangular' or 'polar'. If 'rectangular' (default), complex values + are converted to rectangular form (i.e., real and imaginary part) before + interpolating. If 'polar', the values are instead converted to polar + form (i.e., amplitude and phase) before interpolating. The values are + converted back to complex form after interpolation. + fill_value : float or None + The value used for extrapolation (i.e., `freq` outside the bounds of + the provided grid). If ``None``, values outside the frequency domain + are extrapolated via nearest-neighbor extrapolation. Note that directions + are treated as periodic (and will not need extrapolation). + + Returns + ------- + obj : + A copy of the object where the underlying coordinate system is reshaped. + """ + freq_new = np.asarray_chkfinite(freq).copy() + dirs_new = np.asarray_chkfinite(dirs).copy() + + if freq_hz: + freq_new = 2.0 * np.pi * freq_new + + if degrees: + dirs_new = (np.pi / 180.0) * dirs_new + + self._check_freq(freq_new) + self._check_dirs(dirs_new) + + interp_fun = _GridInterpolate( + self._freq, + self._dirs, + self._vals, + complex_convert=complex_convert, + method="linear", + bounds_error=False, + fill_value=fill_value, + ) + vals_new = interp_fun(freq_new, dirs_new) -class DirectionalBinSpectrum(_SpectrumMixin, BinGrid): + if freq_hz: + vals_new *= 2.0 * np.pi + + if degrees: + vals_new *= np.pi / 180.0 + + new = self.copy() + new._freq, new._dirs, new._vals = freq_new, dirs_new, vals_new + return new + + +class DirectionalBinSpectrum(_SpectrumMixin, Grid): """ Directional binned spectrum. @@ -1863,27 +1872,36 @@ def grid(self, freq_hz=False, degrees=False): return freq, dirs, vals def interpolate( + self, + *args, **kwargs + ): + raise NotImplementedError("Use `.reshape` instead.") + + def reshape( self, freq, freq_hz=False, + complex_convert="rectangular", fill_value=0.0, - **kwargs, ): """ - Interpolate (linear) the spectrum values to match the given frequency and direction - coordinates. - - A 'fill value' is used for extrapolation (i.e. `freq` outside the bounds - of the provided 2-D grid). Directions are treated as periodic. + Reshape the grid to match the given frequency coordinates. Grid + values will be interpolated (linear). Parameters ---------- freq : array-like - 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. + 1-D array of new grid frequency coordinates. Positive and monotonically + increasing. freq_hz : bool If frequency is given in 'Hz'. If ``False``, 'rad/s' is assumed. + complex_convert : str, optional + How to convert complex number grid values before interpolating. Should + be 'rectangular' or 'polar'. If 'rectangular' (default), complex values + are converted to rectangular form (i.e., real and imaginary part) before + interpolating. If 'polar', the values are instead converted to polar + form (i.e., amplitude and phase) before interpolating. The values are + converted back to complex form after interpolation. fill_value : float or None The value used for extrapolation (i.e., `freq` outside the bounds of the provided grid). If ``None``, values outside the frequency domain @@ -1892,20 +1910,34 @@ def interpolate( Returns ------- - array : - Interpolated spectrum density values. + obj : + A copy of the object where the underlying coordinate system is reshaped. """ + freq_new = np.asarray_chkfinite(freq).copy() - vals = super().interpolate( - freq, - freq_hz=freq_hz, + if freq_hz: + freq_new = 2.0 * np.pi * freq_new + + self._check_freq(freq_new) + + interp_fun = _GridInterpolate( + self._freq, + self._dirs, + self._vals, + complex_convert=complex_convert, + method="linear", + bounds_error=False, fill_value=fill_value, ) + vals_new = interp_fun(freq_new, self._dirs) + if freq_hz: - vals *= 2.0 * np.pi + vals_new *= 2.0 * np.pi - return vals + new = self.copy() + new._freq, new._vals = freq_new, vals_new + return new class WaveSpectrum(DisableComplexMixin, DirectionalSpectrum): diff --git a/tests/test_core.py b/tests/test_core.py index 3c5ea8ca..66e90364 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -11,7 +11,7 @@ import waveresponse as wr from waveresponse import ( RAO, - BinGrid, + # BinGrid, CosineFullSpreading, CosineHalfSpreading, DirectionalBinSpectrum, @@ -21,7 +21,7 @@ calculate_response, mirror, ) -from waveresponse._core import _BaseGrid, _check_foldable, _check_is_similar +from waveresponse._core import _check_foldable, _check_is_similar TEST_PATH = Path(__file__).parent @@ -37,20 +37,20 @@ def freq_dirs(): return freq, dirs -@pytest.fixture -def base_grid(freq_dirs): - freq, dirs = freq_dirs - vals = np.random.random((len(freq), len(dirs))) - grid = _BaseGrid( - freq, - dirs, - vals, - freq_hz=True, - degrees=True, - clockwise=True, - waves_coming_from=True, - ) - return grid +# @pytest.fixture +# def base_grid(freq_dirs): +# freq, dirs = freq_dirs +# vals = np.random.random((len(freq), len(dirs))) +# grid = _BaseGrid( +# freq, +# dirs, +# vals, +# freq_hz=True, +# degrees=True, +# clockwise=True, +# waves_coming_from=True, +# ) +# return grid @pytest.fixture @@ -69,20 +69,20 @@ def grid(freq_dirs): return grid -@pytest.fixture -def bingrid(freq_dirs): - freq, dirs = freq_dirs - vals = np.random.random((len(freq), len(dirs))) - grid = BinGrid( - freq, - dirs, - vals, - freq_hz=True, - degrees=True, - clockwise=True, - waves_coming_from=True, - ) - return grid +# @pytest.fixture +# def bingrid(freq_dirs): +# freq, dirs = freq_dirs +# vals = np.random.random((len(freq), len(dirs))) +# grid = BinGrid( +# freq, +# dirs, +# vals, +# freq_hz=True, +# degrees=True, +# clockwise=True, +# waves_coming_from=True, +# ) +# return grid @pytest.fixture @@ -764,12 +764,12 @@ def test_dirs_empty(self): _check_foldable(np.array([]), degrees=True, sym_plane="xz") -class Test__BaseGrid: +class Test__Grid: def test__init__(self): freq = np.linspace(0, 1.0, 10) dirs = np.linspace(0, 360.0, 15, endpoint=False) vals = np.zeros((10, 15)) - grid = _BaseGrid( + grid = Grid( freq, dirs, vals, @@ -787,828 +787,211 @@ def test__init__(self): assert grid._freq_hz is True assert grid._degrees is True - def test__init__2(self): - freq = np.linspace(0, 1.0, 10) - dirs = np.linspace(0, 2.0 * np.pi, 15, endpoint=False) - vals = np.zeros((10, 15)) - grid = _BaseGrid( - freq, - dirs, - vals, - freq_hz=False, - degrees=False, - clockwise=False, - waves_coming_from=False, - ) - - np.testing.assert_array_almost_equal(grid._freq, freq) - np.testing.assert_array_almost_equal(grid._dirs, dirs) - np.testing.assert_array_almost_equal(grid._vals, vals) - assert grid._clockwise is False - assert grid._waves_coming_from is False - assert grid._freq_hz is False - assert grid._degrees is False - - def test__init__raises_duplicate_freq(self): - with pytest.raises(ValueError): - freq = np.array([0, 1, 1, 2]) - dirs = np.array([0, 1, 2]) - vals = np.zeros((4, 3)) - _BaseGrid(freq, dirs, vals) + def test_interpolate(self): + a = 7 + b = 6 - def test__init__raises_duplicate_dirs(self): - with pytest.raises(ValueError): - freq = np.array([0, 1, 2]) - dirs = np.array([0, 1, 1, 2]) - vals = np.zeros((3, 4)) - _BaseGrid(freq, dirs, vals) + yp = np.linspace(0.0, 2.0, 20) + xp = np.linspace(0.0, 359.0, 10) + vp = np.array([[a * x_i + b * y_i for x_i in xp] for y_i in yp]) + grid = Grid(yp, xp, vp, freq_hz=True, degrees=True) - def test__init__raises_negative_freq(self): - with pytest.raises(ValueError): - freq = np.array([-1, 1, 2, 3]) - dirs = np.array([0, 1, 2]) - vals = np.zeros((4, 3)) - _BaseGrid(freq, dirs, vals) + y = np.linspace(0.5, 1.0, 20) + x = np.linspace(5.0, 15.0, 10) + vals_expect = np.array([[a * x_i + b * y_i for x_i in x] for y_i in y]) - def test__init__raises_negative_dirs(self): - with pytest.raises(ValueError): - freq = np.array([0, 1, 2, 3]) - dirs = np.array([-1, 1, 2]) - vals = np.zeros((4, 3)) - Grid(freq, dirs, vals) + vals_out = grid.interpolate(y, x, freq_hz=True, degrees=True) - def test__init__raises_freq_not_increasing(self): - with pytest.raises(ValueError): - freq = np.array([0, 1, 2, 1]) - dirs = np.array([0, 1, 2]) - vals = np.zeros((4, 3)) - _BaseGrid(freq, dirs, vals) + np.testing.assert_array_almost_equal(vals_out, vals_expect) - def test__init__raises_dirs_not_increasing(self): - with pytest.raises(ValueError): - freq = np.array([0, 1, 2]) - dirs = np.array([0, 1, 2, 1]) - vals = np.zeros((3, 4)) - _BaseGrid(freq, dirs, vals) + def test_interpolate2(self): + a = 7 + b = 6 - def test__init__raises_dirs_2pi(self): - with pytest.raises(ValueError): - freq = np.array([0, 1, 2]) - dirs = np.array([0, 1, 2, 2.0 * np.pi]) - vals = np.zeros((3, 4)) - _BaseGrid(freq, dirs, vals, degrees=False) + yp = np.linspace(0.0, 2.0, 20) + xp = np.linspace(0.0, 359.0, 10) + vp = np.array([[a * x_i + b * y_i for x_i in xp] for y_i in yp]) + grid = Grid(yp, xp, vp, freq_hz=True, degrees=True) - def test__init__raises_dirs_greater_than_2pi(self): - with pytest.raises(ValueError): - freq = np.array([0, 1, 2]) - dirs = np.array([0, 1, 2, 3.0 * np.pi]) - vals = np.zeros((3, 4)) - _BaseGrid(freq, dirs, vals, degrees=False) + y = np.linspace(0.5, 1.0, 20) + x = np.linspace(5.0, 15.0, 10) + vals_expect = np.array([[a * x_i + b * y_i for x_i in x] for y_i in y]) - def test__init__raises_freq_not_1d(self): - with pytest.raises(ValueError, match="`freq` must be 1 dimensional."): - freq = np.array([[0, 1], [2, 3]]) - dirs = np.array([0.0, 1.0, 1.5, 2.0]) - vals = np.zeros((4, 4)) - _BaseGrid(freq, dirs, vals) + y_ = (2.0 * np.pi) * y + x_ = (np.pi / 180.0) * x + vals_out = grid.interpolate(y_, x_, freq_hz=False, degrees=False) - def test__init__raises_dirs_not_1d(self): - with pytest.raises(ValueError, match="`dirs` must be 1 dimensional."): - freq = np.array([0, 1, 2, 3]) - dirs = np.array([[0.0, 1.0], [1.5, 2.0]]) - vals = np.zeros((4, 4)) - _BaseGrid(freq, dirs, vals) + np.testing.assert_array_almost_equal(vals_out, vals_expect) - def test__init__raises_vals_shape(self): - with pytest.raises(ValueError): - freq = np.array([0, 1, 2]) - dirs = np.array([0, 1, 2, 3]) - vals = np.zeros((3, 10)) - _BaseGrid(freq, dirs, vals) + def test_interpolate_single_coordinate(self): + a = 7 + b = 6 - def test_from_grid(self): - freq = np.linspace(0, 1.0, 10) - dirs = np.linspace(0, 360.0, 15, endpoint=False) - vals = np.random.random((10, 15)) - grid_in = _BaseGrid( - freq, - dirs, - vals, - freq_hz=True, - degrees=True, - clockwise=True, - waves_coming_from=True, - ) + yp = np.linspace(0.0, 2.0, 20) + xp = np.linspace(0.0, 359.0, 10) + vp = np.array([[a * x_i + b * y_i for x_i in xp] for y_i in yp]) + grid = Grid(yp, xp, vp, freq_hz=True, degrees=True) - grid_out = _BaseGrid.from_grid(grid_in) + vals_out = grid.interpolate(1.8, 12.1, freq_hz=True, degrees=True) - vals_expect = vals.copy() + vals_expect = np.array(a * 12.1 + b * 1.8) - assert isinstance(grid_out, _BaseGrid) - np.testing.assert_array_almost_equal(grid_out._freq, grid_in._freq) - np.testing.assert_array_almost_equal(grid_out._dirs, grid_in._dirs) - np.testing.assert_array_almost_equal(grid_out._vals, vals_expect) - assert grid_out._clockwise == grid_in._clockwise - assert grid_out._waves_coming_from == grid_in._waves_coming_from - assert grid_out._freq_hz == grid_in._freq_hz - assert grid_out._degrees == grid_in._degrees + np.testing.assert_array_almost_equal(vals_out, vals_expect) - def test_freq_None(self): - freq = np.linspace(0, 1.0, 10) - dirs = np.linspace(0, 360.0, 15, endpoint=False) - vals = np.zeros((10, 15)) - grid = _BaseGrid( - freq, - dirs, - vals, - freq_hz=True, - degrees=True, - clockwise=True, - waves_coming_from=True, + def test_interpolate_fill_value(self): + freq = np.array([0, 1, 2]) + dirs = np.array([0, 90, 180, 270]) + vals = np.array( + [ + [1, 2, 3, 4], + [1, 2, 3, 4], + [1, 2, 3, 4], + ] ) + grid = Grid(freq, dirs, vals, freq_hz=True, degrees=True) - freq_out = grid.freq() - np.testing.assert_array_almost_equal(freq_out, freq) + # extrapolate + vals_out = grid.interpolate([10, 20], [0, 90], freq_hz=True, degrees=True) - def test_freq_rads(self): - freq = np.linspace(0, 1.0, 10) - dirs = np.linspace(0, 360.0, 15, endpoint=False) - vals = np.zeros((10, 15)) - grid = _BaseGrid( - freq, - dirs, - vals, - freq_hz=True, - degrees=True, - clockwise=True, - waves_coming_from=True, + vals_expect = np.array( + [ + [0.0, 0.0], + [0.0, 0.0], + ] ) - freq_out = grid.freq(freq_hz=False) - np.testing.assert_array_almost_equal(freq_out, (2.0 * np.pi) * freq) + np.testing.assert_array_almost_equal(vals_out, vals_expect) - def test_freq_hz(self): - freq = np.linspace(0, 1.0, 10) - dirs = np.linspace(0, 360.0, 15, endpoint=False) - vals = np.zeros((10, 15)) - grid = _BaseGrid( - freq, - dirs, - vals, - freq_hz=True, - degrees=True, - clockwise=True, - waves_coming_from=True, + def test_interpolate_fill_value_None(self): + freq = np.array([0, 1, 2]) + dirs = np.array([0, 90, 180, 270]) + vals = np.array( + [ + [1, 2, 3, 4], + [1, 2, 3, 4], + [1, 2, 3, 4], + ] ) + grid = Grid(freq, dirs, vals, freq_hz=True, degrees=True) - freq_out = grid.freq(freq_hz=True) - np.testing.assert_array_almost_equal(freq_out, freq) + # extrapolate + vals_out = grid.interpolate( + [10, 20], [0, 90], freq_hz=True, degrees=True, fill_value=None + ) - def test_dirs_None(self): - freq = np.linspace(0, 1.0, 10) - dirs = np.linspace(0, 360.0, 15, endpoint=False) - vals = np.zeros((10, 15)) - grid = _BaseGrid( - freq, - dirs, - vals, - freq_hz=True, - degrees=True, - clockwise=True, - waves_coming_from=True, + vals_expect = np.array( + [ + [1.0, 2.0], + [1.0, 2.0], + ] ) - dirs_out = grid.dirs() - np.testing.assert_array_almost_equal(dirs_out, dirs) + np.testing.assert_array_almost_equal(vals_out, vals_expect) - def test_dirs_rad(self): - freq = np.linspace(0, 1.0, 10) - dirs = np.linspace(0, 360.0, 15, endpoint=False) - vals = np.zeros((10, 15)) - grid = _BaseGrid( - freq, - dirs, - vals, - freq_hz=True, - degrees=True, - clockwise=True, - waves_coming_from=True, - ) - - dirs_out = grid.dirs(degrees=False) - np.testing.assert_array_almost_equal(dirs_out, (np.pi / 180.0) * dirs) - - def test_dirs_deg(self): - freq = np.linspace(0, 1.0, 10) - dirs = np.linspace(0, 360.0, 15, endpoint=False) - vals = np.zeros((10, 15)) - grid = _BaseGrid( - freq, - dirs, - vals, - freq_hz=True, - degrees=True, - clockwise=True, - waves_coming_from=True, - ) - - dirs_out = grid.dirs(degrees=True) - np.testing.assert_array_almost_equal(dirs_out, dirs) - - def test_wave_convention(self, base_grid): - convention_expect = {"clockwise": True, "waves_coming_from": True} - convention_out = base_grid.wave_convention - assert convention_out == convention_expect - - def test__convert_dirs_radians(self): - dirs_in = np.array([0, np.pi / 4, np.pi / 2, 3.0 * np.pi / 4, np.pi]) - config_org = {"clockwise": False, "waves_coming_from": True} - config_new = {"clockwise": True, "waves_coming_from": False} - dirs_out = _BaseGrid._convert_dirs( - dirs_in, config_new, config_org, degrees=False - ) - - dirs_expect = np.array([np.pi, 3.0 * np.pi / 4, np.pi / 2, np.pi / 4, 0]) - - np.testing.assert_array_almost_equal(dirs_out, dirs_expect) - - def test__convert_dirs_degrees(self): - dirs_in = np.array([0, 45.0, 90.0, 135.0, 180.0]) - config_org = {"clockwise": False, "waves_coming_from": True} - config_new = {"clockwise": True, "waves_coming_from": False} - dirs_out = _BaseGrid._convert_dirs( - dirs_in, config_new, config_org, degrees=True - ) - - dirs_expect = np.array([180.0, 135.0, 90.0, 45.0, 0]) - - np.testing.assert_array_almost_equal(dirs_out, dirs_expect) - - def test__convert(self): - freq = np.linspace(0, 1.0, 10) - dirs = np.linspace(0, 360.0, 15, endpoint=False) - vals = np.zeros((10, 15)) - grid = _BaseGrid( - freq, - dirs, - vals, - freq_hz=True, - degrees=True, - clockwise=False, - waves_coming_from=True, - ) - - freq_in = np.array([0.0, 0.5, 1.0]) - dirs_in = np.array([0, np.pi / 4, np.pi / 2, 3.0 * np.pi / 4, np.pi]) - vals_in = np.array( - [ - [1.0, 2.0, 3.0, 4.0, 5.0], - [1.0, 2.0, 3.0, 4.0, 5.0], - ] - ) - config_org = {"clockwise": False, "waves_coming_from": True} - config_new = {"clockwise": True, "waves_coming_from": False} - freq_out, dirs_out, vals_out = grid._convert( - freq_in, dirs_in, vals_in, config_new, config_org - ) - - freq_expect = freq_in - dirs_expect = np.array([0, np.pi / 4, np.pi / 2, 3.0 * np.pi / 4, np.pi]) - vals_expect = np.array( - [ - [5.0, 4.0, 3.0, 2.0, 1.0], - [5.0, 4.0, 3.0, 2.0, 1.0], - ] - ) - - np.testing.assert_array_almost_equal(freq_out, freq_expect) - np.testing.assert_array_almost_equal(dirs_out, dirs_expect) - np.testing.assert_array_almost_equal(vals_out, vals_expect) - - def test_set_wave_convention(self): - freq_in = np.array([0.0, 0.5, 1.0]) - dirs_in = np.array([0, np.pi / 4, np.pi / 2, 3.0 * np.pi / 4, np.pi]) - vals_in = np.array( - [ - [1.0, 2.0, 3.0, 4.0, 5.0], - [1.0, 2.0, 3.0, 4.0, 5.0], - [1.0, 2.0, 3.0, 4.0, 5.0], - ] - ) - grid = _BaseGrid( - freq_in, - dirs_in, - vals_in, - freq_hz=True, - degrees=False, - clockwise=False, - waves_coming_from=True, - ) - - grid.set_wave_convention(clockwise=True, waves_coming_from=False) - - freq_expect = (2.0 * np.pi) * freq_in - dirs_expect = np.array([0, np.pi / 4, np.pi / 2, 3.0 * np.pi / 4, np.pi]) - vals_expect = np.array( - [ - [5.0, 4.0, 3.0, 2.0, 1.0], - [5.0, 4.0, 3.0, 2.0, 1.0], - [5.0, 4.0, 3.0, 2.0, 1.0], - ] - ) - - np.testing.assert_array_almost_equal(grid._freq, freq_expect) - np.testing.assert_array_almost_equal(grid._dirs, dirs_expect) - np.testing.assert_array_almost_equal(grid._vals, vals_expect) - assert grid._clockwise is True - assert grid._waves_coming_from is False - - def test_copy(self, base_grid): - grid_copy = base_grid.copy() - assert base_grid is base_grid - assert grid_copy is not base_grid - np.testing.assert_array_almost_equal(grid_copy._freq, base_grid._freq) - np.testing.assert_array_almost_equal(grid_copy._dirs, base_grid._dirs) - np.testing.assert_array_almost_equal(grid_copy._vals, base_grid._vals) - assert grid_copy._clockwise == base_grid._clockwise - assert grid_copy._waves_coming_from == base_grid._waves_coming_from - - def test_rotate_deg(self): - freq = np.array([0, 1]) - dirs = np.array([0, 90, 180]) - vals = np.array( - [ - [1, 2, 3], - [1, 2, 3], - ] - ) - grid = _BaseGrid( - freq, - dirs, - vals, - freq_hz=True, - degrees=True, - clockwise=False, - waves_coming_from=True, - ) - - grid_rot = grid.rotate(45, degrees=True) - - freq_expect = (2.0 * np.pi) * np.array([0, 1]) - dirs_expect = (np.pi / 180.0) * np.array([45, 135, 315]) - vals_expect = np.array( - [ - [2, 3, 1], - [2, 3, 1], - ] - ) - - np.testing.assert_array_almost_equal(grid_rot._freq, freq_expect) - np.testing.assert_array_almost_equal(grid_rot._dirs, dirs_expect) - np.testing.assert_array_almost_equal(grid_rot._vals, vals_expect) - - def test_rotate_deg_neg(self): - freq = np.array([0, 1]) - dirs = np.array([0, 90, 180]) - vals = np.array( - [ - [1, 2, 3], - [1, 2, 3], - ] - ) - grid = _BaseGrid( - freq, - dirs, - vals, - freq_hz=True, - degrees=True, - clockwise=False, - waves_coming_from=True, - ) - - grid_rot = grid.rotate(-45, degrees=True) - - freq_expect = (2.0 * np.pi) * np.array([0, 1]) - dirs_expect = (np.pi / 180.0) * np.array([45, 135, 225]) - vals_expect = np.array( - [ - [1, 2, 3], - [1, 2, 3], - ] - ) - - np.testing.assert_array_almost_equal(grid_rot._freq, freq_expect) - np.testing.assert_array_almost_equal(grid_rot._dirs, dirs_expect) - np.testing.assert_array_almost_equal(grid_rot._vals, vals_expect) - - def test_rotate_rad(self): - freq = np.array([0, 1]) - dirs = np.array([0, 90, 180]) - vals = np.array( - [ - [1, 2, 3], - [1, 2, 3], - ] - ) - grid = _BaseGrid( - freq, - dirs, - vals, - freq_hz=True, - degrees=True, - clockwise=False, - waves_coming_from=True, - ) - - grid_rot = grid.rotate(np.pi / 4, degrees=False) - - freq_expect = (2.0 * np.pi) * np.array([0, 1]) - dirs_expect = (np.pi / 180.0) * np.array([45, 135, 315]) - vals_expect = np.array( - [ - [2, 3, 1], - [2, 3, 1], - ] - ) - - np.testing.assert_array_almost_equal(grid_rot._freq, freq_expect) - np.testing.assert_array_almost_equal(grid_rot._dirs, dirs_expect) - np.testing.assert_array_almost_equal(grid_rot._vals, vals_expect) - - def test_rotate_rad_neg(self): - freq = np.array([0, 1]) - dirs = np.array([0, 90, 180]) - vals = np.array( - [ - [1, 2, 3], - [1, 2, 3], - ] - ) - grid = _BaseGrid( - freq, - dirs, - vals, - freq_hz=True, - degrees=True, - clockwise=False, - waves_coming_from=True, - ) - - grid_rot = grid.rotate(-np.pi / 4, degrees=False) - - freq_expect = (2.0 * np.pi) * np.array([0, 1]) - dirs_expect = (np.pi / 180.0) * np.array([45, 135, 225]) - vals_expect = np.array( - [ - [1, 2, 3], - [1, 2, 3], - ] - ) - - np.testing.assert_array_almost_equal(grid_rot._freq, freq_expect) - np.testing.assert_array_almost_equal(grid_rot._dirs, dirs_expect) - np.testing.assert_array_almost_equal(grid_rot._vals, vals_expect) - - def test_grid(self): - freq = np.array([0, 1]) - dirs = np.array([0, 90, 180]) - vals = np.array( - [ - [1, 2, 3], - [1, 2, 3], - ] - ) - grid = _BaseGrid( - freq, - dirs, - vals, - freq_hz=True, - degrees=True, - clockwise=False, - waves_coming_from=True, - ) - - freq_out, dirs_out, vals_out = grid.grid(freq_hz=True, degrees=True) - - freq_expect = np.array([0, 1]) - dirs_expect = np.array([0, 90, 180]) - vals_expect = np.array( - [ - [1, 2, 3], - [1, 2, 3], - ] - ) - - np.testing.assert_array_almost_equal(freq_out, freq_expect) - np.testing.assert_array_almost_equal(dirs_out, dirs_expect) - np.testing.assert_array_almost_equal(vals_out, vals_expect) - - def test__grid2(self): - freq = np.array([0, 1]) - dirs = np.array([0, 90, 180]) - vals = np.array( - [ - [1, 2, 3], - [1, 2, 3], - ] - ) - grid = _BaseGrid( - freq, - dirs, - vals, - freq_hz=True, - degrees=True, - clockwise=False, - waves_coming_from=True, - ) - - freq_out, dirs_out, vals_out = grid.grid(freq_hz=False, degrees=False) - - freq_expect = (2.0 * np.pi) * np.array([0, 1]) - dirs_expect = (np.pi / 180.0) * np.array([0, 90, 180]) - vals_expect = np.array( - [ - [1, 2, 3], - [1, 2, 3], - ] - ) - - np.testing.assert_array_almost_equal(freq_out, freq_expect) - np.testing.assert_array_almost_equal(dirs_out, dirs_expect) - np.testing.assert_array_almost_equal(vals_out, vals_expect) - - def test__mul__(self): - freq_in = np.array([1, 2, 3]) - dirs_in = np.array([0, 10, 20, 30]) - vals_in = np.array( - [ - [1, 2, 3, 4], - [1, 2, 3, 4], - [1, 2, 3, 4], - ] - ) - grid = _BaseGrid(freq_in, dirs_in, vals_in, degrees=True) - - grid_squared = grid * grid - - vals_expect = np.array( - [ - [1, 4, 9, 16], - [1, 4, 9, 16], - [1, 4, 9, 16], - ] - ) - - assert isinstance(grid_squared, _BaseGrid) - assert grid_squared._clockwise == grid._clockwise - assert grid_squared._waves_coming_from == grid._waves_coming_from - np.testing.assert_array_almost_equal(grid_squared._freq, grid._freq) - np.testing.assert_array_almost_equal(grid_squared._dirs, grid._dirs) - np.testing.assert_array_almost_equal(grid_squared._vals, vals_expect) - - def test__mul__numeric(self, base_grid): - grid_scaled = base_grid * 2.0 - np.testing.assert_array_almost_equal(grid_scaled._vals, base_grid._vals * 2.0) - - grid_scaled = base_grid * -2.0 - np.testing.assert_array_almost_equal(grid_scaled._vals, base_grid._vals * -2.0) - - grid_scaled = base_grid * 0.0 - np.testing.assert_array_almost_equal(grid_scaled._vals, base_grid._vals * 0.0) - - grid_scaled = base_grid * 2 - np.testing.assert_array_almost_equal(grid_scaled._vals, base_grid._vals * 2) - - grid_scaled = base_grid * (1 + 1j) - np.testing.assert_array_almost_equal( - grid_scaled._vals, base_grid._vals * (1 + 1j) - ) - - def test__rmul__numeric(self, base_grid): - grid_scaled = 2.0 * base_grid - np.testing.assert_array_almost_equal(grid_scaled._vals, base_grid._vals * 2.0) - - def test__mul__raises_array(self, base_grid): - with pytest.raises(TypeError): - base_grid * base_grid._vals - - def test__mul__raises_type(self, base_grid, rao): - with pytest.raises(TypeError): - base_grid * rao - - def test__mul__raises_shape(self): - freq_in = np.array([1, 2, 3]) - dirs_in = np.array([0, 10, 20, 30]) - vals_in = np.array( - [ - [1, 2, 3, 4], - [1, 2, 3, 4], - [1, 2, 3, 4], - ] - ) - grid = _BaseGrid(freq_in, dirs_in, vals_in, degrees=True) - - freq_in2 = np.array([1, 2]) - dirs_in2 = np.array([0, 10, 20]) - vals_in2 = np.array( - [ - [1, 2, 3], - [1, 2, 3], - ] - ) - grid2 = _BaseGrid(freq_in2, dirs_in2, vals_in2, degrees=True) - - with pytest.raises(ValueError): - grid * grid2 - - def test__mul__raises_freq(self, base_grid): - grid2 = base_grid.copy() - grid2._freq = 2.0 * grid2._freq - - with pytest.raises(ValueError): - base_grid * grid2 - - def test__mul__raises_dirs(self, base_grid): - grid2 = base_grid.copy() - grid2._dirs = 2.0 * grid2._dirs - - with pytest.raises(ValueError): - base_grid * grid2 - - def test__mul__raises_convention(self, base_grid): - base_grid.set_wave_convention(clockwise=False, waves_coming_from=False) - - grid2 = base_grid.copy() - grid2.set_wave_convention(clockwise=True, waves_coming_from=False) - with pytest.raises(ValueError): - base_grid * grid2 - - grid3 = base_grid.copy() - grid3.set_wave_convention(clockwise=False, waves_coming_from=True) - with pytest.raises(ValueError): - base_grid * grid3 - - def test__add__(self, base_grid): - out = base_grid + base_grid - - assert isinstance(out, _BaseGrid) - np.testing.assert_array_almost_equal( - out._vals, base_grid._vals + base_grid._vals - ) + def test_interpolate_complex_rectangular(self): + a_real = 7 + b_real = 6 + a_imag = 3 + b_imag = 9 - def test__add__raises_type(self, base_grid, rao): - with pytest.raises(TypeError): - base_grid + rao + yp = np.linspace(0.0, 2.0, 20) + xp = np.linspace(0.0, 359.0, 10) + vp_real = np.array([[a_real * x_i + b_real * y_i for x_i in xp] for y_i in yp]) + vp_imag = np.array([[a_imag * x_i + b_imag * y_i for x_i in xp] for y_i in yp]) + vp = vp_real + 1j * vp_imag + grid = Grid(yp, xp, vp, freq_hz=True, degrees=True) - @patch("waveresponse._core._check_is_similar") - def test__add__check_is_similar(self, mock_check_is_similar, base_grid): - base_grid + base_grid - mock_check_is_similar.assert_called_once_with( - base_grid, base_grid, exact_type=True + y = np.linspace(0.5, 1.0, 20) + x = np.linspace(5.0, 15.0, 10) + vals_real_expect = np.array( + [[a_real * x_i + b_real * y_i for x_i in x] for y_i in y] ) - - def test__add__numeric(self, base_grid): - grid_added = base_grid + 2.0 - np.testing.assert_array_almost_equal(grid_added._vals, base_grid._vals + 2.0) - - grid_added = base_grid + 0.0 - np.testing.assert_array_almost_equal(grid_added._vals, base_grid._vals + 0.0) - - grid_added = base_grid + 2 - np.testing.assert_array_almost_equal(grid_added._vals, base_grid._vals + 2) - - grid_added = base_grid + (1 + 1j) - np.testing.assert_array_almost_equal( - grid_added._vals, base_grid._vals + (1 + 1j) + vals_imag_expect = np.array( + [[a_imag * x_i + b_imag * y_i for x_i in x] for y_i in y] ) + vals_expect = vals_real_expect + 1j * vals_imag_expect - def test__radd__numeric(self, base_grid): - grid_added = 2.0 + base_grid - np.testing.assert_array_almost_equal(grid_added._vals, base_grid._vals + 2.0) - - def test__sub__(self, base_grid): - out = base_grid - base_grid - - assert isinstance(out, _BaseGrid) - np.testing.assert_array_almost_equal( - out._vals, base_grid._vals - base_grid._vals + vals_out = grid.interpolate( + y, x, freq_hz=True, degrees=True, complex_convert="rectangular" ) - @patch("waveresponse._core._check_is_similar") - def test__sub__check_is_similar(self, mock_check_is_similar, base_grid): - base_grid - base_grid - mock_check_is_similar.assert_called_once_with( - base_grid, base_grid, exact_type=True - ) + np.testing.assert_array_almost_equal(vals_out, vals_expect) - def test__sub__raises_type(self, base_grid, rao): - with pytest.raises(TypeError): - base_grid - rao + def test_interpolate_complex_polar(self): + a_amp = 7 + b_amp = 6 + a_phase = 0.01 + b_phase = 0.03 - def test__sub__numeric(self, base_grid): - grid_subtracted = base_grid - 2.0 - np.testing.assert_array_almost_equal( - grid_subtracted._vals, base_grid._vals - 2.0 + yp = np.linspace(0.0, 2.0, 20) + xp = np.linspace(0.0, 359.0, 10) + vp_amp = np.array([[a_amp * x_i + b_amp * y_i for x_i in xp] for y_i in yp]) + vp_phase = np.array( + [[a_phase * x_i + b_phase * y_i for x_i in xp] for y_i in yp] ) + vp = vp_amp * (np.cos(vp_phase) + 1j * np.sin(vp_phase)) + grid = Grid(yp, xp, vp, freq_hz=True, degrees=True) - grid_subtracted = base_grid - 0.0 - np.testing.assert_array_almost_equal( - grid_subtracted._vals, base_grid._vals - 0.0 + y = np.linspace(0.0, 2.0, 200) + x = np.linspace(0.0, 359.0, 100) + vals_amp_expect = np.array( + [[a_amp * x_i + b_amp * y_i for x_i in x] for y_i in y] ) + x_, y_ = np.meshgrid(x, y, indexing="ij", sparse=True) + vals_phase_cos_expect = RGI((xp, yp), np.cos(vp_phase).T)((x_, y_)).T + vals_phase_sin_expect = RGI((xp, yp), np.sin(vp_phase).T)((x_, y_)).T - grid_subtracted = base_grid - 2 - np.testing.assert_array_almost_equal(grid_subtracted._vals, base_grid._vals - 2) - - grid_subtracted = base_grid - (1 + 1j) - np.testing.assert_array_almost_equal( - grid_subtracted._vals, base_grid._vals - (1 + 1j) + vals_expect = ( + vals_amp_expect + * (vals_phase_cos_expect + 1j * vals_phase_sin_expect) + / np.abs(vals_phase_cos_expect + 1j * vals_phase_sin_expect) ) - def test__rsub__numeric(self, base_grid): - grid_subtracted = 2.0 - base_grid - np.testing.assert_array_almost_equal( - grid_subtracted._vals, base_grid._vals - 2.0 + vals_out = grid.interpolate( + y, x, freq_hz=True, degrees=True, complex_convert="polar" ) - def test__repr__(self, base_grid): - assert str(base_grid) == "_BaseGrid" - - def test_conjugate(self, base_grid): - grid_conj = base_grid.conjugate() - - np.testing.assert_array_almost_equal(grid_conj._freq, base_grid._freq) - np.testing.assert_array_almost_equal(grid_conj._dirs, base_grid._dirs) - np.testing.assert_array_almost_equal( - grid_conj._vals, base_grid._vals.conjugate() - ) + np.testing.assert_array_almost_equal(vals_out, vals_expect) - def test_real(self): - freq_in = np.array([1, 2, 3]) - dirs_in = np.array([0, 10, 20]) - vals_in = np.array( - [ - [1.0 + 0.0j, 0.0 + 1.0j, -1.0 + 0.0j], - [2.0 + 0.0j, 0.0 - 2.0j, -2.0 + 0.0j], - [3.0 + 0.0j, 0.0 + 3.0j, -3.0 + 0.0j], - ] - ) - grid = _BaseGrid( - freq_in, - dirs_in, - vals_in, + def test_interpolate_raises_freq(self): + freq = np.linspace(0, 1.0, 10) + dirs = np.linspace(0, 360.0, 15, endpoint=False) + vals = np.zeros((10, 15)) + grid = Grid( + freq, + dirs, + vals, + freq_hz=True, degrees=True, clockwise=True, waves_coming_from=True, ) - grid_real = grid.real - - vals_expect = np.array( - [ - [1.0, 0.0, -1.0], - [2.0, 0.0, -2.0], - [3.0, 0.0, -3.0], - ] - ) - - assert isinstance(grid_real, _BaseGrid) - np.testing.assert_array_almost_equal(grid_real._vals, vals_expect) + with pytest.raises(ValueError): + grid.interpolate( + [0, 1, 2, 1], [0, 1, 2] + ) # freq not monotonically increasing - def test_imag(self): - freq_in = np.array([1, 2, 3]) - dirs_in = np.array([0, 10, 20]) - vals_in = np.array( - [ - [1.0 + 0.0j, 0.0 + 1.0j, -1.0 + 0.0j], - [2.0 + 0.0j, 0.0 - 2.0j, -2.0 + 0.0j], - [3.0 + 0.0j, 0.0 + 3.0j, -3.0 + 0.0j], - ] - ) - grid = _BaseGrid( - freq_in, - dirs_in, - vals_in, + def test_interpolate_raises_dirs(self): + freq = np.linspace(0, 1.0, 10) + dirs = np.linspace(0, 360.0, 15, endpoint=False) + vals = np.zeros((10, 15)) + grid = Grid( + freq, + dirs, + vals, + freq_hz=True, degrees=True, clockwise=True, waves_coming_from=True, ) - grid_imag = grid.imag - - vals_expect = np.array( - [ - [0.0, 1.0, 0.0], - [0.0, -2.0, 0.0], - [0.0, 3.0, 0.0], - ] - ) - - assert isinstance(grid_imag, _BaseGrid) - np.testing.assert_array_almost_equal(grid_imag._vals, vals_expect) - + with pytest.raises(ValueError): + grid.interpolate( + [0, 1, 2], [0, 1, 2, 1] + ) # dirs not monotonically increasing -class Test_Grid: - def test__init__(self): + def test_interpolate_raises_dirs_outside_bound(self): freq = np.linspace(0, 1.0, 10) dirs = np.linspace(0, 360.0, 15, endpoint=False) vals = np.zeros((10, 15)) @@ -1622,16 +1005,12 @@ def test__init__(self): waves_coming_from=True, ) - assert isinstance(grid, _BaseGrid) - np.testing.assert_array_almost_equal(grid._freq, 2.0 * np.pi * freq) - np.testing.assert_array_almost_equal(grid._dirs, (np.pi / 180.0) * dirs) - np.testing.assert_array_almost_equal(grid._vals, vals) - assert grid._clockwise is True - assert grid._waves_coming_from is True - assert grid._freq_hz is True - assert grid._degrees is True + with pytest.raises(ValueError): + grid.interpolate( + [0, 1, 2], [0, 1, 2, 100], degrees=False + ) # dirs outside bound - def test_interpolate(self): + def test_reshape(self): a = 7 b = 6 @@ -1642,13 +1021,21 @@ def test_interpolate(self): y = np.linspace(0.5, 1.0, 20) x = np.linspace(5.0, 15.0, 10) + grid_reshaped = grid.reshape(y, x, freq_hz=True, degrees=True) + + freq_expect = (2.0 * np.pi) * y + dirs_expect = (np.pi / 180.0) * x vals_expect = np.array([[a * x_i + b * y_i for x_i in x] for y_i in y]) - vals_out = grid.interpolate(y, x, freq_hz=True, degrees=True) + freq_out = grid_reshaped._freq + dirs_out = grid_reshaped._dirs + vals_out = grid_reshaped._vals + np.testing.assert_array_almost_equal(freq_out, freq_expect) + np.testing.assert_array_almost_equal(dirs_out, dirs_expect) np.testing.assert_array_almost_equal(vals_out, vals_expect) - def test_interpolate2(self): + def test_reshape2(self): a = 7 b = 6 @@ -1659,80 +1046,23 @@ def test_interpolate2(self): y = np.linspace(0.5, 1.0, 20) x = np.linspace(5.0, 15.0, 10) - vals_expect = np.array([[a * x_i + b * y_i for x_i in x] for y_i in y]) - y_ = (2.0 * np.pi) * y x_ = (np.pi / 180.0) * x - vals_out = grid.interpolate(y_, x_, freq_hz=False, degrees=False) - - np.testing.assert_array_almost_equal(vals_out, vals_expect) - - def test_interpolate_single_coordinate(self): - a = 7 - b = 6 - - yp = np.linspace(0.0, 2.0, 20) - xp = np.linspace(0.0, 359.0, 10) - vp = np.array([[a * x_i + b * y_i for x_i in xp] for y_i in yp]) - grid = Grid(yp, xp, vp, freq_hz=True, degrees=True) - - vals_out = grid.interpolate(1.8, 12.1, freq_hz=True, degrees=True) - - vals_expect = np.array(a * 12.1 + b * 1.8) - - np.testing.assert_array_almost_equal(vals_out, vals_expect) - - def test_interpolate_fill_value(self): - freq = np.array([0, 1, 2]) - dirs = np.array([0, 90, 180, 270]) - vals = np.array( - [ - [1, 2, 3, 4], - [1, 2, 3, 4], - [1, 2, 3, 4], - ] - ) - grid = Grid(freq, dirs, vals, freq_hz=True, degrees=True) - - # extrapolate - vals_out = grid.interpolate([10, 20], [0, 90], freq_hz=True, degrees=True) - - vals_expect = np.array( - [ - [0.0, 0.0], - [0.0, 0.0], - ] - ) - - np.testing.assert_array_almost_equal(vals_out, vals_expect) - - def test_interpolate_fill_value_None(self): - freq = np.array([0, 1, 2]) - dirs = np.array([0, 90, 180, 270]) - vals = np.array( - [ - [1, 2, 3, 4], - [1, 2, 3, 4], - [1, 2, 3, 4], - ] - ) - grid = Grid(freq, dirs, vals, freq_hz=True, degrees=True) + grid_reshaped = grid.reshape(y_, x_, freq_hz=False, degrees=False) - # extrapolate - vals_out = grid.interpolate( - [10, 20], [0, 90], freq_hz=True, degrees=True, fill_value=None - ) + freq_expect = (2.0 * np.pi) * y + dirs_expect = (np.pi / 180.0) * x + vals_expect = np.array([[a * x_i + b * y_i for x_i in x] for y_i in y]) - vals_expect = np.array( - [ - [1.0, 2.0], - [1.0, 2.0], - ] - ) + freq_out = grid_reshaped._freq + dirs_out = grid_reshaped._dirs + vals_out = grid_reshaped._vals + np.testing.assert_array_almost_equal(freq_out, freq_expect) + np.testing.assert_array_almost_equal(dirs_out, dirs_expect) np.testing.assert_array_almost_equal(vals_out, vals_expect) - def test_interpolate_complex_rectangular(self): + def test_reshape_complex_rectangular(self): a_real = 7 b_real = 6 a_imag = 3 @@ -1747,6 +1077,16 @@ def test_interpolate_complex_rectangular(self): y = np.linspace(0.5, 1.0, 20) x = np.linspace(5.0, 15.0, 10) + grid_reshaped = grid.reshape( + y, x, freq_hz=True, degrees=True, complex_convert="rectangular" + ) + + freq_out = grid_reshaped._freq + dirs_out = grid_reshaped._dirs + vals_out = grid_reshaped._vals + + freq_expect = (2.0 * np.pi) * y + dirs_expect = (np.pi / 180.0) * x vals_real_expect = np.array( [[a_real * x_i + b_real * y_i for x_i in x] for y_i in y] ) @@ -1755,13 +1095,11 @@ def test_interpolate_complex_rectangular(self): ) vals_expect = vals_real_expect + 1j * vals_imag_expect - vals_out = grid.interpolate( - y, x, freq_hz=True, degrees=True, complex_convert="rectangular" - ) - + np.testing.assert_array_almost_equal(freq_out, freq_expect) + np.testing.assert_array_almost_equal(dirs_out, dirs_expect) np.testing.assert_array_almost_equal(vals_out, vals_expect) - def test_interpolate_complex_polar(self): + def test_reshape_complex_polar(self): a_amp = 7 b_amp = 6 a_phase = 0.01 @@ -1776,8 +1114,18 @@ def test_interpolate_complex_polar(self): vp = vp_amp * (np.cos(vp_phase) + 1j * np.sin(vp_phase)) grid = Grid(yp, xp, vp, freq_hz=True, degrees=True) - y = np.linspace(0.0, 2.0, 200) - x = np.linspace(0.0, 359.0, 100) + y = np.linspace(0.5, 1.0, 20) + x = np.linspace(5.0, 15.0, 10) + grid_reshaped = grid.reshape( + y, x, freq_hz=True, degrees=True, complex_convert="polar" + ) + + freq_out = grid_reshaped._freq + dirs_out = grid_reshaped._dirs + vals_out = grid_reshaped._vals + + freq_expect = (2.0 * np.pi) * y + dirs_expect = (np.pi / 180.0) * x vals_amp_expect = np.array( [[a_amp * x_i + b_amp * y_i for x_i in x] for y_i in y] ) @@ -1791,13 +1139,28 @@ def test_interpolate_complex_polar(self): / np.abs(vals_phase_cos_expect + 1j * vals_phase_sin_expect) ) - vals_out = grid.interpolate( - y, x, freq_hz=True, degrees=True, complex_convert="polar" + np.testing.assert_array_almost_equal(freq_out, freq_expect) + np.testing.assert_array_almost_equal(dirs_out, dirs_expect) + np.testing.assert_array_almost_equal(vals_out, vals_expect) + + def test_reshape_raises_freq(self): + freq = np.linspace(0, 1.0, 10) + dirs = np.linspace(0, 360.0, 15, endpoint=False) + vals = np.zeros((10, 15)) + grid = Grid( + freq, + dirs, + vals, + freq_hz=True, + degrees=True, + clockwise=True, + waves_coming_from=True, ) - np.testing.assert_array_almost_equal(vals_out, vals_expect) + with pytest.raises(ValueError): + grid.reshape([0, 1, 2, 1], [0, 1, 2]) # freq not monotonically increasing - def test_interpolate_raises_freq(self): + def test_reshape_raises_dirs(self): freq = np.linspace(0, 1.0, 10) dirs = np.linspace(0, 360.0, 15, endpoint=False) vals = np.zeros((10, 15)) @@ -1812,11 +1175,206 @@ def test_interpolate_raises_freq(self): ) with pytest.raises(ValueError): - grid.interpolate( - [0, 1, 2, 1], [0, 1, 2] - ) # freq not monotonically increasing + grid.reshape([0, 1, 2], [0, 1, 2, 1]) # dirs not monotonically increasing + + def test__repr__(self, grid): + assert str(grid) == "Grid" + + def test__init__2(self): + freq = np.linspace(0, 1.0, 10) + dirs = np.linspace(0, 2.0 * np.pi, 15, endpoint=False) + vals = np.zeros((10, 15)) + grid = Grid( + freq, + dirs, + vals, + freq_hz=False, + degrees=False, + clockwise=False, + waves_coming_from=False, + ) + + np.testing.assert_array_almost_equal(grid._freq, freq) + np.testing.assert_array_almost_equal(grid._dirs, dirs) + np.testing.assert_array_almost_equal(grid._vals, vals) + assert grid._clockwise is False + assert grid._waves_coming_from is False + assert grid._freq_hz is False + assert grid._degrees is False + + def test__init__raises_duplicate_freq(self): + with pytest.raises(ValueError): + freq = np.array([0, 1, 1, 2]) + dirs = np.array([0, 1, 2]) + vals = np.zeros((4, 3)) + Grid(freq, dirs, vals) + + def test__init__raises_duplicate_dirs(self): + with pytest.raises(ValueError): + freq = np.array([0, 1, 2]) + dirs = np.array([0, 1, 1, 2]) + vals = np.zeros((3, 4)) + Grid(freq, dirs, vals) + + def test__init__raises_negative_freq(self): + with pytest.raises(ValueError): + freq = np.array([-1, 1, 2, 3]) + dirs = np.array([0, 1, 2]) + vals = np.zeros((4, 3)) + Grid(freq, dirs, vals) + + def test__init__raises_negative_dirs(self): + with pytest.raises(ValueError): + freq = np.array([0, 1, 2, 3]) + dirs = np.array([-1, 1, 2]) + vals = np.zeros((4, 3)) + Grid(freq, dirs, vals) + + def test__init__raises_freq_not_increasing(self): + with pytest.raises(ValueError): + freq = np.array([0, 1, 2, 1]) + dirs = np.array([0, 1, 2]) + vals = np.zeros((4, 3)) + Grid(freq, dirs, vals) + + def test__init__raises_dirs_not_increasing(self): + with pytest.raises(ValueError): + freq = np.array([0, 1, 2]) + dirs = np.array([0, 1, 2, 1]) + vals = np.zeros((3, 4)) + Grid(freq, dirs, vals) + + def test__init__raises_dirs_2pi(self): + with pytest.raises(ValueError): + freq = np.array([0, 1, 2]) + dirs = np.array([0, 1, 2, 2.0 * np.pi]) + vals = np.zeros((3, 4)) + Grid(freq, dirs, vals, degrees=False) + + def test__init__raises_dirs_greater_than_2pi(self): + with pytest.raises(ValueError): + freq = np.array([0, 1, 2]) + dirs = np.array([0, 1, 2, 3.0 * np.pi]) + vals = np.zeros((3, 4)) + Grid(freq, dirs, vals, degrees=False) + + def test__init__raises_freq_not_1d(self): + with pytest.raises(ValueError, match="`freq` must be 1 dimensional."): + freq = np.array([[0, 1], [2, 3]]) + dirs = np.array([0.0, 1.0, 1.5, 2.0]) + vals = np.zeros((4, 4)) + Grid(freq, dirs, vals) + + def test__init__raises_dirs_not_1d(self): + with pytest.raises(ValueError, match="`dirs` must be 1 dimensional."): + freq = np.array([0, 1, 2, 3]) + dirs = np.array([[0.0, 1.0], [1.5, 2.0]]) + vals = np.zeros((4, 4)) + Grid(freq, dirs, vals) + + def test__init__raises_vals_shape(self): + with pytest.raises(ValueError): + freq = np.array([0, 1, 2]) + dirs = np.array([0, 1, 2, 3]) + vals = np.zeros((3, 10)) + Grid(freq, dirs, vals) + + def test_from_grid(self): + freq = np.linspace(0, 1.0, 10) + dirs = np.linspace(0, 360.0, 15, endpoint=False) + vals = np.random.random((10, 15)) + grid_in = Grid( + freq, + dirs, + vals, + freq_hz=True, + degrees=True, + clockwise=True, + waves_coming_from=True, + ) + + grid_out = Grid.from_grid(grid_in) + + vals_expect = vals.copy() + + assert isinstance(grid_out, Grid) + np.testing.assert_array_almost_equal(grid_out._freq, grid_in._freq) + np.testing.assert_array_almost_equal(grid_out._dirs, grid_in._dirs) + np.testing.assert_array_almost_equal(grid_out._vals, vals_expect) + assert grid_out._clockwise == grid_in._clockwise + assert grid_out._waves_coming_from == grid_in._waves_coming_from + assert grid_out._freq_hz == grid_in._freq_hz + assert grid_out._degrees == grid_in._degrees + + def test_freq_None(self): + freq = np.linspace(0, 1.0, 10) + dirs = np.linspace(0, 360.0, 15, endpoint=False) + vals = np.zeros((10, 15)) + grid = Grid( + freq, + dirs, + vals, + freq_hz=True, + degrees=True, + clockwise=True, + waves_coming_from=True, + ) + + freq_out = grid.freq() + np.testing.assert_array_almost_equal(freq_out, freq) + + def test_freq_rads(self): + freq = np.linspace(0, 1.0, 10) + dirs = np.linspace(0, 360.0, 15, endpoint=False) + vals = np.zeros((10, 15)) + grid = Grid( + freq, + dirs, + vals, + freq_hz=True, + degrees=True, + clockwise=True, + waves_coming_from=True, + ) + + freq_out = grid.freq(freq_hz=False) + np.testing.assert_array_almost_equal(freq_out, (2.0 * np.pi) * freq) + + def test_freq_hz(self): + freq = np.linspace(0, 1.0, 10) + dirs = np.linspace(0, 360.0, 15, endpoint=False) + vals = np.zeros((10, 15)) + grid = Grid( + freq, + dirs, + vals, + freq_hz=True, + degrees=True, + clockwise=True, + waves_coming_from=True, + ) + + freq_out = grid.freq(freq_hz=True) + np.testing.assert_array_almost_equal(freq_out, freq) + + def test_dirs_None(self): + freq = np.linspace(0, 1.0, 10) + dirs = np.linspace(0, 360.0, 15, endpoint=False) + vals = np.zeros((10, 15)) + grid = Grid( + freq, + dirs, + vals, + freq_hz=True, + degrees=True, + clockwise=True, + waves_coming_from=True, + ) - def test_interpolate_raises_dirs(self): + dirs_out = grid.dirs() + np.testing.assert_array_almost_equal(dirs_out, dirs) + + def test_dirs_rad(self): freq = np.linspace(0, 1.0, 10) dirs = np.linspace(0, 360.0, 15, endpoint=False) vals = np.zeros((10, 15)) @@ -1830,12 +1388,10 @@ def test_interpolate_raises_dirs(self): waves_coming_from=True, ) - with pytest.raises(ValueError): - grid.interpolate( - [0, 1, 2], [0, 1, 2, 1] - ) # dirs not monotonically increasing + dirs_out = grid.dirs(degrees=False) + np.testing.assert_array_almost_equal(dirs_out, (np.pi / 180.0) * dirs) - def test_interpolate_raises_dirs_outside_bound(self): + def test_dirs_deg(self): freq = np.linspace(0, 1.0, 10) dirs = np.linspace(0, 360.0, 15, endpoint=False) vals = np.zeros((10, 15)) @@ -1849,527 +1405,596 @@ def test_interpolate_raises_dirs_outside_bound(self): waves_coming_from=True, ) - with pytest.raises(ValueError): - grid.interpolate( - [0, 1, 2], [0, 1, 2, 100], degrees=False - ) # dirs outside bound - - def test_reshape(self): - a = 7 - b = 6 - - yp = np.linspace(0.0, 2.0, 20) - xp = np.linspace(0.0, 359.0, 10) - vp = np.array([[a * x_i + b * y_i for x_i in xp] for y_i in yp]) - grid = Grid(yp, xp, vp, freq_hz=True, degrees=True) + dirs_out = grid.dirs(degrees=True) + np.testing.assert_array_almost_equal(dirs_out, dirs) - y = np.linspace(0.5, 1.0, 20) - x = np.linspace(5.0, 15.0, 10) - grid_reshaped = grid.reshape(y, x, freq_hz=True, degrees=True) + def test_wave_convention(self, grid): + convention_expect = {"clockwise": True, "waves_coming_from": True} + convention_out = grid.wave_convention + assert convention_out == convention_expect - freq_expect = (2.0 * np.pi) * y - dirs_expect = (np.pi / 180.0) * x - vals_expect = np.array([[a * x_i + b * y_i for x_i in x] for y_i in y]) + def test__convert_dirs_radians(self): + dirs_in = np.array([0, np.pi / 4, np.pi / 2, 3.0 * np.pi / 4, np.pi]) + config_org = {"clockwise": False, "waves_coming_from": True} + config_new = {"clockwise": True, "waves_coming_from": False} + dirs_out = Grid._convert_dirs( + dirs_in, config_new, config_org, degrees=False + ) - freq_out = grid_reshaped._freq - dirs_out = grid_reshaped._dirs - vals_out = grid_reshaped._vals + dirs_expect = np.array([np.pi, 3.0 * np.pi / 4, np.pi / 2, np.pi / 4, 0]) - np.testing.assert_array_almost_equal(freq_out, freq_expect) np.testing.assert_array_almost_equal(dirs_out, dirs_expect) - np.testing.assert_array_almost_equal(vals_out, vals_expect) - def test_reshape2(self): - a = 7 - b = 6 + def test__convert_dirs_degrees(self): + dirs_in = np.array([0, 45.0, 90.0, 135.0, 180.0]) + config_org = {"clockwise": False, "waves_coming_from": True} + config_new = {"clockwise": True, "waves_coming_from": False} + dirs_out = Grid._convert_dirs( + dirs_in, config_new, config_org, degrees=True + ) - yp = np.linspace(0.0, 2.0, 20) - xp = np.linspace(0.0, 359.0, 10) - vp = np.array([[a * x_i + b * y_i for x_i in xp] for y_i in yp]) - grid = Grid(yp, xp, vp, freq_hz=True, degrees=True) + dirs_expect = np.array([180.0, 135.0, 90.0, 45.0, 0]) - y = np.linspace(0.5, 1.0, 20) - x = np.linspace(5.0, 15.0, 10) - y_ = (2.0 * np.pi) * y - x_ = (np.pi / 180.0) * x - grid_reshaped = grid.reshape(y_, x_, freq_hz=False, degrees=False) + np.testing.assert_array_almost_equal(dirs_out, dirs_expect) - freq_expect = (2.0 * np.pi) * y - dirs_expect = (np.pi / 180.0) * x - vals_expect = np.array([[a * x_i + b * y_i for x_i in x] for y_i in y]) + def test__convert(self): + freq = np.linspace(0, 1.0, 10) + dirs = np.linspace(0, 360.0, 15, endpoint=False) + vals = np.zeros((10, 15)) + grid = Grid( + freq, + dirs, + vals, + freq_hz=True, + degrees=True, + clockwise=False, + waves_coming_from=True, + ) - freq_out = grid_reshaped._freq - dirs_out = grid_reshaped._dirs - vals_out = grid_reshaped._vals + freq_in = np.array([0.0, 0.5, 1.0]) + dirs_in = np.array([0, np.pi / 4, np.pi / 2, 3.0 * np.pi / 4, np.pi]) + vals_in = np.array( + [ + [1.0, 2.0, 3.0, 4.0, 5.0], + [1.0, 2.0, 3.0, 4.0, 5.0], + ] + ) + config_org = {"clockwise": False, "waves_coming_from": True} + config_new = {"clockwise": True, "waves_coming_from": False} + freq_out, dirs_out, vals_out = grid._convert( + freq_in, dirs_in, vals_in, config_new, config_org + ) + + freq_expect = freq_in + dirs_expect = np.array([0, np.pi / 4, np.pi / 2, 3.0 * np.pi / 4, np.pi]) + vals_expect = np.array( + [ + [5.0, 4.0, 3.0, 2.0, 1.0], + [5.0, 4.0, 3.0, 2.0, 1.0], + ] + ) np.testing.assert_array_almost_equal(freq_out, freq_expect) np.testing.assert_array_almost_equal(dirs_out, dirs_expect) np.testing.assert_array_almost_equal(vals_out, vals_expect) - def test_reshape_complex_rectangular(self): - a_real = 7 - b_real = 6 - a_imag = 3 - b_imag = 9 - - yp = np.linspace(0.0, 2.0, 20) - xp = np.linspace(0.0, 359.0, 10) - vp_real = np.array([[a_real * x_i + b_real * y_i for x_i in xp] for y_i in yp]) - vp_imag = np.array([[a_imag * x_i + b_imag * y_i for x_i in xp] for y_i in yp]) - vp = vp_real + 1j * vp_imag - grid = Grid(yp, xp, vp, freq_hz=True, degrees=True) - - y = np.linspace(0.5, 1.0, 20) - x = np.linspace(5.0, 15.0, 10) - grid_reshaped = grid.reshape( - y, x, freq_hz=True, degrees=True, complex_convert="rectangular" + def test_set_wave_convention(self): + freq_in = np.array([0.0, 0.5, 1.0]) + dirs_in = np.array([0, np.pi / 4, np.pi / 2, 3.0 * np.pi / 4, np.pi]) + vals_in = np.array( + [ + [1.0, 2.0, 3.0, 4.0, 5.0], + [1.0, 2.0, 3.0, 4.0, 5.0], + [1.0, 2.0, 3.0, 4.0, 5.0], + ] + ) + grid = Grid( + freq_in, + dirs_in, + vals_in, + freq_hz=True, + degrees=False, + clockwise=False, + waves_coming_from=True, ) - freq_out = grid_reshaped._freq - dirs_out = grid_reshaped._dirs - vals_out = grid_reshaped._vals + grid.set_wave_convention(clockwise=True, waves_coming_from=False) - freq_expect = (2.0 * np.pi) * y - dirs_expect = (np.pi / 180.0) * x - vals_real_expect = np.array( - [[a_real * x_i + b_real * y_i for x_i in x] for y_i in y] - ) - vals_imag_expect = np.array( - [[a_imag * x_i + b_imag * y_i for x_i in x] for y_i in y] + freq_expect = (2.0 * np.pi) * freq_in + dirs_expect = np.array([0, np.pi / 4, np.pi / 2, 3.0 * np.pi / 4, np.pi]) + vals_expect = np.array( + [ + [5.0, 4.0, 3.0, 2.0, 1.0], + [5.0, 4.0, 3.0, 2.0, 1.0], + [5.0, 4.0, 3.0, 2.0, 1.0], + ] ) - vals_expect = vals_real_expect + 1j * vals_imag_expect - np.testing.assert_array_almost_equal(freq_out, freq_expect) - np.testing.assert_array_almost_equal(dirs_out, dirs_expect) - np.testing.assert_array_almost_equal(vals_out, vals_expect) + np.testing.assert_array_almost_equal(grid._freq, freq_expect) + np.testing.assert_array_almost_equal(grid._dirs, dirs_expect) + np.testing.assert_array_almost_equal(grid._vals, vals_expect) + assert grid._clockwise is True + assert grid._waves_coming_from is False - def test_reshape_complex_polar(self): - a_amp = 7 - b_amp = 6 - a_phase = 0.01 - b_phase = 0.03 + def test_copy(self, grid): + grid_copy = grid.copy() + assert grid is grid + assert grid_copy is not grid + np.testing.assert_array_almost_equal(grid_copy._freq, grid._freq) + np.testing.assert_array_almost_equal(grid_copy._dirs, grid._dirs) + np.testing.assert_array_almost_equal(grid_copy._vals, grid._vals) + assert grid_copy._clockwise == grid._clockwise + assert grid_copy._waves_coming_from == grid._waves_coming_from - yp = np.linspace(0.0, 2.0, 20) - xp = np.linspace(0.0, 359.0, 10) - vp_amp = np.array([[a_amp * x_i + b_amp * y_i for x_i in xp] for y_i in yp]) - vp_phase = np.array( - [[a_phase * x_i + b_phase * y_i for x_i in xp] for y_i in yp] + def test_rotate_deg(self): + freq = np.array([0, 1]) + dirs = np.array([0, 90, 180]) + vals = np.array( + [ + [1, 2, 3], + [1, 2, 3], + ] + ) + grid = Grid( + freq, + dirs, + vals, + freq_hz=True, + degrees=True, + clockwise=False, + waves_coming_from=True, ) - vp = vp_amp * (np.cos(vp_phase) + 1j * np.sin(vp_phase)) - grid = Grid(yp, xp, vp, freq_hz=True, degrees=True) - y = np.linspace(0.5, 1.0, 20) - x = np.linspace(5.0, 15.0, 10) - grid_reshaped = grid.reshape( - y, x, freq_hz=True, degrees=True, complex_convert="polar" + grid_rot = grid.rotate(45, degrees=True) + + freq_expect = (2.0 * np.pi) * np.array([0, 1]) + dirs_expect = (np.pi / 180.0) * np.array([45, 135, 315]) + vals_expect = np.array( + [ + [2, 3, 1], + [2, 3, 1], + ] ) - freq_out = grid_reshaped._freq - dirs_out = grid_reshaped._dirs - vals_out = grid_reshaped._vals + np.testing.assert_array_almost_equal(grid_rot._freq, freq_expect) + np.testing.assert_array_almost_equal(grid_rot._dirs, dirs_expect) + np.testing.assert_array_almost_equal(grid_rot._vals, vals_expect) - freq_expect = (2.0 * np.pi) * y - dirs_expect = (np.pi / 180.0) * x - vals_amp_expect = np.array( - [[a_amp * x_i + b_amp * y_i for x_i in x] for y_i in y] + def test_rotate_deg_neg(self): + freq = np.array([0, 1]) + dirs = np.array([0, 90, 180]) + vals = np.array( + [ + [1, 2, 3], + [1, 2, 3], + ] + ) + grid = Grid( + freq, + dirs, + vals, + freq_hz=True, + degrees=True, + clockwise=False, + waves_coming_from=True, ) - x_, y_ = np.meshgrid(x, y, indexing="ij", sparse=True) - vals_phase_cos_expect = RGI((xp, yp), np.cos(vp_phase).T)((x_, y_)).T - vals_phase_sin_expect = RGI((xp, yp), np.sin(vp_phase).T)((x_, y_)).T - vals_expect = ( - vals_amp_expect - * (vals_phase_cos_expect + 1j * vals_phase_sin_expect) - / np.abs(vals_phase_cos_expect + 1j * vals_phase_sin_expect) + grid_rot = grid.rotate(-45, degrees=True) + + freq_expect = (2.0 * np.pi) * np.array([0, 1]) + dirs_expect = (np.pi / 180.0) * np.array([45, 135, 225]) + vals_expect = np.array( + [ + [1, 2, 3], + [1, 2, 3], + ] ) - np.testing.assert_array_almost_equal(freq_out, freq_expect) - np.testing.assert_array_almost_equal(dirs_out, dirs_expect) - np.testing.assert_array_almost_equal(vals_out, vals_expect) + np.testing.assert_array_almost_equal(grid_rot._freq, freq_expect) + np.testing.assert_array_almost_equal(grid_rot._dirs, dirs_expect) + np.testing.assert_array_almost_equal(grid_rot._vals, vals_expect) - def test_reshape_raises_freq(self): - freq = np.linspace(0, 1.0, 10) - dirs = np.linspace(0, 360.0, 15, endpoint=False) - vals = np.zeros((10, 15)) + def test_rotate_rad(self): + freq = np.array([0, 1]) + dirs = np.array([0, 90, 180]) + vals = np.array( + [ + [1, 2, 3], + [1, 2, 3], + ] + ) grid = Grid( freq, dirs, vals, freq_hz=True, degrees=True, - clockwise=True, + clockwise=False, waves_coming_from=True, ) - with pytest.raises(ValueError): - grid.reshape([0, 1, 2, 1], [0, 1, 2]) # freq not monotonically increasing + grid_rot = grid.rotate(np.pi / 4, degrees=False) - def test_reshape_raises_dirs(self): - freq = np.linspace(0, 1.0, 10) - dirs = np.linspace(0, 360.0, 15, endpoint=False) - vals = np.zeros((10, 15)) + freq_expect = (2.0 * np.pi) * np.array([0, 1]) + dirs_expect = (np.pi / 180.0) * np.array([45, 135, 315]) + vals_expect = np.array( + [ + [2, 3, 1], + [2, 3, 1], + ] + ) + + np.testing.assert_array_almost_equal(grid_rot._freq, freq_expect) + np.testing.assert_array_almost_equal(grid_rot._dirs, dirs_expect) + np.testing.assert_array_almost_equal(grid_rot._vals, vals_expect) + + def test_rotate_rad_neg(self): + freq = np.array([0, 1]) + dirs = np.array([0, 90, 180]) + vals = np.array( + [ + [1, 2, 3], + [1, 2, 3], + ] + ) grid = Grid( freq, dirs, vals, freq_hz=True, degrees=True, - clockwise=True, + clockwise=False, waves_coming_from=True, ) - with pytest.raises(ValueError): - grid.reshape([0, 1, 2], [0, 1, 2, 1]) # dirs not monotonically increasing + grid_rot = grid.rotate(-np.pi / 4, degrees=False) - def test__repr__(self, grid): - assert str(grid) == "Grid" + freq_expect = (2.0 * np.pi) * np.array([0, 1]) + dirs_expect = (np.pi / 180.0) * np.array([45, 135, 225]) + vals_expect = np.array( + [ + [1, 2, 3], + [1, 2, 3], + ] + ) + np.testing.assert_array_almost_equal(grid_rot._freq, freq_expect) + np.testing.assert_array_almost_equal(grid_rot._dirs, dirs_expect) + np.testing.assert_array_almost_equal(grid_rot._vals, vals_expect) -class Test_BinGrid: - def test__init__(self): - freq = np.linspace(0, 1.0, 10) - dirs = np.linspace(0, 360.0, 15, endpoint=False) - vals = np.zeros((10, 15)) - grid = BinGrid( + def test_grid(self): + freq = np.array([0, 1]) + dirs = np.array([0, 90, 180]) + vals = np.array( + [ + [1, 2, 3], + [1, 2, 3], + ] + ) + grid = Grid( freq, dirs, vals, freq_hz=True, degrees=True, - clockwise=True, + clockwise=False, waves_coming_from=True, ) - assert isinstance(grid, _BaseGrid) - np.testing.assert_array_almost_equal(grid._freq, 2.0 * np.pi * freq) - np.testing.assert_array_almost_equal(grid._dirs, (np.pi / 180.0) * dirs) - np.testing.assert_array_almost_equal(grid._vals, vals) - assert grid._clockwise is True - assert grid._waves_coming_from is True - assert grid._freq_hz is True - assert grid._degrees is True - - def test_interpolate(self): - a = 7 - b = 6 - - yp = np.linspace(0.0, 2.0, 20) - xp = np.linspace(0.0, 359.0, 10) - vp = np.array([[a * x_i + b * y_i for x_i in xp] for y_i in yp]) - bingrid = BinGrid(yp, xp, vp, freq_hz=True, degrees=True) - - y = np.linspace(0.5, 1.0, 20) - x = xp - vals_expect = np.array([[a * x_i + b * y_i for x_i in x] for y_i in y]) - - vals_out = bingrid.interpolate(y, freq_hz=True) - - np.testing.assert_array_almost_equal(vals_out, vals_expect) - - def test_interpolate2(self): - a = 7 - b = 6 - - yp = np.linspace(0.0, 2.0, 20) - xp = np.linspace(0.0, 359.0, 10) - vp = np.array([[a * x_i + b * y_i for x_i in xp] for y_i in yp]) - bingrid = BinGrid(yp, xp, vp, freq_hz=True, degrees=True) - - y = np.linspace(0.5, 1.0, 20) - x = xp - vals_expect = np.array([[a * x_i + b * y_i for x_i in x] for y_i in y]) - - y_ = (2.0 * np.pi) * y - vals_out = bingrid.interpolate(y_, freq_hz=False) - - np.testing.assert_array_almost_equal(vals_out, vals_expect) - - def test_interpolate_single_coordinate(self): - a = 7 - b = 6 + freq_out, dirs_out, vals_out = grid.grid(freq_hz=True, degrees=True) - yp = np.linspace(0.0, 2.0, 20) - xp = np.linspace(0.0, 359.0, 10) - vp = vp = np.tile( - np.linspace(0.0, 1.0, len(yp), endpoint=False).reshape(-1, 1), (1, len(xp)) + freq_expect = np.array([0, 1]) + dirs_expect = np.array([0, 90, 180]) + vals_expect = np.array( + [ + [1, 2, 3], + [1, 2, 3], + ] ) - bingrid = BinGrid(yp, xp, vp, freq_hz=True, degrees=True) - - y_ = (yp[4] + yp[5]) / 2 - vals_out = bingrid.interpolate(y_, freq_hz=True) - - vals_expect = ((vp[4] + vp[5]) / 2).reshape(1, -1) + np.testing.assert_array_almost_equal(freq_out, freq_expect) + np.testing.assert_array_almost_equal(dirs_out, dirs_expect) np.testing.assert_array_almost_equal(vals_out, vals_expect) - def test_interpolate_fill_value(self): - freq = np.array([0, 1, 2]) - dirs = np.array([0, 90, 180, 270]) + def test__grid2(self): + freq = np.array([0, 1]) + dirs = np.array([0, 90, 180]) vals = np.array( [ - [1, 2, 3, 4], - [1, 2, 3, 4], - [1, 2, 3, 4], + [1, 2, 3], + [1, 2, 3], ] ) - grid = BinGrid(freq, dirs, vals, freq_hz=True, degrees=True) + grid = Grid( + freq, + dirs, + vals, + freq_hz=True, + degrees=True, + clockwise=False, + waves_coming_from=True, + ) - # extrapolate - vals_out = grid.interpolate([10, 20], freq_hz=True) + freq_out, dirs_out, vals_out = grid.grid(freq_hz=False, degrees=False) - vals_expect = np.zeros((2, len(dirs))) + freq_expect = (2.0 * np.pi) * np.array([0, 1]) + dirs_expect = (np.pi / 180.0) * np.array([0, 90, 180]) + vals_expect = np.array( + [ + [1, 2, 3], + [1, 2, 3], + ] + ) + np.testing.assert_array_almost_equal(freq_out, freq_expect) + np.testing.assert_array_almost_equal(dirs_out, dirs_expect) np.testing.assert_array_almost_equal(vals_out, vals_expect) - def test_interpolate_fill_value_None(self): - freq = np.array([0, 1, 2]) - dirs = np.array([0, 90, 180, 270]) - vals = np.array( + def test__mul__(self): + freq_in = np.array([1, 2, 3]) + dirs_in = np.array([0, 10, 20, 30]) + vals_in = np.array( [ [1, 2, 3, 4], [1, 2, 3, 4], [1, 2, 3, 4], ] ) - grid = BinGrid(freq, dirs, vals, freq_hz=True, degrees=True) + grid = Grid(freq_in, dirs_in, vals_in, degrees=True) - # extrapolate - vals_out = grid.interpolate([10, 20], freq_hz=True, fill_value=None) + grid_squared = grid * grid vals_expect = np.array( [ - [1.0, 2.0, 3.0, 4.0], - [1.0, 2.0, 3.0, 4.0], + [1, 4, 9, 16], + [1, 4, 9, 16], + [1, 4, 9, 16], ] ) - np.testing.assert_array_almost_equal(vals_out, vals_expect) + assert isinstance(grid_squared, Grid) + assert grid_squared._clockwise == grid._clockwise + assert grid_squared._waves_coming_from == grid._waves_coming_from + np.testing.assert_array_almost_equal(grid_squared._freq, grid._freq) + np.testing.assert_array_almost_equal(grid_squared._dirs, grid._dirs) + np.testing.assert_array_almost_equal(grid_squared._vals, vals_expect) - def test_interpolate_complex_rectangular(self): - a_real = 7 - b_real = 6 - a_imag = 3 - b_imag = 9 + def test__mul__numeric(self, grid): + grid_scaled = grid * 2.0 + np.testing.assert_array_almost_equal(grid_scaled._vals, grid._vals * 2.0) - yp = np.linspace(0.0, 2.0, 20) - xp = np.linspace(0.0, 359.0, 10) - vp_real = np.array([[a_real * x_i + b_real * y_i for x_i in xp] for y_i in yp]) - vp_imag = np.array([[a_imag * x_i + b_imag * y_i for x_i in xp] for y_i in yp]) - vp = vp_real + 1j * vp_imag - grid = BinGrid(yp, xp, vp, freq_hz=True, degrees=True) + grid_scaled = grid * -2.0 + np.testing.assert_array_almost_equal(grid_scaled._vals, grid._vals * -2.0) - y = np.linspace(0.5, 1.0, 20) - x = xp - vals_real_expect = np.array( - [[a_real * x_i + b_real * y_i for x_i in x] for y_i in y] - ) - vals_imag_expect = np.array( - [[a_imag * x_i + b_imag * y_i for x_i in x] for y_i in y] - ) - vals_expect = vals_real_expect + 1j * vals_imag_expect + grid_scaled = grid * 0.0 + np.testing.assert_array_almost_equal(grid_scaled._vals, grid._vals * 0.0) - vals_out = grid.interpolate(y, freq_hz=True, complex_convert="rectangular") + grid_scaled = grid * 2 + np.testing.assert_array_almost_equal(grid_scaled._vals, grid._vals * 2) - np.testing.assert_array_almost_equal(vals_out, vals_expect) + grid_scaled = grid * (1 + 1j) + np.testing.assert_array_almost_equal( + grid_scaled._vals, grid._vals * (1 + 1j) + ) - def test_interpolate_complex_polar(self): - a_amp = 7 - b_amp = 6 - a_phase = 0.01 - b_phase = 0.03 + def test__rmul__numeric(self, grid): + grid_scaled = 2.0 * grid + np.testing.assert_array_almost_equal(grid_scaled._vals, grid._vals * 2.0) - yp = np.linspace(0.0, 2.0, 20) - xp = np.linspace(0.0, 359.0, 10) - vp_amp = np.array([[a_amp * x_i + b_amp * y_i for x_i in xp] for y_i in yp]) - vp_phase = np.array( - [[a_phase * x_i + b_phase * y_i for x_i in xp] for y_i in yp] - ) - vp = vp_amp * (np.cos(vp_phase) + 1j * np.sin(vp_phase)) - grid = BinGrid(yp, xp, vp, freq_hz=True, degrees=True) + def test__mul__raises_array(self, grid): + with pytest.raises(TypeError): + grid * grid._vals - y = np.linspace(0.0, 2.0, 200) - x = xp - vals_amp_expect = np.array( - [[a_amp * x_i + b_amp * y_i for x_i in x] for y_i in y] + def test__mul__raises_type(self, grid, rao): + with pytest.raises(TypeError): + grid * rao + + def test__mul__raises_shape(self): + freq_in = np.array([1, 2, 3]) + dirs_in = np.array([0, 10, 20, 30]) + vals_in = np.array( + [ + [1, 2, 3, 4], + [1, 2, 3, 4], + [1, 2, 3, 4], + ] ) - x_, y_ = np.meshgrid(x, y, indexing="ij", sparse=True) - vals_phase_cos_expect = RGI((xp, yp), np.cos(vp_phase).T)((x_, y_)).T - vals_phase_sin_expect = RGI((xp, yp), np.sin(vp_phase).T)((x_, y_)).T + grid = Grid(freq_in, dirs_in, vals_in, degrees=True) - vals_expect = ( - vals_amp_expect - * (vals_phase_cos_expect + 1j * vals_phase_sin_expect) - / np.abs(vals_phase_cos_expect + 1j * vals_phase_sin_expect) + freq_in2 = np.array([1, 2]) + dirs_in2 = np.array([0, 10, 20]) + vals_in2 = np.array( + [ + [1, 2, 3], + [1, 2, 3], + ] ) + grid2 = Grid(freq_in2, dirs_in2, vals_in2, degrees=True) - vals_out = grid.interpolate(y, freq_hz=True, complex_convert="polar") + with pytest.raises(ValueError): + grid * grid2 - np.testing.assert_array_almost_equal(vals_out, vals_expect) + def test__mul__raises_freq(self, grid): + grid2 = grid.copy() + grid2._freq = 2.0 * grid2._freq - def test_interpolate_raises_freq(self): - freq = np.linspace(0, 1.0, 10) - dirs = np.linspace(0, 360.0, 15, endpoint=False) - vals = np.zeros((10, 15)) - grid = BinGrid( - freq, - dirs, - vals, - freq_hz=True, - degrees=True, - clockwise=True, - waves_coming_from=True, - ) + with pytest.raises(ValueError): + grid * grid2 + + def test__mul__raises_dirs(self, grid): + grid2 = grid.copy() + grid2._dirs = 2.0 * grid2._dirs with pytest.raises(ValueError): - grid.interpolate([0, 1, 2, 1]) # freq not monotonically increasing + grid * grid2 - def test_reshape(self): - a = 7 - b = 6 + def test__mul__raises_convention(self, grid): + grid.set_wave_convention(clockwise=False, waves_coming_from=False) - yp = np.linspace(0.0, 2.0, 20) - xp = np.linspace(0.0, 359.0, 10) - vp = np.array([[a * x_i + b * y_i for x_i in xp] for y_i in yp]) - bingrid = BinGrid(yp, xp, vp, freq_hz=True, degrees=True) + grid2 = grid.copy() + grid2.set_wave_convention(clockwise=True, waves_coming_from=False) + with pytest.raises(ValueError): + grid * grid2 - y = np.linspace(0.5, 1.0, 20) - x = xp - grid_reshaped = bingrid.reshape(y, freq_hz=True) + grid3 = grid.copy() + grid3.set_wave_convention(clockwise=False, waves_coming_from=True) + with pytest.raises(ValueError): + grid * grid3 - freq_expect = (2.0 * np.pi) * y - dirs_expect = (np.pi / 180.0) * x - vals_expect = np.array([[a * x_i + b * y_i for x_i in x] for y_i in y]) + def test__add__(self, grid): + out = grid + grid - freq_out = grid_reshaped._freq - dirs_out = grid_reshaped._dirs - vals_out = grid_reshaped._vals + assert isinstance(out, Grid) + np.testing.assert_array_almost_equal( + out._vals, grid._vals + grid._vals + ) - np.testing.assert_array_almost_equal(freq_out, freq_expect) - np.testing.assert_array_almost_equal(dirs_out, dirs_expect) - np.testing.assert_array_almost_equal(vals_out, vals_expect) + def test__add__raises_type(self, grid, rao): + with pytest.raises(TypeError): + grid + rao - def test_reshape2(self): - a = 7 - b = 6 + @patch("waveresponse._core._check_is_similar") + def test__add__check_is_similar(self, mock_check_is_similar, grid): + grid + grid + mock_check_is_similar.assert_called_once_with( + grid, grid, exact_type=True + ) - yp = np.linspace(0.0, 2.0, 20) - xp = np.linspace(0.0, 359.0, 10) - vp = np.array([[a * x_i + b * y_i for x_i in xp] for y_i in yp]) - grid = BinGrid(yp, xp, vp, freq_hz=True, degrees=True) + def test__add__numeric(self, grid): + grid_added = grid + 2.0 + np.testing.assert_array_almost_equal(grid_added._vals, grid._vals + 2.0) - y = np.linspace(0.5, 1.0, 20) - x = xp - y_ = (2.0 * np.pi) * y - grid_reshaped = grid.reshape(y_, freq_hz=False) + grid_added = grid + 0.0 + np.testing.assert_array_almost_equal(grid_added._vals, grid._vals + 0.0) - freq_expect = (2.0 * np.pi) * y - dirs_expect = (np.pi / 180.0) * x - vals_expect = np.array([[a * x_i + b * y_i for x_i in x] for y_i in y]) + grid_added = grid + 2 + np.testing.assert_array_almost_equal(grid_added._vals, grid._vals + 2) - freq_out = grid_reshaped._freq - dirs_out = grid_reshaped._dirs - vals_out = grid_reshaped._vals + grid_added = grid + (1 + 1j) + np.testing.assert_array_almost_equal( + grid_added._vals, grid._vals + (1 + 1j) + ) - np.testing.assert_array_almost_equal(freq_out, freq_expect) - np.testing.assert_array_almost_equal(dirs_out, dirs_expect) - np.testing.assert_array_almost_equal(vals_out, vals_expect) + def test__radd__numeric(self, grid): + grid_added = 2.0 + grid + np.testing.assert_array_almost_equal(grid_added._vals, grid._vals + 2.0) - def test_reshape_complex_rectangular(self): - a_real = 7 - b_real = 6 - a_imag = 3 - b_imag = 9 + def test__sub__(self, grid): + out = grid - grid - yp = np.linspace(0.0, 2.0, 20) - xp = np.linspace(0.0, 359.0, 10) - vp_real = np.array([[a_real * x_i + b_real * y_i for x_i in xp] for y_i in yp]) - vp_imag = np.array([[a_imag * x_i + b_imag * y_i for x_i in xp] for y_i in yp]) - vp = vp_real + 1j * vp_imag - bingrid = BinGrid(yp, xp, vp, freq_hz=True, degrees=True) + assert isinstance(out, Grid) + np.testing.assert_array_almost_equal( + out._vals, grid._vals - grid._vals + ) - y = np.linspace(0.5, 1.0, 20) - x = xp - grid_reshaped = bingrid.reshape(y, freq_hz=True, complex_convert="rectangular") + @patch("waveresponse._core._check_is_similar") + def test__sub__check_is_similar(self, mock_check_is_similar, grid): + grid - grid + mock_check_is_similar.assert_called_once_with( + grid, grid, exact_type=True + ) - freq_out = grid_reshaped._freq - dirs_out = grid_reshaped._dirs - vals_out = grid_reshaped._vals + def test__sub__raises_type(self, grid, rao): + with pytest.raises(TypeError): + grid - rao - freq_expect = (2.0 * np.pi) * y - dirs_expect = (np.pi / 180.0) * x - vals_real_expect = np.array( - [[a_real * x_i + b_real * y_i for x_i in x] for y_i in y] + def test__sub__numeric(self, grid): + grid_subtracted = grid - 2.0 + np.testing.assert_array_almost_equal( + grid_subtracted._vals, grid._vals - 2.0 ) - vals_imag_expect = np.array( - [[a_imag * x_i + b_imag * y_i for x_i in x] for y_i in y] + + grid_subtracted = grid - 0.0 + np.testing.assert_array_almost_equal( + grid_subtracted._vals, grid._vals - 0.0 ) - vals_expect = vals_real_expect + 1j * vals_imag_expect - np.testing.assert_array_almost_equal(freq_out, freq_expect) - np.testing.assert_array_almost_equal(dirs_out, dirs_expect) - np.testing.assert_array_almost_equal(vals_out, vals_expect) + grid_subtracted = grid - 2 + np.testing.assert_array_almost_equal(grid_subtracted._vals, grid._vals - 2) - def test_reshape_complex_polar(self): - a_amp = 7 - b_amp = 6 - a_phase = 0.01 - b_phase = 0.03 + grid_subtracted = grid - (1 + 1j) + np.testing.assert_array_almost_equal( + grid_subtracted._vals, grid._vals - (1 + 1j) + ) - yp = np.linspace(0.0, 2.0, 20) - xp = np.linspace(0.0, 359.0, 10) - vp_amp = np.array([[a_amp * x_i + b_amp * y_i for x_i in xp] for y_i in yp]) - vp_phase = np.array( - [[a_phase * x_i + b_phase * y_i for x_i in xp] for y_i in yp] + def test__rsub__numeric(self, grid): + grid_subtracted = 2.0 - grid + np.testing.assert_array_almost_equal( + grid_subtracted._vals, grid._vals - 2.0 ) - vp = vp_amp * (np.cos(vp_phase) + 1j * np.sin(vp_phase)) - bingrid = BinGrid(yp, xp, vp, freq_hz=True, degrees=True) - y = np.linspace(0.5, 1.0, 20) - x = xp - grid_reshaped = bingrid.reshape(y, freq_hz=True, complex_convert="polar") + def test_conjugate(self, grid): + grid_conj = grid.conjugate() - freq_out = grid_reshaped._freq - dirs_out = grid_reshaped._dirs - vals_out = grid_reshaped._vals + np.testing.assert_array_almost_equal(grid_conj._freq, grid._freq) + np.testing.assert_array_almost_equal(grid_conj._dirs, grid._dirs) + np.testing.assert_array_almost_equal( + grid_conj._vals, grid._vals.conjugate() + ) - freq_expect = (2.0 * np.pi) * y - dirs_expect = (np.pi / 180.0) * x - vals_amp_expect = np.array( - [[a_amp * x_i + b_amp * y_i for x_i in x] for y_i in y] + def test_real(self): + freq_in = np.array([1, 2, 3]) + dirs_in = np.array([0, 10, 20]) + vals_in = np.array( + [ + [1.0 + 0.0j, 0.0 + 1.0j, -1.0 + 0.0j], + [2.0 + 0.0j, 0.0 - 2.0j, -2.0 + 0.0j], + [3.0 + 0.0j, 0.0 + 3.0j, -3.0 + 0.0j], + ] + ) + grid = Grid( + freq_in, + dirs_in, + vals_in, + degrees=True, + clockwise=True, + waves_coming_from=True, ) - x_, y_ = np.meshgrid(x, y, indexing="ij", sparse=True) - vals_phase_cos_expect = RGI((xp, yp), np.cos(vp_phase).T)((x_, y_)).T - vals_phase_sin_expect = RGI((xp, yp), np.sin(vp_phase).T)((x_, y_)).T - vals_expect = ( - vals_amp_expect - * (vals_phase_cos_expect + 1j * vals_phase_sin_expect) - / np.abs(vals_phase_cos_expect + 1j * vals_phase_sin_expect) + grid_real = grid.real + + vals_expect = np.array( + [ + [1.0, 0.0, -1.0], + [2.0, 0.0, -2.0], + [3.0, 0.0, -3.0], + ] ) - np.testing.assert_array_almost_equal(freq_out, freq_expect) - np.testing.assert_array_almost_equal(dirs_out, dirs_expect) - np.testing.assert_array_almost_equal(vals_out, vals_expect) + assert isinstance(grid_real, Grid) + np.testing.assert_array_almost_equal(grid_real._vals, vals_expect) - def test_reshape_raises_freq(self): - freq = np.linspace(0, 1.0, 10) - dirs = np.linspace(0, 360.0, 15, endpoint=False) - vals = np.zeros((10, 15)) - grid = BinGrid( - freq, - dirs, - vals, - freq_hz=True, + def test_imag(self): + freq_in = np.array([1, 2, 3]) + dirs_in = np.array([0, 10, 20]) + vals_in = np.array( + [ + [1.0 + 0.0j, 0.0 + 1.0j, -1.0 + 0.0j], + [2.0 + 0.0j, 0.0 - 2.0j, -2.0 + 0.0j], + [3.0 + 0.0j, 0.0 + 3.0j, -3.0 + 0.0j], + ] + ) + grid = Grid( + freq_in, + dirs_in, + vals_in, degrees=True, clockwise=True, waves_coming_from=True, ) - with pytest.raises(ValueError): - grid.reshape([0, 1, 2, 1]) # freq not monotonically increasing + grid_imag = grid.imag + + vals_expect = np.array( + [ + [0.0, 1.0, 0.0], + [0.0, -2.0, 0.0], + [0.0, 3.0, 0.0], + ] + ) - def test__repr__(self, bingrid): - assert str(bingrid) == "BinGrid" + assert isinstance(grid_imag, Grid) + np.testing.assert_array_almost_equal(grid_imag._vals, vals_expect) class Test_RAO: @@ -4086,7 +3711,7 @@ def test__init___hz_deg(self): waves_coming_from=True, ) - assert isinstance(spectrum, BinGrid) + assert isinstance(spectrum, Grid) assert spectrum._clockwise is True assert spectrum._waves_coming_from is True np.testing.assert_array_almost_equal(spectrum._freq, 2.0 * np.pi * freq_in) @@ -4109,7 +3734,7 @@ def test__init___hz_rad(self): waves_coming_from=True, ) - assert isinstance(spectrum, BinGrid) + assert isinstance(spectrum, Grid) assert spectrum._clockwise is False assert spectrum._waves_coming_from is True np.testing.assert_array_almost_equal(spectrum._freq, 2.0 * np.pi * freq_in) @@ -4132,7 +3757,7 @@ def test__init___rads_deg(self): waves_coming_from=False, ) - assert isinstance(spectrum, BinGrid) + assert isinstance(spectrum, Grid) assert spectrum._clockwise is True assert spectrum._waves_coming_from is False np.testing.assert_array_almost_equal(spectrum._freq, freq_in) @@ -4153,7 +3778,7 @@ def test__init___rads_rad(self): waves_coming_from=False, ) - assert isinstance(spectrum, BinGrid) + assert isinstance(spectrum, Grid) assert spectrum._clockwise is False assert spectrum._waves_coming_from is False np.testing.assert_array_almost_equal(spectrum._freq, freq_in) @@ -4323,43 +3948,7 @@ def test_grid__hz_deg(self): np.testing.assert_array_almost_equal(dirs_out, dirs_expect) np.testing.assert_array_almost_equal(vals_out, vals_expect) - def test_interpolate_hz_deg(self): - a = 7 - b = 6 - - yp = np.linspace(0.0, 2.0, 20) - xp = np.linspace(0.0, 359.0, 10) - vp = np.array([[a * x_i + b * y_i for x_i in xp] for y_i in yp]) - spectrum = DirectionalBinSpectrum(yp, xp, vp, freq_hz=True, degrees=True) - - y = np.linspace(0.5, 1.0, 20) - x = xp - vals_expect = np.array([[a * x_i + b * y_i for x_i in x] for y_i in y]) - - vals_out = spectrum.interpolate(y, freq_hz=True) - - np.testing.assert_array_almost_equal(vals_out, vals_expect) - - def test_interpolate_hz_rad(self): - a = 7 - b = 6 - - yp = np.linspace(0.0, 2.0, 20) - xp = np.linspace(0.0, 359.0, 10) - vp = np.array([[a * x_i + b * y_i for x_i in xp] for y_i in yp]) - spectrum = DirectionalBinSpectrum(yp, xp, vp, freq_hz=True, degrees=True) - - y = np.linspace(0.5, 1.0, 20) - x = xp - vals_expect = np.array([[a * x_i + b * y_i for x_i in x] for y_i in y]) - - x *= np.pi / 180.0 - - vals_out = spectrum.interpolate(y, freq_hz=True) - - np.testing.assert_array_almost_equal(vals_out, vals_expect) - - def test_interpolate_rads_rad(self): + def test_interpolate(self): a = 7 b = 6 @@ -4372,19 +3961,8 @@ def test_interpolate_rads_rad(self): x = xp vals_expect = np.array([[a * x_i + b * y_i for x_i in x] for y_i in y]) - x *= np.pi / 180.0 - y *= 2.0 * np.pi - vals_expect /= 2.0 * np.pi - - vals_out = spectrum.interpolate(y, freq_hz=False) - - np.testing.assert_array_almost_equal(vals_out, vals_expect) - - testdata_full_range_dir = [ - ([1.0, 2.0, 3.0], [0.0, 1.0, 2.0, 3.0, 2.0 * np.pi]), - ([0.0, 1.0, 2.0, 3.0], [0.0, 1.0, 2.0, 3.0, 2.0 * np.pi]), - ([0.0, 1.0, 2.0, 3.0, 2.0 * np.pi], [0.0, 1.0, 2.0, 3.0, 2.0 * np.pi]), - ] + with pytest.raises(NotImplementedError): + _ = spectrum.interpolate(y, freq_hz=True) def test_var(self): y0 = 0.0 From 7ff7c48b75daa805172b11b253e3ce27f81e3938 Mon Sep 17 00:00:00 2001 From: Ali Cetin Date: Wed, 2 Apr 2025 14:00:45 +0200 Subject: [PATCH 02/10] remove obsolte import --- src/waveresponse/__init__.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/waveresponse/__init__.py b/src/waveresponse/__init__.py index 85d7711a..7019c70a 100644 --- a/src/waveresponse/__init__.py +++ b/src/waveresponse/__init__.py @@ -1,6 +1,5 @@ from ._core import ( RAO, - # BinGrid, CosineFullSpreading, CosineHalfSpreading, DirectionalBinSpectrum, @@ -40,7 +39,6 @@ "DirectionalSpectrum", "DirectionalBinSpectrum", "Grid", - # "BinGrid", "JONSWAP", "ModifiedPiersonMoskowitz", "OchiHubble", From 082f76fb3dc01dc1c4891ec075902719fed230f1 Mon Sep 17 00:00:00 2001 From: Ali Cetin Date: Wed, 2 Apr 2025 15:17:43 +0200 Subject: [PATCH 03/10] add GridInterpolator tests --- src/waveresponse/_core.py | 12 +-- tests/test_core.py | 222 ++++++++++++++++++++++++++++++++------ 2 files changed, 194 insertions(+), 40 deletions(-) diff --git a/src/waveresponse/_core.py b/src/waveresponse/_core.py index 5f97a9ae..6ba6dfd9 100644 --- a/src/waveresponse/_core.py +++ b/src/waveresponse/_core.py @@ -142,7 +142,7 @@ def _sort(dirs, vals): return dirs[sorted_args], vals[:, sorted_args] -class _GridInterpolate: +class _GridInterpolator: def __init__(self, freq, dirs, vals, complex_convert="rectangular", **kwargs): """ Interpolation function based on ``scipy.interpolate.RegularGridInterpolator``. @@ -763,7 +763,7 @@ def interpolate( self._check_freq(freq) self._check_dirs(dirs) - interp_fun = _GridInterpolate( + interp_fun = _GridInterpolator( self._freq, self._dirs, self._vals, @@ -836,7 +836,7 @@ def reshape( self._check_freq(freq_new) self._check_dirs(dirs_new) - interp_fun = _GridInterpolate( + interp_fun = _GridInterpolator( self._freq, self._dirs, self._vals, @@ -1074,7 +1074,7 @@ def reshape( self._check_freq(freq_new) self._check_dirs(dirs_new) - interp_fun = _GridInterpolate( + interp_fun = _GridInterpolator( freq_new, dirs_new, self._vals, @@ -1732,7 +1732,7 @@ def reshape( self._check_freq(freq_new) self._check_dirs(dirs_new) - interp_fun = _GridInterpolate( + interp_fun = _GridInterpolator( self._freq, self._dirs, self._vals, @@ -1920,7 +1920,7 @@ def reshape( self._check_freq(freq_new) - interp_fun = _GridInterpolate( + interp_fun = _GridInterpolator( self._freq, self._dirs, self._vals, diff --git a/tests/test_core.py b/tests/test_core.py index 66e90364..75c4e4b9 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -21,7 +21,7 @@ calculate_response, mirror, ) -from waveresponse._core import _check_foldable, _check_is_similar +from waveresponse._core import _check_foldable, _check_is_similar, _GridInterpolator TEST_PATH = Path(__file__).parent @@ -37,22 +37,6 @@ def freq_dirs(): return freq, dirs -# @pytest.fixture -# def base_grid(freq_dirs): -# freq, dirs = freq_dirs -# vals = np.random.random((len(freq), len(dirs))) -# grid = _BaseGrid( -# freq, -# dirs, -# vals, -# freq_hz=True, -# degrees=True, -# clockwise=True, -# waves_coming_from=True, -# ) -# return grid - - @pytest.fixture def grid(freq_dirs): freq, dirs = freq_dirs @@ -69,22 +53,6 @@ def grid(freq_dirs): return grid -# @pytest.fixture -# def bingrid(freq_dirs): -# freq, dirs = freq_dirs -# vals = np.random.random((len(freq), len(dirs))) -# grid = BinGrid( -# freq, -# dirs, -# vals, -# freq_hz=True, -# degrees=True, -# clockwise=True, -# waves_coming_from=True, -# ) -# return grid - - @pytest.fixture def rao(freq_dirs): freq, dirs = freq_dirs @@ -764,7 +732,193 @@ def test_dirs_empty(self): _check_foldable(np.array([]), degrees=True, sym_plane="xz") -class Test__Grid: +class Test__GridInterpolator: + def test_interpolate(self): + a = 7 + b = 6 + + yp = np.linspace(0.0, 2.0, 20) + xp = np.linspace(0.0, 359.0, 10) + vp = np.array([[a * x_i + b * y_i for x_i in xp] for y_i in yp]) + + y = np.linspace(0.5, 1.0, 20) + x = np.linspace(5.0, 15.0, 10) + vals_expect = np.array([[a * x_i + b * y_i for x_i in x] for y_i in y]) + + interp_fun = _GridInterpolator( + yp * 2.*np.pi, + np.radians(xp), + vp + ) + vals_out = interp_fun(y * 2. * np.pi, np.radians(x)) + np.testing.assert_array_almost_equal(vals_out, vals_expect) + + def test_interpolate_single_coordinate(self): + a = 7 + b = 6 + + yp = np.linspace(0.0, 2.0, 20) + xp = np.linspace(0.0, 359.0, 10) + vp = np.array([[a * x_i + b * y_i for x_i in xp] for y_i in yp]) + + interp_fun = _GridInterpolator( + yp * 2.*np.pi, + np.radians(xp), + vp + ) + vals_out = interp_fun(1.8 * 2. * np.pi, np.radians(12.1)) + vals_expect = np.array(a * 12.1 + b * 1.8) + + np.testing.assert_array_almost_equal(vals_out, vals_expect) + + def test_interpolate_fill_value(self): + freq = np.array([0, 1, 2]) + dirs = np.array([0, 90, 180, 270]) + vals = np.array( + [ + [1, 2, 3, 4], + [1, 2, 3, 4], + [1, 2, 3, 4], + ] + ) + interp_fun = _GridInterpolator( + freq * 2.*np.pi, + np.radians(dirs), + vals, + fill_value=0.0, + bounds_error=False, + ) + vals_out = interp_fun(np.array([10, 20]) * 2. * np.pi, np.radians([0, 90])) + + vals_expect = np.array( + [ + [0.0, 0.0], + [0.0, 0.0], + ] + ) + + np.testing.assert_array_almost_equal(vals_out, vals_expect) + + def test_interpolate_fill_value_None(self): + freq = np.array([0, 1, 2]) + dirs = np.array([0, 90, 180, 270]) + vals = np.array( + [ + [1, 2, 3, 4], + [1, 2, 3, 4], + [1, 2, 3, 4], + ] + ) + interp_fun = _GridInterpolator( + freq * 2.*np.pi, + np.radians(dirs), + vals, + fill_value=None, + bounds_error=False, + ) + vals_out = interp_fun(np.array([10, 20]) * 2. * np.pi, np.radians([0, 90])) + + vals_expect = np.array( + [ + [1.0, 2.0], + [1.0, 2.0], + ] + ) + + np.testing.assert_array_almost_equal(vals_out, vals_expect) + + def test_interpolate_complex_rectangular(self): + a_real = 7 + b_real = 6 + a_imag = 3 + b_imag = 9 + + yp = np.linspace(0.0, 2.0, 20) + xp = np.linspace(0.0, 359.0, 10) + vp_real = np.array([[a_real * x_i + b_real * y_i for x_i in xp] for y_i in yp]) + vp_imag = np.array([[a_imag * x_i + b_imag * y_i for x_i in xp] for y_i in yp]) + vp = vp_real + 1j * vp_imag + + y = np.linspace(0.5, 1.0, 20) + x = np.linspace(5.0, 15.0, 10) + vals_real_expect = np.array( + [[a_real * x_i + b_real * y_i for x_i in x] for y_i in y] + ) + vals_imag_expect = np.array( + [[a_imag * x_i + b_imag * y_i for x_i in x] for y_i in y] + ) + vals_expect = vals_real_expect + 1j * vals_imag_expect + + interp_fun = _GridInterpolator( + yp * 2.*np.pi, + np.radians(xp), + vp, + complex_convert="rectangular" + ) + vals_out = interp_fun(y * 2. * np.pi, np.radians(x)) + + np.testing.assert_array_almost_equal(vals_out, vals_expect) + + def test_interpolate_complex_polar(self): + a_amp = 7 + b_amp = 6 + a_phase = 0.01 + b_phase = 0.03 + + yp = np.linspace(0.0, 2.0, 20) + xp = np.linspace(0.0, 359.0, 10) + vp_amp = np.array([[a_amp * x_i + b_amp * y_i for x_i in xp] for y_i in yp]) + vp_phase = np.array( + [[a_phase * x_i + b_phase * y_i for x_i in xp] for y_i in yp] + ) + vp = vp_amp * (np.cos(vp_phase) + 1j * np.sin(vp_phase)) + + y = np.linspace(0.0, 2.0, 200) + x = np.linspace(0.0, 359.0, 100) + vals_amp_expect = np.array( + [[a_amp * x_i + b_amp * y_i for x_i in x] for y_i in y] + ) + x_, y_ = np.meshgrid(x, y, indexing="ij", sparse=True) + vals_phase_cos_expect = RGI((xp, yp), np.cos(vp_phase).T)((x_, y_)).T + vals_phase_sin_expect = RGI((xp, yp), np.sin(vp_phase).T)((x_, y_)).T + + vals_expect = ( + vals_amp_expect + * (vals_phase_cos_expect + 1j * vals_phase_sin_expect) + / np.abs(vals_phase_cos_expect + 1j * vals_phase_sin_expect) + ) + + interp_fun = _GridInterpolator( + yp * 2.*np.pi, + np.radians(xp), + vp, + complex_convert="polar" + ) + vals_out = interp_fun(y * 2. * np.pi, np.radians(x)) + + np.testing.assert_array_almost_equal(vals_out, vals_expect) + + def test_interpolate_raises_outside_bound(self): + freq = np.linspace(0, 1.0, 10) + dirs = np.linspace(0, 360.0, 15, endpoint=False) + vals = np.zeros((10, 15)) + interp_fun = _GridInterpolator( + freq * 2.*np.pi, + np.radians(dirs), + vals, + bounds_error=True, + ) + + with pytest.raises(ValueError): + interp_fun(np.array([0, 0.5]) * 2. * np.pi, np.radians([0, 1, 2, 400])) + + with pytest.raises(ValueError): + interp_fun(np.array([0, 2.]) * 2. * np.pi, np.radians([0, 1, 2, 100])) + + + + +class Test_Grid: def test__init__(self): freq = np.linspace(0, 1.0, 10) dirs = np.linspace(0, 360.0, 15, endpoint=False) From c92ad80b699f1573f84934b9c7d17710a6d5ebb4 Mon Sep 17 00:00:00 2001 From: Ali Cetin Date: Wed, 2 Apr 2025 15:19:56 +0200 Subject: [PATCH 04/10] style --- src/waveresponse/_core.py | 5 +- tests/test_core.py | 97 +++++++++++---------------------------- 2 files changed, 29 insertions(+), 73 deletions(-) diff --git a/src/waveresponse/_core.py b/src/waveresponse/_core.py index 6ba6dfd9..424c3721 100644 --- a/src/waveresponse/_core.py +++ b/src/waveresponse/_core.py @@ -1871,10 +1871,7 @@ def grid(self, freq_hz=False, degrees=False): return freq, dirs, vals - def interpolate( - self, - *args, **kwargs - ): + def interpolate(self, *args, **kwargs): raise NotImplementedError("Use `.reshape` instead.") def reshape( diff --git a/tests/test_core.py b/tests/test_core.py index 75c4e4b9..1bb0bd0e 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -9,9 +9,8 @@ from scipy.interpolate import RegularGridInterpolator as RGI import waveresponse as wr -from waveresponse import ( +from waveresponse import ( # BinGrid, RAO, - # BinGrid, CosineFullSpreading, CosineHalfSpreading, DirectionalBinSpectrum, @@ -745,12 +744,8 @@ def test_interpolate(self): x = np.linspace(5.0, 15.0, 10) vals_expect = np.array([[a * x_i + b * y_i for x_i in x] for y_i in y]) - interp_fun = _GridInterpolator( - yp * 2.*np.pi, - np.radians(xp), - vp - ) - vals_out = interp_fun(y * 2. * np.pi, np.radians(x)) + interp_fun = _GridInterpolator(yp * 2.0 * np.pi, np.radians(xp), vp) + vals_out = interp_fun(y * 2.0 * np.pi, np.radians(x)) np.testing.assert_array_almost_equal(vals_out, vals_expect) def test_interpolate_single_coordinate(self): @@ -761,12 +756,8 @@ def test_interpolate_single_coordinate(self): xp = np.linspace(0.0, 359.0, 10) vp = np.array([[a * x_i + b * y_i for x_i in xp] for y_i in yp]) - interp_fun = _GridInterpolator( - yp * 2.*np.pi, - np.radians(xp), - vp - ) - vals_out = interp_fun(1.8 * 2. * np.pi, np.radians(12.1)) + interp_fun = _GridInterpolator(yp * 2.0 * np.pi, np.radians(xp), vp) + vals_out = interp_fun(1.8 * 2.0 * np.pi, np.radians(12.1)) vals_expect = np.array(a * 12.1 + b * 1.8) np.testing.assert_array_almost_equal(vals_out, vals_expect) @@ -782,13 +773,13 @@ def test_interpolate_fill_value(self): ] ) interp_fun = _GridInterpolator( - freq * 2.*np.pi, + freq * 2.0 * np.pi, np.radians(dirs), vals, fill_value=0.0, bounds_error=False, ) - vals_out = interp_fun(np.array([10, 20]) * 2. * np.pi, np.radians([0, 90])) + vals_out = interp_fun(np.array([10, 20]) * 2.0 * np.pi, np.radians([0, 90])) vals_expect = np.array( [ @@ -810,13 +801,13 @@ def test_interpolate_fill_value_None(self): ] ) interp_fun = _GridInterpolator( - freq * 2.*np.pi, + freq * 2.0 * np.pi, np.radians(dirs), vals, fill_value=None, bounds_error=False, ) - vals_out = interp_fun(np.array([10, 20]) * 2. * np.pi, np.radians([0, 90])) + vals_out = interp_fun(np.array([10, 20]) * 2.0 * np.pi, np.radians([0, 90])) vals_expect = np.array( [ @@ -850,12 +841,9 @@ def test_interpolate_complex_rectangular(self): vals_expect = vals_real_expect + 1j * vals_imag_expect interp_fun = _GridInterpolator( - yp * 2.*np.pi, - np.radians(xp), - vp, - complex_convert="rectangular" + yp * 2.0 * np.pi, np.radians(xp), vp, complex_convert="rectangular" ) - vals_out = interp_fun(y * 2. * np.pi, np.radians(x)) + vals_out = interp_fun(y * 2.0 * np.pi, np.radians(x)) np.testing.assert_array_almost_equal(vals_out, vals_expect) @@ -889,12 +877,9 @@ def test_interpolate_complex_polar(self): ) interp_fun = _GridInterpolator( - yp * 2.*np.pi, - np.radians(xp), - vp, - complex_convert="polar" + yp * 2.0 * np.pi, np.radians(xp), vp, complex_convert="polar" ) - vals_out = interp_fun(y * 2. * np.pi, np.radians(x)) + vals_out = interp_fun(y * 2.0 * np.pi, np.radians(x)) np.testing.assert_array_almost_equal(vals_out, vals_expect) @@ -903,19 +888,17 @@ def test_interpolate_raises_outside_bound(self): dirs = np.linspace(0, 360.0, 15, endpoint=False) vals = np.zeros((10, 15)) interp_fun = _GridInterpolator( - freq * 2.*np.pi, + freq * 2.0 * np.pi, np.radians(dirs), vals, bounds_error=True, ) with pytest.raises(ValueError): - interp_fun(np.array([0, 0.5]) * 2. * np.pi, np.radians([0, 1, 2, 400])) + interp_fun(np.array([0, 0.5]) * 2.0 * np.pi, np.radians([0, 1, 2, 400])) with pytest.raises(ValueError): - interp_fun(np.array([0, 2.]) * 2. * np.pi, np.radians([0, 1, 2, 100])) - - + interp_fun(np.array([0, 2.0]) * 2.0 * np.pi, np.radians([0, 1, 2, 100])) class Test_Grid: @@ -1571,9 +1554,7 @@ def test__convert_dirs_radians(self): dirs_in = np.array([0, np.pi / 4, np.pi / 2, 3.0 * np.pi / 4, np.pi]) config_org = {"clockwise": False, "waves_coming_from": True} config_new = {"clockwise": True, "waves_coming_from": False} - dirs_out = Grid._convert_dirs( - dirs_in, config_new, config_org, degrees=False - ) + dirs_out = Grid._convert_dirs(dirs_in, config_new, config_org, degrees=False) dirs_expect = np.array([np.pi, 3.0 * np.pi / 4, np.pi / 2, np.pi / 4, 0]) @@ -1583,9 +1564,7 @@ def test__convert_dirs_degrees(self): dirs_in = np.array([0, 45.0, 90.0, 135.0, 180.0]) config_org = {"clockwise": False, "waves_coming_from": True} config_new = {"clockwise": True, "waves_coming_from": False} - dirs_out = Grid._convert_dirs( - dirs_in, config_new, config_org, degrees=True - ) + dirs_out = Grid._convert_dirs(dirs_in, config_new, config_org, degrees=True) dirs_expect = np.array([180.0, 135.0, 90.0, 45.0, 0]) @@ -1927,9 +1906,7 @@ def test__mul__numeric(self, grid): np.testing.assert_array_almost_equal(grid_scaled._vals, grid._vals * 2) grid_scaled = grid * (1 + 1j) - np.testing.assert_array_almost_equal( - grid_scaled._vals, grid._vals * (1 + 1j) - ) + np.testing.assert_array_almost_equal(grid_scaled._vals, grid._vals * (1 + 1j)) def test__rmul__numeric(self, grid): grid_scaled = 2.0 * grid @@ -1999,9 +1976,7 @@ def test__add__(self, grid): out = grid + grid assert isinstance(out, Grid) - np.testing.assert_array_almost_equal( - out._vals, grid._vals + grid._vals - ) + np.testing.assert_array_almost_equal(out._vals, grid._vals + grid._vals) def test__add__raises_type(self, grid, rao): with pytest.raises(TypeError): @@ -2010,9 +1985,7 @@ def test__add__raises_type(self, grid, rao): @patch("waveresponse._core._check_is_similar") def test__add__check_is_similar(self, mock_check_is_similar, grid): grid + grid - mock_check_is_similar.assert_called_once_with( - grid, grid, exact_type=True - ) + mock_check_is_similar.assert_called_once_with(grid, grid, exact_type=True) def test__add__numeric(self, grid): grid_added = grid + 2.0 @@ -2025,9 +1998,7 @@ def test__add__numeric(self, grid): np.testing.assert_array_almost_equal(grid_added._vals, grid._vals + 2) grid_added = grid + (1 + 1j) - np.testing.assert_array_almost_equal( - grid_added._vals, grid._vals + (1 + 1j) - ) + np.testing.assert_array_almost_equal(grid_added._vals, grid._vals + (1 + 1j)) def test__radd__numeric(self, grid): grid_added = 2.0 + grid @@ -2037,16 +2008,12 @@ def test__sub__(self, grid): out = grid - grid assert isinstance(out, Grid) - np.testing.assert_array_almost_equal( - out._vals, grid._vals - grid._vals - ) + np.testing.assert_array_almost_equal(out._vals, grid._vals - grid._vals) @patch("waveresponse._core._check_is_similar") def test__sub__check_is_similar(self, mock_check_is_similar, grid): grid - grid - mock_check_is_similar.assert_called_once_with( - grid, grid, exact_type=True - ) + mock_check_is_similar.assert_called_once_with(grid, grid, exact_type=True) def test__sub__raises_type(self, grid, rao): with pytest.raises(TypeError): @@ -2054,14 +2021,10 @@ def test__sub__raises_type(self, grid, rao): def test__sub__numeric(self, grid): grid_subtracted = grid - 2.0 - np.testing.assert_array_almost_equal( - grid_subtracted._vals, grid._vals - 2.0 - ) + np.testing.assert_array_almost_equal(grid_subtracted._vals, grid._vals - 2.0) grid_subtracted = grid - 0.0 - np.testing.assert_array_almost_equal( - grid_subtracted._vals, grid._vals - 0.0 - ) + np.testing.assert_array_almost_equal(grid_subtracted._vals, grid._vals - 0.0) grid_subtracted = grid - 2 np.testing.assert_array_almost_equal(grid_subtracted._vals, grid._vals - 2) @@ -2073,18 +2036,14 @@ def test__sub__numeric(self, grid): def test__rsub__numeric(self, grid): grid_subtracted = 2.0 - grid - np.testing.assert_array_almost_equal( - grid_subtracted._vals, grid._vals - 2.0 - ) + np.testing.assert_array_almost_equal(grid_subtracted._vals, grid._vals - 2.0) def test_conjugate(self, grid): grid_conj = grid.conjugate() np.testing.assert_array_almost_equal(grid_conj._freq, grid._freq) np.testing.assert_array_almost_equal(grid_conj._dirs, grid._dirs) - np.testing.assert_array_almost_equal( - grid_conj._vals, grid._vals.conjugate() - ) + np.testing.assert_array_almost_equal(grid_conj._vals, grid._vals.conjugate()) def test_real(self): freq_in = np.array([1, 2, 3]) From af6a90da6259e575e6e2d1f9ca7f8149b5fbab1b Mon Sep 17 00:00:00 2001 From: Ali Cetin Date: Wed, 2 Apr 2025 15:46:18 +0200 Subject: [PATCH 05/10] merge main fixes --- src/waveresponse/_core.py | 13 ++++++++----- tests/test_core.py | 2 +- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/waveresponse/_core.py b/src/waveresponse/_core.py index 25c53127..ba93cca4 100644 --- a/src/waveresponse/_core.py +++ b/src/waveresponse/_core.py @@ -1654,12 +1654,15 @@ def bingrid(self, freq_hz=False, degrees=False, complex_convert="rectangular"): dirs_bin[0::2] = dirs_tmp[:-1] + np.diff(dirs_tmp) / 2.0 # bin boundaries dirs_bin[1::2] = self._dirs # bin centers - interp_fun = self._interpolate_function( - complex_convert=complex_convert, method="linear", bounds_error=True + interp_fun = _GridInterpolator( + self._freq, + self._dirs, + self._vals, + complex_convert=complex_convert, + method="linear", + bounds_error=True ) - - dirsnew, freqnew = np.meshgrid(dirs_bin, self._freq, indexing="ij", sparse=True) - vals_tmp = interp_fun((dirsnew, freqnew)).T + vals_tmp = interp_fun(self._freq, dirs_bin) vals_binned = np.column_stack( [ diff --git a/tests/test_core.py b/tests/test_core.py index 6b0df700..444b98cf 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -5114,7 +5114,7 @@ def test__init__(self): dirs_expect = (np.pi / 180.0) * dirs vals_expect = vals / (2.0 * np.pi) - assert isinstance(wave, BinGrid) + assert isinstance(wave, Grid) assert isinstance(wave, DirectionalBinSpectrum) np.testing.assert_array_almost_equal(wave._freq, freq_expect) np.testing.assert_array_almost_equal(wave._dirs, dirs_expect) From 301523b6b08cf8a0af39077b508f921cde1644d3 Mon Sep 17 00:00:00 2001 From: Ali Cetin Date: Wed, 2 Apr 2025 15:46:58 +0200 Subject: [PATCH 06/10] style --- 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 ba93cca4..b4327bbb 100644 --- a/src/waveresponse/_core.py +++ b/src/waveresponse/_core.py @@ -1660,7 +1660,7 @@ def bingrid(self, freq_hz=False, degrees=False, complex_convert="rectangular"): self._vals, complex_convert=complex_convert, method="linear", - bounds_error=True + bounds_error=True, ) vals_tmp = interp_fun(self._freq, dirs_bin) From e98a5caa10d9d0e6b27b4c0f56d2a06df791c76d Mon Sep 17 00:00:00 2001 From: Ali Cetin Date: Thu, 3 Apr 2025 08:41:32 +0200 Subject: [PATCH 07/10] change interpolate error --- 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 b4327bbb..a71433f6 100644 --- a/src/waveresponse/_core.py +++ b/src/waveresponse/_core.py @@ -1938,7 +1938,7 @@ def grid(self, freq_hz=False, degrees=False): return freq, dirs, vals def interpolate(self, *args, **kwargs): - raise NotImplementedError("Use `.reshape` instead.") + raise AttributeError("Use `.reshape` instead.") def reshape( self, From b323594c54c34c7ac2b835712921baba43ef85ae Mon Sep 17 00:00:00 2001 From: "Vegard R. Solum" Date: Mon, 7 Apr 2025 10:29:19 +0200 Subject: [PATCH 08/10] fix test raise AttributeError --- tests/test_core.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/tests/test_core.py b/tests/test_core.py index 444b98cf..fcc51e58 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -4329,10 +4329,9 @@ def test_interpolate(self): y = np.linspace(0.5, 1.0, 20) x = xp - vals_expect = np.array([[a * x_i + b * y_i for x_i in x] for y_i in y]) - with pytest.raises(NotImplementedError): - _ = spectrum.interpolate(y, freq_hz=True) + with pytest.raises(AttributeError): + _ = spectrum.interpolate(y, x, freq_hz=True, degrees=True) def test_var(self): y0 = 0.0 From 896cf047b2e33453008969eb0364f3a9f49f1797 Mon Sep 17 00:00:00 2001 From: "Vegard R. Solum" Date: Mon, 7 Apr 2025 11:01:12 +0200 Subject: [PATCH 09/10] move interpolator docstring --- src/waveresponse/_core.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/waveresponse/_core.py b/src/waveresponse/_core.py index a71433f6..a014ff1b 100644 --- a/src/waveresponse/_core.py +++ b/src/waveresponse/_core.py @@ -143,10 +143,10 @@ def _sort(dirs, vals): class _GridInterpolator: + """ + Interpolation function based on ``scipy.interpolate.RegularGridInterpolator``. + """ def __init__(self, freq, dirs, vals, complex_convert="rectangular", **kwargs): - """ - Interpolation function based on ``scipy.interpolate.RegularGridInterpolator``. - """ xp = np.concatenate((dirs[-1:] - 2 * np.pi, dirs, dirs[:1] + 2.0 * np.pi)) yp = freq From e30d2a9b0cca40c628467c72a31c0ea9b190b9f6 Mon Sep 17 00:00:00 2001 From: "Vegard R. Solum" Date: Mon, 7 Apr 2025 11:02:46 +0200 Subject: [PATCH 10/10] black --- src/waveresponse/_core.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/waveresponse/_core.py b/src/waveresponse/_core.py index a014ff1b..d1208104 100644 --- a/src/waveresponse/_core.py +++ b/src/waveresponse/_core.py @@ -146,6 +146,7 @@ class _GridInterpolator: """ Interpolation function based on ``scipy.interpolate.RegularGridInterpolator``. """ + def __init__(self, freq, dirs, vals, complex_convert="rectangular", **kwargs): xp = np.concatenate((dirs[-1:] - 2 * np.pi, dirs, dirs[:1] + 2.0 * np.pi))