wawi 0.0.7__py3-none-any.whl → 0.0.9__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.

Potentially problematic release.


This version of wawi might be problematic. Click here for more details.

wawi/model/_aero.py ADDED
@@ -0,0 +1,363 @@
1
+ import numpy as np
2
+ import json
3
+ from wawi.wind import quasisteady_ads, ADs, compute_aero_matrices, generic_kaimal_matrix
4
+ from wawi.general import fun_scale, fun_sum
5
+ from beef.rotation import rodrot
6
+
7
+ '''
8
+ AERO SUBMODULE
9
+ '''
10
+ class Aero:
11
+ def __init__(self, sections=None, phi_key='full', element_assignments=None, windstate=None):
12
+ self.sections = sections
13
+ self.elements = ensure_list_of_ints(element_assignments)
14
+ self.phi_key = phi_key
15
+ self.phi = None
16
+ self.phi_ixs = dict()
17
+ self.eldef = dict()
18
+
19
+ self.windstate = windstate
20
+
21
+ self.Kfun = None
22
+ self.Cfun = None
23
+
24
+ def get_phi(self, group):
25
+ return self.phi[self.phi_ixs[group], :]
26
+
27
+ @property
28
+ def windstate(self):
29
+ return self._windstate
30
+
31
+ @windstate.setter
32
+ def windstate(self, val):
33
+ self._windstate = val
34
+ self.Cfun = None
35
+ self.Kfun = None
36
+ self.Sqq_aero = None
37
+
38
+ @property
39
+ def K(self):
40
+ if self.Kfun is None:
41
+ return 0.0
42
+ else:
43
+ return self.Kfun
44
+
45
+ @property
46
+ def C(self):
47
+ if self.Cfun is None:
48
+ return 0.0
49
+ else:
50
+ return self.Cfun
51
+
52
+ def get_generic_kaimal(self, nodes=None, group=None):
53
+ if (nodes is None) and (group is None):
54
+ raise ValueError('Input either nodes or group!')
55
+ elif group is not None:
56
+ nodes = self.eldef[group].nodes
57
+
58
+ return lambda om: generic_kaimal_matrix(om, nodes, self.windstate.T, self.windstate.A,
59
+ self.windstate.sigma, self.windstate.C, self.windstate.Lx, self.windstate.U, spectrum_type=self.windstate.spectrum_type)
60
+
61
+
62
+
63
+ def get_aero_matrices(self, omega_reduced=None, aero_sections=None, print_progress=False):
64
+ if aero_sections is None:
65
+ aero_sections = self.elements.keys()
66
+
67
+ Cae_m = [None]*len(aero_sections)
68
+ Kae_m = [None]*len(aero_sections)
69
+
70
+ for ix, sec in enumerate(aero_sections):
71
+ phi = self.get_phi(sec)
72
+ U = self.windstate.U
73
+ AD = self.sections[sec].ADs
74
+ B = self.sections[sec].B
75
+ els = self.elements[sec]
76
+ T_wind = self.windstate.T
77
+
78
+ Kae_m[ix], Cae_m[ix] = compute_aero_matrices(U, AD, B, els, T_wind, phi,
79
+ omega_reduced=omega_reduced, print_progress=print_progress, rho=self.windstate.rho)
80
+
81
+ return fun_sum(*Kae_m), fun_sum(*Cae_m)
82
+
83
+
84
+ def prepare_aero_matrices(self, omega=None, print_progress=False, aero_sections=None):
85
+ self.Kfun, self.Cfun = self.get_aero_matrices(omega_reduced=omega,
86
+ print_progress=print_progress,
87
+ aero_sections=aero_sections)
88
+
89
+
90
+ '''
91
+ AERO SECTION CLASS
92
+ '''
93
+ def ensure_list_of_ints(d):
94
+ for key in d:
95
+ d[key] = [int(di) for di in d[key]]
96
+
97
+ return d
98
+
99
+
100
+ class AeroSection:
101
+ def __init__(self, D=None, B=None, ADs=None, Cd=0.0, dCd=0.0, Cm=0.0, dCm=0.0, Cl=0.0, dCl=0.0, admittance=None):
102
+ self.D = D
103
+ self.B = B
104
+ self.Cd = Cd
105
+ self.dCd = dCd
106
+ self.Cm = Cm
107
+ self.dCm = dCm
108
+ self.Cl = Cl
109
+ self.dCl = dCl
110
+ self.admittance = admittance
111
+
112
+ if ADs is None:
113
+ self.add_quasisteady_ads()
114
+ self.quasisteady = True
115
+ else:
116
+ self.ADs = ADs
117
+ self.quasisteady = False
118
+
119
+ def assign(self, **kwargs):
120
+ for key, val in kwargs.items():
121
+ setattr(self, key, val)
122
+
123
+ @property
124
+ def all_lc(self):
125
+ keys = ['Cd', 'Cm', 'Cl', 'dCd', 'dCm', 'dCl']
126
+ return {key: getattr(self, key) for key in keys}
127
+
128
+ def add_quasisteady_ads(self):
129
+ self.ADs = ADs(**quasisteady_ads(self.D, self.B, self.all_lc), ad_type='quasisteady')
130
+
131
+ def __str__(self):
132
+ return f"<AeroSection> (D = {self.D}, B = {self.B}, Cd = {self.Cd}, Cd' = {self.dCd}, Cl = {self.Cl}, Cl' = {self.dCl}, Cm = {self.Cm}, Cm' = {self.dCm}, Admittance = {self.admittance})"
133
+
134
+
135
+ '''
136
+ WIND STATE CLASS
137
+ '''
138
+
139
+ class Windstate:
140
+ def __init__(self, U0, direction, Au=0.0, Av=0.0, Aw=0.0,
141
+ Iu=0.0, Iv=0.0, Iw=0.0, Cuy=0.0, Cuz=0.0, Cvy=0.0, Cvz=0.0, Cwy=0.0, Cwz=0.0, Lux=0.0, Lvx=0.0, Lwx=0.0,
142
+ x_ref=np.array([0,0,0]), scaling=None, name=None, spectrum_type='kaimal', rho=1.225, options=None):
143
+
144
+ self.U0 = U0
145
+ self.direction = direction # interpreted as positive in clock-wise direction and defines origin and not heading!
146
+
147
+ self.A = np.array([Au, Av, Aw])
148
+ self.I = np.array([Iu, Iv, Iw])
149
+
150
+ self.C = np.array([[0, 0, 0],
151
+ [Cuy, Cvy, Cwy],
152
+ [Cuz, Cvz, Cwz]])
153
+
154
+ self.Lx = np.array([Lux, Lvx, Lwx])
155
+
156
+ self.spectrum_type = spectrum_type
157
+ self.options = options
158
+
159
+ if scaling is None:
160
+ self.scaling = lambda x: 1.0 #{x} = [x,y,z]
161
+ else:
162
+ self.scaling = scaling
163
+
164
+ self.x_ref = x_ref
165
+ self.name = name
166
+ self.rho = rho
167
+
168
+ def __str__(self):
169
+ string = f'''\
170
+ WAWI WindState
171
+ --------------
172
+ U={self.U0:.1f}m/s, direction={self.direction:.1f}deg
173
+ A=[{self.Au:.2f}, {self.Av:.2f}, {self.Aw:.2f}]
174
+ I=[{self.Iu:.2f}, {self.Iv:.2f}, {self.Iw:.2f}]
175
+ Cux, Cvx, Cwx = [{self.C[0,0]}, {self.C[0,1]}, {self.C[0,2]}]
176
+ Cuy, Cvy, Cwy = [{self.C[1,0]}, {self.C[1,1]}, {self.C[1,2]}]
177
+ Cuz, Cvz, Cwz = [{self.C[2,0]}, {self.C[2,1]}, {self.C[2,2]}]
178
+ Lx = [{self.Lx[0]:.2f}, {self.Lx[1]:.2f}, {self.Lx[2]:.2f}]
179
+ '''
180
+
181
+ return string
182
+
183
+
184
+ @property
185
+ def Iu(self):
186
+ return self.I[0]
187
+ @Iu.setter
188
+ def Iu(self, val):
189
+ self.I[0] = val
190
+
191
+ @property
192
+ def Iv(self):
193
+ return self.I[1]
194
+ @Iv.setter
195
+ def Iv(self, val):
196
+ self.I[1] = val
197
+
198
+ @property
199
+ def Iw(self):
200
+ return self.I[2]
201
+ @Iw.setter
202
+ def Iw(self, val):
203
+ self.I[2] = val
204
+
205
+ @property
206
+ def sigma(self):
207
+ return self.I*self.U0
208
+
209
+ @property
210
+ def T(self):
211
+ return rodrot((-self.direction+180)*np.pi/180)
212
+
213
+ @property
214
+ def U(self):
215
+ return fun_scale(self.scaling, self.U0)
216
+
217
+ @property
218
+ def V(self):
219
+ return self.U
220
+
221
+ @property
222
+ def V0(self):
223
+ return self.U0
224
+
225
+ @property
226
+ def Au(self):
227
+ return self.A[0]
228
+ @Au.setter
229
+ def Au(self, val):
230
+ self.A[0] = val
231
+
232
+ @property
233
+ def Av(self):
234
+ return self.A[1]
235
+ @Av.setter
236
+ def Av(self, val):
237
+ self.A[1] = val
238
+
239
+ @property
240
+ def Aw(self):
241
+ return self.A[2]
242
+ @Aw.setter
243
+ def Aw(self, val):
244
+ self.A[2] = val
245
+
246
+ @property
247
+ def sigma_u(self):
248
+ return self.sigma[0]
249
+
250
+ @property
251
+ def sigma_v(self):
252
+ return self.sigma[1]
253
+
254
+ @property
255
+ def sigma_w(self):
256
+ return self.sigma[2]
257
+
258
+ @property
259
+ def Cux(self):
260
+ return self.C[0,0]
261
+ @Cux.setter
262
+ def Cux(self, val):
263
+ self.C[0,0] = val
264
+
265
+ @property
266
+ def Cuy(self):
267
+ return self.C[1,0]
268
+ @Cuy.setter
269
+ def Cuy(self, val):
270
+ self.C[1,0] = val
271
+
272
+ @property
273
+ def Cuz(self):
274
+ return self.C[2,0]
275
+ @Cuz.setter
276
+ def Cuz(self, val):
277
+ self.C[2,0] = val
278
+
279
+ @property
280
+ def Cvx(self):
281
+ return self.C[0,1]
282
+ @Cvx.setter
283
+ def Cvx(self, val):
284
+ self.C[0,1] = val
285
+
286
+ @property
287
+ def Cvy(self):
288
+ return self.C[1,1]
289
+ @Cvy.setter
290
+ def Cvy(self, val):
291
+ self.C[1,1] = val
292
+
293
+ @property
294
+ def Cvz(self):
295
+ return self.C[2,1]
296
+ @Cvz.setter
297
+ def Cvz(self, val):
298
+ self.C[2,1] = val
299
+
300
+ @property
301
+ def Cwx(self):
302
+ return self.C[0,2]
303
+ @Cwx.setter
304
+ def Cwx(self, val):
305
+ self.C[0,2] = val
306
+
307
+ @property
308
+ def Cwy(self):
309
+ return self.C[1,2]
310
+ @Cwy.setter
311
+ def Cwy(self, val):
312
+ self.C[1,2] = val
313
+
314
+ @property
315
+ def Cwz(self):
316
+ return self.C[2,2]
317
+ @Cwz.setter
318
+ def Cwz(self, val):
319
+ self.C[2,2] = val
320
+
321
+ @property
322
+ def Lux(self):
323
+ return self.Lx[0]
324
+ @Lux.setter
325
+ def Lux(self, val):
326
+ self.Lx[0] = val
327
+
328
+ @property
329
+ def Lvx(self):
330
+ return self.Lx[1]
331
+ @Lvx.setter
332
+ def Lvx(self, val):
333
+ self.Lx[1] = val
334
+
335
+ @property
336
+ def Lwx(self):
337
+ return self.Lx[2]
338
+ @Lwx.setter
339
+ def Lwx(self, val):
340
+ self.Lx[2] = val
341
+
342
+ # Alternative constructor
343
+ @classmethod
344
+ def from_json(cls, json_file, **kwargs):
345
+
346
+ with open(json_file, 'r') as fileobj:
347
+ data = json.load(fileobj)
348
+
349
+ direction = data.pop('direction')
350
+ U0 = data.pop('U0')
351
+
352
+ if 'scaling' in data:
353
+ scaling = eval(data.pop('scaling'))
354
+ else:
355
+ scaling = None
356
+
357
+ # Update options if provided (to enable overriding options from screening setup)
358
+ if 'options' in data:
359
+ options = data['options']
360
+ else:
361
+ options = {}
362
+
363
+ return cls(U0, direction, scaling=scaling, options=options, **data)
wawi/model/_dry.py ADDED
@@ -0,0 +1,141 @@
1
+ import numpy as np
2
+
3
+ '''
4
+ DRY MODES SUBMODULE
5
+ '''
6
+
7
+ class ModalDry:
8
+ def __init__(self, phi, phi_x=dict(), local_phi=False, k=None, omega_n=None, m=1.0, xi0=0.0, n_modes=None, m_min=0.0):
9
+
10
+ self._n_modes = n_modes
11
+ self.phi_full = {key: np.array(phi[key]) for key in phi}
12
+ self.local_phi = local_phi # is assumed for all relevant phi
13
+ self.phi_x = phi_x
14
+
15
+ n_none = np.sum([var_i is None for var_i in [m, k, omega_n]])
16
+ if n_none != 1:
17
+ raise ValueError('Exactly two of the variables m, k and omega_n has to be input. This is to ensure consistency and sufficient info. Force m=None if onlyk and omega_n are specified')
18
+
19
+ # Variations of input of m,k and omega_n
20
+ if m is not None and omega_n is not None:
21
+ self._k = np.array(m)*np.array(omega_n)**2
22
+ self._m = np.array(m)
23
+ elif k is not None and omega_n is not None:
24
+ self._k = np.array(k)
25
+ self._m = np.array(k)/np.array(omega_n)**2
26
+ else:
27
+ self._k = np.array(k)
28
+ self._m = np.array(m)
29
+
30
+ self.m_min = m_min
31
+ self._xi0 = xi0
32
+
33
+ if self._m is not None and np.ndim(self._m) == 0:
34
+ self._m = np.array([self._m]*self.n_modes)
35
+
36
+ @property
37
+ def mode_ix(self):
38
+ if hasattr(self, 'm_min'):
39
+ return np.where((self._m>self.m_min)[:self.n_modes])[0]
40
+ else:
41
+ return np.arange(0,self.n_modes)
42
+
43
+ @property
44
+ def omega_n(self):
45
+ return (self.k/self.m)**0.5
46
+
47
+ @property
48
+ def wn(self):
49
+ return self.omega_n
50
+
51
+ @property
52
+ def omega_d(self):
53
+ return np.sqrt(1 - self.xi0) * self.omega_n
54
+
55
+ @property
56
+ def wd(self):
57
+ return self.omega_d
58
+
59
+ @property
60
+ def fn(self):
61
+ return self.omega_n/2/np.pi
62
+
63
+ @property
64
+ def Tn(self):
65
+ return 2*np.pi/self.omega_n
66
+
67
+ @property
68
+ def fd(self):
69
+ return self.omega_d/2/np.pi
70
+
71
+ @property
72
+ def Td(self):
73
+ return 2*np.pi/self.omega_d
74
+
75
+
76
+ @property
77
+ def n_modes(self):
78
+ if self._n_modes is None:
79
+ return list(self.phi_full.values())[0].shape[1]
80
+ else:
81
+ return self._n_modes
82
+
83
+ @n_modes.setter
84
+ def n_modes(self, n):
85
+ self._n_modes = n
86
+
87
+ @property
88
+ def xi0(self):
89
+ if self._xi0 is None:
90
+ return 0.0
91
+ elif np.ndim(self._xi0) == 0:
92
+ return np.array([self._xi0]*self.n_modes)[self.mode_ix]
93
+ else:
94
+ if len(self._xi0) == len(self._m): #full model
95
+ return self._xi0[:self.n_modes][self.mode_ix]
96
+ elif len(self._xi0) == self.n_modes: #truncated
97
+ return self._xi0[self.mode_ix]
98
+ elif len(self._xi0) == len(self.mode_ix): #truncated & filtered
99
+ return self._xi0
100
+ else:
101
+ raise ValueError('''Specified xi0 must be scalar or with same length as total number of modes,
102
+ number of truncated modes (by n_modes), or filtered number of modes (by n_modes and m_min)
103
+ ''')
104
+
105
+ @xi0.setter
106
+ def xi0(self, xi0):
107
+ self._xi0 = xi0
108
+
109
+ @property
110
+ def m(self):
111
+ return self._m[self.mode_ix]
112
+
113
+ @m.setter
114
+ def m(self, m):
115
+ self._m = m
116
+
117
+ @property
118
+ def k(self):
119
+ return self._k[self.mode_ix]
120
+
121
+ @k.setter
122
+ def k(self, k):
123
+ self._k = k
124
+
125
+ def get_phi(self, key='full', use_n_modes=True):
126
+ if use_n_modes:
127
+ return self.phi_full[key][:, self.mode_ix]
128
+ else:
129
+ return self.phi_full[key]
130
+
131
+ @property
132
+ def K(self):
133
+ return np.diag(self.k)
134
+
135
+ @property
136
+ def C(self):
137
+ return (2*np.sqrt(self.K*self.M)*np.diag(self.xi0))
138
+
139
+ @property
140
+ def M(self):
141
+ return np.diag(self.m)