sdypy-model 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.
- sdypy/__init__.py +3 -0
- sdypy/model/__init__.py +7 -0
- sdypy/model/beam/beam.py +206 -0
- sdypy/model/eigenvalue_solution.py +38 -0
- sdypy/model/mesh/__init__.py +1 -0
- sdypy/model/mesh/mesh_2D.py +96 -0
- sdypy/model/shell/shell.py +881 -0
- sdypy/model/tetrahedron/mass_tet_basis.py +109 -0
- sdypy/model/tetrahedron/tet10.py +500 -0
- sdypy_model-0.1.0.dist-info/METADATA +89 -0
- sdypy_model-0.1.0.dist-info/RECORD +12 -0
- sdypy_model-0.1.0.dist-info/WHEEL +4 -0
sdypy/__init__.py
ADDED
sdypy/model/__init__.py
ADDED
sdypy/model/beam/beam.py
ADDED
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
import scipy.linalg
|
|
3
|
+
from scipy.sparse.linalg import eigsh
|
|
4
|
+
from scipy import sparse
|
|
5
|
+
import time
|
|
6
|
+
|
|
7
|
+
def matrices_k_e_timoshenko(l, E, I, A, nu=0.3, k_s=5/6):
|
|
8
|
+
"""
|
|
9
|
+
Stiffness matrix for a beam element (Timoshenko theory).
|
|
10
|
+
"""
|
|
11
|
+
G = E/(2*(1+nu))
|
|
12
|
+
phi = (12*E*I)/(k_s*A*G*l**2)
|
|
13
|
+
|
|
14
|
+
if type(l) == np.ndarray:
|
|
15
|
+
ones = np.ones_like(l)
|
|
16
|
+
else:
|
|
17
|
+
ones = 1
|
|
18
|
+
|
|
19
|
+
K = E*I/(l**3*(1+phi)) * np.array([[12*ones, 6*l, -12*ones, 6*l],
|
|
20
|
+
[6*l, (4+phi)*l**2, -6*l, (2-phi)*l**2],
|
|
21
|
+
[-12*ones, -6*l, 12*ones, -6*l],
|
|
22
|
+
[6*l, (2-phi)*l**2, -6*l, (4+phi)*l**2]])
|
|
23
|
+
|
|
24
|
+
return K
|
|
25
|
+
|
|
26
|
+
def matrices_m_e(l, m):
|
|
27
|
+
"""
|
|
28
|
+
Mass matrix of a beam element.
|
|
29
|
+
|
|
30
|
+
"""
|
|
31
|
+
if type(l) == np.ndarray:
|
|
32
|
+
ones = np.ones_like(l)
|
|
33
|
+
M = (m)/420 * np.array([[156*ones, 22*l, 54*ones, -13*l],
|
|
34
|
+
[22*l, 4*l**2, 13*l, -3*l**2],
|
|
35
|
+
[54*ones, 13*l, 156*ones, -22*l],
|
|
36
|
+
[-13*l, -3*l**2, -22*l, 4*l**2]])
|
|
37
|
+
else:
|
|
38
|
+
M = (m)/420 * np.array([[156, 22*l, 54, -13*l],
|
|
39
|
+
[22*l, 4*l**2, 13*l, -3*l**2],
|
|
40
|
+
[54, 13*l, 156, -22*l],
|
|
41
|
+
[-13*l, -3*l**2, -22*l, 4*l**2]])
|
|
42
|
+
return M
|
|
43
|
+
|
|
44
|
+
def matrices_k_e(l, EI):
|
|
45
|
+
"""
|
|
46
|
+
Stiffness matrix of a beam element.
|
|
47
|
+
|
|
48
|
+
"""
|
|
49
|
+
if type(l) == np.ndarray:
|
|
50
|
+
ones = np.ones_like(l)
|
|
51
|
+
K = (EI)/l**3 * np.array([[12*ones, 6*l, -12*ones, 6*l],
|
|
52
|
+
[6*l, 4*l**2, -6*l, 2*l**2],
|
|
53
|
+
[-12*ones, -6*l, 12*ones, -6*l],
|
|
54
|
+
[6*l, 2*l**2, -6*l, 4*l**2]])
|
|
55
|
+
|
|
56
|
+
else:
|
|
57
|
+
K = (EI)/l**3 * np.array([[12, 6*l, -12, 6*l],
|
|
58
|
+
[6*l, 4*l**2, -6*l, 2*l**2],
|
|
59
|
+
[-12, -6*l, 12, -6*l],
|
|
60
|
+
[6*l, 2*l**2, -6*l, 4*l**2]])
|
|
61
|
+
return K
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class Beam:
|
|
66
|
+
def __init__(self, org, conec, length, width, height, density, Young, n_nodes=None, added_masses=None, mass_locations=None):
|
|
67
|
+
"""
|
|
68
|
+
|
|
69
|
+
Parameters
|
|
70
|
+
----------
|
|
71
|
+
org : array_like
|
|
72
|
+
Organization matrix.
|
|
73
|
+
conec : array_like
|
|
74
|
+
Connectivity matrix.
|
|
75
|
+
length : array_like
|
|
76
|
+
Lengths of the beam elements.
|
|
77
|
+
width : float
|
|
78
|
+
Width of the beam.
|
|
79
|
+
height : float
|
|
80
|
+
Height of the beam.
|
|
81
|
+
mass : array_like
|
|
82
|
+
Masses of the beam elements.
|
|
83
|
+
Young : array_like
|
|
84
|
+
Young's modulus of each beam.
|
|
85
|
+
n_nodes : int, optional
|
|
86
|
+
Number of nodes to construct org and conec if not given.
|
|
87
|
+
"""
|
|
88
|
+
if org is None and n_nodes is not None:
|
|
89
|
+
org = np.linspace(0, np.sum(length), n_nodes)
|
|
90
|
+
conec = np.array([[i, i+1] for i in range(org.shape[0]-1)])
|
|
91
|
+
|
|
92
|
+
self.org = org
|
|
93
|
+
self.conec = conec
|
|
94
|
+
|
|
95
|
+
self.n_dof_node = 2
|
|
96
|
+
self.el_nodes = 2
|
|
97
|
+
self.n_elements = n_nodes - 1
|
|
98
|
+
self.n_dof = self.n_dof_node * n_nodes
|
|
99
|
+
|
|
100
|
+
self.length = length
|
|
101
|
+
self.width = width
|
|
102
|
+
self.height = height
|
|
103
|
+
self.mass = width * height * np.array(length) * np.array(density)
|
|
104
|
+
self.Young = Young
|
|
105
|
+
self.area = self.width*self.height
|
|
106
|
+
|
|
107
|
+
self.I = (self.height**3*self.width)/12
|
|
108
|
+
self.EI = np.array(self.Young) * self.I
|
|
109
|
+
|
|
110
|
+
self.added_masses = added_masses
|
|
111
|
+
self.mass_locations = mass_locations
|
|
112
|
+
|
|
113
|
+
self.construct_loce()
|
|
114
|
+
self.assemble()
|
|
115
|
+
|
|
116
|
+
if self.added_masses is not None and self.mass_locations is not None:
|
|
117
|
+
self.add_mass()
|
|
118
|
+
|
|
119
|
+
def construct_loce(self):
|
|
120
|
+
"""
|
|
121
|
+
Construct LOCE matrix from CONEC.
|
|
122
|
+
"""
|
|
123
|
+
self.loce = []
|
|
124
|
+
insert = np.arange(self.n_dof_node)
|
|
125
|
+
conec1 = self.conec.flatten()
|
|
126
|
+
for node in conec1:
|
|
127
|
+
self.loce.append(node * self.n_dof_node + insert)
|
|
128
|
+
|
|
129
|
+
self.loce = np.asarray(self.loce).flatten()
|
|
130
|
+
self.loce = self.loce.reshape(
|
|
131
|
+
self.conec.shape[0], self.n_dof_node * self.el_nodes)
|
|
132
|
+
|
|
133
|
+
def assemble(self, mode="EB"):
|
|
134
|
+
"""Assemble mass and stiffness matrices.
|
|
135
|
+
|
|
136
|
+
Parameters
|
|
137
|
+
----------
|
|
138
|
+
mode : str, optional
|
|
139
|
+
The mode of assembly. Default is "EB".
|
|
140
|
+
- "EB": Euler-Bernoulli beam theory.
|
|
141
|
+
- "T": Timoshenko beam theory.
|
|
142
|
+
"""
|
|
143
|
+
# self.Ms = np.zeros((self.n_elements, self.n_dof, self.n_dof))
|
|
144
|
+
# self.Ks = np.zeros((self.n_elements, self.n_dof, self.n_dof))
|
|
145
|
+
|
|
146
|
+
self.M = np.zeros((self.n_dof, self.n_dof))
|
|
147
|
+
self.K = np.zeros((self.n_dof, self.n_dof))
|
|
148
|
+
|
|
149
|
+
self.Ms1 = matrices_m_e(self.length, self.mass)
|
|
150
|
+
if mode == "EB":
|
|
151
|
+
self.Ks1 = matrices_k_e(self.length, self.EI)
|
|
152
|
+
elif mode == "Timoshenko":
|
|
153
|
+
self.Ks1 = matrices_k_e_timoshenko(self.length, self.Young, self.I, self.area)
|
|
154
|
+
|
|
155
|
+
for i in range(self.n_elements):
|
|
156
|
+
# self.Ms[i][np.ix_(self.loce[i], self.loce[i])] = self.Ms1[:, :, i]
|
|
157
|
+
# self.Ks[i][np.ix_(self.loce[i], self.loce[i])] = self.Ks1[:, :, i]
|
|
158
|
+
|
|
159
|
+
self.M[np.ix_(self.loce[i], self.loce[i])] += self.Ms1[:, :, i]
|
|
160
|
+
self.K[np.ix_(self.loce[i], self.loce[i])] += self.Ks1[:, :, i]
|
|
161
|
+
|
|
162
|
+
def add_mass(self):
|
|
163
|
+
for i, m in zip(self.mass_locations, self.added_masses):
|
|
164
|
+
# self.M[np.ix_(self.loce[i], self.loce[i])][0, 0] += m
|
|
165
|
+
self.M[self.loce[i][0], self.loce[i][0]] += m
|
|
166
|
+
|
|
167
|
+
def solve(self, lanczos=True, n=10):
|
|
168
|
+
"""Solve eigen problem."""
|
|
169
|
+
# if lanczos is True:
|
|
170
|
+
# try:
|
|
171
|
+
# K = sparse.csc_matrix(self.K)
|
|
172
|
+
# M = sparse.csc_matrix(self.M)
|
|
173
|
+
|
|
174
|
+
# eigval, eigvec = eigsh(K, M=M, k=n, sigma=0, which='LM')
|
|
175
|
+
# # print('lanczos')
|
|
176
|
+
# # else:
|
|
177
|
+
# except:
|
|
178
|
+
# print('full')
|
|
179
|
+
# eigval, eigvec = scipy.linalg.eig(self.K, self.M)
|
|
180
|
+
|
|
181
|
+
try:
|
|
182
|
+
K = sparse.csc_matrix(self.K)
|
|
183
|
+
M = sparse.csc_matrix(self.M)
|
|
184
|
+
|
|
185
|
+
eigval, eigvec = eigsh(K, M=M, k=n, sigma=0, which='LM')
|
|
186
|
+
except:
|
|
187
|
+
K = sparse.csc_matrix(self.K)
|
|
188
|
+
M = sparse.csc_matrix(self.M)
|
|
189
|
+
|
|
190
|
+
eigval, eigvec = eigsh(K, M=M, k=n, sigma=100, which='LM')
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
sort_inds = np.argsort(eigval) # sortiramo po velikosti
|
|
194
|
+
eigval = eigval[sort_inds]
|
|
195
|
+
eigvec = eigvec[:, sort_inds]
|
|
196
|
+
|
|
197
|
+
eig_omega = np.sqrt(eigval)
|
|
198
|
+
eig_omega[eig_omega != eig_omega] = 0 # nan element are 0
|
|
199
|
+
self.nat_freq = np.real(eig_omega / (2*np.pi))
|
|
200
|
+
self.A = eigvec
|
|
201
|
+
|
|
202
|
+
return self.nat_freq, np.real(self.A)
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
from scipy.sparse.linalg import eigsh
|
|
2
|
+
from scipy import sparse
|
|
3
|
+
from scipy.linalg import eigh
|
|
4
|
+
import numpy as np
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def solve_eigenvalue(K, M, n_modes=None, convert_to_hz=False):
|
|
8
|
+
"""
|
|
9
|
+
Solve the eigenvalue problem.
|
|
10
|
+
|
|
11
|
+
Parameters
|
|
12
|
+
----------
|
|
13
|
+
K : ndarray or sparse
|
|
14
|
+
Stiffness matrix.
|
|
15
|
+
M : ndarray or sparse
|
|
16
|
+
Mass matrix.
|
|
17
|
+
n_modes : int or None
|
|
18
|
+
Number of modes to compute. If None, all modes are computed.
|
|
19
|
+
|
|
20
|
+
Returns
|
|
21
|
+
-------
|
|
22
|
+
eigenvalues : ndarray
|
|
23
|
+
Eigenvalues.
|
|
24
|
+
eigenvectors : ndarray
|
|
25
|
+
Eigenvectors.
|
|
26
|
+
"""
|
|
27
|
+
if sparse.issparse(K) and sparse.issparse(M):
|
|
28
|
+
if n_modes is None:
|
|
29
|
+
n_modes = K.shape[0] - 1
|
|
30
|
+
|
|
31
|
+
eigenvalues, eigenvectors = eigsh(K, M=M, k=n_modes, sigma=0, which='LM')
|
|
32
|
+
else:
|
|
33
|
+
eigenvalues, eigenvectors = eigh(K, M=M)
|
|
34
|
+
|
|
35
|
+
if convert_to_hz:
|
|
36
|
+
eigenvalues = np.sqrt(eigenvalues) / (2 * np.pi)
|
|
37
|
+
|
|
38
|
+
return eigenvalues, eigenvectors
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .mesh_2D import generate_quadrilateral_mesh
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import numpy as np
|
|
3
|
+
import pyvista as pv
|
|
4
|
+
from pyvistaqt import BackgroundPlotter
|
|
5
|
+
|
|
6
|
+
def generate_quadrilateral_mesh(path_to_step, element_size, path_to_mesh=None, show_mesh="None"):
|
|
7
|
+
"""Generate quadrilateral mesh (semi-structured).
|
|
8
|
+
|
|
9
|
+
Parameters
|
|
10
|
+
----------
|
|
11
|
+
path_to_step : str
|
|
12
|
+
Path to the STEP file.
|
|
13
|
+
element_size : float
|
|
14
|
+
Desired element size in STEP units.
|
|
15
|
+
path_to_mesh : str
|
|
16
|
+
Save path for the mesh. If not given, the mesh will not be saved.
|
|
17
|
+
show_mesh : bool
|
|
18
|
+
If True, display the mesh.
|
|
19
|
+
|
|
20
|
+
Returns
|
|
21
|
+
-------
|
|
22
|
+
nodes : np.ndarray
|
|
23
|
+
Node coordinates.
|
|
24
|
+
elements : np.ndarray
|
|
25
|
+
Element connectivity.
|
|
26
|
+
"""
|
|
27
|
+
import gmsh
|
|
28
|
+
model_name = os.path.basename(path_to_step).split(".")[0]
|
|
29
|
+
|
|
30
|
+
# Initialize GMSH
|
|
31
|
+
gmsh.initialize()
|
|
32
|
+
|
|
33
|
+
# Add a new model named 'imported_model'
|
|
34
|
+
gmsh.model.add(model_name)
|
|
35
|
+
|
|
36
|
+
# Import the STEP file
|
|
37
|
+
gmsh.model.occ.importShapes(path_to_step)
|
|
38
|
+
|
|
39
|
+
# Synchronize to ensure all imported geometries are available in the model
|
|
40
|
+
gmsh.model.occ.synchronize()
|
|
41
|
+
|
|
42
|
+
# Identify surfaces to be meshed
|
|
43
|
+
# You can list all surfaces and then select the desired ones
|
|
44
|
+
entities = gmsh.model.getEntities(dim=2)
|
|
45
|
+
|
|
46
|
+
gmsh.model.mesh.field.add("MathEval", 1)
|
|
47
|
+
gmsh.model.mesh.field.setString(1, "F", f"{element_size}")
|
|
48
|
+
gmsh.model.mesh.field.setAsBackgroundMesh(1)
|
|
49
|
+
|
|
50
|
+
# Set mesh options for 2D quadrilateral semi-structured mesh
|
|
51
|
+
gmsh.option.setNumber("Mesh.Algorithm", 8) # Use the transfinite algorithm for semi-structured meshes
|
|
52
|
+
gmsh.option.setNumber("Mesh.RecombineAll", 1) # Recombine into quadrilaterals
|
|
53
|
+
|
|
54
|
+
# Optionally set transfinite meshing parameters (if you have specific preferences)
|
|
55
|
+
# For each surface, define the transfinite algorithm parameters
|
|
56
|
+
for entity in entities:
|
|
57
|
+
gmsh.model.mesh.setTransfiniteSurface(entity[1])
|
|
58
|
+
gmsh.model.mesh.setRecombine(2, entity[1])
|
|
59
|
+
|
|
60
|
+
# Generate the 2D mesh
|
|
61
|
+
gmsh.model.mesh.generate(2)
|
|
62
|
+
|
|
63
|
+
# Save the mesh to a file
|
|
64
|
+
if path_to_mesh:
|
|
65
|
+
gmsh.write(path_to_mesh)
|
|
66
|
+
|
|
67
|
+
node_tags, node_coords, _ = gmsh.model.mesh.getNodes()
|
|
68
|
+
element_types, element_tags, element_node_tags = gmsh.model.mesh.getElements()
|
|
69
|
+
nodes = np.reshape(node_coords, (-1, 3))
|
|
70
|
+
i = np.argmin(np.abs(element_types - 3)) # element type 3 represent quadrilaterals
|
|
71
|
+
elements = np.reshape(element_node_tags[i], (-1, 4)) - 1
|
|
72
|
+
|
|
73
|
+
if show_mesh == "gmsh":
|
|
74
|
+
gmsh.fltk.run()
|
|
75
|
+
elif show_mesh == "pyvista":
|
|
76
|
+
mesh = pv.PolyData(nodes)
|
|
77
|
+
# Add cells (elements) to the mesh
|
|
78
|
+
faces = []
|
|
79
|
+
for element in elements:
|
|
80
|
+
face = np.hstack([[4], element]) # Add number of points per cell (4 for quadrilateral)
|
|
81
|
+
faces.append(face)
|
|
82
|
+
faces = np.hstack(faces).astype(np.int64)
|
|
83
|
+
mesh.faces = faces
|
|
84
|
+
|
|
85
|
+
# Visualize the mesh with displacement coloring
|
|
86
|
+
plotter = BackgroundPlotter()
|
|
87
|
+
plotter.add_mesh(mesh, show_edges=True, edge_color='black')
|
|
88
|
+
plotter.show_grid()
|
|
89
|
+
plotter.show()
|
|
90
|
+
else:
|
|
91
|
+
pass
|
|
92
|
+
|
|
93
|
+
# Finalize GMSH
|
|
94
|
+
gmsh.finalize()
|
|
95
|
+
|
|
96
|
+
return nodes, elements
|