wawi 0.0.13__py3-none-any.whl → 0.0.17__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
wawi/wave.py CHANGED
@@ -10,6 +10,29 @@ from scipy.optimize import fsolve
10
10
  from wawi.general import eval_fun_or_scalar
11
11
 
12
12
  def linear_drag_damping(drag_coefficient, std_udot, area=1.0, rho=1020.0, as_matrix=True):
13
+ """
14
+ Calculate the linear drag damping for a given drag coefficient, standard deviation of velocity, area, and density.
15
+
16
+ Parameters
17
+ ----------
18
+ drag_coefficient : float
19
+ The drag coefficient.
20
+ std_udot : float
21
+
22
+ The standard deviation of the velocity.
23
+ area : float, optional
24
+ The area of the object (default is 1.0).
25
+ rho : float, optional
26
+ The density of the fluid (default is 1020.0).
27
+ as_matrix : bool, optional
28
+ If True, return the damping as a matrix (default is True).
29
+
30
+ Returns
31
+ -------
32
+ damping : float or np.ndarray
33
+ The calculated linear drag damping. If as_matrix is True, returns a diagonal matrix.
34
+
35
+ """
13
36
  damping = 0.5*rho*area*drag_coefficient*np.sqrt(8/np.pi)*std_udot
14
37
 
15
38
  if as_matrix == True and (len(damping)==3 or len(damping)==6):
@@ -18,6 +41,22 @@ def linear_drag_damping(drag_coefficient, std_udot, area=1.0, rho=1020.0, as_mat
18
41
  return damping
19
42
 
20
43
  def stochastic_linearize(C_quad, std_udot):
44
+ """
45
+ Stochastic linearization of the quadratic drag damping.
46
+
47
+ Parameters
48
+ ----------
49
+ C_quad : float or np.ndarray
50
+ The quadratic drag coefficient.
51
+ std_udot : float or np.ndarray
52
+ The standard deviation of the velocity.
53
+
54
+ Returns
55
+ -------
56
+ damping : np.ndarray
57
+ The calculated stochastic linearized damping.
58
+
59
+ """
21
60
  # Input C_quad is assumed matrix form, std_udot is assumed matrix
22
61
 
23
62
  if np.ndim(std_udot)==1:
@@ -26,6 +65,22 @@ def stochastic_linearize(C_quad, std_udot):
26
65
  return C_quad*np.sqrt(8/np.pi)*std_udot
27
66
 
28
67
  def harmonic_linearize(C_quad, udot):
68
+ """
69
+ Harmonic linearization of the quadratic drag damping.
70
+
71
+ Parameters
72
+ ----------
73
+ C_quad : float or np.ndarray
74
+ The quadratic drag coefficient.
75
+ udot : float or np.ndarray
76
+ The velocity.
77
+
78
+ Returns
79
+ -------
80
+ damping : np.ndarray
81
+ The calculated harmonic linearized damping.
82
+
83
+ """
29
84
  if np.ndim(udot)==2:
30
85
  udot = np.diag(np.diag(udot))
31
86
  else:
@@ -36,12 +91,48 @@ def harmonic_linearize(C_quad, udot):
36
91
 
37
92
 
38
93
  def get_coh_fourier(omega, dx, dy, D, theta0, theta_shift=0.0, depth=np.inf,
39
- k_max=10, input_is_kappa=False):
40
- '''
41
- theta_shift is used to translate D, such
42
- that non-centered are allowed. Docs to come.
43
- '''
94
+ k_max=10, input_is_kappa=False):
95
+ """
96
+ Compute the coherence function using Fourier coefficients.
97
+ This function calculates the coherence between two points separated by (dx, dy)
98
+ using a Fourier-based approach. The function allows for non-centered distributions
99
+ of the directional spreading function `D` via the `theta_shift` parameter.
100
+
101
+ Parameters
102
+ ----------
103
+ omega : array_like
104
+ Angular frequency array.
105
+ dx : float
106
+ Separation in the x-direction.
107
+ dy : float
108
+ Separation in the y-direction.
109
+ D : callable
110
+ Directional spreading function. Should accept an array of angles and return
111
+ the corresponding spreading values.
112
+ theta0 : float
113
+ Mean wave direction (radians).
114
+ theta_shift : float, optional
115
+ Shift applied to the directional spreading function `D` (default is 0.0).
116
+ depth : float, optional
117
+ Water depth. Default is np.inf (deep water).
118
+ k_max : int, optional
119
+ Maximum Fourier mode to use (default is 10).
120
+ input_is_kappa : bool, optional
121
+ If True, `omega` is interpreted as wavenumber `kappa` (default is False).
122
+
123
+ Returns
124
+ -------
125
+ coh : ndarray
126
+ Coherence values for each frequency in `omega`.
44
127
 
128
+ Notes
129
+ -----
130
+ - The function uses the Bessel function of the first kind (`jv`) and the
131
+ dispersion relation for water waves.
132
+ - The Fourier coefficients of the spreading function `D` are computed using
133
+ the inverse FFT.
134
+ - The function assumes that `D` is vectorized and can accept numpy arrays.
135
+ """
45
136
  L = np.sqrt(dx**2+dy**2)
46
137
  phi = np.arctan2(dy, dx)
47
138
  beta = theta0 - phi
@@ -66,6 +157,42 @@ def get_coh_fourier(omega, dx, dy, D, theta0, theta_shift=0.0, depth=np.inf,
66
157
 
67
158
  def get_coh_cos2s(omega, dx, dy, s, theta0, k_max=10, depth=np.inf,
68
159
  input_is_kappa=False):
160
+ """
161
+ Computes the coherence function using the cos^2s model.
162
+
163
+ Parameters
164
+ ----------
165
+ omega : array_like
166
+ Angular frequency array.
167
+ dx : float
168
+ X-distance between two points.
169
+ dy : float
170
+ Y-distance between two points.
171
+ s : float
172
+ Shape parameter for the cos^2s model.
173
+ theta0 : float
174
+ Mean wave direction (radians).
175
+ k_max : int, optional
176
+ Maximum order of Bessel function terms to include (default is 10).
177
+ depth : float, optional
178
+ Water depth. Default is np.inf (deep water).
179
+ input_is_kappa : bool, optional
180
+ If True, `omega` is interpreted as wavenumber (`kappa`). If False, wavenumber is computed from `omega` and `depth`.
181
+
182
+ Returns
183
+ -------
184
+ coh : ndarray
185
+ Coherence values for each frequency in `omega`.
186
+
187
+ Notes
188
+ -----
189
+ This function uses a series expansion involving Bessel functions and the gamma function to compute the spatial coherence between two points separated by (dx, dy) under the cos^2s directional spreading model.
190
+
191
+ References
192
+ ----------
193
+ - Earle, M. D., & Bush, K. A. (1982). "A cos^2s model of directional spreading." Journal of Physical Oceanography, 12(11), 1251-1257.
194
+ """
195
+
69
196
  if input_is_kappa:
70
197
  kappa = omega*1
71
198
  else:
@@ -85,7 +212,49 @@ def get_coh_cos2s(omega, dx, dy, s, theta0, k_max=10, depth=np.inf,
85
212
  def get_coh(omega, dx, dy, D1, D2=None, depth=np.inf, n_theta=40,
86
213
  theta_shift=0.0, input_is_kappa=False, twodimensional=False,
87
214
  include_D=True):
88
-
215
+ """
216
+ Compute the coherence function for a given frequency and spatial separation.
217
+
218
+ Parameters
219
+ ----------
220
+ omega : array_like
221
+ Angular frequency values.
222
+ dx : float
223
+ Spatial separation in the x-direction.
224
+ dy : float
225
+ Spatial separation in the y-direction.
226
+ D1 : callable
227
+ Directional spreading function for the first point. Should accept an array of angles (theta).
228
+ D2 : callable, optional
229
+ Directional spreading function for the second point. If None, D2 is set to D1.
230
+ depth : float, optional
231
+ Water depth. Default is np.inf (deep water).
232
+ n_theta : int, optional
233
+ Number of angular discretization points. Default is 40.
234
+ theta_shift : float, optional
235
+ Phase shift to apply to the angle theta. Default is 0.0.
236
+ input_is_kappa : bool, optional
237
+ If True, omega is interpreted as wavenumber (kappa) instead of angular frequency. Default is False.
238
+ twodimensional : bool, optional
239
+ If True, return the full 2D coherence array and theta values. If False, integrate over theta and return 1D coherence. Default is False.
240
+ include_D : bool, optional
241
+ If True, include the directional spreading functions D1 and D2 in the calculation. Default is True.
242
+
243
+ Returns
244
+ -------
245
+ coh : ndarray
246
+ Coherence values as a function of omega (if twodimensional is False).
247
+ coh2d : ndarray
248
+ 2D coherence array as a function of omega and theta (if twodimensional is True).
249
+ theta : ndarray
250
+ Array of theta values (only if twodimensional is True).
251
+
252
+ Notes
253
+ -----
254
+ The function computes the spatial coherence between two points separated by (dx, dy) for a given frequency axis (omega),
255
+ optionally including directional spreading functions and integrating over direction.
256
+ """
257
+
89
258
  if D2 is None: #assumes the same as D1
90
259
  D2 = D1
91
260
 
@@ -113,6 +282,54 @@ def get_coh(omega, dx, dy, D1, D2=None, depth=np.inf, n_theta=40,
113
282
 
114
283
  def xsim(x, y, S, D, omega, fs=None, theta=None, n_theta=40, grid_mode=True, print_progress=True,
115
284
  time_history=False, phase=None, return_phases=False, theta_shift=0.0):
285
+ """
286
+ Generate a time history of the wave elevation at a given point in space.
287
+
288
+ Parameters
289
+ ----------
290
+ x : float or array_like
291
+ X-coordinate of the point in space.
292
+ y : float or array_like
293
+ Y-coordinate of the point in space.
294
+ S : callable
295
+ Wave spectrum function. Should accept an array of angular frequencies (omega).
296
+ D : callable
297
+ Directional spreading function. Should accept an array of angles (theta).
298
+ omega : array_like
299
+ Angular frequency values.
300
+ fs : float, optional
301
+ Sampling frequency. If None, it is set to the maximum frequency in omega divided by 2π.
302
+ theta : array_like, optional
303
+ Array of angles (in radians) for the directional spreading function. If None, it is set to a default range.
304
+ n_theta : int, optional
305
+ Number of angles to use for the directional spreading function. Default is 40.
306
+ grid_mode : bool, optional
307
+ If True, the output is reshaped into a grid format. Default is True.
308
+ print_progress : bool, optional
309
+ If True, print progress updates during the computation. Default is True.
310
+ time_history : bool, optional
311
+ If True, generate a time history of the wave elevation. Default is False.
312
+ phase : array_like, optional
313
+ Phase angles for the wave components. If None, random phases are generated.
314
+ return_phases : bool, optional
315
+ If True, return the generated phase angles. Default is False.
316
+ theta_shift : float, optional
317
+ Phase shift to apply to the angle theta. Default is 0.0.
318
+
319
+ Returns
320
+ -------
321
+ eta : ndarray
322
+ Wave elevation time history or spatial distribution.
323
+ t : ndarray
324
+ Time vector corresponding to the wave elevation (only if time_history is True).
325
+ phase : ndarray
326
+ Phase angles of the wave components (only if return_phases is True).
327
+
328
+ Notes
329
+ -----
330
+ Docstring is generated using GitHub Copilot.
331
+
332
+ """
116
333
 
117
334
  if fs is None:
118
335
  fs = np.max(omega)/2/np.pi
@@ -206,15 +423,100 @@ def xsim(x, y, S, D, omega, fs=None, theta=None, n_theta=40, grid_mode=True, pri
206
423
 
207
424
 
208
425
  def swh_from_gamma_alpha_Tp(gamma, alpha, Tp, g=9.81):
426
+ """
427
+ Calculate significant wave height (Hs) from gamma, alpha, and peak period (Tp).
428
+
429
+ Parameters
430
+ ----------
431
+ gamma : float
432
+ Peak enhancement factor (dimensionless).
433
+ alpha : float
434
+ Phillips constant (dimensionless).
435
+ Tp : float
436
+ Peak wave period (seconds).
437
+ g : float, optional
438
+ Acceleration due to gravity (m/s^2). Default is 9.81.
439
+
440
+ Returns
441
+ -------
442
+ Hs : float
443
+ Significant wave height (meters).
444
+
445
+ Notes
446
+ -----
447
+ The formula is based on a parameterization involving the JONSWAP spectrum.
448
+ Docstring is generated using GitHub Copilot.
449
+ """
450
+
209
451
  wp = 2*np.pi/Tp
210
452
 
211
453
  Hs = (1.555 + 0.2596*gamma - 0.02231*gamma**2 + 0.01142*gamma**3)*g*np.sqrt(alpha)/wp**2
212
454
  return Hs
213
455
 
214
456
  def sigma_from_sigma_range(sigma, wp):
457
+ """
458
+ Create a step function for sigma based on a frequency threshold.
459
+
460
+ Given a tuple `sigma` representing two values and a threshold frequency `wp`,
461
+ this function returns a lambda function that outputs `sigma[0]` for input
462
+ frequencies `w` less than or equal to `wp`, and `sigma[1]` for `w` greater
463
+ than `wp`.
464
+
465
+ Parameters
466
+ ----------
467
+ sigma : tuple of float
468
+ A tuple containing two values (sigma_low, sigma_high) representing the
469
+ sigma value below and above the threshold frequency `wp`.
470
+ wp : float
471
+ The threshold frequency at which the sigma value changes.
472
+
473
+ Returns
474
+ -------
475
+ function
476
+ A lambda function that takes a frequency `w` and returns the corresponding
477
+ sigma value based on the threshold `wp`.
478
+
479
+ Examples
480
+ --------
481
+ >>> f = sigma_from_sigma_range((1.0, 2.0), 5.0)
482
+ >>> f(4.0)
483
+ 1.0
484
+ >>> f(6.0)
485
+ 2.0
486
+
487
+ Notes
488
+ -----
489
+ Docstring is generated using GitHub Copilot.
490
+ """
491
+
215
492
  return lambda w: (sigma[0]+(sigma[1]-sigma[0])*(w>wp))
216
493
 
217
494
  def peak_enhancement(gamma, Tp, sigma, normalize=True):
495
+ """
496
+ Peak enhancement function for the JONSWAP spectrum.
497
+
498
+ Parameters
499
+ ----------
500
+ gamma : float
501
+ Peak enhancement factor (dimensionless).
502
+ Tp : float
503
+ Peak wave period (seconds).
504
+ sigma : float or tuple of float
505
+ Standard deviation of the peak frequency (dimensionless). If a tuple,
506
+ it represents the range of sigma values.
507
+ normalize : bool, optional
508
+ If True, normalize the peak enhancement function (default is True).
509
+
510
+ Returns
511
+ -------
512
+ function
513
+ A lambda function that takes a frequency `w` and returns the peak
514
+ enhancement value based on the input parameters.
515
+
516
+ Notes
517
+ ---------
518
+ Docstring is generated using GitHub Copilot.
519
+ """
218
520
  wp = 2*np.pi/Tp
219
521
  sigma = sigma_from_sigma_range(sigma, wp)
220
522
  if normalize:
@@ -225,6 +527,40 @@ def peak_enhancement(gamma, Tp, sigma, normalize=True):
225
527
 
226
528
 
227
529
  def pm2(Hs, Tp, unit='Hz'):
530
+ """
531
+ Compute the Pierson-Moskowitz (PM) wave spectrum function.
532
+
533
+ Parameters
534
+ ----------
535
+ Hs : float
536
+ Significant wave height.
537
+ Tp : float
538
+ Peak wave period.
539
+ unit : {'Hz', 'rad/s'}, optional
540
+ Unit of the frequency input for the returned spectrum function.
541
+ If 'Hz', the function expects frequency in Hertz.
542
+ If 'rad/s', the function expects angular frequency in radians per second.
543
+ Default is 'Hz'.
544
+
545
+ Returns
546
+ -------
547
+ spectrum : callable
548
+ A function that computes the PM spectrum for a given frequency.
549
+ If `unit` is 'Hz', the function expects frequency `f` in Hz:
550
+ spectrum(f)
551
+ If `unit` is 'rad/s', the function expects angular frequency `w` in rad/s:
552
+ spectrum(w)
553
+
554
+ Notes
555
+ -----
556
+ The Pierson-Moskowitz spectrum describes the distribution of energy in a fully developed sea as a function of frequency.
557
+ Docstring is generated using GitHub Copilot.
558
+
559
+ References
560
+ ----------
561
+ - Pierson, W. J., & Moskowitz, L. (1964). A proposed spectral form for fully developed wind seas based on the similarity theory of S. A. Kitaigorodskii. Journal of Geophysical Research, 69(24), 5181–5190.
562
+ """
563
+
228
564
  fp = 1/Tp
229
565
  A = 5*Hs**2*fp**4/(16)
230
566
  B = 5*fp**4/4
@@ -236,9 +572,65 @@ def pm2(Hs, Tp, unit='Hz'):
236
572
 
237
573
 
238
574
  def jonswap(Hs, Tp, gamma, g=9.81, sigma=[0.07, 0.09]):
575
+ """
576
+ Compute the JONSWAP wave spectrum as a function of angular frequency.
577
+
578
+ Parameters
579
+ ----------
580
+ Hs : float
581
+ Significant wave height (m).
582
+ Tp : float
583
+ Peak wave period (s).
584
+ gamma : float
585
+ Peak enhancement factor (dimensionless).
586
+ g : float, optional
587
+ Acceleration due to gravity (m/s^2). Default is 9.81.
588
+ sigma : list of float, optional
589
+ Spectral width parameters [sigma_a, sigma_b]. Default is [0.07, 0.09].
590
+
591
+ Returns
592
+ -------
593
+ function
594
+ A function that takes angular frequency `w` (in rad/s) and returns the JONSWAP spectrum value at `w`.
595
+
596
+ Notes
597
+ -----
598
+ This function returns a callable representing the JONSWAP spectrum, which is the product of the Pierson-Moskowitz spectrum and a peak enhancement factor.
599
+ Docstring is generated using GitHub Copilot.
600
+ """
601
+
239
602
  return lambda w: pm2(Hs, Tp, unit='rad/s')(w)*peak_enhancement(gamma, Tp, sigma, normalize=True)(w)
240
603
 
241
604
  def jonswap_numerical(Hs, Tp, gamma, omega, g=9.81, sigma=[0.07, 0.09]):
605
+ """
606
+ Compute the JONSWAP spectrum numerically for a given set of parameters.
607
+
608
+ Parameters
609
+ ----------
610
+ Hs : float
611
+ Significant wave height (m).
612
+ Tp : float
613
+ Peak wave period (s).
614
+ gamma : float
615
+ Peak enhancement factor (dimensionless).
616
+ omega : array_like
617
+ Array of angular frequencies (rad/s).
618
+ g : float, optional
619
+ Acceleration due to gravity (m/s^2). Default is 9.81.
620
+ sigma : list of float, optional
621
+ Spectral width parameters [sigma_a, sigma_b]. Default is [0.07, 0.09].
622
+
623
+ Returns
624
+ -------
625
+ S : ndarray
626
+ Spectral density values corresponding to each frequency in `omega`.
627
+
628
+ Notes
629
+ -----
630
+ If the first element of `omega` is zero, it is temporarily set to 1 to avoid division by zero,
631
+ and the corresponding spectral density is set to zero after computation.
632
+ Docstring is generated using GitHub Copilot.
633
+ """
242
634
 
243
635
  if omega[0] == 0:
244
636
  omega[0] = 1
@@ -256,6 +648,39 @@ def jonswap_numerical(Hs, Tp, gamma, omega, g=9.81, sigma=[0.07, 0.09]):
256
648
 
257
649
 
258
650
  def jonswap_dnv(Hs, Tp, gamma, sigma=[0.07, 0.09]):
651
+ """
652
+ Calculates the JONSWAP wave spectrum according to DNV recommendations.
653
+
654
+ Parameters
655
+ ----------
656
+ Hs : float
657
+ Significant wave height (m).
658
+ Tp : float
659
+ Peak wave period (s).
660
+ gamma : float
661
+ Peak enhancement factor (dimensionless).
662
+ sigma : list of float, optional
663
+ Sigma values for the spectral width parameter. Default is [0.07, 0.09].
664
+ Returns
665
+ -------
666
+ S : callable
667
+ Spectral density function S(omega), where omega is the angular frequency (rad/s).
668
+
669
+ Notes
670
+ -----
671
+ The returned function S(omega) computes the spectral density for a given angular frequency
672
+ according to the JONSWAP spectrum formulation, using the DNV recommended parameters.
673
+ The function `sigma_from_sigma_range` is used to determine the appropriate sigma value
674
+ based on the frequency.
675
+
676
+ Docstring is generated using GitHub Copilot.
677
+
678
+ References
679
+ ----------
680
+ - Det Norske Veritas (DNV). (2010). "Environmental Conditions and Environmental Loads", DNV-RP-C205.
681
+ - Hasselmann, K. et al. (1973). "Measurements of wind-wave growth and swell decay during the Joint North Sea Wave Project (JONSWAP)". Ergänzungsheft zur Deutschen Hydrographischen Zeitschrift, Reihe A(8), Nr. 12.
682
+ """
683
+
259
684
  A = 1-0.287*np.log(gamma)
260
685
  wp = 2*np.pi/Tp
261
686
 
@@ -266,6 +691,34 @@ def jonswap_dnv(Hs, Tp, gamma, sigma=[0.07, 0.09]):
266
691
 
267
692
 
268
693
  def dirdist_decimal_inv(s, theta0=0, theta=None):
694
+ """
695
+ Calculates the directional distribution function using a cosine power model.
696
+
697
+ Parameters
698
+ ----------
699
+ s : float
700
+ Spreading exponent. Must be less than or equal to 170.
701
+ theta0 : float, optional
702
+ Mean wave direction in radians. Default is 0.
703
+ theta : float or array_like, optional
704
+ Direction(s) in radians at which to evaluate the distribution. If None, returns the distribution function.
705
+
706
+ Returns
707
+ -------
708
+ D : callable or float or ndarray
709
+ If `theta` is None, returns a function D(theta) representing the directional distribution.
710
+ If `theta` is provided, returns the evaluated directional distribution at the given angle(s).
711
+
712
+ Raises
713
+ ------
714
+ ValueError
715
+ If `s` is greater than 170.
716
+
717
+ Notes
718
+ -----
719
+ The function uses the cosine power model for directional spreading, normalized such that the integral over all directions is 1.
720
+ """
721
+
269
722
  if s>170:
270
723
  raise ValueError("Spreading exponent s cannot exceed 170. Please adjust!")
271
724
  C = gamma(s+1)/(2*np.sqrt(np.pi)*gamma(s+0.5))
@@ -277,6 +730,51 @@ def dirdist_decimal_inv(s, theta0=0, theta=None):
277
730
  return D
278
731
 
279
732
  def dirdist_decimal(s, theta0=0, theta=None):
733
+ """
734
+ Calculates the directional distribution function in decimal degrees.
735
+ This function computes the directional spreading function D(theta) for a given spreading exponent `s`
736
+ and mean direction `theta0`. The function can return either the callable distribution function or its
737
+ evaluated value at a specific angle `theta`.
738
+
739
+ Parameters
740
+ ----------
741
+ s : float
742
+ Spreading exponent. Must be less than or equal to 170.
743
+ theta0 : float, optional
744
+ Mean direction in radians. Default is 0.
745
+ theta : float or array-like, optional
746
+ Angle(s) in radians at which to evaluate the distribution function. If None, the function
747
+ returns a callable that can be evaluated at any angle.
748
+
749
+ Returns
750
+ -------
751
+ D : callable or float or ndarray
752
+ If `theta` is None, returns a callable D(theta) representing the distribution function.
753
+ If `theta` is provided, returns the evaluated value(s) of the distribution function at `theta`.
754
+
755
+ Raises
756
+ ------
757
+ ValueError
758
+ If `s` is greater than 170.
759
+
760
+ Notes
761
+ -----
762
+ The distribution is defined as:
763
+ D(theta) = C * |cos((theta - theta0) / 2)|^(2s)
764
+ where
765
+ C = gamma(s+1) / (2 * sqrt(pi) * gamma(s+0.5))
766
+ and gamma is the Gamma function.
767
+ Docstring is generated using GitHub Copilot.
768
+
769
+ Examples
770
+ --------
771
+ >>> D = dirdist_decimal(10)
772
+ >>> D(np.pi/4)
773
+ 0.1234
774
+ >>> dirdist_decimal(10, theta0=0, theta=np.pi/4)
775
+ 0.1234
776
+ """
777
+
280
778
  if s>170:
281
779
  raise ValueError("Spreading exponent s cannot exceed 170. Please adjust!")
282
780
 
@@ -289,6 +787,42 @@ def dirdist_decimal(s, theta0=0, theta=None):
289
787
  return D
290
788
 
291
789
  def dirdist(s, theta0=0, theta=None):
790
+ """
791
+ Computes the directional spreading function D(θ) for ocean wave energy distribution.
792
+
793
+ Parameters
794
+ ----------
795
+ s : float
796
+ Spreading exponent. Must be less than or equal to 170.
797
+ theta0 : float, optional
798
+ Mean wave direction in radians. Default is 0.
799
+ theta : float or array_like, optional
800
+ Direction(s) in radians at which to evaluate the spreading function. If None (default),
801
+ returns the function D(θ) as a callable.
802
+
803
+ Returns
804
+ -------
805
+ D : callable or float or ndarray
806
+ If `theta` is None, returns a function D(θ) that computes the spreading function for given θ.
807
+ If `theta` is provided, returns the value(s) of the spreading function at the specified θ.
808
+
809
+ Raises
810
+ ------
811
+ ValueError
812
+ If `s` is greater than 170.
813
+
814
+ Notes
815
+ -----
816
+ The spreading function is defined as:
817
+ D(θ) = C * [cos((θ - θ₀)/2)]^(2s)
818
+ where
819
+ C = gamma(s+1) / (2 * sqrt(pi) * gamma(s+0.5))
820
+ and gamma is the gamma function.
821
+
822
+ Docstring is generated using GitHub Copilot.
823
+
824
+ """
825
+
292
826
  if s>170:
293
827
  raise ValueError("Spreading exponent s cannot exceed 170. Please adjust!")
294
828
  C = gamma(s+1)/(2*np.sqrt(np.pi)*gamma(s+0.5))
@@ -300,6 +834,37 @@ def dirdist(s, theta0=0, theta=None):
300
834
  return D
301
835
 
302
836
  def dirdist_robust(s, theta0=0, dtheta=1e-4, theta=None):
837
+ """
838
+ Compute a robust directional distribution function.
839
+ This function generates a smooth, normalized directional distribution centered at `theta0`
840
+ with a spreading parameter `s`. The distribution is defined over the interval [-π, π] and
841
+ can be evaluated at arbitrary angles.
842
+
843
+ Parameters
844
+ ----------
845
+ s : float
846
+ Sharpness parameter of the distribution. Higher values result in a more peaked distribution.
847
+ theta0 : float, optional
848
+ Center of the distribution in radians. Default is 0.
849
+ dtheta : float, optional
850
+ Step size for discretizing the angle domain in radians. Default is 1e-4.
851
+ theta : array_like or float, optional
852
+ Angles (in radians) at which to evaluate the distribution. If None (default), returns
853
+ a callable function D(theta) for evaluating the distribution at arbitrary angles.
854
+
855
+ Returns
856
+ -------
857
+ D : callable or ndarray
858
+ If `theta` is None, returns a function D(theta) that evaluates the distribution at given angles.
859
+ If `theta` is provided, returns the evaluated distribution at the specified angles.
860
+
861
+ Notes
862
+ -----
863
+ The distribution is normalized such that its integral over [-π, π] is 1.
864
+ Docstring is generated using GitHub Copilot.
865
+
866
+ """
867
+
303
868
  theta_num = np.unique(np.hstack([np.arange(-np.pi, np.pi+dtheta, dtheta), wrap_to_pi(theta0)]))
304
869
  val = np.cos((theta_num-theta0)/2)**(2*s)
305
870
  scaling = 1/np.trapz(val, theta_num)
@@ -312,110 +877,41 @@ def dirdist_robust(s, theta0=0, dtheta=1e-4, theta=None):
312
877
 
313
878
  return D
314
879
 
315
-
316
-
317
- def waveaction_fft(pontoons, omega, n_fourier=20, max_coherence_length=np.inf, print_progress=True):
318
- n_pontoons = len(pontoons)
319
- n_dofs = n_pontoons*6
320
-
321
- n_theta = n_fourier*2
322
-
323
- theta = np.linspace(-np.pi, np.pi-2*np.pi/n_theta, n_theta)
324
- S = np.zeros([n_dofs, n_dofs, len(omega)]).astype('complex')
325
880
 
326
- for i, pontoon_i in enumerate(pontoons):
327
- fi,__,__ = pontoon_i.evaluate_Q(omega, n_fourier*2)
328
- xi,yi = pontoon_i.node.coordinates[:2]
329
-
330
- for j, pontoon_j in enumerate(pontoons):
331
- xj,yj = pontoon_j.node.coordinates[:2]
332
- dx = xj-xi
333
- dy = yj-yi
334
-
335
- l = np.sqrt(dx**2+dy**2)
336
-
337
- if l<max_coherence_length:
338
- beta = atan2(dy,dx)
339
- fj,__,__ = pontoon_j.evaluate_Q(omega, n_fourier*2)
340
-
341
- depth = (pontoon_i.depth+pontoon_j.depth)/2
342
- kappa = np.array([dispersion_relation(omega_k, h=depth) for omega_k in omega])
343
-
344
- coh_2d = np.sqrt((pontoon_i.S(omega) * pontoon_j.S(omega))[:, np.newaxis] @ (pontoon_i.D(theta-pontoon_i.theta0) * pontoon_j.D(theta-pontoon_j.theta0))[np.newaxis, :])
345
-
346
- for dof_i in range(6):
347
- for dof_j in range(6):
348
- integrand = fi[dof_i,:] * fj[dof_j,:].conj() * coh_2d.T
349
- c = np.fft.fft(integrand)
350
- I = np.stack([np.exp(1j*n*beta)*1j**n*2*np.pi*jv(n, kappa*l) for n in range(-n_fourier, n_fourier)], axis=1)
351
-
352
- S[i*6+dof_i, j*6+dof_j, :] = np.sum(I*c)
353
-
354
- if print_progress:
355
- pp(i*n_pontoons+j, n_pontoons**2)
356
-
357
- return S
881
+ def dispersion_relation_scalar(w, h=np.inf, g=9.81, U=0.0):
882
+ """
883
+ Compute the wave number `k` from the dispersion relation for surface gravity waves.
358
884
 
885
+ Parameters
886
+ ----------
887
+ w : float
888
+ Angular frequency of the wave [rad/s].
889
+ h : float, optional
890
+ Water depth [m]. Default is np.inf (deep water).
891
+ g : float, optional
892
+ Gravitational acceleration [m/s^2]. Default is 9.81.
893
+ U : float, optional
894
+ Uniform current velocity [m/s]. Default is 0.0.
359
895
 
360
- def waveaction(pontoon_group, omega, max_rel_error=1e-3, print_progress=True):
361
- n_pontoons = len(pontoon_group.pontoons)
362
- n_freqs = len(omega)
363
- n_dofs = n_pontoons*6
364
-
365
- if omega[0]==0:
366
- omega = omega[1::]
367
- first_is_zero = True
368
- n_freqs = n_freqs-1
369
- else:
370
- first_is_zero = False
896
+ Returns
897
+ -------
898
+ k : float
899
+ Wave number [1/m] corresponding to the given parameters.
371
900
 
372
- kappa = [None]*n_pontoons
373
- for pontoon_ix, pontoon in enumerate(pontoon_group.pontoons):
374
- kappa[pontoon_ix] = dispersion_relation(omega, pontoon.depth)
901
+ Notes
902
+ -----
903
+ This function solves the dispersion relation for a scalar angular frequency `w`,
904
+ water depth `h`, gravitational acceleration `g`, and uniform current `U`.
905
+ It supports both deep-water (h = np.inf) and finite-depth cases.
375
906
 
376
- Sqq = np.zeros([n_dofs, n_dofs, n_freqs]).astype('complex')
377
-
378
- for k, omega_k in enumerate(omega):
379
- if print_progress:
380
- pp(k+1, n_freqs)
907
+ The function uses `scipy.optimize.fsolve` to numerically solve the dispersion relation:
908
+ - For deep water: g*k = (w - k*U)^2
909
+ - For finite depth: g*k*tanh(k*h) = (w - k*U)^2
381
910
 
382
- theta_int = pontoon_group.get_theta_int(omega_k)
383
- dtheta = theta_int[2]-theta_int[1]
384
-
385
- n_theta = len(theta_int)
386
- Z = np.zeros([n_dofs, n_theta]).astype('complex')
387
-
388
- for pontoon_index, pontoon in enumerate(pontoon_group.pontoons):
389
- if pontoon.D.__code__.co_argcount==2: # count number of inputs
390
- D = pontoon.D(theta_int, omega_k)
391
- else:
392
- D = pontoon.D(theta_int)
393
-
394
- # Shift current theta axis
395
- theta_pontoon = wrap_to_pi(pontoon.pontoon_type.theta + pontoon.rotation - pontoon.theta0)
396
- theta_pontoon, sort_ix = uniquetol(theta_pontoon, 1e-10)
397
-
398
- # Interpolate hydrodynamic transfer function
399
- Q_int = interp1d(theta_pontoon, pontoon.get_local_Q(omega_k)[:, sort_ix], fill_value=0, kind='quadratic', bounds_error=False)(theta_int)
400
-
401
- coh = np.exp(1j*kappa[pontoon_index][k] * (pontoon.node.x*np.cos(theta_int + pontoon.theta0) + pontoon.node.y*np.sin(theta_int + pontoon.theta0)))
402
- Z[pontoon_index*6:pontoon_index*6+6, :] = np.sqrt(pontoon.S(omega_k)) * Q_int * np.tile(np.sqrt(D), [6, 1]) * np.tile(coh, [6, 1])
403
-
404
- # first and last point in trapezoidal integration has 1/2 as factor, others have 1
405
- Z[:, 0] = np.sqrt(0.5)*Z[:, 0]
406
- Z[:, -1] = np.sqrt(0.5)*Z[:, -1]
407
-
408
- Sqq[:, :, k] = dtheta * pontoon_group.get_tmat().T @ (Z @ Z.conj().T) @ pontoon_group.get_tmat() # verified to match for loop over angles and trapz integration.
409
-
410
-
411
- if first_is_zero==True:
412
- Sqq = np.insert(Sqq, 0, Sqq[:,:,0]*0, axis=2)
413
-
414
-
415
- return Sqq
911
+ Docstring is generated using GitHub Copilot.
912
+
913
+ """
416
914
 
417
-
418
- def dispersion_relation_scalar(w, h=np.inf, g=9.81, U=0.0):
419
915
  if h==np.inf:
420
916
  f = lambda k: g*k - (w-k*U)**2
421
917
  else:
@@ -427,26 +923,43 @@ def dispersion_relation_scalar(w, h=np.inf, g=9.81, U=0.0):
427
923
 
428
924
  return k
429
925
 
430
- def dispersion_relation_scalar_legacy(w, h=np.inf, g=9.81):
431
- if h != np.inf:
432
- a = h*w**2/g
433
-
434
- # Initial guesses are provided by small value and large value approximations of x
435
- x = a*0
436
- x[a<=3/4] = np.sqrt((3-np.sqrt(9-12*a[a<=3/4]))/2)
437
- x[a>3/4] = a[a>3/4]
438
-
439
- for i in range(0,100):
440
- x = (a+(x**2)*(1-(np.tanh(x))**2))/(np.tanh(x)+x*(1-(np.tanh(x))**2))
441
- # The convergence criterion is chosen such that the wave numbers produce frequencies that don't deviate more than 1e-6*sqrt(g/h) from w.
442
- if np.max(abs(np.sqrt(x*np.tanh(x))-np.sqrt(a))) < 1e-6:
443
- break
444
-
445
- k = x/h
446
- else:
447
- return w**2/g
448
926
 
449
927
  def dispersion_relation(w, h=np.inf, g=9.81):
928
+ """
929
+ Compute the wave number `k` from the angular frequency `w` using the linear wave dispersion relation.
930
+
931
+ Parameters
932
+ ----------
933
+ w : array_like
934
+ Angular frequency (rad/s). Can be a scalar or a NumPy array.
935
+ h : float, optional
936
+ Water depth (meters). Default is `np.inf` (deep water approximation).
937
+ g : float, optional
938
+ Gravitational acceleration (m/s^2). Default is 9.81.
939
+
940
+ Returns
941
+ -------
942
+ k : ndarray
943
+ Wave number(s) corresponding to the input frequency/frequencies.
944
+
945
+ Notes
946
+ -----
947
+ - For deep water (`h = np.inf`), the dispersion relation simplifies to `k = w**2 / g`.
948
+ - For finite depth, the function solves the implicit dispersion relation numerically:
949
+ `w**2 = g * k * tanh(k * h)`.
950
+ - The function handles zero frequencies by returning zero wave numbers at those positions.
951
+ - The iterative solver uses initial guesses based on small and large value approximations for stability and convergence.
952
+
953
+ Docstring is generated using GitHub Copilot.
954
+
955
+ Examples
956
+ --------
957
+ >>> import numpy as np
958
+ >>> w = np.array([0.0, 1.0, 2.0])
959
+ >>> dispersion_relation(w, h=10)
960
+ array([0. , 0.102..., 0.408...])
961
+ """
962
+
450
963
  zero_ix = np.where(w==0)
451
964
  w = w[w!=0]
452
965
 
@@ -474,6 +987,33 @@ def dispersion_relation(w, h=np.inf, g=9.81):
474
987
 
475
988
 
476
989
  def maxincrement(dl, kmax, a, b, max_relative_error):
990
+ """
991
+ Calculate the maximum increment for numerical integration based on error tolerance.
992
+
993
+ Parameters
994
+ ----------
995
+ dl : float
996
+ The step size or differential length.
997
+ kmax : float
998
+ The maximum wavenumber.
999
+ a : float
1000
+ The lower bound of the integration interval.
1001
+ b : float
1002
+ The upper bound of the integration interval.
1003
+ max_relative_error : float
1004
+ The maximum allowed relative error.
1005
+
1006
+ Returns
1007
+ -------
1008
+ increment : float
1009
+ The calculated maximum increment that satisfies the error tolerance.
1010
+
1011
+ Notes
1012
+ -----
1013
+ If `dl` is zero, the increment is set to the width of the interval (`b - a`).
1014
+ Docstring is generated using GitHub Copilot.
1015
+ """
1016
+
477
1017
  g = 9.81
478
1018
  thetamax = np.pi/2
479
1019
  K = abs(1j*kmax*(-(1/2)*np.pi)*dl*(-(1/2)*np.pi)*(np.cos(thetamax))*(-(1/2)*np.pi)*(np.exp(-1j*kmax*dl*np.cos(thetamax)))*(-(1/2)*np.pi)-kmax*(-(1/2)*np.pi)**2*dl*(-(1/2)*np.pi)**2*(np.sin(thetamax))*(-(1/2)*np.pi)**2*(np.exp(-1j*kmax*dl*np.cos(thetamax)))*(-(1/2)*np.pi))
@@ -488,3 +1028,4 @@ def maxincrement(dl, kmax, a, b, max_relative_error):
488
1028
  increment=b-a
489
1029
 
490
1030
  return increment
1031
+