lamkit 0.1.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.
- lamkit/__init__.py +19 -0
- lamkit/analysis/__init__.py +0 -0
- lamkit/analysis/buckling.py +406 -0
- lamkit/analysis/laminate.py +757 -0
- lamkit/analysis/larc05.py +977 -0
- lamkit/analysis/material.py +319 -0
- lamkit/components/_S.py +2563 -0
- lamkit/components/__init__.py +0 -0
- lamkit/components/_ii_F.py +5429 -0
- lamkit/components/build_k.py +192 -0
- lamkit/components/functions.py +68 -0
- lamkit/components/write_pre_integrated_terms.py +118 -0
- lamkit/components/write_shape_function.py +95 -0
- lamkit/lekhnitskii/__init__.py +22 -0
- lamkit/lekhnitskii/hole.py +400 -0
- lamkit/lekhnitskii/homogenisation.py +215 -0
- lamkit/lekhnitskii/loaded_hole.py +405 -0
- lamkit/lekhnitskii/unloaded_hole.py +258 -0
- lamkit/lekhnitskii/utils.py +162 -0
- lamkit/requirements.py +438 -0
- lamkit/utils.py +190 -0
- lamkit-0.1.0.dist-info/METADATA +80 -0
- lamkit-0.1.0.dist-info/RECORD +25 -0
- lamkit-0.1.0.dist-info/WHEEL +4 -0
- lamkit-0.1.0.dist-info/licenses/LICENSE +21 -0
lamkit/__init__.py
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
__version__ = '0.1.0'
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
from .analysis.material import Material, Ply
|
|
5
|
+
from .analysis.laminate import Laminate
|
|
6
|
+
from .analysis.larc05 import LaRC05
|
|
7
|
+
from lamkit.lekhnitskii.hole import Hole
|
|
8
|
+
from lamkit.lekhnitskii.unloaded_hole import UnloadedHole
|
|
9
|
+
from lamkit.requirements import EngineeringRequirements
|
|
10
|
+
|
|
11
|
+
__all__ = [
|
|
12
|
+
'Material',
|
|
13
|
+
'Ply',
|
|
14
|
+
'Laminate',
|
|
15
|
+
'LaRC05',
|
|
16
|
+
'Hole',
|
|
17
|
+
'UnloadedHole',
|
|
18
|
+
'Requirements',
|
|
19
|
+
]
|
|
File without changes
|
|
@@ -0,0 +1,406 @@
|
|
|
1
|
+
"""
|
|
2
|
+
This is a modified version of the composipy package.
|
|
3
|
+
It is used to calculate the buckling load of a laminate plate.
|
|
4
|
+
|
|
5
|
+
Reference:
|
|
6
|
+
https://github.com/rafaelpsilva07/composipy
|
|
7
|
+
|
|
8
|
+
Author: Runze Li @ Department of Aeronautics, Imperial College London
|
|
9
|
+
Date: 2026-03-25
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from itertools import product
|
|
13
|
+
from typing import Dict, Iterable, Tuple
|
|
14
|
+
|
|
15
|
+
import numpy as np
|
|
16
|
+
from scipy.sparse import csr_matrix
|
|
17
|
+
from scipy.sparse.linalg import eigsh
|
|
18
|
+
import matplotlib.pyplot as plt
|
|
19
|
+
import os
|
|
20
|
+
|
|
21
|
+
from lamkit.analysis.laminate import Laminate
|
|
22
|
+
from lamkit.components.functions import sxieta
|
|
23
|
+
from lamkit.components.build_k import (
|
|
24
|
+
calc_K11_ijkl,
|
|
25
|
+
calc_k12_ijkl,
|
|
26
|
+
calc_k13_ijkl,
|
|
27
|
+
calc_k21_ijkl,
|
|
28
|
+
calc_k22_ijkl,
|
|
29
|
+
calc_k23_ijkl,
|
|
30
|
+
calc_k31_ijkl,
|
|
31
|
+
calc_k32_ijkl,
|
|
32
|
+
calc_k33_ijkl,
|
|
33
|
+
calc_kG33_ijkl,
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
ConstraintDict = Dict[str, Iterable[str]]
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class BucklingAnalysis:
|
|
41
|
+
"""
|
|
42
|
+
Buckling analysis of a laminate plate based on Ritz approximation.
|
|
43
|
+
|
|
44
|
+
Parameters
|
|
45
|
+
----------
|
|
46
|
+
laminate : Laminate
|
|
47
|
+
Laminate object that provides `A`, `D`, and `ABD` stiffness matrices.
|
|
48
|
+
a : float
|
|
49
|
+
Plate length along x direction (mm).
|
|
50
|
+
b : float
|
|
51
|
+
Plate length along y direction (mm).
|
|
52
|
+
constraints : str | dict, default "PINNED"
|
|
53
|
+
Boundary-condition definition. Built-in options: "PINNED", "CLAMPED".
|
|
54
|
+
A custom dict can define edge constraints for keys `x0`, `xa`, `y0`, `yb`.
|
|
55
|
+
Nxx, Nyy, Nxy : float, default 0.0
|
|
56
|
+
In-plane pre-buckling loads, force per unit length (N/mm).
|
|
57
|
+
`Fx = Nxx * b`, `Fy = Nyy * a`, `Fxy = Nxy * a * b`.
|
|
58
|
+
m, n : int, default 10
|
|
59
|
+
Number of Ritz terms in x and y directions.
|
|
60
|
+
"""
|
|
61
|
+
|
|
62
|
+
def __init__(self, laminate: Laminate, a: float, b: float,
|
|
63
|
+
constraints: str | ConstraintDict = "PINNED",
|
|
64
|
+
Nxx: float = 0.0, Nyy: float = 0.0, Nxy: float = 0.0,
|
|
65
|
+
m: int = 10, n: int = 10) -> None:
|
|
66
|
+
|
|
67
|
+
if not isinstance(laminate, Laminate):
|
|
68
|
+
raise TypeError("laminate must be a lamkit.analysis.laminate.Laminate instance")
|
|
69
|
+
if float(a) <= 0 or float(b) <= 0:
|
|
70
|
+
raise ValueError("a and b must be positive")
|
|
71
|
+
if int(m) < 1 or int(n) < 1:
|
|
72
|
+
raise ValueError("m and n must be >= 1")
|
|
73
|
+
|
|
74
|
+
self._laminate = laminate
|
|
75
|
+
self._a = float(a)
|
|
76
|
+
self._b = float(b)
|
|
77
|
+
self._constraints = constraints
|
|
78
|
+
self._Nxx = float(Nxx)
|
|
79
|
+
self._Nyy = float(Nyy)
|
|
80
|
+
self._Nxy = float(Nxy)
|
|
81
|
+
self._m = int(m)
|
|
82
|
+
self._n = int(n)
|
|
83
|
+
|
|
84
|
+
self.su_idx = None
|
|
85
|
+
self.sv_idx = None
|
|
86
|
+
self.sw_idx = None
|
|
87
|
+
self.eigenvalue = None
|
|
88
|
+
self.eigenvector = None
|
|
89
|
+
|
|
90
|
+
@property
|
|
91
|
+
def laminate(self) -> Laminate:
|
|
92
|
+
"""Return the laminate object used by this analysis."""
|
|
93
|
+
return self._laminate
|
|
94
|
+
|
|
95
|
+
@property
|
|
96
|
+
def a(self) -> float:
|
|
97
|
+
"""Return plate length in x direction."""
|
|
98
|
+
return self._a
|
|
99
|
+
|
|
100
|
+
@property
|
|
101
|
+
def b(self) -> float:
|
|
102
|
+
"""Return plate length in y direction."""
|
|
103
|
+
return self._b
|
|
104
|
+
|
|
105
|
+
@property
|
|
106
|
+
def constraints(self) -> str | ConstraintDict:
|
|
107
|
+
"""Return boundary condition definition."""
|
|
108
|
+
return self._constraints
|
|
109
|
+
|
|
110
|
+
@property
|
|
111
|
+
def Nxx(self) -> float:
|
|
112
|
+
"""Return in-plane normal load in x direction."""
|
|
113
|
+
return self._Nxx
|
|
114
|
+
|
|
115
|
+
@property
|
|
116
|
+
def Nyy(self) -> float:
|
|
117
|
+
"""Return in-plane normal load in y direction."""
|
|
118
|
+
return self._Nyy
|
|
119
|
+
|
|
120
|
+
@property
|
|
121
|
+
def Nxy(self) -> float:
|
|
122
|
+
"""Return in-plane shear load."""
|
|
123
|
+
return self._Nxy
|
|
124
|
+
|
|
125
|
+
@property
|
|
126
|
+
def m(self) -> int:
|
|
127
|
+
"""Return number of Ritz terms along x."""
|
|
128
|
+
return self._m
|
|
129
|
+
|
|
130
|
+
@property
|
|
131
|
+
def n(self) -> int:
|
|
132
|
+
"""Return number of Ritz terms along y."""
|
|
133
|
+
return self._n
|
|
134
|
+
|
|
135
|
+
def _compute_constraints(self) -> Tuple[list, list, list]:
|
|
136
|
+
"""
|
|
137
|
+
Build Ritz index sets that satisfy the selected boundary conditions.
|
|
138
|
+
|
|
139
|
+
The method filters basis-function families for in-plane (`u`, `v`) and
|
|
140
|
+
out-of-plane (`w`) fields according to constrained translations/rotations
|
|
141
|
+
on each edge, then builds Cartesian products used for matrix assembly.
|
|
142
|
+
|
|
143
|
+
Returns
|
|
144
|
+
-------
|
|
145
|
+
tuple[list, list, list]
|
|
146
|
+
`(uidx, vidx, widx)` index lists for assembling stiffness terms.
|
|
147
|
+
"""
|
|
148
|
+
if self.constraints == "PINNED":
|
|
149
|
+
x0 = xa = y0 = yb = ["TX", "TY", "TZ"]
|
|
150
|
+
elif self.constraints == "CLAMPED":
|
|
151
|
+
x0 = xa = y0 = yb = ["TX", "TY", "TZ", "RX", "RY", "RZ"]
|
|
152
|
+
else:
|
|
153
|
+
if not isinstance(self.constraints, dict):
|
|
154
|
+
raise TypeError("constraints must be 'PINNED', 'CLAMPED' or a constraint dict")
|
|
155
|
+
x0 = list(self.constraints.get("x0", []))
|
|
156
|
+
xa = list(self.constraints.get("xa", []))
|
|
157
|
+
y0 = list(self.constraints.get("y0", []))
|
|
158
|
+
yb = list(self.constraints.get("yb", []))
|
|
159
|
+
|
|
160
|
+
sm = [i for i in range(self.m + 4)]
|
|
161
|
+
sn = [i for i in range(self.n + 4)]
|
|
162
|
+
|
|
163
|
+
um, un = sm.copy(), sn.copy()
|
|
164
|
+
vm, vn = sm.copy(), sn.copy()
|
|
165
|
+
wm, wn = sm.copy(), sn.copy()
|
|
166
|
+
|
|
167
|
+
if "TX" in x0:
|
|
168
|
+
um.remove(0)
|
|
169
|
+
if "TY" in x0:
|
|
170
|
+
vm.remove(0)
|
|
171
|
+
if "TZ" in x0:
|
|
172
|
+
wm.remove(0)
|
|
173
|
+
if "RX" in x0:
|
|
174
|
+
um.remove(1)
|
|
175
|
+
if "RY" in x0:
|
|
176
|
+
vm.remove(1)
|
|
177
|
+
if "RZ" in x0:
|
|
178
|
+
wm.remove(1)
|
|
179
|
+
|
|
180
|
+
if "TX" in xa:
|
|
181
|
+
um.remove(2)
|
|
182
|
+
if "TY" in xa:
|
|
183
|
+
vm.remove(2)
|
|
184
|
+
if "TZ" in xa:
|
|
185
|
+
wm.remove(2)
|
|
186
|
+
if "RX" in xa:
|
|
187
|
+
um.remove(3)
|
|
188
|
+
if "RY" in xa:
|
|
189
|
+
vm.remove(3)
|
|
190
|
+
if "RZ" in xa:
|
|
191
|
+
wm.remove(3)
|
|
192
|
+
|
|
193
|
+
if "TX" in y0:
|
|
194
|
+
un.remove(0)
|
|
195
|
+
if "TY" in y0:
|
|
196
|
+
vn.remove(0)
|
|
197
|
+
if "TZ" in y0:
|
|
198
|
+
wn.remove(0)
|
|
199
|
+
if "RX" in y0:
|
|
200
|
+
un.remove(1)
|
|
201
|
+
if "RY" in y0:
|
|
202
|
+
vn.remove(1)
|
|
203
|
+
if "RZ" in y0:
|
|
204
|
+
wn.remove(1)
|
|
205
|
+
|
|
206
|
+
if "TX" in yb:
|
|
207
|
+
un.remove(2)
|
|
208
|
+
if "TY" in yb:
|
|
209
|
+
vn.remove(2)
|
|
210
|
+
if "TZ" in yb:
|
|
211
|
+
wn.remove(2)
|
|
212
|
+
if "RX" in yb:
|
|
213
|
+
un.remove(3)
|
|
214
|
+
if "RY" in yb:
|
|
215
|
+
vn.remove(3)
|
|
216
|
+
if "RZ" in yb:
|
|
217
|
+
wn.remove(3)
|
|
218
|
+
|
|
219
|
+
um, un = um[0 : self.m], un[0 : self.n]
|
|
220
|
+
vm, vn = vm[0 : self.m], vn[0 : self.n]
|
|
221
|
+
wm, wn = wm[0 : self.m], wn[0 : self.n]
|
|
222
|
+
|
|
223
|
+
uidx = list(product(um, un, um, un))
|
|
224
|
+
vidx = list(product(vm, vn, vm, vn))
|
|
225
|
+
widx = list(product(wm, wn, wm, wn))
|
|
226
|
+
|
|
227
|
+
self.su_idx = list(product(um, un))
|
|
228
|
+
self.sv_idx = list(product(vm, vn))
|
|
229
|
+
self.sw_idx = list(product(wm, wn))
|
|
230
|
+
|
|
231
|
+
return (uidx, vidx, widx)
|
|
232
|
+
|
|
233
|
+
def calc_K_KG_ABD(self) -> Tuple[np.ndarray, np.ndarray]:
|
|
234
|
+
"""
|
|
235
|
+
Assemble structural and geometric stiffness matrices using full ABD coupling.
|
|
236
|
+
|
|
237
|
+
Returns
|
|
238
|
+
-------
|
|
239
|
+
tuple[np.ndarray, np.ndarray]
|
|
240
|
+
`(K, KG)` where `K` is the structural stiffness matrix and `KG` is
|
|
241
|
+
the geometric stiffness matrix.
|
|
242
|
+
"""
|
|
243
|
+
# Read laminate extensional/coupling/bending stiffness entries.
|
|
244
|
+
A11, A12, A16, B11, B12, B16 = self.laminate.ABD[0, :]
|
|
245
|
+
A12, A22, A26, B12, B22, B26 = self.laminate.ABD[1, :]
|
|
246
|
+
A16, A26, A66, B16, B26, B66 = self.laminate.ABD[2, :]
|
|
247
|
+
B11, B12, B16, D11, D12, D16 = self.laminate.ABD[3, :]
|
|
248
|
+
B12, B22, B26, D12, D22, D26 = self.laminate.ABD[4, :]
|
|
249
|
+
B16, B26, B66, D16, D26, D66 = self.laminate.ABD[5, :]
|
|
250
|
+
|
|
251
|
+
k11, k12, k13, k21, k22, k23, k31, k32, k33 = [], [], [], [], [], [], [], [], []
|
|
252
|
+
k33g = []
|
|
253
|
+
|
|
254
|
+
uidx, vidx, widx = self._compute_constraints()
|
|
255
|
+
size = self.m**2 * self.n**2
|
|
256
|
+
|
|
257
|
+
# Loop over Ritz index combinations and evaluate pre-integrated terms.
|
|
258
|
+
for i in range(size):
|
|
259
|
+
ui, uj, uk, ul = uidx[i]
|
|
260
|
+
vi, vj, vk, vl = vidx[i]
|
|
261
|
+
wi, wj, wk, wl = widx[i]
|
|
262
|
+
|
|
263
|
+
k11.append(calc_K11_ijkl(self.a, self.b, ui, uj, uk, ul, A11, A16, A66))
|
|
264
|
+
k12.append(calc_k12_ijkl(self.a, self.b, ui, uj, vk, vl, A12, A16, A26, A66))
|
|
265
|
+
k13.append(calc_k13_ijkl(self.a, self.b, ui, uj, wk, wl, B11, B12, B16, B26, B66))
|
|
266
|
+
k21.append(calc_k21_ijkl(self.a, self.b, vi, vj, uk, ul, A12, A16, A26, A66))
|
|
267
|
+
k22.append(calc_k22_ijkl(self.a, self.b, vi, vj, vk, vl, A22, A26, A66))
|
|
268
|
+
k23.append(calc_k23_ijkl(self.a, self.b, vi, vj, wk, wl, B12, B16, B22, B26, B66))
|
|
269
|
+
k31.append(calc_k31_ijkl(self.a, self.b, wi, wj, uk, ul, B11, B12, B16, B26, B66))
|
|
270
|
+
k32.append(calc_k32_ijkl(self.a, self.b, wi, wj, vk, vl, B11, B12, B16, B22, B26, B66))
|
|
271
|
+
k33.append(calc_k33_ijkl(self.a, self.b, wi, wj, wk, wl, D11, D12, D22, D16, D26, D66))
|
|
272
|
+
k33g.append(calc_kG33_ijkl(self.a, self.b, wi, wj, wk, wl, self.Nxx, self.Nyy, self.Nxy))
|
|
273
|
+
|
|
274
|
+
dim = self.m * self.n
|
|
275
|
+
k11 = np.array(k11).reshape(dim, dim)
|
|
276
|
+
k12 = np.array(k12).reshape(dim, dim)
|
|
277
|
+
k13 = np.array(k13).reshape(dim, dim)
|
|
278
|
+
k21 = np.array(k21).reshape(dim, dim)
|
|
279
|
+
k22 = np.array(k22).reshape(dim, dim)
|
|
280
|
+
k23 = np.array(k23).reshape(dim, dim)
|
|
281
|
+
k31 = np.array(k31).reshape(dim, dim)
|
|
282
|
+
k32 = np.array(k32).reshape(dim, dim)
|
|
283
|
+
k33 = np.array(k33).reshape(dim, dim)
|
|
284
|
+
k00 = np.zeros((dim, dim))
|
|
285
|
+
k33g = np.array(k33g).reshape(dim, dim)
|
|
286
|
+
|
|
287
|
+
# Build global block matrices:
|
|
288
|
+
# K = [[Kuu, Kuv, Kuw], [Kvu, Kvv, Kvw], [Kwu, Kwv, Kww]]
|
|
289
|
+
K = np.vstack([np.hstack([k11, k12, k13]), np.hstack([k21, k22, k23]), np.hstack([k31, k32, k33])])
|
|
290
|
+
KG = np.vstack([np.hstack([k00, k00, k00]), np.hstack([k00, k00, k00]), np.hstack([k00, k00, k33g])])
|
|
291
|
+
|
|
292
|
+
return K, KG
|
|
293
|
+
|
|
294
|
+
def calc_K_KG_D(self) -> Tuple[np.ndarray, np.ndarray]:
|
|
295
|
+
"""
|
|
296
|
+
Assemble stiffness matrices using bending-only (`D`) approximation.
|
|
297
|
+
|
|
298
|
+
This reduced formulation keeps only the transverse displacement field
|
|
299
|
+
in the buckling eigen problem.
|
|
300
|
+
|
|
301
|
+
Returns
|
|
302
|
+
-------
|
|
303
|
+
tuple[np.ndarray, np.ndarray]
|
|
304
|
+
`(K, KG)` bending stiffness and geometric stiffness matrices.
|
|
305
|
+
"""
|
|
306
|
+
# Read bending stiffness matrix entries.
|
|
307
|
+
D11, D12, D16 = self.laminate.D[0, :]
|
|
308
|
+
D12, D22, D26 = self.laminate.D[1, :]
|
|
309
|
+
D16, D26, D66 = self.laminate.D[2, :]
|
|
310
|
+
|
|
311
|
+
k33 = []
|
|
312
|
+
k33g = []
|
|
313
|
+
_, _, widx = self._compute_constraints()
|
|
314
|
+
size = self.m**2 * self.n**2
|
|
315
|
+
|
|
316
|
+
# Evaluate bending and geometric terms for each Ritz pair.
|
|
317
|
+
for i in range(size):
|
|
318
|
+
wi, wj, wk, wl = widx[i]
|
|
319
|
+
k33.append(calc_k33_ijkl(self.a, self.b, wi, wj, wk, wl, D11, D12, D22, D16, D26, D66))
|
|
320
|
+
k33g.append(calc_kG33_ijkl(self.a, self.b, wi, wj, wk, wl, self.Nxx, self.Nyy, self.Nxy))
|
|
321
|
+
|
|
322
|
+
dim = self.m * self.n
|
|
323
|
+
K = np.array(k33).reshape(dim, dim)
|
|
324
|
+
KG = np.array(k33g).reshape(dim, dim)
|
|
325
|
+
return K, KG
|
|
326
|
+
|
|
327
|
+
def buckling_analysis(self, num_eigvalues: int = 5) -> Tuple[np.ndarray, np.ndarray]:
|
|
328
|
+
"""
|
|
329
|
+
Solve the generalized eigenvalue buckling problem.
|
|
330
|
+
|
|
331
|
+
The current implementation uses the bending-only matrices from
|
|
332
|
+
`calc_K_KG_D` and solves:
|
|
333
|
+
KG * phi = lambda * K * phi
|
|
334
|
+
then converts to load multipliers as `-1/lambda`.
|
|
335
|
+
|
|
336
|
+
Parameters
|
|
337
|
+
----------
|
|
338
|
+
num_eigvalues : int, default 5
|
|
339
|
+
Requested number of lowest-magnitude eigenvalues.
|
|
340
|
+
|
|
341
|
+
Returns
|
|
342
|
+
-------
|
|
343
|
+
tuple[np.ndarray, np.ndarray]
|
|
344
|
+
Eigenvalues (load multipliers) and eigenvectors.
|
|
345
|
+
"""
|
|
346
|
+
K, KG = self.calc_K_KG_D()
|
|
347
|
+
K, KG = csr_matrix(K), csr_matrix(KG)
|
|
348
|
+
|
|
349
|
+
k = min(int(num_eigvalues), KG.shape[0] - 2)
|
|
350
|
+
eigvals, eigvecs = eigsh(A=KG, k=k, which="SM", M=K, tol=0.0, sigma=1.0, mode="cayley")
|
|
351
|
+
eigvals = -1.0 / eigvals
|
|
352
|
+
|
|
353
|
+
self.eigenvalue, self.eigenvector = eigvals, eigvecs
|
|
354
|
+
return eigvals, eigvecs
|
|
355
|
+
|
|
356
|
+
|
|
357
|
+
def plot_buckling_modes(analysis: BucklingAnalysis,
|
|
358
|
+
eigvals: np.ndarray, n_modes: int,
|
|
359
|
+
ngridx: int, ngridy: int, case_text: str, save_path: str) -> None:
|
|
360
|
+
"""
|
|
361
|
+
Plot several buckling modes in one figure with multiple subplots.
|
|
362
|
+
"""
|
|
363
|
+
n_modes = min(int(n_modes), int(eigvals.shape[0]))
|
|
364
|
+
n_cols = 2
|
|
365
|
+
n_rows = (n_modes + n_cols - 1) // n_cols
|
|
366
|
+
|
|
367
|
+
xi_arr = np.linspace(-1.0, 1.0, ngridx)
|
|
368
|
+
eta_arr = np.linspace(-1.0, 1.0, ngridy)
|
|
369
|
+
xi_mesh, eta_mesh = np.meshgrid(xi_arr, eta_arr)
|
|
370
|
+
x_mesh = (analysis.a / 2.0) * (xi_mesh + 1.0)
|
|
371
|
+
y_mesh = (analysis.b / 2.0) * (eta_mesh + 1.0)
|
|
372
|
+
|
|
373
|
+
fig = plt.figure(figsize=(7 * n_cols, 5.5 * n_rows))
|
|
374
|
+
|
|
375
|
+
for mode_idx in range(n_modes):
|
|
376
|
+
c_values = analysis.eigenvector[:, mode_idx]
|
|
377
|
+
len_w = len(analysis.sw_idx)
|
|
378
|
+
cw_values = c_values[-len_w:]
|
|
379
|
+
|
|
380
|
+
z = np.zeros((ngridx, ngridy))
|
|
381
|
+
for i in range(ngridx):
|
|
382
|
+
for j in range(ngridy):
|
|
383
|
+
sw = sxieta(analysis.sw_idx, xi_mesh[i, j], eta_mesh[i, j])
|
|
384
|
+
z[i, j] = float(sw @ cw_values)
|
|
385
|
+
|
|
386
|
+
is_buckled = float(eigvals[mode_idx]) <= 1.0
|
|
387
|
+
buckle_text = "buckled" if is_buckled else "not-buckled"
|
|
388
|
+
|
|
389
|
+
ax = fig.add_subplot(n_rows, n_cols, mode_idx + 1, projection="3d")
|
|
390
|
+
ax.plot_surface(x_mesh, y_mesh, z, cmap="coolwarm")
|
|
391
|
+
ax.set_title(f"Mode {mode_idx + 1} | {buckle_text}\n(lambda={eigvals[mode_idx]:.3f})")
|
|
392
|
+
ax.set_xticks(np.linspace(0.0, max(analysis.a, analysis.b), 5))
|
|
393
|
+
ax.set_yticks(np.linspace(0.0, max(analysis.a, analysis.b), 5))
|
|
394
|
+
ax.set_xlabel("x (mm)")
|
|
395
|
+
ax.set_ylabel("y (mm)")
|
|
396
|
+
ax.set_zlabel("w (mm)")
|
|
397
|
+
|
|
398
|
+
fig.suptitle(f"Case: {case_text}", fontsize=14)
|
|
399
|
+
fig.tight_layout(rect=(0.0, 0.0, 1.0, 0.96))
|
|
400
|
+
|
|
401
|
+
out_dir = os.path.dirname(save_path)
|
|
402
|
+
if out_dir:
|
|
403
|
+
os.makedirs(out_dir, exist_ok=True)
|
|
404
|
+
fig.savefig(save_path, dpi=180, bbox_inches="tight")
|
|
405
|
+
plt.close(fig)
|
|
406
|
+
|