• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

MHKiT-Software / MHKiT-Python / 11297657098

11 Oct 2024 06:42PM UTC coverage: 86.222%. First build
11297657098

Pull #352

github

web-flow
Merge 77c1980f4 into d364d565d
Pull Request #352: Speed up wave.resource module

77 of 81 new or added lines in 4 files covered. (95.06%)

8974 of 10408 relevant lines covered (86.22%)

5.17 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

83.08
/mhkit/wave/resource.py
1
import warnings
6✔
2
from scipy.optimize import fsolve as _fsolve
6✔
3
from scipy import signal as _signal
6✔
4
import pandas as pd
6✔
5
import xarray as xr
6✔
6
import numpy as np
6✔
7
from mhkit.utils import to_numeric_array, convert_to_dataarray
6✔
8

9

10
### Spectrum
11
def elevation_spectrum(
6✔
12
    eta,
13
    sample_rate,
14
    nnft,
15
    window="hann",
16
    detrend=True,
17
    noverlap=None,
18
    time_dimension="",
19
    to_pandas=True,
20
):
21
    """
22
    Calculates the wave energy spectrum from wave elevation time-series
23

24
    Parameters
25
    ------------
26
    eta: pandas DataFrame, pandas Series, xarray DataArray, or xarray Dataset
27
        Wave surface elevation [m] indexed by time [datetime or s]
28
    sample_rate: float
29
        Data frequency [Hz]
30
    nnft: integer
31
        Number of bins in the Fast Fourier Transform
32
    window: string (optional)
33
        Signal window type. 'hann' is used by default given the broadband
34
        nature of waves. See scipy.signal.get_window for more options.
35
    detrend: bool (optional)
36
        Specifies if a linear trend is removed from the data before
37
        calculating the wave energy spectrum.  Data is detrended by default.
38
    noverlap: int, optional
39
        Number of points to overlap between segments. If None,
40
        ``noverlap = nperseg / 2``.  Defaults to None.
41
    time_dimension: string (optional)
42
        Name of the xarray dimension corresponding to time. If not supplied,
43
        defaults to the first dimension. Does not affect pandas input.
44
    to_pandas: bool (optional)
45
        Flag to output pandas instead of xarray. Default = True.
46

47
    Returns
48
    ---------
49
    S: pandas DataFrame or xr.Dataset
50
        Spectral density [m^2/Hz] indexed by frequency [Hz]
51
    """
52

53
    # TODO: Add confidence intervals, equal energy frequency spacing, and NDBC
54
    #       frequency spacing
55
    # TODO: may need to raise an error for the length of nnft- signal.welch breaks when nfft is too short
56
    eta = convert_to_dataarray(eta)
6✔
57
    if not isinstance(sample_rate, (float, int)):
6✔
58
        raise TypeError(
×
59
            f"sample_rate must be of type int or float. Got: {type(sample_rate)}"
60
        )
61
    if not isinstance(nnft, int):
6✔
62
        raise TypeError(f"nnft must be of type int. Got: {type(nnft)}")
×
63
    if not isinstance(window, str):
6✔
64
        raise TypeError(f"window must be of type str. Got: {type(window)}")
×
65
    if not isinstance(detrend, bool):
6✔
66
        raise TypeError(f"detrend must be of type bool. Got: {type(detrend)}")
×
67
    if not nnft > 0:
6✔
68
        raise ValueError(f"nnft must be > 0. Got: {nnft}")
×
69
    if not sample_rate > 0:
6✔
70
        raise ValueError(f"sample_rate must be > 0. Got: {sample_rate}")
×
71
    if not isinstance(to_pandas, bool):
6✔
72
        raise TypeError(f"to_pandas must be of type bool. Got: {type(to_pandas)}")
×
73

74
    if time_dimension == "":
6✔
75
        time_dimension = list(eta.dims)[0]
6✔
76
    else:
77
        if time_dimension not in list(eta.dims):
×
78
            raise ValueError(
×
79
                f"time_dimension is not a dimension of eta ({list(eta.dims)}). Got: {time_dimension}."
80
            )
81
    time = eta[time_dimension]
6✔
82
    delta_t = time.values[1] - time.values[0]
6✔
83
    if not np.allclose(time.diff(dim=time_dimension)[1:], delta_t):
6✔
84
        raise ValueError(
×
85
            "Time bins are not evenly spaced. Create a constant "
86
            + "temporal spacing for eta."
87
        )
88

89
    if detrend:
6✔
90
        eta = _signal.detrend(
6✔
91
            eta.dropna(dim=time_dimension), axis=-1, type="linear", bp=0
92
        )
93
    [f, wave_spec_measured] = _signal.welch(
6✔
94
        eta,
95
        fs=sample_rate,
96
        window=window,
97
        nperseg=nnft,
98
        nfft=nnft,
99
        noverlap=noverlap,
100
    )
101
    S = xr.DataArray(
6✔
102
        data=wave_spec_measured, dims=["Frequency"], coords={"Frequency": f}
103
    )
104

105
    if to_pandas:
6✔
106
        S = S.to_pandas()
6✔
107

108
    return S
6✔
109

110

111
def pierson_moskowitz_spectrum(f, Tp, Hs, to_pandas=True):
6✔
112
    """
113
    Calculates Pierson-Moskowitz Spectrum from IEC TS 62600-2 ED2 Annex C.2 (2019)
114

115
    Parameters
116
    ------------
117
    f: list, np.ndarray, pd.Series, xr.DataArray
118
        Frequency [Hz]
119
    Tp: float/int
120
        Peak period [s]
121
    Hs: float/int
122
        Significant wave height [m]
123
    to_pandas: bool (optional)
124
        Flag to output pandas instead of xarray. Default = True.
125

126
    Returns
127
    ---------
128
    S: xarray Dataset
129
        Spectral density [m^2/Hz] indexed frequency [Hz]
130

131
    """
132
    f = to_numeric_array(f, "f")
6✔
133
    if not isinstance(Tp, (int, float)):
6✔
134
        raise TypeError(f"Tp must be of type int or float. Got: {type(Tp)}")
×
135
    if not isinstance(Hs, (int, float)):
6✔
136
        raise TypeError(f"Hs must be of type int or float. Got: {type(Hs)}")
×
137
    if not isinstance(to_pandas, bool):
6✔
138
        raise TypeError(f"to_pandas must be of type bool. Got: {type(to_pandas)}")
×
139

140
    f.sort()
6✔
141
    B_PM = (5 / 4) * (1 / Tp) ** 4
6✔
142
    A_PM = B_PM * (Hs / 2) ** 2
6✔
143

144
    # Avoid a divide by zero if the 0 frequency is provided
145
    # The zero frequency should always have 0 amplitude, otherwise
146
    # we end up with a mean offset when computing the surface elevation.
147
    Sf = np.zeros(f.size)
6✔
148
    if f[0] == 0.0:
6✔
149
        inds = range(1, f.size)
6✔
150
    else:
151
        inds = range(0, f.size)
6✔
152

153
    Sf[inds] = A_PM * f[inds] ** (-5) * np.exp(-B_PM * f[inds] ** (-4))
6✔
154

155
    name = "Pierson-Moskowitz (" + str(Tp) + "s)"
6✔
156
    S = xr.Dataset(data_vars={name: (["Frequency"], Sf)}, coords={"Frequency": f})
6✔
157

158
    if to_pandas:
6✔
159
        S = S.to_pandas()
6✔
160

161
    return S
6✔
162

163

164
def jonswap_spectrum(f, Tp, Hs, gamma=None, to_pandas=True):
6✔
165
    """
166
    Calculates JONSWAP Spectrum from IEC TS 62600-2 ED2 Annex C.2 (2019)
167

168
    Parameters
169
    ------------
170
    f: list, np.ndarray, pd.Series, xr.DataArray
171
        Frequency [Hz]
172
    Tp: float/int
173
        Peak period [s]
174
    Hs: float/int
175
        Significant wave height [m]
176
    gamma: float (optional)
177
        Gamma
178
    to_pandas: bool (optional)
179
        Flag to output pandas instead of xarray. Default = True.
180

181
    Returns
182
    ---------
183
    S: pandas Series or xarray DataArray
184
        Spectral density [m^2/Hz] indexed frequency [Hz]
185
    """
186
    f = to_numeric_array(f, "f")
6✔
187
    if not isinstance(Tp, (int, float)):
6✔
188
        raise TypeError(f"Tp must be of type int or float. Got: {type(Tp)}")
×
189
    if not isinstance(Hs, (int, float)):
6✔
190
        raise TypeError(f"Hs must be of type int or float. Got: {type(Hs)}")
×
191
    if not isinstance(gamma, (int, float, type(None))):
6✔
192
        raise TypeError(
×
193
            f"If specified, gamma must be of type int or float. Got: {type(gamma)}"
194
        )
195
    if not isinstance(to_pandas, bool):
6✔
196
        raise TypeError(f"to_pandas must be of type bool. Got: {type(to_pandas)}")
×
197

198
    f.sort()
6✔
199
    B_PM = (5 / 4) * (1 / Tp) ** 4
6✔
200
    A_PM = B_PM * (Hs / 2) ** 2
6✔
201

202
    # Avoid a divide by zero if the 0 frequency is provided
203
    # The zero frequency should always have 0 amplitude, otherwise
204
    # we end up with a mean offset when computing the surface elevation.
205
    S_f = np.zeros(f.size)
6✔
206
    if f[0] == 0.0:
6✔
207
        inds = range(1, f.size)
6✔
208
    else:
209
        inds = range(0, f.size)
6✔
210

211
    S_f[inds] = A_PM * f[inds] ** (-5) * np.exp(-B_PM * f[inds] ** (-4))
6✔
212

213
    if not gamma:
6✔
214
        TpsqrtHs = Tp / np.sqrt(Hs)
6✔
215
        if TpsqrtHs <= 3.6:
6✔
216
            gamma = 5
×
217
        elif TpsqrtHs > 5:
6✔
218
            gamma = 1
6✔
219
        else:
220
            gamma = np.exp(5.75 - 1.15 * TpsqrtHs)
6✔
221

222
    # Cutoff frequencies for gamma function
223
    siga = 0.07
6✔
224
    sigb = 0.09
6✔
225

226
    fp = 1 / Tp  # peak frequency
6✔
227
    lind = np.where(f <= fp)
6✔
228
    hind = np.where(f > fp)
6✔
229
    Gf = np.zeros(f.shape)
6✔
230
    Gf[lind] = gamma ** np.exp(-((f[lind] - fp) ** 2) / (2 * siga**2 * fp**2))
6✔
231
    Gf[hind] = gamma ** np.exp(-((f[hind] - fp) ** 2) / (2 * sigb**2 * fp**2))
6✔
232
    C = 1 - 0.287 * np.log(gamma)
6✔
233
    Sf = C * S_f * Gf
6✔
234

235
    name = "JONSWAP (" + str(Hs) + "m," + str(Tp) + "s)"
6✔
236
    S = xr.Dataset(data_vars={name: (["Frequency"], Sf)}, coords={"Frequency": f})
6✔
237

238
    if to_pandas:
6✔
239
        S = S.to_pandas()
6✔
240

241
    return S
6✔
242

243

244
### Metrics
245
def surface_elevation(
6✔
246
    S,
247
    time_index,
248
    seed=None,
249
    frequency_bins=None,
250
    phases=None,
251
    method="ifft",
252
    frequency_dimension="",
253
    to_pandas=True,
254
):
255
    """
256
    Calculates wave elevation time-series from spectrum
257

258
    Parameters
259
    ------------
260
    S: pandas DataFrame, pandas Series, xarray DataArray, or xarray Dataset
261
        Spectral density [m^2/Hz] indexed by frequency [Hz]
262
    time_index: numpy array
263
        Time used to create the wave elevation time-series [s],
264
        for example, time = np.arange(0,100,0.01)
265
    seed: int (optional)
266
        Random seed
267
    frequency_bins: numpy array, pandas Series, or xarray DataArray (optional)
268
        Bin widths for frequency of S. Required for unevenly sized bins
269
    phases: pandas DataFrame, pandas Series, xarray DataArray, or xarray Dataset
270
        Explicit phases for frequency components (overrides seed)
271
        for example, phases = np.random.rand(len(S)) * 2 * np.pi
272
    method: str (optional)
273
        Method used to calculate the surface elevation. 'ifft'
274
        (Inverse Fast Fourier Transform) used by default if the
275
        given frequency_bins==None or is evenly spaced.
276
        'sum_of_sines' explicitly sums each frequency component
277
        and used by default if uneven frequency_bins are provided.
278
        The 'ifft' method is significantly faster.
279
    frequency_dimension: string (optional)
280
        Name of the xarray dimension corresponding to frequency. If not supplied,
281
        defaults to the first dimension (the index for pandas input).
282
    to_pandas: bool (optional)
283
        Flag to output pandas instead of xarray. Default = True.
284

285
    Returns
286
    ---------
287
    eta: pandas DataFrame or xarray Dataset
288
        Wave surface elevation [m] indexed by time [s]
289

290
    """
291
    time_index = to_numeric_array(time_index, "time_index")
6✔
292
    S = convert_to_dataarray(S)
6✔
293
    if not isinstance(seed, (type(None), int)):
6✔
294
        raise TypeError(f"If specified, seed must be of type int. Got: {type(seed)}")
×
295
    if not isinstance(phases, type(None)):
6✔
296
        phases = convert_to_dataarray(phases)
6✔
297
    if not isinstance(method, str):
6✔
298
        raise TypeError(f"method must be of type str. Got: {type(method)}")
×
299
    if not isinstance(to_pandas, bool):
6✔
300
        raise TypeError(f"to_pandas must be of type bool. Got: {type(to_pandas)}")
×
301

302
    if frequency_dimension == "":
6✔
303
        frequency_dimension = list(S.coords)[0]
6✔
304
    elif frequency_dimension not in list(S.dims):
×
305
        # frequency_dimension given, but not in list of possible dimensions
306
        raise ValueError(
×
307
            f"frequency_dimension is not a dimension of S ({list(S.dims)}). Got: {frequency_dimension}."
308
        )
309
    frequency_axis = list(S.dims).index(frequency_dimension)
6✔
310

311
    # Create dimensions and coordinates for the new dataset (frequency becomes time)
312
    new_dims = list(S.dims)
6✔
313
    new_dims[frequency_axis] = "Time"
6✔
314
    new_coords = S.sum(dim=frequency_dimension).coords
6✔
315
    new_coords = new_coords.assign({"Time": time_index})
6✔
316
    f = S[frequency_dimension]
6✔
317

318
    if not isinstance(frequency_bins, (type(None), np.ndarray)):
6✔
319
        frequency_bins = convert_to_dataarray(frequency_bins)
6✔
320
    elif isinstance(frequency_bins, np.ndarray):
6✔
321
        frequency_bins = xr.DataArray(
6✔
322
            data=frequency_bins,
323
            dims=frequency_dimension,
324
            coords={frequency_dimension: f},
325
        )
326
    if frequency_bins is not None:
6✔
327
        if not frequency_bins.squeeze().shape == f.shape:
6✔
328
            raise ValueError(
×
329
                "shape of frequency_bins must only contain 1 column and match the shape of the frequency dimension of S"
330
            )
331
        delta_f = frequency_bins
6✔
332
        delta_f_even = np.allclose(frequency_bins, frequency_bins[0])
6✔
333
        if delta_f_even:
6✔
334
            # reduce delta_f to a scalar if it is uniform
335
            delta_f = delta_f[0].item()
6✔
336
    else:
337
        delta_f = f.values[1] - f.values[0]
6✔
338
        delta_f_even = np.allclose(f.diff(dim=frequency_dimension)[1:], delta_f)
6✔
339
    if phases is not None:
6✔
340
        if not phases.shape == S.shape:
6✔
NEW
341
            raise ValueError(
×
342
                "shape of variables in phases must match shape of variables in S"
343
            )
344
    if method == "ifft":
6✔
345
        # ifft method must have a zero frequency and evenly spaced frequency bins
346
        if not f[0] == 0:
6✔
347
            warnings.warn(
6✔
348
                f"ifft method must have zero frequency defined. Lowest frequency is: {f[0].values}. Setting method to less efficient `sum_of_sines` method."
349
            )
350
            method = "sum_of_sines"
6✔
351
        if not delta_f_even:
6✔
NEW
352
            warnings.warn(
×
353
                f"ifft method must have evenly spaced frequency bins. Setting method to less efficient `sum_of_sines` method."
354
            )
NEW
355
            method = "sum_of_sines"
×
356
    elif method == "sum_of_sines":
6✔
357
        # For sum of sines, does not matter if there is a zero frequency or if frequency bins are evenly spaced
358
        pass
6✔
359
    else:
NEW
360
        raise ValueError(f"Method must be 'ifft' or 'sum_of_sines'. Got: {method}")
×
361

362
    omega = xr.DataArray(
6✔
363
        data=2 * np.pi * f, dims=frequency_dimension, coords={frequency_dimension: f}
364
    )
365

366
    if phases is None:
6✔
367
        np.random.seed(seed)
6✔
368
        phase = xr.DataArray(
6✔
369
            data=2 * np.pi * np.random.random_sample(S[frequency_dimension].shape),
370
            dims=frequency_dimension,
371
            coords={frequency_dimension: f},
372
        )
373
    else:
374
        phase = phases
6✔
375

376
    # Wave amplitude times delta f
377
    A = np.sqrt(2 * S * delta_f)
6✔
378

379
    if method == "ifft":
6✔
380
        A_cmplx = A * (np.cos(phase) + 1j * np.sin(phase))
6✔
381
        # eta_tmp = np.fft.irfft(0.5 * A_cmplx.values * time_index.size, time_index.size)
382
        eta_tmp = np.fft.irfftn(
6✔
383
            0.5 * A_cmplx * time_index.size,
384
            list(time_index.shape),
385
            axes=[frequency_axis],
386
        )
387
        eta = xr.DataArray(data=eta_tmp, dims=new_dims, coords=new_coords)
6✔
388

389
    elif method == "sum_of_sines":
6✔
390
        # Product of omega and time
391
        B = np.outer(time_index, omega)
6✔
392
        B = B.reshape((len(time_index), len(omega)))
6✔
393
        B = xr.DataArray(
6✔
394
            data=B,
395
            dims=["Time", frequency_dimension],
396
            coords={"Time": time_index, frequency_dimension: f},
397
        )
398

399
        # wave elevation
400
        C = np.cos(B + phase)
6✔
401
        eta = (C * A).sum(dim=frequency_dimension)
6✔
402

403
    if to_pandas:
6✔
404
        eta = eta.to_pandas()
6✔
405

406
    return eta
6✔
407

408

409
def frequency_moment(S, N, frequency_bins=None, frequency_dimension="", to_pandas=True):
6✔
410
    """
411
    Calculates the Nth frequency moment of the spectrum
412

413
    Parameters
414
    -----------
415
    S: pandas DataFrame, pandas Series, xarray DataArray, or xarray Dataset
416
        Spectral density [m^2/Hz] indexed by frequency [Hz]
417
    N: int
418
        Moment (0 for 0th, 1 for 1st ....)
419
    frequency_bins: numpy array or pandas Series (optional)
420
        Bin widths for frequency of S. Required for unevenly sized bins
421
    frequency_dimension: string (optional)
422
        Name of the xarray dimension corresponding to frequency. If not supplied,
423
        defaults to the first dimension. Does not affect pandas input.
424
    to_pandas: bool (optional)
425
        Flag to output pandas instead of xarray. Default = True.
426

427
    Returns
428
    -------
429
    m: pandas DataFrame or xarray Dataset
430
        Nth Frequency Moment indexed by S.columns
431
    """
432
    S = convert_to_dataarray(S)
6✔
433
    if not isinstance(N, int):
6✔
434
        raise TypeError(f"N must be of type int. Got: {type(N)}")
×
435
    if not isinstance(to_pandas, bool):
6✔
436
        raise TypeError(f"to_pandas must be of type bool. Got: {type(to_pandas)}")
×
437

438
    if frequency_dimension == "":
6✔
439
        frequency_dimension = list(S.coords)[0]
6✔
440
    elif frequency_dimension not in list(S.dims):
×
441
        raise ValueError(
×
442
            f"frequency_dimension is not a dimension of S ({list(S.dims)}). Got: {frequency_dimension}."
443
        )
444
    f = S[frequency_dimension]
6✔
445

446
    # Eq 8 in IEC 62600-101
447
    S = S.sel({frequency_dimension: slice(1e-12, f.max())})  # omit frequency of 0
6✔
448
    f = S[frequency_dimension]  # reset frequency_dimension without the 0 frequency
6✔
449

450
    fn = np.power(f, N)
6✔
451
    if frequency_bins is None:
6✔
452
        delta_f = f.diff(dim=frequency_dimension)
6✔
453
        delta_f0 = f[1] - f[0]
6✔
454
        delta_f0 = delta_f0.assign_coords({frequency_dimension: f[0]})
6✔
455
        delta_f = xr.concat([delta_f0, delta_f], dim=frequency_dimension)
6✔
456
    else:
457
        delta_f = xr.DataArray(
6✔
458
            data=convert_to_dataarray(frequency_bins),
459
            dims=frequency_dimension,
460
            coords={frequency_dimension: f},
461
        )
462

463
    m = S * fn * delta_f
6✔
464
    m = m.sum(dim=frequency_dimension)
6✔
465

466
    m.name = "m" + str(N)
6✔
467

468
    if to_pandas:
6✔
469
        m = m.to_pandas()
6✔
470

471
    return m
6✔
472

473

474
def significant_wave_height(
6✔
475
    S, frequency_dimension="", frequency_bins=None, to_pandas=True
476
):
477
    """
478
    Calculates wave height from spectra
479

480
    Parameters
481
    ------------
482
    S: pandas DataFrame, pandas Series, xarray DataArray, or xarray Dataset
483
        Spectral density [m^2/Hz] indexed by frequency [Hz]
484
    frequency_dimension: string (optional)
485
        Name of the xarray dimension corresponding to frequency. If not supplied,
486
        defaults to the first dimension. Does not affect pandas input.
487
    frequency_bins: numpy array or pandas Series (optional)
488
        Bin widths for frequency of S. Required for unevenly sized bins
489
    to_pandas: bool (optional)
490
        Flag to output pandas instead of xarray. Default = True.
491

492
    Returns
493
    ---------
494
    Hm0: pandas DataFrame or xarray Dataset
495
        Significant wave height [m] index by S.columns
496
    """
497
    S = convert_to_dataarray(S)
6✔
498
    if not isinstance(to_pandas, bool):
6✔
499
        raise TypeError(f"to_pandas must be of type bool. Got: {type(to_pandas)}")
×
500

501
    # Eq 12 in IEC 62600-101
502
    m0 = frequency_moment(
6✔
503
        S,
504
        0,
505
        frequency_bins=frequency_bins,
506
        frequency_dimension=frequency_dimension,
507
        to_pandas=False,
508
    )
509
    Hm0 = 4 * np.sqrt(m0)
6✔
510

511
    if to_pandas:
6✔
512
        Hm0 = Hm0.to_pandas()
6✔
513

514
    return Hm0
6✔
515

516

517
def average_zero_crossing_period(
6✔
518
    S, frequency_dimension="", frequency_bins=None, to_pandas=True
519
):
520
    """
521
    Calculates wave average zero crossing period from spectra
522

523
    Parameters
524
    ------------
525
    S: pandas DataFrame, pandas Series, xarray DataArray, or xarray Dataset
526
        Spectral density [m^2/Hz] indexed by frequency [Hz]
527
    frequency_dimension: string (optional)
528
        Name of the xarray dimension corresponding to frequency. If not supplied,
529
        defaults to the first dimension. Does not affect pandas input.
530
    frequency_bins: numpy array or pandas Series (optional)
531
        Bin widths for frequency of S. Required for unevenly sized bins
532
    to_pandas: bool (optional)
533
        Flag to output pandas instead of xarray. Default = True.
534

535
    Returns
536
    ---------
537
    Tz: pandas DataFrame or xarray Dataset
538
        Average zero crossing period [s] indexed by S.columns
539
    """
540
    if not isinstance(to_pandas, bool):
6✔
541
        raise TypeError(f"to_pandas must be of type bool. Got: {type(to_pandas)}")
×
542

543
    # Eq 15 in IEC 62600-101
544
    m0 = frequency_moment(
6✔
545
        S,
546
        0,
547
        frequency_bins=frequency_bins,
548
        frequency_dimension=frequency_dimension,
549
        to_pandas=False,
550
    )
551
    m2 = frequency_moment(
6✔
552
        S,
553
        2,
554
        frequency_bins=frequency_bins,
555
        frequency_dimension=frequency_dimension,
556
        to_pandas=False,
557
    )
558
    Tz = np.sqrt(m0 / m2)
6✔
559

560
    if to_pandas:
6✔
561
        Tz = Tz.to_pandas()
6✔
562

563
    return Tz
6✔
564

565

566
def average_crest_period(
6✔
567
    S, frequency_dimension="", frequency_bins=None, to_pandas=True
568
):
569
    """
570
    Calculates wave average crest period from spectra
571

572
    Parameters
573
    ------------
574
    S: pandas DataFrame, pandas Series, xarray DataArray, or xarray Dataset
575
        Spectral density [m^2/Hz] indexed by frequency [Hz]
576
    frequency_dimension: string (optional)
577
        Name of the xarray dimension corresponding to frequency. If not supplied,
578
        defaults to the first dimension. Does not affect pandas input.
579
    frequency_bins: numpy array or pandas Series (optional)
580
        Bin widths for frequency of S. Required for unevenly sized bins
581
    to_pandas: bool (optional)
582
        Flag to output pandas instead of xarray. Default = True.
583

584
    Returns
585
    ---------
586
    Tavg: pandas DataFrame or xarray Dataset
587
        Average wave period [s] indexed by S.columns
588

589
    """
590
    if not isinstance(to_pandas, bool):
6✔
591
        raise TypeError(f"to_pandas must be of type bool. Got: {type(to_pandas)}")
×
592

593
    m2 = frequency_moment(
6✔
594
        S,
595
        2,
596
        frequency_bins=frequency_bins,
597
        frequency_dimension=frequency_dimension,
598
        to_pandas=False,
599
    )
600
    m4 = frequency_moment(
6✔
601
        S,
602
        4,
603
        frequency_bins=frequency_bins,
604
        frequency_dimension=frequency_dimension,
605
        to_pandas=False,
606
    )
607

608
    Tavg = np.sqrt(m2 / m4)
6✔
609

610
    if to_pandas:
6✔
611
        Tavg = Tavg.to_pandas()
6✔
612

613
    return Tavg
6✔
614

615

616
def average_wave_period(S, frequency_dimension="", frequency_bins=None, to_pandas=True):
6✔
617
    """
618
    Calculates mean wave period from spectra
619

620
    Parameters
621
    ------------
622
    S: pandas DataFrame, pandas Series, xarray DataArray, or xarray Dataset
623
        Spectral density [m^2/Hz] indexed by frequency [Hz]
624
    frequency_dimension: string (optional)
625
        Name of the xarray dimension corresponding to frequency. If not supplied,
626
        defaults to the first dimension. Does not affect pandas input.
627
    frequency_bins: numpy array or pandas Series (optional)
628
        Bin widths for frequency of S. Required for unevenly sized bins
629
    to_pandas: bool (optional)
630
        Flag to output pandas instead of xarray. Default = True.
631

632
    Returns
633
    ---------
634
    Tm: pandas DataFrame or xarray Dataset
635
        Mean wave period [s] indexed by S.columns
636
    """
637
    if not isinstance(to_pandas, bool):
6✔
638
        raise TypeError(f"to_pandas must be of type bool. Got: {type(to_pandas)}")
×
639

640
    m0 = frequency_moment(
6✔
641
        S,
642
        0,
643
        frequency_bins=frequency_bins,
644
        frequency_dimension=frequency_dimension,
645
        to_pandas=False,
646
    )
647
    m1 = frequency_moment(
6✔
648
        S,
649
        1,
650
        frequency_bins=frequency_bins,
651
        frequency_dimension=frequency_dimension,
652
        to_pandas=False,
653
    )
654

655
    Tm = np.sqrt(m0 / m1)
6✔
656

657
    if to_pandas:
6✔
658
        Tm = Tm.to_pandas()
6✔
659

660
    return Tm
6✔
661

662

663
def peak_period(S, frequency_dimension="", to_pandas=True):
6✔
664
    """
665
    Calculates wave peak period from spectra
666

667
    Parameters
668
    ------------
669
    S: pandas DataFrame, pandas Series, xarray DataArray, or xarray Dataset
670
        Spectral density [m^2/Hz] indexed by frequency [Hz]
671
    frequency_dimension: string (optional)
672
        Name of the xarray dimension corresponding to frequency. If not supplied,
673
        defaults to the first dimension. Does not affect pandas input.
674
    to_pandas: bool (optional)
675
        Flag to output pandas instead of xarray. Default = True.
676

677
    Returns
678
    ---------
679
    Tp: pandas DataFrame or xarray Dataset
680
        Wave peak period [s] indexed by S.columns
681
    """
682
    S = convert_to_dataarray(S)
6✔
683
    if not isinstance(to_pandas, bool):
6✔
684
        raise TypeError(f"to_pandas must be of type bool. Got: {type(to_pandas)}")
×
685

686
    if frequency_dimension == "":
6✔
687
        frequency_dimension = list(S.coords)[0]
6✔
688
    elif frequency_dimension not in list(S.dims):
×
689
        raise ValueError(
×
690
            f"frequency_dimension is not a dimension of S ({list(S.dims)}). Got: {frequency_dimension}."
691
        )
692

693
    # Eq 14 in IEC 62600-101
694
    fp = S.idxmax(dim=frequency_dimension)  # Hz
6✔
695
    Tp = 1 / fp
6✔
696

697
    if to_pandas:
6✔
698
        Tp = Tp.to_pandas()
6✔
699

700
    return Tp
6✔
701

702

703
def energy_period(S, frequency_dimension="", frequency_bins=None, to_pandas=True):
6✔
704
    """
705
    Calculates wave energy period from spectra
706

707
    Parameters
708
    ------------
709
    S: pandas DataFrame, pandas Series, xarray DataArray, or xarray Dataset
710
        Spectral density [m^2/Hz] indexed by frequency [Hz]
711
    frequency_dimension: string (optional)
712
        Name of the xarray dimension corresponding to frequency. If not supplied,
713
        defaults to the first dimension. Does not affect pandas input.
714
    frequency_bins: numpy array or pandas Series (optional)
715
        Bin widths for frequency of S. Required for unevenly sized bins
716
    to_pandas: bool (optional)
717
        Flag to output pandas instead of xarray. Default = True.
718

719
    Returns
720
    ---------
721
    Te: pandas DataFrame or xarray Dataset
722
        Wave energy period [s] indexed by S.columns
723
    """
724
    S = convert_to_dataarray(S)
6✔
725
    if not isinstance(to_pandas, bool):
6✔
726
        raise TypeError(f"to_pandas must be of type bool. Got: {type(to_pandas)}")
×
727

728
    mn1 = frequency_moment(
6✔
729
        S,
730
        -1,
731
        frequency_bins=frequency_bins,
732
        frequency_dimension=frequency_dimension,
733
        to_pandas=False,
734
    )
735
    m0 = frequency_moment(
6✔
736
        S,
737
        0,
738
        frequency_bins=frequency_bins,
739
        frequency_dimension=frequency_dimension,
740
        to_pandas=False,
741
    )
742

743
    # Eq 13 in IEC 62600-101
744
    Te = mn1 / m0
6✔
745
    Te.name = "Te"
6✔
746

747
    if to_pandas:
6✔
748
        Te = Te.to_pandas()
6✔
749

750
    return Te
6✔
751

752

753
def spectral_bandwidth(S, frequency_dimension="", frequency_bins=None, to_pandas=True):
6✔
754
    """
755
    Calculates bandwidth from spectra
756

757
    Parameters
758
    ------------
759
    S: pandas DataFrame, pandas Series, xarray DataArray, or xarray Dataset
760
        Spectral density [m^2/Hz] indexed by frequency [Hz]
761
    frequency_dimension: string (optional)
762
        Name of the xarray dimension corresponding to frequency. If not supplied,
763
        defaults to the first dimension. Does not affect pandas input.
764
    frequency_bins: numpy array or pandas Series (optional)
765
        Bin widths for frequency of S. Required for unevenly sized bins
766
    to_pandas: bool (optional)
767
        Flag to output pandas instead of xarray. Default = True.
768

769
    Returns
770
    ---------
771
    e: pandas DataFrame or xarray Dataset
772
        Spectral bandwidth [s] indexed by S.columns
773
    """
774
    S = convert_to_dataarray(S)
6✔
775
    if not isinstance(to_pandas, bool):
6✔
776
        raise TypeError(f"to_pandas must be of type bool. Got: {type(to_pandas)}")
×
777

778
    m2 = frequency_moment(
6✔
779
        S,
780
        2,
781
        frequency_bins=frequency_bins,
782
        frequency_dimension=frequency_dimension,
783
        to_pandas=False,
784
    )
785
    m0 = frequency_moment(
6✔
786
        S,
787
        0,
788
        frequency_bins=frequency_bins,
789
        frequency_dimension=frequency_dimension,
790
        to_pandas=False,
791
    )
792
    m4 = frequency_moment(
6✔
793
        S,
794
        4,
795
        frequency_bins=frequency_bins,
796
        frequency_dimension=frequency_dimension,
797
        to_pandas=False,
798
    )
799

800
    e = np.sqrt(1 - (m2**2) / (m0 / m4))
6✔
801

802
    if to_pandas:
6✔
803
        e = e.to_pandas()
6✔
804

805
    return e
6✔
806

807

808
def spectral_width(S, frequency_dimension="", frequency_bins=None, to_pandas=True):
6✔
809
    """
810
    Calculates wave spectral width from spectra
811

812
    Parameters
813
    ------------
814
    S: pandas DataFrame, pandas Series, xarray DataArray, or xarray Dataset
815
        Spectral density [m^2/Hz] indexed by frequency [Hz]
816
    frequency_dimension: string (optional)
817
        Name of the xarray dimension corresponding to frequency. If not supplied,
818
        defaults to the first dimension. Does not affect pandas input.
819
    frequency_bins: numpy array or pandas Series (optional)
820
        Bin widths for frequency of S. Required for unevenly sized bins
821
    to_pandas: bool (optional)
822
        Flag to output pandas instead of xarray. Default = True.
823

824
    Returns
825
    ---------
826
    v: pandas DataFrame or xarray Dataset
827
        Spectral width [m] indexed by S.columns
828
    """
829
    if not isinstance(to_pandas, bool):
6✔
830
        raise TypeError(f"to_pandas must be of type bool. Got: {type(to_pandas)}")
×
831

832
    mn2 = frequency_moment(
6✔
833
        S,
834
        -2,
835
        frequency_bins=frequency_bins,
836
        frequency_dimension=frequency_dimension,
837
        to_pandas=False,
838
    )
839
    m0 = frequency_moment(
6✔
840
        S,
841
        0,
842
        frequency_bins=frequency_bins,
843
        frequency_dimension=frequency_dimension,
844
        to_pandas=False,
845
    )
846
    mn1 = frequency_moment(
6✔
847
        S,
848
        -1,
849
        frequency_bins=frequency_bins,
850
        frequency_dimension=frequency_dimension,
851
        to_pandas=False,
852
    )
853

854
    # Eq 16 in IEC 62600-101
855
    v = np.sqrt((m0 * mn2 / np.power(mn1, 2)) - 1)
6✔
856

857
    if to_pandas:
6✔
858
        v = v.to_pandas()
6✔
859

860
    return v
6✔
861

862

863
def energy_flux(
6✔
864
    S,
865
    h,
866
    deep=False,
867
    rho=1025,
868
    g=9.80665,
869
    ratio=2,
870
    frequency_dimension="",
871
    to_pandas=True,
872
):
873
    """
874
    Calculates the omnidirectional wave energy flux of the spectra
875

876
    Parameters
877
    -----------
878
    S: pandas DataFrame, pandas Series, xarray DataArray, or xarray Dataset
879
        Spectral density [m^2/Hz] indexed by frequency [Hz]
880
    h: float
881
        Water depth [m]
882
    deep: bool (optional)
883
        If True use the deep water approximation. Default False. When
884
        False a depth check is run to check for shallow water. The ratio
885
        of the shallow water regime can be changed using the ratio
886
        keyword.
887
    rho: float (optional)
888
        Water Density [kg/m^3]. Default = 1025 kg/m^3
889
    g : float (optional)
890
        Gravitational acceleration [m/s^2]. Default = 9.80665 m/s^2
891
    ratio: float or int (optional)
892
        Only applied if depth=False. If h/l > ratio,
893
        water depth will be set to deep. Default ratio = 2.
894
    frequency_dimension: string (optional)
895
        Name of the xarray dimension corresponding to frequency. If not supplied,
896
        defaults to the first dimension. Does not affect pandas input.
897
    to_pandas: bool (optional)
898
        Flag to output pandas instead of xarray. Default = True.
899

900
    Returns
901
    -------
902
    J: pandas DataFrame or xarray Dataset
903
        Omni-directional wave energy flux [W/m] indexed by S.columns
904
    """
905
    S = convert_to_dataarray(S)
6✔
906
    if not isinstance(h, (int, float)):
6✔
907
        raise TypeError(f"h must be of type int or float. Got: {type(h)}")
×
908
    if not isinstance(deep, bool):
6✔
909
        raise TypeError(f"deep must be of type bool. Got: {type(deep)}")
×
910
    if not isinstance(rho, (int, float)):
6✔
911
        raise TypeError(f"rho must be of type int or float. Got: {type(rho)}")
×
912
    if not isinstance(g, (int, float)):
6✔
913
        raise TypeError(f"g must be of type int or float. Got: {type(g)}")
×
914
    if not isinstance(ratio, (int, float)):
6✔
915
        raise TypeError(f"ratio must be of type int or float. Got: {type(ratio)}")
×
916
    if not isinstance(to_pandas, bool):
6✔
917
        raise TypeError(f"to_pandas must be of type bool. Got: {type(to_pandas)}")
×
918

919
    if frequency_dimension == "":
6✔
920
        frequency_dimension = list(S.coords)[0]
6✔
921
    elif frequency_dimension not in list(S.dims):
×
922
        raise ValueError(
×
923
            f"frequency_dimension is not a dimension of S ({list(S.dims)}). Got: {frequency_dimension}."
924
        )
925
    f = S[frequency_dimension]
6✔
926

927
    if deep:
6✔
928
        # Eq 8 in IEC 62600-100, deep water simplification
929
        Te = energy_period(S, to_pandas=False)
6✔
930
        Hm0 = significant_wave_height(S, to_pandas=False)
6✔
931

932
        coeff = rho * (g**2) / (64 * np.pi)
6✔
933

934
        J = coeff * (Hm0**2) * Te
6✔
935

936
    else:
937
        # deep water flag is false
938
        k = wave_number(f, h, rho, g, to_pandas=False)
6✔
939

940
        # wave celerity (group velocity)
941
        Cg = wave_celerity(k, h, g, depth_check=True, ratio=ratio, to_pandas=False)
6✔
942

943
        # Calculating the wave energy flux, Eq 9 in IEC 62600-101
944
        delta_f = f.diff(dim=frequency_dimension)
6✔
945
        delta_f0 = f[1] - f[0]
6✔
946
        delta_f0 = delta_f0.assign_coords({frequency_dimension: f[0]})
6✔
947
        delta_f = xr.concat([delta_f0, delta_f], dim=frequency_dimension)
6✔
948

949
        CgSdelF = S * delta_f * Cg
6✔
950

951
        J = rho * g * CgSdelF.sum(dim=frequency_dimension)
6✔
952

953
    if to_pandas:
6✔
954
        J = J.to_pandas()
6✔
955

956
    return J
6✔
957

958

959
def energy_period_to_peak_period(Te, gamma):
6✔
960
    """
961
    Convert from spectral energy period (Te) to peak period (Tp) using ITTC approximation for JONSWAP Spectrum.
962

963
    Approximation is given in "The Specialist Committee on Waves, Final Report
964
    and Recommendations to the 23rd ITTC", Proceedings of the 23rd ITTC - Volume
965
    2, Table A4.
966

967
    Parameters
968
    ----------
969
    Te: int, float, np.ndarray, pd.Series, pd.DataFrame, xr.DataArray, xr.Dataset
970
    gamma: float or int
971
        Peak enhancement factor for JONSWAP spectrum
972

973
    Returns
974
    -------
975
    Tp: float or array
976
        Spectral peak period [s]
977
    """
978
    if not isinstance(
6✔
979
        Te, (int, float, np.ndarray, pd.Series, pd.DataFrame, xr.DataArray, xr.Dataset)
980
    ):
981
        raise TypeError(
×
982
            f"Te must be an int, float, np.ndarray, pd.Series, pd.DataFrame, xr.DataArray or xr.Dataset. Got: {type(Te)}"
983
        )
984
    if not isinstance(gamma, (float, int)):
6✔
985
        raise TypeError(f"gamma must be of type float or int. Got: {type(gamma)}")
×
986

987
    factor = 0.8255 + 0.03852 * gamma - 0.005537 * gamma**2 + 0.0003154 * gamma**3
6✔
988

989
    Tp = Te / factor
6✔
990

991
    return Tp
6✔
992

993

994
def wave_celerity(
6✔
995
    k, h, g=9.80665, depth_check=False, ratio=2, frequency_dimension="", to_pandas=True
996
):
997
    """
998
    Calculates wave celerity (group velocity)
999

1000
    Parameters
1001
    ----------
1002
    k: pandas DataFrame, pandas Series, xarray DataArray, or xarray Dataset
1003
        Wave number [1/m] indexed by frequency [Hz]
1004
    h: float
1005
        Water depth [m]
1006
    g : float (optional)
1007
        Gravitational acceleration [m/s^2]. Default 9.80665 m/s.
1008
    depth_check: bool (optional)
1009
        If True check depth regime. Default False.
1010
    ratio: float or int (optional)
1011
        Only applied if depth_check=True. If h/l > ratio,
1012
        water depth will be set to deep. Default ratio = 2
1013
    frequency_dimension: string (optional)
1014
        Name of the xarray dimension corresponding to frequency. If not supplied,
1015
        defaults to the first dimension. Does not affect pandas input.
1016
    to_pandas: bool (optional)
1017
        Flag to output pandas instead of xarray. Default = True.
1018

1019
    Returns
1020
    -------
1021
    Cg: pandas DataFrame or xarray Dataset
1022
        Water celerity [m/s] indexed by frequency [Hz]
1023
    """
1024
    k = convert_to_dataarray(k)
6✔
1025
    if not isinstance(h, (int, float)):
6✔
1026
        raise TypeError(f"h must be of type int or float. Got: {type(h)}")
×
1027
    if not isinstance(g, (int, float)):
6✔
1028
        raise TypeError(f"g must be of type int or float. Got: {type(g)}")
×
1029
    if not isinstance(depth_check, bool):
6✔
1030
        raise TypeError(f"depth_check must be of type bool. Got: {type(depth_check)}")
×
1031
    if not isinstance(ratio, (int, float)):
6✔
1032
        raise TypeError(f"ratio must be of type int or float. Got: {type(ratio)}")
×
1033
    if not isinstance(to_pandas, bool):
6✔
1034
        raise TypeError(f"to_pandas must be of type bool. Got: {type(to_pandas)}")
×
1035

1036
    if frequency_dimension == "":
6✔
1037
        frequency_dimension = list(k.coords)[0]
6✔
1038
    elif frequency_dimension not in list(k.dims):
×
1039
        raise ValueError(
×
1040
            f"frequency_dimension is not a dimension of k ({list(k.dims)}). Got: {frequency_dimension}."
1041
        )
1042
    f = k[frequency_dimension]
6✔
1043
    k = k.values
6✔
1044

1045
    if depth_check:
6✔
1046
        l = wave_length(k)
6✔
1047

1048
        # get depth regime
1049
        dr = depth_regime(l, h, ratio=ratio)
6✔
1050

1051
        # deep frequencies
1052
        df = f[dr]
6✔
1053
        dk = k[dr]
6✔
1054

1055
        # deep water approximation
1056
        dCg = np.pi * df / dk
6✔
1057
        dCg = xr.DataArray(
6✔
1058
            data=dCg, dims=frequency_dimension, coords={frequency_dimension: df}
1059
        )
1060
        dCg.name = "Cg"
6✔
1061

1062
        # shallow frequencies
1063
        sf = f[~dr]
6✔
1064
        sk = k[~dr]
6✔
1065
        sCg = (np.pi * sf / sk) * (1 + (2 * h * sk) / np.sinh(2 * h * sk))
6✔
1066
        sCg = xr.DataArray(
6✔
1067
            data=sCg, dims=frequency_dimension, coords={frequency_dimension: sf}
1068
        )
1069
        sCg.name = "Cg"
6✔
1070

1071
        Cg = xr.concat([dCg, sCg], dim=frequency_dimension).sortby(frequency_dimension)
6✔
1072
        Cg.name = "Cg"
6✔
1073

1074
    else:
1075
        # Eq 10 in IEC 62600-101
1076
        Cg = (np.pi * f / k) * (1 + (2 * h * k) / np.sinh(2 * h * k))
6✔
1077
        Cg = xr.DataArray(
6✔
1078
            data=Cg, dims=frequency_dimension, coords={frequency_dimension: f}
1079
        )
1080
        Cg.name = "Cg"
6✔
1081

1082
    # Cg = Cg.to_dataset()
1083

1084
    if to_pandas:
6✔
1085
        Cg = Cg.to_pandas()
6✔
1086

1087
    return Cg
6✔
1088

1089

1090
def wave_length(k):
6✔
1091
    """
1092
    Calculates wave length from wave number
1093
    To compute: 2*pi/wavenumber
1094

1095
    Parameters
1096
    -------------
1097
    k: int, float, numpy ndarray, pandas Series, pandas DataFrame, xarray DataArray, or xarray Dataset
1098
        Wave number [1/m] indexed by frequency
1099

1100
    Returns
1101
    ---------
1102
    l: int, float, numpy ndarray, pandas Series, pandas DataFrame, xarray DataArray, or xarray Dataset
1103
        Wave length [m] indexed by frequency. Output type is identical to the type of k.
1104
    """
1105
    if not isinstance(
6✔
1106
        k, (int, float, np.ndarray, pd.Series, pd.DataFrame, xr.DataArray, xr.Dataset)
1107
    ):
1108
        raise TypeError(
×
1109
            f"k must be an int, float, np.ndarray, pd.Series, pd.DataFrame, xr.DataArray or xr.Dataset. Got: {type(k)}"
1110
        )
1111

1112
    l = 2 * np.pi / k
6✔
1113

1114
    return l
6✔
1115

1116

1117
def wave_number(f, h, rho=1025, g=9.80665, to_pandas=True):
6✔
1118
    """
1119
    Calculates wave number
1120

1121
    To compute wave number from angular frequency (w), convert w to f before
1122
    using this function (f = w/2*pi)
1123

1124
    Parameters
1125
    -----------
1126
    f: int, float, numpy ndarray, pandas DataFrame, pandas Series, xarray DataArray
1127
        Frequency [Hz]
1128
    h: float
1129
        Water depth [m]
1130
    rho: float (optional)
1131
        Water density [kg/m^3]
1132
    g: float (optional)
1133
        Gravitational acceleration [m/s^2]
1134
    to_pandas: bool (optional)
1135
        Flag to output pandas instead of xarray. Default = True.
1136

1137
    Returns
1138
    -------
1139
    k: pandas DataFrame or xarray Dataset
1140
        Wave number [1/m] indexed by frequency [Hz]
1141
    """
1142
    if isinstance(f, (int, float)):
6✔
1143
        f = np.asarray([f])
6✔
1144
    # f = convert_to_dataarray(f)
1145
    if not isinstance(h, (int, float)):
6✔
1146
        raise TypeError(f"h must be of type int or float. Got: {type(h)}")
×
1147
    if not isinstance(rho, (int, float)):
6✔
1148
        raise TypeError(f"rho must be of type int or float. Got: {type(rho)}")
×
1149
    if not isinstance(g, (int, float)):
6✔
1150
        raise TypeError(f"g must be of type int or float. Got: {type(g)}")
×
1151
    if not isinstance(to_pandas, bool):
6✔
1152
        raise TypeError(f"to_pandas must be of type bool. Got: {type(to_pandas)}")
×
1153

1154
    w = 2 * np.pi * f  # angular frequency
6✔
1155
    xi = w / np.sqrt(g / h)  # note: =h*wa/sqrt(h*g/h)
6✔
1156
    yi = xi * xi / np.power(1.0 - np.exp(-np.power(xi, 2.4908)), 0.4015)
6✔
1157
    k0 = yi / h  # Initial guess without current-wave interaction
6✔
1158

1159
    # Eq 11 in IEC 62600-101 using initial guess from Guo (2002)
1160
    def func(kk):
6✔
1161
        val = np.power(w, 2) - g * kk * np.tanh(kk * h)
6✔
1162
        return val
6✔
1163

1164
    mask = np.abs(func(k0)) > 1e-9
6✔
1165
    if mask.sum() > 0:
6✔
1166
        k0_mask = k0[mask]
6✔
1167
        w = w[mask]
6✔
1168

1169
        k, info, ier, mesg = _fsolve(func, k0_mask, full_output=True)
6✔
1170
        if not ier == 1:
6✔
1171
            raise ValueError("Wave number not found. " + mesg)
×
1172
        k0[mask] = k
6✔
1173
    k = k0
6✔
1174

1175
    if isinstance(k0, (pd.Series, xr.DataArray)):
6✔
1176
        k.name = "k"
6✔
1177

1178
    # if to_pandas:
1179
    #     k = k.to_pandas()
1180

1181
    return k
6✔
1182

1183

1184
def depth_regime(l, h, ratio=2):
6✔
1185
    """
1186
    Calculates the depth regime based on wavelength and height
1187
    Deep water: h/l > ratio
1188
    This function exists so sinh in wave celerity doesn't blow
1189
    up to infinity.
1190

1191
    P.K. Kundu, I.M. Cohen (2000) suggest h/l >> 1 for deep water (pg 209)
1192
    Same citation as above, they also suggest for 3% accuracy, h/l > 0.28 (pg 210)
1193
    However, since this function allows multiple wavelengths, higher ratio
1194
    numbers are more accurate across varying wavelengths.
1195

1196
    Parameters
1197
    ----------
1198
    l: int, float, np.ndarray, pd.Series, pd.DataFrame, xr.DataArray, xr.Dataset
1199
        wavelength [m]
1200
    h: float or int
1201
        water column depth [m]
1202
    ratio: float or int (optional)
1203
        if h/l > ratio, water depth will be set to deep. Default ratio = 2
1204

1205
    Returns
1206
    -------
1207
    depth_reg: boolean or boolean array-like
1208
        Boolean True if deep water, False otherwise
1209
    """
1210
    if not isinstance(
6✔
1211
        l, (int, float, np.ndarray, pd.Series, pd.DataFrame, xr.DataArray, xr.Dataset)
1212
    ):
1213
        raise TypeError(
×
1214
            f"l must be of type int, float, np.ndarray, pd.DataFrame, pd.Series, xr.DataArray, or xr.Dataset. Got: {type(l)}"
1215
        )
1216
    if not isinstance(h, (int, float)):
6✔
1217
        raise TypeError(f"h must be of type int or float. Got: {type(h)}")
×
1218

1219
    depth_reg = h / l > ratio
6✔
1220

1221
    return depth_reg
6✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc