UVEC 1.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.
- UVEC/__init__.py +1 -0
- UVEC/__version__.py +3 -0
- UVEC/get_path_uvec.py +83 -0
- UVEC/uvec_ten_dof_vehicle_2D/__init__.py +3 -0
- UVEC/uvec_ten_dof_vehicle_2D/base_model.py +348 -0
- UVEC/uvec_ten_dof_vehicle_2D/hertzian_contact.py +45 -0
- UVEC/uvec_ten_dof_vehicle_2D/irregularities.py +59 -0
- UVEC/uvec_ten_dof_vehicle_2D/newmark_solver.py +105 -0
- UVEC/uvec_ten_dof_vehicle_2D/uvec.py +286 -0
- UVEC/uvec_two_dof_vehicle_2D/__init__.py +3 -0
- UVEC/uvec_two_dof_vehicle_2D/uvec.py +133 -0
- UVEC-1.1.0.dist-info/LICENSE +28 -0
- UVEC-1.1.0.dist-info/METADATA +52 -0
- UVEC-1.1.0.dist-info/RECORD +16 -0
- UVEC-1.1.0.dist-info/WHEEL +5 -0
- UVEC-1.1.0.dist-info/top_level.txt +1 -0
UVEC/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .__version__ import __version__, __title__
|
UVEC/__version__.py
ADDED
UVEC/get_path_uvec.py
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import json
|
|
3
|
+
import site
|
|
4
|
+
import sysconfig
|
|
5
|
+
|
|
6
|
+
from .__version__ import __version__, __title__
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def editable_path(folder_path: str) -> str:
|
|
10
|
+
"""
|
|
11
|
+
Collect the package location when installed in editable mode.
|
|
12
|
+
|
|
13
|
+
Args:
|
|
14
|
+
- folder_path (str): Path to the system package folder
|
|
15
|
+
|
|
16
|
+
Returns:
|
|
17
|
+
- str: Path to the package installed in editable mode
|
|
18
|
+
|
|
19
|
+
"""
|
|
20
|
+
with open(os.path.join(folder_path, "direct_url.json"), "r") as f:
|
|
21
|
+
data = json.load(f)
|
|
22
|
+
|
|
23
|
+
path_package = data["url"].split("file://")[1]
|
|
24
|
+
|
|
25
|
+
with open(os.path.join(folder_path, "top_level.txt"), "r") as f:
|
|
26
|
+
packages = f.read().splitlines()
|
|
27
|
+
|
|
28
|
+
return os.path.join(path_package, packages[0])
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def is_installed_editable(package_name: str) -> bool:
|
|
32
|
+
"""
|
|
33
|
+
Checks if the given package is installed in editable mode.
|
|
34
|
+
Returns True if installed in editable mode, otherwise False.
|
|
35
|
+
|
|
36
|
+
Args:
|
|
37
|
+
- package_name (str): Name of the package to check.
|
|
38
|
+
|
|
39
|
+
Returns:
|
|
40
|
+
- bool: True if installed in editable mode, otherwise False.
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
return os.path.isfile(os.path.join(site.getsitepackages()[0], f"__editable__.{package_name}.pth"))
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def get_package_path() -> str:
|
|
47
|
+
"""
|
|
48
|
+
Gets the path to the current package in the site-packages directory.
|
|
49
|
+
|
|
50
|
+
Returns:
|
|
51
|
+
- str: Path to the package.
|
|
52
|
+
"""
|
|
53
|
+
|
|
54
|
+
package_name = "-".join([__title__, __version__])
|
|
55
|
+
site_packages_path = sysconfig.get_paths()["purelib"]
|
|
56
|
+
dist_info_path = os.path.join(site_packages_path, f"{package_name}.dist-info")
|
|
57
|
+
|
|
58
|
+
is_editable = is_installed_editable(package_name)
|
|
59
|
+
|
|
60
|
+
if not is_editable:
|
|
61
|
+
# if installed in regular mode
|
|
62
|
+
site_packages_path = sysconfig.get_paths()["purelib"]
|
|
63
|
+
package_path = os.path.join(site_packages_path, __name__.split(".")[0])
|
|
64
|
+
return package_path
|
|
65
|
+
else:
|
|
66
|
+
# if installed in editable mode
|
|
67
|
+
return editable_path(dist_info_path)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def get_path_file(uvec_name: str) -> str:
|
|
71
|
+
"""
|
|
72
|
+
Sets the global path_file variable and performs the copy operation.
|
|
73
|
+
|
|
74
|
+
Args:
|
|
75
|
+
- uvec_name (str): Name of the uvec file to copy.
|
|
76
|
+
|
|
77
|
+
Returns:
|
|
78
|
+
- str: Global path to the uvec file.
|
|
79
|
+
"""
|
|
80
|
+
|
|
81
|
+
package_path = get_package_path()
|
|
82
|
+
|
|
83
|
+
return os.path.join(package_path, uvec_name)
|
|
@@ -0,0 +1,348 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
|
|
3
|
+
GRAVITY = 9.81
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Cart():
|
|
7
|
+
"""
|
|
8
|
+
Cart class
|
|
9
|
+
|
|
10
|
+
Attributes:
|
|
11
|
+
- bogies (list): list of bogies
|
|
12
|
+
- dofs (list): list of degrees of freedom
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
def __init__(self):
|
|
16
|
+
"""
|
|
17
|
+
Initialise the cart
|
|
18
|
+
"""
|
|
19
|
+
self.bogies = []
|
|
20
|
+
self.dofs = []
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class Bogie():
|
|
24
|
+
"""
|
|
25
|
+
Bogie class
|
|
26
|
+
|
|
27
|
+
Attributes:
|
|
28
|
+
- wheels (list): list of wheels
|
|
29
|
+
- dofs (list): list of degrees of freedom
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
def __init__(self):
|
|
33
|
+
"""
|
|
34
|
+
Initialise the bogie
|
|
35
|
+
"""
|
|
36
|
+
self.wheels = []
|
|
37
|
+
self.dofs = []
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class Wheel():
|
|
41
|
+
"""
|
|
42
|
+
Wheel class
|
|
43
|
+
|
|
44
|
+
Attributes:
|
|
45
|
+
- dofs (list): list of degrees of freedom
|
|
46
|
+
"""
|
|
47
|
+
|
|
48
|
+
def __init__(self):
|
|
49
|
+
self.dofs = []
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class TrainModel():
|
|
53
|
+
|
|
54
|
+
def __init__(self):
|
|
55
|
+
|
|
56
|
+
self.n_carts = None
|
|
57
|
+
self.n_bogies = None
|
|
58
|
+
self.n_wheels = None
|
|
59
|
+
|
|
60
|
+
self.cart_inertia = None
|
|
61
|
+
self.cart_mass = None
|
|
62
|
+
self.cart_stiffness = None
|
|
63
|
+
self.cart_damping = None
|
|
64
|
+
self.bogie_distances = None
|
|
65
|
+
self.bogie_inertia = None
|
|
66
|
+
self.bogie_mass = None
|
|
67
|
+
self.wheel_inertia = None
|
|
68
|
+
self.wheel_mass = None
|
|
69
|
+
self.wheel_stiffness = None
|
|
70
|
+
self.wheel_damping = None
|
|
71
|
+
self.wheel_distances = None
|
|
72
|
+
|
|
73
|
+
self.carts = []
|
|
74
|
+
|
|
75
|
+
self.ndof = None
|
|
76
|
+
self.contact_dofs = []
|
|
77
|
+
|
|
78
|
+
self.contact_coeff = None
|
|
79
|
+
self.contact_power = None
|
|
80
|
+
|
|
81
|
+
def initialise(self):
|
|
82
|
+
"""
|
|
83
|
+
Initialise the train system
|
|
84
|
+
|
|
85
|
+
:return:
|
|
86
|
+
"""
|
|
87
|
+
self.generate_components()
|
|
88
|
+
self.set_dofs()
|
|
89
|
+
|
|
90
|
+
def calculate_n_elements(self):
|
|
91
|
+
"""
|
|
92
|
+
Calculate the number of bogies and wheels in the train system
|
|
93
|
+
|
|
94
|
+
:return:
|
|
95
|
+
"""
|
|
96
|
+
|
|
97
|
+
self.n_bogies = len(self.bogie_distances)
|
|
98
|
+
self.n_wheels = len(self.wheel_distances)
|
|
99
|
+
|
|
100
|
+
def generate_components(self):
|
|
101
|
+
"""
|
|
102
|
+
Generate the train components, i.e. wheels, bogies and carts
|
|
103
|
+
|
|
104
|
+
:return:
|
|
105
|
+
"""
|
|
106
|
+
|
|
107
|
+
self.calculate_n_elements()
|
|
108
|
+
|
|
109
|
+
for i in range(self.n_carts):
|
|
110
|
+
cart = Cart()
|
|
111
|
+
for j in range(self.n_bogies):
|
|
112
|
+
bogie = Bogie()
|
|
113
|
+
for k in range(len(self.wheel_distances)):
|
|
114
|
+
wheel = Wheel()
|
|
115
|
+
bogie.wheels.append(wheel)
|
|
116
|
+
cart.bogies.append(bogie)
|
|
117
|
+
self.carts.append(cart)
|
|
118
|
+
|
|
119
|
+
def set_dofs(self):
|
|
120
|
+
"""
|
|
121
|
+
Set the degrees of freedom for each component and calculate the total number of degrees of freedom
|
|
122
|
+
|
|
123
|
+
:return:
|
|
124
|
+
"""
|
|
125
|
+
|
|
126
|
+
i_dof = 0
|
|
127
|
+
|
|
128
|
+
for cart in self.carts:
|
|
129
|
+
# cart has displacement and rotation dof
|
|
130
|
+
cart.dofs = [i_dof, i_dof + 1]
|
|
131
|
+
i_dof += 2
|
|
132
|
+
|
|
133
|
+
for bogie in cart.bogies:
|
|
134
|
+
# bogie has displacement and rotation dof
|
|
135
|
+
bogie.dofs = [i_dof, i_dof + 1]
|
|
136
|
+
i_dof += 2
|
|
137
|
+
|
|
138
|
+
for wheel in bogie.wheels:
|
|
139
|
+
# wheel has displacement dof
|
|
140
|
+
wheel.dofs = [i_dof]
|
|
141
|
+
self.contact_dofs.append(i_dof)
|
|
142
|
+
i_dof += 1
|
|
143
|
+
|
|
144
|
+
self.ndof = i_dof
|
|
145
|
+
|
|
146
|
+
def generate_mass_matrix(self):
|
|
147
|
+
"""
|
|
148
|
+
Generate the mass matrix
|
|
149
|
+
"""
|
|
150
|
+
|
|
151
|
+
mass_matrix = np.zeros((self.ndof, self.ndof))
|
|
152
|
+
|
|
153
|
+
for cart in self.carts:
|
|
154
|
+
mass_matrix[cart.dofs[0], cart.dofs[0]] += self.cart_mass
|
|
155
|
+
mass_matrix[cart.dofs[1], cart.dofs[1]] += self.cart_inertia
|
|
156
|
+
|
|
157
|
+
for bogie in cart.bogies:
|
|
158
|
+
mass_matrix[bogie.dofs[0], bogie.dofs[0]] += self.bogie_mass
|
|
159
|
+
mass_matrix[bogie.dofs[1], bogie.dofs[1]] += self.bogie_inertia
|
|
160
|
+
|
|
161
|
+
for wheel in bogie.wheels:
|
|
162
|
+
mass_matrix[wheel.dofs[0], wheel.dofs[0]] += self.wheel_mass
|
|
163
|
+
|
|
164
|
+
return mass_matrix
|
|
165
|
+
|
|
166
|
+
def generate_stiffness_matrix(self):
|
|
167
|
+
"""
|
|
168
|
+
Generate stiffness matrix
|
|
169
|
+
"""
|
|
170
|
+
stiffness_matrix = np.zeros((self.ndof, self.ndof))
|
|
171
|
+
|
|
172
|
+
for cart in self.carts:
|
|
173
|
+
stiffness_matrix[cart.dofs[0], cart.dofs[0]] += self.cart_stiffness * len(cart.bogies)
|
|
174
|
+
|
|
175
|
+
for b, bogie in enumerate(cart.bogies):
|
|
176
|
+
stiffness_matrix[cart.dofs[1], cart.dofs[1]] += self.cart_stiffness * self.bogie_distances[b]**2
|
|
177
|
+
|
|
178
|
+
stiffness_matrix[cart.dofs[0], bogie.dofs[0]] += -self.cart_stiffness
|
|
179
|
+
stiffness_matrix[bogie.dofs[0], cart.dofs[0]] += -self.cart_stiffness
|
|
180
|
+
|
|
181
|
+
stiffness_matrix[cart.dofs[1], bogie.dofs[0]] += self.cart_stiffness * self.bogie_distances[b]
|
|
182
|
+
stiffness_matrix[bogie.dofs[0], cart.dofs[1]] += self.cart_stiffness * self.bogie_distances[b]
|
|
183
|
+
|
|
184
|
+
stiffness_matrix[bogie.dofs[0], bogie.dofs[0]] += self.cart_stiffness
|
|
185
|
+
|
|
186
|
+
stiffness_matrix[bogie.dofs[0], bogie.dofs[0]] += self.wheel_stiffness * len(bogie.wheels)
|
|
187
|
+
|
|
188
|
+
for w, wheel in enumerate(bogie.wheels):
|
|
189
|
+
|
|
190
|
+
stiffness_matrix[bogie.dofs[1], bogie.dofs[1]] += self.wheel_stiffness * self.wheel_distances[w]**2
|
|
191
|
+
stiffness_matrix[bogie.dofs[0], wheel.dofs[0]] += -self.wheel_stiffness
|
|
192
|
+
stiffness_matrix[wheel.dofs[0], bogie.dofs[0]] += -self.wheel_stiffness
|
|
193
|
+
|
|
194
|
+
stiffness_matrix[bogie.dofs[1], wheel.dofs[0]] += self.wheel_stiffness * self.wheel_distances[w]
|
|
195
|
+
stiffness_matrix[wheel.dofs[0], bogie.dofs[1]] += self.wheel_stiffness * self.wheel_distances[w]
|
|
196
|
+
|
|
197
|
+
stiffness_matrix[wheel.dofs[0], wheel.dofs[0]] += self.wheel_stiffness
|
|
198
|
+
|
|
199
|
+
return stiffness_matrix
|
|
200
|
+
|
|
201
|
+
def generate_damping_matrix(self):
|
|
202
|
+
"""
|
|
203
|
+
Generate damping matrix
|
|
204
|
+
|
|
205
|
+
:return:
|
|
206
|
+
"""
|
|
207
|
+
|
|
208
|
+
damping_matrix = np.zeros((self.ndof, self.ndof))
|
|
209
|
+
|
|
210
|
+
for cart in self.carts:
|
|
211
|
+
damping_matrix[cart.dofs[0], cart.dofs[0]] += self.cart_damping * len(cart.bogies)
|
|
212
|
+
|
|
213
|
+
for b, bogie in enumerate(cart.bogies):
|
|
214
|
+
damping_matrix[cart.dofs[1], cart.dofs[1]] += self.cart_damping * self.bogie_distances[b]**2
|
|
215
|
+
|
|
216
|
+
damping_matrix[cart.dofs[0], bogie.dofs[0]] += -self.cart_damping
|
|
217
|
+
damping_matrix[bogie.dofs[0], cart.dofs[0]] += -self.cart_damping
|
|
218
|
+
|
|
219
|
+
damping_matrix[cart.dofs[1], bogie.dofs[0]] += self.cart_damping * self.bogie_distances[b]
|
|
220
|
+
damping_matrix[bogie.dofs[0], cart.dofs[1]] += self.cart_damping * self.bogie_distances[b]
|
|
221
|
+
|
|
222
|
+
damping_matrix[bogie.dofs[0], bogie.dofs[0]] += self.cart_damping
|
|
223
|
+
|
|
224
|
+
damping_matrix[bogie.dofs[0], bogie.dofs[0]] += self.wheel_damping * len(bogie.wheels)
|
|
225
|
+
for w, wheel in enumerate(bogie.wheels):
|
|
226
|
+
damping_matrix[bogie.dofs[1], bogie.dofs[1]] += self.wheel_damping * self.wheel_distances[w]**2
|
|
227
|
+
damping_matrix[bogie.dofs[0], wheel.dofs[0]] += -self.wheel_damping
|
|
228
|
+
damping_matrix[wheel.dofs[0], bogie.dofs[0]] += -self.wheel_damping
|
|
229
|
+
|
|
230
|
+
damping_matrix[bogie.dofs[1], wheel.dofs[0]] += self.wheel_damping * self.wheel_distances[w]
|
|
231
|
+
damping_matrix[wheel.dofs[0], bogie.dofs[1]] += self.wheel_damping * self.wheel_distances[w]
|
|
232
|
+
|
|
233
|
+
damping_matrix[wheel.dofs[0], wheel.dofs[0]] += self.wheel_damping
|
|
234
|
+
|
|
235
|
+
return damping_matrix
|
|
236
|
+
|
|
237
|
+
def generate_force_vector(self):
|
|
238
|
+
"""
|
|
239
|
+
Generate force vector
|
|
240
|
+
"""
|
|
241
|
+
force_vector = np.zeros(self.ndof)
|
|
242
|
+
|
|
243
|
+
for cart in self.carts:
|
|
244
|
+
force_vector[cart.dofs[0]] = -self.cart_mass * GRAVITY
|
|
245
|
+
|
|
246
|
+
for bogie in cart.bogies:
|
|
247
|
+
force_vector[bogie.dofs[0]] = -self.bogie_mass * GRAVITY
|
|
248
|
+
|
|
249
|
+
for wheel in bogie.wheels:
|
|
250
|
+
force_vector[wheel.dofs[0]] = -self.wheel_mass * GRAVITY
|
|
251
|
+
|
|
252
|
+
return force_vector
|
|
253
|
+
|
|
254
|
+
def calculate_static_contact_force(self):
|
|
255
|
+
"""
|
|
256
|
+
Calculate static contact force
|
|
257
|
+
|
|
258
|
+
:return:
|
|
259
|
+
"""
|
|
260
|
+
|
|
261
|
+
contact_forces = []
|
|
262
|
+
|
|
263
|
+
# Calculate static contact force
|
|
264
|
+
for cart in self.carts:
|
|
265
|
+
distributed_load_cart = self.cart_mass * -GRAVITY / len(cart.bogies)
|
|
266
|
+
|
|
267
|
+
for bogie in cart.bogies:
|
|
268
|
+
|
|
269
|
+
distributed_load_bogie = (self.bogie_mass * -GRAVITY + distributed_load_cart) / len(bogie.wheels)
|
|
270
|
+
|
|
271
|
+
for _ in bogie.wheels:
|
|
272
|
+
contact_force = self.wheel_mass * -GRAVITY + distributed_load_bogie
|
|
273
|
+
contact_forces.append(contact_force)
|
|
274
|
+
|
|
275
|
+
return np.array(contact_forces)
|
|
276
|
+
|
|
277
|
+
def apply_dirichlet_bc(self, A, b, bc_indices):
|
|
278
|
+
"""
|
|
279
|
+
Apply Dirichlet boundary conditions to a stiffness matrix and force vector.
|
|
280
|
+
|
|
281
|
+
:param A: The stiffness matrix.
|
|
282
|
+
:param b: The force vector.
|
|
283
|
+
:param bc_indices: The indices at which to apply the boundary conditions.
|
|
284
|
+
|
|
285
|
+
:returns: The modified stiffness matrix and force vector.
|
|
286
|
+
"""
|
|
287
|
+
|
|
288
|
+
# Replace the i-th row of A with the unit vector.
|
|
289
|
+
for index in bc_indices:
|
|
290
|
+
# Replace the i-th row of A with the unit vector.
|
|
291
|
+
A[index, :] = 0.0
|
|
292
|
+
A[:, index] = 0.0
|
|
293
|
+
A[index, index] = 1.0
|
|
294
|
+
|
|
295
|
+
b[index] = 0.0
|
|
296
|
+
|
|
297
|
+
return A, b
|
|
298
|
+
|
|
299
|
+
def calculate_initial_displacement(self, K, F, u_wheels):
|
|
300
|
+
"""
|
|
301
|
+
Calculate initial displacement
|
|
302
|
+
|
|
303
|
+
:param K: stiffness matrix
|
|
304
|
+
:param F: force vector
|
|
305
|
+
:param u_wheels: initial displacement of the wheels
|
|
306
|
+
:return:
|
|
307
|
+
"""
|
|
308
|
+
|
|
309
|
+
# apply dirichlet boundary condition
|
|
310
|
+
u = np.zeros(self.ndof)
|
|
311
|
+
u[self.contact_dofs] = u_wheels
|
|
312
|
+
|
|
313
|
+
F_internal = K.dot(u)
|
|
314
|
+
F_total = F - F_internal
|
|
315
|
+
|
|
316
|
+
K_constr, F_constr, = self.apply_dirichlet_bc(np.copy(K), np.copy(F_total), self.contact_dofs)
|
|
317
|
+
|
|
318
|
+
# Solve the linear system.
|
|
319
|
+
u = np.linalg.solve(K_constr, F_constr)
|
|
320
|
+
|
|
321
|
+
# # add dirichlet boundary condition
|
|
322
|
+
u[self.contact_dofs] = u_wheels
|
|
323
|
+
|
|
324
|
+
return u
|
|
325
|
+
|
|
326
|
+
def generate_global_matrices(self):
|
|
327
|
+
"""
|
|
328
|
+
Generate global matrices
|
|
329
|
+
|
|
330
|
+
and apply dirichlet boundary condition on elements without mass or inertia term
|
|
331
|
+
:return:
|
|
332
|
+
"""
|
|
333
|
+
|
|
334
|
+
K = self.generate_stiffness_matrix()
|
|
335
|
+
C = self.generate_damping_matrix()
|
|
336
|
+
M = self.generate_mass_matrix()
|
|
337
|
+
F = self.generate_force_vector()
|
|
338
|
+
|
|
339
|
+
# dirichlet constraint on elements without mass or intertia term
|
|
340
|
+
tolerance = 1e-10
|
|
341
|
+
mask = np.abs(np.diag(M)) < tolerance
|
|
342
|
+
|
|
343
|
+
np.fill_diagonal(M, np.where(mask, 1, np.diag(M)))
|
|
344
|
+
np.fill_diagonal(K, np.where(mask, 1, np.diag(K)))
|
|
345
|
+
np.fill_diagonal(C, np.where(mask, 1, np.diag(C)))
|
|
346
|
+
F[mask] = 0
|
|
347
|
+
|
|
348
|
+
return K, C, M, F
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class HertzianContact():
|
|
5
|
+
"""
|
|
6
|
+
Hertzian contact model
|
|
7
|
+
|
|
8
|
+
Attributes:
|
|
9
|
+
- contact_coeff (float): contact coefficient
|
|
10
|
+
- contact_power (float): contact power
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
def __init__(self):
|
|
14
|
+
"""
|
|
15
|
+
Constructor of the HertzianContact class
|
|
16
|
+
"""
|
|
17
|
+
self.contact_coeff = None # contact coefficient
|
|
18
|
+
self.contact_power = None # contact power
|
|
19
|
+
|
|
20
|
+
def calculate_contact_force(self, du: np.ndarray) -> np.ndarray:
|
|
21
|
+
"""
|
|
22
|
+
Calculate contact force
|
|
23
|
+
|
|
24
|
+
Args:
|
|
25
|
+
- du (np.ndarray): contact deformation
|
|
26
|
+
|
|
27
|
+
Returns:
|
|
28
|
+
- np.ndarray: contact force
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
contact_force = np.sign(-du) * np.nan_to_num((1 / self.contact_coeff * -du)**self.contact_power)
|
|
32
|
+
return contact_force
|
|
33
|
+
|
|
34
|
+
def calculate_contact_deformation(self, F: np.ndarray) -> np.ndarray:
|
|
35
|
+
"""
|
|
36
|
+
Calculate contact deformation
|
|
37
|
+
|
|
38
|
+
Args:
|
|
39
|
+
- F (np.ndarray): contact force
|
|
40
|
+
|
|
41
|
+
Returns:
|
|
42
|
+
- np.ndarray: contact deformation
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
return np.sign(F) * self.contact_coeff * np.abs(F)**(1 / self.contact_power)
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def calculate_rail_irregularity(x: float,
|
|
5
|
+
f_min: float = 2,
|
|
6
|
+
f_max: float = 500,
|
|
7
|
+
N: int = 2000,
|
|
8
|
+
Av: float = 0.00002095,
|
|
9
|
+
omega_c: float = 0.8242,
|
|
10
|
+
seed: int = 14) -> float:
|
|
11
|
+
"""
|
|
12
|
+
Creates rail unevenness following :cite: `zhang_2001`.
|
|
13
|
+
|
|
14
|
+
A summary of default values can be found in : cite: `Podworna_2015`.
|
|
15
|
+
|
|
16
|
+
Args:
|
|
17
|
+
- x (float) : position of the node [m]
|
|
18
|
+
- f_min (float): minimum spatial frequency for the PSD of the unevenness [1/m] (default 2 1/m)
|
|
19
|
+
- f_max: (float) maximum spatial frequency for the PSD of the unevenness [1/m] (default 500 1/m)
|
|
20
|
+
- N (int): number of frequency increments [-] (default 2000)
|
|
21
|
+
- Av (float): vertical track irregularity parameters [m2 rad/m] (default 0.00002095 m2 rad/m)
|
|
22
|
+
- omega_c (float): critical wave number [rad/m] (default 0.8242 rad/m)
|
|
23
|
+
- seed: (int) seed for random generator [-] (default 14)
|
|
24
|
+
|
|
25
|
+
Returns:
|
|
26
|
+
- irregularity (float): irregularity at the node [m]
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
# random generator
|
|
30
|
+
random_generator = np.random.default_rng(seed)
|
|
31
|
+
|
|
32
|
+
# define omega range
|
|
33
|
+
omega_max = 2 * np.pi * f_max
|
|
34
|
+
omega_min = 2 * np.pi * f_min
|
|
35
|
+
delta_omega = (omega_max - omega_min) / N
|
|
36
|
+
|
|
37
|
+
# for each frequency increment
|
|
38
|
+
omega_n = omega_min + delta_omega * np.arange(N)
|
|
39
|
+
phi = random_generator.uniform(0, 2 * np.pi, N)
|
|
40
|
+
irregularity = np.sum(np.sqrt(4 * spectral(omega_n, Av, omega_c) * delta_omega) * np.cos(omega_n * x - phi))
|
|
41
|
+
|
|
42
|
+
return irregularity
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def spectral(omega: np.ndarray, Av: float, omega_c: float) -> np.ndarray:
|
|
46
|
+
"""
|
|
47
|
+
Computes spectral unevenness
|
|
48
|
+
|
|
49
|
+
Args:
|
|
50
|
+
- omega (np.ndarray): wave number [rad/m]
|
|
51
|
+
- Av (float): vertical track irregularity parameters [m2 rad/m]
|
|
52
|
+
- omega_c (float): critical wave number [rad/m]
|
|
53
|
+
|
|
54
|
+
Returns:
|
|
55
|
+
- spectral_unevenness (np.ndarray): spectral unevenness [m3 / rad]
|
|
56
|
+
"""
|
|
57
|
+
|
|
58
|
+
spectral_unevenness = 2 * np.pi * Av * omega_c**2 / ((omega**2 + omega_c**2) * omega**2)
|
|
59
|
+
return spectral_unevenness
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
from typing import Tuple
|
|
2
|
+
import numpy as np
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class StaticSolver():
|
|
6
|
+
"""
|
|
7
|
+
Class for static solver
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
def __init__(self):
|
|
11
|
+
pass
|
|
12
|
+
|
|
13
|
+
def calculate(self, K: np.ndarray, F: np.ndarray) -> np.ndarray:
|
|
14
|
+
"""
|
|
15
|
+
Calculate the displacement of a statically loaded structure
|
|
16
|
+
|
|
17
|
+
Args:
|
|
18
|
+
- K (np.ndarray): stiffness matrix
|
|
19
|
+
- F (np.ndarray): force vector
|
|
20
|
+
|
|
21
|
+
Returns:
|
|
22
|
+
- np.ndarray: displacement vector
|
|
23
|
+
"""
|
|
24
|
+
return np.linalg.solve(K, F)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class NewmarkExplicit():
|
|
28
|
+
"""
|
|
29
|
+
Newmark integration scheme for explicit time integration
|
|
30
|
+
|
|
31
|
+
Attributes:
|
|
32
|
+
- beta (float): beta parameter
|
|
33
|
+
- gamma (float): gamma parameter
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
def __init__(self, beta: float = 0.25, gamma: float = 0.5):
|
|
37
|
+
"""
|
|
38
|
+
Constructor of the NewmarkExplicit class
|
|
39
|
+
|
|
40
|
+
Args:
|
|
41
|
+
- beta (float): beta parameter
|
|
42
|
+
- gamma (float): gamma parameter
|
|
43
|
+
"""
|
|
44
|
+
self.beta = beta
|
|
45
|
+
self.gamma = gamma
|
|
46
|
+
|
|
47
|
+
def calculate(self, M: np.ndarray, C: np.ndarray, K: np.ndarray, F: np.ndarray, dt: float, t_index: int,
|
|
48
|
+
u_ini: np.ndarray, v_ini: np.ndarray, a_ini: np.ndarray) -> Tuple[np.ndarray, np.ndarray, np.ndarray]:
|
|
49
|
+
"""
|
|
50
|
+
Newmark integration step.
|
|
51
|
+
|
|
52
|
+
Args:
|
|
53
|
+
- M (np.ndarray): mass matrix
|
|
54
|
+
- C (np.ndarray): damping matrix
|
|
55
|
+
- K (np.ndarray): stiffness matrix
|
|
56
|
+
- F (np.ndarray): force vector
|
|
57
|
+
- dt (float): time step
|
|
58
|
+
- t_index (int): time index
|
|
59
|
+
- u_ini (np.ndarray): initial displacement
|
|
60
|
+
- v_ini (np.ndarray): initial velocity
|
|
61
|
+
- a_ini (np.ndarray): initial acceleration
|
|
62
|
+
|
|
63
|
+
Returns:
|
|
64
|
+
- Tuple[np.ndarray, np.ndarray, np.ndarray]: tuple containing the new displacement, velocity and \
|
|
65
|
+
acceleration
|
|
66
|
+
"""
|
|
67
|
+
|
|
68
|
+
# calculate time step size
|
|
69
|
+
|
|
70
|
+
# constants for the Newmark integration
|
|
71
|
+
beta = self.beta
|
|
72
|
+
gamma = self.gamma
|
|
73
|
+
|
|
74
|
+
# initial acceleration
|
|
75
|
+
|
|
76
|
+
if t_index == 0:
|
|
77
|
+
a_ini = np.linalg.solve(M, F - C.dot(v_ini) - K.dot(u_ini))
|
|
78
|
+
|
|
79
|
+
# calculate newmark constants
|
|
80
|
+
a0 = 1 / (beta * dt**2)
|
|
81
|
+
a1 = gamma / (beta * dt)
|
|
82
|
+
a2 = 1 / (beta * dt)
|
|
83
|
+
a3 = 1 / (2 * beta) - 1
|
|
84
|
+
a4 = gamma / beta - 1
|
|
85
|
+
a5 = dt / 2 * (gamma / beta - 2)
|
|
86
|
+
# a5 = gamma/beta - 2*dt/2
|
|
87
|
+
a6 = dt * (1 - gamma)
|
|
88
|
+
a7 = gamma * dt
|
|
89
|
+
|
|
90
|
+
# calculate effective stiffness matrix
|
|
91
|
+
K_eff = K + a0 * M + a1 * C
|
|
92
|
+
|
|
93
|
+
# calculate effective force vector
|
|
94
|
+
m_part = M @ (a0 * u_ini + a2 * v_ini + a3 * a_ini)
|
|
95
|
+
c_part = C @ (a1 * u_ini + a4 * v_ini + a5 * a_ini)
|
|
96
|
+
force_eff = F + m_part + c_part
|
|
97
|
+
|
|
98
|
+
# solve for displacement
|
|
99
|
+
u = np.linalg.solve(K_eff, force_eff)
|
|
100
|
+
|
|
101
|
+
# solve for acceleration and velocity
|
|
102
|
+
a = a0 * (u - u_ini) - a2 * v_ini - a3 * a_ini
|
|
103
|
+
v = v_ini + a6 * a_ini + a7 * a
|
|
104
|
+
|
|
105
|
+
return u, v, a
|
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import numpy as np
|
|
3
|
+
from typing import Tuple, List
|
|
4
|
+
|
|
5
|
+
from UVEC.uvec_ten_dof_vehicle_2D.base_model import TrainModel
|
|
6
|
+
from UVEC.uvec_ten_dof_vehicle_2D.hertzian_contact import HertzianContact
|
|
7
|
+
from UVEC.uvec_ten_dof_vehicle_2D.newmark_solver import NewmarkExplicit
|
|
8
|
+
from UVEC.uvec_ten_dof_vehicle_2D.irregularities import calculate_rail_irregularity
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def uvec(json_string: str) -> str:
|
|
12
|
+
"""
|
|
13
|
+
Args:
|
|
14
|
+
- json_string (str): json string containing the uvec data
|
|
15
|
+
|
|
16
|
+
Returns:
|
|
17
|
+
- str: json string containing the load data
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
# Get the uvec data
|
|
21
|
+
uvec_data = json.loads(json_string)
|
|
22
|
+
|
|
23
|
+
# Check if analysis is static
|
|
24
|
+
static = uvec_data["parameters"]["static_initialisation"]
|
|
25
|
+
|
|
26
|
+
if static:
|
|
27
|
+
uvec_data = compute_static_solution(uvec_data)
|
|
28
|
+
else:
|
|
29
|
+
uvec_data = compute_dynamic_solution(uvec_data)
|
|
30
|
+
|
|
31
|
+
return json.dumps(uvec_data)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def compute_static_solution(uvec_data: dict) -> dict:
|
|
35
|
+
"""
|
|
36
|
+
Compute the static solution for the UVEC
|
|
37
|
+
|
|
38
|
+
Args:
|
|
39
|
+
- uvec_data (dict): dictionary containing the UVEC data
|
|
40
|
+
|
|
41
|
+
Returns:
|
|
42
|
+
- dict: dictionary containing the UVEC data
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
u = uvec_data["u"]
|
|
46
|
+
# theta = uvec_data["theta"]
|
|
47
|
+
time_index = uvec_data["time_index"]
|
|
48
|
+
time_step = uvec_data["dt"]
|
|
49
|
+
state = uvec_data["state"]
|
|
50
|
+
|
|
51
|
+
# Get the uvec parameters
|
|
52
|
+
parameters = uvec_data["parameters"]
|
|
53
|
+
|
|
54
|
+
# initialise the train system
|
|
55
|
+
(_, _, K, F_train), train = initialise(time_index, parameters, state)
|
|
56
|
+
|
|
57
|
+
# calculate norm of u vector, gravity is downwards
|
|
58
|
+
gravity_axis = parameters["gravity_axis"]
|
|
59
|
+
|
|
60
|
+
u_vertical = [u[uw][gravity_axis] for uw in u.keys()]
|
|
61
|
+
|
|
62
|
+
if time_index <= 0:
|
|
63
|
+
state["previous_time"] = 0
|
|
64
|
+
state["previous_time_index"] = time_index
|
|
65
|
+
|
|
66
|
+
if "wheel_configuration" in parameters.keys():
|
|
67
|
+
state["current_position"] = [position for position in parameters["wheel_configuration"]]
|
|
68
|
+
|
|
69
|
+
if time_index > state["previous_time_index"]:
|
|
70
|
+
state["previous_time"] += time_step
|
|
71
|
+
|
|
72
|
+
if "wheel_configuration" in parameters.keys():
|
|
73
|
+
for i in range(len(parameters["wheel_configuration"])):
|
|
74
|
+
state["current_position"][i] = state["current_position"][i] + parameters["velocity"] * time_step
|
|
75
|
+
|
|
76
|
+
# check if vertical track irregularity parameter is present and add irregularities if required
|
|
77
|
+
if "irr_parameters" in parameters.keys():
|
|
78
|
+
irregularity_parameters = parameters["irr_parameters"]
|
|
79
|
+
|
|
80
|
+
for i in range(len(u_vertical)):
|
|
81
|
+
u_vertical[i] = (calculate_rail_irregularity(state["current_position"][i], **irregularity_parameters) +
|
|
82
|
+
u_vertical[i])
|
|
83
|
+
|
|
84
|
+
# calculate static displacement
|
|
85
|
+
u_static = train.calculate_initial_displacement(K, F_train, u_vertical)
|
|
86
|
+
|
|
87
|
+
state["u"] = u_static.tolist()
|
|
88
|
+
state["v"] = np.zeros_like(u_static).tolist()
|
|
89
|
+
state["a"] = np.zeros_like(u_static).tolist()
|
|
90
|
+
|
|
91
|
+
# # calculate contact forces
|
|
92
|
+
F_contact = -train.calculate_static_contact_force()
|
|
93
|
+
|
|
94
|
+
# calculate unit vector
|
|
95
|
+
aux = {}
|
|
96
|
+
for i, val in enumerate(F_contact):
|
|
97
|
+
aux[i + 1] = [0., (-val).tolist(), 0.]
|
|
98
|
+
uvec_data["loads"] = aux
|
|
99
|
+
|
|
100
|
+
state["previous_time_index"] = time_index
|
|
101
|
+
|
|
102
|
+
return uvec_data
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def compute_dynamic_solution(uvec_data: dict) -> dict:
|
|
106
|
+
"""
|
|
107
|
+
Compute the dynamic solution for the UVEC
|
|
108
|
+
|
|
109
|
+
Args:
|
|
110
|
+
- uvec_data (dict): dictionary containing the UVEC data
|
|
111
|
+
|
|
112
|
+
Returns:
|
|
113
|
+
- dict: dictionary containing the UVEC data
|
|
114
|
+
"""
|
|
115
|
+
|
|
116
|
+
u = uvec_data["u"]
|
|
117
|
+
# theta = uvec_data["theta"]
|
|
118
|
+
time_index = uvec_data["time_index"]
|
|
119
|
+
time_step = uvec_data["dt"]
|
|
120
|
+
state = uvec_data["state"]
|
|
121
|
+
|
|
122
|
+
# Get the uvec parameters
|
|
123
|
+
parameters = uvec_data["parameters"]
|
|
124
|
+
|
|
125
|
+
# initialise the train system
|
|
126
|
+
(M, C, K, F_train), train = initialise(time_index, parameters, state)
|
|
127
|
+
|
|
128
|
+
# calculate norm of u vector, gravity is downwards
|
|
129
|
+
gravity_axis = parameters["gravity_axis"]
|
|
130
|
+
|
|
131
|
+
u_vertical = [u[uw][gravity_axis] for uw in u.keys()]
|
|
132
|
+
|
|
133
|
+
if time_index <= 0:
|
|
134
|
+
# calculate static displacement
|
|
135
|
+
u_static = train.calculate_initial_displacement(K, F_train, u_vertical)
|
|
136
|
+
state["u"] = u_static
|
|
137
|
+
state["v"] = np.zeros_like(u_static)
|
|
138
|
+
state["a"] = np.zeros_like(u_static)
|
|
139
|
+
state["previous_time"] = 0
|
|
140
|
+
state["previous_time_index"] = time_index
|
|
141
|
+
|
|
142
|
+
if "wheel_configuration" in parameters.keys():
|
|
143
|
+
state["current_position"] = [position for position in parameters["wheel_configuration"]]
|
|
144
|
+
|
|
145
|
+
state["u"] = np.array(state["u"])
|
|
146
|
+
state["v"] = np.array(state["v"])
|
|
147
|
+
state["a"] = np.array(state["a"])
|
|
148
|
+
|
|
149
|
+
if time_index > state["previous_time_index"]:
|
|
150
|
+
state["previous_time"] += time_step
|
|
151
|
+
|
|
152
|
+
if "wheel_configuration" in parameters.keys():
|
|
153
|
+
for i in range(len(parameters["wheel_configuration"])):
|
|
154
|
+
state["current_position"][i] = state["current_position"][i] + parameters["velocity"] * time_step
|
|
155
|
+
|
|
156
|
+
# check if vertical track irregularity parameter is present and add irregularities if required
|
|
157
|
+
if "irr_parameters" in parameters.keys():
|
|
158
|
+
irregularity_parameters = parameters["irr_parameters"]
|
|
159
|
+
|
|
160
|
+
for i in range(len(u_vertical)):
|
|
161
|
+
u_vertical[i] = (calculate_rail_irregularity(state["current_position"][i], **irregularity_parameters) +
|
|
162
|
+
u_vertical[i])
|
|
163
|
+
|
|
164
|
+
# calculate contact forces
|
|
165
|
+
F_contact = calculate_contact_forces(u_vertical, train.calculate_static_contact_force(), state, parameters, train,
|
|
166
|
+
time_index)
|
|
167
|
+
|
|
168
|
+
# calculate force vector
|
|
169
|
+
F = F_train
|
|
170
|
+
F[train.contact_dofs] = F[train.contact_dofs] + F_contact
|
|
171
|
+
|
|
172
|
+
# scale the force vector based on the amount of initialisation steps
|
|
173
|
+
if "initialisation_steps" in parameters:
|
|
174
|
+
if time_index + 1 < parameters["initialisation_steps"]:
|
|
175
|
+
F = F * (time_index + 1) / parameters["initialisation_steps"]
|
|
176
|
+
|
|
177
|
+
# calculate new state
|
|
178
|
+
u_train, v_train, a_train = calculate(state, (M, C, K, F), time_step, time_index)
|
|
179
|
+
|
|
180
|
+
state["u"] = u_train.tolist()
|
|
181
|
+
state["v"] = v_train.tolist()
|
|
182
|
+
state["a"] = a_train.tolist()
|
|
183
|
+
|
|
184
|
+
# calculate unit vector
|
|
185
|
+
aux = {}
|
|
186
|
+
for i, val in enumerate(F_contact):
|
|
187
|
+
aux[i + 1] = [0., (-val).tolist(), 0.]
|
|
188
|
+
uvec_data["loads"] = aux
|
|
189
|
+
|
|
190
|
+
state["previous_time_index"] = time_index
|
|
191
|
+
|
|
192
|
+
return uvec_data
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
def initialise(time_index: int, parameters: dict,
|
|
196
|
+
state: dict) -> Tuple[Tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray], TrainModel]:
|
|
197
|
+
"""
|
|
198
|
+
Initialise the train system
|
|
199
|
+
|
|
200
|
+
Args:
|
|
201
|
+
- time_index (int): time index
|
|
202
|
+
- parameters (dict): dictionary containing the parameters
|
|
203
|
+
- state (dict): dictionary containing the state
|
|
204
|
+
|
|
205
|
+
Returns:
|
|
206
|
+
- Tuple[Tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray], TrainModel]: tuple containing the global \
|
|
207
|
+
matrices (M, C, K, F) and the train model
|
|
208
|
+
"""
|
|
209
|
+
|
|
210
|
+
train = TrainModel()
|
|
211
|
+
|
|
212
|
+
train.n_carts = parameters["n_carts"]
|
|
213
|
+
train.cart_inertia = parameters["cart_inertia"]
|
|
214
|
+
train.cart_mass = parameters["cart_mass"]
|
|
215
|
+
train.cart_stiffness = parameters["cart_stiffness"]
|
|
216
|
+
train.cart_damping = parameters["cart_damping"]
|
|
217
|
+
|
|
218
|
+
train.bogie_distances = parameters["bogie_distances"]
|
|
219
|
+
|
|
220
|
+
train.bogie_inertia = parameters["bogie_inertia"]
|
|
221
|
+
train.bogie_mass = parameters["bogie_mass"]
|
|
222
|
+
train.wheel_distances = parameters["wheel_distances"]
|
|
223
|
+
|
|
224
|
+
train.wheel_mass = parameters["wheel_mass"]
|
|
225
|
+
train.wheel_stiffness = parameters["wheel_stiffness"]
|
|
226
|
+
train.wheel_damping = parameters["wheel_damping"]
|
|
227
|
+
|
|
228
|
+
train.initialise()
|
|
229
|
+
|
|
230
|
+
# set global matrices
|
|
231
|
+
K, C, M, F = train.generate_global_matrices()
|
|
232
|
+
|
|
233
|
+
return (M, C, K, F), train
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
def calculate_contact_forces(u: List, F_static: np.ndarray, state: dict, parameters: dict, train: TrainModel,
|
|
237
|
+
time_index: int) -> np.ndarray:
|
|
238
|
+
"""
|
|
239
|
+
Calculate the contact forces
|
|
240
|
+
|
|
241
|
+
Args:
|
|
242
|
+
- u (List): vertical displacement of the wheels
|
|
243
|
+
- F_static (np.ndarray): static contact force
|
|
244
|
+
- state (dict): dictionary containing the state
|
|
245
|
+
- parameters (dict): dictionary containing the parameters
|
|
246
|
+
- train (TrainModel): train model
|
|
247
|
+
- time_index (int): time index
|
|
248
|
+
|
|
249
|
+
Returns:
|
|
250
|
+
- np.ndarray: array containing the contact forces
|
|
251
|
+
"""
|
|
252
|
+
|
|
253
|
+
contact_method = HertzianContact()
|
|
254
|
+
contact_method.contact_coeff = parameters["contact_coefficient"]
|
|
255
|
+
contact_method.contact_power = parameters["contact_power"]
|
|
256
|
+
|
|
257
|
+
u_wheel = np.array(state["u"])[train.contact_dofs]
|
|
258
|
+
|
|
259
|
+
static_contact_u = contact_method.calculate_contact_deformation(F_static)
|
|
260
|
+
|
|
261
|
+
du = u_wheel + static_contact_u - u
|
|
262
|
+
|
|
263
|
+
return contact_method.calculate_contact_force(du)
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
def calculate(state: dict, matrices: Tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray], time_step,
|
|
267
|
+
t) -> Tuple[np.ndarray, np.ndarray, np.ndarray]:
|
|
268
|
+
"""
|
|
269
|
+
Calculate the new state dynamic
|
|
270
|
+
|
|
271
|
+
Args:
|
|
272
|
+
- state (dict): dictionary containing the state
|
|
273
|
+
- matrices (Tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray]): tuple containing the global matrices \
|
|
274
|
+
[M, C, K, F]
|
|
275
|
+
- time_step (float): time step
|
|
276
|
+
- t (int): time index
|
|
277
|
+
|
|
278
|
+
Returns:
|
|
279
|
+
- tuple: tuple containing the new state
|
|
280
|
+
"""
|
|
281
|
+
|
|
282
|
+
(M, C, K, F) = matrices
|
|
283
|
+
(u, v, a) = state["u"], state["v"], state["a"]
|
|
284
|
+
|
|
285
|
+
solver = NewmarkExplicit()
|
|
286
|
+
return solver.calculate(M, C, K, F, time_step, t, u, v, a)
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import numpy as np
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def uvec(json_string: str) -> str:
|
|
6
|
+
"""
|
|
7
|
+
Args:
|
|
8
|
+
- json_string (str): json string containing the uvec data
|
|
9
|
+
|
|
10
|
+
Returns:
|
|
11
|
+
- str: json string containing the load data
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
gravity_axis = 1
|
|
15
|
+
|
|
16
|
+
# Get the uvec data
|
|
17
|
+
uvec_data = json.loads(json_string)
|
|
18
|
+
|
|
19
|
+
# load the data
|
|
20
|
+
u = uvec_data["u"]
|
|
21
|
+
# theta = uvec_data["theta"]
|
|
22
|
+
time_index = uvec_data["time_index"]
|
|
23
|
+
time_step = uvec_data["dt"]
|
|
24
|
+
state = uvec_data["state"]
|
|
25
|
+
|
|
26
|
+
# Get the uvec parameters
|
|
27
|
+
# mass_1 = uvec_data["parameters"]["m1"] # mass of the top
|
|
28
|
+
mass_2 = uvec_data["parameters"]["m2"] # mass of the bottom
|
|
29
|
+
stiffness = uvec_data["parameters"]["k"]
|
|
30
|
+
damping = uvec_data["parameters"]["c"]
|
|
31
|
+
|
|
32
|
+
u_beam = [u[uw][gravity_axis] for uw in u.keys()]
|
|
33
|
+
|
|
34
|
+
# if first two time step initialise the variables. need to be two because we need the acceleration and velocity
|
|
35
|
+
if time_index <= 1:
|
|
36
|
+
# two dof system
|
|
37
|
+
state["u"] = 0
|
|
38
|
+
state["v"] = 0
|
|
39
|
+
state["a"] = 0
|
|
40
|
+
state["u_g_previous"] = 0
|
|
41
|
+
state["v_g_previous"] = 0
|
|
42
|
+
state["a_g_previous"] = 0
|
|
43
|
+
state["u_beam"] = 0
|
|
44
|
+
|
|
45
|
+
# compute the dof for the system given the u_beam
|
|
46
|
+
state["u_beam"] = u_beam[0]
|
|
47
|
+
state = compute_dofs(uvec_data["parameters"], state, time_step, time_index)
|
|
48
|
+
|
|
49
|
+
force = mass_2 * state["a_g_previous"] + damping * state["v_g_previous"] + stiffness * (state["u_beam"] -
|
|
50
|
+
state["u"]) - mass_2 * 9.81
|
|
51
|
+
|
|
52
|
+
# Set the load data
|
|
53
|
+
uvec_data['loads'] = {1: [0, -force, 0]}
|
|
54
|
+
uvec_data["state"] = state
|
|
55
|
+
|
|
56
|
+
return json.dumps(uvec_data)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def compute_dofs(parameters: dict, state: dict, delta_t: float, time_index: int) -> dict:
|
|
60
|
+
"""
|
|
61
|
+
Compute the solution of a two dof system using the Newmark method
|
|
62
|
+
|
|
63
|
+
Args:
|
|
64
|
+
- parameters (dict): dictionary containing the parameters of the system
|
|
65
|
+
- state (dict): dictionary containing the state of the system
|
|
66
|
+
- delta_t (float): time step
|
|
67
|
+
- time_index (int): time index
|
|
68
|
+
|
|
69
|
+
Returns:
|
|
70
|
+
- dict: dictionary containing the state of the system
|
|
71
|
+
"""
|
|
72
|
+
|
|
73
|
+
m1 = parameters["m1"]
|
|
74
|
+
# m2 = parameters["m2"]
|
|
75
|
+
k1 = parameters["k"]
|
|
76
|
+
c1 = parameters["c"]
|
|
77
|
+
external_displacement = state["u_beam"]
|
|
78
|
+
u = np.array(state["u"])
|
|
79
|
+
v = np.array(state["v"])
|
|
80
|
+
a = np.array(state["a"])
|
|
81
|
+
|
|
82
|
+
if time_index < 1:
|
|
83
|
+
return state
|
|
84
|
+
|
|
85
|
+
beta = 0.25
|
|
86
|
+
gamma = 0.5
|
|
87
|
+
|
|
88
|
+
M = m1
|
|
89
|
+
K = k1
|
|
90
|
+
C = c1
|
|
91
|
+
|
|
92
|
+
# external force
|
|
93
|
+
f_ext = k1 * external_displacement + M * 9.81
|
|
94
|
+
|
|
95
|
+
a1 = 1 / (beta * delta_t**2)
|
|
96
|
+
a2 = 1 / (beta * delta_t)
|
|
97
|
+
a3 = 1 / (2 * beta) - 1
|
|
98
|
+
a4 = gamma / (beta * delta_t)
|
|
99
|
+
a5 = (gamma / beta) - 1
|
|
100
|
+
a6 = (gamma / beta - 2) * delta_t / 2
|
|
101
|
+
|
|
102
|
+
# newmark method
|
|
103
|
+
K_till = K + C * a4 + M * a1
|
|
104
|
+
|
|
105
|
+
m_part = u * a1 + v * a2 + a * a3
|
|
106
|
+
c_part = u * a4 + v * a5 + a * a6
|
|
107
|
+
m_part = M * m_part
|
|
108
|
+
c_part = C * c_part
|
|
109
|
+
|
|
110
|
+
# external force
|
|
111
|
+
force_ext = f_ext + m_part + c_part
|
|
112
|
+
|
|
113
|
+
# solve
|
|
114
|
+
uu = force_ext / K_till
|
|
115
|
+
|
|
116
|
+
# velocity calculated through Newmark relation
|
|
117
|
+
vv = (uu - u) * a4 - v * a5 - a * a6
|
|
118
|
+
# acceleration calculated through Newmark relation
|
|
119
|
+
aa = (uu - u) * a1 - v * a2 - a * a3
|
|
120
|
+
|
|
121
|
+
state["u"] = uu
|
|
122
|
+
state["v"] = vv
|
|
123
|
+
state["a"] = aa
|
|
124
|
+
state["u_beam"] = external_displacement
|
|
125
|
+
|
|
126
|
+
new_d = external_displacement
|
|
127
|
+
new_v = (external_displacement - state["u_g_previous"]) / delta_t
|
|
128
|
+
new_a = (new_v - state["v_g_previous"]) / delta_t
|
|
129
|
+
state["u_g_previous"] = new_d
|
|
130
|
+
state["v_g_previous"] = new_v
|
|
131
|
+
state["a_g_previous"] = new_a
|
|
132
|
+
|
|
133
|
+
return state
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
BSD 3-Clause License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2023-2025, StemVibrations
|
|
4
|
+
|
|
5
|
+
Redistribution and use in source and binary forms, with or without
|
|
6
|
+
modification, are permitted provided that the following conditions are met:
|
|
7
|
+
|
|
8
|
+
1. Redistributions of source code must retain the above copyright notice, this
|
|
9
|
+
list of conditions and the following disclaimer.
|
|
10
|
+
|
|
11
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
12
|
+
this list of conditions and the following disclaimer in the documentation
|
|
13
|
+
and/or other materials provided with the distribution.
|
|
14
|
+
|
|
15
|
+
3. Neither the name of the copyright holder nor the names of its
|
|
16
|
+
contributors may be used to endorse or promote products derived from
|
|
17
|
+
this software without specific prior written permission.
|
|
18
|
+
|
|
19
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
20
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
21
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
22
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
23
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
24
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
25
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
26
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
27
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
28
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
Metadata-Version: 2.2
|
|
2
|
+
Name: UVEC
|
|
3
|
+
Version: 1.1.0
|
|
4
|
+
Summary: User defined VEhiCle models [UVEC]
|
|
5
|
+
Author: attr: UVEC.__author__
|
|
6
|
+
Classifier: Programming Language :: Python :: 3
|
|
7
|
+
Classifier: License :: OSI Approved :: BSD License
|
|
8
|
+
Classifier: Operating System :: Microsoft :: Windows
|
|
9
|
+
Classifier: Operating System :: Unix
|
|
10
|
+
Requires-Python: >=3.9
|
|
11
|
+
Description-Content-Type: text/markdown
|
|
12
|
+
License-File: LICENSE
|
|
13
|
+
Requires-Dist: numpy>=2.0.2
|
|
14
|
+
Requires-Dist: matplotlib>=3.9.3
|
|
15
|
+
Provides-Extra: testing
|
|
16
|
+
Requires-Dist: pytest>=8.3; extra == "testing"
|
|
17
|
+
Requires-Dist: pytest-cov>=6.0; extra == "testing"
|
|
18
|
+
Requires-Dist: tox>=4.23; extra == "testing"
|
|
19
|
+
Requires-Dist: mypy>=1.13; extra == "testing"
|
|
20
|
+
Requires-Dist: yapf>=0.43; extra == "testing"
|
|
21
|
+
|
|
22
|
+

|
|
23
|
+
[](https://codecov.io/gh/StemVibrations/vehicle_models)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
# User defined VEhiCle models [UVEC]
|
|
27
|
+
|
|
28
|
+
This repository contains the User defined VEhiCle models [UVEC] for STEM.
|
|
29
|
+
|
|
30
|
+
At the moment, the following vehicle models are available:
|
|
31
|
+
|
|
32
|
+
* `ten_dof_vehicle_2D`: 2-dimensional 10 degrees-of-freedom vehicle model (based on [Zhai and Sun, 1994](https://www.tandfonline.com/doi/abs/10.1080/00423119308969544))
|
|
33
|
+
|
|
34
|
+
* `two_dof_vehicle_2D`: 2-dimensional 2 degrees-of-freedom vehicle model (based on [Lou and Zeng, 2004](https://onlinelibrary.wiley.com/doi/epdf/10.1002/nme.1207))
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
Project structure:
|
|
38
|
+
|
|
39
|
+
UVEC
|
|
40
|
+
├── README.md
|
|
41
|
+
├── requirements.txt
|
|
42
|
+
├── setup.cfg
|
|
43
|
+
├── UVEC
|
|
44
|
+
│ |── __init__.py
|
|
45
|
+
│ ├── uvec_ten_dof_vehicle_2D
|
|
46
|
+
│ │ ├── __init__.py
|
|
47
|
+
│ │ ├── ten_dof_vehicle_2D.py
|
|
48
|
+
│ │ └── base_model.py
|
|
49
|
+
│ ├── uvec_two_dof_vehicle_2D
|
|
50
|
+
│ │ ├── __init__.py
|
|
51
|
+
│ │ ├── ten_dof_vehicle_2D.py
|
|
52
|
+
│ │ └── base_model.py
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
UVEC/__init__.py,sha256=oEmWzkIpsyBXZUkuoz7yniRZXlEoP-mRdaDF5chynA8,48
|
|
2
|
+
UVEC/__version__.py,sha256=oap2Ds8LnjHFaffZOUJ6emcPummRLtwK15YwAMnsNxA,66
|
|
3
|
+
UVEC/get_path_uvec.py,sha256=H5uRrq8oH-k5ls87MKjIRMkQVjDyEjHdqzJtNwWOmhw,2252
|
|
4
|
+
UVEC/uvec_ten_dof_vehicle_2D/__init__.py,sha256=5MFS0Gwk9JEJEHFseiYhX39RTxdXWvHDHpiawNoG0YM,84
|
|
5
|
+
UVEC/uvec_ten_dof_vehicle_2D/base_model.py,sha256=NlUm6kM70eL-HmOq2Wm7Ouy6S6T1qwJUvxRD454uVc8,10702
|
|
6
|
+
UVEC/uvec_ten_dof_vehicle_2D/hertzian_contact.py,sha256=buHl_MIsfKEr1hYZXXfTOvQa8IXic2zHJ5KDxMrke4s,1143
|
|
7
|
+
UVEC/uvec_ten_dof_vehicle_2D/irregularities.py,sha256=eHvXh4VuI_t8E3bL92_Dhq1fFLuVHDilaxfCAKDsYCc,2212
|
|
8
|
+
UVEC/uvec_ten_dof_vehicle_2D/newmark_solver.py,sha256=9CaGsNwKWnC4uNahdhGHkvpJl1rNk3q9QLEoEgkt5Bk,3014
|
|
9
|
+
UVEC/uvec_ten_dof_vehicle_2D/uvec.py,sha256=FNKqzHwb6TfzKjuHOr9VTIgThkOEzRFzVARWXpAGAI4,9463
|
|
10
|
+
UVEC/uvec_two_dof_vehicle_2D/__init__.py,sha256=VU9o7SmvfV6K75adFufzyJHn3JDliqk0SUGL8_L1U2c,84
|
|
11
|
+
UVEC/uvec_two_dof_vehicle_2D/uvec.py,sha256=GCF4q6pKmV5wu4ICEMipssjxntlTuQM4n5OrvtlH7YU,3791
|
|
12
|
+
UVEC-1.1.0.dist-info/LICENSE,sha256=WbcTzZtVX4H8BLF_mD2BtvCL8oh43TQtyugBiPKVDwo,1506
|
|
13
|
+
UVEC-1.1.0.dist-info/METADATA,sha256=3efh23pwnFBzVZ0_5h4hQ1oTKH2UQRlLh3wyfNtfhZ8,1921
|
|
14
|
+
UVEC-1.1.0.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
|
|
15
|
+
UVEC-1.1.0.dist-info/top_level.txt,sha256=dtSJchm8Httkhr-o294HMrAFCkQamKLbPtakH0TmsvE,5
|
|
16
|
+
UVEC-1.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
UVEC
|