weac 3.0.0__py3-none-any.whl → 3.0.2__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.
@@ -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
+
8
+ import numpy as np
9
+
10
+ from weac.components import WeakLayer
11
+ from weac.constants import SHEAR_CORRECTION_FACTOR
12
+ from weac.core.slab import Slab
13
+ from weac.utils.misc import decompose_to_normal_tangential
14
+
15
+ logger = logging.getLogger(__name__)
16
+
17
+
18
+ class Eigensystem:
19
+ """
20
+ Calculates system properties and solves the eigenvalue problem
21
+ for a layered beam on an elastic foundation (Winkler model).
22
+
23
+ Attributes
24
+ ----------
25
+ weak_layer: WeakLayer
26
+ slab: Slab
27
+
28
+ System properties
29
+ -----------------
30
+ A11: float # extensional stiffness
31
+ B11: float # coupling stiffness
32
+ D11: float # bending stiffness
33
+ kA55: float # shear stiffness
34
+ K0: float # foundation stiffness
35
+
36
+ Eigenvalues and Eigenvectors
37
+ ----------------------------
38
+ ewC: NDArray[np.complex128] # shape (k): Complex Eigenvalues
39
+ ewR: NDArray[np.float64] # shape (k): Real Eigenvalues
40
+ evC: NDArray[np.complex128] # shape (6, k): Complex Eigenvectors
41
+ evR: NDArray[np.float64] # shape (6, k): Real Eigenvectors
42
+ sR: NDArray[np.float64] # shape (k): Real positive eigenvalue shifts
43
+ # (for numerical robustness)
44
+ sC: NDArray[np.float64] # shape (k): Complex positive eigenvalue shifts
45
+ # (for numerical robustness)
46
+ """
47
+
48
+ # Input data
49
+ weak_layer: WeakLayer
50
+ slab: Slab
51
+
52
+ # System properties
53
+ A11: float # extensional stiffness
54
+ B11: float # coupling stiffness
55
+ D11: float # bending stiffness
56
+ kA55: float # shear stiffness
57
+ K0: float # foundation stiffness
58
+
59
+ K: np.ndarray # System Matrix
60
+
61
+ # Eigenvalues and Eigenvectors
62
+ ewC: np.ndarray[np.complex128] # shape (k): Complex Eigenvalues
63
+ ewR: np.ndarray[np.float64] # shape (k): Real Eigenvalues
64
+ evC: np.ndarray[np.complex128] # shape (6, k): Complex Eigenvectors
65
+ evR: np.ndarray[np.float64] # shape (6, k): Real Eigenvectors
66
+ sR: np.ndarray[
67
+ np.float64
68
+ ] # shape (k): Real positive eigenvalue shifts (for numerical robustness)
69
+ sC: np.ndarray[
70
+ np.float64
71
+ ] # shape (k): Complex positive eigenvalue shifts (for numerical robustness)
72
+
73
+ def __init__(self, weak_layer: WeakLayer, slab: Slab):
74
+ self.slab = slab
75
+ self.weak_layer = weak_layer
76
+
77
+ self.calc_eigensystem()
78
+
79
+ def calc_eigensystem(self):
80
+ """Calculate the fundamental system of the problem."""
81
+ self._calc_laminate_stiffness_parameters()
82
+ self.K = self.assemble_system_matrix(kn=None, kt=None)
83
+ self.ewC, self.ewR, self.evC, self.evR, self.sR, self.sC = (
84
+ self.calc_eigenvalues_and_eigenvectors(self.K)
85
+ )
86
+
87
+ def _calc_laminate_stiffness_parameters(self):
88
+ """
89
+ Provide ABD matrix.
90
+
91
+ Return plane-strain laminate stiffness matrix (ABD matrix).
92
+ """
93
+ # Append z_{1} at top of surface layer
94
+ zis = np.concatenate(([-self.slab.H / 2], self.slab.zi_bottom))
95
+
96
+ # Initialize stiffness components
97
+ A11, B11, D11, kA55 = 0, 0, 0, 0
98
+ # Add layerwise contributions
99
+ for i in range(len(zis) - 1):
100
+ E = self.slab.Ei[i]
101
+ G = self.slab.Gi[i]
102
+ nu = self.slab.nui[i]
103
+ A11 += E / (1 - nu**2) * (zis[i + 1] - zis[i])
104
+ B11 += 1 / 2 * E / (1 - nu**2) * (zis[i + 1] ** 2 - zis[i] ** 2)
105
+ D11 += 1 / 3 * E / (1 - nu**2) * (zis[i + 1] ** 3 - zis[i] ** 3)
106
+ kA55 += SHEAR_CORRECTION_FACTOR * G * (zis[i + 1] - zis[i])
107
+
108
+ self.A11 = A11
109
+ self.B11 = B11
110
+ self.D11 = D11
111
+ self.kA55 = kA55
112
+ self.K0 = B11**2 - A11 * D11
113
+
114
+ def assemble_system_matrix(
115
+ self, kn: float | None, kt: float | None
116
+ ) -> np.ndarray[np.float64]:
117
+ """
118
+ Assemble first-order ODE system matrix K.
119
+
120
+ Using the solution vector z = [u, u', w, w', psi, psi']
121
+ the ODE system is written in the form Az' + Bz = d
122
+ and rearranged to z' = -(A^-1)Bz + (A^-1)d = Kz + q
123
+
124
+ Returns
125
+ -------
126
+ NDArray[np.float64]
127
+ System matrix K (6x6).
128
+ """
129
+ kn = kn or self.weak_layer.kn
130
+ kt = kt or self.weak_layer.kt
131
+ H = self.slab.H # total slab thickness
132
+ h = self.weak_layer.h # weak layer thickness
133
+
134
+ # Abbreviations
135
+ K21 = kt * (-2 * self.D11 + self.B11 * (H + h)) / (2 * self.K0)
136
+ K24 = (
137
+ 2 * self.D11 * kt * h
138
+ - self.B11 * kt * h * (H + h)
139
+ + 4 * self.B11 * self.kA55
140
+ ) / (4 * self.K0)
141
+ K25 = (
142
+ -2 * self.D11 * H * kt
143
+ + self.B11 * H * kt * (H + h)
144
+ + 4 * self.B11 * self.kA55
145
+ ) / (4 * self.K0)
146
+ K43 = kn / self.kA55
147
+ K61 = kt * (2 * self.B11 - self.A11 * (H + h)) / (2 * self.K0)
148
+ K64 = (
149
+ -2 * self.B11 * kt * h
150
+ + self.A11 * kt * h * (H + h)
151
+ - 4 * self.A11 * self.kA55
152
+ ) / (4 * self.K0)
153
+ K65 = (
154
+ 2 * self.B11 * H * kt
155
+ - self.A11 * H * kt * (H + h)
156
+ - 4 * self.A11 * self.kA55
157
+ ) / (4 * self.K0)
158
+
159
+ # System matrix
160
+ K = [
161
+ [0, 1, 0, 0, 0, 0],
162
+ [K21, 0, 0, K24, K25, 0],
163
+ [0, 0, 0, 1, 0, 0],
164
+ [0, 0, K43, 0, 0, -1],
165
+ [0, 0, 0, 0, 0, 1],
166
+ [K61, 0, 0, K64, K65, 0],
167
+ ]
168
+
169
+ return np.array(K, dtype=np.float64)
170
+
171
+ def calc_eigenvalues_and_eigenvectors(
172
+ self, system_matrix: np.ndarray[np.float64]
173
+ ) -> tuple[
174
+ np.ndarray[np.complex128],
175
+ np.ndarray[np.float64],
176
+ np.ndarray[np.complex128],
177
+ np.ndarray[np.float64],
178
+ np.ndarray[np.float64],
179
+ np.ndarray[np.float64],
180
+ ]:
181
+ """
182
+ Calculate eigenvalues and eigenvectors of the system matrix.
183
+
184
+ Parameters:
185
+ -----------
186
+ system_matrix: NDArray # system_matrix size (6x6) of the eigenvalue problem
187
+
188
+ Return:
189
+ -------
190
+ ewC: NDArray[np.complex128] # shape (k): Complex Eigenvalues
191
+ ewR: NDArray[np.float64] # shape (g): Real Eigenvalues
192
+ evC: NDArray[np.complex128] # shape (6, k): Complex Eigenvectors
193
+ evR: NDArray[np.float64] # shape (6, g): Real Eigenvectors
194
+ sR: NDArray[np.float64] # shape (k): Real positive eigenvalue shifts
195
+ # (for numerical robustness)
196
+ sC: NDArray[np.float64] # shape (g): Complex positive eigenvalue shifts
197
+ # (for numerical robustness)
198
+ """
199
+ # Calculate eigenvalues (ew) and eigenvectors (ev)
200
+ ew, ev = np.linalg.eig(system_matrix)
201
+ # Classify real and complex eigenvalues
202
+ real = (ew.imag == 0) & (ew.real != 0) # real eigenvalues
203
+ cmplx = ew.imag > 0 # positive complex conjugates
204
+ # Eigenvalues
205
+ ewC = ew[cmplx]
206
+ ewR = ew[real].real
207
+ # Eigenvectors
208
+ evC = ev[:, cmplx]
209
+ evR = ev[:, real].real
210
+ # Prepare positive eigenvalue shifts for numerical robustness
211
+ # 1. Keep small-positive eigenvalues away from zero, to not have a near-singular matrix
212
+ sR, sC = np.zeros(ewR.shape), np.zeros(ewC.shape)
213
+ sR[ewR > 0], sC[ewC > 0] = -1, -1
214
+ return ewC, ewR, evC, evR, sR, sC
215
+
216
+ def zh(
217
+ self, x: float, length: float = 0, has_foundation: bool = True
218
+ ) -> np.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
+ ) -> np.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) -> np.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, :]