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
|
@@ -0,0 +1,967 @@
|
|
|
1
|
+
# DEPENDENCIES
|
|
2
|
+
import copy # Allows us to create copies of objects in memory
|
|
3
|
+
import math # Math functionality
|
|
4
|
+
import numpy as np # Numpy for working with arrays
|
|
5
|
+
import matplotlib.pyplot as plt # Plotting functionality
|
|
6
|
+
import matplotlib.colors # For colormap functionality
|
|
7
|
+
import ipywidgets as widgets
|
|
8
|
+
from glob import glob # Allows check that file exists before import
|
|
9
|
+
from numpy import genfromtxt # For importing structure data from csv
|
|
10
|
+
import pandas as pd
|
|
11
|
+
|
|
12
|
+
from InputData import Input
|
|
13
|
+
from Materials import Material
|
|
14
|
+
from Elements import Rope_Elements_III
|
|
15
|
+
from Elements import BarElements_I
|
|
16
|
+
|
|
17
|
+
class IterationClass:
|
|
18
|
+
def __init__(self, use_iteration=False):
|
|
19
|
+
print("INIT")
|
|
20
|
+
##________ Subclasses __________##
|
|
21
|
+
self.Inp = Input()
|
|
22
|
+
self.Mat = Material()
|
|
23
|
+
self.CableNonlinear = Rope_Elements_III(self.Inp)
|
|
24
|
+
self.BarLinear = BarElements_I(self.Inp)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
self.swt = False
|
|
28
|
+
|
|
29
|
+
##___ Iteration parameters _________##
|
|
30
|
+
|
|
31
|
+
self.nForceIncrements = 10
|
|
32
|
+
self.convThreshold = 1 # (N) Threshold on average percentage increase in incremental deflection
|
|
33
|
+
|
|
34
|
+
self.checkSlackAfter = 80
|
|
35
|
+
|
|
36
|
+
# Member Types
|
|
37
|
+
|
|
38
|
+
self.memberType = []
|
|
39
|
+
self.ClassifyMemberType()
|
|
40
|
+
|
|
41
|
+
##____ Containers_____##
|
|
42
|
+
# Initialise a container to hold the set of global displacements for each external load increment
|
|
43
|
+
self.UG_FINAL = np.empty([self.Inp.nDoF, 0])
|
|
44
|
+
|
|
45
|
+
# Initialise a container to hold the set of internal forces for each external load increment
|
|
46
|
+
self.FI_FINAL = np.empty([self.Inp.nDoF, 0])
|
|
47
|
+
|
|
48
|
+
# Initialise a container to hold the set of axial forces for each external load increment
|
|
49
|
+
self.EXTFORCES = np.empty([self.Inp.nDoF, 0])
|
|
50
|
+
|
|
51
|
+
# Initialise a container to hold the set of axial forces for each external load increment
|
|
52
|
+
self.MBRFORCES = np.empty([len(self.Inp.members), 0])
|
|
53
|
+
|
|
54
|
+
# Initialise global disp vector
|
|
55
|
+
self.UG = np.zeros(
|
|
56
|
+
[self.Inp.nDoF, 1]
|
|
57
|
+
) # Initialise global displacement vector to zero (undeformed state)
|
|
58
|
+
|
|
59
|
+
# Calculate initial transformation matrices for all members based on undeformed position
|
|
60
|
+
self.TMs = self.calculateTransMatrices(self.UG)
|
|
61
|
+
|
|
62
|
+
# Init point loads to global force vector
|
|
63
|
+
|
|
64
|
+
self.AddPointLoadsGlobal()
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
# Calculate initial lengths
|
|
68
|
+
|
|
69
|
+
self.calculateInitialLengths()
|
|
70
|
+
self.SelfweigthLoadVector()
|
|
71
|
+
|
|
72
|
+
# Calculate internal force system based on any pre-tension in members
|
|
73
|
+
self.F_pre = self.initPretension()
|
|
74
|
+
|
|
75
|
+
# Initialise a container to store incremental displacements calculated for each iteration [Xa], [Xb] etc.
|
|
76
|
+
self.UG_inc = np.empty([self.Inp.nDoF, 0])
|
|
77
|
+
self.UG_inc = np.append(
|
|
78
|
+
self.UG_inc, self.UG, axis=1
|
|
79
|
+
) # Add the initial (zero) displacement record
|
|
80
|
+
|
|
81
|
+
# Initialise a container to store incremental internal forces calculated for each iteration [Fa], [Fb] etc.
|
|
82
|
+
self.F_inc = np.empty([self.Inp.nDoF, 0])
|
|
83
|
+
print(self.F_inc)
|
|
84
|
+
print(self.F_pre)
|
|
85
|
+
self.F_inc = np.append(
|
|
86
|
+
self.F_inc, self.F_pre, axis=1
|
|
87
|
+
) # Add the initial pre-tension force record
|
|
88
|
+
|
|
89
|
+
if use_iteration:
|
|
90
|
+
self.MainConvergenceLoop()
|
|
91
|
+
else:
|
|
92
|
+
self.SolveLinear_NoIteration(treat_cables_as_bars=True)
|
|
93
|
+
|
|
94
|
+
def ClassifyMemberType(self):
|
|
95
|
+
print("Classify Member type")
|
|
96
|
+
for n,m in enumerate(self.Inp.members):
|
|
97
|
+
#Initially assume all members are bars
|
|
98
|
+
self.memberType.append('b')
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
#Check if member is a cable
|
|
102
|
+
for c in self.Inp.cables:
|
|
103
|
+
if(m[0] in c and m[1] in c):
|
|
104
|
+
self.memberType[n] = 'c'
|
|
105
|
+
|
|
106
|
+
def AddPointLoadsGlobal(self):
|
|
107
|
+
self.forceVector = np.zeros((len(self.Inp.nodes) * 2, 1))
|
|
108
|
+
|
|
109
|
+
if len(self.Inp.forceLocationData) > 0:
|
|
110
|
+
# Split force location data
|
|
111
|
+
try:
|
|
112
|
+
forcedNodes = self.Inp.forceLocationData[:, 0].astype(
|
|
113
|
+
int
|
|
114
|
+
) # Ensure these are integers)
|
|
115
|
+
xForceIndizes = 2 * forcedNodes - 2
|
|
116
|
+
yForceIndizes = 2 * forcedNodes - 1
|
|
117
|
+
|
|
118
|
+
ForceP = self.Inp.forceLocationData[:, 1].reshape(-1, 1)
|
|
119
|
+
print("FORCE P")
|
|
120
|
+
print(ForceP)
|
|
121
|
+
|
|
122
|
+
# Assign forces to degrees of freedom
|
|
123
|
+
for i in range(0,len(ForceP),1):
|
|
124
|
+
|
|
125
|
+
if self.Inp.forceDirections[i] == "x":
|
|
126
|
+
self.forceVector[xForceIndizes[i]] = ForceP[i]
|
|
127
|
+
elif self.Inp.forceDirections[i] == "y":
|
|
128
|
+
self.forceVector[yForceIndizes[i]] = ForceP[i]
|
|
129
|
+
except:
|
|
130
|
+
pass
|
|
131
|
+
|
|
132
|
+
def calculateInitialLengths(self):
|
|
133
|
+
self.lengths = np.zeros(len(self.Inp.members))
|
|
134
|
+
for n, mbr in enumerate(self.Inp.members):
|
|
135
|
+
|
|
136
|
+
# Calculate undeformed length of member
|
|
137
|
+
node_i = mbr[0] # Node number for node i of this member
|
|
138
|
+
node_j = mbr[1] # Node number for node j of this member
|
|
139
|
+
ix = self.Inp.nodes[node_i - 1][0] # x-coord for node i
|
|
140
|
+
iy = self.Inp.nodes[node_i - 1][1] # y-coord for node i
|
|
141
|
+
jx = self.Inp.nodes[node_j - 1][0] # x-coord for node j
|
|
142
|
+
jy = self.Inp.nodes[node_j - 1][1] # y-coord for node j
|
|
143
|
+
|
|
144
|
+
dx = jx - ix # x-component of vector along member
|
|
145
|
+
dy = jy - iy # y-component of vector along member
|
|
146
|
+
length = math.sqrt(dx**2 + dy**2) # Magnitude of vector (length of member)
|
|
147
|
+
if (length == 0):
|
|
148
|
+
print("Length = 0 at index ", n )
|
|
149
|
+
self.lengths[n] = length
|
|
150
|
+
|
|
151
|
+
def initPretension(self):
|
|
152
|
+
"""
|
|
153
|
+
P = axial pre-tension specified for each bar
|
|
154
|
+
Calculate the force vector [F_pre] for each bar [F_pre] = [T'][AA'][P]
|
|
155
|
+
Combine into an overal vector representing the internal force system and return
|
|
156
|
+
"""
|
|
157
|
+
self.F_pre = np.array(
|
|
158
|
+
[np.zeros(len(self.forceVector))]
|
|
159
|
+
).T # Initialse internal force vector
|
|
160
|
+
|
|
161
|
+
for n, mbr in enumerate(self.Inp.members):
|
|
162
|
+
node_i = mbr[0] # Node number for node i of this member
|
|
163
|
+
node_j = mbr[1] # Node number for node j of this member
|
|
164
|
+
|
|
165
|
+
# Index of DoF for this member
|
|
166
|
+
ia = 2 * node_i - 2 # horizontal DoF at node i of this member
|
|
167
|
+
ib = 2 * node_i - 1 # vertical DoF at node i of this member
|
|
168
|
+
ja = 2 * node_j - 2 # horizontal DoF at node j of this member
|
|
169
|
+
jb = 2 * node_j - 1 # vertical DoF at node j of this member
|
|
170
|
+
|
|
171
|
+
# Determine internal pre-tension in global coords
|
|
172
|
+
TM = self.TMs[n, :, :]
|
|
173
|
+
AAp = np.array([[1], [0]])
|
|
174
|
+
P = self.Mat.P0[n]
|
|
175
|
+
F_pre_global = np.matmul(TM.T, AAp) * P
|
|
176
|
+
|
|
177
|
+
# Add member pre-tension to overall record
|
|
178
|
+
self.F_pre[ia, 0] = self.F_pre[ia, 0] + F_pre_global[0][0]
|
|
179
|
+
self.F_pre[ib, 0] = self.F_pre[ib, 0] + F_pre_global[1][0]
|
|
180
|
+
self.F_pre[ja, 0] = self.F_pre[ja, 0] + F_pre_global[2][0]
|
|
181
|
+
self.F_pre[jb, 0] = self.F_pre[jb, 0] + F_pre_global[3][0]
|
|
182
|
+
|
|
183
|
+
return self.F_pre
|
|
184
|
+
|
|
185
|
+
def calculateTransMatrices(self, UG):
|
|
186
|
+
"""
|
|
187
|
+
Optimized:
|
|
188
|
+
- Bars ('b'): transformation is constant (undeformed geometry) -> keep initial TM
|
|
189
|
+
- Cables ('c'): update TM each iteration based on deformed geometry
|
|
190
|
+
|
|
191
|
+
Returns array shape (nMembers, 2, 4)
|
|
192
|
+
"""
|
|
193
|
+
|
|
194
|
+
nM = len(self.Inp.members)
|
|
195
|
+
|
|
196
|
+
# -------------------------------------------------------
|
|
197
|
+
# Create constant (initial) TMs once (for bars AND cables)
|
|
198
|
+
# -------------------------------------------------------
|
|
199
|
+
if not hasattr(self, "TMs_const") or self.TMs_const is None:
|
|
200
|
+
self.TMs_const = np.zeros((nM, 2, 4), dtype=float)
|
|
201
|
+
|
|
202
|
+
for n, mbr in enumerate(self.Inp.members):
|
|
203
|
+
node_i = int(mbr[0])
|
|
204
|
+
node_j = int(mbr[1])
|
|
205
|
+
|
|
206
|
+
ix = float(self.Inp.nodes[node_i - 1, 0])
|
|
207
|
+
iy = float(self.Inp.nodes[node_i - 1, 1])
|
|
208
|
+
jx = float(self.Inp.nodes[node_j - 1, 0])
|
|
209
|
+
jy = float(self.Inp.nodes[node_j - 1, 1])
|
|
210
|
+
|
|
211
|
+
TM0 = self.CableNonlinear.calculateTransMatrix([ix, iy], [jx, jy])
|
|
212
|
+
self.TMs_const[n, :, :] = TM0
|
|
213
|
+
|
|
214
|
+
# start from constant TMs
|
|
215
|
+
TMs = self.TMs_const.copy()
|
|
216
|
+
|
|
217
|
+
# -------------------------------------------------------
|
|
218
|
+
# Update ONLY cables
|
|
219
|
+
# -------------------------------------------------------
|
|
220
|
+
for n, mbr in enumerate(self.Inp.members):
|
|
221
|
+
if self.memberType[n] != "c":
|
|
222
|
+
continue # bars: keep constant TM
|
|
223
|
+
|
|
224
|
+
node_i = int(mbr[0])
|
|
225
|
+
node_j = int(mbr[1])
|
|
226
|
+
|
|
227
|
+
ia = 2 * node_i - 2
|
|
228
|
+
ib = 2 * node_i - 1
|
|
229
|
+
ja = 2 * node_j - 2
|
|
230
|
+
jb = 2 * node_j - 1
|
|
231
|
+
|
|
232
|
+
# deformed positions = initial + cumulative displacements
|
|
233
|
+
ix = float(self.Inp.nodes[node_i - 1, 0]) + float(UG[ia, 0])
|
|
234
|
+
iy = float(self.Inp.nodes[node_i - 1, 1]) + float(UG[ib, 0])
|
|
235
|
+
jx = float(self.Inp.nodes[node_j - 1, 0]) + float(UG[ja, 0])
|
|
236
|
+
jy = float(self.Inp.nodes[node_j - 1, 1]) + float(UG[jb, 0])
|
|
237
|
+
|
|
238
|
+
TM = self.CableNonlinear.calculateTransMatrix([ix, iy], [jx, jy])
|
|
239
|
+
TMs[n, :, :] = TM
|
|
240
|
+
|
|
241
|
+
return TMs
|
|
242
|
+
|
|
243
|
+
def buildStructureStiffnessMatrix(self, UG,TMs):
|
|
244
|
+
"""
|
|
245
|
+
Standard construction of Primary and Structure stiffness matrix
|
|
246
|
+
Construction of non-linear element stiffness matrix handled in a child function
|
|
247
|
+
"""
|
|
248
|
+
Kp = np.zeros(
|
|
249
|
+
[self.Inp.nDoF, self.Inp.nDoF]
|
|
250
|
+
) # Initialise the primary stiffness matrix
|
|
251
|
+
|
|
252
|
+
# store spring stiffness diagonal (for equilibrium check)
|
|
253
|
+
self.Kspring_diag = np.zeros(self.Inp.nDoF, dtype=float)
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
for n, mbr in enumerate(self.Inp.members):
|
|
257
|
+
node_i = mbr[0] # Node number for node i of this member
|
|
258
|
+
node_j = mbr[1] # Node number for node j of this member
|
|
259
|
+
|
|
260
|
+
# Construct (potentially) non-linear element stiffness matrix
|
|
261
|
+
|
|
262
|
+
# [K11, K12, K21, K22] = self.CableNonlinear.buildElementStiffnessMatrix(
|
|
263
|
+
# n, UG, TMs, self.lengths, self.Mat.P0, self.Mat.E, self.Mat.A
|
|
264
|
+
# )
|
|
265
|
+
|
|
266
|
+
if self.memberType[n] == 'c':
|
|
267
|
+
# cable / nonlinear
|
|
268
|
+
[K11, K12, K21, K22] = self.CableNonlinear.buildElementStiffnessMatrix(
|
|
269
|
+
n, UG, TMs, self.lengths, self.Mat.P0, self.Mat.E, self.Mat.A
|
|
270
|
+
)
|
|
271
|
+
else:
|
|
272
|
+
# bar / linear (Theorie I. Ordnung)
|
|
273
|
+
[K11, K12, K21, K22] = self.BarLinear.buildElementStiffnessMatrix(
|
|
274
|
+
n, UG, None, None, self.Mat.P0, self.Mat.E, self.Mat.A
|
|
275
|
+
)
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
# Primary stiffness matrix indices associated with each node
|
|
279
|
+
# i.e. node 1 occupies indices 0 and 1 (accessed in Python with [0:2])
|
|
280
|
+
ia = 2 * node_i - 2 # index 0
|
|
281
|
+
ib = 2 * node_i - 1 # index 1
|
|
282
|
+
ja = 2 * node_j - 2 # index 2
|
|
283
|
+
jb = 2 * node_j - 1 # index 3
|
|
284
|
+
|
|
285
|
+
Kp[ia : ib + 1, ia : ib + 1] = Kp[ia : ib + 1, ia : ib + 1] + K11
|
|
286
|
+
Kp[ia : ib + 1, ja : jb + 1] = Kp[ia : ib + 1, ja : jb + 1] + K12
|
|
287
|
+
Kp[ja : jb + 1, ia : ib + 1] = Kp[ja : jb + 1, ia : ib + 1] + K21
|
|
288
|
+
Kp[ja : jb + 1, ja : jb + 1] = Kp[ja : jb + 1, ja : jb + 1] + K22
|
|
289
|
+
|
|
290
|
+
# Add springs
|
|
291
|
+
|
|
292
|
+
if len(self.Inp.springLocationData) > 0:
|
|
293
|
+
# Split force location data
|
|
294
|
+
try:
|
|
295
|
+
forcedNodes = self.Inp.springLocationData[:, 1].astype(
|
|
296
|
+
int
|
|
297
|
+
) # Ensure these are integers)
|
|
298
|
+
xSpringIndizes = 2 * forcedNodes - 2
|
|
299
|
+
ySpringIndizes = 2 * forcedNodes -1
|
|
300
|
+
|
|
301
|
+
# print("Indizes")
|
|
302
|
+
# print(xSpringIndizes)
|
|
303
|
+
# print(ySpringIndizes)
|
|
304
|
+
|
|
305
|
+
SpringC = self.Inp.springLocationData[:, 2].reshape(-1, 1)
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
# Assign forces to degrees of freedom
|
|
309
|
+
for i in range(0,len(SpringC),1):
|
|
310
|
+
|
|
311
|
+
if self.Inp.SpringDirections[i] == "x":
|
|
312
|
+
Kp[xSpringIndizes[i]][xSpringIndizes[i]] += SpringC[i]
|
|
313
|
+
self.Kspring_diag[xSpringIndizes[i]] += SpringC[i]
|
|
314
|
+
elif self.Inp.SpringDirections[i] == "y":
|
|
315
|
+
Kp[ySpringIndizes[i]][ySpringIndizes[i]] += SpringC[i]
|
|
316
|
+
self.Kspring_diag[ySpringIndizes[i]] += SpringC[i]
|
|
317
|
+
except:
|
|
318
|
+
pass
|
|
319
|
+
|
|
320
|
+
# Reduce to structure stiffness matrix by deleting rows and columns for restrained DoF
|
|
321
|
+
if (len(self.Inp.restrainedIndex)>0):
|
|
322
|
+
# print("RESTRAINED INDEX")
|
|
323
|
+
# print(self.Inp.restrainedIndex)
|
|
324
|
+
Ks = np.delete(Kp, self.Inp.restrainedIndex, 0) # Delete rows
|
|
325
|
+
Ks = np.delete(Ks, self.Inp.restrainedIndex, 1) # Delete columns
|
|
326
|
+
else:
|
|
327
|
+
Ks = Kp
|
|
328
|
+
|
|
329
|
+
|
|
330
|
+
Ks = np.matrix(
|
|
331
|
+
Ks
|
|
332
|
+
) # Convert Ks from numpy.ndarray to numpy.matrix to use build in inverter function
|
|
333
|
+
|
|
334
|
+
|
|
335
|
+
|
|
336
|
+
|
|
337
|
+
return Ks
|
|
338
|
+
|
|
339
|
+
def solveDisplacements(self, Ks, F_inequilibrium):
|
|
340
|
+
"""
|
|
341
|
+
Standard solving for structural displacements
|
|
342
|
+
"""
|
|
343
|
+
|
|
344
|
+
forceVectorRed = copy.copy(
|
|
345
|
+
F_inequilibrium
|
|
346
|
+
) # Make a copy of forceVector so the copy can be edited, leaving the original unchanged
|
|
347
|
+
if (len(self.Inp.restrainedIndex)>0):
|
|
348
|
+
forceVectorRed = np.delete(
|
|
349
|
+
forceVectorRed, self.Inp.restrainedIndex, 0
|
|
350
|
+
) # Delete rows corresponding to restrained DoF
|
|
351
|
+
else:
|
|
352
|
+
forceVectorRed = forceVectorRed
|
|
353
|
+
|
|
354
|
+
#U = Ks.I * forceVectorRed
|
|
355
|
+
U = np.linalg.solve(Ks, forceVectorRed)
|
|
356
|
+
|
|
357
|
+
|
|
358
|
+
# Build the global displacement vector inclusing zeros as restrained degrees of freedom
|
|
359
|
+
UG = np.zeros(
|
|
360
|
+
self.Inp.nDoF
|
|
361
|
+
) # Initialise an array to hold the global displacement vector
|
|
362
|
+
c = 0 # Initialise a counter to track how many restraints have been imposed
|
|
363
|
+
for i in np.arange(self.Inp.nDoF):
|
|
364
|
+
if i in self.Inp.restrainedIndex:
|
|
365
|
+
# Impose zero displacement
|
|
366
|
+
UG[i] = 0
|
|
367
|
+
else:
|
|
368
|
+
# Assign actual displacement
|
|
369
|
+
UG[i] = U[c]
|
|
370
|
+
c = c + 1
|
|
371
|
+
|
|
372
|
+
UG = np.array([UG]).T
|
|
373
|
+
|
|
374
|
+
return UG
|
|
375
|
+
|
|
376
|
+
def SelfweigthLoadVector(self):
|
|
377
|
+
|
|
378
|
+
if(self.swt):
|
|
379
|
+
self.SW_at_supports = np.empty((0,2))
|
|
380
|
+
for n, mbr in enumerate(self.Inp.members):
|
|
381
|
+
node_i = mbr[0] #Node number for node i of this member
|
|
382
|
+
node_j = mbr[1] #Node number for node j of this member
|
|
383
|
+
length = self.lengths[n]
|
|
384
|
+
sw = length*self.Mat.gamma[n] #(N) Self-weight of the member
|
|
385
|
+
F_node = sw/2 #(N) Self-weight distributed into each node
|
|
386
|
+
# print("FNODE")
|
|
387
|
+
# print(F_node)
|
|
388
|
+
iy = 2*node_i-1 #index of y-DoF for node i
|
|
389
|
+
jy = 2*node_j-1 #index of y-DoF for node j
|
|
390
|
+
self.forceVector[iy] = self.forceVector[iy] -F_node
|
|
391
|
+
self.forceVector[jy] = self.forceVector[jy] -F_node
|
|
392
|
+
|
|
393
|
+
#Check if SW needs to be directly added to supports (if elements connect to supports)
|
|
394
|
+
if(iy+1 in self.Inp.restrainedDoF):
|
|
395
|
+
supportSW = np.array([iy, F_node])
|
|
396
|
+
self.SW_at_supports = np.append(self.SW_at_supports, [supportSW], axis=0) #Store y-DoF at support and force to be added
|
|
397
|
+
if(jy+1 in self.Inp.restrainedDoF):
|
|
398
|
+
supportSW = np.array([jy, F_node])
|
|
399
|
+
self.SW_at_supports = np.append(self.SW_at_supports, [supportSW], axis=0) #Store y-DoF at support and force to be added
|
|
400
|
+
print(self.forceVector)
|
|
401
|
+
print(len(self.forceVector))
|
|
402
|
+
else:
|
|
403
|
+
pass
|
|
404
|
+
|
|
405
|
+
def updateInternalForceSystem(self, UG):
|
|
406
|
+
"""
|
|
407
|
+
Build internal force vector F_int (global DoF order) for the CURRENT increment UG.
|
|
408
|
+
|
|
409
|
+
- Cable elements ('c'): nonlinear (deformed geometry, AA-matrix, etc.) -> uses self.TMs[n]
|
|
410
|
+
- Bar elements ('b'): linear Theorie I. Ordnung -> uses constant direction cosines from BarLinear
|
|
411
|
+
|
|
412
|
+
IMPORTANT:
|
|
413
|
+
- Pretension P0 is already handled separately via self.F_pre / self.F_inc initial column.
|
|
414
|
+
Therefore for BAR internal force increment we DO NOT add P0 again here (avoid double count).
|
|
415
|
+
"""
|
|
416
|
+
|
|
417
|
+
F_int = np.zeros((self.Inp.nDoF, 1), dtype=float)
|
|
418
|
+
|
|
419
|
+
for n, mbr in enumerate(self.Inp.members):
|
|
420
|
+
node_i, node_j = int(mbr[0]), int(mbr[1])
|
|
421
|
+
|
|
422
|
+
# global DoF indices for this member
|
|
423
|
+
ia = 2 * node_i - 2
|
|
424
|
+
ib = 2 * node_i - 1
|
|
425
|
+
ja = 2 * node_j - 2
|
|
426
|
+
jb = 2 * node_j - 1
|
|
427
|
+
|
|
428
|
+
# -------------------------
|
|
429
|
+
# CABLE (nonlinear)
|
|
430
|
+
# -------------------------
|
|
431
|
+
if self.memberType[n] == "c":
|
|
432
|
+
# incremental displacements (global)
|
|
433
|
+
d_ix = float(UG[ia, 0])
|
|
434
|
+
d_iy = float(UG[ib, 0])
|
|
435
|
+
d_jx = float(UG[ja, 0])
|
|
436
|
+
d_jy = float(UG[jb, 0])
|
|
437
|
+
|
|
438
|
+
# current transformation (computed for cumulative shape, stored in self.TMs)
|
|
439
|
+
TM = self.TMs[n, :, :] # shape (2,4)
|
|
440
|
+
|
|
441
|
+
# local incremental displacements
|
|
442
|
+
localDisp = TM @ np.array([[d_ix], [d_iy], [d_jx], [d_jy]], dtype=float)
|
|
443
|
+
u = float(localDisp[0, 0])
|
|
444
|
+
v = float(localDisp[1, 0])
|
|
445
|
+
|
|
446
|
+
# extension from nonlinear geometry
|
|
447
|
+
Lo = float(self.lengths[n])
|
|
448
|
+
e = math.sqrt((Lo + u) ** 2 + v**2) - Lo
|
|
449
|
+
|
|
450
|
+
# AA matrix
|
|
451
|
+
denom = (Lo + e)
|
|
452
|
+
if abs(denom) < 1e-14:
|
|
453
|
+
# numerical guard (should not really happen)
|
|
454
|
+
continue
|
|
455
|
+
|
|
456
|
+
a1 = (Lo + u) / denom
|
|
457
|
+
a2 = v / denom
|
|
458
|
+
AA = np.array([[a1, a2]], dtype=float) # (1,2)
|
|
459
|
+
|
|
460
|
+
# axial load increment (no P0 here; P0 was handled via initPretension)
|
|
461
|
+
P = (float(self.Mat.E[n]) * float(self.Mat.A[n]) / Lo) * e
|
|
462
|
+
|
|
463
|
+
# back to global nodal forces (4x1)
|
|
464
|
+
F_global = (TM.T @ AA.T) * P # (4,1)
|
|
465
|
+
|
|
466
|
+
F_int[ia, 0] += float(F_global[0, 0])
|
|
467
|
+
F_int[ib, 0] += float(F_global[1, 0])
|
|
468
|
+
F_int[ja, 0] += float(F_global[2, 0])
|
|
469
|
+
F_int[jb, 0] += float(F_global[3, 0])
|
|
470
|
+
|
|
471
|
+
# -------------------------
|
|
472
|
+
# BAR (linear, Theorie I. Ordnung)
|
|
473
|
+
# -------------------------
|
|
474
|
+
else:
|
|
475
|
+
# element axial force increment from linear truss theory:
|
|
476
|
+
# N = (EA/L) * [-c -s c s] * u_e
|
|
477
|
+
f_e = self.BarLinear.internal_nodal_forces_global(
|
|
478
|
+
n,
|
|
479
|
+
UG,
|
|
480
|
+
self.Mat.E,
|
|
481
|
+
self.Mat.A,
|
|
482
|
+
P0=None, # do NOT add P0 here (already in self.F_pre)
|
|
483
|
+
) # shape (4,1)
|
|
484
|
+
|
|
485
|
+
F_int[ia, 0] += float(f_e[0, 0])
|
|
486
|
+
F_int[ib, 0] += float(f_e[1, 0])
|
|
487
|
+
F_int[ja, 0] += float(f_e[2, 0])
|
|
488
|
+
F_int[jb, 0] += float(f_e[3, 0])
|
|
489
|
+
|
|
490
|
+
return F_int
|
|
491
|
+
|
|
492
|
+
def testForConvergence(self, it, threshold, F_ineq):
|
|
493
|
+
"""
|
|
494
|
+
Test if structure has converged by comparing the maximum force in the equilibrium
|
|
495
|
+
force vector against a threshold for the simulation.
|
|
496
|
+
"""
|
|
497
|
+
notConverged = True # Initialise the convergence flag
|
|
498
|
+
maxIneq = 0
|
|
499
|
+
if it > 0:
|
|
500
|
+
maxIneq = np.max(abs(F_ineq[self.Inp.freeDoF]))
|
|
501
|
+
|
|
502
|
+
if maxIneq < threshold:
|
|
503
|
+
notConverged = False
|
|
504
|
+
|
|
505
|
+
return notConverged, maxIneq
|
|
506
|
+
|
|
507
|
+
def calculateMbrForces(self, UG):
|
|
508
|
+
"""
|
|
509
|
+
Calculates the member forces based on change in length of each member
|
|
510
|
+
Takes in the cumulative global displacement vector as UG
|
|
511
|
+
"""
|
|
512
|
+
|
|
513
|
+
mbrForces = np.zeros(
|
|
514
|
+
len(self.Inp.members)
|
|
515
|
+
) # Initialise a container to hold axial forces
|
|
516
|
+
|
|
517
|
+
for n, mbr in enumerate(self.Inp.members):
|
|
518
|
+
node_i = mbr[0] # Node number for node i of this member
|
|
519
|
+
node_j = mbr[1] # Node number for node j of this member
|
|
520
|
+
|
|
521
|
+
# Index of DoF for this member
|
|
522
|
+
ia = 2 * node_i - 2 # horizontal DoF at node i of this member
|
|
523
|
+
ib = 2 * node_i - 1 # vertical DoF at node i of this member
|
|
524
|
+
ja = 2 * node_j - 2 # horizontal DoF at node j of this member
|
|
525
|
+
jb = 2 * node_j - 1 # vertical DoF at node j of this member
|
|
526
|
+
|
|
527
|
+
# New positions = initial pos + cum deflection
|
|
528
|
+
ix = self.Inp.nodes[node_i - 1, 0] + UG[ia, 0]
|
|
529
|
+
iy = self.Inp.nodes[node_i - 1, 1] + UG[ib, 0]
|
|
530
|
+
jx = self.Inp.nodes[node_j - 1, 0] + UG[ja, 0]
|
|
531
|
+
jy = self.Inp.nodes[node_j - 1, 1] + UG[jb, 0]
|
|
532
|
+
|
|
533
|
+
dx = jx - ix # x-component of vector along member
|
|
534
|
+
dy = jy - iy # y-component of vector along member
|
|
535
|
+
newLength = math.sqrt(
|
|
536
|
+
dx**2 + dy**2
|
|
537
|
+
) # Magnitude of vector (length of member)
|
|
538
|
+
|
|
539
|
+
deltaL = newLength - self.lengths[n] # Change in length
|
|
540
|
+
force = (
|
|
541
|
+
self.Mat.P0[n]
|
|
542
|
+
+ deltaL * self.Mat.E[n] * self.Mat.A[n] / self.lengths[n]
|
|
543
|
+
) # Axial force due to change in length and any pre-tension
|
|
544
|
+
mbrForces[n] = force # Store member force
|
|
545
|
+
|
|
546
|
+
return mbrForces
|
|
547
|
+
|
|
548
|
+
def AdditionalSupportForce(self):
|
|
549
|
+
self.reactionsFlag = False #Initialise a flag so we can plot a message re. reactions when necessary
|
|
550
|
+
if(self.swt):
|
|
551
|
+
if self.SW_at_supports.size>0:
|
|
552
|
+
self.reactionsFlag = True
|
|
553
|
+
for SW in self.SW_at_supports:
|
|
554
|
+
index = int(SW[0]) #Index of the global force vector 'FG' to update
|
|
555
|
+
self.FI_FINAL[index,:] = self.FI_FINAL[index,:] + SW[1] #Add nodal SW force directly to FG
|
|
556
|
+
|
|
557
|
+
def MainConvergenceLoop(self):
|
|
558
|
+
i = 0 # Initialise an iteration counter (zeros out for each load increment)
|
|
559
|
+
inc = 0 # Initialise load increment counter
|
|
560
|
+
notConverged = True # Initialise convergence flag
|
|
561
|
+
|
|
562
|
+
# Init kspring-diagonal for the first iteration
|
|
563
|
+
# It's overwriten in the generation of the stiffness matrix
|
|
564
|
+
# in each loop. Here just for the first run, where the stiffness matrix isn't initialized
|
|
565
|
+
self.Kspring_diag = np.zeros(self.Inp.nDoF, dtype=float)
|
|
566
|
+
|
|
567
|
+
|
|
568
|
+
self.forceIncrement = (
|
|
569
|
+
self.forceVector / self.nForceIncrements
|
|
570
|
+
) # Determine the force increment for each convergence test
|
|
571
|
+
self.maxForce = (
|
|
572
|
+
self.forceVector
|
|
573
|
+
) # Define a vector to store the total external force applied
|
|
574
|
+
self.forceVector = (
|
|
575
|
+
self.forceIncrement
|
|
576
|
+
) # Initialise the forceVector to the first increment of load
|
|
577
|
+
|
|
578
|
+
# print("Force vector")
|
|
579
|
+
# print(self.forceVector)
|
|
580
|
+
|
|
581
|
+
|
|
582
|
+
while notConverged and i < 10000:
|
|
583
|
+
|
|
584
|
+
# Calculate the cumulative internal forces Fi_total = Fa + Fb + Fc + ...
|
|
585
|
+
Fi_total = np.matrix(
|
|
586
|
+
np.sum(self.F_inc, axis=1)
|
|
587
|
+
).T # Sum across columns of F_inc
|
|
588
|
+
|
|
589
|
+
# Calculate the cumulative incremental displacements UG_total = Xa + Xb + Xc + ...
|
|
590
|
+
UG_total = np.matrix(
|
|
591
|
+
np.sum(self.UG_inc, axis=1)
|
|
592
|
+
).T # Sum across columns of UG_inc
|
|
593
|
+
|
|
594
|
+
# Inequilibrium force vector used in this iteration F_EXT - Fi_total or externalForces - (cumulative) InternalForceSystem
|
|
595
|
+
|
|
596
|
+
# add spring forces to internal force balance
|
|
597
|
+
# (springs are in K, so their resisting forces must appear in equilibrium)
|
|
598
|
+
F_spring = self.Kspring_diag.reshape(-1, 1) * np.asarray(UG_total, dtype=float)
|
|
599
|
+
|
|
600
|
+
|
|
601
|
+
self.F_inequilibrium = self.forceVector - Fi_total - F_spring
|
|
602
|
+
|
|
603
|
+
# Build the structure stiffness matrix based on current position (using cumulative displacements)
|
|
604
|
+
Ks = self.buildStructureStiffnessMatrix(UG_total,self.TMs)
|
|
605
|
+
|
|
606
|
+
# Solve for global (incremental) displacement vector [Xn] for this iteration
|
|
607
|
+
self.UG = self.solveDisplacements(Ks, self.F_inequilibrium)
|
|
608
|
+
|
|
609
|
+
# Calculate a new transformation matrix for each member based on cum disp up to previous iteration
|
|
610
|
+
self.TMs = self.calculateTransMatrices(UG_total)
|
|
611
|
+
|
|
612
|
+
# if i == 0:
|
|
613
|
+
# print(self.TMs)
|
|
614
|
+
|
|
615
|
+
# Calculate the internal force system based on new incremental displacements, [Fn]
|
|
616
|
+
F_int = self.updateInternalForceSystem(self.UG)
|
|
617
|
+
|
|
618
|
+
# Save incremental displacements and internal forces for this iteration
|
|
619
|
+
self.UG_inc = np.append(self.UG_inc, self.UG, axis=1)
|
|
620
|
+
self.F_inc = np.append(self.F_inc, F_int, axis=1)
|
|
621
|
+
|
|
622
|
+
# Test for convergence
|
|
623
|
+
notConverged, maxIneq = self.testForConvergence(
|
|
624
|
+
i, self.convThreshold, self.F_inequilibrium
|
|
625
|
+
)
|
|
626
|
+
|
|
627
|
+
i += 1
|
|
628
|
+
|
|
629
|
+
# If system has converged, save converged displacements, forces and increment external loading
|
|
630
|
+
if not notConverged:
|
|
631
|
+
self.hasSlackElements = False #Initialise a flag to indicate presence of new slack elements
|
|
632
|
+
mbrForces = self.calculateMbrForces(UG_total) #Calculate member forces based on current set of displacements
|
|
633
|
+
|
|
634
|
+
#Test for compression in cable elements if designated number of converged increments reached
|
|
635
|
+
|
|
636
|
+
if inc > self.checkSlackAfter:
|
|
637
|
+
for m, mbr in enumerate(self.Inp.members):
|
|
638
|
+
if self.memberType[m] == 'c' and mbrForces[m]<0:
|
|
639
|
+
print(f'Compression in cable element from from nodes {mbr[0]} to {mbr[1]}')
|
|
640
|
+
self.hasSlackElements = True #Switch slack elements flag
|
|
641
|
+
self.Mat.A[m] = 0 #Eliminate member stiffness by seting its cross-sectional area to zero
|
|
642
|
+
|
|
643
|
+
|
|
644
|
+
|
|
645
|
+
print(
|
|
646
|
+
f"System has converged for load increment {inc} after {i-1} iterations"
|
|
647
|
+
)
|
|
648
|
+
|
|
649
|
+
|
|
650
|
+
self.UG_FINAL = np.append(
|
|
651
|
+
self.UG_FINAL, UG_total, axis=1
|
|
652
|
+
) # Add the converged displacement record
|
|
653
|
+
self.UG_inc = np.empty(
|
|
654
|
+
[self.Inp.nDoF, 0]
|
|
655
|
+
) # Zero out the record of incremental displacements for the next load increment
|
|
656
|
+
self.UG_inc = np.array(
|
|
657
|
+
np.append(self.UG_inc, UG_total, axis=1)
|
|
658
|
+
) # Add the initial displacement record for next load increment (manually cast as ndarray instead of matrix)
|
|
659
|
+
|
|
660
|
+
self.FI_FINAL = np.append(
|
|
661
|
+
self.FI_FINAL, Fi_total, axis=1
|
|
662
|
+
) # Add the converged force record
|
|
663
|
+
self.F_inc = np.empty(
|
|
664
|
+
[self.Inp.nDoF, 0]
|
|
665
|
+
) # Zero out the record of incremental forces for the next load increment
|
|
666
|
+
self.F_inc = np.array(
|
|
667
|
+
np.append(self.F_inc, Fi_total, axis=1)
|
|
668
|
+
) # Add the initial force record for next load increment (manually cast as ndarray instead of matrix)
|
|
669
|
+
|
|
670
|
+
|
|
671
|
+
self.mbrForces = self.calculateMbrForces(
|
|
672
|
+
self.UG_FINAL[:, -1]
|
|
673
|
+
) # Calculate the member forces based on change in mbr length
|
|
674
|
+
self.MBRFORCES = np.append(
|
|
675
|
+
self.MBRFORCES, np.matrix(self.mbrForces).T, axis=1
|
|
676
|
+
) # Add the converged axial forces record
|
|
677
|
+
|
|
678
|
+
self.EXTFORCES = np.append(
|
|
679
|
+
self.EXTFORCES, self.forceVector, axis=1
|
|
680
|
+
) # Add the external force vector for this load increment
|
|
681
|
+
|
|
682
|
+
# Test if all external loading has been applied
|
|
683
|
+
if abs(sum(self.forceVector).item()) < abs(sum(self.maxForce).item()):
|
|
684
|
+
i = 0 # Reset counter for next load increment
|
|
685
|
+
inc += 1
|
|
686
|
+
self.forceVector = (
|
|
687
|
+
self.forceVector + self.forceIncrement
|
|
688
|
+
) # Increment the applied load
|
|
689
|
+
notConverged = (
|
|
690
|
+
True # Reset notConverged flag for next load increment
|
|
691
|
+
)
|
|
692
|
+
|
|
693
|
+
self.AdditionalSupportForce()
|
|
694
|
+
|
|
695
|
+
def SolveLinear_NoIteration(self, treat_cables_as_bars=True):
|
|
696
|
+
"""
|
|
697
|
+
Linear solve (no iteration, no deformed geometry):
|
|
698
|
+
K * u = F
|
|
699
|
+
|
|
700
|
+
- Builds global stiffness matrix once from undeformed geometry.
|
|
701
|
+
- Solves once for displacements.
|
|
702
|
+
- Computes reactions and member forces.
|
|
703
|
+
|
|
704
|
+
treat_cables_as_bars:
|
|
705
|
+
True -> use linear bar stiffness also for members typed 'c'
|
|
706
|
+
False -> raise error if cables exist (strict linear truss only)
|
|
707
|
+
"""
|
|
708
|
+
|
|
709
|
+
# ---------------------------------------------------------
|
|
710
|
+
# 1) Build external load vector (global) once
|
|
711
|
+
# ---------------------------------------------------------
|
|
712
|
+
self.forceVector = np.zeros((len(self.Inp.nodes) * 2, 1), dtype=float)
|
|
713
|
+
self.AddPointLoadsGlobal()
|
|
714
|
+
self.calculateInitialLengths()
|
|
715
|
+
self.SelfweigthLoadVector() # only acts if self.swt=True
|
|
716
|
+
|
|
717
|
+
F = self.forceVector.copy() # global full vector (nDoF,1)
|
|
718
|
+
|
|
719
|
+
# ---------------------------------------------------------
|
|
720
|
+
# 2) Build global stiffness Kp (full) from undeformed geometry
|
|
721
|
+
# ---------------------------------------------------------
|
|
722
|
+
nDoF = self.Inp.nDoF
|
|
723
|
+
Kp = np.zeros((nDoF, nDoF), dtype=float)
|
|
724
|
+
|
|
725
|
+
# Springs: store diagonal like before
|
|
726
|
+
self.Kspring_diag = np.zeros(nDoF, dtype=float)
|
|
727
|
+
|
|
728
|
+
for n, mbr in enumerate(self.Inp.members):
|
|
729
|
+
node_i, node_j = int(mbr[0]), int(mbr[1])
|
|
730
|
+
|
|
731
|
+
if (self.memberType[n] == "c") and (not treat_cables_as_bars):
|
|
732
|
+
raise ValueError(
|
|
733
|
+
f"Member {n+1} (nodes {node_i}-{node_j}) is a cable. "
|
|
734
|
+
"Set treat_cables_as_bars=True or remove cable members for strict linear solve."
|
|
735
|
+
)
|
|
736
|
+
|
|
737
|
+
# Use linear bar element stiffness (undeformed)
|
|
738
|
+
K11, K12, K21, K22 = self.BarLinear.buildElementStiffnessMatrix(
|
|
739
|
+
n, None, None, None, self.Mat.P0, self.Mat.E, self.Mat.A
|
|
740
|
+
)
|
|
741
|
+
|
|
742
|
+
ia = 2 * node_i - 2
|
|
743
|
+
ib = 2 * node_i - 1
|
|
744
|
+
ja = 2 * node_j - 2
|
|
745
|
+
jb = 2 * node_j - 1
|
|
746
|
+
|
|
747
|
+
Kp[ia:ib+1, ia:ib+1] += K11
|
|
748
|
+
Kp[ia:ib+1, ja:jb+1] += K12
|
|
749
|
+
Kp[ja:jb+1, ia:ib+1] += K21
|
|
750
|
+
Kp[ja:jb+1, ja:jb+1] += K22
|
|
751
|
+
|
|
752
|
+
# ---------------------------------------------------------
|
|
753
|
+
# 3) Add springs (same logic as your iterative build)
|
|
754
|
+
# ---------------------------------------------------------
|
|
755
|
+
if len(self.Inp.springLocationData) > 0:
|
|
756
|
+
try:
|
|
757
|
+
forcedNodes = self.Inp.springLocationData[:, 1].astype(int)
|
|
758
|
+
xIdx = 2 * forcedNodes - 2
|
|
759
|
+
yIdx = 2 * forcedNodes - 1
|
|
760
|
+
SpringC = self.Inp.springLocationData[:, 2].reshape(-1)
|
|
761
|
+
|
|
762
|
+
for i in range(len(SpringC)):
|
|
763
|
+
c = float(SpringC[i])
|
|
764
|
+
if self.Inp.SpringDirections[i] == "x":
|
|
765
|
+
Kp[xIdx[i], xIdx[i]] += c
|
|
766
|
+
self.Kspring_diag[xIdx[i]] += c
|
|
767
|
+
elif self.Inp.SpringDirections[i] == "y":
|
|
768
|
+
Kp[yIdx[i], yIdx[i]] += c
|
|
769
|
+
self.Kspring_diag[yIdx[i]] += c
|
|
770
|
+
except:
|
|
771
|
+
pass
|
|
772
|
+
|
|
773
|
+
# ---------------------------------------------------------
|
|
774
|
+
# 4) Reduce and solve
|
|
775
|
+
# ---------------------------------------------------------
|
|
776
|
+
if len(self.Inp.restrainedIndex) > 0:
|
|
777
|
+
free = np.array([i for i in range(nDoF) if i not in self.Inp.restrainedIndex], dtype=int)
|
|
778
|
+
else:
|
|
779
|
+
free = np.arange(nDoF, dtype=int)
|
|
780
|
+
|
|
781
|
+
Kff = Kp[np.ix_(free, free)]
|
|
782
|
+
Ff = F[free, :]
|
|
783
|
+
|
|
784
|
+
uf = np.linalg.solve(Kff, Ff)
|
|
785
|
+
|
|
786
|
+
# build full displacement vector u (restrained are 0)
|
|
787
|
+
u = np.zeros((nDoF, 1), dtype=float)
|
|
788
|
+
u[free, 0] = uf[:, 0]
|
|
789
|
+
|
|
790
|
+
# Save like your usual output containers (single step)
|
|
791
|
+
self.UG_FINAL = u.copy()
|
|
792
|
+
self.EXTFORCES = F.copy()
|
|
793
|
+
|
|
794
|
+
# ---------------------------------------------------------
|
|
795
|
+
# 5) Reactions (full)
|
|
796
|
+
# ---------------------------------------------------------
|
|
797
|
+
R = (Kp @ u) - F # includes spring reactions etc.
|
|
798
|
+
self.FI_FINAL = (Kp @ u) # "internal nodal forces" equivalent
|
|
799
|
+
self.Reactions = R
|
|
800
|
+
|
|
801
|
+
# ---------------------------------------------------------
|
|
802
|
+
# 6) Member forces (linear)
|
|
803
|
+
# include pretension if you want: P0 added in axial_force(..., P0=self.Mat.P0)
|
|
804
|
+
# ---------------------------------------------------------
|
|
805
|
+
mbrN = np.zeros((len(self.Inp.members), 1), dtype=float)
|
|
806
|
+
for n in range(len(self.Inp.members)):
|
|
807
|
+
# linear axial force (tension +)
|
|
808
|
+
N = self.BarLinear.axial_force(n, u, self.Mat.E, self.Mat.A, P0=self.Mat.P0)
|
|
809
|
+
mbrN[n, 0] = N
|
|
810
|
+
|
|
811
|
+
self.MBRFORCES = mbrN.copy()
|
|
812
|
+
|
|
813
|
+
return u, R, mbrN
|
|
814
|
+
|
|
815
|
+
|
|
816
|
+
|
|
817
|
+
def Summarize(self):
|
|
818
|
+
#Generate output statements
|
|
819
|
+
print(f"OUTSTANDING FORCE IMBALANCE")
|
|
820
|
+
for i in np.arange(0,self.Inp.nDoF):
|
|
821
|
+
if i not in self.Inp.restrainedIndex:
|
|
822
|
+
print(f"Remaining force imbalance at DoF {i} is {round(self.F_inequilibrium[i,0]/1000,3)} kN")
|
|
823
|
+
|
|
824
|
+
maxInequality = round(max(abs(self.F_inequilibrium[self.Inp.freeDoF,0])).item()/1000,3)
|
|
825
|
+
print(f"(max = {maxInequality} kN)")
|
|
826
|
+
|
|
827
|
+
print("")
|
|
828
|
+
print("REACTIONS")
|
|
829
|
+
|
|
830
|
+
f_int = self.FI_FINAL[:,-1]
|
|
831
|
+
for i in np.arange(0,len(self.Inp.restrainedIndex)):
|
|
832
|
+
index = self.Inp.restrainedIndex[i]
|
|
833
|
+
print(f"Reaction at DoF {index+1}: {round(f_int[index].item()/1000,2)} kN")
|
|
834
|
+
|
|
835
|
+
# last converged displacement vector
|
|
836
|
+
u_last = np.asarray(self.UG_FINAL[:, -1], dtype=float).reshape(-1, 1) # (nDoF,1)
|
|
837
|
+
|
|
838
|
+
# spring stiffness (diagonal) as vector
|
|
839
|
+
k = np.asarray(self.Kspring_diag, dtype=float).reshape(-1, 1) # (nDoF,1)
|
|
840
|
+
|
|
841
|
+
# elementwise spring forces
|
|
842
|
+
f_springs = k * u_last # (nDoF,1)
|
|
843
|
+
|
|
844
|
+
|
|
845
|
+
try:
|
|
846
|
+
# Federkräfte (letzter Lastschritt)
|
|
847
|
+
f = np.asarray(f_springs, dtype=float).flatten()
|
|
848
|
+
|
|
849
|
+
springno = self.Inp.springLocationData[:, 0].astype(int)
|
|
850
|
+
forcedNodes = self.Inp.springLocationData[:, 1].astype(int)
|
|
851
|
+
dirs = np.asarray(self.Inp.SpringDirections)
|
|
852
|
+
|
|
853
|
+
print("\nSPRING FORCES (per spring):")
|
|
854
|
+
for no, node, d in zip(springno, forcedNodes, dirs):
|
|
855
|
+
d = str(d).strip().lower().replace('"', '')
|
|
856
|
+
|
|
857
|
+
if d == "x":
|
|
858
|
+
dof = 2 * node - 2
|
|
859
|
+
elif d in ("y", "z"):
|
|
860
|
+
dof = 2 * node - 1
|
|
861
|
+
else:
|
|
862
|
+
raise ValueError(f"Unknown spring direction: {d}")
|
|
863
|
+
|
|
864
|
+
print(
|
|
865
|
+
f"Spring {no} | Node {node} | Dir {d} | "
|
|
866
|
+
f"DoF {dof} | Force = {f[dof]/1000:.2f} kN"
|
|
867
|
+
)
|
|
868
|
+
|
|
869
|
+
except Exception as e:
|
|
870
|
+
print("No springs in the system")
|
|
871
|
+
# optional:
|
|
872
|
+
# print(e)
|
|
873
|
+
|
|
874
|
+
print("")
|
|
875
|
+
print("MEMBER FORCES (incl. any pre-tension)")
|
|
876
|
+
for n, mbr in enumerate(self.Inp.members):
|
|
877
|
+
print(f"Force in member {n+1} (nodes {mbr[0]} to {mbr[1]}) is {round(self.mbrForces[n]/1000,2)} kN")
|
|
878
|
+
|
|
879
|
+
print("")
|
|
880
|
+
print("NODAL DISPLACEMENTS")
|
|
881
|
+
ug = self.UG_FINAL[:,-1]
|
|
882
|
+
for n, node in enumerate(self.Inp.nodes):
|
|
883
|
+
ix = 2*(n+1)-2 #horizontal DoF for this node
|
|
884
|
+
iy = 2*(n+1)-1 #vertical DoF for this node
|
|
885
|
+
|
|
886
|
+
ux = round(ug[ix,0],5) #Horizontal nodal displacement
|
|
887
|
+
uy = round(ug[iy,0],5) #Vertical nodal displacement
|
|
888
|
+
print(f"Node {n+1}: Ux = {ux} m, Uy = {uy} m")
|
|
889
|
+
|
|
890
|
+
def SummarizeLinear(self):
|
|
891
|
+
print("LINEAR SOLVE (NO ITERATION)")
|
|
892
|
+
|
|
893
|
+
# -------------------------------------------------
|
|
894
|
+
# REACTIONS
|
|
895
|
+
# -------------------------------------------------
|
|
896
|
+
print("\nREACTIONS (at restrained DoF):")
|
|
897
|
+
for idx in self.Inp.restrainedIndex:
|
|
898
|
+
print(f"DoF {idx+1}: R = {self.Reactions[idx,0]/1000:.2f} kN")
|
|
899
|
+
|
|
900
|
+
# -------------------------------------------------
|
|
901
|
+
# MEMBER FORCES
|
|
902
|
+
# -------------------------------------------------
|
|
903
|
+
print("\nMEMBER FORCES (incl. P0):")
|
|
904
|
+
for n, mbr in enumerate(self.Inp.members):
|
|
905
|
+
print(
|
|
906
|
+
f"Member {n+1} (nodes {mbr[0]}-{mbr[1]}): "
|
|
907
|
+
f"N = {self.MBRFORCES[n,0]/1000:.2f} kN"
|
|
908
|
+
)
|
|
909
|
+
|
|
910
|
+
# -------------------------------------------------
|
|
911
|
+
# NODAL DISPLACEMENTS
|
|
912
|
+
# -------------------------------------------------
|
|
913
|
+
print("\nNODAL DISPLACEMENTS:")
|
|
914
|
+
for n in range(len(self.Inp.nodes)):
|
|
915
|
+
ix = 2 * (n + 1) - 2
|
|
916
|
+
iy = 2 * (n + 1) - 1
|
|
917
|
+
print(
|
|
918
|
+
f"Node {n+1}: "
|
|
919
|
+
f"Ux = {self.UG_FINAL[ix,0]:.6e} m, "
|
|
920
|
+
f"Uy = {self.UG_FINAL[iy,0]:.6e} m"
|
|
921
|
+
)
|
|
922
|
+
|
|
923
|
+
# -------------------------------------------------
|
|
924
|
+
# SPRING FORCES
|
|
925
|
+
# -------------------------------------------------
|
|
926
|
+
if len(self.Inp.springLocationData) == 0:
|
|
927
|
+
print("\nNO SPRINGS IN SYSTEM")
|
|
928
|
+
return
|
|
929
|
+
|
|
930
|
+
print("\nSPRING FORCES:")
|
|
931
|
+
|
|
932
|
+
# displacement vector
|
|
933
|
+
u = self.UG_FINAL.reshape(-1, 1)
|
|
934
|
+
|
|
935
|
+
# diagonal spring stiffness vector
|
|
936
|
+
kdiag = self.Kspring_diag.reshape(-1, 1)
|
|
937
|
+
|
|
938
|
+
# spring forces per DoF
|
|
939
|
+
f_spring = kdiag * u
|
|
940
|
+
|
|
941
|
+
try:
|
|
942
|
+
spring_no = self.Inp.springLocationData[:, 0].astype(int)
|
|
943
|
+
nodes = self.Inp.springLocationData[:, 1].astype(int)
|
|
944
|
+
k_values = self.Inp.springLocationData[:, 2].astype(float)
|
|
945
|
+
directions = np.asarray(self.Inp.SpringDirections)
|
|
946
|
+
|
|
947
|
+
for no, node, k_i, d in zip(spring_no, nodes, k_values, directions):
|
|
948
|
+
d = str(d).strip().lower().replace('"', '')
|
|
949
|
+
|
|
950
|
+
if d == "x":
|
|
951
|
+
dof = 2 * node - 2
|
|
952
|
+
elif d == "y":
|
|
953
|
+
dof = 2 * node - 1
|
|
954
|
+
else:
|
|
955
|
+
raise ValueError(f"Unknown spring direction: {d}")
|
|
956
|
+
|
|
957
|
+
print(
|
|
958
|
+
f"Spring {no} | Node {node} | Dir {d} | "
|
|
959
|
+
f"k = {k_i:.3e} N/m | "
|
|
960
|
+
f"u = {u[dof,0]:.6e} m | "
|
|
961
|
+
f"F = {f_spring[dof,0]/1000:.2f} kN"
|
|
962
|
+
)
|
|
963
|
+
|
|
964
|
+
except Exception as e:
|
|
965
|
+
print("Error while printing spring forces")
|
|
966
|
+
# print(e)
|
|
967
|
+
|