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 ADDED
@@ -0,0 +1,3 @@
1
+ __version__ = "0.1.0"
2
+
3
+ from . import model
@@ -0,0 +1,7 @@
1
+ import pyLump as lumped
2
+ from .shell.shell import Shell
3
+ from .tetrahedron.tet10 import Tetrahedron
4
+ from .beam.beam import Beam
5
+ from .eigenvalue_solution import solve_eigenvalue
6
+
7
+ from . import mesh
@@ -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