1919from clouddrift .wavelet import morse_logspace_freq , morse_wavelet , wavelet_transform
2020
2121
22- def inertial_oscillations_from_positions (
22+ def inertial_oscillation_from_position (
2323 longitude : np .ndarray ,
2424 latitude : np .ndarray ,
25- relative_bandwidth : float ,
25+ relative_bandwidth : Optional [float ] = None ,
26+ wavelet_duration : Optional [float ] = None ,
2627 time_step : Optional [float ] = 3600.0 ,
2728 relative_vorticity : Optional [Union [float , np .ndarray ]] = 0.0 ,
2829) -> np .ndarray :
@@ -31,19 +32,27 @@ def inertial_oscillations_from_positions(
3132 This function acts by performing a time-frequency analysis of horizontal displacements
3233 with analytic Morse wavelets. It extracts the portion of the wavelet transform signal
3334 that follows the inertial frequency (opposite of Coriolis frequency) as a function of time,
34- potentially shifted in frequency by a measure of relative vorticity.
35+ potentially shifted in frequency by a measure of relative vorticity. The result is a pair
36+ of zonal and meridional relative displacements in meters.
37+
38+ This function is equivalent to a bandpass filtering of the horizontal displacements. The characteristics
39+ of the filter are defined by the relative bandwidth of the wavelet transform or by the duration of the wavelet,
40+ see the parameters below.
3541
3642 Parameters
3743 ----------
3844 longitude : array-like
3945 Longitude sequence. Unidimensional array input.
4046 latitude : array-like
4147 Latitude sequence. Unidimensional array input.
42- relative_bandwidth : float
48+ relative_bandwidth : float, optional
4349 Bandwidth of the frequency-domain equivalent filter for the extraction of the inertial
4450 oscillations; a number less or equal to one which is a fraction of the inertial frequency.
4551 A value of 0.1 leads to a bandpass filter equivalent of +/- 10 percent of the inertial frequency.
46- time_step : float
52+ wavelet_duration : float, optional
53+ Duration of the wavelet, or inverse of the relative bandwidth, which can be passed instead of the
54+ relative bandwidth.
55+ time_step : float, optional
4756 The constant time interval between data points in seconds. Default is 3600.
4857 relative_vorticity: Optional, float or array-like
4958 Relative vorticity adding to the local Coriolis frequency. If "f" is the Coriolis
@@ -63,28 +72,44 @@ def inertial_oscillations_from_positions(
6372 To extract displacements from inertial oscillations from sequences of longitude
6473 and latitude values, equivalent to bandpass around 20 percent of the local inertial frequency:
6574
66- >>> xhat, yhat = extract_inertial_from_position(longitude, latitude, 0.2)
75+ >>> xhat, yhat = inertial_oscillation_from_position(longitude, latitude, relative_bandwidth=0.2)
76+
77+ The same result can be obtained by specifying the wavelet duration instead of the relative bandwidth:
78+
79+ >>> xhat, yhat = inertial_oscillation_from_position(longitude, latitude, wavelet_duration=5)
6780
6881 Next, the residual positions from the inertial displacements can be obtained with another function:
6982
70- >>> residual_longitudes, residual_latitudes = residual_positions_from_displacements (longitude, latitude, xhat, yhat)
83+ >>> residual_longitudes, residual_latitudes = residual_position_from_displacement (longitude, latitude, xhat, yhat)
7184
7285 Raises
7386 ------
7487 ValueError
7588 If longitude and latitude arrays do not have the same shape.
7689 If relative_vorticity is an array and does not have the same shape as longitude and latitude.
7790 If time_step is not a float.
91+ If both relative_bandwidth and wavelet_duration are specified.
92+ If neither relative_bandwidth nor wavelet_duration are specified.
7893 If the absolute value of relative_bandwidth is not in the range (0,1].
94+ If the wavelet duration is not greater than or equal to 1.
7995
8096 See Also
8197 --------
82- :func:`residual_positions_from_displacements `, `wavelet_transform`, `morse_wavelet`
98+ :func:`residual_position_from_displacement `, `wavelet_transform`, `morse_wavelet`
8399
84100 """
85101 if longitude .shape != latitude .shape :
86102 raise ValueError ("longitude and latitude arrays must have the same shape." )
87103
104+ if relative_bandwidth is not None and wavelet_duration is not None :
105+ raise ValueError (
106+ "Only one of 'relative_bandwidth' and 'wavelet_duration' can be specified"
107+ )
108+ elif relative_bandwidth is None and wavelet_duration is None :
109+ raise ValueError (
110+ "One of 'relative_bandwidth' and 'wavelet_duration' must be specified"
111+ )
112+
88113 # length of data sequence
89114 data_length = longitude .shape [0 ]
90115
@@ -95,14 +120,20 @@ def inertial_oscillations_from_positions(
95120 raise ValueError (
96121 "relative_vorticity must be a float or the same shape as longitude and latitude."
97122 )
123+ if relative_bandwidth is not None :
124+ if not 0 < np .abs (relative_bandwidth ) <= 1 :
125+ raise ValueError ("relative_bandwidth must be in the (0, 1]) range" )
98126
99- if not 0 < np .abs (relative_bandwidth ) <= 1 :
100- raise ValueError ("relative_bandwidth must be in the (0, 1]) range" )
127+ if wavelet_duration is not None :
128+ if not wavelet_duration >= 1 :
129+ raise ValueError ("wavelet_duration must be greater than or equal to 1" )
101130
102131 # wavelet parameters are gamma and beta
103132 gamma = 3 # symmetric wavelet
104133 density = 16 # results relative insensitive to this parameter
105- wavelet_duration = 1 / np .abs (relative_bandwidth ) # P parameter
134+ # calculate beta from wavelet duration or from relative bandwidth
135+ if relative_bandwidth is not None :
136+ wavelet_duration = 1 / np .abs (relative_bandwidth ) # P parameter
106137 beta = wavelet_duration ** 2 / gamma
107138
108139 if isinstance (latitude , xr .DataArray ):
@@ -187,9 +218,9 @@ def inertial_oscillations_from_positions(
187218 return xhat , yhat
188219
189220
190- def residual_positions_from_displacements (
191- longitude : Union [float , np .ndarray ],
192- latitude : Union [float , np .ndarray ],
221+ def residual_position_from_displacement (
222+ longitude : Union [float , np .ndarray , xr . DataArray ],
223+ latitude : Union [float , np .ndarray , xr . DataArray ],
193224 x : Union [float , np .ndarray ],
194225 y : Union [float , np .ndarray ],
195226) -> Union [Tuple [float ], Tuple [np .ndarray ]]:
@@ -202,9 +233,9 @@ def residual_positions_from_displacements(
202233
203234 Parameters
204235 ----------
205- longitude : float or np.ndarray
236+ longitude : float or array-like
206237 Longitude in degrees.
207- latitude : float or np.ndarray
238+ latitude : float or array-like
208239 Latitude in degrees.
209240 x : float or np.ndarray
210241 Zonal displacement in meters.
@@ -224,9 +255,15 @@ def residual_positions_from_displacements(
224255 circumference of the Earth from original position (longitude,latitude) = (1,0):
225256
226257 >>> from clouddrift.sphere import EARTH_RADIUS_METERS
227- >>> residual_positions_from_displacements (1,0,2 * np.pi * EARTH_RADIUS_METERS / 360,0)
258+ >>> residual_position_from_displacement (1,0,2 * np.pi * EARTH_RADIUS_METERS / 360,0)
228259 (0.0, 0.0)
229260 """
261+ # convert to numpy arrays to insure consistent outputs
262+ if isinstance (longitude , xr .DataArray ):
263+ longitude = longitude .to_numpy ()
264+ if isinstance (latitude , xr .DataArray ):
265+ latitude = latitude .to_numpy ()
266+
230267 latitudehat = 180 / np .pi * y / EARTH_RADIUS_METERS
231268 longitudehat = (
232269 180 / np .pi * x / (EARTH_RADIUS_METERS * np .cos (np .radians (latitude )))
0 commit comments