milcapy 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- milcapy/__init__.py +29 -0
- milcapy/analysis/__init__.py +0 -0
- milcapy/analysis/linear_static.py +392 -0
- milcapy/analysis/manager.py +101 -0
- milcapy/core/__init__.py +0 -0
- milcapy/core/node.py +94 -0
- milcapy/core/results.py +256 -0
- milcapy/elements/CST.py +137 -0
- milcapy/elements/MQ6.py +330 -0
- milcapy/elements/MQ6I.py +246 -0
- milcapy/elements/MQ6IMod.py +40 -0
- milcapy/elements/__init__.py +0 -0
- milcapy/elements/member.py +390 -0
- milcapy/elements/truss.py +133 -0
- milcapy/loads/__init__.py +9 -0
- milcapy/loads/load.py +1100 -0
- milcapy/loads/load_pattern.py +419 -0
- milcapy/material/__init__.py +0 -0
- milcapy/material/material.py +68 -0
- milcapy/model/__init__.py +0 -0
- milcapy/model/model.py +1482 -0
- milcapy/plotter/UIdisplay.py +926 -0
- milcapy/plotter/__init__.py +0 -0
- milcapy/plotter/load.py +454 -0
- milcapy/plotter/options.py +450 -0
- milcapy/plotter/plotter.py +2115 -0
- milcapy/plotter/plotter_values.py +219 -0
- milcapy/plotter/suports.py +322 -0
- milcapy/plotter/utils.py +103 -0
- milcapy/plotter/widgets.py +569 -0
- milcapy/postprocess/CST_pp.py +55 -0
- milcapy/postprocess/__init__.py +0 -0
- milcapy/postprocess/membranebQ6.py +7 -0
- milcapy/postprocess/post_processing.py +220 -0
- milcapy/postprocess/segment_member.py +406 -0
- milcapy/report/__init__.py +0 -0
- milcapy/report/report.py +26 -0
- milcapy/section/__init__.py +0 -0
- milcapy/section/section.py +304 -0
- milcapy/utils/__init__.py +24 -0
- milcapy/utils/element.py +283 -0
- milcapy/utils/geometry.py +106 -0
- milcapy/utils/types.py +96 -0
- milcapy-0.1.0.dist-info/METADATA +627 -0
- milcapy-0.1.0.dist-info/RECORD +47 -0
- milcapy-0.1.0.dist-info/WHEEL +5 -0
- milcapy-0.1.0.dist-info/top_level.txt +1 -0
milcapy/__init__.py
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
from .model.model import SystemMilcaModel
|
|
2
|
+
from .utils.types import (
|
|
3
|
+
BeamTheoriesType,
|
|
4
|
+
CoordinateSystemType,
|
|
5
|
+
DirectionType,
|
|
6
|
+
StateType,
|
|
7
|
+
LoadType,
|
|
8
|
+
FieldTypeMembrane,
|
|
9
|
+
ConstitutiveModel,
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
class SystemModel(SystemMilcaModel):
|
|
13
|
+
"""
|
|
14
|
+
Class to create a model for the MILCA software.
|
|
15
|
+
|
|
16
|
+
Attributes:
|
|
17
|
+
model (SystemMilcaModel): Instance of the SystemMilcaModel class.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
def __init__(self):
|
|
21
|
+
super().__init__()
|
|
22
|
+
|
|
23
|
+
def model_viewer(
|
|
24
|
+
model: SystemMilcaModel
|
|
25
|
+
):
|
|
26
|
+
"""
|
|
27
|
+
Muestra la interfaz gráfica para visualizar el modelo.
|
|
28
|
+
"""
|
|
29
|
+
model.show()
|
|
File without changes
|
|
@@ -0,0 +1,392 @@
|
|
|
1
|
+
from typing import TYPE_CHECKING, Tuple, Dict
|
|
2
|
+
import numpy as np
|
|
3
|
+
import time
|
|
4
|
+
from milcapy.loads.load import PrescribedDOF
|
|
5
|
+
|
|
6
|
+
if TYPE_CHECKING:
|
|
7
|
+
from milcapy.model.model import SystemMilcaModel
|
|
8
|
+
|
|
9
|
+
class LinearStaticAnalysis:
|
|
10
|
+
"""
|
|
11
|
+
Solucionador basado en el Método de Rigidez Directa.
|
|
12
|
+
Resuelve sistemas de ecuaciones estructurales K*u = F usando métodos de solución directa.
|
|
13
|
+
Usa numpy.linalg.solve(K, F)
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
def __init__(
|
|
17
|
+
self,
|
|
18
|
+
model: "SystemMilcaModel",
|
|
19
|
+
) -> None:
|
|
20
|
+
"""
|
|
21
|
+
Inicializa el solucionador con el modelo y el método de solución.
|
|
22
|
+
|
|
23
|
+
Args:
|
|
24
|
+
model: Modelo estructural que contiene K_global y F_global.
|
|
25
|
+
"""
|
|
26
|
+
self.model = model
|
|
27
|
+
|
|
28
|
+
# Rendimiento y diagnóstico
|
|
29
|
+
self.solution_time = 0.0
|
|
30
|
+
self.assembly_time = 0.0
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _dof_map(
|
|
34
|
+
self,
|
|
35
|
+
) -> Tuple[np.ndarray, np.ndarray, Dict[int, float]]:
|
|
36
|
+
"""Mapea los grados de libertad del modelo para trabajar con numpy.
|
|
37
|
+
[0, 1, 2, ] pertenece al nodo 1 y así sucesivamente."""
|
|
38
|
+
nn = len(self.model.nodes)
|
|
39
|
+
restraints = np.zeros(nn * 3, dtype=bool)
|
|
40
|
+
|
|
41
|
+
prescribed_dofs = {} # {dof: value}
|
|
42
|
+
|
|
43
|
+
for node in self.model.nodes.values():
|
|
44
|
+
preDOF: PrescribedDOF = node.prescribed_dofs.get(self.model.current_load_pattern, PrescribedDOF())
|
|
45
|
+
|
|
46
|
+
restraints[node.dofs[0] - 1] = node.restraints[0] #or preDOF.ux is not None
|
|
47
|
+
restraints[node.dofs[1] - 1] = node.restraints[1] #or preDOF.uy is not None
|
|
48
|
+
restraints[node.dofs[2] - 1] = node.restraints[2] #or preDOF.rz is not None
|
|
49
|
+
|
|
50
|
+
if preDOF.ux is not None:
|
|
51
|
+
prescribed_dofs[node.dofs[0] - 1] = preDOF.ux
|
|
52
|
+
if preDOF.uy is not None:
|
|
53
|
+
prescribed_dofs[node.dofs[1] - 1] = preDOF.uy
|
|
54
|
+
if preDOF.rz is not None:
|
|
55
|
+
prescribed_dofs[node.dofs[2] - 1] = preDOF.rz
|
|
56
|
+
|
|
57
|
+
free_dofs = np.where(~restraints)[0]
|
|
58
|
+
restrained_dofs = np.where(restraints)[0]
|
|
59
|
+
|
|
60
|
+
return free_dofs, restrained_dofs, prescribed_dofs
|
|
61
|
+
|
|
62
|
+
def assemble_global_load_vector(
|
|
63
|
+
self,
|
|
64
|
+
) -> np.ndarray:
|
|
65
|
+
"""Calcula el vector de carga global del sistema.
|
|
66
|
+
|
|
67
|
+
Returns:
|
|
68
|
+
np.ndarray: Vector de carga global.
|
|
69
|
+
"""
|
|
70
|
+
start_time = time.time()
|
|
71
|
+
|
|
72
|
+
nn = len(self.model.nodes)
|
|
73
|
+
F = np.zeros(nn * 3)
|
|
74
|
+
|
|
75
|
+
# Asignar fuerzas nodales almacenadas en los nodos
|
|
76
|
+
for node in self.model.nodes.values():
|
|
77
|
+
f = node.load_vector()
|
|
78
|
+
dofs = node.dofs
|
|
79
|
+
F[dofs - 1] += f
|
|
80
|
+
|
|
81
|
+
# Agregar el vector de fuerzas globales almacenadas en los miembros
|
|
82
|
+
for member in self.model.members.values():
|
|
83
|
+
f = member.global_load_vector()
|
|
84
|
+
dofs = member.dofs
|
|
85
|
+
F[dofs - 1] += f
|
|
86
|
+
|
|
87
|
+
# Agregar cargas de los elementos CST
|
|
88
|
+
for cst in self.model.csts.values():
|
|
89
|
+
f = cst.get_load_vector()
|
|
90
|
+
dofs = cst.dofs
|
|
91
|
+
F[dofs - 1] += f
|
|
92
|
+
|
|
93
|
+
# Agregar cargas de los elementos Truss
|
|
94
|
+
for truss in self.model.trusses.values():
|
|
95
|
+
f = truss.global_load_vector()
|
|
96
|
+
dofs = truss.dofs
|
|
97
|
+
F[dofs - 1] += f
|
|
98
|
+
|
|
99
|
+
end_time = time.time()
|
|
100
|
+
self.assembly_time += (end_time - start_time)
|
|
101
|
+
|
|
102
|
+
return F
|
|
103
|
+
|
|
104
|
+
def assemble_global_stiffness_matrix(
|
|
105
|
+
self,
|
|
106
|
+
) -> np.ndarray:
|
|
107
|
+
"""Ensamblaje de la matriz de rigidez global.
|
|
108
|
+
|
|
109
|
+
Returns:
|
|
110
|
+
np.ndarray: Matriz de rigidez global.
|
|
111
|
+
"""
|
|
112
|
+
start_time = time.time()
|
|
113
|
+
nn = len(self.model.nodes)
|
|
114
|
+
K = np.zeros((nn * 3, nn * 3))
|
|
115
|
+
|
|
116
|
+
# Ensamblar la matriz de rigidez global
|
|
117
|
+
# Rigides de los elmentos 1D
|
|
118
|
+
for member in self.model.members.values():
|
|
119
|
+
k = member.global_stiffness_matrix()
|
|
120
|
+
dofs = member.dofs
|
|
121
|
+
|
|
122
|
+
rows = np.repeat(dofs - 1, 6)
|
|
123
|
+
cols = np.tile(dofs - 1, 6)
|
|
124
|
+
values = k.flatten()
|
|
125
|
+
|
|
126
|
+
for i, j, val in zip(rows, cols, values):
|
|
127
|
+
K[i, j] += val
|
|
128
|
+
|
|
129
|
+
# Rigidez de los elementos CST
|
|
130
|
+
for cst in self.model.csts.values():
|
|
131
|
+
k = cst.global_stiffness_matrix()
|
|
132
|
+
dofs = cst.dofs
|
|
133
|
+
|
|
134
|
+
rows = np.repeat(dofs - 1, 6)
|
|
135
|
+
cols = np.tile(dofs - 1, 6)
|
|
136
|
+
values = k.flatten()
|
|
137
|
+
|
|
138
|
+
for i, j, val in zip(rows, cols, values):
|
|
139
|
+
K[i, j] += val
|
|
140
|
+
|
|
141
|
+
# Rigidez de los elementos Membrane Quad 6
|
|
142
|
+
for membQ6 in self.model.membrane_q6.values():
|
|
143
|
+
k = membQ6.global_stiffness_matrix()
|
|
144
|
+
dofs = membQ6.dofs
|
|
145
|
+
|
|
146
|
+
rows = np.repeat(dofs - 1, 12)
|
|
147
|
+
cols = np.tile(dofs - 1, 12)
|
|
148
|
+
values = k.flatten()
|
|
149
|
+
|
|
150
|
+
for i, j, val in zip(rows, cols, values):
|
|
151
|
+
K[i, j] += val
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
# Rigidez de los elementos Membrana Quad6I
|
|
155
|
+
for membQ6I in self.model.membrane_q6i.values():
|
|
156
|
+
k = membQ6I.global_stiffness_matrix()
|
|
157
|
+
dofs = membQ6I.dofs
|
|
158
|
+
|
|
159
|
+
rows = np.repeat(dofs - 1, 8) # 8 es el numero de grados de libertad del elemento
|
|
160
|
+
cols = np.tile(dofs - 1, 8)
|
|
161
|
+
values = k.flatten()
|
|
162
|
+
|
|
163
|
+
for i, j, val in zip(rows, cols, values):
|
|
164
|
+
K[i, j] += val
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
# Rigidez de los elementos Armadura
|
|
168
|
+
for truss in self.model.trusses.values():
|
|
169
|
+
k = truss.global_stiffness_matrix()
|
|
170
|
+
dofs = truss.dofs
|
|
171
|
+
|
|
172
|
+
rows = np.repeat(dofs - 1, 4)
|
|
173
|
+
cols = np.tile(dofs - 1, 4)
|
|
174
|
+
values = k.flatten()
|
|
175
|
+
|
|
176
|
+
for i, j, val in zip(rows, cols, values):
|
|
177
|
+
K[i, j] += val
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
# aplicar las condiciones de ejes locales de los nodos
|
|
181
|
+
# for node in self.model.nodes.values():
|
|
182
|
+
# if node.local_axis is not None:
|
|
183
|
+
# dofs = node.dofs
|
|
184
|
+
# Tlg = node.local_axis.get_transformation_matrix()
|
|
185
|
+
# kfc = Tlg.T @ K[dofs - 1, dofs - 1] @ Tlg
|
|
186
|
+
# kfila = np.zeros((3, nn * 3))
|
|
187
|
+
# kcolumna = np.zeros((nn * 3, 3))
|
|
188
|
+
# for node in self.model.nodes.values():
|
|
189
|
+
# dofi = node.dofs
|
|
190
|
+
# kfila[:, dofi - 1] += Tlg.T @ K[dofs - 1, dofi - 1]
|
|
191
|
+
# kcolumna[dofi - 1, :] += K[dofi - 1, dofs - 1] @ Tlg
|
|
192
|
+
|
|
193
|
+
# K[dofs - 1, :] = kfila
|
|
194
|
+
# K[:, dofs - 1] = kcolumna
|
|
195
|
+
# K[dofs - 1, dofs - 1] = kfc
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
K = self.apply_local_axes(K, self.model)
|
|
199
|
+
|
|
200
|
+
# Kold = K.copy()
|
|
201
|
+
# for node in self.model.nodes.values():
|
|
202
|
+
# if node.local_axis is not None:
|
|
203
|
+
# dofs = node.dofs
|
|
204
|
+
# Tlg = node.local_axis.get_transformation_matrix()
|
|
205
|
+
|
|
206
|
+
# # diagonal
|
|
207
|
+
# kfc = Tlg.T @ Kold[dofs - 1, dofs - 1] @ Tlg
|
|
208
|
+
|
|
209
|
+
# # fila y columna
|
|
210
|
+
# kfila = np.zeros((3, nn * 3))
|
|
211
|
+
# kcolumna = np.zeros((nn * 3, 3))
|
|
212
|
+
# for nodej in self.model.nodes.values():
|
|
213
|
+
# dofj = nodej.dofs
|
|
214
|
+
# kfila[:, dofj - 1] = Tlg.T @ Kold[dofs - 1, dofj - 1]
|
|
215
|
+
# kcolumna[dofj - 1, :] = Kold[dofs - 1, dofj - 1] @ Tlg
|
|
216
|
+
|
|
217
|
+
# # escribir en la nueva K
|
|
218
|
+
# K[dofs - 1, :] = kfila
|
|
219
|
+
# K[:, dofs - 1] = kcolumna
|
|
220
|
+
# K[dofs - 1, dofs - 1] = kfc
|
|
221
|
+
|
|
222
|
+
# for i, j, val in zip(rows, cols, values):
|
|
223
|
+
# K[i, j] += val
|
|
224
|
+
|
|
225
|
+
end_time = time.time()
|
|
226
|
+
self.assembly_time += (end_time - start_time)
|
|
227
|
+
|
|
228
|
+
return K
|
|
229
|
+
|
|
230
|
+
@staticmethod
|
|
231
|
+
def apply_local_axes(
|
|
232
|
+
K: np.ndarray,
|
|
233
|
+
model: "SystemMilcaModel"
|
|
234
|
+
) -> np.ndarray:
|
|
235
|
+
"""
|
|
236
|
+
Aplica las transformaciones locales de cada nodo a la matriz de rigidez global K.
|
|
237
|
+
"""
|
|
238
|
+
nn = len(model.nodes)
|
|
239
|
+
ndof = nn * 3
|
|
240
|
+
Kold = K.copy()
|
|
241
|
+
Knew = Kold.copy()
|
|
242
|
+
|
|
243
|
+
for node in model.nodes.values():
|
|
244
|
+
if node.local_axis is None:
|
|
245
|
+
continue
|
|
246
|
+
|
|
247
|
+
dofs = node.dofs - 1 # índices base 0
|
|
248
|
+
T = node.local_axis.get_transformation_matrix()
|
|
249
|
+
|
|
250
|
+
# fila
|
|
251
|
+
for nodej in model.nodes.values():
|
|
252
|
+
dofj = nodej.dofs - 1
|
|
253
|
+
Knew[np.ix_(dofs, dofj)] = T.T @ Kold[np.ix_(dofs, dofj)]
|
|
254
|
+
|
|
255
|
+
# columna
|
|
256
|
+
for nodej in model.nodes.values():
|
|
257
|
+
dofj = nodej.dofs - 1
|
|
258
|
+
Knew[np.ix_(dofj, dofs)] = Kold[np.ix_(dofj, dofs)] @ T
|
|
259
|
+
|
|
260
|
+
# diagonal
|
|
261
|
+
Knew[np.ix_(dofs, dofs)] = T.T @ Kold[np.ix_(dofs, dofs)] @ T
|
|
262
|
+
|
|
263
|
+
return Knew
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
def apply_boundary_conditions(
|
|
268
|
+
self,
|
|
269
|
+
K: np.ndarray,
|
|
270
|
+
F: np.ndarray,
|
|
271
|
+
free_dofs: np.ndarray,
|
|
272
|
+
restrained_dofs: np.ndarray
|
|
273
|
+
) -> Tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray, np.ndarray, np.ndarray]:
|
|
274
|
+
"""Aplica las condiciones de frontera.
|
|
275
|
+
|
|
276
|
+
Args:
|
|
277
|
+
K (np.ndarray): Matriz de rigidez global.
|
|
278
|
+
F (np.ndarray): Vector de fuerzas global.
|
|
279
|
+
|
|
280
|
+
Returns:
|
|
281
|
+
Tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray, np.ndarray, np.ndarray]:
|
|
282
|
+
- K_d: Matriz de rigidez para grados de libertad libres.
|
|
283
|
+
- K_dc: Matriz de rigidez que relaciona GDL libres con restringidos.
|
|
284
|
+
- K_cd: Matriz de rigidez que relaciona GDL restringidos con libres.
|
|
285
|
+
- K_c: Matriz de rigidez para GDL restringidos.
|
|
286
|
+
- F_d: Vector de fuerzas para GDL libres.
|
|
287
|
+
- F_c: Vector de fuerzas para GDL restringidos.
|
|
288
|
+
"""
|
|
289
|
+
|
|
290
|
+
# Aplicar las condiciones de apoyos elásticos
|
|
291
|
+
nn = len(self.model.nodes)
|
|
292
|
+
K_kk = np.zeros((nn * 3, nn * 3))
|
|
293
|
+
for node in self.model.nodes.values():
|
|
294
|
+
if node.elastic_supports is not None:
|
|
295
|
+
kx, ky, krz = node.elastic_supports.get_elastic_supports()
|
|
296
|
+
if kx is not None:
|
|
297
|
+
K_kk[node.dofs[0] - 1, node.dofs[0] - 1] += kx
|
|
298
|
+
if ky is not None:
|
|
299
|
+
K_kk[node.dofs[1] - 1, node.dofs[1] - 1] += ky
|
|
300
|
+
if krz is not None:
|
|
301
|
+
K_kk[node.dofs[2] - 1, node.dofs[2] - 1] += krz
|
|
302
|
+
K = K + K_kk
|
|
303
|
+
|
|
304
|
+
# Reducir la matriz de rigidez (d: desconocidos, c: conocidos)
|
|
305
|
+
# | Kd Kdc |
|
|
306
|
+
# K = | |
|
|
307
|
+
# | Kcd Kc |
|
|
308
|
+
K_d = K[np.ix_(free_dofs, free_dofs)]
|
|
309
|
+
K_dc = K[np.ix_(free_dofs, restrained_dofs)]
|
|
310
|
+
K_cd = K[np.ix_(restrained_dofs, free_dofs)]
|
|
311
|
+
K_c = K[np.ix_(restrained_dofs, restrained_dofs)]
|
|
312
|
+
|
|
313
|
+
# Reducir el vector de fuerzas
|
|
314
|
+
# | Fd |
|
|
315
|
+
# F = | |
|
|
316
|
+
# | Fc |
|
|
317
|
+
F_d = F[free_dofs]
|
|
318
|
+
F_c = F[restrained_dofs]
|
|
319
|
+
|
|
320
|
+
return K_d, K_dc, K_cd, K_c, F_d, F_c, K_kk
|
|
321
|
+
|
|
322
|
+
def solve(
|
|
323
|
+
self,
|
|
324
|
+
) -> Tuple[np.ndarray, np.ndarray]:
|
|
325
|
+
"""Resuelve el sistema de ecuaciones F = KU.
|
|
326
|
+
|
|
327
|
+
Returns:
|
|
328
|
+
Tuple[np.ndarray, np.ndarray]:
|
|
329
|
+
- Desplazamientos nodales
|
|
330
|
+
- Reacciones en los apoyos.
|
|
331
|
+
"""
|
|
332
|
+
|
|
333
|
+
start_time = time.time()
|
|
334
|
+
# Obtener la matriz de rigidez global y el vector de fuerzas global
|
|
335
|
+
K = self.assemble_global_stiffness_matrix()
|
|
336
|
+
F = self.assemble_global_load_vector()
|
|
337
|
+
self.model.global_stiffness_matrix = K
|
|
338
|
+
self.model.global_load_vector = {}
|
|
339
|
+
self.model.global_load_vector[self.model.current_load_pattern] = F
|
|
340
|
+
|
|
341
|
+
# Aplicar las condiciones de frontera
|
|
342
|
+
free_dofs, restrained_dofs, prescribed_dofs = self._dof_map()
|
|
343
|
+
K_d, K_dc, K_cd, K_c, F_d, F_c, K_kk = self.apply_boundary_conditions(K, F, free_dofs, restrained_dofs)
|
|
344
|
+
|
|
345
|
+
# Resolver el sistema de ecuaciones
|
|
346
|
+
# |Ud|
|
|
347
|
+
# U = | | = displacements
|
|
348
|
+
# |Uc|
|
|
349
|
+
|
|
350
|
+
# Colocar los desplazamientos en los grados de libertad libres
|
|
351
|
+
nn = len(self.model.nodes)
|
|
352
|
+
# completar el vector de desplazamientos
|
|
353
|
+
displacements = np.zeros(nn * 3)
|
|
354
|
+
# Aplicar las condiciones de prescindidos
|
|
355
|
+
displacements[list(prescribed_dofs.keys())] = list(prescribed_dofs.values())
|
|
356
|
+
Uc = np.copy(displacements[restrained_dofs])
|
|
357
|
+
Upc = np.copy(displacements[free_dofs])
|
|
358
|
+
|
|
359
|
+
# resolver el sistema de ecuaciones
|
|
360
|
+
if np.linalg.det(K_d) == 0:
|
|
361
|
+
try:
|
|
362
|
+
factorDeRegularizacion = 1e-7
|
|
363
|
+
Warning(f"La matriz de rigidez global es singular, por lo que para resolver se le sumara un I[ndof. ndof] * {factorDeRegularizacion} a la matriz de rigidez global")
|
|
364
|
+
K_d += np.eye(K_d.shape[0]) * factorDeRegularizacion
|
|
365
|
+
U_d = np.linalg.solve(K_d, F_d - K_dc @ Uc - K_d @ Upc)
|
|
366
|
+
except:
|
|
367
|
+
raise ValueError("La matriz de rigidez global es singular y no se puede resolver")
|
|
368
|
+
else:
|
|
369
|
+
U_d = np.linalg.solve(K_d, F_d - K_dc @ Uc - K_d @ Upc)
|
|
370
|
+
|
|
371
|
+
|
|
372
|
+
displacements[free_dofs] = U_d + Upc
|
|
373
|
+
|
|
374
|
+
# Calcular las reacciones en los apoyos
|
|
375
|
+
R = K_cd @ (U_d + Upc) + K_c @ Uc - F_c
|
|
376
|
+
# Completar el vector de reacciones
|
|
377
|
+
reactions = np.zeros(nn * 3)
|
|
378
|
+
reactions[restrained_dofs] = R
|
|
379
|
+
# corregir las reacciones por las condiciones de apoyos elásticos
|
|
380
|
+
reactions -= K_kk @ displacements
|
|
381
|
+
|
|
382
|
+
# Otra forma de alcular las reacciones en los apoyos, para no estar completando el vector de reacciones
|
|
383
|
+
# R = K_global * U_global - F_global
|
|
384
|
+
# reactions = K @ displacements - F
|
|
385
|
+
|
|
386
|
+
end_time = time.time()
|
|
387
|
+
self.solution_time = (end_time - start_time)
|
|
388
|
+
|
|
389
|
+
## Tiempo de solucion
|
|
390
|
+
print(f"Tiempo de solucion: {self.solution_time} para el Load Pattern: {self.model.current_load_pattern}")
|
|
391
|
+
|
|
392
|
+
return displacements, reactions
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
from typing import TYPE_CHECKING, ValuesView
|
|
2
|
+
from milcapy.analysis.linear_static import LinearStaticAnalysis
|
|
3
|
+
from milcapy.postprocess.post_processing import PostProcessing
|
|
4
|
+
from milcapy.core.results import Results
|
|
5
|
+
from milcapy.utils.types import StateType
|
|
6
|
+
if TYPE_CHECKING:
|
|
7
|
+
from milcapy.model.model import SystemMilcaModel
|
|
8
|
+
from milcapy.loads.load_pattern import LoadPattern
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class AnalysisManager:
|
|
13
|
+
"""Clase mananger para el análisis estructural.
|
|
14
|
+
maneja los tipos de análisis y opciones de análisis estructural.
|
|
15
|
+
realiza el análisis estructural para todos las condiciones de carga."""
|
|
16
|
+
|
|
17
|
+
def __init__(
|
|
18
|
+
self,
|
|
19
|
+
model: "SystemMilcaModel",
|
|
20
|
+
) -> None:
|
|
21
|
+
"""
|
|
22
|
+
Inicializa el análisis estructural.
|
|
23
|
+
|
|
24
|
+
Args:
|
|
25
|
+
model: Sistema estructural a analizar.
|
|
26
|
+
"""
|
|
27
|
+
self.model = model
|
|
28
|
+
self.load_patterns = model.load_patterns
|
|
29
|
+
self.results = model.results
|
|
30
|
+
self.current_load_pattern: str | None = None
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def notify_all(
|
|
34
|
+
self,
|
|
35
|
+
) -> None:
|
|
36
|
+
"""Notifica a todos los componentes del modelo."""
|
|
37
|
+
# Notificar a todos los miembros del modelo
|
|
38
|
+
for member in self.model.members.values():
|
|
39
|
+
member.set_current_load_pattern(self.current_load_pattern)
|
|
40
|
+
# Notificar a todos los cerchas del modelo
|
|
41
|
+
for truss in self.model.trusses.values():
|
|
42
|
+
truss.set_current_load_pattern(self.current_load_pattern)
|
|
43
|
+
# Notificar a todos los nodos del modelo
|
|
44
|
+
for node in self.model.nodes.values():
|
|
45
|
+
node.set_current_load_pattern(self.current_load_pattern)
|
|
46
|
+
# Notificar al modelo
|
|
47
|
+
self.model.current_load_pattern = self.current_load_pattern
|
|
48
|
+
|
|
49
|
+
def analyze_for_list_load_pattern(
|
|
50
|
+
self,
|
|
51
|
+
load_pattern_name: list[str] | None = None,
|
|
52
|
+
) -> None:
|
|
53
|
+
"""Ejecuta el análisis estructural para todas las condiciones de carga."""
|
|
54
|
+
|
|
55
|
+
patterns_to_analyze: list[LoadPattern] | ValuesView[LoadPattern] = []
|
|
56
|
+
|
|
57
|
+
if load_pattern_name is not None:
|
|
58
|
+
patterns_to_analyze = [self.load_patterns[name] for name in load_pattern_name]
|
|
59
|
+
else:
|
|
60
|
+
patterns_to_analyze = self.load_patterns.values()
|
|
61
|
+
|
|
62
|
+
# solucionar para cada load pattern
|
|
63
|
+
for load_pattern in patterns_to_analyze:
|
|
64
|
+
if load_pattern.state == StateType.INACTIVE:
|
|
65
|
+
continue
|
|
66
|
+
# SYSTEM NOTIFICATION: notificar a todos los componentes del modelo
|
|
67
|
+
self.current_load_pattern = load_pattern.name
|
|
68
|
+
self.notify_all()
|
|
69
|
+
|
|
70
|
+
# Asignar las cargas a los nodos y miembros almacenados en el patrón de carga
|
|
71
|
+
load_pattern.assign_loads_to_nodes()
|
|
72
|
+
load_pattern.assign_loads_to_members()
|
|
73
|
+
load_pattern.assign_prescribed_dofs_to_nodes()
|
|
74
|
+
|
|
75
|
+
# resolver el modelo
|
|
76
|
+
analysis = LinearStaticAnalysis(self.model)
|
|
77
|
+
displacements, reactions = analysis.solve()
|
|
78
|
+
|
|
79
|
+
# Crear un objeto de resultados para almacenar los resultados del análisis
|
|
80
|
+
self.results[load_pattern.name] = Results()
|
|
81
|
+
self.results[load_pattern.name].set_model_displacements(displacements)
|
|
82
|
+
self.results[load_pattern.name].set_model_reactions(reactions)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
# crear un objeto de post-procesamiento para procesar los resultados
|
|
86
|
+
post_processing = PostProcessing(self.model, self.results[load_pattern.name], self.model.postprocessing_options, load_pattern.name)
|
|
87
|
+
post_processing.process_displacements_for_nodes()
|
|
88
|
+
post_processing.process_reactions_for_nodes()
|
|
89
|
+
post_processing.process_displacements_for_members()
|
|
90
|
+
post_processing.process_internal_forces_for_members()
|
|
91
|
+
post_processing.process_displacements_for_cst()
|
|
92
|
+
post_processing.post_process_for_cst()
|
|
93
|
+
post_processing.process_displacements_for_membrane_q6()
|
|
94
|
+
post_processing.process_displacements_for_membrane_q6i()
|
|
95
|
+
post_processing.post_process_for_members()
|
|
96
|
+
post_processing.process_displacements_for_trusses()
|
|
97
|
+
post_processing.process_internal_forces_for_trusses()
|
|
98
|
+
post_processing.post_process_for_trusses()
|
|
99
|
+
|
|
100
|
+
# actualizar el estado de analisis en LoadPattern
|
|
101
|
+
load_pattern.analyzed = True
|
milcapy/core/__init__.py
ADDED
|
File without changes
|
milcapy/core/node.py
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
from typing import TYPE_CHECKING, Dict, Optional
|
|
2
|
+
from milcapy.loads.load import PointLoad, PrescribedDOF, ElasticSupport, LocalAxis
|
|
3
|
+
import numpy as np
|
|
4
|
+
|
|
5
|
+
if TYPE_CHECKING:
|
|
6
|
+
from milcapy.utils.types import Restraints
|
|
7
|
+
from milcapy.utils.geometry import Vertex
|
|
8
|
+
|
|
9
|
+
class Node:
|
|
10
|
+
"""
|
|
11
|
+
Representa un nodo en el modelo estructural.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
def __init__(
|
|
15
|
+
self,
|
|
16
|
+
id: int,
|
|
17
|
+
vertex: "Vertex"
|
|
18
|
+
) -> None:
|
|
19
|
+
"""
|
|
20
|
+
Inicializa un nodo con su identificador y posición.
|
|
21
|
+
|
|
22
|
+
Args:
|
|
23
|
+
id (int): Identificador único del nodo.
|
|
24
|
+
vertex (Vertex): Coordenadas (x, y) del nodo.
|
|
25
|
+
"""
|
|
26
|
+
self.id: int = id
|
|
27
|
+
self.vertex: "Vertex" = vertex
|
|
28
|
+
self.restraints: "Restraints" = (
|
|
29
|
+
False, False, False) # Restricciones del nodo
|
|
30
|
+
|
|
31
|
+
# Cálculo de los índices de grados de libertad
|
|
32
|
+
self.dofs: np.ndarray = np.array([
|
|
33
|
+
self.id * 3 - 2, # DOF en x
|
|
34
|
+
self.id * 3 - 1, # DOF en y
|
|
35
|
+
self.id * 3 # DOF en theta
|
|
36
|
+
], dtype=int)
|
|
37
|
+
|
|
38
|
+
# Cargas aplicadas al nodo
|
|
39
|
+
self.loads: Dict[str, PointLoad] = {} # {pattern_name: PointLoad}
|
|
40
|
+
|
|
41
|
+
# Patrón de carga actual
|
|
42
|
+
self.current_load_pattern: Optional[str] = None
|
|
43
|
+
|
|
44
|
+
# Desplazamientos prescindidos
|
|
45
|
+
self.prescribed_dofs: Dict[str, PrescribedDOF] = {}
|
|
46
|
+
|
|
47
|
+
# apoyos elásticos
|
|
48
|
+
self.elastic_supports: Optional[ElasticSupport] = None
|
|
49
|
+
|
|
50
|
+
# eje local
|
|
51
|
+
self.local_axis: Optional[LocalAxis] = None
|
|
52
|
+
|
|
53
|
+
def load_vector(self) -> np.ndarray:
|
|
54
|
+
"""
|
|
55
|
+
Vector de cargas aplicadas al nodo para el patrón de carga actual en sistema global.
|
|
56
|
+
"""
|
|
57
|
+
load = self.loads.get(self.current_load_pattern, PointLoad())
|
|
58
|
+
return load.components
|
|
59
|
+
|
|
60
|
+
def set_current_load_pattern(self, load_pattern_name: str) -> None:
|
|
61
|
+
"""
|
|
62
|
+
Establece el patrón de carga actual del nodo.
|
|
63
|
+
"""
|
|
64
|
+
self.current_load_pattern = load_pattern_name
|
|
65
|
+
|
|
66
|
+
def set_restraints(self, restraints: "Restraints") -> None:
|
|
67
|
+
"""
|
|
68
|
+
Establece las restricciones del nodo.
|
|
69
|
+
"""
|
|
70
|
+
self.restraints = restraints
|
|
71
|
+
|
|
72
|
+
def set_load(self, load: "PointLoad") -> None:
|
|
73
|
+
"""
|
|
74
|
+
Establece una carga puntual en el nodo.
|
|
75
|
+
"""
|
|
76
|
+
self.loads[self.current_load_pattern] = load
|
|
77
|
+
|
|
78
|
+
def set_prescribed_dof(self, prescribed_dof: "PrescribedDOF") -> None:
|
|
79
|
+
"""
|
|
80
|
+
Establece los desplazamientos prescindidos del nodo.
|
|
81
|
+
"""
|
|
82
|
+
self.prescribed_dofs[self.current_load_pattern] = prescribed_dof
|
|
83
|
+
|
|
84
|
+
def set_elastic_support(self, elastic_support: ElasticSupport) -> None:
|
|
85
|
+
"""
|
|
86
|
+
Establece los apoyos elásticos del nodo.
|
|
87
|
+
"""
|
|
88
|
+
self.elastic_supports = elastic_support
|
|
89
|
+
|
|
90
|
+
def set_local_axis(self, local_axis: LocalAxis) -> None:
|
|
91
|
+
"""
|
|
92
|
+
Establece el eje local del nodo. ojo esto hace que el vector de fuerzas aplicados, desplazamientos prescindidos y apoyos elásticos se transformen al sistema local.
|
|
93
|
+
"""
|
|
94
|
+
self.local_axis = local_axis
|