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 ADDED
@@ -0,0 +1 @@
1
+ from .__version__ import __version__, __title__
UVEC/__version__.py ADDED
@@ -0,0 +1,3 @@
1
+ __title__ = "UVEC"
2
+ __version__ = "1.1.0"
3
+ __author__ = "STEM team"
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,3 @@
1
+ from UVEC.get_path_uvec import get_path_file
2
+
3
+ UVEC_NAME = "uvec_ten_dof_vehicle_2D"
@@ -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,3 @@
1
+ from UVEC.get_path_uvec import get_path_file
2
+
3
+ UVEC_NAME = "uvec_two_dof_vehicle_2D"
@@ -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
+ ![Tests](https://github.com/StemVibrations/vehicle_models/actions/workflows/workflow.yml/badge.svg)
23
+ [![codecov](https://codecov.io/gh/StemVibrations/vehicle_models/graph/badge.svg?token=0SCPA6LAGT)](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,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (75.8.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1 @@
1
+ UVEC