@@ -538,6 +538,47 @@ def rotate(self, angle, degrees=False):
538538 new ._dirs , new ._vals = _sort (dirs_new , new ._vals )
539539 return new
540540
541+ def _interpolate_function (self , complex_convert = "rectangular" , ** kw ):
542+ """
543+ Interpolation function based on ``scipy.interpolate.RegularGridInterpolator``.
544+ """
545+ xp = np .concatenate (
546+ (self ._dirs [- 1 :] - 2 * np .pi , self ._dirs , self ._dirs [:1 ] + 2.0 * np .pi )
547+ )
548+
549+ yp = self ._freq
550+ zp = np .concatenate (
551+ (
552+ self ._vals [:, - 1 :],
553+ self ._vals ,
554+ self ._vals [:, :1 ],
555+ ),
556+ axis = 1 ,
557+ )
558+
559+ if np .all (np .isreal (zp )):
560+ return RGI ((xp , yp ), zp .T , ** kw )
561+ elif complex_convert .lower () == "polar" :
562+ amp , phase = complex_to_polar (zp , phase_degrees = False )
563+ phase_complex = np .cos (phase ) + 1j * np .sin (phase )
564+ interp_amp = RGI ((xp , yp ), amp .T , ** kw )
565+ interp_phase = RGI ((xp , yp ), phase_complex .T , ** kw )
566+ return lambda * args , ** kwargs : (
567+ polar_to_complex (
568+ interp_amp (* args , ** kwargs ),
569+ np .angle (interp_phase (* args , ** kwargs )),
570+ phase_degrees = False ,
571+ )
572+ )
573+ elif complex_convert .lower () == "rectangular" :
574+ interp_real = RGI ((xp , yp ), np .real (zp .T ), ** kw )
575+ interp_imag = RGI ((xp , yp ), np .imag (zp .T ), ** kw )
576+ return lambda * args , ** kwargs : (
577+ interp_real (* args , ** kwargs ) + 1j * interp_imag (* args , ** kwargs )
578+ )
579+ else :
580+ raise ValueError ("Unknown 'complex_convert' type" )
581+
541582 def __mul__ (self , other ):
542583 """
543584 Multiply values (element-wise).
@@ -658,7 +699,9 @@ def imag(self):
658699
659700class Grid (_BaseGrid ):
660701 """
661- Two-dimentional frequency/(wave)direction grid.
702+ A two-dimensional grid with values as a function of frequency and wave direction.
703+ The values are assumed to represent a continuous field. I.e., the values can
704+ be interpolated in the frequency and direction domain.
662705
663706 Parameters
664707 ----------
@@ -686,47 +729,6 @@ class Grid(_BaseGrid):
686729 def __repr__ (self ):
687730 return "Grid"
688731
689- def _interpolate_function (self , complex_convert = "rectangular" , ** kw ):
690- """
691- Interpolation function based on ``scipy.interpolate.RegularGridInterpolator``.
692- """
693- xp = np .concatenate (
694- (self ._dirs [- 1 :] - 2 * np .pi , self ._dirs , self ._dirs [:1 ] + 2.0 * np .pi )
695- )
696-
697- yp = self ._freq
698- zp = np .concatenate (
699- (
700- self ._vals [:, - 1 :],
701- self ._vals ,
702- self ._vals [:, :1 ],
703- ),
704- axis = 1 ,
705- )
706-
707- if np .all (np .isreal (zp )):
708- return RGI ((xp , yp ), zp .T , ** kw )
709- elif complex_convert .lower () == "polar" :
710- amp , phase = complex_to_polar (zp , phase_degrees = False )
711- phase_complex = np .cos (phase ) + 1j * np .sin (phase )
712- interp_amp = RGI ((xp , yp ), amp .T , ** kw )
713- interp_phase = RGI ((xp , yp ), phase_complex .T , ** kw )
714- return lambda * args , ** kwargs : (
715- polar_to_complex (
716- interp_amp (* args , ** kwargs ),
717- np .angle (interp_phase (* args , ** kwargs )),
718- phase_degrees = False ,
719- )
720- )
721- elif complex_convert .lower () == "rectangular" :
722- interp_real = RGI ((xp , yp ), np .real (zp .T ), ** kw )
723- interp_imag = RGI ((xp , yp ), np .imag (zp .T ), ** kw )
724- return lambda * args , ** kwargs : (
725- interp_real (* args , ** kwargs ) + 1j * interp_imag (* args , ** kwargs )
726- )
727- else :
728- raise ValueError ("Unknown 'complex_convert' type" )
729-
730732 def interpolate (
731733 self ,
732734 freq ,
@@ -861,6 +863,147 @@ def reshape(
861863 return new
862864
863865
866+ class BinGrid (_BaseGrid ):
867+ """
868+ A two-dimensional grid with values as a function of frequency and wave direction.
869+ The values are assumed to represent a continuous field along the frequency axis,
870+ but are treated as bins along the direction axis. I.e., the values can only be
871+ interpolated in the frequency domain.
872+
873+ Parameters
874+ ----------
875+ freq : array-like
876+ 1-D array of grid frequency coordinates. Positive and monotonically increasing.
877+ dirs : array-like
878+ 1-D array of grid direction coordinates. Positive and monotonically increasing.
879+ Must cover the directional range [0, 360) degrees (or [0, 2 * numpy.pi) radians).
880+ vals : array-like (N, M)
881+ Values associated with the grid. Should be a 2-D array of shape (N, M),
882+ such that ``N=len(freq)`` and ``M=len(dirs)``.
883+ freq_hz : bool
884+ If frequency is given in 'Hz'. If ``False``, 'rad/s' is assumed.
885+ degrees : bool
886+ If direction is given in 'degrees'. If ``False``, 'radians' is assumed.
887+ clockwise : bool
888+ If positive directions are defined to be 'clockwise' (``True``) or 'counterclockwise'
889+ (``False``). Clockwise means that the directions follow the right-hand rule
890+ with an axis pointing downwards.
891+ waves_coming_from : bool
892+ If waves are 'coming from' the given directions. If ``False``, 'going towards'
893+ convention is assumed.
894+ """
895+
896+ def __repr__ (self ):
897+ return "BinGrid"
898+
899+ def interpolate (
900+ self ,
901+ freq ,
902+ freq_hz = False ,
903+ complex_convert = "rectangular" ,
904+ fill_value = 0.0 ,
905+ ):
906+ """
907+ Interpolate (linear) the grid values to match the given frequency coordinates.
908+
909+ A 'fill value' is used for extrapolation (i.e. `freq` outside the bounds
910+ of the provided 2-D grid). Directions are treated as periodic.
911+
912+ Parameters
913+ ----------
914+ freq : array-like
915+ 1-D array of grid frequency coordinates. Positive and monotonically increasing.
916+ freq_hz : bool
917+ If frequency is given in 'Hz'. If ``False``, 'rad/s' is assumed.
918+ complex_convert : str, optional
919+ How to convert complex number grid values before interpolating. Should
920+ be 'rectangular' or 'polar'. If 'rectangular' (default), complex values
921+ are converted to rectangular form (i.e., real and imaginary part) before
922+ interpolating. If 'polar', the values are instead converted to polar
923+ form (i.e., amplitude and phase) before interpolating. The values are
924+ converted back to complex form after interpolation.
925+ fill_value : float or None
926+ The value used for extrapolation (i.e., `freq` outside the bounds of
927+ the provided grid). If ``None``, values outside the frequency domain
928+ are extrapolated via nearest-neighbor extrapolation. Note that directions
929+ are treated as periodic (and will not need extrapolation).
930+
931+ Returns
932+ -------
933+ array :
934+ Interpolated grid values.
935+ """
936+ freq = np .asarray_chkfinite (freq ).reshape (- 1 )
937+
938+ if freq_hz :
939+ freq = 2.0 * np .pi * freq
940+
941+ self ._check_freq (freq )
942+
943+ interp_fun = self ._interpolate_function (
944+ complex_convert = complex_convert ,
945+ method = "linear" ,
946+ bounds_error = False ,
947+ fill_value = fill_value ,
948+ )
949+
950+ dirsnew , freqnew = np .meshgrid (self ._dirs , freq , indexing = "ij" , sparse = True )
951+ return interp_fun ((dirsnew , freqnew )).T
952+
953+ def reshape (
954+ self ,
955+ freq ,
956+ freq_hz = False ,
957+ complex_convert = "rectangular" ,
958+ fill_value = 0.0 ,
959+ ):
960+ """
961+ Reshape the grid to match the given frequency coordinates. Grid
962+ values will be interpolated (linear).
963+
964+ Parameters
965+ ----------
966+ freq : array-like
967+ 1-D array of new grid frequency coordinates. Positive and monotonically
968+ increasing.
969+ freq_hz : bool
970+ If frequency is given in 'Hz'. If ``False``, 'rad/s' is assumed.
971+ complex_convert : str, optional
972+ How to convert complex number grid values before interpolating. Should
973+ be 'rectangular' or 'polar'. If 'rectangular' (default), complex values
974+ are converted to rectangular form (i.e., real and imaginary part) before
975+ interpolating. If 'polar', the values are instead converted to polar
976+ form (i.e., amplitude and phase) before interpolating. The values are
977+ converted back to complex form after interpolation.
978+ fill_value : float or None
979+ The value used for extrapolation (i.e., `freq` outside the bounds of
980+ the provided grid). If ``None``, values outside the frequency domain
981+ are extrapolated via nearest-neighbor extrapolation. Note that directions
982+ are treated as periodic (and will not need extrapolation).
983+
984+ Returns
985+ -------
986+ obj :
987+ A copy of the object where the underlying coordinate system is reshaped.
988+ """
989+ freq_new = np .asarray_chkfinite (freq ).copy ()
990+
991+ if freq_hz :
992+ freq_new = 2.0 * np .pi * freq_new
993+
994+ self ._check_freq (freq_new )
995+
996+ vals_new = self .interpolate (
997+ freq_new ,
998+ freq_hz = False ,
999+ complex_convert = complex_convert ,
1000+ fill_value = fill_value ,
1001+ )
1002+ new = self .copy ()
1003+ new ._freq , new ._vals = freq_new , vals_new
1004+ return new
1005+
1006+
8641007class DisableComplexMixin :
8651008 @property
8661009 def imag (self ):
0 commit comments