weac 2.6.4__py3-none-any.whl → 3.0.1__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/analysis/__init__.py +23 -0
- weac/analysis/analyzer.py +790 -0
- weac/analysis/criteria_evaluator.py +1169 -0
- weac/analysis/plotter.py +1922 -0
- weac/components/__init__.py +21 -0
- weac/components/config.py +33 -0
- weac/components/criteria_config.py +86 -0
- weac/components/layer.py +284 -0
- weac/components/model_input.py +103 -0
- weac/components/scenario_config.py +72 -0
- weac/components/segment.py +31 -0
- weac/constants.py +37 -0
- weac/core/__init__.py +10 -0
- weac/core/eigensystem.py +405 -0
- weac/core/field_quantities.py +273 -0
- weac/core/scenario.py +200 -0
- weac/core/slab.py +149 -0
- weac/core/slab_touchdown.py +363 -0
- weac/core/system_model.py +413 -0
- weac/core/unknown_constants_solver.py +444 -0
- weac/logging_config.py +39 -0
- weac/utils/__init__.py +0 -0
- weac/utils/geldsetzer.py +166 -0
- weac/utils/misc.py +127 -0
- weac/utils/snow_types.py +82 -0
- weac/utils/snowpilot_parser.py +332 -0
- {weac-2.6.4.dist-info → weac-3.0.1.dist-info}/METADATA +196 -64
- weac-3.0.1.dist-info/RECORD +32 -0
- weac-3.0.1.dist-info/licenses/LICENSE +21 -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/licenses/LICENSE +0 -24
- {weac-2.6.4.dist-info → weac-3.0.1.dist-info}/WHEEL +0 -0
- {weac-2.6.4.dist-info → weac-3.0.1.dist-info}/top_level.txt +0 -0
weac/core/eigensystem.py
ADDED
|
@@ -0,0 +1,405 @@
|
|
|
1
|
+
"""
|
|
2
|
+
This module provides the Eigensystem class, which is used to solve
|
|
3
|
+
the eigenvalue problem for a layered beam on an elastic foundation.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import logging
|
|
7
|
+
from typing import Optional
|
|
8
|
+
|
|
9
|
+
import numpy as np
|
|
10
|
+
from numpy.typing import NDArray
|
|
11
|
+
|
|
12
|
+
from weac.components import WeakLayer
|
|
13
|
+
from weac.constants import SHEAR_CORRECTION_FACTOR
|
|
14
|
+
from weac.core.slab import Slab
|
|
15
|
+
from weac.utils.misc import decompose_to_normal_tangential
|
|
16
|
+
|
|
17
|
+
logger = logging.getLogger(__name__)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class Eigensystem:
|
|
21
|
+
"""
|
|
22
|
+
Calculates system properties and solves the eigenvalue problem
|
|
23
|
+
for a layered beam on an elastic foundation (Winkler model).
|
|
24
|
+
|
|
25
|
+
Attributes
|
|
26
|
+
----------
|
|
27
|
+
weak_layer: WeakLayer
|
|
28
|
+
slab: Slab
|
|
29
|
+
|
|
30
|
+
System properties
|
|
31
|
+
-----------------
|
|
32
|
+
A11: float # extensional stiffness
|
|
33
|
+
B11: float # coupling stiffness
|
|
34
|
+
D11: float # bending stiffness
|
|
35
|
+
kA55: float # shear stiffness
|
|
36
|
+
K0: float # foundation stiffness
|
|
37
|
+
|
|
38
|
+
Eigenvalues and Eigenvectors
|
|
39
|
+
----------------------------
|
|
40
|
+
ewC: NDArray[np.complex128] # shape (k): Complex Eigenvalues
|
|
41
|
+
ewR: NDArray[np.float64] # shape (k): Real Eigenvalues
|
|
42
|
+
evC: NDArray[np.complex128] # shape (6, k): Complex Eigenvectors
|
|
43
|
+
evR: NDArray[np.float64] # shape (6, k): Real Eigenvectors
|
|
44
|
+
sR: NDArray[np.float64] # shape (k): Real positive eigenvalue shifts
|
|
45
|
+
# (for numerical robustness)
|
|
46
|
+
sC: NDArray[np.float64] # shape (k): Complex positive eigenvalue shifts
|
|
47
|
+
# (for numerical robustness)
|
|
48
|
+
"""
|
|
49
|
+
|
|
50
|
+
# Input data
|
|
51
|
+
weak_layer: WeakLayer
|
|
52
|
+
slab: Slab
|
|
53
|
+
|
|
54
|
+
# System properties
|
|
55
|
+
A11: float # extensional stiffness
|
|
56
|
+
B11: float # coupling stiffness
|
|
57
|
+
D11: float # bending stiffness
|
|
58
|
+
kA55: float # shear stiffness
|
|
59
|
+
K0: float # foundation stiffness
|
|
60
|
+
|
|
61
|
+
K: NDArray # System Matrix
|
|
62
|
+
|
|
63
|
+
# Eigenvalues and Eigenvectors
|
|
64
|
+
ewC: NDArray[np.complex128] # shape (k): Complex Eigenvalues
|
|
65
|
+
ewR: NDArray[np.float64] # shape (k): Real Eigenvalues
|
|
66
|
+
evC: NDArray[np.complex128] # shape (6, k): Complex Eigenvectors
|
|
67
|
+
evR: NDArray[np.float64] # shape (6, k): Real Eigenvectors
|
|
68
|
+
sR: NDArray[
|
|
69
|
+
np.float64
|
|
70
|
+
] # shape (k): Real positive eigenvalue shifts (for numerical robustness)
|
|
71
|
+
sC: NDArray[
|
|
72
|
+
np.float64
|
|
73
|
+
] # shape (k): Complex positive eigenvalue shifts (for numerical robustness)
|
|
74
|
+
|
|
75
|
+
def __init__(self, weak_layer: WeakLayer, slab: Slab):
|
|
76
|
+
self.slab = slab
|
|
77
|
+
self.weak_layer = weak_layer
|
|
78
|
+
|
|
79
|
+
self.calc_eigensystem()
|
|
80
|
+
|
|
81
|
+
def calc_eigensystem(self):
|
|
82
|
+
"""Calculate the fundamental system of the problem."""
|
|
83
|
+
self._calc_laminate_stiffness_parameters()
|
|
84
|
+
self.K = self.assemble_system_matrix(kn=None, kt=None)
|
|
85
|
+
self.ewC, self.ewR, self.evC, self.evR, self.sR, self.sC = (
|
|
86
|
+
self.calc_eigenvalues_and_eigenvectors(self.K)
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
def _calc_laminate_stiffness_parameters(self):
|
|
90
|
+
"""
|
|
91
|
+
Provide ABD matrix.
|
|
92
|
+
|
|
93
|
+
Return plane-strain laminate stiffness matrix (ABD matrix).
|
|
94
|
+
"""
|
|
95
|
+
# Append z_{1} at top of surface layer
|
|
96
|
+
zis = np.concatenate(([-self.slab.H / 2], self.slab.zi_bottom))
|
|
97
|
+
|
|
98
|
+
# Initialize stiffness components
|
|
99
|
+
A11, B11, D11, kA55 = 0, 0, 0, 0
|
|
100
|
+
# Add layerwise contributions
|
|
101
|
+
for i in range(len(zis) - 1):
|
|
102
|
+
E = self.slab.Ei[i]
|
|
103
|
+
G = self.slab.Gi[i]
|
|
104
|
+
nu = self.slab.nui[i]
|
|
105
|
+
A11 += E / (1 - nu**2) * (zis[i + 1] - zis[i])
|
|
106
|
+
B11 += 1 / 2 * E / (1 - nu**2) * (zis[i + 1] ** 2 - zis[i] ** 2)
|
|
107
|
+
D11 += 1 / 3 * E / (1 - nu**2) * (zis[i + 1] ** 3 - zis[i] ** 3)
|
|
108
|
+
kA55 += SHEAR_CORRECTION_FACTOR * G * (zis[i + 1] - zis[i])
|
|
109
|
+
|
|
110
|
+
self.A11 = A11
|
|
111
|
+
self.B11 = B11
|
|
112
|
+
self.D11 = D11
|
|
113
|
+
self.kA55 = kA55
|
|
114
|
+
self.K0 = B11**2 - A11 * D11
|
|
115
|
+
|
|
116
|
+
def assemble_system_matrix(
|
|
117
|
+
self, kn: Optional[float], kt: Optional[float]
|
|
118
|
+
) -> NDArray[np.float64]:
|
|
119
|
+
"""
|
|
120
|
+
Assemble first-order ODE system matrix K.
|
|
121
|
+
|
|
122
|
+
Using the solution vector z = [u, u', w, w', psi, psi']
|
|
123
|
+
the ODE system is written in the form Az' + Bz = d
|
|
124
|
+
and rearranged to z' = -(A^-1)Bz + (A^-1)d = Kz + q
|
|
125
|
+
|
|
126
|
+
Returns
|
|
127
|
+
-------
|
|
128
|
+
NDArray[np.float64]
|
|
129
|
+
System matrix K (6x6).
|
|
130
|
+
"""
|
|
131
|
+
kn = kn or self.weak_layer.kn
|
|
132
|
+
kt = kt or self.weak_layer.kt
|
|
133
|
+
H = self.slab.H # total slab thickness
|
|
134
|
+
h = self.weak_layer.h # weak layer thickness
|
|
135
|
+
|
|
136
|
+
# Abbreviations
|
|
137
|
+
K21 = kt * (-2 * self.D11 + self.B11 * (H + h)) / (2 * self.K0)
|
|
138
|
+
K24 = (
|
|
139
|
+
2 * self.D11 * kt * h
|
|
140
|
+
- self.B11 * kt * h * (H + h)
|
|
141
|
+
+ 4 * self.B11 * self.kA55
|
|
142
|
+
) / (4 * self.K0)
|
|
143
|
+
K25 = (
|
|
144
|
+
-2 * self.D11 * H * kt
|
|
145
|
+
+ self.B11 * H * kt * (H + h)
|
|
146
|
+
+ 4 * self.B11 * self.kA55
|
|
147
|
+
) / (4 * self.K0)
|
|
148
|
+
K43 = kn / self.kA55
|
|
149
|
+
K61 = kt * (2 * self.B11 - self.A11 * (H + h)) / (2 * self.K0)
|
|
150
|
+
K64 = (
|
|
151
|
+
-2 * self.B11 * kt * h
|
|
152
|
+
+ self.A11 * kt * h * (H + h)
|
|
153
|
+
- 4 * self.A11 * self.kA55
|
|
154
|
+
) / (4 * self.K0)
|
|
155
|
+
K65 = (
|
|
156
|
+
2 * self.B11 * H * kt
|
|
157
|
+
- self.A11 * H * kt * (H + h)
|
|
158
|
+
- 4 * self.A11 * self.kA55
|
|
159
|
+
) / (4 * self.K0)
|
|
160
|
+
|
|
161
|
+
# System matrix
|
|
162
|
+
K = [
|
|
163
|
+
[0, 1, 0, 0, 0, 0],
|
|
164
|
+
[K21, 0, 0, K24, K25, 0],
|
|
165
|
+
[0, 0, 0, 1, 0, 0],
|
|
166
|
+
[0, 0, K43, 0, 0, -1],
|
|
167
|
+
[0, 0, 0, 0, 0, 1],
|
|
168
|
+
[K61, 0, 0, K64, K65, 0],
|
|
169
|
+
]
|
|
170
|
+
|
|
171
|
+
return np.array(K, dtype=np.float64)
|
|
172
|
+
|
|
173
|
+
def calc_eigenvalues_and_eigenvectors(
|
|
174
|
+
self, system_matrix: NDArray[np.float64]
|
|
175
|
+
) -> tuple[
|
|
176
|
+
NDArray[np.complex128],
|
|
177
|
+
NDArray[np.float64],
|
|
178
|
+
NDArray[np.complex128],
|
|
179
|
+
NDArray[np.float64],
|
|
180
|
+
NDArray[np.float64],
|
|
181
|
+
NDArray[np.float64],
|
|
182
|
+
]:
|
|
183
|
+
"""
|
|
184
|
+
Calculate eigenvalues and eigenvectors of the system matrix.
|
|
185
|
+
|
|
186
|
+
Parameters:
|
|
187
|
+
-----------
|
|
188
|
+
system_matrix: NDArray # system_matrix size (6x6) of the eigenvalue problem
|
|
189
|
+
|
|
190
|
+
Return:
|
|
191
|
+
-------
|
|
192
|
+
ewC: NDArray[np.complex128] # shape (k): Complex Eigenvalues
|
|
193
|
+
ewR: NDArray[np.float64] # shape (g): Real Eigenvalues
|
|
194
|
+
evC: NDArray[np.complex128] # shape (6, k): Complex Eigenvectors
|
|
195
|
+
evR: NDArray[np.float64] # shape (6, g): Real Eigenvectors
|
|
196
|
+
sR: NDArray[np.float64] # shape (k): Real positive eigenvalue shifts
|
|
197
|
+
# (for numerical robustness)
|
|
198
|
+
sC: NDArray[np.float64] # shape (g): Complex positive eigenvalue shifts
|
|
199
|
+
# (for numerical robustness)
|
|
200
|
+
"""
|
|
201
|
+
# Calculate eigenvalues (ew) and eigenvectors (ev)
|
|
202
|
+
ew, ev = np.linalg.eig(system_matrix)
|
|
203
|
+
# Classify real and complex eigenvalues
|
|
204
|
+
real = (ew.imag == 0) & (ew.real != 0) # real eigenvalues
|
|
205
|
+
cmplx = ew.imag > 0 # positive complex conjugates
|
|
206
|
+
# Eigenvalues
|
|
207
|
+
ewC = ew[cmplx]
|
|
208
|
+
ewR = ew[real].real
|
|
209
|
+
# Eigenvectors
|
|
210
|
+
evC = ev[:, cmplx]
|
|
211
|
+
evR = ev[:, real].real
|
|
212
|
+
# Prepare positive eigenvalue shifts for numerical robustness
|
|
213
|
+
# 1. Keep small-positive eigenvalues away from zero, to not have a near-singular matrix
|
|
214
|
+
sR, sC = np.zeros(ewR.shape), np.zeros(ewC.shape)
|
|
215
|
+
sR[ewR > 0], sC[ewC > 0] = -1, -1
|
|
216
|
+
return ewC, ewR, evC, evR, sR, sC
|
|
217
|
+
|
|
218
|
+
def zh(self, x: float, length: float = 0, has_foundation: bool = True) -> NDArray:
|
|
219
|
+
"""
|
|
220
|
+
Compute bedded or free complementary solution at position x.
|
|
221
|
+
|
|
222
|
+
Arguments
|
|
223
|
+
---------
|
|
224
|
+
x : float
|
|
225
|
+
Horizontal coordinate (mm).
|
|
226
|
+
length : float, optional
|
|
227
|
+
Segment length (mm). Default is 0.
|
|
228
|
+
has_foundation : bool
|
|
229
|
+
Indicates whether segment has foundation or not. Default
|
|
230
|
+
is True.
|
|
231
|
+
|
|
232
|
+
Returns
|
|
233
|
+
-------
|
|
234
|
+
zh : ndarray
|
|
235
|
+
Complementary solution matrix (6x6) at position x.
|
|
236
|
+
"""
|
|
237
|
+
if has_foundation:
|
|
238
|
+
zh = np.concatenate(
|
|
239
|
+
[
|
|
240
|
+
# Real
|
|
241
|
+
self.evR * np.exp(self.ewR * (x + length * self.sR)),
|
|
242
|
+
# Complex
|
|
243
|
+
np.exp(self.ewC.real * (x + length * self.sC))
|
|
244
|
+
* (
|
|
245
|
+
self.evC.real * np.cos(self.ewC.imag * x)
|
|
246
|
+
- self.evC.imag * np.sin(self.ewC.imag * x)
|
|
247
|
+
),
|
|
248
|
+
# Complex
|
|
249
|
+
np.exp(self.ewC.real * (x + length * self.sC))
|
|
250
|
+
* (
|
|
251
|
+
self.evC.imag * np.cos(self.ewC.imag * x)
|
|
252
|
+
+ self.evC.real * np.sin(self.ewC.imag * x)
|
|
253
|
+
),
|
|
254
|
+
],
|
|
255
|
+
axis=1,
|
|
256
|
+
)
|
|
257
|
+
else:
|
|
258
|
+
# Abbreviations
|
|
259
|
+
H14 = 3 * self.B11 / self.A11 * x**2
|
|
260
|
+
H24 = 6 * self.B11 / self.A11 * x
|
|
261
|
+
H54 = -3 * x**2 + 6 * self.K0 / (self.A11 * self.kA55)
|
|
262
|
+
# Complementary solution matrix of free segments
|
|
263
|
+
zh = np.array(
|
|
264
|
+
[
|
|
265
|
+
[0, 0, 0, H14, 1, x],
|
|
266
|
+
[0, 0, 0, H24, 0, 1],
|
|
267
|
+
[1, x, x**2, x**3, 0, 0],
|
|
268
|
+
[0, 1, 2 * x, 3 * x**2, 0, 0],
|
|
269
|
+
[0, -1, -2 * x, H54, 0, 0],
|
|
270
|
+
[0, 0, -2, -6 * x, 0, 0],
|
|
271
|
+
]
|
|
272
|
+
)
|
|
273
|
+
|
|
274
|
+
return zh
|
|
275
|
+
|
|
276
|
+
def zp(
|
|
277
|
+
self, x: float, phi: float = 0, has_foundation=True, qs: float = 0
|
|
278
|
+
) -> NDArray:
|
|
279
|
+
"""
|
|
280
|
+
Compute bedded or free particular integrals at position x.
|
|
281
|
+
|
|
282
|
+
Arguments
|
|
283
|
+
---------
|
|
284
|
+
x : float
|
|
285
|
+
Horizontal coordinate (mm).
|
|
286
|
+
phi : float
|
|
287
|
+
Inclination (degrees).
|
|
288
|
+
has_foundation : bool
|
|
289
|
+
Indicates whether segment has foundation (True) or not
|
|
290
|
+
(False). Default is True.
|
|
291
|
+
qs : float
|
|
292
|
+
additional surface load weight
|
|
293
|
+
|
|
294
|
+
Returns
|
|
295
|
+
-------
|
|
296
|
+
zp : ndarray
|
|
297
|
+
Particular integral vector (6x1) at position x.
|
|
298
|
+
"""
|
|
299
|
+
# Get weight and surface loads
|
|
300
|
+
qw_n, qw_t = decompose_to_normal_tangential(f=self.slab.qw, phi=phi)
|
|
301
|
+
qs_n, qs_t = decompose_to_normal_tangential(f=qs, phi=phi)
|
|
302
|
+
|
|
303
|
+
# Weak Layer properties
|
|
304
|
+
kn = self.weak_layer.kn
|
|
305
|
+
kt = self.weak_layer.kt
|
|
306
|
+
h = self.weak_layer.h
|
|
307
|
+
|
|
308
|
+
# Slab properties
|
|
309
|
+
H = self.slab.H
|
|
310
|
+
z_cog = self.slab.z_cog
|
|
311
|
+
|
|
312
|
+
# Laminate stiffnesses
|
|
313
|
+
A11 = self.A11
|
|
314
|
+
B11 = self.B11
|
|
315
|
+
kA55 = self.kA55
|
|
316
|
+
K0 = self.K0
|
|
317
|
+
|
|
318
|
+
# Assemble particular integral vectors
|
|
319
|
+
if has_foundation:
|
|
320
|
+
zp = np.array(
|
|
321
|
+
[
|
|
322
|
+
[
|
|
323
|
+
(qw_t + qs_t) / kt
|
|
324
|
+
+ H * qw_t * (H + h - 2 * z_cog) / (4 * kA55)
|
|
325
|
+
+ H * qs_t * (2 * H + h) / (4 * kA55)
|
|
326
|
+
],
|
|
327
|
+
[0],
|
|
328
|
+
[(qw_n + qs_n) / kn],
|
|
329
|
+
[0],
|
|
330
|
+
[-(qw_t * (H + h - 2 * z_cog) + qs_t * (2 * H + h)) / (2 * kA55)],
|
|
331
|
+
[0],
|
|
332
|
+
]
|
|
333
|
+
)
|
|
334
|
+
else:
|
|
335
|
+
zp = np.array(
|
|
336
|
+
[
|
|
337
|
+
[
|
|
338
|
+
(-3 * (qw_t + qs_t) / A11 - B11 * (qw_n + qs_n) * x / K0)
|
|
339
|
+
/ 6
|
|
340
|
+
* x**2
|
|
341
|
+
],
|
|
342
|
+
[(-2 * (qw_t + qs_t) / A11 - B11 * (qw_n + qs_n) * x / K0) / 2 * x],
|
|
343
|
+
[-A11 * (qw_n + qs_n) * x**4 / (24 * K0)],
|
|
344
|
+
[-A11 * (qw_n + qs_n) * x**3 / (6 * K0)],
|
|
345
|
+
[
|
|
346
|
+
A11 * (qw_n + qs_n) * x**3 / (6 * K0)
|
|
347
|
+
+ (
|
|
348
|
+
(z_cog - B11 / A11) * qw_t
|
|
349
|
+
- H * qs_t / 2
|
|
350
|
+
- (qw_n + qs_n) * x
|
|
351
|
+
)
|
|
352
|
+
/ kA55
|
|
353
|
+
],
|
|
354
|
+
[(qw_n + qs_n) * (A11 * x**2 / (2 * K0) - 1 / kA55)],
|
|
355
|
+
]
|
|
356
|
+
)
|
|
357
|
+
|
|
358
|
+
return zp
|
|
359
|
+
|
|
360
|
+
def get_load_vector(self, phi: float, qs: float = 0) -> NDArray:
|
|
361
|
+
"""
|
|
362
|
+
Compute system load vector q.
|
|
363
|
+
|
|
364
|
+
Using the solution vector z = [u, u', w, w', psi, psi']
|
|
365
|
+
the ODE system is written in the form Az' + Bz = d
|
|
366
|
+
and rearranged to z' = -(A ^ -1)Bz + (A ^ -1)d = Kz + q
|
|
367
|
+
|
|
368
|
+
Arguments
|
|
369
|
+
---------
|
|
370
|
+
phi : float
|
|
371
|
+
Inclination [deg]. Counterclockwise positive.
|
|
372
|
+
qs : float
|
|
373
|
+
Surface Load [N/mm]
|
|
374
|
+
|
|
375
|
+
Returns
|
|
376
|
+
-------
|
|
377
|
+
ndarray
|
|
378
|
+
System load vector q (6x1).
|
|
379
|
+
"""
|
|
380
|
+
# Get weight and surface loads
|
|
381
|
+
qw_n, qw_t = decompose_to_normal_tangential(f=self.slab.qw, phi=phi)
|
|
382
|
+
qs_n, qs_t = decompose_to_normal_tangential(f=qs, phi=phi)
|
|
383
|
+
|
|
384
|
+
return np.array(
|
|
385
|
+
[
|
|
386
|
+
[0],
|
|
387
|
+
[
|
|
388
|
+
(
|
|
389
|
+
self.B11 * (self.slab.H * qs_t - 2 * qw_t * self.slab.z_cog)
|
|
390
|
+
+ 2 * self.D11 * (qw_t + qs_t)
|
|
391
|
+
)
|
|
392
|
+
/ (2 * self.K0)
|
|
393
|
+
],
|
|
394
|
+
[0],
|
|
395
|
+
[-(qw_n + qs_n) / self.kA55],
|
|
396
|
+
[0],
|
|
397
|
+
[
|
|
398
|
+
-(
|
|
399
|
+
self.A11 * (self.slab.H * qs_t - 2 * qw_t * self.slab.z_cog)
|
|
400
|
+
+ 2 * self.B11 * (qw_t + qs_t)
|
|
401
|
+
)
|
|
402
|
+
/ (2 * self.K0)
|
|
403
|
+
],
|
|
404
|
+
]
|
|
405
|
+
)
|
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
"""
|
|
2
|
+
This module defines the FieldQuantities class, which is responsible for calculating
|
|
3
|
+
and providing access to various physical quantities within the slab.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from typing import Literal
|
|
7
|
+
|
|
8
|
+
import numpy as np
|
|
9
|
+
|
|
10
|
+
from weac.core.eigensystem import Eigensystem
|
|
11
|
+
|
|
12
|
+
LengthUnit = Literal["m", "cm", "mm", "um"]
|
|
13
|
+
AngleUnit = Literal["deg", "rad"]
|
|
14
|
+
StressUnit = Literal["Pa", "kPa", "MPa", "GPa"]
|
|
15
|
+
EnergyUnit = Literal["J/m^2", "kJ/m^2", "N/mm"]
|
|
16
|
+
Unit = Literal[LengthUnit, AngleUnit, StressUnit, EnergyUnit]
|
|
17
|
+
|
|
18
|
+
_UNIT_FACTOR: dict[str, float] = {
|
|
19
|
+
"m": 1e-3,
|
|
20
|
+
"cm": 1e-1,
|
|
21
|
+
"mm": 1,
|
|
22
|
+
"um": 1e3,
|
|
23
|
+
"rad": 1,
|
|
24
|
+
"deg": 180 / np.pi,
|
|
25
|
+
"Pa": 1e6,
|
|
26
|
+
"kPa": 1e3,
|
|
27
|
+
"MPa": 1,
|
|
28
|
+
"GPa": 1e-3,
|
|
29
|
+
"J/m^2": 1e3, # joule per square meter
|
|
30
|
+
"kJ/m^2": 1, # kilojoule per square meter
|
|
31
|
+
"N/mm": 1, # newton per millimeter
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class FieldQuantities: # pylint: disable=too-many-instance-attributes, too-many-public-methods
|
|
36
|
+
"""
|
|
37
|
+
Convenience accessors for a 6xN solution matrix Z =
|
|
38
|
+
[u, u', w, w', ψ, ψ']ᵀ. All functions are *vectorized* along the second
|
|
39
|
+
axis (x-coordinate), so they return an `ndarray` of length N.
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
def __init__(self, eigensystem: Eigensystem):
|
|
43
|
+
self.es = eigensystem
|
|
44
|
+
|
|
45
|
+
@staticmethod
|
|
46
|
+
def _unit_factor(unit: Unit, /) -> float:
|
|
47
|
+
"""Return multiplicative factor associated with *unit*."""
|
|
48
|
+
try:
|
|
49
|
+
return _UNIT_FACTOR[unit]
|
|
50
|
+
except KeyError as exc:
|
|
51
|
+
raise ValueError(
|
|
52
|
+
f"Unsupported unit: {unit!r}, supported units are {_UNIT_FACTOR}"
|
|
53
|
+
) from exc
|
|
54
|
+
|
|
55
|
+
def u(
|
|
56
|
+
self,
|
|
57
|
+
Z: np.ndarray,
|
|
58
|
+
h0: float = 0,
|
|
59
|
+
unit: LengthUnit = "mm",
|
|
60
|
+
) -> float | np.ndarray:
|
|
61
|
+
"""Horizontal displacement *u = u₀ + h₀ ψ* at depth h₀."""
|
|
62
|
+
return self._unit_factor(unit) * (Z[0, :] + h0 * self.psi(Z))
|
|
63
|
+
|
|
64
|
+
def du_dx(self, Z: np.ndarray, h0: float) -> float | np.ndarray:
|
|
65
|
+
"""Derivative u' = u₀' + h₀ ψ'."""
|
|
66
|
+
return Z[1, :] + h0 * self.dpsi_dx(Z)
|
|
67
|
+
|
|
68
|
+
def w(self, Z: np.ndarray, unit: LengthUnit = "mm") -> float | np.ndarray:
|
|
69
|
+
"""Center-line deflection *w*."""
|
|
70
|
+
return self._unit_factor(unit) * Z[2, :]
|
|
71
|
+
|
|
72
|
+
def dw_dx(self, Z: np.ndarray) -> float | np.ndarray:
|
|
73
|
+
"""First derivative w'."""
|
|
74
|
+
return Z[3, :]
|
|
75
|
+
|
|
76
|
+
def psi(
|
|
77
|
+
self,
|
|
78
|
+
Z: np.ndarray,
|
|
79
|
+
unit: AngleUnit = "rad",
|
|
80
|
+
) -> float | np.ndarray:
|
|
81
|
+
"""Rotation ψ of the mid-plane."""
|
|
82
|
+
factor = self._unit_factor(unit)
|
|
83
|
+
return factor * Z[4, :]
|
|
84
|
+
|
|
85
|
+
def dpsi_dx(self, Z: np.ndarray) -> float | np.ndarray:
|
|
86
|
+
"""First derivative ψ′."""
|
|
87
|
+
return Z[5, :]
|
|
88
|
+
|
|
89
|
+
def N(self, Z: np.ndarray) -> float | np.ndarray:
|
|
90
|
+
"""Axial normal force N = A11 u' + B11 psi' in the slab [N]"""
|
|
91
|
+
return self.es.A11 * Z[1, :] + self.es.B11 * Z[5, :]
|
|
92
|
+
|
|
93
|
+
def M(self, Z: np.ndarray) -> float | np.ndarray:
|
|
94
|
+
"""Bending moment M = B11 u' + D11 psi' in the slab [Nmm]"""
|
|
95
|
+
return self.es.B11 * Z[1, :] + self.es.D11 * Z[5, :]
|
|
96
|
+
|
|
97
|
+
def V(self, Z: np.ndarray) -> float | np.ndarray:
|
|
98
|
+
"""Vertical shear force V = kA55(w' + psi) [N]"""
|
|
99
|
+
return self.es.kA55 * (Z[3, :] + Z[4, :])
|
|
100
|
+
|
|
101
|
+
def sig(self, Z: np.ndarray, unit: StressUnit = "MPa") -> float | np.ndarray:
|
|
102
|
+
"""Weak-layer normal stress"""
|
|
103
|
+
return -self._unit_factor(unit) * self.es.weak_layer.kn * self.w(Z)
|
|
104
|
+
|
|
105
|
+
def tau(self, Z: np.ndarray, unit: StressUnit = "MPa") -> float | np.ndarray:
|
|
106
|
+
"""Weak-layer shear stress"""
|
|
107
|
+
return (
|
|
108
|
+
-self._unit_factor(unit)
|
|
109
|
+
* self.es.weak_layer.kt
|
|
110
|
+
* (
|
|
111
|
+
self.dw_dx(Z) * self.es.weak_layer.h / 2
|
|
112
|
+
- self.u(Z, h0=self.es.slab.H / 2)
|
|
113
|
+
)
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
def eps(self, Z: np.ndarray) -> float | np.ndarray:
|
|
117
|
+
"""Weak-layer normal strain"""
|
|
118
|
+
return -self.w(Z) / self.es.weak_layer.h
|
|
119
|
+
|
|
120
|
+
def gamma(self, Z: np.ndarray) -> float | np.ndarray:
|
|
121
|
+
"""Weak-layer shear strain."""
|
|
122
|
+
return (
|
|
123
|
+
self.dw_dx(Z) / 2 - self.u(Z, h0=self.es.slab.H / 2) / self.es.weak_layer.h
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
def Gi(self, Ztip: np.ndarray, unit: EnergyUnit = "kJ/m^2") -> float | np.ndarray:
|
|
127
|
+
"""Mode I differential energy release rate at crack tip.
|
|
128
|
+
|
|
129
|
+
Arguments
|
|
130
|
+
---------
|
|
131
|
+
Ztip : ndarray
|
|
132
|
+
Solution vector [u(x) u'(x) w(x) w'(x) psi(x) psi'(x)]^T
|
|
133
|
+
at the crack tip.
|
|
134
|
+
unit : {'N/mm', 'kJ/m^2', 'J/m^2'}, optional
|
|
135
|
+
Desired output unit. Default is kJ/m^2.
|
|
136
|
+
"""
|
|
137
|
+
return (
|
|
138
|
+
self._unit_factor(unit) * self.sig(Ztip) ** 2 / (2 * self.es.weak_layer.kn)
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
def Gii(self, Ztip: np.ndarray, unit: EnergyUnit = "kJ/m^2") -> float | np.ndarray:
|
|
142
|
+
"""Mode II differential energy release rate at crack tip.
|
|
143
|
+
|
|
144
|
+
Arguments
|
|
145
|
+
---------
|
|
146
|
+
Ztip : ndarray
|
|
147
|
+
Solution vector [u(x) u'(x) w(x) w'(x) psi(x) psi'(x)]^T
|
|
148
|
+
at the crack tip.
|
|
149
|
+
unit : {'N/mm', 'kJ/m^2', 'J/m^2'}, optional
|
|
150
|
+
Desired output unit. Default is kJ/m^2 = N/mm.
|
|
151
|
+
"""
|
|
152
|
+
return (
|
|
153
|
+
self._unit_factor(unit) * self.tau(Ztip) ** 2 / (2 * self.es.weak_layer.kt)
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
def dz_dx(self, z: np.ndarray, phi: float, qs: float = 0) -> np.ndarray:
|
|
157
|
+
"""First derivative z'(x) = K*z(x) + q of the solution vector.
|
|
158
|
+
|
|
159
|
+
z'(x) = [u'(x) u''(x) w'(x) w''(x) psi'(x), psi''(x)]^T
|
|
160
|
+
|
|
161
|
+
Parameters
|
|
162
|
+
----------
|
|
163
|
+
z : ndarray
|
|
164
|
+
Solution vector [u(x) u'(x) w(x) w'(x) psi(x), psi'(x)]^T
|
|
165
|
+
phi : float
|
|
166
|
+
Inclination (degrees). Counterclockwise positive.
|
|
167
|
+
|
|
168
|
+
Returns
|
|
169
|
+
-------
|
|
170
|
+
ndarray
|
|
171
|
+
First derivative z'(x) for the solution vector (6x1).
|
|
172
|
+
"""
|
|
173
|
+
K = self.es.K
|
|
174
|
+
q = self.es.get_load_vector(phi=phi, qs=qs)
|
|
175
|
+
return np.dot(K, z) + q
|
|
176
|
+
|
|
177
|
+
def dz_dxdx(self, z: np.ndarray, phi: float, qs: float) -> np.ndarray:
|
|
178
|
+
"""
|
|
179
|
+
Get second derivative z''(x) = K*z'(x) of the solution vector.
|
|
180
|
+
|
|
181
|
+
z''(x) = [u''(x) u'''(x) w''(x) w'''(x) psi''(x), psi'''(x)]^T
|
|
182
|
+
|
|
183
|
+
Parameters
|
|
184
|
+
----------
|
|
185
|
+
z : ndarray
|
|
186
|
+
Solution vector [u(x) u'(x) w(x) w'(x) psi(x), psi'(x)]^T
|
|
187
|
+
phi : float
|
|
188
|
+
Inclination (degrees). Counterclockwise positive.
|
|
189
|
+
|
|
190
|
+
Returns
|
|
191
|
+
-------
|
|
192
|
+
ndarray
|
|
193
|
+
Second derivative z''(x) = (K*z(x) + q)' = K*z'(x) = K*(K*z(x) + q)
|
|
194
|
+
of the solution vector (6x1).
|
|
195
|
+
"""
|
|
196
|
+
K = self.es.K
|
|
197
|
+
q = self.es.get_load_vector(phi=phi, qs=qs)
|
|
198
|
+
dz_dx = np.dot(K, z) + q
|
|
199
|
+
return np.dot(K, dz_dx)
|
|
200
|
+
|
|
201
|
+
def du0_dxdx(self, z: np.ndarray, phi: float, qs: float) -> float | np.ndarray:
|
|
202
|
+
"""
|
|
203
|
+
Get second derivative of the horiz. centerline displacement u0''(x).
|
|
204
|
+
|
|
205
|
+
Parameters
|
|
206
|
+
----------
|
|
207
|
+
z : ndarray
|
|
208
|
+
Solution vector [u(x) u'(x) w(x) w'(x) psi(x) psi'(x)]^T.
|
|
209
|
+
phi : float
|
|
210
|
+
Inclination (degrees). Counterclockwise positive.
|
|
211
|
+
|
|
212
|
+
Returns
|
|
213
|
+
-------
|
|
214
|
+
ndarray, float
|
|
215
|
+
Second derivative of the horizontal centerline displacement
|
|
216
|
+
u0''(x) (1/mm).
|
|
217
|
+
"""
|
|
218
|
+
return self.dz_dx(z, phi, qs)[1, :]
|
|
219
|
+
|
|
220
|
+
def dpsi_dxdx(self, z: np.ndarray, phi: float, qs: float) -> float | np.ndarray:
|
|
221
|
+
"""
|
|
222
|
+
Get second derivative of the cross-section rotation psi''(x).
|
|
223
|
+
|
|
224
|
+
Parameters
|
|
225
|
+
----------
|
|
226
|
+
z : ndarray
|
|
227
|
+
Solution vector [u(x) u'(x) w(x) w'(x) psi(x) psi'(x)]^T.
|
|
228
|
+
phi : float
|
|
229
|
+
Inclination (degrees). Counterclockwise positive.
|
|
230
|
+
|
|
231
|
+
Returns
|
|
232
|
+
-------
|
|
233
|
+
ndarray, float
|
|
234
|
+
Second derivative of the cross-section rotation psi''(x) (1/mm^2).
|
|
235
|
+
"""
|
|
236
|
+
return self.dz_dx(z, phi, qs)[5, :]
|
|
237
|
+
|
|
238
|
+
def du0_dxdxdx(self, z: np.ndarray, phi: float, qs: float) -> float | np.ndarray:
|
|
239
|
+
"""
|
|
240
|
+
Get third derivative of the horiz. centerline displacement u0'''(x).
|
|
241
|
+
|
|
242
|
+
Parameters
|
|
243
|
+
----------
|
|
244
|
+
z : ndarray
|
|
245
|
+
Solution vector [u(x) u'(x) w(x) w'(x) psi(x) psi'(x)]^T.
|
|
246
|
+
phi : float
|
|
247
|
+
Inclination (degrees). Counterclockwise positive.
|
|
248
|
+
|
|
249
|
+
Returns
|
|
250
|
+
-------
|
|
251
|
+
ndarray, float
|
|
252
|
+
Third derivative of the horizontal centerline displacement
|
|
253
|
+
u0'''(x) (1/mm^2).
|
|
254
|
+
"""
|
|
255
|
+
return self.dz_dxdx(z, phi, qs)[1, :]
|
|
256
|
+
|
|
257
|
+
def dpsi_dxdxdx(self, z: np.ndarray, phi: float, qs: float) -> float | np.ndarray:
|
|
258
|
+
"""
|
|
259
|
+
Get third derivative of the cross-section rotation psi'''(x).
|
|
260
|
+
|
|
261
|
+
Parameters
|
|
262
|
+
----------
|
|
263
|
+
z : ndarray
|
|
264
|
+
Solution vector [u(x) u'(x) w(x) w'(x) psi(x) psi'(x)]^T.
|
|
265
|
+
phi : float
|
|
266
|
+
Inclination (degrees). Counterclockwise positive.
|
|
267
|
+
|
|
268
|
+
Returns
|
|
269
|
+
-------
|
|
270
|
+
ndarray, float
|
|
271
|
+
Third derivative of the cross-section rotation psi'''(x) (1/mm^3).
|
|
272
|
+
"""
|
|
273
|
+
return self.dz_dxdx(z, phi, qs)[5, :]
|