weac 2.6.4__py3-none-any.whl → 3.0.0__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.
- weac/__init__.py +2 -14
- weac/constants.py +37 -0
- weac/logging_config.py +39 -0
- {weac-2.6.4.dist-info → weac-3.0.0.dist-info}/METADATA +194 -62
- weac-3.0.0.dist-info/RECORD +8 -0
- weac/eigensystem.py +0 -658
- weac/inverse.py +0 -51
- weac/layered.py +0 -64
- weac/mixins.py +0 -2083
- weac/plot.py +0 -675
- weac/tools.py +0 -334
- weac-2.6.4.dist-info/RECORD +0 -12
- {weac-2.6.4.dist-info → weac-3.0.0.dist-info}/WHEEL +0 -0
- {weac-2.6.4.dist-info → weac-3.0.0.dist-info}/licenses/LICENSE +0 -0
- {weac-2.6.4.dist-info → weac-3.0.0.dist-info}/top_level.txt +0 -0
weac/mixins.py
DELETED
|
@@ -1,2083 +0,0 @@
|
|
|
1
|
-
"""Mixins for the elastic analysis of layered snow slabs."""
|
|
2
|
-
# pylint: disable=invalid-name,too-many-locals,too-many-arguments,too-many-lines
|
|
3
|
-
|
|
4
|
-
# Standard library imports
|
|
5
|
-
from functools import partial
|
|
6
|
-
|
|
7
|
-
# Third party imports
|
|
8
|
-
import numpy as np
|
|
9
|
-
from scipy.integrate import cumulative_trapezoid, quad
|
|
10
|
-
from scipy.optimize import brentq
|
|
11
|
-
|
|
12
|
-
# Module imports
|
|
13
|
-
from weac.tools import calc_vertical_bc_center_of_gravity, tensile_strength_slab
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
class FieldQuantitiesMixin:
|
|
17
|
-
"""
|
|
18
|
-
Mixin for field quantities.
|
|
19
|
-
|
|
20
|
-
Provides methods for the computation of displacements, stresses,
|
|
21
|
-
strains, and energy release rates from the solution vector.
|
|
22
|
-
"""
|
|
23
|
-
|
|
24
|
-
# pylint: disable=no-self-use
|
|
25
|
-
def w(self, Z, unit="mm"):
|
|
26
|
-
"""
|
|
27
|
-
Get centerline deflection w.
|
|
28
|
-
|
|
29
|
-
Arguments
|
|
30
|
-
---------
|
|
31
|
-
Z : ndarray
|
|
32
|
-
Solution vector [u(x) u'(x) w(x) w'(x) psi(x) psi'(x)]^T.
|
|
33
|
-
unit : {'m', 'cm', 'mm', 'um'}, optional
|
|
34
|
-
Desired output unit. Default is mm.
|
|
35
|
-
|
|
36
|
-
Returns
|
|
37
|
-
-------
|
|
38
|
-
float
|
|
39
|
-
Deflection w (in specified unit) of the slab.
|
|
40
|
-
"""
|
|
41
|
-
convert = {
|
|
42
|
-
"m": 1e-3, # meters
|
|
43
|
-
"cm": 1e-1, # centimeters
|
|
44
|
-
"mm": 1, # millimeters
|
|
45
|
-
"um": 1e3, # micrometers
|
|
46
|
-
}
|
|
47
|
-
return convert[unit] * Z[2, :]
|
|
48
|
-
|
|
49
|
-
def dw_dx(self, Z):
|
|
50
|
-
"""
|
|
51
|
-
Get first derivative w' of the centerline deflection.
|
|
52
|
-
|
|
53
|
-
Arguments
|
|
54
|
-
---------
|
|
55
|
-
Z : ndarray
|
|
56
|
-
Solution vector [u(x) u'(x) w(x) w'(x) psi(x) psi'(x)]^T.
|
|
57
|
-
|
|
58
|
-
Returns
|
|
59
|
-
-------
|
|
60
|
-
float
|
|
61
|
-
First derivative w' of the deflection of the slab.
|
|
62
|
-
"""
|
|
63
|
-
return Z[3, :]
|
|
64
|
-
|
|
65
|
-
def psi(self, Z, unit="rad"):
|
|
66
|
-
"""
|
|
67
|
-
Get midplane rotation psi.
|
|
68
|
-
|
|
69
|
-
Arguments
|
|
70
|
-
---------
|
|
71
|
-
Z : ndarray
|
|
72
|
-
Solution vector [u(x) u'(x) w(x) w'(x) psi(x) psi'(x)]^T.
|
|
73
|
-
unit : {'deg', 'degrees', 'rad', 'radians'}, optional
|
|
74
|
-
Desired output unit. Default is radians.
|
|
75
|
-
|
|
76
|
-
Returns
|
|
77
|
-
-------
|
|
78
|
-
psi : float
|
|
79
|
-
Cross-section rotation psi (radians) of the slab.
|
|
80
|
-
"""
|
|
81
|
-
if unit in ["deg", "degree", "degrees"]:
|
|
82
|
-
psi = np.rad2deg(Z[4, :])
|
|
83
|
-
elif unit in ["rad", "radian", "radians"]:
|
|
84
|
-
psi = Z[4, :]
|
|
85
|
-
return psi
|
|
86
|
-
|
|
87
|
-
def dpsi_dx(self, Z):
|
|
88
|
-
"""
|
|
89
|
-
Get first derivative psi' of the midplane rotation.
|
|
90
|
-
|
|
91
|
-
Arguments
|
|
92
|
-
---------
|
|
93
|
-
Z : ndarray
|
|
94
|
-
Solution vector [u(x) u'(x) w(x) w'(x) psi(x) psi'(x)]^T.
|
|
95
|
-
|
|
96
|
-
Returns
|
|
97
|
-
-------
|
|
98
|
-
float
|
|
99
|
-
First derivative psi' of the midplane rotation (radians/mm)
|
|
100
|
-
of the slab.
|
|
101
|
-
"""
|
|
102
|
-
return Z[5, :]
|
|
103
|
-
|
|
104
|
-
# pylint: enable=no-self-use
|
|
105
|
-
def u(self, Z, z0, unit="mm"):
|
|
106
|
-
"""
|
|
107
|
-
Get horizontal displacement u = u0 + z0 psi.
|
|
108
|
-
|
|
109
|
-
Arguments
|
|
110
|
-
---------
|
|
111
|
-
Z : ndarray
|
|
112
|
-
Solution vector [u(x) u'(x) w(x) w'(x) psi(x) psi'(x)]^T.
|
|
113
|
-
z0 : float
|
|
114
|
-
Z-coordinate (mm) where u is to be evaluated.
|
|
115
|
-
unit : {'m', 'cm', 'mm', 'um'}, optional
|
|
116
|
-
Desired output unit. Default is mm.
|
|
117
|
-
|
|
118
|
-
Returns
|
|
119
|
-
-------
|
|
120
|
-
float
|
|
121
|
-
Horizontal displacement u (unit) of the slab.
|
|
122
|
-
"""
|
|
123
|
-
convert = {
|
|
124
|
-
"m": 1e-3, # meters
|
|
125
|
-
"cm": 1e-1, # centimeters
|
|
126
|
-
"mm": 1, # millimeters
|
|
127
|
-
"um": 1e3, # micrometers
|
|
128
|
-
}
|
|
129
|
-
return convert[unit] * (Z[0, :] + z0 * self.psi(Z))
|
|
130
|
-
|
|
131
|
-
def du_dx(self, Z, z0):
|
|
132
|
-
"""
|
|
133
|
-
Get first derivative of the horizontal displacement.
|
|
134
|
-
|
|
135
|
-
Arguments
|
|
136
|
-
---------
|
|
137
|
-
Z : ndarray
|
|
138
|
-
Solution vector [u(x) u'(x) w(x) w'(x) psi(x) psi'(x)]^T.
|
|
139
|
-
z0 : float
|
|
140
|
-
Z-coordinate (mm) where u is to be evaluated.
|
|
141
|
-
|
|
142
|
-
Returns
|
|
143
|
-
-------
|
|
144
|
-
float
|
|
145
|
-
First derivative u' = u0' + z0 psi' of the horizontal
|
|
146
|
-
displacement of the slab.
|
|
147
|
-
"""
|
|
148
|
-
return Z[1, :] + z0 * self.dpsi_dx(Z)
|
|
149
|
-
|
|
150
|
-
def N(self, Z):
|
|
151
|
-
"""
|
|
152
|
-
Get the axial normal force N = A11 u' + B11 psi' in the slab.
|
|
153
|
-
|
|
154
|
-
Arguments
|
|
155
|
-
---------
|
|
156
|
-
Z : ndarray
|
|
157
|
-
Solution vector [u(x) u'(x) w(x) w'(x) psi(x) psi'(x)]^T.
|
|
158
|
-
|
|
159
|
-
Returns
|
|
160
|
-
-------
|
|
161
|
-
float
|
|
162
|
-
Axial normal force N (N) in the slab.
|
|
163
|
-
"""
|
|
164
|
-
return self.A11 * Z[1, :] + self.B11 * Z[5, :]
|
|
165
|
-
|
|
166
|
-
def M(self, Z):
|
|
167
|
-
"""
|
|
168
|
-
Get bending moment M = B11 u' + D11 psi' in the slab.
|
|
169
|
-
|
|
170
|
-
Arguments
|
|
171
|
-
---------
|
|
172
|
-
Z : ndarray
|
|
173
|
-
Solution vector [u(x) u'(x) w(x) w'(x) psi(x) psi'(x)]^T.
|
|
174
|
-
|
|
175
|
-
Returns
|
|
176
|
-
-------
|
|
177
|
-
float
|
|
178
|
-
Bending moment M (Nmm) in the slab.
|
|
179
|
-
"""
|
|
180
|
-
return self.B11 * Z[1, :] + self.D11 * Z[5, :]
|
|
181
|
-
|
|
182
|
-
def V(self, Z):
|
|
183
|
-
"""
|
|
184
|
-
Get vertical shear force V = kA55(w' + psi).
|
|
185
|
-
|
|
186
|
-
Arguments
|
|
187
|
-
---------
|
|
188
|
-
Z : ndarray
|
|
189
|
-
Solution vector [u(x) u'(x) w(x) w'(x) psi(x) psi'(x)]^T.
|
|
190
|
-
|
|
191
|
-
Returns
|
|
192
|
-
-------
|
|
193
|
-
float
|
|
194
|
-
Vertical shear force V (N) in the slab.
|
|
195
|
-
"""
|
|
196
|
-
return self.kA55 * (Z[3, :] + Z[4, :])
|
|
197
|
-
|
|
198
|
-
def sig(self, Z, unit="MPa"):
|
|
199
|
-
"""
|
|
200
|
-
Get weak-layer normal stress.
|
|
201
|
-
|
|
202
|
-
Arguments
|
|
203
|
-
---------
|
|
204
|
-
Z : ndarray
|
|
205
|
-
Solution vector [u(x) u'(x) w(x) w'(x) psi(x) psi'(x)]^T.
|
|
206
|
-
unit : {'MPa', 'kPa'}, optional
|
|
207
|
-
Desired output unit. Default is MPa.
|
|
208
|
-
|
|
209
|
-
Returns
|
|
210
|
-
-------
|
|
211
|
-
float
|
|
212
|
-
Weak-layer normal stress sigma (in specified unit).
|
|
213
|
-
"""
|
|
214
|
-
convert = {"kPa": 1e3, "MPa": 1}
|
|
215
|
-
return -convert[unit] * self.kn * self.w(Z)
|
|
216
|
-
|
|
217
|
-
def tau(self, Z, unit="MPa"):
|
|
218
|
-
"""
|
|
219
|
-
Get weak-layer shear stress.
|
|
220
|
-
|
|
221
|
-
Arguments
|
|
222
|
-
---------
|
|
223
|
-
Z : ndarray
|
|
224
|
-
Solution vector [u(x) u'(x) w(x) w'(x) psi(x) psi'(x)]^T.
|
|
225
|
-
unit : {'MPa', 'kPa'}, optional
|
|
226
|
-
Desired output unit. Default is MPa.
|
|
227
|
-
|
|
228
|
-
Returns
|
|
229
|
-
-------
|
|
230
|
-
float
|
|
231
|
-
Weak-layer shear stress tau (in specified unit).
|
|
232
|
-
"""
|
|
233
|
-
convert = {"kPa": 1e3, "MPa": 1}
|
|
234
|
-
return (
|
|
235
|
-
-convert[unit]
|
|
236
|
-
* self.kt
|
|
237
|
-
* (self.dw_dx(Z) * self.t / 2 - self.u(Z, z0=self.h / 2))
|
|
238
|
-
)
|
|
239
|
-
|
|
240
|
-
def eps(self, Z):
|
|
241
|
-
"""
|
|
242
|
-
Get weak-layer normal strain.
|
|
243
|
-
|
|
244
|
-
Arguments
|
|
245
|
-
---------
|
|
246
|
-
Z : ndarray
|
|
247
|
-
Solution vector [u(x) u'(x) w(x) w'(x) psi(x) psi'(x)]^T.
|
|
248
|
-
|
|
249
|
-
Returns
|
|
250
|
-
-------
|
|
251
|
-
float
|
|
252
|
-
Weak-layer normal strain epsilon.
|
|
253
|
-
"""
|
|
254
|
-
return -self.w(Z) / self.t
|
|
255
|
-
|
|
256
|
-
def gamma(self, Z):
|
|
257
|
-
"""
|
|
258
|
-
Get weak-layer shear strain.
|
|
259
|
-
|
|
260
|
-
Arguments
|
|
261
|
-
---------
|
|
262
|
-
Z : ndarray
|
|
263
|
-
Solution vector [u(x) u'(x) w(x) w'(x) psi(x) psi'(x)]^T.
|
|
264
|
-
|
|
265
|
-
Returns
|
|
266
|
-
-------
|
|
267
|
-
float
|
|
268
|
-
Weak-layer shear strain gamma.
|
|
269
|
-
"""
|
|
270
|
-
return self.dw_dx(Z) / 2 - self.u(Z, z0=self.h / 2) / self.t
|
|
271
|
-
|
|
272
|
-
def Gi(self, Ztip, unit="kJ/m^2"):
|
|
273
|
-
"""
|
|
274
|
-
Get mode I differential energy release rate at crack tip.
|
|
275
|
-
|
|
276
|
-
Arguments
|
|
277
|
-
---------
|
|
278
|
-
Ztip : ndarray
|
|
279
|
-
Solution vector [u(x) u'(x) w(x) w'(x) psi(x) psi'(x)]^T
|
|
280
|
-
at the crack tip.
|
|
281
|
-
unit : {'N/mm', 'kJ/m^2', 'J/m^2'}, optional
|
|
282
|
-
Desired output unit. Default is kJ/m^2.
|
|
283
|
-
|
|
284
|
-
Returns
|
|
285
|
-
-------
|
|
286
|
-
float
|
|
287
|
-
Mode I differential energy release rate (N/mm = kJ/m^2
|
|
288
|
-
or J/m^2) at the crack tip.
|
|
289
|
-
"""
|
|
290
|
-
convert = {
|
|
291
|
-
"J/m^2": 1e3, # joule per square meter
|
|
292
|
-
"kJ/m^2": 1, # kilojoule per square meter
|
|
293
|
-
"N/mm": 1, # newton per millimeter
|
|
294
|
-
}
|
|
295
|
-
return convert[unit] * self.sig(Ztip) ** 2 / (2 * self.kn)
|
|
296
|
-
|
|
297
|
-
def Gii(self, Ztip, unit="kJ/m^2"):
|
|
298
|
-
"""
|
|
299
|
-
Get mode II differential energy release rate at crack tip.
|
|
300
|
-
|
|
301
|
-
Arguments
|
|
302
|
-
---------
|
|
303
|
-
Ztip : ndarray
|
|
304
|
-
Solution vector [u(x) u'(x) w(x) w'(x) psi(x) psi'(x)]^T
|
|
305
|
-
at the crack tip.
|
|
306
|
-
unit : {'N/mm', 'kJ/m^2', 'J/m^2'}, optional
|
|
307
|
-
Desired output unit. Default is kJ/m^2 = N/mm.
|
|
308
|
-
|
|
309
|
-
Returns
|
|
310
|
-
-------
|
|
311
|
-
float
|
|
312
|
-
Mode II differential energy release rate (N/mm = kJ/m^2
|
|
313
|
-
or J/m^2) at the crack tip.
|
|
314
|
-
"""
|
|
315
|
-
convert = {
|
|
316
|
-
"J/m^2": 1e3, # joule per square meter
|
|
317
|
-
"kJ/m^2": 1, # kilojoule per square meter
|
|
318
|
-
"N/mm": 1, # newton per millimeter
|
|
319
|
-
}
|
|
320
|
-
return convert[unit] * self.tau(Ztip) ** 2 / (2 * self.kt)
|
|
321
|
-
|
|
322
|
-
def int1(self, x, z0, z1):
|
|
323
|
-
"""
|
|
324
|
-
Get mode I crack opening integrand at integration points xi.
|
|
325
|
-
|
|
326
|
-
Arguments
|
|
327
|
-
---------
|
|
328
|
-
x : float, ndarray
|
|
329
|
-
X-coordinate where integrand is to be evaluated (mm).
|
|
330
|
-
z0 : callable
|
|
331
|
-
Function that returns the solution vector of the uncracked
|
|
332
|
-
configuration.
|
|
333
|
-
z1 : callable
|
|
334
|
-
Function that returns the solution vector of the cracked
|
|
335
|
-
configuration.
|
|
336
|
-
|
|
337
|
-
Returns
|
|
338
|
-
-------
|
|
339
|
-
float or ndarray
|
|
340
|
-
Integrant of the mode I crack opening integral.
|
|
341
|
-
"""
|
|
342
|
-
return self.sig(z0(x)) * self.eps(z1(x)) * self.t
|
|
343
|
-
|
|
344
|
-
def int2(self, x, z0, z1):
|
|
345
|
-
"""
|
|
346
|
-
Get mode II crack opening integrand at integration points xi.
|
|
347
|
-
|
|
348
|
-
Arguments
|
|
349
|
-
---------
|
|
350
|
-
x : float, ndarray
|
|
351
|
-
X-coordinate where integrand is to be evaluated (mm).
|
|
352
|
-
z0 : callable
|
|
353
|
-
Function that returns the solution vector of the uncracked
|
|
354
|
-
configuration.
|
|
355
|
-
z1 : callable
|
|
356
|
-
Function that returns the solution vector of the cracked
|
|
357
|
-
configuration.
|
|
358
|
-
|
|
359
|
-
Returns
|
|
360
|
-
-------
|
|
361
|
-
float or ndarray
|
|
362
|
-
Integrant of the mode II crack opening integral.
|
|
363
|
-
"""
|
|
364
|
-
return self.tau(z0(x)) * self.gamma(z1(x)) * self.t
|
|
365
|
-
|
|
366
|
-
def dz_dx(self, z, phi):
|
|
367
|
-
"""
|
|
368
|
-
Get first derivative z'(x) = K*z(x) + q of the solution vector.
|
|
369
|
-
|
|
370
|
-
z'(x) = [u'(x) u''(x) w'(x) w''(x) psi'(x), psi''(x)]^T
|
|
371
|
-
|
|
372
|
-
Parameters
|
|
373
|
-
----------
|
|
374
|
-
z : ndarray
|
|
375
|
-
Solution vector [u(x) u'(x) w(x) w'(x) psi(x), psi'(x)]^T
|
|
376
|
-
phi : float
|
|
377
|
-
Inclination (degrees). Counterclockwise positive.
|
|
378
|
-
|
|
379
|
-
Returns
|
|
380
|
-
-------
|
|
381
|
-
ndarray, float
|
|
382
|
-
First derivative z'(x) for the solution vector (6x1).
|
|
383
|
-
"""
|
|
384
|
-
K = self.calc_system_matrix()
|
|
385
|
-
q = self.get_load_vector(phi)
|
|
386
|
-
return np.dot(K, z) + q
|
|
387
|
-
|
|
388
|
-
def dz_dxdx(self, z, phi):
|
|
389
|
-
"""
|
|
390
|
-
Get second derivative z''(x) = K*z'(x) of the solution vector.
|
|
391
|
-
|
|
392
|
-
z''(x) = [u''(x) u'''(x) w''(x) w'''(x) psi''(x), psi'''(x)]^T
|
|
393
|
-
|
|
394
|
-
Parameters
|
|
395
|
-
----------
|
|
396
|
-
z : ndarray
|
|
397
|
-
Solution vector [u(x) u'(x) w(x) w'(x) psi(x), psi'(x)]^T
|
|
398
|
-
phi : float
|
|
399
|
-
Inclination (degrees). Counterclockwise positive.
|
|
400
|
-
|
|
401
|
-
Returns
|
|
402
|
-
-------
|
|
403
|
-
ndarray, float
|
|
404
|
-
Second derivative z''(x) = (K*z(x) + q)' = K*z'(x) = K*(K*z(x) + q)
|
|
405
|
-
of the solution vector (6x1).
|
|
406
|
-
"""
|
|
407
|
-
K = self.calc_system_matrix()
|
|
408
|
-
q = self.get_load_vector(phi)
|
|
409
|
-
dz_dx = np.dot(K, z) + q
|
|
410
|
-
return np.dot(K, dz_dx)
|
|
411
|
-
|
|
412
|
-
def du0_dxdx(self, z, phi):
|
|
413
|
-
"""
|
|
414
|
-
Get second derivative of the horiz. centerline displacement u0''(x).
|
|
415
|
-
|
|
416
|
-
Parameters
|
|
417
|
-
----------
|
|
418
|
-
z : ndarray
|
|
419
|
-
Solution vector [u(x) u'(x) w(x) w'(x) psi(x) psi'(x)]^T.
|
|
420
|
-
phi : float
|
|
421
|
-
Inclination (degrees). Counterclockwise positive.
|
|
422
|
-
|
|
423
|
-
Returns
|
|
424
|
-
-------
|
|
425
|
-
ndarray, float
|
|
426
|
-
Second derivative of the horizontal centerline displacement
|
|
427
|
-
u0''(x) (1/mm).
|
|
428
|
-
"""
|
|
429
|
-
return self.dz_dx(z, phi)[1, :]
|
|
430
|
-
|
|
431
|
-
def dpsi_dxdx(self, z, phi):
|
|
432
|
-
"""
|
|
433
|
-
Get second derivative of the cross-section rotation psi''(x).
|
|
434
|
-
|
|
435
|
-
Parameters
|
|
436
|
-
----------
|
|
437
|
-
z : ndarray
|
|
438
|
-
Solution vector [u(x) u'(x) w(x) w'(x) psi(x) psi'(x)]^T.
|
|
439
|
-
phi : float
|
|
440
|
-
Inclination (degrees). Counterclockwise positive.
|
|
441
|
-
|
|
442
|
-
Returns
|
|
443
|
-
-------
|
|
444
|
-
ndarray, float
|
|
445
|
-
Second derivative of the cross-section rotation psi''(x) (1/mm^2).
|
|
446
|
-
"""
|
|
447
|
-
return self.dz_dx(z, phi)[5, :]
|
|
448
|
-
|
|
449
|
-
def du0_dxdxdx(self, z, phi):
|
|
450
|
-
"""
|
|
451
|
-
Get third derivative of the horiz. centerline displacement u0'''(x).
|
|
452
|
-
|
|
453
|
-
Parameters
|
|
454
|
-
----------
|
|
455
|
-
z : ndarray
|
|
456
|
-
Solution vector [u(x) u'(x) w(x) w'(x) psi(x) psi'(x)]^T.
|
|
457
|
-
phi : float
|
|
458
|
-
Inclination (degrees). Counterclockwise positive.
|
|
459
|
-
|
|
460
|
-
Returns
|
|
461
|
-
-------
|
|
462
|
-
ndarray, float
|
|
463
|
-
Third derivative of the horizontal centerline displacement
|
|
464
|
-
u0'''(x) (1/mm^2).
|
|
465
|
-
"""
|
|
466
|
-
return self.dz_dxdx(z, phi)[1, :]
|
|
467
|
-
|
|
468
|
-
def dpsi_dxdxdx(self, z, phi):
|
|
469
|
-
"""
|
|
470
|
-
Get third derivative of the cross-section rotation psi'''(x).
|
|
471
|
-
|
|
472
|
-
Parameters
|
|
473
|
-
----------
|
|
474
|
-
z : ndarray
|
|
475
|
-
Solution vector [u(x) u'(x) w(x) w'(x) psi(x) psi'(x)]^T.
|
|
476
|
-
phi : float
|
|
477
|
-
Inclination (degrees). Counterclockwise positive.
|
|
478
|
-
|
|
479
|
-
Returns
|
|
480
|
-
-------
|
|
481
|
-
ndarray, float
|
|
482
|
-
Third derivative of the cross-section rotation psi'''(x) (1/mm^3).
|
|
483
|
-
"""
|
|
484
|
-
return self.dz_dxdx(z, phi)[5, :]
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
class SlabContactMixin:
|
|
488
|
-
"""
|
|
489
|
-
Mixin for handling the touchdown situation in a PST.
|
|
490
|
-
|
|
491
|
-
Provides Methods for the calculation of substitute spring stiffnesses,
|
|
492
|
-
cracklength-tresholds and element lengths.
|
|
493
|
-
"""
|
|
494
|
-
|
|
495
|
-
# pylint: disable=too-many-instance-attributes
|
|
496
|
-
|
|
497
|
-
def set_columnlength(self, L):
|
|
498
|
-
"""
|
|
499
|
-
Set cracklength.
|
|
500
|
-
|
|
501
|
-
Arguments
|
|
502
|
-
---------
|
|
503
|
-
L : float
|
|
504
|
-
Column length of a PST (mm).
|
|
505
|
-
"""
|
|
506
|
-
self.L = L
|
|
507
|
-
|
|
508
|
-
def set_cracklength(self, a):
|
|
509
|
-
"""
|
|
510
|
-
Set cracklength.
|
|
511
|
-
|
|
512
|
-
Arguments
|
|
513
|
-
---------
|
|
514
|
-
a : float
|
|
515
|
-
Cracklength in a PST (mm).
|
|
516
|
-
"""
|
|
517
|
-
self.a = a
|
|
518
|
-
|
|
519
|
-
def set_tc(self, cf):
|
|
520
|
-
"""
|
|
521
|
-
Set height of the crack.
|
|
522
|
-
|
|
523
|
-
Arguments
|
|
524
|
-
---------
|
|
525
|
-
cf : float
|
|
526
|
-
Collapse-factor. Ratio of the crack height to the
|
|
527
|
-
uncollapsed weak-layer height.
|
|
528
|
-
"""
|
|
529
|
-
# mark argument as intentionally unused (API compatibility)
|
|
530
|
-
_ = cf
|
|
531
|
-
# subtract displacement under constact load from collapsed wl height
|
|
532
|
-
qn = self.calc_qn()
|
|
533
|
-
collapse_height = 4.70 * (1 - np.exp(-self.t / 7.78))
|
|
534
|
-
self.tc = collapse_height - qn / self.kn
|
|
535
|
-
|
|
536
|
-
def set_phi(self, phi):
|
|
537
|
-
"""
|
|
538
|
-
Set inclination of the slab.
|
|
539
|
-
|
|
540
|
-
Arguments
|
|
541
|
-
---------
|
|
542
|
-
phi : float
|
|
543
|
-
Inclination of the slab (°).
|
|
544
|
-
"""
|
|
545
|
-
self.phi = phi
|
|
546
|
-
|
|
547
|
-
def set_stiffness_ratio(self, ratio=1000):
|
|
548
|
-
"""
|
|
549
|
-
Set ratio between collapsed and uncollapsed weak-layer stiffness.
|
|
550
|
-
|
|
551
|
-
Parameters
|
|
552
|
-
----------
|
|
553
|
-
ratio : int, optional
|
|
554
|
-
Stiffness ratio between collapsed and uncollapsed weak layer.
|
|
555
|
-
Default is 1000.
|
|
556
|
-
"""
|
|
557
|
-
self.ratio = ratio
|
|
558
|
-
|
|
559
|
-
def calc_qn(self):
|
|
560
|
-
"""
|
|
561
|
-
Calc total surface normal load.
|
|
562
|
-
|
|
563
|
-
Returns
|
|
564
|
-
-------
|
|
565
|
-
float
|
|
566
|
-
Total surface normal load (N/mm).
|
|
567
|
-
"""
|
|
568
|
-
return self.get_weight_load(self.phi)[0] + self.get_surface_load(self.phi)[0]
|
|
569
|
-
|
|
570
|
-
def calc_qt(self):
|
|
571
|
-
"""
|
|
572
|
-
Calc total surface normal load.
|
|
573
|
-
|
|
574
|
-
Returns
|
|
575
|
-
-------
|
|
576
|
-
float
|
|
577
|
-
Total surface normal load (N/mm).
|
|
578
|
-
"""
|
|
579
|
-
return self.get_weight_load(self.phi)[1] + self.get_surface_load(self.phi)[1]
|
|
580
|
-
|
|
581
|
-
def substitute_stiffness(self, L, support="rested", dof="rot"):
|
|
582
|
-
"""
|
|
583
|
-
Calc substitute stiffness for beam on elastic foundation.
|
|
584
|
-
|
|
585
|
-
Arguments
|
|
586
|
-
---------
|
|
587
|
-
L : float
|
|
588
|
-
Total length of the PST-column (mm).
|
|
589
|
-
support : string
|
|
590
|
-
Type of segment foundation. Defaults to 'rested'.
|
|
591
|
-
dof : string
|
|
592
|
-
Type of substitute spring, either 'rot' or 'trans'. Defaults to 'rot'.
|
|
593
|
-
|
|
594
|
-
Returns
|
|
595
|
-
-------
|
|
596
|
-
k : stiffness of substitute spring.
|
|
597
|
-
"""
|
|
598
|
-
# adjust system to substitute system
|
|
599
|
-
if dof in ["rot"]:
|
|
600
|
-
tempsys = self.system
|
|
601
|
-
self.system = "rot"
|
|
602
|
-
if dof in ["trans"]:
|
|
603
|
-
tempsys = self.system
|
|
604
|
-
self.system = "trans"
|
|
605
|
-
|
|
606
|
-
# Change eigensystem for rested segment
|
|
607
|
-
if support in ["rested"]:
|
|
608
|
-
tempkn = self.kn
|
|
609
|
-
tempkt = self.kt
|
|
610
|
-
self.kn = self.ratio * self.kn
|
|
611
|
-
self.kt = self.ratio * self.kt
|
|
612
|
-
self.calc_system_matrix()
|
|
613
|
-
self.calc_eigensystem()
|
|
614
|
-
|
|
615
|
-
# prepare list of segment characteristics
|
|
616
|
-
segments = {
|
|
617
|
-
"li": np.array([L, 0.0]),
|
|
618
|
-
"mi": np.array([0]),
|
|
619
|
-
"ki": np.array([True, True]),
|
|
620
|
-
}
|
|
621
|
-
# solve system of equations
|
|
622
|
-
constants = self.assemble_and_solve(phi=0, **segments)
|
|
623
|
-
# calculate stiffness
|
|
624
|
-
_, z_pst, _ = self.rasterize_solution(C=constants, phi=0, num=1, **segments)
|
|
625
|
-
if dof in ["rot"]:
|
|
626
|
-
k = abs(1 / self.psi(z_pst)[0])
|
|
627
|
-
if dof in ["trans"]:
|
|
628
|
-
k = abs(1 / self.w(z_pst)[0])
|
|
629
|
-
|
|
630
|
-
# Reset to previous system and eigensystem
|
|
631
|
-
self.system = tempsys
|
|
632
|
-
if support in ["rested"]:
|
|
633
|
-
self.kn = tempkn
|
|
634
|
-
self.kt = tempkt
|
|
635
|
-
self.calc_system_matrix()
|
|
636
|
-
self.calc_eigensystem()
|
|
637
|
-
|
|
638
|
-
return k
|
|
639
|
-
|
|
640
|
-
def calc_a1(self):
|
|
641
|
-
"""
|
|
642
|
-
Calc transition lengths a1 (aAB).
|
|
643
|
-
|
|
644
|
-
Returns
|
|
645
|
-
-------
|
|
646
|
-
a1 : float
|
|
647
|
-
Length of the crack for transition of stage A to stage B (mm).
|
|
648
|
-
"""
|
|
649
|
-
# Unpack variables
|
|
650
|
-
bs = -(self.B11**2 / self.A11 - self.D11)
|
|
651
|
-
ss = self.kA55
|
|
652
|
-
L = self.L
|
|
653
|
-
tc = self.tc
|
|
654
|
-
qn = self.calc_qn()
|
|
655
|
-
|
|
656
|
-
# Create polynomial expression
|
|
657
|
-
def polynomial(x):
|
|
658
|
-
# Spring stiffness supported segment
|
|
659
|
-
kRl = self.substitute_stiffness(L - x, "supported", "rot")
|
|
660
|
-
kNl = self.substitute_stiffness(L - x, "supported", "trans")
|
|
661
|
-
c1 = 1 / (8 * bs)
|
|
662
|
-
c2 = 1 / (2 * kRl)
|
|
663
|
-
c3 = 1 / (2 * ss)
|
|
664
|
-
c4 = 1 / kNl
|
|
665
|
-
c5 = -tc / qn
|
|
666
|
-
return c1 * x**4 + c2 * x**3 + c3 * x**2 + c4 * x + c5
|
|
667
|
-
|
|
668
|
-
# Find root
|
|
669
|
-
a1 = brentq(polynomial, L / 1000, 999 / 1000 * L)
|
|
670
|
-
|
|
671
|
-
return a1
|
|
672
|
-
|
|
673
|
-
def calc_a2(self):
|
|
674
|
-
"""
|
|
675
|
-
Calc transition lengths a2 (aBC).
|
|
676
|
-
|
|
677
|
-
Returns
|
|
678
|
-
-------
|
|
679
|
-
a2 : float
|
|
680
|
-
Length of the crack for transition of stage B to stage C (mm).
|
|
681
|
-
"""
|
|
682
|
-
# Unpack variables
|
|
683
|
-
bs = -(self.B11**2 / self.A11 - self.D11)
|
|
684
|
-
ss = self.kA55
|
|
685
|
-
L = self.L
|
|
686
|
-
tc = self.tc
|
|
687
|
-
qn = self.calc_qn()
|
|
688
|
-
|
|
689
|
-
# Create polynomial function
|
|
690
|
-
def polynomial(x):
|
|
691
|
-
# Spring stiffness supported segment
|
|
692
|
-
kRl = self.substitute_stiffness(L - x, "supported", "rot")
|
|
693
|
-
kNl = self.substitute_stiffness(L - x, "supported", "trans")
|
|
694
|
-
c1 = ss**2 * kRl * kNl * qn
|
|
695
|
-
c2 = 6 * ss**2 * bs * kNl * qn
|
|
696
|
-
c3 = 30 * bs * ss * kRl * kNl * qn
|
|
697
|
-
c4 = 24 * bs * qn * (2 * ss**2 * kRl + 3 * bs * ss * kNl)
|
|
698
|
-
c5 = 72 * bs * (bs * qn * (ss**2 + kRl * kNl) - ss**2 * kRl * kNl * tc)
|
|
699
|
-
c6 = 144 * bs * ss * (bs * kRl * qn - bs * ss * kNl * tc)
|
|
700
|
-
c7 = -144 * bs**2 * ss * kRl * kNl * tc
|
|
701
|
-
return (
|
|
702
|
-
c1 * x**6 + c2 * x**5 + c3 * x**4 + c4 * x**3 + c5 * x**2 + c6 * x + c7
|
|
703
|
-
)
|
|
704
|
-
|
|
705
|
-
# Find root
|
|
706
|
-
a2 = brentq(polynomial, L / 1000, 999 / 1000 * L)
|
|
707
|
-
|
|
708
|
-
return a2
|
|
709
|
-
|
|
710
|
-
def calc_lA(self):
|
|
711
|
-
"""
|
|
712
|
-
Calculate the length of the touchdown element in mode A.
|
|
713
|
-
"""
|
|
714
|
-
lA = self.a
|
|
715
|
-
|
|
716
|
-
return lA
|
|
717
|
-
|
|
718
|
-
def calc_lB(self):
|
|
719
|
-
"""
|
|
720
|
-
Calculate the length of the touchdown element in mode B.
|
|
721
|
-
"""
|
|
722
|
-
lB = self.a
|
|
723
|
-
|
|
724
|
-
return lB
|
|
725
|
-
|
|
726
|
-
def calc_lC(self):
|
|
727
|
-
"""
|
|
728
|
-
Calculate the length of the touchdown element in mode C.
|
|
729
|
-
"""
|
|
730
|
-
# Unpack variables
|
|
731
|
-
bs = -(self.B11**2 / self.A11 - self.D11)
|
|
732
|
-
ss = self.kA55
|
|
733
|
-
L = self.L
|
|
734
|
-
a = self.a
|
|
735
|
-
tc = self.tc
|
|
736
|
-
qn = self.calc_qn()
|
|
737
|
-
|
|
738
|
-
def polynomial(x):
|
|
739
|
-
# Spring stiffness supported segment
|
|
740
|
-
kRl = self.substitute_stiffness(L - a, "supported", "rot")
|
|
741
|
-
kNl = self.substitute_stiffness(L - a, "supported", "trans")
|
|
742
|
-
# Spring stiffness rested segment
|
|
743
|
-
kRr = self.substitute_stiffness(a - x, "rested", "rot")
|
|
744
|
-
# define constants
|
|
745
|
-
c1 = ss**2 * kRl * kNl * qn
|
|
746
|
-
c2 = 6 * ss * kNl * qn * (bs * ss + kRl * kRr)
|
|
747
|
-
c3 = 30 * bs * ss * kNl * qn * (kRl + kRr)
|
|
748
|
-
c4 = (
|
|
749
|
-
24
|
|
750
|
-
* bs
|
|
751
|
-
* qn
|
|
752
|
-
* (2 * ss**2 * kRl + 3 * bs * ss * kNl + 3 * kRl * kRr * kNl)
|
|
753
|
-
)
|
|
754
|
-
c5 = (
|
|
755
|
-
72
|
|
756
|
-
* bs
|
|
757
|
-
* (
|
|
758
|
-
bs * qn * (ss**2 + kNl * (kRl + kRr))
|
|
759
|
-
+ ss * kRl * (2 * kRr * qn - ss * kNl * tc)
|
|
760
|
-
)
|
|
761
|
-
)
|
|
762
|
-
c6 = (
|
|
763
|
-
144
|
|
764
|
-
* bs
|
|
765
|
-
* ss
|
|
766
|
-
* (bs * qn * (kRl + kRr) - kNl * tc * (bs * ss + kRl * kRr))
|
|
767
|
-
)
|
|
768
|
-
c7 = -144 * bs**2 * ss * kNl * tc * (kRl + kRr)
|
|
769
|
-
return (
|
|
770
|
-
c1 * x**6 + c2 * x**5 + c3 * x**4 + c4 * x**3 + c5 * x**2 + c6 * x + c7
|
|
771
|
-
)
|
|
772
|
-
|
|
773
|
-
# Find root
|
|
774
|
-
lC = brentq(polynomial, a / 1000, 999 / 1000 * a)
|
|
775
|
-
|
|
776
|
-
return lC
|
|
777
|
-
|
|
778
|
-
def set_touchdown_attributes(self, L, a, cf, phi, ratio):
|
|
779
|
-
"""Set class attributes for touchdown consideration"""
|
|
780
|
-
self.set_columnlength(L)
|
|
781
|
-
self.set_cracklength(a)
|
|
782
|
-
self.set_phi(phi)
|
|
783
|
-
self.set_tc(cf)
|
|
784
|
-
self.set_stiffness_ratio(ratio)
|
|
785
|
-
|
|
786
|
-
def calc_touchdown_mode(self):
|
|
787
|
-
"""Calculate touchdown-mode from thresholds"""
|
|
788
|
-
if self.touchdown:
|
|
789
|
-
# Calculate stage transitions
|
|
790
|
-
self.a1 = self.calc_a1()
|
|
791
|
-
self.a2 = self.calc_a2()
|
|
792
|
-
# Assign stage
|
|
793
|
-
if self.a <= self.a1:
|
|
794
|
-
mode = "A"
|
|
795
|
-
elif self.a1 < self.a <= self.a2:
|
|
796
|
-
mode = "B"
|
|
797
|
-
elif self.a2 < self.a:
|
|
798
|
-
mode = "C"
|
|
799
|
-
self.mode = mode
|
|
800
|
-
else:
|
|
801
|
-
self.mode = "A"
|
|
802
|
-
|
|
803
|
-
def calc_touchdown_length(self):
|
|
804
|
-
"""Calculate touchdown length"""
|
|
805
|
-
if self.mode in ["A"]:
|
|
806
|
-
self.td = self.calc_lA()
|
|
807
|
-
elif self.mode in ["B"]:
|
|
808
|
-
self.td = self.calc_lB()
|
|
809
|
-
elif self.mode in ["C"]:
|
|
810
|
-
self.td = self.calc_lC()
|
|
811
|
-
|
|
812
|
-
def calc_touchdown_system(self, L, a, cf, phi, ratio=1000):
|
|
813
|
-
"""Calculate touchdown"""
|
|
814
|
-
self.set_touchdown_attributes(L, a, cf, phi, ratio)
|
|
815
|
-
self.calc_touchdown_mode()
|
|
816
|
-
self.calc_touchdown_length()
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
class SolutionMixin:
|
|
820
|
-
"""
|
|
821
|
-
Mixin for the solution of boundary value problems.
|
|
822
|
-
|
|
823
|
-
Provides methods for the assembly of the system of equations
|
|
824
|
-
and for the computation of the free constants.
|
|
825
|
-
"""
|
|
826
|
-
|
|
827
|
-
def bc(self, z, k=False, pos="mid"):
|
|
828
|
-
"""
|
|
829
|
-
Provide equations for free (pst) or infinite (skiers) ends.
|
|
830
|
-
|
|
831
|
-
Arguments
|
|
832
|
-
---------
|
|
833
|
-
z : ndarray
|
|
834
|
-
Solution vector (6x1) at a certain position x.
|
|
835
|
-
l : float, optional
|
|
836
|
-
Length of the segment in consideration. Default is zero.
|
|
837
|
-
k : boolean
|
|
838
|
-
Indicates whether segment has foundation(True) or not (False).
|
|
839
|
-
Default is False.
|
|
840
|
-
pos : {'left', 'mid', 'right', 'l', 'm', 'r'}, optional
|
|
841
|
-
Determines whether the segement under consideration
|
|
842
|
-
is a left boundary segement (left, l), one of the
|
|
843
|
-
center segement (mid, m), or a right boundary
|
|
844
|
-
segement (right, r). Default is 'mid'.
|
|
845
|
-
|
|
846
|
-
Returns
|
|
847
|
-
-------
|
|
848
|
-
bc : ndarray
|
|
849
|
-
Boundary condition vector (lenght 3) at position x.
|
|
850
|
-
"""
|
|
851
|
-
|
|
852
|
-
# Set boundary conditions for PST-systems
|
|
853
|
-
if self.system in ["pst-", "-pst"]:
|
|
854
|
-
if not k:
|
|
855
|
-
if self.mode in ["A"]:
|
|
856
|
-
# Free end
|
|
857
|
-
bc = np.array([self.N(z), self.M(z), self.V(z)])
|
|
858
|
-
elif self.mode in ["B"] and pos in ["r", "right"]:
|
|
859
|
-
# Touchdown right
|
|
860
|
-
bc = np.array([self.N(z), self.M(z), self.w(z)])
|
|
861
|
-
elif self.mode in ["B"] and pos in ["l", "left"]: # Kann dieser Block
|
|
862
|
-
# Touchdown left # verschwinden? Analog zu 'A'
|
|
863
|
-
bc = np.array([self.N(z), self.M(z), self.w(z)])
|
|
864
|
-
elif self.mode in ["C"] and pos in ["r", "right"]:
|
|
865
|
-
# Spring stiffness
|
|
866
|
-
kR = self.substitute_stiffness(self.a - self.td, "rested", "rot")
|
|
867
|
-
# Touchdown right
|
|
868
|
-
bc = np.array([self.N(z), self.M(z) + kR * self.psi(z), self.w(z)])
|
|
869
|
-
elif self.mode in ["C"] and pos in ["l", "left"]:
|
|
870
|
-
# Spring stiffness
|
|
871
|
-
kR = self.substitute_stiffness(self.a - self.td, "rested", "rot")
|
|
872
|
-
# Touchdown left
|
|
873
|
-
bc = np.array([self.N(z), self.M(z) - kR * self.psi(z), self.w(z)])
|
|
874
|
-
else:
|
|
875
|
-
# Free end
|
|
876
|
-
bc = np.array([self.N(z), self.M(z), self.V(z)])
|
|
877
|
-
# Set boundary conditions for PST-systems with vertical faces
|
|
878
|
-
elif self.system in ["-vpst", "vpst-"]:
|
|
879
|
-
bc = np.array([self.N(z), self.M(z), self.V(z)])
|
|
880
|
-
# Set boundary conditions for SKIER-systems
|
|
881
|
-
elif self.system in ["skier", "skiers"]:
|
|
882
|
-
# Infinite end (vanishing complementary solution)
|
|
883
|
-
bc = np.array([self.u(z, z0=0), self.w(z), self.psi(z)])
|
|
884
|
-
# Set boundary conditions for substitute spring calculus
|
|
885
|
-
elif self.system in ["rot", "trans"]:
|
|
886
|
-
bc = np.array([self.N(z), self.M(z), self.V(z)])
|
|
887
|
-
else:
|
|
888
|
-
raise ValueError(
|
|
889
|
-
f"Boundary conditions not defined for system of type {self.system}."
|
|
890
|
-
)
|
|
891
|
-
|
|
892
|
-
return bc
|
|
893
|
-
|
|
894
|
-
def eqs(self, zl, zr, k=False, pos="mid"):
|
|
895
|
-
"""
|
|
896
|
-
Provide boundary or transmission conditions for beam segments.
|
|
897
|
-
|
|
898
|
-
Arguments
|
|
899
|
-
---------
|
|
900
|
-
zl : ndarray
|
|
901
|
-
Solution vector (6x1) at left end of beam segement.
|
|
902
|
-
zr : ndarray
|
|
903
|
-
Solution vector (6x1) at right end of beam segement.
|
|
904
|
-
k : boolean
|
|
905
|
-
Indicates whether segment has foundation(True) or not (False).
|
|
906
|
-
Default is False.
|
|
907
|
-
pos: {'left', 'mid', 'right', 'l', 'm', 'r'}, optional
|
|
908
|
-
Determines whether the segement under consideration
|
|
909
|
-
is a left boundary segement (left, l), one of the
|
|
910
|
-
center segement (mid, m), or a right boundary
|
|
911
|
-
segement (right, r). Default is 'mid'.
|
|
912
|
-
|
|
913
|
-
Returns
|
|
914
|
-
-------
|
|
915
|
-
eqs : ndarray
|
|
916
|
-
Vector (of length 9) of boundary conditions (3) and
|
|
917
|
-
transmission conditions (6) for boundary segements
|
|
918
|
-
or vector of transmission conditions (of length 6+6)
|
|
919
|
-
for center segments.
|
|
920
|
-
"""
|
|
921
|
-
if pos in ("l", "left"):
|
|
922
|
-
eqs = np.array(
|
|
923
|
-
[
|
|
924
|
-
self.bc(zl, k, pos)[0], # Left boundary condition
|
|
925
|
-
self.bc(zl, k, pos)[1], # Left boundary condition
|
|
926
|
-
self.bc(zl, k, pos)[2], # Left boundary condition
|
|
927
|
-
self.u(zr, z0=0), # ui(xi = li)
|
|
928
|
-
self.w(zr), # wi(xi = li)
|
|
929
|
-
self.psi(zr), # psii(xi = li)
|
|
930
|
-
self.N(zr), # Ni(xi = li)
|
|
931
|
-
self.M(zr), # Mi(xi = li)
|
|
932
|
-
self.V(zr),
|
|
933
|
-
]
|
|
934
|
-
) # Vi(xi = li)
|
|
935
|
-
elif pos in ("m", "mid"):
|
|
936
|
-
eqs = np.array(
|
|
937
|
-
[
|
|
938
|
-
-self.u(zl, z0=0), # -ui(xi = 0)
|
|
939
|
-
-self.w(zl), # -wi(xi = 0)
|
|
940
|
-
-self.psi(zl), # -psii(xi = 0)
|
|
941
|
-
-self.N(zl), # -Ni(xi = 0)
|
|
942
|
-
-self.M(zl), # -Mi(xi = 0)
|
|
943
|
-
-self.V(zl), # -Vi(xi = 0)
|
|
944
|
-
self.u(zr, z0=0), # ui(xi = li)
|
|
945
|
-
self.w(zr), # wi(xi = li)
|
|
946
|
-
self.psi(zr), # psii(xi = li)
|
|
947
|
-
self.N(zr), # Ni(xi = li)
|
|
948
|
-
self.M(zr), # Mi(xi = li)
|
|
949
|
-
self.V(zr),
|
|
950
|
-
]
|
|
951
|
-
) # Vi(xi = li)
|
|
952
|
-
elif pos in ("r", "right"):
|
|
953
|
-
eqs = np.array(
|
|
954
|
-
[
|
|
955
|
-
-self.u(zl, z0=0), # -ui(xi = 0)
|
|
956
|
-
-self.w(zl), # -wi(xi = 0)
|
|
957
|
-
-self.psi(zl), # -psii(xi = 0)
|
|
958
|
-
-self.N(zl), # -Ni(xi = 0)
|
|
959
|
-
-self.M(zl), # -Mi(xi = 0)
|
|
960
|
-
-self.V(zl), # -Vi(xi = 0)
|
|
961
|
-
self.bc(zr, k, pos)[0], # Right boundary condition
|
|
962
|
-
self.bc(zr, k, pos)[1], # Right boundary condition
|
|
963
|
-
self.bc(zr, k, pos)[2],
|
|
964
|
-
]
|
|
965
|
-
) # Right boundary condition
|
|
966
|
-
else:
|
|
967
|
-
raise ValueError(
|
|
968
|
-
(
|
|
969
|
-
f"Invalid position argument {pos} given. "
|
|
970
|
-
"Valid segment positions are l, m, and r, "
|
|
971
|
-
"or left, mid and right."
|
|
972
|
-
)
|
|
973
|
-
)
|
|
974
|
-
return eqs
|
|
975
|
-
|
|
976
|
-
def calc_segments(
|
|
977
|
-
self,
|
|
978
|
-
li: list[float] | list[int] | bool = False,
|
|
979
|
-
mi: list[float] | list[int] | bool = False,
|
|
980
|
-
ki: list[bool] | bool = False,
|
|
981
|
-
k0: list[bool] | bool = False,
|
|
982
|
-
L: float = 1e4,
|
|
983
|
-
a: float = 0,
|
|
984
|
-
m: float = 0,
|
|
985
|
-
phi: float = 0,
|
|
986
|
-
cf: float = 0.5,
|
|
987
|
-
ratio: float = 1000,
|
|
988
|
-
**kwargs,
|
|
989
|
-
):
|
|
990
|
-
"""
|
|
991
|
-
Assemble lists defining the segments.
|
|
992
|
-
|
|
993
|
-
This includes length (li), foundation (ki, k0), and skier
|
|
994
|
-
weight (mi).
|
|
995
|
-
|
|
996
|
-
Arguments
|
|
997
|
-
---------
|
|
998
|
-
li : squence, optional
|
|
999
|
-
List of lengths of segements(mm). Used for system 'skiers'.
|
|
1000
|
-
mi : squence, optional
|
|
1001
|
-
List of skier weigths (kg) at segement boundaries. Used for
|
|
1002
|
-
system 'skiers'.
|
|
1003
|
-
ki : squence, optional
|
|
1004
|
-
List of one bool per segement indicating whether segement
|
|
1005
|
-
has foundation (True) or not (False) in the cracked state.
|
|
1006
|
-
Used for system 'skiers'.
|
|
1007
|
-
k0 : squence, optional
|
|
1008
|
-
List of one bool per segement indicating whether segement
|
|
1009
|
-
has foundation(True) or not (False) in the uncracked state.
|
|
1010
|
-
Used for system 'skiers'.
|
|
1011
|
-
L : float, optional
|
|
1012
|
-
Total length of model (mm). Used for systems 'pst-', '-pst',
|
|
1013
|
-
'vpst-', '-vpst', and 'skier'.
|
|
1014
|
-
a : float, optional
|
|
1015
|
-
Crack length (mm). Used for systems 'pst-', '-pst', 'pst-',
|
|
1016
|
-
'-pst', and 'skier'.
|
|
1017
|
-
phi : float, optional
|
|
1018
|
-
Inclination (degree).
|
|
1019
|
-
m : float, optional
|
|
1020
|
-
Weight of skier (kg) in the axial center of the model.
|
|
1021
|
-
Used for system 'skier'.
|
|
1022
|
-
cf : float, optional
|
|
1023
|
-
Collapse factor. Ratio of the crack height to the uncollapsed
|
|
1024
|
-
weak-layer height. Used for systems 'pst-', '-pst'. Default is 0.5.
|
|
1025
|
-
ratio : float, optional
|
|
1026
|
-
Stiffness ratio between collapsed and uncollapsed weak layer.
|
|
1027
|
-
Default is 1000.
|
|
1028
|
-
|
|
1029
|
-
Returns
|
|
1030
|
-
-------
|
|
1031
|
-
segments : dict
|
|
1032
|
-
Dictionary with lists of touchdown booleans (tdi), segement
|
|
1033
|
-
lengths (li), skier weights (mi), and foundation booleans
|
|
1034
|
-
in the cracked (ki) and uncracked (k0) configurations.
|
|
1035
|
-
"""
|
|
1036
|
-
|
|
1037
|
-
_ = kwargs # Unused arguments
|
|
1038
|
-
|
|
1039
|
-
# Precompute touchdown properties
|
|
1040
|
-
self.calc_touchdown_system(L=L, a=a, cf=cf, phi=phi, ratio=ratio)
|
|
1041
|
-
|
|
1042
|
-
# Assemble list defining the segments
|
|
1043
|
-
if self.system == "skiers":
|
|
1044
|
-
li = np.array(li) # Segment lengths
|
|
1045
|
-
mi = np.array(mi) # Skier weights
|
|
1046
|
-
ki = np.array(ki) # Crack
|
|
1047
|
-
k0 = np.array(k0) # No crack
|
|
1048
|
-
elif self.system == "pst-":
|
|
1049
|
-
li = np.array([L - self.a, self.td]) # Segment lengths
|
|
1050
|
-
mi = np.array([0]) # Skier weights
|
|
1051
|
-
ki = np.array([True, False]) # Crack
|
|
1052
|
-
k0 = np.array([True, True]) # No crack
|
|
1053
|
-
elif self.system == "-pst":
|
|
1054
|
-
li = np.array([self.td, L - self.a]) # Segment lengths
|
|
1055
|
-
mi = np.array([0]) # Skier weights
|
|
1056
|
-
ki = np.array([False, True]) # Crack
|
|
1057
|
-
k0 = np.array([True, True]) # No crack
|
|
1058
|
-
elif self.system == "vpst-":
|
|
1059
|
-
li = np.array([L - a, a]) # Segment lengths
|
|
1060
|
-
mi = np.array([0]) # Skier weights
|
|
1061
|
-
ki = np.array([True, False]) # Crack
|
|
1062
|
-
k0 = np.array([True, True]) # No crack
|
|
1063
|
-
elif self.system == "-vpst":
|
|
1064
|
-
li = np.array([a, L - a]) # Segment lengths
|
|
1065
|
-
mi = np.array([0]) # Skier weights
|
|
1066
|
-
ki = np.array([False, True]) # Crack
|
|
1067
|
-
k0 = np.array([True, True]) # No crack
|
|
1068
|
-
elif self.system == "skier":
|
|
1069
|
-
lb = (L - self.a) / 2 # Half bedded length
|
|
1070
|
-
lf = self.a / 2 # Half free length
|
|
1071
|
-
li = np.array([lb, lf, lf, lb]) # Segment lengths
|
|
1072
|
-
mi = np.array([0, m, 0]) # Skier weights
|
|
1073
|
-
ki = np.array([True, False, False, True]) # Crack
|
|
1074
|
-
k0 = np.array([True, True, True, True]) # No crack
|
|
1075
|
-
else:
|
|
1076
|
-
raise ValueError(f"System {self.system} is not implemented.")
|
|
1077
|
-
|
|
1078
|
-
# Fill dictionary
|
|
1079
|
-
segments = {
|
|
1080
|
-
"nocrack": {"li": li, "mi": mi, "ki": k0},
|
|
1081
|
-
"crack": {"li": li, "mi": mi, "ki": ki},
|
|
1082
|
-
"both": {"li": li, "mi": mi, "ki": ki, "k0": k0},
|
|
1083
|
-
}
|
|
1084
|
-
return segments
|
|
1085
|
-
|
|
1086
|
-
def assemble_and_solve(self, phi, li, mi, ki):
|
|
1087
|
-
"""
|
|
1088
|
-
Compute free constants for arbitrary beam assembly.
|
|
1089
|
-
|
|
1090
|
-
Assemble LHS from supported and unsupported segments in the form
|
|
1091
|
-
[ ] [ zh1 0 0 ... 0 0 0 ][ ] [ ] [ ] left
|
|
1092
|
-
[ ] [ zh1 zh2 0 ... 0 0 0 ][ ] [ ] [ ] mid
|
|
1093
|
-
[ ] [ 0 zh2 zh3 ... 0 0 0 ][ ] [ ] [ ] mid
|
|
1094
|
-
[z0] = [ ... ... ... ... ... ... ... ][ C ] + [ zp ] = [ rhs ] mid
|
|
1095
|
-
[ ] [ 0 0 0 ... zhL zhM 0 ][ ] [ ] [ ] mid
|
|
1096
|
-
[ ] [ 0 0 0 ... 0 zhM zhN ][ ] [ ] [ ] mid
|
|
1097
|
-
[ ] [ 0 0 0 ... 0 0 zhN ][ ] [ ] [ ] right
|
|
1098
|
-
and solve for constants C.
|
|
1099
|
-
|
|
1100
|
-
Arguments
|
|
1101
|
-
---------
|
|
1102
|
-
phi : float
|
|
1103
|
-
Inclination (degrees).
|
|
1104
|
-
li : ndarray
|
|
1105
|
-
List of lengths of segements (mm).
|
|
1106
|
-
mi : ndarray
|
|
1107
|
-
List of skier weigths (kg) at segement boundaries.
|
|
1108
|
-
ki : ndarray
|
|
1109
|
-
List of one bool per segement indicating whether segement
|
|
1110
|
-
has foundation (True) or not (False).
|
|
1111
|
-
|
|
1112
|
-
Returns
|
|
1113
|
-
-------
|
|
1114
|
-
C : ndarray
|
|
1115
|
-
Matrix(6xN) of solution constants for a system of N
|
|
1116
|
-
segements. Columns contain the 6 constants of each segement.
|
|
1117
|
-
"""
|
|
1118
|
-
# --- CATCH ERRORS ----------------------------------------------------
|
|
1119
|
-
|
|
1120
|
-
# No foundation
|
|
1121
|
-
if not any(ki):
|
|
1122
|
-
raise ValueError("Provide at least one supported segment.")
|
|
1123
|
-
# Mismatch of number of segements and transisions
|
|
1124
|
-
if len(li) != len(ki) or len(li) - 1 != len(mi):
|
|
1125
|
-
raise ValueError(
|
|
1126
|
-
"Make sure len(li)=N, len(ki)=N, and "
|
|
1127
|
-
"len(mi)=N-1 for a system of N segments."
|
|
1128
|
-
)
|
|
1129
|
-
|
|
1130
|
-
if self.system not in ["pst-", "-pst", "vpst-", "-vpst", "rot", "trans"]:
|
|
1131
|
-
# Boundary segments must be on foundation for infinite BCs
|
|
1132
|
-
if not all([ki[0], ki[-1]]):
|
|
1133
|
-
raise ValueError(
|
|
1134
|
-
"Provide supported boundary segments in "
|
|
1135
|
-
"order to account for infinite extensions."
|
|
1136
|
-
)
|
|
1137
|
-
# Make sure infinity boundary conditions are far enough from skiers
|
|
1138
|
-
if li[0] < 5e3 or li[-1] < 5e3:
|
|
1139
|
-
print(
|
|
1140
|
-
(
|
|
1141
|
-
"WARNING: Boundary segments are short. Make sure "
|
|
1142
|
-
"the complementary solution has decayed to the "
|
|
1143
|
-
"boundaries."
|
|
1144
|
-
)
|
|
1145
|
-
)
|
|
1146
|
-
|
|
1147
|
-
# --- PREPROCESSING ---------------------------------------------------
|
|
1148
|
-
|
|
1149
|
-
# Determine size of linear system of equations
|
|
1150
|
-
nS = len(li) # Number of beam segments
|
|
1151
|
-
|
|
1152
|
-
nDOF = 6 # Number of free constants per segment
|
|
1153
|
-
|
|
1154
|
-
# Add dummy segment if only one segment provided
|
|
1155
|
-
if nS == 1:
|
|
1156
|
-
li.append(0)
|
|
1157
|
-
ki.append(True)
|
|
1158
|
-
mi.append(0)
|
|
1159
|
-
nS = 2
|
|
1160
|
-
|
|
1161
|
-
# Assemble position vector
|
|
1162
|
-
pi = np.full(nS, "m")
|
|
1163
|
-
pi[0], pi[-1] = "l", "r"
|
|
1164
|
-
|
|
1165
|
-
# Initialize matrices
|
|
1166
|
-
zh0 = np.zeros([nS * 6, nS * nDOF])
|
|
1167
|
-
zp0 = np.zeros([nS * 6, 1])
|
|
1168
|
-
rhs = np.zeros([nS * 6, 1])
|
|
1169
|
-
|
|
1170
|
-
# --- ASSEMBLE LINEAR SYSTEM OF EQUATIONS -----------------------------
|
|
1171
|
-
|
|
1172
|
-
# Loop through segments to assemble left-hand side
|
|
1173
|
-
for i in range(nS):
|
|
1174
|
-
# Length, foundation and position of segment i
|
|
1175
|
-
l, k, pos = li[i], ki[i], pi[i]
|
|
1176
|
-
# Transmission conditions at left and right segment ends
|
|
1177
|
-
zhi = self.eqs(
|
|
1178
|
-
zl=self.zh(x=0, l=l, bed=k), zr=self.zh(x=l, l=l, bed=k), k=k, pos=pos
|
|
1179
|
-
)
|
|
1180
|
-
zpi = self.eqs(
|
|
1181
|
-
zl=self.zp(x=0, phi=phi, bed=k),
|
|
1182
|
-
zr=self.zp(x=l, phi=phi, bed=k),
|
|
1183
|
-
k=k,
|
|
1184
|
-
pos=pos,
|
|
1185
|
-
)
|
|
1186
|
-
# Rows for left-hand side assembly
|
|
1187
|
-
start = 0 if i == 0 else 3
|
|
1188
|
-
stop = 6 if i == nS - 1 else 9
|
|
1189
|
-
# Assemble left-hand side
|
|
1190
|
-
zh0[(6 * i - start) : (6 * i + stop), i * nDOF : (i + 1) * nDOF] = zhi
|
|
1191
|
-
zp0[(6 * i - start) : (6 * i + stop)] += zpi
|
|
1192
|
-
|
|
1193
|
-
# Loop through loads to assemble right-hand side
|
|
1194
|
-
for i, m in enumerate(mi, start=1):
|
|
1195
|
-
# Get skier loads
|
|
1196
|
-
Fn, Ft = self.get_skier_load(m, phi)
|
|
1197
|
-
# Right-hand side for transmission from segment i-1 to segment i
|
|
1198
|
-
rhs[6 * i : 6 * i + 3] = np.vstack([Ft, -Ft * self.h / 2, Fn])
|
|
1199
|
-
# Set rhs so that complementary integral vanishes at boundaries
|
|
1200
|
-
if self.system not in ["pst-", "-pst", "rested"]:
|
|
1201
|
-
rhs[:3] = self.bc(self.zp(x=0, phi=phi, bed=ki[0]))
|
|
1202
|
-
rhs[-3:] = self.bc(self.zp(x=li[-1], phi=phi, bed=ki[-1]))
|
|
1203
|
-
|
|
1204
|
-
# Set rhs for vertical faces
|
|
1205
|
-
if self.system in ["vpst-", "-vpst"]:
|
|
1206
|
-
# Calculate center of gravity and mass of
|
|
1207
|
-
# added or cut off slab segement
|
|
1208
|
-
xs, zs, m = calc_vertical_bc_center_of_gravity(self.slab, phi)
|
|
1209
|
-
# Convert slope angle to radians
|
|
1210
|
-
phi = np.deg2rad(phi)
|
|
1211
|
-
# Translate inbto section forces and moments
|
|
1212
|
-
N = -self.g * m * np.sin(phi)
|
|
1213
|
-
M = -self.g * m * (xs * np.cos(phi) + zs * np.sin(phi))
|
|
1214
|
-
V = self.g * m * np.cos(phi)
|
|
1215
|
-
# Add to right-hand side
|
|
1216
|
-
rhs[:3] = np.vstack([N, M, V]) # left end
|
|
1217
|
-
rhs[-3:] = np.vstack([N, M, V]) # right end
|
|
1218
|
-
|
|
1219
|
-
# Loop through segments to set touchdown conditions at rhs
|
|
1220
|
-
for i in range(nS):
|
|
1221
|
-
# Length, foundation and position of segment i
|
|
1222
|
-
l, k, pos = li[i], ki[i], pi[i]
|
|
1223
|
-
# Set displacement BC in stage B
|
|
1224
|
-
if not k and bool(self.mode in ["B"]):
|
|
1225
|
-
if i == 0:
|
|
1226
|
-
rhs[:3] = np.vstack([0, 0, self.tc])
|
|
1227
|
-
if i == (nS - 1):
|
|
1228
|
-
rhs[-3:] = np.vstack([0, 0, self.tc])
|
|
1229
|
-
# Set normal force and displacement BC for stage C
|
|
1230
|
-
if not k and bool(self.mode in ["C"]):
|
|
1231
|
-
N = self.calc_qt() * (self.a - self.td)
|
|
1232
|
-
if i == 0:
|
|
1233
|
-
rhs[:3] = np.vstack([-N, 0, self.tc])
|
|
1234
|
-
if i == (nS - 1):
|
|
1235
|
-
rhs[-3:] = np.vstack([N, 0, self.tc])
|
|
1236
|
-
|
|
1237
|
-
# Rhs for substitute spring stiffness
|
|
1238
|
-
if self.system in ["rot"]:
|
|
1239
|
-
# apply arbitrary moment of 1 at left boundary
|
|
1240
|
-
rhs = rhs * 0
|
|
1241
|
-
rhs[1] = 1
|
|
1242
|
-
if self.system in ["trans"]:
|
|
1243
|
-
# apply arbitrary force of 1 at left boundary
|
|
1244
|
-
rhs = rhs * 0
|
|
1245
|
-
rhs[2] = 1
|
|
1246
|
-
|
|
1247
|
-
# --- SOLVE -----------------------------------------------------------
|
|
1248
|
-
|
|
1249
|
-
# Solve z0 = zh0*C + zp0 = rhs for constants, i.e. zh0*C = rhs - zp0
|
|
1250
|
-
C = np.linalg.solve(zh0, rhs - zp0)
|
|
1251
|
-
# Sort (nDOF = 6) constants for each segment into columns of a matrix
|
|
1252
|
-
return C.reshape([-1, nDOF]).T
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
class AnalysisMixin:
|
|
1256
|
-
"""
|
|
1257
|
-
Mixin for the analysis of model outputs.
|
|
1258
|
-
|
|
1259
|
-
Provides methods for the analysis of layered slabs on compliant
|
|
1260
|
-
elastic foundations.
|
|
1261
|
-
"""
|
|
1262
|
-
|
|
1263
|
-
def rasterize_solution(
|
|
1264
|
-
self,
|
|
1265
|
-
C: np.ndarray,
|
|
1266
|
-
phi: float,
|
|
1267
|
-
li: list[float] | bool,
|
|
1268
|
-
ki: list[bool] | bool,
|
|
1269
|
-
num: int = 250,
|
|
1270
|
-
**kwargs,
|
|
1271
|
-
):
|
|
1272
|
-
"""
|
|
1273
|
-
Compute rasterized solution vector.
|
|
1274
|
-
|
|
1275
|
-
Arguments
|
|
1276
|
-
---------
|
|
1277
|
-
C : ndarray
|
|
1278
|
-
Vector of free constants.
|
|
1279
|
-
phi : float
|
|
1280
|
-
Inclination (degrees).
|
|
1281
|
-
li : ndarray
|
|
1282
|
-
List of segment lengths (mm).
|
|
1283
|
-
ki : ndarray
|
|
1284
|
-
List of booleans indicating whether segment lies on
|
|
1285
|
-
a foundation or not.
|
|
1286
|
-
num : int
|
|
1287
|
-
Number of grid points.
|
|
1288
|
-
|
|
1289
|
-
Returns
|
|
1290
|
-
-------
|
|
1291
|
-
xq : ndarray
|
|
1292
|
-
Grid point x-coordinates at which solution vector
|
|
1293
|
-
is discretized.
|
|
1294
|
-
zq : ndarray
|
|
1295
|
-
Matrix with solution vectors as colums at grid
|
|
1296
|
-
points xq.
|
|
1297
|
-
xb : ndarray
|
|
1298
|
-
Grid point x-coordinates that lie on a foundation.
|
|
1299
|
-
"""
|
|
1300
|
-
# Unused arguments
|
|
1301
|
-
_ = kwargs
|
|
1302
|
-
|
|
1303
|
-
# Drop zero-length segments
|
|
1304
|
-
li = abs(li)
|
|
1305
|
-
isnonzero = li > 0
|
|
1306
|
-
C, ki, li = C[:, isnonzero], ki[isnonzero], li[isnonzero]
|
|
1307
|
-
|
|
1308
|
-
# Compute number of plot points per segment (+1 for last segment)
|
|
1309
|
-
nq = np.ceil(li / li.sum() * num).astype("int")
|
|
1310
|
-
nq[-1] += 1
|
|
1311
|
-
|
|
1312
|
-
# Provide cumulated length and plot point lists
|
|
1313
|
-
lic = np.insert(np.cumsum(li), 0, 0)
|
|
1314
|
-
nqc = np.insert(np.cumsum(nq), 0, 0)
|
|
1315
|
-
|
|
1316
|
-
# Initialize arrays
|
|
1317
|
-
issupported = np.full(nq.sum(), True)
|
|
1318
|
-
xq = np.full(nq.sum(), np.nan)
|
|
1319
|
-
zq = np.full([6, xq.size], np.nan)
|
|
1320
|
-
|
|
1321
|
-
# Loop through segments
|
|
1322
|
-
for i, l in enumerate(li):
|
|
1323
|
-
# Get local x-coordinates of segment i
|
|
1324
|
-
xi = np.linspace(0, l, num=nq[i], endpoint=(i == li.size - 1)) # pylint: disable=superfluous-parens
|
|
1325
|
-
# Compute start and end coordinates of segment i
|
|
1326
|
-
x0 = lic[i]
|
|
1327
|
-
# Assemble global coordinate vector
|
|
1328
|
-
xq[nqc[i] : nqc[i + 1]] = x0 + xi
|
|
1329
|
-
# Mask coordinates not on foundation (including endpoints)
|
|
1330
|
-
if not ki[i]:
|
|
1331
|
-
issupported[nqc[i] : nqc[i + 1]] = False
|
|
1332
|
-
# Compute segment solution
|
|
1333
|
-
zi = self.z(xi, C[:, [i]], l, phi, ki[i])
|
|
1334
|
-
# Assemble global solution matrix
|
|
1335
|
-
zq[:, nqc[i] : nqc[i + 1]] = zi
|
|
1336
|
-
|
|
1337
|
-
# Make sure cracktips are included
|
|
1338
|
-
transmissionbool = [ki[j] or ki[j + 1] for j, _ in enumerate(ki[:-1])]
|
|
1339
|
-
for i, truefalse in enumerate(transmissionbool, start=1):
|
|
1340
|
-
issupported[nqc[i]] = truefalse
|
|
1341
|
-
|
|
1342
|
-
# Assemble vector of coordinates on foundation
|
|
1343
|
-
xb = np.full(nq.sum(), np.nan)
|
|
1344
|
-
xb[issupported] = xq[issupported]
|
|
1345
|
-
|
|
1346
|
-
return xq, zq, xb
|
|
1347
|
-
|
|
1348
|
-
def ginc(self, C0, C1, phi, li, ki, k0, **kwargs):
|
|
1349
|
-
"""
|
|
1350
|
-
Compute incremental energy relase rate of of all cracks.
|
|
1351
|
-
|
|
1352
|
-
Arguments
|
|
1353
|
-
---------
|
|
1354
|
-
C0 : ndarray
|
|
1355
|
-
Free constants of uncracked solution.
|
|
1356
|
-
C1 : ndarray
|
|
1357
|
-
Free constants of cracked solution.
|
|
1358
|
-
phi : float
|
|
1359
|
-
Inclination (degress).
|
|
1360
|
-
li : ndarray
|
|
1361
|
-
List of segment lengths.
|
|
1362
|
-
ki : ndarray
|
|
1363
|
-
List of booleans indicating whether segment lies on
|
|
1364
|
-
a foundation or not in the cracked configuration.
|
|
1365
|
-
k0 : ndarray
|
|
1366
|
-
List of booleans indicating whether segment lies on
|
|
1367
|
-
a foundation or not in the uncracked configuration.
|
|
1368
|
-
|
|
1369
|
-
Returns
|
|
1370
|
-
-------
|
|
1371
|
-
ndarray
|
|
1372
|
-
List of total, mode I, and mode II energy release rates.
|
|
1373
|
-
"""
|
|
1374
|
-
# Unused arguments
|
|
1375
|
-
_ = kwargs
|
|
1376
|
-
|
|
1377
|
-
# Make sure inputs are np.arrays
|
|
1378
|
-
li, ki, k0 = np.array(li), np.array(ki), np.array(k0)
|
|
1379
|
-
|
|
1380
|
-
# Reduce inputs to segments with crack advance
|
|
1381
|
-
iscrack = k0 & ~ki
|
|
1382
|
-
C0, C1, li = C0[:, iscrack], C1[:, iscrack], li[iscrack]
|
|
1383
|
-
|
|
1384
|
-
# Compute total crack lenght and initialize outputs
|
|
1385
|
-
da = li.sum() if li.sum() > 0 else np.nan
|
|
1386
|
-
Ginc1, Ginc2 = 0, 0
|
|
1387
|
-
|
|
1388
|
-
# Loop through segments with crack advance
|
|
1389
|
-
for j, l in enumerate(li):
|
|
1390
|
-
# Uncracked (0) and cracked (1) solutions at integration points
|
|
1391
|
-
z0 = partial(self.z, C=C0[:, [j]], l=l, phi=phi, bed=True)
|
|
1392
|
-
z1 = partial(self.z, C=C1[:, [j]], l=l, phi=phi, bed=False)
|
|
1393
|
-
|
|
1394
|
-
# Mode I (1) and II (2) integrands at integration points
|
|
1395
|
-
int1 = partial(self.int1, z0=z0, z1=z1)
|
|
1396
|
-
int2 = partial(self.int2, z0=z0, z1=z1)
|
|
1397
|
-
|
|
1398
|
-
# Segement contributions to total crack opening integral
|
|
1399
|
-
Ginc1 += quad(int1, 0, l, epsabs=self.tol, epsrel=self.tol)[0] / (2 * da)
|
|
1400
|
-
Ginc2 += quad(int2, 0, l, epsabs=self.tol, epsrel=self.tol)[0] / (2 * da)
|
|
1401
|
-
|
|
1402
|
-
return np.array([Ginc1 + Ginc2, Ginc1, Ginc2]).flatten()
|
|
1403
|
-
|
|
1404
|
-
def gdif(self, C, phi, li, ki, unit="kJ/m^2", **kwargs):
|
|
1405
|
-
"""
|
|
1406
|
-
Compute differential energy release rate of all crack tips.
|
|
1407
|
-
|
|
1408
|
-
Arguments
|
|
1409
|
-
---------
|
|
1410
|
-
C : ndarray
|
|
1411
|
-
Free constants of the solution.
|
|
1412
|
-
phi : float
|
|
1413
|
-
Inclination (degress).
|
|
1414
|
-
li : ndarray
|
|
1415
|
-
List of segment lengths.
|
|
1416
|
-
ki : ndarray
|
|
1417
|
-
List of booleans indicating whether segment lies on
|
|
1418
|
-
a foundation or not in the cracked configuration.
|
|
1419
|
-
|
|
1420
|
-
Returns
|
|
1421
|
-
-------
|
|
1422
|
-
ndarray
|
|
1423
|
-
List of total, mode I, and mode II energy release rates.
|
|
1424
|
-
"""
|
|
1425
|
-
# Unused arguments
|
|
1426
|
-
_ = kwargs
|
|
1427
|
-
|
|
1428
|
-
# Get number and indices of segment transitions
|
|
1429
|
-
ntr = len(li) - 1
|
|
1430
|
-
itr = np.arange(ntr)
|
|
1431
|
-
|
|
1432
|
-
# Identify supported-free and free-supported transitions as crack tips
|
|
1433
|
-
iscracktip = [ki[j] != ki[j + 1] for j in range(ntr)]
|
|
1434
|
-
|
|
1435
|
-
# Transition indices of crack tips and total number of crack tips
|
|
1436
|
-
ict = itr[iscracktip]
|
|
1437
|
-
nct = len(ict)
|
|
1438
|
-
|
|
1439
|
-
# Initialize energy release rate array
|
|
1440
|
-
Gdif = np.zeros([3, nct])
|
|
1441
|
-
|
|
1442
|
-
# Compute energy relase rate of all crack tips
|
|
1443
|
-
for j, idx in enumerate(ict):
|
|
1444
|
-
# Solution at crack tip
|
|
1445
|
-
z = self.z(li[idx], C[:, [idx]], li[idx], phi, bed=ki[idx])
|
|
1446
|
-
# Mode I and II differential energy release rates
|
|
1447
|
-
Gdif[1:, j] = np.concatenate((self.Gi(z, unit=unit), self.Gii(z, unit=unit)))
|
|
1448
|
-
|
|
1449
|
-
# Sum mode I and II contributions
|
|
1450
|
-
Gdif[0, :] = Gdif[1, :] + Gdif[2, :]
|
|
1451
|
-
|
|
1452
|
-
# Adjust contributions for center cracks
|
|
1453
|
-
if nct > 1:
|
|
1454
|
-
avgmask = np.full(nct, True) # Initialize mask
|
|
1455
|
-
avgmask[[0, -1]] = ki[[0, -1]] # Do not weight edge cracks
|
|
1456
|
-
Gdif[:, avgmask] *= 0.5 # Weigth with half crack length
|
|
1457
|
-
|
|
1458
|
-
# Return total differential energy release rate of all crack tips
|
|
1459
|
-
return Gdif.sum(axis=1)
|
|
1460
|
-
|
|
1461
|
-
def get_zmesh(self, dz=2):
|
|
1462
|
-
"""
|
|
1463
|
-
Get z-coordinates of grid points and corresponding elastic properties.
|
|
1464
|
-
|
|
1465
|
-
Arguments
|
|
1466
|
-
---------
|
|
1467
|
-
dz : float, optional
|
|
1468
|
-
Element size along z-axis (mm). Default is 2 mm.
|
|
1469
|
-
|
|
1470
|
-
Returns
|
|
1471
|
-
-------
|
|
1472
|
-
mesh : ndarray
|
|
1473
|
-
Mesh along z-axis. Columns are a list of z-coordinates (mm) of
|
|
1474
|
-
grid points along z-axis with at least two grid points (top,
|
|
1475
|
-
bottom) per layer, Young's modulus of each grid point, shear
|
|
1476
|
-
modulus of each grid point, and Poisson's ratio of each grid
|
|
1477
|
-
point.
|
|
1478
|
-
"""
|
|
1479
|
-
# Get ply (layer) coordinates
|
|
1480
|
-
z = self.get_ply_coordinates()
|
|
1481
|
-
# Compute number of grid points per layer
|
|
1482
|
-
nlayer = np.ceil((z[1:] - z[:-1]) / dz).astype(np.int32) + 1
|
|
1483
|
-
# Calculate grid points as list of z-coordinates (mm)
|
|
1484
|
-
zi = np.hstack(
|
|
1485
|
-
[np.linspace(z[i], z[i + 1], n, endpoint=True) for i, n in enumerate(nlayer)]
|
|
1486
|
-
)
|
|
1487
|
-
# Get lists of corresponding elastic properties (E, nu, rho)
|
|
1488
|
-
si = np.repeat(self.slab[:, [2, 4, 0]], nlayer, axis=0)
|
|
1489
|
-
# Assemble mesh with columns (z, E, G, nu)
|
|
1490
|
-
return np.column_stack([zi, si])
|
|
1491
|
-
|
|
1492
|
-
def Sxx(self, Z, phi, dz=2, unit="kPa"):
|
|
1493
|
-
"""
|
|
1494
|
-
Compute axial normal stress in slab layers.
|
|
1495
|
-
|
|
1496
|
-
Arguments
|
|
1497
|
-
----------
|
|
1498
|
-
Z : ndarray
|
|
1499
|
-
Solution vector [u(x) u'(x) w(x) w'(x) psi(x), psi'(x)]^T
|
|
1500
|
-
phi : float
|
|
1501
|
-
Inclination (degrees). Counterclockwise positive.
|
|
1502
|
-
dz : float, optional
|
|
1503
|
-
Element size along z-axis (mm). Default is 2 mm.
|
|
1504
|
-
unit : {'kPa', 'MPa'}, optional
|
|
1505
|
-
Desired output unit. Default is 'kPa'.
|
|
1506
|
-
|
|
1507
|
-
Returns
|
|
1508
|
-
-------
|
|
1509
|
-
ndarray, float
|
|
1510
|
-
Axial slab normal stress in specified unit.
|
|
1511
|
-
"""
|
|
1512
|
-
# Unit conversion dict
|
|
1513
|
-
convert = {"kPa": 1e3, "MPa": 1}
|
|
1514
|
-
|
|
1515
|
-
# Get mesh along z-axis
|
|
1516
|
-
zmesh = self.get_zmesh(dz=dz)
|
|
1517
|
-
zi = zmesh[:, 0]
|
|
1518
|
-
rho = 1e-12 * zmesh[:, 3]
|
|
1519
|
-
|
|
1520
|
-
# Get dimensions of stress field (n rows, m columns)
|
|
1521
|
-
n = zmesh.shape[0]
|
|
1522
|
-
m = Z.shape[1]
|
|
1523
|
-
|
|
1524
|
-
# Initialize axial normal stress Sxx
|
|
1525
|
-
Sxx = np.zeros(shape=[n, m])
|
|
1526
|
-
|
|
1527
|
-
# Compute axial normal stress Sxx at grid points in MPa
|
|
1528
|
-
for i, (z, E, nu, _) in enumerate(zmesh):
|
|
1529
|
-
Sxx[i, :] = E / (1 - nu**2) * self.du_dx(Z, z)
|
|
1530
|
-
|
|
1531
|
-
# Calculate weight load at grid points and superimpose on stress field
|
|
1532
|
-
qt = -rho * self.g * np.sin(np.deg2rad(phi))
|
|
1533
|
-
for i, qi in enumerate(qt[:-1]):
|
|
1534
|
-
Sxx[i, :] += qi * (zi[i + 1] - zi[i])
|
|
1535
|
-
Sxx[-1, :] += qt[-1] * (zi[-1] - zi[-2])
|
|
1536
|
-
|
|
1537
|
-
# Return axial normal stress in specified unit
|
|
1538
|
-
return convert[unit] * Sxx
|
|
1539
|
-
|
|
1540
|
-
def Txz(self, Z, phi, dz=2, unit="kPa"):
|
|
1541
|
-
"""
|
|
1542
|
-
Compute shear stress in slab layers.
|
|
1543
|
-
|
|
1544
|
-
Arguments
|
|
1545
|
-
----------
|
|
1546
|
-
Z : ndarray
|
|
1547
|
-
Solution vector [u(x) u'(x) w(x) w'(x) psi(x), psi'(x)]^T
|
|
1548
|
-
phi : float
|
|
1549
|
-
Inclination (degrees). Counterclockwise positive.
|
|
1550
|
-
dz : float, optional
|
|
1551
|
-
Element size along z-axis (mm). Default is 2 mm.
|
|
1552
|
-
unit : {'kPa', 'MPa'}, optional
|
|
1553
|
-
Desired output unit. Default is 'kPa'.
|
|
1554
|
-
|
|
1555
|
-
Returns
|
|
1556
|
-
-------
|
|
1557
|
-
ndarray
|
|
1558
|
-
Shear stress at grid points in the slab in specified unit.
|
|
1559
|
-
"""
|
|
1560
|
-
# Unit conversion dict
|
|
1561
|
-
convert = {"kPa": 1e3, "MPa": 1}
|
|
1562
|
-
# Get mesh along z-axis
|
|
1563
|
-
zmesh = self.get_zmesh(dz=dz)
|
|
1564
|
-
zi = zmesh[:, 0]
|
|
1565
|
-
rho = 1e-12 * zmesh[:, 3]
|
|
1566
|
-
|
|
1567
|
-
# Get dimensions of stress field (n rows, m columns)
|
|
1568
|
-
n = zmesh.shape[0]
|
|
1569
|
-
m = Z.shape[1]
|
|
1570
|
-
|
|
1571
|
-
# Get second derivatives of centerline displacement u0 and
|
|
1572
|
-
# cross-section rotaiton psi of all grid points along the x-axis
|
|
1573
|
-
du0_dxdx = self.du0_dxdx(Z, phi)
|
|
1574
|
-
dpsi_dxdx = self.dpsi_dxdx(Z, phi)
|
|
1575
|
-
|
|
1576
|
-
# Initialize first derivative of axial normal stress sxx w.r.t. x
|
|
1577
|
-
dsxx_dx = np.zeros(shape=[n, m])
|
|
1578
|
-
|
|
1579
|
-
# Calculate first derivative of sxx at z-grid points
|
|
1580
|
-
for i, (z, E, nu, _) in enumerate(zmesh):
|
|
1581
|
-
dsxx_dx[i, :] = E / (1 - nu**2) * (du0_dxdx + z * dpsi_dxdx)
|
|
1582
|
-
|
|
1583
|
-
# Calculate weight load at grid points
|
|
1584
|
-
qt = -rho * self.g * np.sin(np.deg2rad(phi))
|
|
1585
|
-
|
|
1586
|
-
# Integrate -dsxx_dx along z and add cumulative weight load
|
|
1587
|
-
# to obtain shear stress Txz in MPa
|
|
1588
|
-
Txz = cumulative_trapezoid(dsxx_dx, zi, axis=0, initial=0)
|
|
1589
|
-
Txz += cumulative_trapezoid(qt, zi, initial=0)[:, None]
|
|
1590
|
-
|
|
1591
|
-
# Return shear stress Txz in specified unit
|
|
1592
|
-
return convert[unit] * Txz
|
|
1593
|
-
|
|
1594
|
-
def Szz(self, Z, phi, dz=2, unit="kPa"):
|
|
1595
|
-
"""
|
|
1596
|
-
Compute transverse normal stress in slab layers.
|
|
1597
|
-
|
|
1598
|
-
Arguments
|
|
1599
|
-
----------
|
|
1600
|
-
Z : ndarray
|
|
1601
|
-
Solution vector [u(x) u'(x) w(x) w'(x) psi(x), psi'(x)]^T
|
|
1602
|
-
phi : float
|
|
1603
|
-
Inclination (degrees). Counterclockwise positive.
|
|
1604
|
-
dz : float, optional
|
|
1605
|
-
Element size along z-axis (mm). Default is 2 mm.
|
|
1606
|
-
unit : {'kPa', 'MPa'}, optional
|
|
1607
|
-
Desired output unit. Default is 'kPa'.
|
|
1608
|
-
|
|
1609
|
-
Returns
|
|
1610
|
-
-------
|
|
1611
|
-
ndarray, float
|
|
1612
|
-
Transverse normal stress at grid points in the slab in
|
|
1613
|
-
specified unit.
|
|
1614
|
-
"""
|
|
1615
|
-
# Unit conversion dict
|
|
1616
|
-
convert = {"kPa": 1e3, "MPa": 1}
|
|
1617
|
-
|
|
1618
|
-
# Get mesh along z-axis
|
|
1619
|
-
zmesh = self.get_zmesh(dz=dz)
|
|
1620
|
-
zi = zmesh[:, 0]
|
|
1621
|
-
rho = 1e-12 * zmesh[:, 3]
|
|
1622
|
-
|
|
1623
|
-
# Get dimensions of stress field (n rows, m columns)
|
|
1624
|
-
n = zmesh.shape[0]
|
|
1625
|
-
m = Z.shape[1]
|
|
1626
|
-
|
|
1627
|
-
# Get third derivatives of centerline displacement u0 and
|
|
1628
|
-
# cross-section rotaiton psi of all grid points along the x-axis
|
|
1629
|
-
du0_dxdxdx = self.du0_dxdxdx(Z, phi)
|
|
1630
|
-
dpsi_dxdxdx = self.dpsi_dxdxdx(Z, phi)
|
|
1631
|
-
|
|
1632
|
-
# Initialize second derivative of axial normal stress sxx w.r.t. x
|
|
1633
|
-
dsxx_dxdx = np.zeros(shape=[n, m])
|
|
1634
|
-
|
|
1635
|
-
# Calculate second derivative of sxx at z-grid points
|
|
1636
|
-
for i, (z, E, nu, _) in enumerate(zmesh):
|
|
1637
|
-
dsxx_dxdx[i, :] = E / (1 - nu**2) * (du0_dxdxdx + z * dpsi_dxdxdx)
|
|
1638
|
-
|
|
1639
|
-
# Calculate weight load at grid points
|
|
1640
|
-
qn = rho * self.g * np.cos(np.deg2rad(phi))
|
|
1641
|
-
|
|
1642
|
-
# Integrate dsxx_dxdx twice along z to obtain transverse
|
|
1643
|
-
# normal stress Szz in MPa
|
|
1644
|
-
integrand = cumulative_trapezoid(dsxx_dxdx, zi, axis=0, initial=0)
|
|
1645
|
-
Szz = cumulative_trapezoid(integrand, zi, axis=0, initial=0)
|
|
1646
|
-
Szz += cumulative_trapezoid(-qn, zi, initial=0)[:, None]
|
|
1647
|
-
|
|
1648
|
-
# Return shear stress txz in specified unit
|
|
1649
|
-
return convert[unit] * Szz
|
|
1650
|
-
|
|
1651
|
-
def principal_stress_slab(
|
|
1652
|
-
self, Z, phi, dz=2, unit="kPa", val="max", normalize=False
|
|
1653
|
-
):
|
|
1654
|
-
"""
|
|
1655
|
-
Compute maxium or minimum principal stress in slab layers.
|
|
1656
|
-
|
|
1657
|
-
Arguments
|
|
1658
|
-
---------
|
|
1659
|
-
Z : ndarray
|
|
1660
|
-
Solution vector [u(x) u'(x) w(x) w'(x) psi(x), psi'(x)]^T
|
|
1661
|
-
phi : float
|
|
1662
|
-
Inclination (degrees). Counterclockwise positive.
|
|
1663
|
-
dz : float, optional
|
|
1664
|
-
Element size along z-axis (mm). Default is 2 mm.
|
|
1665
|
-
unit : {'kPa', 'MPa'}, optional
|
|
1666
|
-
Desired output unit. Default is 'kPa'.
|
|
1667
|
-
val : str, optional
|
|
1668
|
-
Maximum 'max' or minimum 'min' principal stress. Default is 'max'.
|
|
1669
|
-
normalize : bool
|
|
1670
|
-
Toggle layerwise normalization to strength.
|
|
1671
|
-
|
|
1672
|
-
Returns
|
|
1673
|
-
-------
|
|
1674
|
-
ndarray
|
|
1675
|
-
Maximum or minimum principal stress in specified unit.
|
|
1676
|
-
|
|
1677
|
-
Raises
|
|
1678
|
-
------
|
|
1679
|
-
ValueError
|
|
1680
|
-
If specified principal stress component is neither 'max' nor
|
|
1681
|
-
'min', or if normalization of compressive principal stress
|
|
1682
|
-
is requested.
|
|
1683
|
-
"""
|
|
1684
|
-
# Raise error if specified component is not available
|
|
1685
|
-
if val not in ["min", "max"]:
|
|
1686
|
-
raise ValueError(f"Component {val} not defined.")
|
|
1687
|
-
|
|
1688
|
-
# Multiplier selection dict
|
|
1689
|
-
m = {"max": 1, "min": -1}
|
|
1690
|
-
|
|
1691
|
-
# Get axial normal stresses, shear stresses, transverse normal stresses
|
|
1692
|
-
Sxx = self.Sxx(Z=Z, phi=phi, dz=dz, unit=unit)
|
|
1693
|
-
Txz = self.Txz(Z=Z, phi=phi, dz=dz, unit=unit)
|
|
1694
|
-
Szz = self.Szz(Z=Z, phi=phi, dz=dz, unit=unit)
|
|
1695
|
-
|
|
1696
|
-
# Calculate principal stress
|
|
1697
|
-
Ps = (Sxx + Szz) / 2 + m[val] * np.sqrt((Sxx - Szz) ** 2 + 4 * Txz**2) / 2
|
|
1698
|
-
|
|
1699
|
-
# Raise error if normalization of compressive stresses is attempted
|
|
1700
|
-
if normalize and val == "min":
|
|
1701
|
-
raise ValueError("Can only normlize tensile stresses.")
|
|
1702
|
-
|
|
1703
|
-
# Normalize tensile stresses to tensile strength
|
|
1704
|
-
if normalize and val == "max":
|
|
1705
|
-
# Get layer densities
|
|
1706
|
-
rho = self.get_zmesh(dz=dz)[:, 3]
|
|
1707
|
-
# Normlize maximum principal stress to layers' tensile strength
|
|
1708
|
-
return Ps / tensile_strength_slab(rho, unit=unit)[:, None]
|
|
1709
|
-
|
|
1710
|
-
# Return absolute principal stresses
|
|
1711
|
-
return Ps
|
|
1712
|
-
|
|
1713
|
-
def principal_stress_weaklayer(
|
|
1714
|
-
self, Z, sc=2.6, unit="kPa", val="min", normalize=False
|
|
1715
|
-
):
|
|
1716
|
-
"""
|
|
1717
|
-
Compute maxium or minimum principal stress in the weak layer.
|
|
1718
|
-
|
|
1719
|
-
Arguments
|
|
1720
|
-
---------
|
|
1721
|
-
Z : ndarray
|
|
1722
|
-
Solution vector [u(x) u'(x) w(x) w'(x) psi(x), psi'(x)]^T
|
|
1723
|
-
sc : float
|
|
1724
|
-
Weak-layer compressive strength. Default is 2.6 kPa.
|
|
1725
|
-
unit : {'kPa', 'MPa'}, optional
|
|
1726
|
-
Desired output unit. Default is 'kPa'.
|
|
1727
|
-
val : str, optional
|
|
1728
|
-
Maximum 'max' or minimum 'min' principal stress. Default is 'min'.
|
|
1729
|
-
normalize : bool
|
|
1730
|
-
Toggle layerwise normalization to strength.
|
|
1731
|
-
|
|
1732
|
-
Returns
|
|
1733
|
-
-------
|
|
1734
|
-
ndarray
|
|
1735
|
-
Maximum or minimum principal stress in specified unit.
|
|
1736
|
-
|
|
1737
|
-
Raises
|
|
1738
|
-
------
|
|
1739
|
-
ValueError
|
|
1740
|
-
If specified principal stress component is neither 'max' nor
|
|
1741
|
-
'min', or if normalization of tensile principal stress
|
|
1742
|
-
is requested.
|
|
1743
|
-
"""
|
|
1744
|
-
# Raise error if specified component is not available
|
|
1745
|
-
if val not in ["min", "max"]:
|
|
1746
|
-
raise ValueError(f"Component {val} not defined.")
|
|
1747
|
-
|
|
1748
|
-
# Multiplier selection dict
|
|
1749
|
-
m = {"max": 1, "min": -1}
|
|
1750
|
-
|
|
1751
|
-
# Get weak-layer normal and shear stresses
|
|
1752
|
-
sig = self.sig(Z, unit=unit)
|
|
1753
|
-
tau = self.tau(Z, unit=unit)
|
|
1754
|
-
|
|
1755
|
-
# Calculate principal stress
|
|
1756
|
-
ps = sig / 2 + m[val] * np.sqrt(sig**2 + 4 * tau**2) / 2
|
|
1757
|
-
|
|
1758
|
-
# Raise error if normalization of tensile stresses is attempted
|
|
1759
|
-
if normalize and val == "max":
|
|
1760
|
-
raise ValueError("Can only normlize compressive stresses.")
|
|
1761
|
-
|
|
1762
|
-
# Normalize compressive stresses to compressive strength
|
|
1763
|
-
if normalize and val == "min":
|
|
1764
|
-
return ps / sc
|
|
1765
|
-
|
|
1766
|
-
# Return absolute principal stresses
|
|
1767
|
-
return ps
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
class OutputMixin:
|
|
1771
|
-
"""
|
|
1772
|
-
Mixin for outputs.
|
|
1773
|
-
|
|
1774
|
-
Provides convenience methods for the assembly of output lists
|
|
1775
|
-
such as rasterized displacements or rasterized stresses.
|
|
1776
|
-
"""
|
|
1777
|
-
|
|
1778
|
-
def external_potential(self, C, phi, L, **segments):
|
|
1779
|
-
"""
|
|
1780
|
-
Compute total external potential (pst only).
|
|
1781
|
-
|
|
1782
|
-
Arguments
|
|
1783
|
-
---------
|
|
1784
|
-
C : ndarray
|
|
1785
|
-
Matrix(6xN) of solution constants for a system of N
|
|
1786
|
-
segements. Columns contain the 6 constants of each segement.
|
|
1787
|
-
phi : float
|
|
1788
|
-
Inclination of the slab (°).
|
|
1789
|
-
L : float, optional
|
|
1790
|
-
Total length of model (mm).
|
|
1791
|
-
segments : dict
|
|
1792
|
-
Dictionary with lists of touchdown booleans (tdi), segement
|
|
1793
|
-
lengths (li), skier weights (mi), and foundation booleans
|
|
1794
|
-
in the cracked (ki) and uncracked (k0) configurations.
|
|
1795
|
-
|
|
1796
|
-
Returns
|
|
1797
|
-
-------
|
|
1798
|
-
Pi_ext : float
|
|
1799
|
-
Total external potential (Nmm).
|
|
1800
|
-
"""
|
|
1801
|
-
# Rasterize solution
|
|
1802
|
-
xq, zq, xb = self.rasterize_solution(C=C, phi=phi, **segments)
|
|
1803
|
-
_ = xq, xb
|
|
1804
|
-
# Compute displacements where weight loads are applied
|
|
1805
|
-
w0 = self.w(zq)
|
|
1806
|
-
us = self.u(zq, z0=self.zs)
|
|
1807
|
-
# Get weight loads
|
|
1808
|
-
qn = self.calc_qn()
|
|
1809
|
-
qt = self.calc_qt()
|
|
1810
|
-
# use +/- and us[0]/us[-1] according to system and phi
|
|
1811
|
-
# compute total external potential
|
|
1812
|
-
Pi_ext = (
|
|
1813
|
-
-qn * (segments["li"][0] + segments["li"][1]) * np.average(w0)
|
|
1814
|
-
- qn * (L - (segments["li"][0] + segments["li"][1])) * self.tc
|
|
1815
|
-
)
|
|
1816
|
-
# Ensure
|
|
1817
|
-
if self.system in ["pst-"]:
|
|
1818
|
-
ub = us[-1]
|
|
1819
|
-
elif self.system in ["-pst"]:
|
|
1820
|
-
ub = us[0]
|
|
1821
|
-
Pi_ext += (
|
|
1822
|
-
-qt * (segments["li"][0] + segments["li"][1]) * np.average(us)
|
|
1823
|
-
- qt * (L - (segments["li"][0] + segments["li"][1])) * ub
|
|
1824
|
-
)
|
|
1825
|
-
if self.system not in ["pst-", "-pst"]:
|
|
1826
|
-
print("Input error: Only pst-setup implemented at the moment.")
|
|
1827
|
-
|
|
1828
|
-
return Pi_ext
|
|
1829
|
-
|
|
1830
|
-
def internal_potential(self, C, phi, L, **segments):
|
|
1831
|
-
"""
|
|
1832
|
-
Compute total internal potential (pst only).
|
|
1833
|
-
|
|
1834
|
-
Arguments
|
|
1835
|
-
---------
|
|
1836
|
-
C : ndarray
|
|
1837
|
-
Matrix(6xN) of solution constants for a system of N
|
|
1838
|
-
segements. Columns contain the 6 constants of each segement.
|
|
1839
|
-
phi : float
|
|
1840
|
-
Inclination of the slab (°).
|
|
1841
|
-
L : float, optional
|
|
1842
|
-
Total length of model (mm).
|
|
1843
|
-
segments : dict
|
|
1844
|
-
Dictionary with lists of touchdown booleans (tdi), segement
|
|
1845
|
-
lengths (li), skier weights (mi), and foundation booleans
|
|
1846
|
-
in the cracked (ki) and uncracked (k0) configurations.
|
|
1847
|
-
|
|
1848
|
-
Returns
|
|
1849
|
-
-------
|
|
1850
|
-
Pi_int : float
|
|
1851
|
-
Total internal potential (Nmm).
|
|
1852
|
-
"""
|
|
1853
|
-
# Rasterize solution
|
|
1854
|
-
xq, zq, xb = self.rasterize_solution(C=C, phi=phi, **segments)
|
|
1855
|
-
|
|
1856
|
-
# Compute section forces
|
|
1857
|
-
N, M, V = self.N(zq), self.M(zq), self.V(zq)
|
|
1858
|
-
|
|
1859
|
-
# Drop parts of the solution that are not a foundation
|
|
1860
|
-
zweak = zq[:, ~np.isnan(xb)]
|
|
1861
|
-
xweak = xb[~np.isnan(xb)]
|
|
1862
|
-
|
|
1863
|
-
# Compute weak layer displacements
|
|
1864
|
-
wweak = self.w(zweak)
|
|
1865
|
-
uweak = self.u(zweak, z0=self.h / 2)
|
|
1866
|
-
|
|
1867
|
-
# Compute stored energy of the slab (monte-carlo integration)
|
|
1868
|
-
n = len(xq)
|
|
1869
|
-
nweak = len(xweak)
|
|
1870
|
-
# energy share from moment, shear force, wl normal and tangential springs
|
|
1871
|
-
Pi_int = (
|
|
1872
|
-
L / 2 / n / self.A11 * np.sum([Ni**2 for Ni in N])
|
|
1873
|
-
+ L
|
|
1874
|
-
/ 2
|
|
1875
|
-
/ n
|
|
1876
|
-
/ (self.D11 - self.B11**2 / self.A11)
|
|
1877
|
-
* np.sum([Mi**2 for Mi in M])
|
|
1878
|
-
+ L / 2 / n / self.kA55 * np.sum([Vi**2 for Vi in V])
|
|
1879
|
-
+ L * self.kn / 2 / nweak * np.sum([wi**2 for wi in wweak])
|
|
1880
|
-
+ L * self.kt / 2 / nweak * np.sum([ui**2 for ui in uweak])
|
|
1881
|
-
)
|
|
1882
|
-
# energy share from substitute rotation spring
|
|
1883
|
-
if self.system in ["pst-"]:
|
|
1884
|
-
Pi_int += 1 / 2 * M[-1] * (self.psi(zq)[-1]) ** 2
|
|
1885
|
-
elif self.system in ["-pst"]:
|
|
1886
|
-
Pi_int += 1 / 2 * M[0] * (self.psi(zq)[0]) ** 2
|
|
1887
|
-
else:
|
|
1888
|
-
print("Input error: Only pst-setup implemented at the moment.")
|
|
1889
|
-
|
|
1890
|
-
return Pi_int
|
|
1891
|
-
|
|
1892
|
-
def total_potential(self, C, phi, L, **segments):
|
|
1893
|
-
"""
|
|
1894
|
-
Returns total differential potential
|
|
1895
|
-
|
|
1896
|
-
Arguments
|
|
1897
|
-
---------
|
|
1898
|
-
C : ndarray
|
|
1899
|
-
Matrix(6xN) of solution constants for a system of N
|
|
1900
|
-
segements. Columns contain the 6 constants of each segement.
|
|
1901
|
-
phi : float
|
|
1902
|
-
Inclination of the slab (°).
|
|
1903
|
-
L : float, optional
|
|
1904
|
-
Total length of model (mm).
|
|
1905
|
-
segments : dict
|
|
1906
|
-
Dictionary with lists of touchdown booleans (tdi), segement
|
|
1907
|
-
lengths (li), skier weights (mi), and foundation booleans
|
|
1908
|
-
in the cracked (ki) and uncracked (k0) configurations.
|
|
1909
|
-
|
|
1910
|
-
Returns
|
|
1911
|
-
-------
|
|
1912
|
-
Pi : float
|
|
1913
|
-
Total differential potential (Nmm).
|
|
1914
|
-
"""
|
|
1915
|
-
Pi_int = self.internal_potential(C, phi, L, **segments)
|
|
1916
|
-
Pi_ext = self.external_potential(C, phi, L, **segments)
|
|
1917
|
-
|
|
1918
|
-
return Pi_int + Pi_ext
|
|
1919
|
-
|
|
1920
|
-
def get_weaklayer_shearstress(self, x, z, unit="MPa", removeNaNs=False):
|
|
1921
|
-
"""
|
|
1922
|
-
Compute weak-layer shear stress.
|
|
1923
|
-
|
|
1924
|
-
Arguments
|
|
1925
|
-
---------
|
|
1926
|
-
x : ndarray
|
|
1927
|
-
Discretized x-coordinates (mm) where coordinates of unsupported
|
|
1928
|
-
(no foundation) segments are NaNs.
|
|
1929
|
-
z : ndarray
|
|
1930
|
-
Solution vectors at positions x as columns of matrix z.
|
|
1931
|
-
unit : {'MPa', 'kPa'}, optional
|
|
1932
|
-
Stress output unit. Default is MPa.
|
|
1933
|
-
keepNaNs : bool
|
|
1934
|
-
If set, do not remove
|
|
1935
|
-
|
|
1936
|
-
Returns
|
|
1937
|
-
-------
|
|
1938
|
-
x : ndarray
|
|
1939
|
-
Horizontal coordinates (cm).
|
|
1940
|
-
sig : ndarray
|
|
1941
|
-
Normal stress (stress unit input).
|
|
1942
|
-
"""
|
|
1943
|
-
# Convert coordinates from mm to cm and stresses from MPa to unit
|
|
1944
|
-
x = x / 10
|
|
1945
|
-
tau = self.tau(z, unit=unit)
|
|
1946
|
-
# Filter stresses in unspupported segments
|
|
1947
|
-
if removeNaNs:
|
|
1948
|
-
# Remove coordinate-stress pairs where no weak layer is present
|
|
1949
|
-
tau = tau[~np.isnan(x)]
|
|
1950
|
-
x = x[~np.isnan(x)]
|
|
1951
|
-
else:
|
|
1952
|
-
# Set stress NaN where no weak layer is present
|
|
1953
|
-
tau[np.isnan(x)] = np.nan
|
|
1954
|
-
|
|
1955
|
-
return x, tau
|
|
1956
|
-
|
|
1957
|
-
def get_weaklayer_normalstress(self, x, z, unit="MPa", removeNaNs=False):
|
|
1958
|
-
"""
|
|
1959
|
-
Compute weak-layer normal stress.
|
|
1960
|
-
|
|
1961
|
-
Arguments
|
|
1962
|
-
---------
|
|
1963
|
-
x : ndarray
|
|
1964
|
-
Discretized x-coordinates (mm) where coordinates of unsupported
|
|
1965
|
-
(no foundation) segments are NaNs.
|
|
1966
|
-
z : ndarray
|
|
1967
|
-
Solution vectors at positions x as columns of matrix z.
|
|
1968
|
-
unit : {'MPa', 'kPa'}, optional
|
|
1969
|
-
Stress output unit. Default is MPa.
|
|
1970
|
-
keepNaNs : bool
|
|
1971
|
-
If set, do not remove
|
|
1972
|
-
|
|
1973
|
-
Returns
|
|
1974
|
-
-------
|
|
1975
|
-
x : ndarray
|
|
1976
|
-
Horizontal coordinates (cm).
|
|
1977
|
-
sig : ndarray
|
|
1978
|
-
Normal stress (stress unit input).
|
|
1979
|
-
"""
|
|
1980
|
-
# Convert coordinates from mm to cm and stresses from MPa to unit
|
|
1981
|
-
x = x / 10
|
|
1982
|
-
sig = self.sig(z, unit=unit)
|
|
1983
|
-
# Filter stresses in unspupported segments
|
|
1984
|
-
if removeNaNs:
|
|
1985
|
-
# Remove coordinate-stress pairs where no weak layer is present
|
|
1986
|
-
sig = sig[~np.isnan(x)]
|
|
1987
|
-
x = x[~np.isnan(x)]
|
|
1988
|
-
else:
|
|
1989
|
-
# Set stress NaN where no weak layer is present
|
|
1990
|
-
sig[np.isnan(x)] = np.nan
|
|
1991
|
-
|
|
1992
|
-
return x, sig
|
|
1993
|
-
|
|
1994
|
-
def get_slab_displacement(self, x, z, loc="mid", unit="mm"):
|
|
1995
|
-
"""
|
|
1996
|
-
Compute horizontal slab displacement.
|
|
1997
|
-
|
|
1998
|
-
Arguments
|
|
1999
|
-
---------
|
|
2000
|
-
x : ndarray
|
|
2001
|
-
Discretized x-coordinates (mm) where coordinates of
|
|
2002
|
-
unsupported (no foundation) segments are NaNs.
|
|
2003
|
-
z : ndarray
|
|
2004
|
-
Solution vectors at positions x as columns of matrix z.
|
|
2005
|
-
loc : {'top', 'mid', 'bot'}
|
|
2006
|
-
Get displacements of top, midplane or bottom of slab.
|
|
2007
|
-
Default is mid.
|
|
2008
|
-
unit : {'m', 'cm', 'mm', 'um'}, optional
|
|
2009
|
-
Displacement output unit. Default is mm.
|
|
2010
|
-
|
|
2011
|
-
Returns
|
|
2012
|
-
-------
|
|
2013
|
-
x : ndarray
|
|
2014
|
-
Horizontal coordinates (cm).
|
|
2015
|
-
ndarray
|
|
2016
|
-
Horizontal displacements (unit input).
|
|
2017
|
-
"""
|
|
2018
|
-
# Coordinates (cm)
|
|
2019
|
-
x = x / 10
|
|
2020
|
-
# Locator
|
|
2021
|
-
z0 = {"top": -self.h / 2, "mid": 0, "bot": self.h / 2}
|
|
2022
|
-
# Displacement (unit)
|
|
2023
|
-
u = self.u(z, z0=z0[loc], unit=unit)
|
|
2024
|
-
# Output array
|
|
2025
|
-
return x, u
|
|
2026
|
-
|
|
2027
|
-
def get_slab_deflection(self, x, z, unit="mm"):
|
|
2028
|
-
"""
|
|
2029
|
-
Compute vertical slab displacement.
|
|
2030
|
-
|
|
2031
|
-
Arguments
|
|
2032
|
-
---------
|
|
2033
|
-
x : ndarray
|
|
2034
|
-
Discretized x-coordinates (mm) where coordinates of
|
|
2035
|
-
unsupported (no foundation) segments are NaNs.
|
|
2036
|
-
z : ndarray
|
|
2037
|
-
Solution vectors at positions x as columns of matrix z.
|
|
2038
|
-
Default is mid.
|
|
2039
|
-
unit : {'m', 'cm', 'mm', 'um'}, optional
|
|
2040
|
-
Displacement output unit. Default is mm.
|
|
2041
|
-
|
|
2042
|
-
Returns
|
|
2043
|
-
-------
|
|
2044
|
-
x : ndarray
|
|
2045
|
-
Horizontal coordinates (cm).
|
|
2046
|
-
ndarray
|
|
2047
|
-
Vertical deflections (unit input).
|
|
2048
|
-
"""
|
|
2049
|
-
# Coordinates (cm)
|
|
2050
|
-
x = x / 10
|
|
2051
|
-
# Deflection (unit)
|
|
2052
|
-
w = self.w(z, unit=unit)
|
|
2053
|
-
# Output array
|
|
2054
|
-
return x, w
|
|
2055
|
-
|
|
2056
|
-
def get_slab_rotation(self, x, z, unit="degrees"):
|
|
2057
|
-
"""
|
|
2058
|
-
Compute slab cross-section rotation angle.
|
|
2059
|
-
|
|
2060
|
-
Arguments
|
|
2061
|
-
---------
|
|
2062
|
-
x : ndarray
|
|
2063
|
-
Discretized x-coordinates (mm) where coordinates of
|
|
2064
|
-
unsupported (no foundation) segments are NaNs.
|
|
2065
|
-
z : ndarray
|
|
2066
|
-
Solution vectors at positions x as columns of matrix z.
|
|
2067
|
-
Default is mid.
|
|
2068
|
-
unit : {'deg', degrees', 'rad', 'radians'}, optional
|
|
2069
|
-
Rotation angle output unit. Default is degrees.
|
|
2070
|
-
|
|
2071
|
-
Returns
|
|
2072
|
-
-------
|
|
2073
|
-
x : ndarray
|
|
2074
|
-
Horizontal coordinates (cm).
|
|
2075
|
-
ndarray
|
|
2076
|
-
Cross section rotations (unit input).
|
|
2077
|
-
"""
|
|
2078
|
-
# Coordinates (cm)
|
|
2079
|
-
x = x / 10
|
|
2080
|
-
# Cross-section rotation angle (unit)
|
|
2081
|
-
psi = self.psi(z, unit=unit)
|
|
2082
|
-
# Output array
|
|
2083
|
-
return x, psi
|