wawi 0.0.1__py3-none-any.whl → 0.0.3__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
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