kib-lap 0.5__cp313-cp313-win_amd64.whl → 0.7.7__cp313-cp313-win_amd64.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.
- KIB_LAP/Betonbau/TEST_Rectangular.py +21 -0
- KIB_LAP/Betonbau/beam_rectangular.py +4 -0
- KIB_LAP/FACHWERKEBEN/Elements.py +209 -0
- KIB_LAP/FACHWERKEBEN/InputData.py +118 -0
- KIB_LAP/FACHWERKEBEN/Iteration.py +967 -0
- KIB_LAP/FACHWERKEBEN/Materials.py +30 -0
- KIB_LAP/FACHWERKEBEN/Plotting.py +681 -0
- KIB_LAP/FACHWERKEBEN/__init__.py +4 -0
- KIB_LAP/FACHWERKEBEN/main.py +27 -0
- KIB_LAP/Plattentragwerke/PlateBendingKirchhoff.py +36 -29
- KIB_LAP/STABRAUM/InputData.py +13 -2
- KIB_LAP/STABRAUM/Output_Data.py +61 -0
- KIB_LAP/STABRAUM/Plotting.py +1453 -0
- KIB_LAP/STABRAUM/Programm.py +518 -1026
- KIB_LAP/STABRAUM/Steifigkeitsmatrix.py +338 -117
- KIB_LAP/STABRAUM/main.py +58 -0
- KIB_LAP/STABRAUM/results.py +37 -0
- KIB_LAP/Scheibe/Assemble_Stiffness.py +246 -0
- KIB_LAP/Scheibe/Element_Stiffness.py +362 -0
- KIB_LAP/Scheibe/Meshing.py +365 -0
- KIB_LAP/Scheibe/Output.py +34 -0
- KIB_LAP/Scheibe/Plotting.py +722 -0
- KIB_LAP/Scheibe/Shell_Calculation.py +523 -0
- KIB_LAP/Scheibe/Testing_Mesh.py +25 -0
- KIB_LAP/Scheibe/__init__.py +14 -0
- KIB_LAP/Scheibe/main.py +33 -0
- KIB_LAP/StabEbenRitz/Biegedrillknicken.py +757 -0
- KIB_LAP/StabEbenRitz/Biegedrillknicken_Trigeometry.py +328 -0
- KIB_LAP/StabEbenRitz/Querschnittswerte.py +527 -0
- KIB_LAP/StabEbenRitz/Stabberechnung_Klasse.py +868 -0
- KIB_LAP/plate_bending_cpp.cp313-win_amd64.pyd +0 -0
- KIB_LAP/plate_buckling_cpp.cp313-win_amd64.pyd +0 -0
- {kib_lap-0.5.dist-info → kib_lap-0.7.7.dist-info}/METADATA +1 -1
- {kib_lap-0.5.dist-info → kib_lap-0.7.7.dist-info}/RECORD +37 -19
- Examples/Cross_Section_Thin.py +0 -61
- KIB_LAP/Betonbau/Bemessung_Zust_II.py +0 -648
- KIB_LAP/Betonbau/Iterative_Design.py +0 -723
- KIB_LAP/Plattentragwerke/NumInte.cpp +0 -23
- KIB_LAP/Plattentragwerke/NumericalIntegration.cpp +0 -23
- KIB_LAP/Plattentragwerke/plate_bending_cpp.cp313-win_amd64.pyd +0 -0
- KIB_LAP/main.py +0 -2
- {Examples → KIB_LAP/StabEbenRitz}/__init__.py +0 -0
- {kib_lap-0.5.dist-info → kib_lap-0.7.7.dist-info}/WHEEL +0 -0
- {kib_lap-0.5.dist-info → kib_lap-0.7.7.dist-info}/top_level.txt +0 -0
KIB_LAP/STABRAUM/Programm.py
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
# STABRAUM-Dependencies
|
|
2
|
+
try:
|
|
3
|
+
from Steifigkeitsmatrix import *
|
|
4
|
+
from InputData import Input
|
|
5
|
+
from results import AnalysisResults
|
|
6
|
+
except:
|
|
7
|
+
from KIB_LAP.STABRAUM.Steifigkeitsmatrix import *
|
|
8
|
+
from KIB_LAP.STABRAUM.InputData import Input
|
|
9
|
+
from KIB_LAP.STABRAUM.results import AnalysisResults
|
|
10
|
+
|
|
11
|
+
|
|
3
12
|
import sympy as sp
|
|
4
13
|
|
|
5
14
|
import matplotlib.pyplot as plt
|
|
@@ -10,6 +19,7 @@ import matplotlib.patches as mpatches
|
|
|
10
19
|
from matplotlib.widgets import Slider
|
|
11
20
|
from mpl_toolkits.mplot3d.art3d import Line3DCollection
|
|
12
21
|
|
|
22
|
+
|
|
13
23
|
def _set_axes_equal(ax, extra: float = 0.0):
|
|
14
24
|
"""
|
|
15
25
|
Erzwingt identische numerische Achsenlimits.
|
|
@@ -64,8 +74,96 @@ class mainloop:
|
|
|
64
74
|
self.MTS_el_i_store = np.zeros((len(self.Inp.members), 2, 1))
|
|
65
75
|
self.N_el_i_store = np.zeros((len(self.Inp.members), 2, 1))
|
|
66
76
|
|
|
67
|
-
|
|
68
|
-
|
|
77
|
+
# ------------------------------------------------------------
|
|
78
|
+
# DEBUG / SINGULARITÄTSTEST
|
|
79
|
+
# ------------------------------------------------------------
|
|
80
|
+
def check_singularity(self, K=None, tol_row=1e-10, tol_rank=1e-8, verbose=True):
|
|
81
|
+
"""
|
|
82
|
+
Prüft typische Ursachen für Singularität:
|
|
83
|
+
- Rank-Defizit
|
|
84
|
+
- Nullzeilen/Nullspalten (DOFs ohne Steifigkeit)
|
|
85
|
+
- isolierte Knoten (nicht in members)
|
|
86
|
+
- 0-Längen-Elemente
|
|
87
|
+
Gibt ein Dict mit Diagnosen zurück.
|
|
88
|
+
"""
|
|
89
|
+
if K is None:
|
|
90
|
+
K = self.GesMat
|
|
91
|
+
|
|
92
|
+
info = {}
|
|
93
|
+
|
|
94
|
+
# -------- Rank --------
|
|
95
|
+
try:
|
|
96
|
+
r = np.linalg.matrix_rank(K, tol=tol_rank)
|
|
97
|
+
except TypeError:
|
|
98
|
+
# falls numpy Version keine tol als kw hat
|
|
99
|
+
r = np.linalg.matrix_rank(K)
|
|
100
|
+
n = K.shape[0]
|
|
101
|
+
info["rank"] = int(r)
|
|
102
|
+
info["n"] = int(n)
|
|
103
|
+
info["deficit"] = int(n - r)
|
|
104
|
+
|
|
105
|
+
# -------- Nullzeilen/Nullspalten --------
|
|
106
|
+
row_norm = np.linalg.norm(K, axis=1)
|
|
107
|
+
col_norm = np.linalg.norm(K, axis=0)
|
|
108
|
+
zero_rows = np.where(row_norm < tol_row)[0]
|
|
109
|
+
zero_cols = np.where(col_norm < tol_row)[0]
|
|
110
|
+
|
|
111
|
+
info["zero_rows"] = zero_rows.tolist()
|
|
112
|
+
info["zero_cols"] = zero_cols.tolist()
|
|
113
|
+
|
|
114
|
+
# -------- DOF Mapping (nur wenn 7 dof/node) --------
|
|
115
|
+
def gdof_to_node_ldof(gdof):
|
|
116
|
+
return int(gdof // 7 + 1), int(gdof % 7)
|
|
117
|
+
|
|
118
|
+
info["zero_rows_nodes"] = [gdof_to_node_ldof(i) for i in zero_rows]
|
|
119
|
+
info["zero_cols_nodes"] = [gdof_to_node_ldof(i) for i in zero_cols]
|
|
120
|
+
|
|
121
|
+
# -------- isolierte Knoten --------
|
|
122
|
+
try:
|
|
123
|
+
na = list(self.Inp.members["na"])
|
|
124
|
+
ne = list(self.Inp.members["ne"])
|
|
125
|
+
used = set(na) | set(ne)
|
|
126
|
+
all_nodes = set(range(1, len(self.Inp.nodes["x[m]"]) + 1))
|
|
127
|
+
isolated = sorted(all_nodes - used)
|
|
128
|
+
except Exception:
|
|
129
|
+
isolated = []
|
|
130
|
+
info["isolated_nodes"] = isolated
|
|
131
|
+
|
|
132
|
+
# -------- 0-Längen-Elemente --------
|
|
133
|
+
zero_len_elems = []
|
|
134
|
+
try:
|
|
135
|
+
for eidx, (a, e) in enumerate(
|
|
136
|
+
zip(self.Inp.members["na"], self.Inp.members["ne"]), start=1
|
|
137
|
+
):
|
|
138
|
+
xa = self.Inp.nodes["x[m]"][a - 1]
|
|
139
|
+
ya = self.Inp.nodes["y[m]"][a - 1]
|
|
140
|
+
za = self.Inp.nodes["z[m]"][a - 1]
|
|
141
|
+
xb = self.Inp.nodes["x[m]"][e - 1]
|
|
142
|
+
yb = self.Inp.nodes["y[m]"][e - 1]
|
|
143
|
+
zb = self.Inp.nodes["z[m]"][e - 1]
|
|
144
|
+
L = float(np.sqrt((xa - xb) ** 2 + (ya - yb) ** 2 + (za - zb) ** 2))
|
|
145
|
+
if L < 1e-12:
|
|
146
|
+
zero_len_elems.append((eidx, int(a), int(e)))
|
|
147
|
+
except Exception:
|
|
148
|
+
pass
|
|
149
|
+
info["zero_length_elements"] = zero_len_elems
|
|
150
|
+
|
|
151
|
+
# -------- Ausgabe --------
|
|
152
|
+
if verbose:
|
|
153
|
+
print("\n=== Singularity check ===")
|
|
154
|
+
print(f"size n = {n}, rank = {r}, deficit = {n-r}")
|
|
155
|
+
print(f"zero rows: {len(zero_rows)}, zero cols: {len(zero_cols)}")
|
|
156
|
+
if len(zero_rows) > 0:
|
|
157
|
+
print("first zero rows (gdof -> node,ldof):")
|
|
158
|
+
for gdof in zero_rows[:30]:
|
|
159
|
+
node, ldof = gdof_to_node_ldof(gdof)
|
|
160
|
+
print(f" gdof {gdof:4d} -> node {node}, ldof {ldof}")
|
|
161
|
+
if len(isolated) > 0:
|
|
162
|
+
print("isolated nodes:", isolated)
|
|
163
|
+
if len(zero_len_elems) > 0:
|
|
164
|
+
print("zero-length elements (eid, na, ne):", zero_len_elems)
|
|
165
|
+
|
|
166
|
+
return info
|
|
69
167
|
|
|
70
168
|
def CalculateTransMat(self):
|
|
71
169
|
print("Calculate Transmatrices")
|
|
@@ -185,7 +283,7 @@ class mainloop:
|
|
|
185
283
|
)
|
|
186
284
|
|
|
187
285
|
K_el_i = np.matmul(
|
|
188
|
-
self.TransMats[i]
|
|
286
|
+
self.TransMats[i], np.matmul(K_el_i, self.TransMats[i].T)
|
|
189
287
|
)
|
|
190
288
|
|
|
191
289
|
self.K_el_i_store[i] = K_el_i
|
|
@@ -236,174 +334,124 @@ class mainloop:
|
|
|
236
334
|
|
|
237
335
|
def LocalLoadVectorLine(self):
|
|
238
336
|
"""
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
Each local force vector is stored in a separate local force vector, \n
|
|
242
|
-
which is taken into account, when the inner forces are calculated afterwards \n
|
|
243
|
-
Members of the local load vector are: \n
|
|
244
|
-
- Temperature loading \n
|
|
245
|
-
- Member forces \n
|
|
337
|
+
Lokale Festendkräfte aus Linienlasten (lokales System!).
|
|
338
|
+
Speichert in self.S_loc_elem_line und gibt dieses Array zurück.
|
|
246
339
|
"""
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
# Element Loading (line loads, single loads)
|
|
340
|
+
n_elem = len(self.Inp.members["na"])
|
|
341
|
+
self.S_loc_elem_line = np.zeros((14, n_elem), dtype=float)
|
|
251
342
|
|
|
252
343
|
res_mbr = self.Inp.ElementLoads["Member"]
|
|
253
344
|
res_line_a = self.Inp.ElementLoads["qza"]
|
|
254
|
-
res_line_b = self.Inp.ElementLoads["qze"]
|
|
255
|
-
|
|
256
|
-
for i in range(0, len(res_mbr)):
|
|
257
|
-
j = int(res_mbr[i] - 1)
|
|
258
|
-
print("JJJ")
|
|
259
|
-
print(j)
|
|
260
|
-
print(res_mbr)
|
|
261
|
-
TransMatj = self.TransMats[j]
|
|
262
|
-
|
|
263
|
-
self.S_loc_elem_line[3, j] = (
|
|
264
|
-
+res_line_a[i] * self.member_length[j] / 2
|
|
265
|
-
) # VZ
|
|
266
|
-
self.S_loc_elem_line[10, j] = +res_line_a[i] * self.member_length[j] / 2
|
|
267
|
-
|
|
268
|
-
self.S_loc_elem_line[4, j] = (
|
|
269
|
-
-res_line_a[i] * self.member_length[j] ** 2 / 12
|
|
270
|
-
) # MY
|
|
271
|
-
self.S_loc_elem_line[11, j] = (
|
|
272
|
-
+res_line_a[i] * self.member_length[j] ** 2 / 12
|
|
273
|
-
)
|
|
345
|
+
res_line_b = self.Inp.ElementLoads["qze"] # aktuell nicht benutzt
|
|
274
346
|
|
|
275
|
-
|
|
347
|
+
for k in range(len(res_mbr)):
|
|
348
|
+
e = int(res_mbr[k] - 1)
|
|
276
349
|
|
|
277
|
-
|
|
350
|
+
L = float(self.member_length[e])
|
|
351
|
+
q = float(res_line_a[k]) # TODO: falls qza != qze -> konsistente Formeln
|
|
278
352
|
|
|
279
|
-
|
|
353
|
+
# Lokale Festendkräfte (deine Konvention beibehalten)
|
|
354
|
+
# Vz
|
|
355
|
+
self.S_loc_elem_line[3, e] += +q * L / 2.0
|
|
356
|
+
self.S_loc_elem_line[10, e] += +q * L / 2.0
|
|
280
357
|
|
|
281
|
-
|
|
358
|
+
# My
|
|
359
|
+
self.S_loc_elem_line[4, e] += -q * L**2 / 12.0
|
|
360
|
+
self.S_loc_elem_line[11, e] += +q * L**2 / 12.0
|
|
361
|
+
|
|
362
|
+
return self.S_loc_elem_line
|
|
282
363
|
|
|
283
364
|
def LocalLoadVectorTemp(self):
|
|
284
365
|
"""
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
Each local force vector is stored in a separate local force vector, \n
|
|
288
|
-
which is taken into account, when the inner forces are calculated afterwards \n
|
|
289
|
-
Members of the local load vector are: \n
|
|
290
|
-
- Temperature loading \n
|
|
291
|
-
- Member forces \n
|
|
366
|
+
Lokale Festendkräfte aus Temperatur (lokales System!).
|
|
367
|
+
Speichert in self.S_loc_elem_temp und gibt dieses Array zurück.
|
|
292
368
|
"""
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
# Temperature Loading
|
|
369
|
+
n_elem = len(self.Inp.members["na"])
|
|
370
|
+
self.S_loc_elem_temp = np.zeros((14, n_elem), dtype=float)
|
|
297
371
|
|
|
298
372
|
res_mbr = self.Inp.TemperatureForces["Member"]
|
|
299
|
-
|
|
300
373
|
res_tem_dT = self.Inp.TemperatureForces["dT[K]"]
|
|
301
374
|
res_tem_dTz = self.Inp.TemperatureForces["dTz[K]"]
|
|
302
375
|
res_tem_dTy = self.Inp.TemperatureForces["dTy[K]"]
|
|
303
376
|
|
|
304
|
-
for
|
|
305
|
-
|
|
306
|
-
TransMatj = self.TransMats[j]
|
|
307
|
-
# Local temperature loadings
|
|
308
|
-
self.S_loc_elem_temp[0, j] = (
|
|
309
|
-
-self.ElemStem.E * self.ElemStem.A * 1e-5 * res_tem_dT[i]
|
|
310
|
-
) # N
|
|
311
|
-
self.S_loc_elem_temp[7, j] = (
|
|
312
|
-
+self.ElemStem.E * self.ElemStem.A * 1e-5 * res_tem_dT[i]
|
|
313
|
-
)
|
|
377
|
+
for k in range(len(res_mbr)):
|
|
378
|
+
e = int(res_mbr[k] - 1)
|
|
314
379
|
|
|
315
|
-
self.
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
self.S_loc_elem_temp[9, j] = (
|
|
319
|
-
+self.ElemStem.E * self.ElemStem.I_z * 1e-5 * res_tem_dTy[i]
|
|
320
|
-
)
|
|
380
|
+
EA = self.ElemStem.E * self.ElemStem.A
|
|
381
|
+
EIz = self.ElemStem.E * self.ElemStem.I_z
|
|
382
|
+
EIy = self.ElemStem.E * self.ElemStem.I_y
|
|
321
383
|
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
)
|
|
325
|
-
self.S_loc_elem_temp[11, j] = (
|
|
326
|
-
+self.ElemStem.E * self.ElemStem.I_y * 1e-5 * res_tem_dTz[i]
|
|
327
|
-
)
|
|
384
|
+
dT = float(res_tem_dT[k])
|
|
385
|
+
dTz = float(res_tem_dTz[k])
|
|
386
|
+
dTy = float(res_tem_dTy[k])
|
|
328
387
|
|
|
329
|
-
|
|
388
|
+
# N
|
|
389
|
+
self.S_loc_elem_temp[0, e] += -EA * 1e-5 * dT
|
|
390
|
+
self.S_loc_elem_temp[7, e] += +EA * 1e-5 * dT
|
|
330
391
|
|
|
331
|
-
#
|
|
392
|
+
# Mz
|
|
393
|
+
self.S_loc_elem_temp[2, e] += -EIz * 1e-5 * dTy
|
|
394
|
+
self.S_loc_elem_temp[9, e] += +EIz * 1e-5 * dTy
|
|
332
395
|
|
|
333
|
-
|
|
396
|
+
# My
|
|
397
|
+
self.S_loc_elem_temp[4, e] += -EIy * 1e-5 * dTz
|
|
398
|
+
self.S_loc_elem_temp[11, e] += +EIy * 1e-5 * dTz
|
|
334
399
|
|
|
335
|
-
return
|
|
400
|
+
return self.S_loc_elem_temp
|
|
336
401
|
|
|
337
402
|
def GlobalLoadVector(self):
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
403
|
+
"""
|
|
404
|
+
Globaler Lastvektor:
|
|
405
|
+
- Element-Festendkräfte (Temp + Linienlast) lokal -> global via T
|
|
406
|
+
- Knotenlasten (global) addieren
|
|
407
|
+
"""
|
|
408
|
+
# erzeugt/updated self.S_loc_elem_temp / self.S_loc_elem_line
|
|
409
|
+
self.LocalLoadVectorTemp()
|
|
410
|
+
self.LocalLoadVectorLine()
|
|
346
411
|
|
|
347
|
-
|
|
412
|
+
n_elem = len(self.Inp.members["na"])
|
|
413
|
+
F_glob = np.zeros(self.Inp.nDoF, dtype=float)
|
|
348
414
|
|
|
349
415
|
na_memb = self.Inp.members["na"]
|
|
350
416
|
ne_memb = self.Inp.members["ne"]
|
|
351
417
|
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
node_j = ne_memb[i]
|
|
418
|
+
for e in range(n_elem):
|
|
419
|
+
node_i = int(na_memb[e])
|
|
420
|
+
node_j = int(ne_memb[e])
|
|
356
421
|
|
|
357
|
-
|
|
358
|
-
|
|
422
|
+
base_i = 7 * (node_i - 1)
|
|
423
|
+
base_j = 7 * (node_j - 1)
|
|
359
424
|
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
dof_Vza = 7 * (node_i - 1) + 3
|
|
364
|
-
dof_Vze = 7 * (node_j - 1) + 3
|
|
365
|
-
|
|
366
|
-
dof_Mza = 7 * (node_i - 1) + 2
|
|
367
|
-
dof_Mze = 7 * (node_j - 1) + 2
|
|
368
|
-
|
|
369
|
-
dof_Mya = 7 * (node_i - 1) + 2
|
|
370
|
-
dof_Mye = 7 * (node_j - 1) + 2
|
|
371
|
-
|
|
372
|
-
F_glob[dof_Na] += F_loc_temp[0, i] + F_loc_line[0, i]
|
|
373
|
-
F_glob[dof_Ne] += F_loc_temp[7, i] + F_loc_line[7, i]
|
|
374
|
-
|
|
375
|
-
F_glob[dof_Vya] += F_loc_temp[1, i]
|
|
376
|
-
F_glob[dof_Vye] += F_loc_temp[8, i]
|
|
377
|
-
|
|
378
|
-
F_glob[dof_Vza] += F_loc_temp[3, i] + F_loc_line[3, i]
|
|
379
|
-
F_glob[dof_Vze] += F_loc_temp[10, i] + F_loc_line[10, i]
|
|
425
|
+
F_e_loc = (self.S_loc_elem_temp[:, e] + self.S_loc_elem_line[:, e]).reshape(
|
|
426
|
+
14, 1
|
|
427
|
+
)
|
|
380
428
|
|
|
381
|
-
|
|
382
|
-
|
|
429
|
+
T = self.TransMats[e] # local -> global
|
|
430
|
+
F_e_glob = (T @ F_e_loc).ravel() # (14,)
|
|
383
431
|
|
|
384
|
-
F_glob[
|
|
385
|
-
F_glob[
|
|
432
|
+
F_glob[base_i : base_i + 7] += F_e_glob[0:7]
|
|
433
|
+
F_glob[base_j : base_j + 7] += F_e_glob[7:14]
|
|
386
434
|
|
|
387
|
-
#
|
|
435
|
+
# Knotenlasten (global)
|
|
388
436
|
res_nodes = self.Inp.NodalForces["Node"]
|
|
389
437
|
res_dof = self.Inp.NodalForces["Dof"]
|
|
390
438
|
res_forc = self.Inp.NodalForces["Value[MN/MNm]"]
|
|
391
439
|
|
|
392
|
-
for
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
440
|
+
for k in range(len(res_nodes)):
|
|
441
|
+
node = int(res_nodes[k])
|
|
442
|
+
base = 7 * (node - 1)
|
|
443
|
+
dof = str(res_dof[k]).strip().lower()
|
|
444
|
+
|
|
445
|
+
if dof == "fx":
|
|
446
|
+
idx = base + 0
|
|
447
|
+
elif dof == "fy":
|
|
448
|
+
idx = base + 1
|
|
449
|
+
elif dof == "fz":
|
|
450
|
+
idx = base + 3
|
|
451
|
+
else:
|
|
452
|
+
continue
|
|
404
453
|
|
|
405
|
-
|
|
406
|
-
print("Local force vectors ", self.S_loc_elem_temp)
|
|
454
|
+
F_glob[idx] += float(res_forc[k])
|
|
407
455
|
|
|
408
456
|
return F_glob
|
|
409
457
|
|
|
@@ -431,973 +479,417 @@ class mainloop:
|
|
|
431
479
|
return u_el
|
|
432
480
|
|
|
433
481
|
def CalculateLocalInnerForces(self):
|
|
434
|
-
s_el = np.zeros([14, len(self.Inp.members["na"])])
|
|
435
|
-
|
|
436
|
-
for i in range(0, len(self.Inp.members["na"]), 1):
|
|
437
|
-
s_el[:, i] = np.matmul(self.K_el_i_store[i], self.u_el[:, i])
|
|
438
|
-
self.MZ_el_i_store[i] = np.array([s_el[2, i] * (-1), s_el[9, i]]).reshape(
|
|
439
|
-
2, 1
|
|
440
|
-
) # Left is *(-1)
|
|
441
|
-
self.MY_el_i_store[i] = np.array([s_el[4, i] * (-1), s_el[11, i]]).reshape(
|
|
442
|
-
2, 1
|
|
443
|
-
) # Left is *(-1)
|
|
444
|
-
self.MX_el_i_store[i] = np.array([s_el[5, i] * (-1), s_el[12, i]]).reshape(
|
|
445
|
-
2, 1
|
|
446
|
-
) # Left is *(-1)
|
|
447
|
-
|
|
448
|
-
self.N_el_i_store[i] = np.array([s_el[0, i] * (-1), s_el[7, i]]).reshape(
|
|
449
|
-
2, 1
|
|
450
|
-
) # Left is *(-1)
|
|
451
|
-
self.VY_el_i_store[i] = np.array([s_el[1, i] * (-1), s_el[8, i]]).reshape(
|
|
452
|
-
2, 1
|
|
453
|
-
) # Left is *(-1)
|
|
454
|
-
self.VZ_el_i_store[i] = np.array([s_el[3, i] * (-1), s_el[10, i]]).reshape(
|
|
455
|
-
2, 1
|
|
456
|
-
) # Left is *(-1)
|
|
457
|
-
|
|
458
|
-
# Explicit transformation in local coordinates
|
|
459
|
-
|
|
460
|
-
self.N_el_i_store[i] = (
|
|
461
|
-
self.N_el_i_store[i] * self.TransMats[i][0, 0]
|
|
462
|
-
+ self.VY_el_i_store[i] * self.TransMats[i][0, 1]
|
|
463
|
-
+ self.VZ_el_i_store[i] * self.TransMats[i][0, 3]
|
|
464
|
-
- np.array(
|
|
465
|
-
[-self.S_loc_elem_temp[0, i], self.S_loc_elem_temp[7, i]]
|
|
466
|
-
).reshape(
|
|
467
|
-
2, 1
|
|
468
|
-
) # Left is * (-1)
|
|
469
|
-
# - np.array([-self.S_loc_elem_line[0, i], self.S_loc_elem_line[7, i]]).reshape(2, 1) # Left is * (-1)
|
|
470
|
-
)
|
|
471
|
-
|
|
472
|
-
self.MY_el_i_store[i] = (
|
|
473
|
-
self.MX_el_i_store[i] * self.TransMats[i][4, 5]
|
|
474
|
-
+ self.MY_el_i_store[i] * self.TransMats[i][4, 4]
|
|
475
|
-
+ self.MZ_el_i_store[i] * self.TransMats[i][4, 2]
|
|
476
|
-
- np.array(
|
|
477
|
-
[-self.S_loc_elem_temp[4, i], self.S_loc_elem_temp[11, i]]
|
|
478
|
-
).reshape(
|
|
479
|
-
2, 1
|
|
480
|
-
) # Left is * (-1)
|
|
481
|
-
# - np.array([-self.S_loc_elem_line[4, i], self.S_loc_elem_line[11, i]]).reshape(2, 1) # Left is * (-1)
|
|
482
|
-
)
|
|
483
|
-
|
|
484
|
-
return s_el
|
|
485
|
-
|
|
486
|
-
def MainConvergence(self):
|
|
487
|
-
|
|
488
|
-
self.TransMats = self.CalculateTransMat()
|
|
489
|
-
self.GesMat = self.BuildStructureStiffnessMatrix()
|
|
490
|
-
|
|
491
|
-
self.RestraintData()
|
|
492
|
-
|
|
493
|
-
self.FGes = self.GlobalLoadVector()
|
|
494
|
-
|
|
495
|
-
self.u_ges = self.SolveDisplacement()
|
|
496
|
-
|
|
497
|
-
self.u_el = self.StoreLocalDisplacements()
|
|
498
|
-
|
|
499
|
-
self.s_el = self.CalculateLocalInnerForces()
|
|
500
|
-
|
|
501
|
-
def plot_structure_3d(self, nodes, na_memb, ne_memb):
|
|
502
482
|
"""
|
|
503
|
-
|
|
483
|
+
Berechnet lokale Schnittgrößen an linkem und rechtem Schnittufer:
|
|
504
484
|
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
"""
|
|
509
|
-
fig = plt.figure()
|
|
510
|
-
ax = fig.add_subplot(projection="3d")
|
|
511
|
-
|
|
512
|
-
# Plot all members
|
|
513
|
-
for i in range(len(na_memb)):
|
|
514
|
-
node_i = na_memb[i]
|
|
515
|
-
node_j = ne_memb[i]
|
|
516
|
-
|
|
517
|
-
ix = nodes["x[m]"][node_i - 1]
|
|
518
|
-
iy = nodes["y[m]"][node_i - 1]
|
|
519
|
-
iz = nodes["z[m]"][node_i - 1]
|
|
520
|
-
|
|
521
|
-
jx = nodes["x[m]"][node_j - 1]
|
|
522
|
-
jy = nodes["y[m]"][node_j - 1]
|
|
523
|
-
jz = nodes["z[m]"][node_j - 1]
|
|
485
|
+
- f_glob = K_el_global @ u_el_global (wie bei dir gespeichert)
|
|
486
|
+
- f_loc = T.T @ f_glob (global -> local, da T local->global)
|
|
487
|
+
- f_eff = f_loc - (f0_temp_loc + f0_line_loc)
|
|
524
488
|
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
489
|
+
Konvention:
|
|
490
|
+
- Linkes Schnittufer: s_L = - f_I,loc
|
|
491
|
+
- Rechtes Schnittufer: s_R = + f_J,loc
|
|
492
|
+
- Zug positiv
|
|
528
493
|
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
# Achsenbeschriftung und Titel
|
|
532
|
-
ax.set_xlabel("X [m]")
|
|
533
|
-
ax.set_ylabel("Y [m]")
|
|
534
|
-
ax.set_zlabel("Z [m]")
|
|
535
|
-
ax.set_title("Unverformte Struktur")
|
|
536
|
-
|
|
537
|
-
return fig, ax
|
|
538
|
-
|
|
539
|
-
def calculate_orthogonal_unit_vector_cross_product(self, xi, zi, xj, zj):
|
|
494
|
+
Ergebnis in self.*_el_i_store[i] jeweils als (2,1): [links; rechts]
|
|
540
495
|
"""
|
|
541
|
-
Berechnet einen normierten orthogonalen Vektor zur Strukturlinie von (xi, zi) nach (xj, zj)
|
|
542
|
-
mittels Kreuzprodukt.
|
|
543
496
|
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
- xj, zj: Koordinaten des Endknotens
|
|
497
|
+
n_elem = len(self.Inp.members["na"])
|
|
498
|
+
s_el = np.zeros((14, n_elem), dtype=float)
|
|
547
499
|
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
# Richtungsvektor in 3D (y-Komponente ist 0)
|
|
553
|
-
v = np.array([xj - xi, 0, zj - zi])
|
|
500
|
+
# Hole (lokale) Festendkräfte. Falls du die Arrays noch nicht als Attribute speicherst,
|
|
501
|
+
# werden sie hier neu erzeugt.
|
|
502
|
+
F_loc_temp = self.LocalLoadVectorTemp()
|
|
503
|
+
F_loc_line = self.LocalLoadVectorLine()
|
|
554
504
|
|
|
555
|
-
|
|
556
|
-
y_unit = np.array([0, 1, 0])
|
|
505
|
+
for e in range(n_elem):
|
|
557
506
|
|
|
558
|
-
|
|
559
|
-
|
|
507
|
+
# 1) Element-Endkräfte aus K*u (im Koordinatensystem von K_el_i_store & u_el)
|
|
508
|
+
f_glob = (self.K_el_i_store[e] @ self.u_el[:, e]).reshape(14, 1)
|
|
509
|
+
s_el[:, e] = f_glob.ravel()
|
|
560
510
|
|
|
561
|
-
|
|
562
|
-
|
|
511
|
+
# 2) global -> local
|
|
512
|
+
T = self.TransMats[e] # local -> global
|
|
513
|
+
f_loc = T.T @ f_glob # global -> local
|
|
563
514
|
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
if norm == 0:
|
|
567
|
-
raise ValueError(
|
|
568
|
-
"Die Strukturlinie hat keine Länge. Start- und Endknoten sind identisch."
|
|
569
|
-
)
|
|
515
|
+
# 3) lokale Festendkräfte (Temp + Linienlast)
|
|
516
|
+
f0_loc = (F_loc_temp[:, e] + F_loc_line[:, e]).reshape(14, 1)
|
|
570
517
|
|
|
571
|
-
|
|
518
|
+
# 4) wirksame lokale Endkräfte
|
|
519
|
+
f_eff = f_loc - f0_loc
|
|
572
520
|
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
521
|
+
# 5) Schnittufer-Abbildung: links = -f_I, rechts = +f_J
|
|
522
|
+
# Indizes laut Layout:
|
|
523
|
+
# [0] Na [1] Vya [2] Mza [3] Vza [4] Mya [5] Mxa [6] Mwa
|
|
524
|
+
# [7] Nb [8] Vyb [9] Mzb [10]Vzb [11]Myb [12]Mxb [13]Mwb
|
|
576
525
|
|
|
577
|
-
|
|
526
|
+
N_L, N_R = -f_eff[0, 0], f_eff[7, 0]
|
|
527
|
+
Vy_L, Vy_R = -f_eff[1, 0], f_eff[8, 0]
|
|
528
|
+
Mz_L, Mz_R = -f_eff[2, 0], f_eff[9, 0]
|
|
529
|
+
Vz_L, Vz_R = -f_eff[3, 0], f_eff[10, 0]
|
|
530
|
+
My_L, My_R = -f_eff[4, 0], f_eff[11, 0]
|
|
531
|
+
Mx_L, Mx_R = -f_eff[5, 0], f_eff[12, 0]
|
|
578
532
|
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
Parameter:
|
|
584
|
-
ax : matplotlib.axes.Axes
|
|
585
|
-
Die 2D-Achse zum Plotten
|
|
586
|
-
xi, zi : float
|
|
587
|
-
Startpunkt (globale Koordinaten in x und z)
|
|
588
|
-
my_local : float
|
|
589
|
-
Lokaler My-Wert
|
|
590
|
-
unit_vector : array-like, shape (2,)
|
|
591
|
-
Normalisierter orthogonaler Vektor für das Moment
|
|
592
|
-
scale : float, optional
|
|
593
|
-
Maßstabsfaktor für die Pfeillänge
|
|
594
|
-
"""
|
|
595
|
-
if my_local == 0:
|
|
596
|
-
return # Kein Moment zu plotten
|
|
597
|
-
|
|
598
|
-
# Kleine Verbindungslinie vom Knoten zum Start des Pfeils
|
|
599
|
-
connection_length = 0.05 * scale # Anpassbarer Wert
|
|
600
|
-
|
|
601
|
-
conn_x = xi + connection_length * unit_vector[0] * my_local * scale
|
|
602
|
-
conn_z = zi + connection_length * unit_vector[1] * my_local * scale
|
|
603
|
-
|
|
604
|
-
if my_local >= 0:
|
|
605
|
-
ax.plot([xi, conn_x], [zi, conn_z], color="blue", linewidth=1)
|
|
606
|
-
if my_local < 0:
|
|
607
|
-
ax.plot([xi, conn_x], [zi, conn_z], color="red", linewidth=1)
|
|
608
|
-
|
|
609
|
-
# Berechnung des Vektors, skaliert durch Moment und Maßstabsfaktor
|
|
610
|
-
vec = scale * my_local * unit_vector
|
|
611
|
-
|
|
612
|
-
# Farbwahl basierend auf dem Vorzeichen des Moments
|
|
613
|
-
color = "blue" if my_local >= 0 else "red"
|
|
614
|
-
|
|
615
|
-
# Plotten des Pfeils
|
|
616
|
-
# ax.arrow(conn_x, conn_z, vec[0], vec[1],
|
|
617
|
-
# head_width=0.05 * scale, head_length=0.1 * scale,
|
|
618
|
-
# fc=color, ec=color, length_includes_head=True)
|
|
619
|
-
|
|
620
|
-
# Berechnung des Endpunkts für die Textplatzierung
|
|
621
|
-
x_end = conn_x
|
|
622
|
-
z_end = conn_z
|
|
623
|
-
|
|
624
|
-
# Moment-Text
|
|
625
|
-
moment_text = f"My = {my_local:.3f} MNm"
|
|
626
|
-
|
|
627
|
-
# Textversatz für bessere Sichtbarkeit
|
|
628
|
-
text_offset = 0 # 0.05 * scale
|
|
629
|
-
ax.text(
|
|
630
|
-
x_end + text_offset * unit_vector[0],
|
|
631
|
-
z_end + text_offset * unit_vector[1],
|
|
632
|
-
moment_text,
|
|
633
|
-
color=color,
|
|
634
|
-
fontsize=8,
|
|
635
|
-
)
|
|
533
|
+
self.N_el_i_store[e] = np.array([N_L, N_R]).reshape(2, 1)
|
|
534
|
+
self.VY_el_i_store[e] = np.array([Vy_L, Vy_R]).reshape(2, 1)
|
|
535
|
+
self.VZ_el_i_store[e] = np.array([Vz_L, Vz_R]).reshape(2, 1)
|
|
636
536
|
|
|
637
|
-
|
|
537
|
+
self.MZ_el_i_store[e] = np.array([Mz_L, Mz_R]).reshape(2, 1)
|
|
538
|
+
self.MY_el_i_store[e] = np.array([My_L, My_R]).reshape(2, 1)
|
|
539
|
+
self.MX_el_i_store[e] = np.array([Mx_L, Mx_R]).reshape(2, 1)
|
|
638
540
|
|
|
639
|
-
|
|
640
|
-
"""
|
|
641
|
-
Plot My-Momente für alle Elemente an ihren Knoten in 2D (x-z-Ebene).
|
|
642
|
-
|
|
643
|
-
Parameter:
|
|
644
|
-
ax : matplotlib.axes.Axes
|
|
645
|
-
Die 2D-Achse zum Plotten
|
|
646
|
-
nodes : dict
|
|
647
|
-
Dictionary mit Knotenkordinaten
|
|
648
|
-
na_memb, ne_memb : Listen
|
|
649
|
-
Listen von Knotennummern für die Elemente
|
|
650
|
-
My_el_i_store : ndarray
|
|
651
|
-
Array von My-Werten pro Element und Knoten
|
|
652
|
-
scale : float, optional
|
|
653
|
-
Maßstabsfaktor für die Pfeile
|
|
654
|
-
"""
|
|
655
|
-
for i in range(len(na_memb)):
|
|
656
|
-
node_i = na_memb[i]
|
|
657
|
-
node_j = ne_memb[i]
|
|
658
|
-
|
|
659
|
-
ix = nodes["x[m]"][node_i - 1]
|
|
660
|
-
iz = nodes["z[m]"][node_i - 1]
|
|
661
|
-
|
|
662
|
-
jx = nodes["x[m]"][node_j - 1]
|
|
663
|
-
jz = nodes["z[m]"][node_j - 1]
|
|
664
|
-
|
|
665
|
-
# Berechne die Einheitsvektoren für positives und negatives My mittels Kreuzprodukt
|
|
666
|
-
try:
|
|
667
|
-
unit_vector_pos, unit_vector_neg = (
|
|
668
|
-
self.calculate_orthogonal_unit_vector_cross_product(ix, iz, jx, jz)
|
|
669
|
-
)
|
|
670
|
-
print(
|
|
671
|
-
f"Element {i+1}: unit_vector_pos = {unit_vector_pos}, unit_vector_neg = {unit_vector_neg}"
|
|
672
|
-
)
|
|
673
|
-
except ValueError as e:
|
|
674
|
-
print(f"Fehler bei Mitglied {i+1}: {e}")
|
|
675
|
-
continue
|
|
541
|
+
return s_el
|
|
676
542
|
|
|
677
|
-
|
|
678
|
-
My_a = My_el_i_store[i, 0, 0] # [i,0] -> Knoten a
|
|
679
|
-
My_b = My_el_i_store[i, 1, 0] # [i,1] -> Knoten b
|
|
680
|
-
|
|
681
|
-
try:
|
|
682
|
-
# 1) Pfeil am Knoten a
|
|
683
|
-
conn_ix, conn_iz = self.plot_moment_my_2d(
|
|
684
|
-
ax, ix, iz, My_a, unit_vector_pos, scale=scale
|
|
685
|
-
)
|
|
686
|
-
# 2) Pfeil am Knoten b
|
|
687
|
-
conn_jx, conn_jz = self.plot_moment_my_2d(
|
|
688
|
-
ax, jx, jz, My_b, unit_vector_pos, scale=scale
|
|
689
|
-
)
|
|
690
|
-
ax.plot(
|
|
691
|
-
[conn_ix, conn_jx], [conn_iz, conn_jz], color="black", linewidth=1
|
|
692
|
-
)
|
|
693
|
-
except:
|
|
694
|
-
pass
|
|
695
|
-
|
|
696
|
-
# Hinzufügen einer Legende zur Unterscheidung
|
|
697
|
-
red_patch = mpatches.Patch(color="blue", label="Positives My")
|
|
698
|
-
blue_patch = mpatches.Patch(color="red", label="Negatives My")
|
|
699
|
-
ax.legend(handles=[red_patch, blue_patch])
|
|
700
|
-
|
|
701
|
-
def plot_normalforce_N_2d(self, ax, xi, zi, N_local, unit_vector, scale=1):
|
|
543
|
+
def SpringsData(self):
|
|
702
544
|
"""
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
Startpunkt (globale Koordinaten in x und z)
|
|
710
|
-
N_local : float
|
|
711
|
-
Lokaler N-Wert
|
|
712
|
-
unit_vector : array-like, shape (2,)
|
|
713
|
-
Normalisierter orthogonaler Vektor für das Moment
|
|
714
|
-
scale : float, optional
|
|
715
|
-
Maßstabsfaktor für die Pfeillänge
|
|
545
|
+
Baut Koppel-Federn zwischen zwei Knoten in die globale Matrix self.GesMat ein.
|
|
546
|
+
CSV: node_a,node_e,dof,cp/cm[MN,m]
|
|
547
|
+
Annahmen:
|
|
548
|
+
- node_* ist 1-basiert
|
|
549
|
+
- dof ist 0-basiert (0..6)
|
|
550
|
+
- 7 DoF pro Knoten
|
|
716
551
|
"""
|
|
552
|
+
# Wenn Input die Datei nicht geladen hat: nichts tun
|
|
553
|
+
if not hasattr(self.Inp, "SpringsData"):
|
|
554
|
+
return
|
|
555
|
+
if self.Inp.SpringsData is None or len(self.Inp.SpringsData) == 0:
|
|
556
|
+
return
|
|
717
557
|
|
|
718
|
-
|
|
719
|
-
|
|
558
|
+
for _, row in self.Inp.SpringsData.iterrows():
|
|
559
|
+
na = int(row["node_a"])
|
|
560
|
+
ne = int(row["node_e"])
|
|
561
|
+
dof = int(row["dof"])
|
|
562
|
+
k = float(row["cp[MN]"])
|
|
720
563
|
|
|
721
|
-
|
|
722
|
-
|
|
564
|
+
ia = 7 * (na - 1) + dof
|
|
565
|
+
ie = 7 * (ne - 1) + dof
|
|
723
566
|
|
|
724
|
-
|
|
725
|
-
|
|
567
|
+
# 2x2 Feder-Block
|
|
568
|
+
self.GesMat[ia, ia] += k
|
|
569
|
+
self.GesMat[ia, ie] -= k
|
|
570
|
+
self.GesMat[ie, ia] -= k
|
|
571
|
+
self.GesMat[ie, ie] += k
|
|
726
572
|
|
|
727
|
-
if N_local >= 0:
|
|
728
|
-
ax.plot([xi, conn_x], [zi, conn_z], color="blue", linewidth=1)
|
|
729
|
-
if N_local < 0:
|
|
730
|
-
ax.plot([xi, conn_x], [zi, conn_z], color="red", linewidth=1)
|
|
731
573
|
|
|
732
|
-
# Berechnung des Vektors, skaliert durch Moment und Maßstabsfaktor
|
|
733
|
-
vec = scale * N_local * unit_vector
|
|
734
574
|
|
|
735
|
-
|
|
736
|
-
color = "blue" if N_local >= 0 else "red"
|
|
575
|
+
def MainConvergence(self):
|
|
737
576
|
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
# head_width=0.05 * scale, head_length=0.1 * scale,
|
|
741
|
-
# fc=color, ec=color, length_includes_head=True)
|
|
577
|
+
self.TransMats = self.CalculateTransMat()
|
|
578
|
+
self.GesMat = self.BuildStructureStiffnessMatrix()
|
|
742
579
|
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
580
|
+
self.RestraintData()
|
|
581
|
+
self.SpringsData() # ✅ HIER
|
|
582
|
+
# <<< HIER: Singularitäts-Check >>>
|
|
583
|
+
self.check_singularity(self.GesMat, verbose=True)
|
|
746
584
|
|
|
747
|
-
|
|
748
|
-
moment_text = f"N = {N_local:.3f} MNm"
|
|
585
|
+
self.FGes = self.GlobalLoadVector()
|
|
749
586
|
|
|
750
|
-
|
|
751
|
-
text_offset = 0 # 0.05 * scale
|
|
752
|
-
ax.text(
|
|
753
|
-
x_end + text_offset * unit_vector[0],
|
|
754
|
-
z_end + text_offset * unit_vector[1],
|
|
755
|
-
moment_text,
|
|
756
|
-
color=color,
|
|
757
|
-
fontsize=8,
|
|
758
|
-
)
|
|
587
|
+
self.u_ges = self.SolveDisplacement()
|
|
759
588
|
|
|
760
|
-
|
|
589
|
+
self.u_el = self.StoreLocalDisplacements()
|
|
761
590
|
|
|
762
|
-
|
|
763
|
-
"""
|
|
764
|
-
Plot N-Momente für alle Elemente an ihren Knoten in 2D (x-z-Ebene).
|
|
765
|
-
|
|
766
|
-
Parameter:
|
|
767
|
-
ax : matplotlib.axes.Axes
|
|
768
|
-
Die 2D-Achse zum Plotten
|
|
769
|
-
nodes : dict
|
|
770
|
-
Dictionary mit Knotenkordinaten
|
|
771
|
-
na_memb, ne_memb : Listen
|
|
772
|
-
Listen von Knotennummern für die Elemente
|
|
773
|
-
N_el_i_store : ndarray
|
|
774
|
-
Array von N-Werten pro Element und Knoten
|
|
775
|
-
scale : float, optional
|
|
776
|
-
Maßstabsfaktor für die Pfeile
|
|
777
|
-
"""
|
|
778
|
-
for i in range(len(na_memb)):
|
|
779
|
-
node_i = na_memb[i]
|
|
780
|
-
node_j = ne_memb[i]
|
|
781
|
-
|
|
782
|
-
ix = nodes["x[m]"][node_i - 1]
|
|
783
|
-
iz = nodes["z[m]"][node_i - 1]
|
|
784
|
-
|
|
785
|
-
jx = nodes["x[m]"][node_j - 1]
|
|
786
|
-
jz = nodes["z[m]"][node_j - 1]
|
|
787
|
-
|
|
788
|
-
# Berechne die Einheitsvektoren für positives und negatives My mittels Kreuzprodukt
|
|
789
|
-
try:
|
|
790
|
-
unit_vector_pos, unit_vector_neg = (
|
|
791
|
-
self.calculate_orthogonal_unit_vector_cross_product(ix, iz, jx, jz)
|
|
792
|
-
)
|
|
793
|
-
print(
|
|
794
|
-
f"Element {i+1}: unit_vector_pos = {unit_vector_pos}, unit_vector_neg = {unit_vector_neg}"
|
|
795
|
-
)
|
|
796
|
-
except ValueError as e:
|
|
797
|
-
print(f"Fehler bei Mitglied {i+1}: {e}")
|
|
798
|
-
continue
|
|
591
|
+
self.s_el = self.CalculateLocalInnerForces()
|
|
799
592
|
|
|
800
|
-
|
|
801
|
-
N_a = N_el_i_store[i, 0, 0] # [i,0] -> Knoten a
|
|
802
|
-
N_b = N_el_i_store[i, 1, 0] # [i,1] -> Knoten b
|
|
803
|
-
|
|
804
|
-
try:
|
|
805
|
-
# 1) Pfeil am Knoten a
|
|
806
|
-
conn_ix, conn_iz = self.plot_normalforce_N_2d(
|
|
807
|
-
ax, ix, iz, N_a, unit_vector_pos, scale=scale
|
|
808
|
-
)
|
|
809
|
-
# 2) Pfeil am Knoten b
|
|
810
|
-
conn_jx, conn_jz = self.plot_normalforce_N_2d(
|
|
811
|
-
ax, jx, jz, N_b, unit_vector_pos, scale=scale
|
|
812
|
-
)
|
|
813
|
-
ax.plot(
|
|
814
|
-
[conn_ix, conn_jx], [conn_iz, conn_jz], color="black", linewidth=1
|
|
815
|
-
)
|
|
816
|
-
except:
|
|
817
|
-
pass
|
|
818
|
-
|
|
819
|
-
# Hinzufügen einer Legende zur Unterscheidung
|
|
820
|
-
red_patch = mpatches.Patch(color="blue", label="Positives N")
|
|
821
|
-
blue_patch = mpatches.Patch(color="red", label="Negatives N")
|
|
822
|
-
ax.legend(handles=[red_patch, blue_patch])
|
|
823
|
-
|
|
824
|
-
def plot_structure_2d(self, nodes, na_memb, ne_memb):
|
|
593
|
+
def run(self) -> AnalysisResults:
|
|
825
594
|
"""
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
Parameter:
|
|
829
|
-
nodes: dict mit den Schlüsseln ['x[m]', 'y[m]', 'z[m]']
|
|
830
|
-
na_memb, ne_memb: Listen mit Knoten-IDs für Start und Ende der Elemente
|
|
595
|
+
Führt die komplette Berechnung aus und gibt ein Results-Objekt zurück.
|
|
831
596
|
"""
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
# Alle Elemente plotten
|
|
835
|
-
for i in range(len(na_memb)):
|
|
836
|
-
node_i = na_memb[i]
|
|
837
|
-
node_j = ne_memb[i]
|
|
597
|
+
self.MainConvergence()
|
|
838
598
|
|
|
839
|
-
|
|
840
|
-
|
|
599
|
+
return AnalysisResults(
|
|
600
|
+
Inp=self.Inp,
|
|
601
|
+
u_ges=self.u_ges,
|
|
602
|
+
GesMat=self.GesMat,
|
|
603
|
+
FGes=self.FGes,
|
|
604
|
+
TransMats=self.TransMats,
|
|
605
|
+
K_el_i_store=self.K_el_i_store,
|
|
606
|
+
u_el=self.u_el,
|
|
607
|
+
s_el=self.s_el,
|
|
608
|
+
N_el_i_store=self.N_el_i_store,
|
|
609
|
+
VY_el_i_store=self.VY_el_i_store,
|
|
610
|
+
VZ_el_i_store=self.VZ_el_i_store,
|
|
611
|
+
MX_el_i_store=self.MX_el_i_store,
|
|
612
|
+
MY_el_i_store=self.MY_el_i_store,
|
|
613
|
+
MZ_el_i_store=self.MZ_el_i_store,
|
|
614
|
+
member_length=np.array(self.member_length, dtype=float),
|
|
615
|
+
)
|
|
841
616
|
|
|
842
|
-
|
|
843
|
-
|
|
617
|
+
def check_global_equilibrium(self):
|
|
618
|
+
r = (
|
|
619
|
+
self.GesMat @ self.u_ges - self.FGes
|
|
620
|
+
).ravel() # Reaktionsvektor auf allen DOFs
|
|
844
621
|
|
|
845
|
-
|
|
846
|
-
|
|
622
|
+
# welche DOFs sind "gelagert"? (klassisch: Cp groß oder Fix)
|
|
623
|
+
# Bei dir: RestraintData addiert Cp auf Diagonale.
|
|
624
|
+
# -> als Näherung: DOF ist gelagert, wenn Cp > 0 in Input.
|
|
625
|
+
restrained = np.zeros_like(r, dtype=bool)
|
|
626
|
+
for _, row in self.Inp.RestraintData.iterrows():
|
|
627
|
+
gdof = 7 * (int(row["Node"]) - 1) + int(row["Dof"])
|
|
628
|
+
restrained[gdof] = True
|
|
847
629
|
|
|
848
|
-
|
|
630
|
+
free = ~restrained
|
|
849
631
|
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
ax.set_ylabel("Z [m]")
|
|
853
|
-
ax.set_title("Unverformte Struktur (x-z-Ebene)")
|
|
854
|
-
ax.grid(True)
|
|
632
|
+
print("max residual on FREE dofs:", np.max(np.abs(r[free])))
|
|
633
|
+
print("sum residual on FREE dofs:", np.sum(r[free]))
|
|
855
634
|
|
|
856
|
-
|
|
635
|
+
def debug_single_fx(self, node=2, Fx=1.0):
|
|
636
|
+
# setze alle Lasten leer
|
|
637
|
+
self.Inp.ElementLoads = self.Inp.ElementLoads.iloc[0:0].copy()
|
|
638
|
+
self.Inp.TemperatureForces = self.Inp.TemperatureForces.iloc[0:0].copy()
|
|
639
|
+
self.Inp.NodalForces = self.Inp.NodalForces.iloc[0:0].copy()
|
|
857
640
|
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
Kombinierte Methode zum Plotten der Struktur und der Biegemomente My in 2D (x-z-Ebene).
|
|
641
|
+
# eine Last
|
|
642
|
+
import pandas as pd
|
|
861
643
|
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
Skalierungsfaktor für die Momente
|
|
865
|
-
"""
|
|
866
|
-
fig, ax = self.plot_structure_2d(
|
|
867
|
-
self.Inp.nodes, self.Inp.members["na"], self.Inp.members["ne"]
|
|
644
|
+
self.Inp.NodalForces = pd.DataFrame(
|
|
645
|
+
[{"Node": node, "Dof": "Fx", "Value[MN/MNm]": Fx}]
|
|
868
646
|
)
|
|
869
647
|
|
|
870
|
-
|
|
871
|
-
self.plot_all_My_2d(
|
|
872
|
-
ax,
|
|
873
|
-
self.Inp.nodes,
|
|
874
|
-
self.Inp.members["na"],
|
|
875
|
-
self.Inp.members["ne"],
|
|
876
|
-
self.MY_el_i_store,
|
|
877
|
-
scale=scale,
|
|
878
|
-
)
|
|
648
|
+
self.MainConvergence()
|
|
879
649
|
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
650
|
+
ux = self.u_ges[7 * (node - 1) + 0]
|
|
651
|
+
uy = self.u_ges[7 * (node - 1) + 1]
|
|
652
|
+
uz = self.u_ges[7 * (node - 1) + 3]
|
|
653
|
+
print("ux, uy, uz =", ux, uy, uz)
|
|
654
|
+
|
|
655
|
+
def sum_reactions_fx(self):
|
|
656
|
+
r = (self.GesMat @ self.u_ges - self.FGes).ravel()
|
|
657
|
+
|
|
658
|
+
Rx = 0.0
|
|
659
|
+
for _, row in self.Inp.RestraintData.iterrows():
|
|
660
|
+
node = int(row["Node"])
|
|
661
|
+
dof = int(row["Dof"])
|
|
662
|
+
if dof == 0: # ux-DOF
|
|
663
|
+
Rx += r[7 * (node - 1) + 0]
|
|
664
|
+
|
|
665
|
+
Fx = 0.0
|
|
666
|
+
for _, row in self.Inp.NodalForces.iterrows():
|
|
667
|
+
if str(row["Dof"]).strip().lower() == "fx":
|
|
668
|
+
Fx += float(row["Value[MN/MNm]"])
|
|
669
|
+
|
|
670
|
+
print("Sum Fx (applied) =", Fx)
|
|
671
|
+
print("Sum Rx (support) =", Rx)
|
|
672
|
+
print("Fx + Rx =", Fx + Rx)
|
|
673
|
+
|
|
674
|
+
def sum_spring_reactions_fx(self):
|
|
675
|
+
u = self.u_ges.ravel()
|
|
676
|
+
|
|
677
|
+
Rx = 0.0
|
|
678
|
+
Fx = 0.0
|
|
679
|
+
|
|
680
|
+
# aufgebrachte nodale Fx
|
|
681
|
+
for _, row in self.Inp.NodalForces.iterrows():
|
|
682
|
+
if str(row["Dof"]).strip().lower() == "fx":
|
|
683
|
+
Fx += float(row["Value[MN/MNm]"])
|
|
684
|
+
|
|
685
|
+
# Federkräfte aus RestraintData: k * u an genau diesen DOFs
|
|
686
|
+
for _, row in self.Inp.RestraintData.iterrows():
|
|
687
|
+
node = int(row["Node"])
|
|
688
|
+
dof = int(row["Dof"])
|
|
689
|
+
k = float(row["Cp[MN/m]/[MNm/m]"])
|
|
690
|
+
gdof = 7 * (node - 1) + dof
|
|
691
|
+
|
|
692
|
+
if dof == 0: # ux
|
|
693
|
+
Rx += k * u[gdof]
|
|
694
|
+
|
|
695
|
+
print("Sum Fx (applied) =", Fx)
|
|
696
|
+
print("Sum spring Rx =", Rx)
|
|
697
|
+
print("Fx - Rx =", Fx - Rx)
|
|
698
|
+
|
|
699
|
+
####________________________Iteration Theorie II.Order___________________________####
|
|
700
|
+
def BuildGeometricStiffnessMatrix(self):
|
|
701
|
+
"""
|
|
702
|
+
Baut globale geometrische Steifigkeitsmatrix Kg(u) aus aktuellen Schnittgrößen.
|
|
703
|
+
Voraussetzung: self.u_ges, self.u_el, self.s_el bzw. *_store sind aktuell.
|
|
704
|
+
"""
|
|
705
|
+
nD = self.Inp.nDoF
|
|
706
|
+
Kg_glob = np.zeros((nD, nD), dtype=float)
|
|
707
|
+
|
|
708
|
+
na_memb = self.Inp.members["na"].to_numpy()
|
|
709
|
+
ne_memb = self.Inp.members["ne"].to_numpy()
|
|
710
|
+
|
|
711
|
+
# Default: keine qy/qz (wenn du es noch nicht sauber mapst)
|
|
712
|
+
# Du kannst qy/qz später elementweise aus ElementLoads ableiten.
|
|
713
|
+
qy = 0.0
|
|
714
|
+
qz = 0.0
|
|
715
|
+
yq = 0.0
|
|
716
|
+
zq = 0.0
|
|
717
|
+
|
|
718
|
+
# Schwerpunkt/Schubmittelpunkt des Querschnitts
|
|
719
|
+
# (muss aus Input kommen; hier: fallback = 0)
|
|
720
|
+
yM = 0.0
|
|
721
|
+
zM = 0.0
|
|
722
|
+
|
|
723
|
+
for e in range(len(na_memb)):
|
|
724
|
+
ni = int(na_memb[e])
|
|
725
|
+
nj = int(ne_memb[e])
|
|
726
|
+
L = float(self.member_length[e])
|
|
727
|
+
|
|
728
|
+
# Schnittgrößen aus deinen Stores (links/rechts)
|
|
729
|
+
N_L, N_R = float(self.N_el_i_store[e][0, 0]), float(
|
|
730
|
+
self.N_el_i_store[e][1, 0]
|
|
731
|
+
)
|
|
732
|
+
My_L, My_R = float(self.MY_el_i_store[e][0, 0]), float(
|
|
733
|
+
self.MY_el_i_store[e][1, 0]
|
|
734
|
+
)
|
|
735
|
+
Mz_L, Mz_R = float(self.MZ_el_i_store[e][0, 0]), float(
|
|
736
|
+
self.MZ_el_i_store[e][1, 0]
|
|
737
|
+
)
|
|
738
|
+
Mx_L, Mx_R = float(self.MX_el_i_store[e][0, 0]), float(
|
|
739
|
+
self.MX_el_i_store[e][1, 0]
|
|
740
|
+
)
|
|
884
741
|
|
|
885
|
-
|
|
742
|
+
# Für Kg brauchst du i.d.R. konstante N, lineare M-Verläufe:
|
|
743
|
+
Nbar = 0.5 * (N_L + N_R)
|
|
744
|
+
|
|
745
|
+
# Mr: je nach Definition (Torsion/Warping). Als Start: mittleres Torsionsmoment
|
|
746
|
+
Mr = 0.5 * (Mx_L + Mx_R)
|
|
747
|
+
|
|
748
|
+
# lokale Kg nach deiner Formel
|
|
749
|
+
Kg_loc = self.ElemStem.Kg_theory_II_order(
|
|
750
|
+
L=L,
|
|
751
|
+
N=Nbar,
|
|
752
|
+
My_a=My_L,
|
|
753
|
+
My_b=My_R,
|
|
754
|
+
Mz_a=Mz_L,
|
|
755
|
+
Mz_b=Mz_R,
|
|
756
|
+
Mr=Mr,
|
|
757
|
+
qy=qy,
|
|
758
|
+
qz=qz,
|
|
759
|
+
yq=yq,
|
|
760
|
+
zq=zq,
|
|
761
|
+
yM=yM,
|
|
762
|
+
zM=zM,
|
|
763
|
+
)
|
|
886
764
|
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
765
|
+
# local -> global
|
|
766
|
+
T = self.TransMats[e]
|
|
767
|
+
Kg_e_glob = T @ Kg_loc @ T.T
|
|
890
768
|
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
"""
|
|
895
|
-
fig, ax = self.plot_structure_2d(
|
|
896
|
-
self.Inp.nodes, self.Inp.members["na"], self.Inp.members["ne"]
|
|
897
|
-
)
|
|
769
|
+
# Assembling in globale Matrix
|
|
770
|
+
bi = 7 * (ni - 1)
|
|
771
|
+
bj = 7 * (nj - 1)
|
|
898
772
|
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
self.Inp.members["na"],
|
|
904
|
-
self.Inp.members["ne"],
|
|
905
|
-
self.N_el_i_store,
|
|
906
|
-
scale=scale,
|
|
907
|
-
)
|
|
773
|
+
Kg_glob[bi : bi + 7, bi : bi + 7] += Kg_e_glob[0:7, 0:7]
|
|
774
|
+
Kg_glob[bi : bi + 7, bj : bj + 7] += Kg_e_glob[0:7, 7:14]
|
|
775
|
+
Kg_glob[bj : bj + 7, bi : bi + 7] += Kg_e_glob[7:14, 0:7]
|
|
776
|
+
Kg_glob[bj : bj + 7, bj : bj + 7] += Kg_e_glob[7:14, 7:14]
|
|
908
777
|
|
|
909
|
-
|
|
910
|
-
ax.set_aspect("equal", adjustable="datalim")
|
|
911
|
-
ax.relim()
|
|
912
|
-
ax.autoscale_view()
|
|
913
|
-
|
|
914
|
-
return fig, ax
|
|
915
|
-
|
|
916
|
-
def plot_structure_deformed_3d(
|
|
917
|
-
self,
|
|
918
|
-
scale: float = 1.0,
|
|
919
|
-
show_undeformed: bool = True,
|
|
920
|
-
node_labels: bool = False,
|
|
921
|
-
undeformed_kwargs: dict | None = None,
|
|
922
|
-
deformed_kwargs: dict | None = None,
|
|
923
|
-
):
|
|
924
|
-
"""
|
|
925
|
-
Zeichnet das räumliche Tragwerk samt skalierter Verformungen.
|
|
926
|
-
|
|
927
|
-
Parameters
|
|
928
|
-
----------
|
|
929
|
-
scale : float, optional
|
|
930
|
-
Maßstabsfaktor für die Verschiebungen.
|
|
931
|
-
show_undeformed : bool, optional
|
|
932
|
-
Wenn True, wird das unverformte System zusätzlich angezeigt.
|
|
933
|
-
node_labels : bool, optional
|
|
934
|
-
Beschriftet die Knoten mit ihrer Nummer.
|
|
935
|
-
undeformed_kwargs / deformed_kwargs : dict, optional
|
|
936
|
-
Extra-Keyword-Argumente für die Linien (Farbe, Linienstärke …).
|
|
937
|
-
"""
|
|
938
|
-
if undeformed_kwargs is None:
|
|
939
|
-
undeformed_kwargs = dict(color="lightgray", lw=1.0, zorder=1)
|
|
940
|
-
if deformed_kwargs is None:
|
|
941
|
-
deformed_kwargs = dict(color="blue", lw=2.0, zorder=3)
|
|
942
|
-
|
|
943
|
-
fig = plt.figure(figsize=(10, 8))
|
|
944
|
-
ax: Axes3D = fig.add_subplot(projection="3d")
|
|
945
|
-
|
|
946
|
-
na = self.Inp.members["na"]
|
|
947
|
-
ne = self.Inp.members["ne"]
|
|
948
|
-
|
|
949
|
-
# Kurzfunktionen für Verschiebungen
|
|
950
|
-
def ux(n):
|
|
951
|
-
return self.u_ges[7 * (n - 1) + 0] # u_x
|
|
952
|
-
|
|
953
|
-
def uy(n):
|
|
954
|
-
return self.u_ges[7 * (n - 1) + 1] # u_y
|
|
955
|
-
|
|
956
|
-
def uz(n):
|
|
957
|
-
return self.u_ges[7 * (n - 1) + 3] # u_z
|
|
958
|
-
|
|
959
|
-
# 1) Unverformte Struktur
|
|
960
|
-
if show_undeformed:
|
|
961
|
-
for a, e in zip(na, ne):
|
|
962
|
-
xa, ya, za = (
|
|
963
|
-
self.Inp.nodes[k][a - 1] for k in ("x[m]", "y[m]", "z[m]")
|
|
964
|
-
)
|
|
965
|
-
xe, ye, ze = (
|
|
966
|
-
self.Inp.nodes[k][e - 1] for k in ("x[m]", "y[m]", "z[m]")
|
|
967
|
-
)
|
|
968
|
-
ax.plot([xa, xe], [ya, ye], [za, ze], **undeformed_kwargs)
|
|
969
|
-
|
|
970
|
-
# 2) Verformte Struktur
|
|
971
|
-
for a, e in zip(na, ne):
|
|
972
|
-
xa, ya, za = (self.Inp.nodes[k][a - 1] for k in ("x[m]", "y[m]", "z[m]"))
|
|
973
|
-
xe, ye, ze = (self.Inp.nodes[k][e - 1] for k in ("x[m]", "y[m]", "z[m]"))
|
|
974
|
-
|
|
975
|
-
ax.plot(
|
|
976
|
-
[xa + scale * ux(a), xe + scale * ux(e)],
|
|
977
|
-
[ya + scale * uy(a), ye + scale * uy(e)],
|
|
978
|
-
[za + scale * uz(a), ze + scale * uz(e)],
|
|
979
|
-
**deformed_kwargs,
|
|
980
|
-
)
|
|
778
|
+
return Kg_glob
|
|
981
779
|
|
|
982
|
-
|
|
983
|
-
if node_labels:
|
|
984
|
-
for n in range(1, len(self.Inp.nodes) + 1):
|
|
985
|
-
x0 = self.Inp.nodes["x[m]"][n - 1] + scale * ux(n)
|
|
986
|
-
y0 = self.Inp.nodes["y[m]"][n - 1] + scale * uy(n)
|
|
987
|
-
z0 = self.Inp.nodes["z[m]"][n - 1] + scale * uz(n)
|
|
988
|
-
ax.text(x0, y0, z0, f"{n}", fontsize=8, ha="center", va="center")
|
|
989
|
-
|
|
990
|
-
# Achsen und Optik
|
|
991
|
-
ax.set_xlabel("X [m]")
|
|
992
|
-
ax.set_ylabel("Y [m]")
|
|
993
|
-
ax.set_zlabel("Z [m]")
|
|
994
|
-
ax.set_title(f"Verformte Struktur 3-D (Skalierung {scale:g})")
|
|
995
|
-
ax.set_box_aspect((1, 1, 1)) # gleiches Seitenverhältnis
|
|
996
|
-
fig.tight_layout()
|
|
997
|
-
|
|
998
|
-
# Optional: Blickwinkel anpassen (z. B. isometrisch)
|
|
999
|
-
# ax.view_init(elev=20, azim=-60)
|
|
1000
|
-
|
|
1001
|
-
return fig, ax
|
|
1002
|
-
|
|
1003
|
-
def plot_structure_deformed_3d_interactive(
|
|
1004
|
-
self,
|
|
1005
|
-
scale_init: float = 1.0,
|
|
1006
|
-
show_undeformed: bool = True,
|
|
1007
|
-
node_labels: bool = False,
|
|
1008
|
-
):
|
|
780
|
+
def SolveSecondOrder(self, max_iter=30, tol=1e-8, verbose=True):
|
|
1009
781
|
"""
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
782
|
+
II.-Ordnung: Picard-Iteration
|
|
783
|
+
1) K = Ke + Kg(u_k)
|
|
784
|
+
2) löse u_{k+1}
|
|
785
|
+
3) update Schnittgrößen, Kg, ...
|
|
1013
786
|
"""
|
|
787
|
+
self.TransMats = self.CalculateTransMat()
|
|
1014
788
|
|
|
1015
|
-
#
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
ax = fig.add_subplot(projection="3d")
|
|
1025
|
-
|
|
1026
|
-
# 1) nach dem allerersten Zeichnen
|
|
1027
|
-
_set_axes_equal(ax)
|
|
1028
|
-
|
|
1029
|
-
na, ne = self.Inp.members["na"], self.Inp.members["ne"]
|
|
1030
|
-
|
|
1031
|
-
# Listen zum schnellen Updaten
|
|
1032
|
-
deformed_lines: list[Line3D] = []
|
|
1033
|
-
|
|
1034
|
-
# ── Linien zeichnen (undeformed + deformed) ───────────────────────
|
|
1035
|
-
for a, e in zip(na, ne):
|
|
1036
|
-
xa0, ya0, za0 = (self.Inp.nodes[k][a - 1] for k in ("x[m]", "y[m]", "z[m]"))
|
|
1037
|
-
xe0, ye0, ze0 = (self.Inp.nodes[k][e - 1] for k in ("x[m]", "y[m]", "z[m]"))
|
|
1038
|
-
|
|
1039
|
-
if show_undeformed:
|
|
1040
|
-
ax.plot(
|
|
1041
|
-
[xa0, xe0],
|
|
1042
|
-
[ya0, ye0],
|
|
1043
|
-
[za0, ze0],
|
|
1044
|
-
color="lightgray",
|
|
1045
|
-
lw=1,
|
|
1046
|
-
zorder=1,
|
|
1047
|
-
)
|
|
1048
|
-
|
|
1049
|
-
# deformed-Linie initial
|
|
1050
|
-
xd = [xa0 + scale_init * ux(a), xe0 + scale_init * ux(e)]
|
|
1051
|
-
yd = [ya0 + scale_init * uy(a), ye0 + scale_init * uy(e)]
|
|
1052
|
-
zd = [za0 + scale_init * uz(a), ze0 + scale_init * uz(e)]
|
|
1053
|
-
|
|
1054
|
-
(ld,) = ax.plot(xd, yd, zd, color="tab:blue", lw=2, zorder=3)
|
|
1055
|
-
deformed_lines.append(ld)
|
|
1056
|
-
|
|
1057
|
-
# Knotennummern (optional) – als separate Textobjekte
|
|
1058
|
-
text_elems = []
|
|
1059
|
-
if node_labels:
|
|
1060
|
-
for n in range(1, len(self.Inp.nodes) + 1):
|
|
1061
|
-
x0, y0, z0 = (
|
|
1062
|
-
self.Inp.nodes[k][n - 1] for k in ("x[m]", "y[m]", "z[m]")
|
|
1063
|
-
)
|
|
1064
|
-
t = ax.text(
|
|
1065
|
-
x0 + scale_init * ux(n),
|
|
1066
|
-
y0 + scale_init * uy(n),
|
|
1067
|
-
z0 + scale_init * uz(n),
|
|
1068
|
-
f"{n}",
|
|
1069
|
-
fontsize=8,
|
|
1070
|
-
ha="center",
|
|
1071
|
-
va="center",
|
|
1072
|
-
zorder=4,
|
|
1073
|
-
)
|
|
1074
|
-
text_elems.append(t)
|
|
1075
|
-
|
|
1076
|
-
# ─── Achsenformatierung ───────────────────────────────────────────
|
|
1077
|
-
ax.set_xlabel("X [m]")
|
|
1078
|
-
ax.set_ylabel("Y [m]")
|
|
1079
|
-
ax.set_zlabel("Z [m]")
|
|
1080
|
-
ax.set_title("Verformte Struktur – interaktiv")
|
|
1081
|
-
try:
|
|
1082
|
-
ax.set_box_aspect((1, 1, 1)) # Matplotlib ≥ 3.3
|
|
1083
|
-
except AttributeError:
|
|
1084
|
-
_set_axes_equal(ax)
|
|
1085
|
-
|
|
1086
|
-
# ─── Slider-UI unter dem Plot platzieren ──────────────────────────
|
|
1087
|
-
fig.subplots_adjust(bottom=0.25) # Platz für drei Slider
|
|
1088
|
-
|
|
1089
|
-
# Achsen-Koordinaten [links, unten, breite, höhe]
|
|
1090
|
-
ax_elev = fig.add_axes([0.13, 0.15, 0.74, 0.03])
|
|
1091
|
-
ax_azim = fig.add_axes([0.13, 0.10, 0.74, 0.03])
|
|
1092
|
-
ax_scale = fig.add_axes([0.13, 0.05, 0.74, 0.03])
|
|
1093
|
-
|
|
1094
|
-
s_elev = Slider(ax_elev, "Elev (°)", -90, 90, valinit=20, valstep=1)
|
|
1095
|
-
s_azim = Slider(ax_azim, "Azim (°)", -180, 180, valinit=-60, valstep=1)
|
|
1096
|
-
s_scale = Slider(ax_scale, "Scale", 0.0, scale_init * 10000, valinit=scale_init)
|
|
1097
|
-
|
|
1098
|
-
# ─── Callback-Funktionen ──────────────────────────────────────────
|
|
1099
|
-
def _update_view(_):
|
|
1100
|
-
ax.view_init(elev=s_elev.val, azim=s_azim.val)
|
|
1101
|
-
fig.canvas.draw_idle()
|
|
1102
|
-
|
|
1103
|
-
def _update_scale(_):
|
|
1104
|
-
s = s_scale.val
|
|
1105
|
-
for (a, e), line in zip(zip(na, ne), deformed_lines):
|
|
1106
|
-
xa0, ya0, za0 = (
|
|
1107
|
-
self.Inp.nodes[k][a - 1] for k in ("x[m]", "y[m]", "z[m]")
|
|
1108
|
-
)
|
|
1109
|
-
xe0, ye0, ze0 = (
|
|
1110
|
-
self.Inp.nodes[k][e - 1] for k in ("x[m]", "y[m]", "z[m]")
|
|
1111
|
-
)
|
|
1112
|
-
# neue Koordinaten
|
|
1113
|
-
line.set_data_3d(
|
|
1114
|
-
[xa0 + s * ux(a), xe0 + s * ux(e)],
|
|
1115
|
-
[ya0 + s * uy(a), ye0 + s * uy(e)],
|
|
1116
|
-
[za0 + s * uz(a), ze0 + s * uz(e)],
|
|
1117
|
-
)
|
|
1118
|
-
# Knotentexte mit skalieren
|
|
1119
|
-
if node_labels:
|
|
1120
|
-
for n, txt in enumerate(text_elems, start=1):
|
|
1121
|
-
x0, y0, z0 = (
|
|
1122
|
-
self.Inp.nodes[k][n - 1] for k in ("x[m]", "y[m]", "z[m]")
|
|
1123
|
-
)
|
|
1124
|
-
txt.set_position((x0 + s * ux(n), y0 + s * uy(n)))
|
|
1125
|
-
txt.set_3d_properties(z0 + s * uz(n), zdir="z")
|
|
1126
|
-
# Limits neu auf Würfel setzen
|
|
1127
|
-
try:
|
|
1128
|
-
ax.set_box_aspect((1, 1, 1))
|
|
1129
|
-
except AttributeError:
|
|
1130
|
-
_set_axes_equal(ax)
|
|
1131
|
-
fig.canvas.draw_idle()
|
|
1132
|
-
|
|
1133
|
-
# Slider verbinden
|
|
1134
|
-
s_elev.on_changed(_update_view)
|
|
1135
|
-
s_azim.on_changed(_update_view)
|
|
1136
|
-
s_scale.on_changed(_update_scale)
|
|
1137
|
-
|
|
1138
|
-
# Startansicht
|
|
1139
|
-
_update_view(None)
|
|
1140
|
-
_update_scale(None)
|
|
1141
|
-
|
|
1142
|
-
return fig, ax, (s_elev, s_azim, s_scale)
|
|
1143
|
-
|
|
1144
|
-
def orthogonal_unit_vector_3d(self,pt_i, pt_j, prefer_axis="y"):
|
|
1145
|
-
"""
|
|
1146
|
-
Liefert einen normierten Vektor w, der senkrecht auf der
|
|
1147
|
-
Stabachse v = (pt_j - pt_i) steht.
|
|
1148
|
-
|
|
1149
|
-
Parameters
|
|
1150
|
-
----------
|
|
1151
|
-
pt_i, pt_j : array-like (3,)
|
|
1152
|
-
Globale xyz-Koordinaten der Knoten i und j.
|
|
1153
|
-
prefer_axis : {"x","y","z"}, optional
|
|
1154
|
-
Welcher globale Achsvektor soll primär verwendet werden,
|
|
1155
|
-
um das Kreuzprodukt zu bilden? Wähle eine Achse, die
|
|
1156
|
-
im Regelfall nicht parallel zur Elementachse v ist.
|
|
1157
|
-
|
|
1158
|
-
Returns
|
|
1159
|
-
-------
|
|
1160
|
-
w : ndarray (3,)
|
|
1161
|
-
Normierter Orthogonalvektor.
|
|
1162
|
-
"""
|
|
1163
|
-
v = np.asarray(pt_j) - np.asarray(pt_i)
|
|
1164
|
-
v_norm = np.linalg.norm(v)
|
|
1165
|
-
if v_norm == 0:
|
|
1166
|
-
raise ValueError("Elementlänge 0 – identische Knoten?")
|
|
1167
|
-
|
|
1168
|
-
v = v / v_norm
|
|
1169
|
-
|
|
1170
|
-
# Globalen Hilfsvektor wählen
|
|
1171
|
-
axes = {"x": np.array([1, 0, 0]),
|
|
1172
|
-
"y": np.array([0, 1, 0]),
|
|
1173
|
-
"z": np.array([0, 0, 1])}
|
|
1174
|
-
u = axes.get(prefer_axis, axes["y"])
|
|
1175
|
-
|
|
1176
|
-
# Prüfen, ob v und u (fast) parallel sind
|
|
1177
|
-
if abs(np.dot(v, u)) > 0.95: # nahezu kollinear
|
|
1178
|
-
u = axes["z"] if prefer_axis != "z" else axes["x"]
|
|
1179
|
-
|
|
1180
|
-
w = np.cross(v, u)
|
|
1181
|
-
w_norm = np.linalg.norm(w)
|
|
1182
|
-
if w_norm == 0:
|
|
1183
|
-
raise ValueError("Konnte keinen Orthogonalvektor berechnen.")
|
|
1184
|
-
return w / w_norm
|
|
1185
|
-
|
|
1186
|
-
def plot_moment_my_3d(self,ax, x0, y0, z0, m_val, w, scale=1.0,
|
|
1187
|
-
color_pos="tab:blue", color_neg="tab:red",
|
|
1188
|
-
text=True):
|
|
1189
|
-
"""
|
|
1190
|
-
Zeichnet einen Momentenpfeil (My) im 3-D-Plot.
|
|
1191
|
-
|
|
1192
|
-
Parameter
|
|
1193
|
-
---------
|
|
1194
|
-
ax : mpl_toolkits.mplot3d.Axes3D
|
|
1195
|
-
x0,y0,z0 : float
|
|
1196
|
-
Koordinaten des Ausgangsknotens (global).
|
|
1197
|
-
m_val : float
|
|
1198
|
-
Momentwert (positiv / negativ gemäß System).
|
|
1199
|
-
w : ndarray (3,)
|
|
1200
|
-
Normierter Orthogonalvektor (Ausgabe der o.g. Funktion).
|
|
1201
|
-
scale : float
|
|
1202
|
-
Skalierungsfaktor für die Pfeillänge.
|
|
1203
|
-
"""
|
|
1204
|
-
if m_val == 0:
|
|
1205
|
-
return None # nichts zu plotten
|
|
1206
|
-
|
|
1207
|
-
# Farbe
|
|
1208
|
-
col = color_pos if m_val >= 0 else color_neg
|
|
1209
|
-
|
|
1210
|
-
# kleine Verbindungslinie vom Knoten nach außen
|
|
1211
|
-
link_len = 0.05 * scale
|
|
1212
|
-
P1 = np.array([x0, y0, z0])
|
|
1213
|
-
P2 = P1 + link_len * w * abs(m_val)
|
|
1214
|
-
|
|
1215
|
-
# Pfeil
|
|
1216
|
-
vec = scale * m_val * w
|
|
1217
|
-
Q = P2 + vec
|
|
1218
|
-
|
|
1219
|
-
# Plotten
|
|
1220
|
-
ax.plot([P1[0], P2[0]], [P1[1], P2[1]], [P1[2], P2[2]], color=col, lw=1)
|
|
1221
|
-
ax.quiver(P2[0], P2[1], P2[2],
|
|
1222
|
-
vec[0], vec[1], vec[2],
|
|
1223
|
-
arrow_length_ratio=0.15, color=col, linewidth=1)
|
|
1224
|
-
|
|
1225
|
-
# Text
|
|
1226
|
-
if text:
|
|
1227
|
-
txt = f"My={m_val:.2f}"
|
|
1228
|
-
ax.text(Q[0], Q[1], Q[2], txt, fontsize=8, color=col)
|
|
1229
|
-
|
|
1230
|
-
return (P1, P2, Q) # falls du später noch updaten willst
|
|
1231
|
-
|
|
1232
|
-
def plot_My_3d(
|
|
1233
|
-
self,
|
|
1234
|
-
scale: float = 1,
|
|
1235
|
-
show_axes: bool = True,
|
|
1236
|
-
show_stabs: bool = True,
|
|
1237
|
-
text: bool = True,
|
|
1238
|
-
prefer_axis: str = "y",
|
|
1239
|
-
):
|
|
1240
|
-
"""
|
|
1241
|
-
Erstellt EINEN separaten 3-D-Plot, in dem ausschließlich die
|
|
1242
|
-
My-Momente (Biegung um lokale y-Achse) dargestellt werden.
|
|
1243
|
-
|
|
1244
|
-
Parameter
|
|
1245
|
-
---------
|
|
1246
|
-
scale : float globaler Faktor für die Pfeillängen (Moment * scale)
|
|
1247
|
-
show_axes : bool Achsenbeschriftungen + Würfelanzeige?
|
|
1248
|
-
show_stabs : bool graue Stabachsen als Anhalt zeichnen?
|
|
1249
|
-
text : bool Momentwert als Text an Pfeilspitze?
|
|
1250
|
-
prefer_axis : {"x","y","z"} welche globale Achse zur Bildung
|
|
1251
|
-
des Orthogonalvektors primär verwenden?
|
|
1252
|
-
"""
|
|
1253
|
-
fig = plt.figure(figsize=(8, 6))
|
|
1254
|
-
ax = fig.add_subplot(projection="3d")
|
|
1255
|
-
ax.set_title("My-Momente 3-D")
|
|
1256
|
-
|
|
1257
|
-
na, ne = self.Inp.members["na"], self.Inp.members["ne"]
|
|
1258
|
-
|
|
1259
|
-
# ----- 1. optional: Stabachsen als helle Linien --------------------
|
|
1260
|
-
if show_stabs:
|
|
1261
|
-
segs = []
|
|
1262
|
-
for a, e in zip(na, ne):
|
|
1263
|
-
Pi = [self.Inp.nodes[k][a - 1] for k in ("x[m]", "y[m]", "z[m]")]
|
|
1264
|
-
Pj = [self.Inp.nodes[k][e - 1] for k in ("x[m]", "y[m]", "z[m]")]
|
|
1265
|
-
segs.append([Pi, Pj])
|
|
1266
|
-
lc = Line3DCollection(segs, colors="lightgray", linewidths=1, zorder=0)
|
|
1267
|
-
ax.add_collection3d(lc)
|
|
1268
|
-
|
|
1269
|
-
# ----- 2. Momente plotten ------------------------------------------
|
|
1270
|
-
for idx, (a, e) in enumerate(zip(na, ne)):
|
|
1271
|
-
Pi = np.array([self.Inp.nodes[k][a - 1] for k in ("x[m]", "y[m]", "z[m]")])
|
|
1272
|
-
Pj = np.array([self.Inp.nodes[k][e - 1] for k in ("x[m]", "y[m]", "z[m]")])
|
|
1273
|
-
|
|
1274
|
-
w = self.orthogonal_unit_vector_3d(Pi, Pj, prefer_axis=prefer_axis)
|
|
1275
|
-
|
|
1276
|
-
My_a = float(self.MY_el_i_store[idx, 0, 0])
|
|
1277
|
-
My_b = float(self.MY_el_i_store[idx, 1, 0])
|
|
1278
|
-
|
|
1279
|
-
self.plot_moment_my_3d(ax, *Pi, My_a, w, scale=scale, text=text)
|
|
1280
|
-
self.plot_moment_my_3d(ax, *Pj, My_b, w, scale=scale, text=text)
|
|
1281
|
-
|
|
1282
|
-
# ----- 3. Optik -----------------------------------------------------
|
|
1283
|
-
if show_axes:
|
|
1284
|
-
ax.set_xlabel("X [m]")
|
|
1285
|
-
ax.set_ylabel("Y [m]")
|
|
1286
|
-
ax.set_zlabel("Z [m]")
|
|
1287
|
-
else: # Achsen ausblenden
|
|
1288
|
-
ax.set_axis_off()
|
|
1289
|
-
|
|
1290
|
-
# kubische Limits
|
|
1291
|
-
_set_axes_equal(ax)
|
|
1292
|
-
fig.tight_layout()
|
|
1293
|
-
|
|
1294
|
-
return fig, ax
|
|
1295
|
-
|
|
1296
|
-
def plot_My_3d_interactive(
|
|
1297
|
-
self,
|
|
1298
|
-
scale_init: float = 1e-3,
|
|
1299
|
-
show_axes: bool = True,
|
|
1300
|
-
show_stabs: bool = True,
|
|
1301
|
-
text: bool = True,
|
|
1302
|
-
prefer_axis: str = "y",
|
|
1303
|
-
):
|
|
1304
|
-
"""
|
|
1305
|
-
Interaktive 3-D-Darstellung der My-Momente mit drei Slidern:
|
|
1306
|
-
• Elevation (θ) • Azimut (φ) • Skalierung s
|
|
1307
|
-
"""
|
|
1308
|
-
import matplotlib.pyplot as plt
|
|
1309
|
-
|
|
1310
|
-
# ---------- Grundplot zeichnen -------------------------------------
|
|
1311
|
-
fig, ax = self.plot_My_3d(
|
|
1312
|
-
scale=scale_init,
|
|
1313
|
-
show_axes=show_axes,
|
|
1314
|
-
show_stabs=show_stabs,
|
|
1315
|
-
text=text,
|
|
1316
|
-
prefer_axis=prefer_axis,
|
|
1317
|
-
)
|
|
789
|
+
# 1) Ke aufbauen (wie bisher)
|
|
790
|
+
Ke_glob = self.BuildStructureStiffnessMatrix()
|
|
791
|
+
self.GesMat = Ke_glob.copy()
|
|
792
|
+
|
|
793
|
+
# Lager/Federn in Ke
|
|
794
|
+
self.RestraintData()
|
|
795
|
+
|
|
796
|
+
# Lastvektor (global) einmal
|
|
797
|
+
self.FGes = self.GlobalLoadVector()
|
|
1318
798
|
|
|
1319
|
-
#
|
|
1320
|
-
|
|
799
|
+
# Start: lineare Lösung
|
|
800
|
+
u = np.linalg.solve(self.GesMat, self.FGes)
|
|
801
|
+
if verbose:
|
|
802
|
+
print("II-Order: initial linear solve done.")
|
|
1321
803
|
|
|
1322
|
-
|
|
1323
|
-
|
|
804
|
+
for it in range(1, max_iter + 1):
|
|
805
|
+
self.u_ges = u
|
|
806
|
+
self.u_el = self.StoreLocalDisplacements()
|
|
807
|
+
self.s_el = self.CalculateLocalInnerForces() # update N,My,Mz,...
|
|
1324
808
|
|
|
1325
|
-
|
|
1326
|
-
ax_azim = fig.add_axes([0.14, 0.10, 0.72, 0.03])
|
|
1327
|
-
ax_scale = fig.add_axes([0.14, 0.05, 0.72, 0.03])
|
|
809
|
+
Kg_glob = self.BuildGeometricStiffnessMatrix()
|
|
1328
810
|
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
s_scale = Slider(ax_scale, "Scale", 0.0, scale_init*100, valinit=scale_init)
|
|
811
|
+
# Totalmatrix neu: Ke + Kg + restraints
|
|
812
|
+
Ktot = Ke_glob + Kg_glob
|
|
1332
813
|
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
for art in list(ax.get_children()):
|
|
1337
|
-
if art not in base_children:
|
|
1338
|
-
art.remove()
|
|
814
|
+
# restraints nochmal addieren (weil Ktot neu)
|
|
815
|
+
self.GesMat = Ktot
|
|
816
|
+
self.RestraintData()
|
|
1339
817
|
|
|
1340
|
-
#
|
|
1341
|
-
|
|
1342
|
-
for idx, (a, e) in enumerate(zip(na, ne)):
|
|
1343
|
-
Pi = np.array([self.Inp.nodes[k][a - 1] for k in ("x[m]","y[m]","z[m]")])
|
|
1344
|
-
Pj = np.array([self.Inp.nodes[k][e - 1] for k in ("x[m]","y[m]","z[m]")])
|
|
818
|
+
# lösen
|
|
819
|
+
u_new = np.linalg.solve(self.GesMat, self.FGes)
|
|
1345
820
|
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
821
|
+
# Konvergenz
|
|
822
|
+
rel = np.linalg.norm(u_new - u) / (np.linalg.norm(u_new) + 1e-16)
|
|
823
|
+
if verbose:
|
|
824
|
+
print(f"II-Order iter {it}: rel_du = {rel:.3e}")
|
|
1349
825
|
|
|
1350
|
-
|
|
1351
|
-
|
|
826
|
+
u = u_new
|
|
827
|
+
if rel < tol:
|
|
828
|
+
break
|
|
1352
829
|
|
|
1353
|
-
|
|
1354
|
-
|
|
830
|
+
# final speichern
|
|
831
|
+
self.u_ges = u
|
|
832
|
+
self.u_el = self.StoreLocalDisplacements()
|
|
833
|
+
self.s_el = self.CalculateLocalInnerForces()
|
|
834
|
+
return u
|
|
1355
835
|
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
836
|
+
#####_______________________ BUCKLING ___________________________#####
|
|
837
|
+
def get_free_dofs_mask(self):
|
|
838
|
+
restrained = np.zeros(self.Inp.nDoF, dtype=bool)
|
|
839
|
+
for _, row in self.Inp.RestraintData.iterrows():
|
|
840
|
+
gdof = 7 * (int(row["Node"]) - 1) + int(row["Dof"])
|
|
841
|
+
restrained[gdof] = True
|
|
842
|
+
return ~restrained
|
|
843
|
+
|
|
844
|
+
def BucklingEigen_alpha_crit(self, use_second_order_prestate=False, verbose=True):
|
|
845
|
+
"""
|
|
846
|
+
Linear buckling: (Ke + alpha Kg) phi = 0
|
|
847
|
+
-> solve Ke phi = -alpha Kg phi
|
|
848
|
+
Returns: eigenvalues alpha (sorted), eigenvectors in full dof size (optional)
|
|
849
|
+
"""
|
|
850
|
+
self.TransMats = self.CalculateTransMat()
|
|
1360
851
|
|
|
1361
|
-
|
|
1362
|
-
|
|
852
|
+
# Ke inkl. Lager
|
|
853
|
+
Ke = self.BuildStructureStiffnessMatrix()
|
|
854
|
+
self.GesMat = Ke.copy()
|
|
855
|
+
self.RestraintData()
|
|
1363
856
|
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
s_scale.on_changed(_update_scale)
|
|
857
|
+
# Vorzustand bestimmen (für Kg):
|
|
858
|
+
self.FGes = self.GlobalLoadVector()
|
|
1367
859
|
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
860
|
+
if use_second_order_prestate:
|
|
861
|
+
self.SolveSecondOrder(max_iter=30, tol=1e-8, verbose=False)
|
|
862
|
+
else:
|
|
863
|
+
self.u_ges = np.linalg.solve(self.GesMat, self.FGes)
|
|
864
|
+
self.u_el = self.StoreLocalDisplacements()
|
|
865
|
+
self.s_el = self.CalculateLocalInnerForces()
|
|
1371
866
|
|
|
1372
|
-
|
|
867
|
+
Kg = self.BuildGeometricStiffnessMatrix()
|
|
1373
868
|
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
# Für Desktop-Skripte (nicht im Notebook) ein GUI-Backend wählen:
|
|
1378
|
-
matplotlib.use("TkAgg") # oder "QtAgg"
|
|
1379
|
-
import matplotlib.pyplot as plt
|
|
869
|
+
free = self.get_free_dofs_mask()
|
|
870
|
+
Ke_ff = self.GesMat[np.ix_(free, free)]
|
|
871
|
+
Kg_ff = Kg[np.ix_(free, free)]
|
|
1380
872
|
|
|
1381
|
-
|
|
1382
|
-
|
|
873
|
+
# EVP: Ke_ff^{-1} (-Kg_ff) v = alpha v
|
|
874
|
+
# -> solve A v = alpha v with A = inv(Ke_ff) @ (-Kg_ff)
|
|
875
|
+
A = np.linalg.solve(Ke_ff, -Kg_ff)
|
|
1383
876
|
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
ax1.invert_yaxis()
|
|
1387
|
-
fig1.savefig("plots/ClampHinged_Inclination.eps", format="eps")
|
|
877
|
+
# Eigenwerte
|
|
878
|
+
eigvals, eigvecs = np.linalg.eig(A)
|
|
1388
879
|
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
880
|
+
# reell filtern & sortieren (kleinster positiver)
|
|
881
|
+
eigvals = np.real_if_close(eigvals, tol=1e-6)
|
|
882
|
+
eigvals_real = np.real(eigvals)
|
|
1392
883
|
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
node_labels=True,
|
|
1397
|
-
)
|
|
884
|
+
# sinnvolle Kandidaten: alpha > 0
|
|
885
|
+
pos = eigvals_real[eigvals_real > 1e-9]
|
|
886
|
+
pos_sorted = np.sort(pos)
|
|
1398
887
|
|
|
1399
|
-
|
|
888
|
+
if verbose:
|
|
889
|
+
if len(pos_sorted) == 0:
|
|
890
|
+
print("No positive alpha found (check sign convention of N/Kg).")
|
|
891
|
+
else:
|
|
892
|
+
print("alpha_crit =", pos_sorted[0])
|
|
893
|
+
print("next alphas:", pos_sorted[:5])
|
|
1400
894
|
|
|
1401
|
-
|
|
1402
|
-
plt.show() # Fenster offen → hier läuft die Event-Schleife
|
|
1403
|
-
# Fenster zu → show() kehrt zurück → Script endet
|
|
895
|
+
return pos_sorted, (eigvals_real, eigvecs)
|