wawi 0.0.1__py3-none-any.whl → 0.0.5__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 ADDED
@@ -0,0 +1,491 @@
1
+ import numpy as np
2
+ from math import atan2
3
+ from scipy.interpolate import interp1d
4
+ from scipy.special import jv
5
+ from .general import wrap_to_pi, uniquetol, zero_pad_upsample, get_omega_upsampled
6
+ from .tools import print_progress as pp
7
+ from inspect import isfunction
8
+ from scipy.special import jv, gamma
9
+ from scipy.optimize import fsolve
10
+ from wawi.general import eval_fun_or_scalar
11
+
12
+ def linear_drag_damping(drag_coefficient, std_udot, area=1.0, rho=1020.0, as_matrix=True):
13
+ damping = 0.5*rho*area*drag_coefficient*np.sqrt(8/np.pi)*std_udot
14
+
15
+ if as_matrix == True and (len(damping)==3 or len(damping)==6):
16
+ damping = np.diag(damping)
17
+
18
+ return damping
19
+
20
+ def stochastic_linearize(C_quad, std_udot):
21
+ # Input C_quad is assumed matrix form, std_udot is assumed matrix
22
+
23
+ if np.ndim(std_udot)==1:
24
+ std_udot = np.diag(std_udot)
25
+
26
+ return C_quad*np.sqrt(8/np.pi)*std_udot
27
+
28
+ def harmonic_linearize(C_quad, udot):
29
+ if np.ndim(udot)==2:
30
+ udot = np.diag(np.diag(udot))
31
+ else:
32
+ udot = np.diag(udot)
33
+
34
+ C_quad = np.diag(np.diag(C_quad))
35
+ return 8/(3*np.pi)*C_quad*np.abs(udot)
36
+
37
+
38
+ 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
+ '''
44
+
45
+ L = np.sqrt(dx**2+dy**2)
46
+ phi = np.arctan2(dy, dx)
47
+ beta = theta0 - phi
48
+
49
+ if input_is_kappa:
50
+ kappa = omega*1
51
+ else:
52
+ kappa = dispersion_relation(omega, h=depth)[:, np.newaxis]
53
+
54
+ # Establish from Fourier coefficients
55
+ k = np.arange(-k_max, k_max+1)[np.newaxis, :]
56
+ theta = np.linspace(-np.pi, np.pi, k_max*2+1) #ensures odd number of fft coeff.
57
+
58
+ c = np.fft.ifft(D(theta + theta0-theta_shift))
59
+ c = np.hstack([c[-k_max:], c[:k_max+1]])[np.newaxis, :]
60
+
61
+ coh = 2*np.pi*np.sum(
62
+ np.tile(c*1j**k*np.exp(-1j*k*beta), [len(kappa), 1])
63
+ * jv(k, kappa*L), axis=1)
64
+
65
+ return coh
66
+
67
+ def get_coh_cos2s(omega, dx, dy, s, theta0, k_max=10, depth=np.inf,
68
+ input_is_kappa=False):
69
+ if input_is_kappa:
70
+ kappa = omega*1
71
+ else:
72
+ kappa = dispersion_relation(omega, h=depth)[:, np.newaxis]
73
+
74
+ L = np.sqrt(dx**2 + dy**2)
75
+ phi = np.arctan2(dy, dx)
76
+ beta = theta0 - phi
77
+
78
+ k = np.arange(-k_max, k_max+1)[np.newaxis, :]
79
+ c = 1/(2*np.pi) * gamma(s+1)**2/(gamma(s-k+1)*gamma(s+k+1))
80
+ coh = 2*np.pi * np.sum(np.tile(c*1j**k*np.exp(-1j*k*beta),
81
+ [len(kappa), 1]) * jv(k, kappa*L), axis=1)
82
+
83
+ return coh
84
+
85
+ def get_coh(omega, dx, dy, D1, D2=None, depth=np.inf, n_theta=40,
86
+ theta_shift=0.0, input_is_kappa=False, twodimensional=False,
87
+ include_D=True):
88
+
89
+ if D2 is None: #assumes the same as D1
90
+ D2 = D1
91
+
92
+ if input_is_kappa:
93
+ kappa = omega*1
94
+ else:
95
+ kappa = dispersion_relation(omega, h=depth)
96
+
97
+ theta = np.linspace(-np.pi, np.pi, n_theta)
98
+
99
+ if include_D:
100
+ D_eval = np.sqrt(D1(theta)*D2(theta))
101
+ else:
102
+ D_eval = 1.0
103
+
104
+ coh2d = D_eval*np.exp(-1j*kappa[:, np.newaxis] @ ((np.cos(theta+theta_shift)*dx + np.sin(theta+theta_shift)*dy))[np.newaxis, :])
105
+
106
+ if twodimensional:
107
+ return coh2d, theta
108
+ else:
109
+ coh = np.trapz(coh2d, x=theta, axis=1)
110
+
111
+ return coh
112
+
113
+
114
+ def xsim(x, y, S, D, omega, fs=None, theta=None, n_theta=40, grid_mode=True, print_progress=True,
115
+ time_history=False, phase=None, return_phases=False, theta_shift=0.0):
116
+
117
+ if fs is None:
118
+ fs = np.max(omega)/2/np.pi
119
+
120
+ if theta is None:
121
+ theta = np.linspace(-np.pi, np.pi, n_theta)
122
+
123
+
124
+ if not isfunction(S):
125
+ Sfun = lambda x, y: S
126
+ else:
127
+ Sfun = S
128
+
129
+ if not isfunction(D):
130
+ Dfun = lambda x, y: D
131
+ else:
132
+ Dfun = D
133
+
134
+ if grid_mode:
135
+ xx,yy = np.meshgrid(x,y)
136
+ xvec = x*1
137
+ yvec = y*1
138
+ x = xx.flatten()
139
+ y = yy.flatten()
140
+
141
+ domega = omega[1] - omega[0]
142
+
143
+ if len(theta)>1:
144
+ dtheta = theta[1] - theta[0]
145
+ else:
146
+ dtheta = 1.0
147
+
148
+ omegai = get_omega_upsampled(omega, fs*2*np.pi)
149
+ kappa = omega**2 / 9.81 #assume deep-water waves - can be generalized later (different depths at different positions possible also)
150
+
151
+ # Create kappa grid
152
+ # Attempt to fix function theta_shift (non centered dirdist definitions with theta0 as function)
153
+ # kappax = lambda x,y: kappa[:, np.newaxis] @ np.cos(theta+eval_fun_or_scalar(theta_shift,x,y))[np.newaxis, :]
154
+ # kappay = lambda x,y: kappa[:, np.newaxis] @ np.sin(theta+eval_fun_or_scalar(theta_shift,x,y))[np.newaxis, :]
155
+
156
+ kappax = kappa[:, np.newaxis] @ np.cos(theta+theta_shift)[np.newaxis, :]
157
+ kappay = kappa[:, np.newaxis] @ np.sin(theta+theta_shift)[np.newaxis, :]
158
+
159
+ n_freqs = len(omega)
160
+ n_freqs_i = len(omegai)
161
+ n_angles = len(theta)
162
+
163
+ if phase is None:
164
+ phase = np.exp(1j*np.random.rand(n_freqs, n_angles)*2*np.pi)
165
+
166
+ if time_history:
167
+ eta = np.zeros([n_freqs_i, len(x)])
168
+ selection = np.arange(n_freqs_i)
169
+ n_t = n_freqs_i*1
170
+ else:
171
+ eta = np.zeros([1, len(x)])
172
+ selection = np.array(0)
173
+ n_t = 1
174
+
175
+ for ix in range(len(x)):
176
+ Sthis = Sfun(x[ix], y[ix])(omega)[:, np.newaxis]
177
+ Dthis = Dfun(x[ix], y[ix])(theta)[np.newaxis, :]
178
+
179
+ B0 = np.sqrt(2 * Sthis * Dthis * domega * dtheta)
180
+ Bkr = B0*np.exp(-1j*(kappax*x[ix] + kappay*y[ix])) * phase
181
+ if Bkr.shape[1]>1:
182
+ Bkr_sum = np.trapz(Bkr, axis=1)
183
+ else:
184
+ Bkr_sum = Bkr[:,0]
185
+
186
+ Bkr_sum = zero_pad_upsample(Bkr_sum, omega, fs*2*np.pi)
187
+
188
+ eta[:, ix] = np.fft.fftshift(len(omegai) * np.real(np.fft.ifft(Bkr_sum)))[selection]
189
+
190
+ if print_progress:
191
+ pp(ix+1, len(x), postfix=f' | x={x[ix]:.1f}m, y={y[ix]:.1f}m ')
192
+
193
+ t = np.linspace(0, 2*np.pi/domega, n_freqs_i)[selection].T
194
+
195
+ if grid_mode:
196
+ if time_history:
197
+ eta = np.swapaxes(eta, 0, 1) # after swap: gridcombos x time
198
+ eta = np.reshape(eta, [len(yvec), len(xvec), -1])
199
+ else:
200
+ eta = np.reshape(eta, [len(yvec), len(xvec)])
201
+
202
+ # Return
203
+ if return_phases:
204
+ return eta, t, phase
205
+ else:
206
+ return eta, t
207
+
208
+
209
+ def swh_from_gamma_alpha_Tp(gamma, alpha, Tp, g=9.81):
210
+ wp = 2*np.pi/Tp
211
+
212
+ Hs = (1.555 + 0.2596*gamma - 0.02231*gamma**2 + 0.01142*gamma**3)*g*np.sqrt(alpha)/wp**2
213
+ return Hs
214
+
215
+ def sigma_from_sigma_range(sigma, wp):
216
+ return lambda w: (sigma[0]+(sigma[1]-sigma[0])*(w>wp))
217
+
218
+ def peak_enhancement(gamma, Tp, sigma, normalize=True):
219
+ wp = 2*np.pi/Tp
220
+ sigma = sigma_from_sigma_range(sigma, wp)
221
+ if normalize:
222
+ A_gamma = (1 - 0.287*np.log(gamma))
223
+ return lambda w: gamma**np.exp(-(w-wp)**2/(2*sigma(w)**2*wp**2)) * A_gamma
224
+ else:
225
+ return lambda w: gamma**np.exp(-(w-wp)**2/(2*sigma(w)**2*wp**2))
226
+
227
+
228
+ def pm2(Hs, Tp, unit='Hz'):
229
+ fp = 1/Tp
230
+ A = 5*Hs**2*fp**4/(16)
231
+ B = 5*fp**4/4
232
+
233
+ if unit == 'Hz':
234
+ return lambda f: A/f**5*np.exp(-B/f**4)
235
+ elif unit == 'rad/s':
236
+ return lambda w: A/(w/2/np.pi)**5*np.exp(-B/(w/2/np.pi)**4)/2/np.pi
237
+
238
+
239
+ def jonswap(Hs, Tp, gamma, g=9.81, sigma=[0.07, 0.09]):
240
+ return lambda w: pm2(Hs, Tp, unit='rad/s')(w)*peak_enhancement(gamma, Tp, sigma, normalize=True)(w)
241
+
242
+ def jonswap_numerical(Hs, Tp, gamma, omega, g=9.81, sigma=[0.07, 0.09]):
243
+
244
+ if omega[0] == 0:
245
+ omega[0] = 1
246
+ first_is_zero = True
247
+ else:
248
+ first_is_zero = False
249
+
250
+ S = jonswap(Hs, Tp, gamma, g=g, sigma=sigma)(omega)
251
+
252
+ if first_is_zero:
253
+ S[0] = 0.0
254
+ omega[0] = 0.0
255
+
256
+ return S
257
+
258
+
259
+ def jonswap_dnv(Hs, Tp, gamma, sigma=[0.07, 0.09]):
260
+ A = 1-0.287*np.log(gamma)
261
+ wp = 2*np.pi/Tp
262
+
263
+ sigma = sigma_from_sigma_range(sigma, wp)
264
+ S = lambda omega: A*5.0/16.0*Hs**2*wp**4/(omega**5)*np.exp(-5/4*(omega/wp)**(-4))*gamma**(np.exp(-0.5*((omega-wp)/sigma(omega)/wp)**2))
265
+
266
+ return S
267
+
268
+
269
+ def dirdist_decimal_inv(s, theta0=0, theta=None):
270
+ if s>170:
271
+ raise ValueError("Spreading exponent s cannot exceed 170. Please adjust!")
272
+ C = gamma(s+1)/(2*np.sqrt(np.pi)*gamma(s+0.5))
273
+ D = lambda theta: C*(np.abs(np.cos((theta+theta0)/2)))**(2*s)
274
+
275
+ if theta!=None:
276
+ D = D(theta)
277
+
278
+ return D
279
+
280
+ def dirdist_decimal(s, theta0=0, theta=None):
281
+ if s>170:
282
+ raise ValueError("Spreading exponent s cannot exceed 170. Please adjust!")
283
+
284
+ C = gamma(s+1)/(2*np.sqrt(np.pi)*gamma(s+0.5))
285
+ D = lambda theta: C*(np.abs(np.cos((theta-theta0)/2)))**(2*s)
286
+
287
+ if theta!=None:
288
+ D = D(theta)
289
+
290
+ return D
291
+
292
+ def dirdist(s, theta0=0, theta=None):
293
+ if s>170:
294
+ raise ValueError("Spreading exponent s cannot exceed 170. Please adjust!")
295
+ C = gamma(s+1)/(2*np.sqrt(np.pi)*gamma(s+0.5))
296
+ D = lambda theta: C*(np.cos((theta-theta0)/2))**(2*s)
297
+
298
+ if theta!=None:
299
+ D = D(theta)
300
+
301
+ return D
302
+
303
+ def dirdist_robust(s, theta0=0, dtheta=1e-4, theta=None):
304
+ theta_num = np.unique(np.hstack([np.arange(-np.pi, np.pi+dtheta, dtheta), wrap_to_pi(theta0)]))
305
+ val = np.cos((theta_num-theta0)/2)**(2*s)
306
+ scaling = 1/np.trapz(val, theta_num)
307
+
308
+ def D(theta):
309
+ return interp1d(theta_num, val*scaling)(wrap_to_pi(theta))
310
+
311
+ if theta!=None:
312
+ D = D(theta)
313
+
314
+ return D
315
+
316
+
317
+
318
+ def waveaction_fft(pontoons, omega, n_fourier=20, max_coherence_length=np.inf, print_progress=True):
319
+ n_pontoons = len(pontoons)
320
+ n_dofs = n_pontoons*6
321
+
322
+ n_theta = n_fourier*2
323
+
324
+ theta = np.linspace(-np.pi, np.pi-2*np.pi/n_theta, n_theta)
325
+ S = np.zeros([n_dofs, n_dofs, len(omega)]).astype('complex')
326
+
327
+ for i, pontoon_i in enumerate(pontoons):
328
+ fi,__,__ = pontoon_i.evaluate_Q(omega, n_fourier*2)
329
+ xi,yi = pontoon_i.node.coordinates[:2]
330
+
331
+ for j, pontoon_j in enumerate(pontoons):
332
+ xj,yj = pontoon_j.node.coordinates[:2]
333
+ dx = xj-xi
334
+ dy = yj-yi
335
+
336
+ l = np.sqrt(dx**2+dy**2)
337
+
338
+ if l<max_coherence_length:
339
+ beta = atan2(dy,dx)
340
+ fj,__,__ = pontoon_j.evaluate_Q(omega, n_fourier*2)
341
+
342
+ depth = (pontoon_i.depth+pontoon_j.depth)/2
343
+ kappa = np.array([dispersion_relation(omega_k, h=depth) for omega_k in omega])
344
+
345
+ 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, :])
346
+
347
+ for dof_i in range(6):
348
+ for dof_j in range(6):
349
+ integrand = fi[dof_i,:] * fj[dof_j,:].conj() * coh_2d.T
350
+ c = np.fft.fft(integrand)
351
+ 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)
352
+
353
+ S[i*6+dof_i, j*6+dof_j, :] = np.sum(I*c)
354
+
355
+ if print_progress:
356
+ pp(i*n_pontoons+j, n_pontoons**2)
357
+
358
+ return S
359
+
360
+
361
+ def waveaction(pontoon_group, omega, max_rel_error=1e-3, print_progress=True):
362
+ n_pontoons = len(pontoon_group.pontoons)
363
+ n_freqs = len(omega)
364
+ n_dofs = n_pontoons*6
365
+
366
+ if omega[0]==0:
367
+ omega = omega[1::]
368
+ first_is_zero = True
369
+ n_freqs = n_freqs-1
370
+ else:
371
+ first_is_zero = False
372
+
373
+ kappa = [None]*n_pontoons
374
+ for pontoon_ix, pontoon in enumerate(pontoon_group.pontoons):
375
+ kappa[pontoon_ix] = dispersion_relation(omega, pontoon.depth)
376
+
377
+ Sqq = np.zeros([n_dofs, n_dofs, n_freqs]).astype('complex')
378
+
379
+ for k, omega_k in enumerate(omega):
380
+ if print_progress:
381
+ pp(k+1, n_freqs)
382
+
383
+ theta_int = pontoon_group.get_theta_int(omega_k)
384
+ dtheta = theta_int[2]-theta_int[1]
385
+
386
+ n_theta = len(theta_int)
387
+ Z = np.zeros([n_dofs, n_theta]).astype('complex')
388
+
389
+ for pontoon_index, pontoon in enumerate(pontoon_group.pontoons):
390
+ if pontoon.D.__code__.co_argcount==2: # count number of inputs
391
+ D = pontoon.D(theta_int, omega_k)
392
+ else:
393
+ D = pontoon.D(theta_int)
394
+
395
+ # Shift current theta axis
396
+ theta_pontoon = wrap_to_pi(pontoon.pontoon_type.theta + pontoon.rotation - pontoon.theta0)
397
+ theta_pontoon, sort_ix = uniquetol(theta_pontoon, 1e-10)
398
+
399
+ # Interpolate hydrodynamic transfer function
400
+ Q_int = interp1d(theta_pontoon, pontoon.get_local_Q(omega_k)[:, sort_ix], fill_value=0, kind='quadratic', bounds_error=False)(theta_int)
401
+
402
+ 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)))
403
+ 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])
404
+
405
+ # first and last point in trapezoidal integration has 1/2 as factor, others have 1
406
+ Z[:, 0] = np.sqrt(0.5)*Z[:, 0]
407
+ Z[:, -1] = np.sqrt(0.5)*Z[:, -1]
408
+
409
+ 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.
410
+
411
+
412
+ if first_is_zero==True:
413
+ Sqq = np.insert(Sqq, 0, Sqq[:,:,0]*0, axis=2)
414
+
415
+
416
+ return Sqq
417
+
418
+
419
+ def dispersion_relation_scalar(w, h=np.inf, g=9.81, U=0.0):
420
+ if h==np.inf:
421
+ f = lambda k: g*k - (w-k*U)**2
422
+ else:
423
+ f = lambda k: g*k*np.tanh(k*h) - (w-k*U)**2
424
+
425
+ k0 = w**2/g # deep-water, zero-current wave number
426
+
427
+ k = fsolve(f, x0=k0)[0]
428
+
429
+ return k
430
+
431
+ def dispersion_relation_scalar_legacy(w, h=np.inf, g=9.81):
432
+ if h != np.inf:
433
+ a = h*w**2/g
434
+
435
+ # Initial guesses are provided by small value and large value approximations of x
436
+ x = a*0
437
+ x[a<=3/4] = np.sqrt((3-np.sqrt(9-12*a[a<=3/4]))/2)
438
+ x[a>3/4] = a[a>3/4]
439
+
440
+ for i in range(0,100):
441
+ x = (a+(x**2)*(1-(np.tanh(x))**2))/(np.tanh(x)+x*(1-(np.tanh(x))**2))
442
+ # 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.
443
+ if np.max(abs(np.sqrt(x*np.tanh(x))-np.sqrt(a))) < 1e-6:
444
+ break
445
+
446
+ k = x/h
447
+ else:
448
+ return w**2/g
449
+
450
+ def dispersion_relation(w, h=np.inf, g=9.81):
451
+ zero_ix = np.where(w==0)
452
+ w = w[w!=0]
453
+
454
+ if h != np.Inf:
455
+ a = h*w**2/g
456
+
457
+ # Initial guesses are provided by small value and large value approximations of x
458
+ x = a*0
459
+ x[a<=3/4] = np.sqrt((3-np.sqrt(9-12*a[a<=3/4]))/2)
460
+ x[a>3/4] = a[a>3/4]
461
+
462
+ for i in range(0,100):
463
+ x = (a+(x**2)*(1-(np.tanh(x))**2))/(np.tanh(x)+x*(1-(np.tanh(x))**2))
464
+ # 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.
465
+ if np.max(abs(np.sqrt(x*np.tanh(x))-np.sqrt(a))) < 1e-6:
466
+ break
467
+
468
+ k = x/h
469
+ else:
470
+ k = w**2/g
471
+
472
+ k = np.insert(k, zero_ix[0], 0)
473
+
474
+ return k
475
+
476
+
477
+ def maxincrement(dl, kmax, a, b, max_relative_error):
478
+ g = 9.81
479
+ thetamax = np.pi/2
480
+ 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))
481
+
482
+ max_val = abs(np.exp(-1j*dl))
483
+ max_error = max_val*max_relative_error
484
+ N = np.sqrt((K*(b-a)**3)/(12*max_error))
485
+
486
+ increment=(b-a)/N
487
+
488
+ if dl==0:
489
+ increment=b-a
490
+
491
+ return increment