weac 3.0.0__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.
@@ -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, :]