Skip to content

Commit 92764e4

Browse files
authored
Merge pull request #1216 from Raimundovpn/frf
Frf
2 parents 39181cc + af18f79 commit 92764e4

File tree

5 files changed

+108
-94
lines changed

5 files changed

+108
-94
lines changed

ross/results.py

Lines changed: 72 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -6,20 +6,20 @@
66
import copy
77
import inspect
88
from abc import ABC
9-
from prettytable import PrettyTable
109
from collections.abc import Iterable
1110
from warnings import warn
1211

1312
import numpy as np
1413
import pandas as pd
1514
import toml
15+
from numba import njit
16+
from numpy import linalg as la
1617
from plotly import graph_objects as go
1718
from plotly.subplots import make_subplots
19+
from prettytable import PrettyTable
1820
from scipy.fft import fft
19-
from numpy import linalg as la
20-
from numba import njit
2121

22-
from ross.plotly_theme import tableau_colors, coolwarm_r
22+
from ross.plotly_theme import coolwarm_r, tableau_colors
2323
from ross.units import Q_, check_units
2424
from ross.utils import intersection
2525

@@ -286,19 +286,16 @@ def calculate_amplitude(self, angle):
286286
Tuple with (amplitude, phase) value.
287287
The amplitude units are the same as the ru_e and rv_e used to create the orbit.
288288
"""
289-
# find closest angle index
289+
290290
if angle == "major":
291291
return self.major_axis, self.major_angle
292292
elif angle == "minor":
293293
return self.minor_axis, self.minor_angle
294294

295-
idx = (np.abs(self.angle - angle)).argmin()
296-
amplitude = np.sqrt(self.x_circle[idx] ** 2 + self.y_circle[idx] ** 2)
297-
phase = self.angle[0] + angle
298-
if phase > 2 * np.pi:
299-
phase -= 2 * np.pi
300-
301-
return amplitude, phase
295+
amp_complex = self.ru_e * np.cos(angle) + self.rv_e * np.sin(angle)
296+
amp_abs = np.abs(self.ru_e * np.cos(angle) + self.rv_e * np.sin(angle))
297+
phase = 2 * np.pi - np.mod(np.angle(amp_complex), 2 * np.pi)
298+
return amp_abs, phase
302299

303300
def plot_orbit(self, fig=None):
304301
if fig is None:
@@ -359,7 +356,7 @@ def _init_orbit(ru_e, rv_e):
359356
nv = nv
360357
# fmt: off
361358
T = np.array([[ru * np.cos(nu), -ru * np.sin(nu)],
362-
[rv * np.cos(nv), -rv * np.sin(nv)]])
359+
[rv * np.cos(nv), -rv * np.sin(nv)]])
363360
# fmt: on
364361
H = T @ T.T
365362

@@ -2625,8 +2622,8 @@ def plot_with_mode_shape(
26252622
):
26262623
try:
26272624
import random
2628-
from dash import Dash
2629-
from dash import dcc, html
2625+
2626+
from dash import Dash, dcc, html
26302627
from dash.dependencies import Input, Output
26312628
except ImportError:
26322629
raise ImportError("Please install dash to use this feature.")
@@ -3305,19 +3302,39 @@ def data_magnitude(
33053302
try:
33063303
probe_tag = p[2]
33073304
except IndexError:
3308-
probe_tag = f"Probe {i+1} - Node {p[0]}"
3305+
probe_tag = f"Probe {i + 1} - Node {p[0]}"
33093306

33103307
amplitude = []
33113308
for speed_idx in range(len(self.speed_range)):
3312-
ru_e, rv_e = response[:, speed_idx][
3313-
self.rotor.number_dof * node : self.rotor.number_dof * node + 2
3314-
]
3315-
orbit = Orbit(
3316-
node=node, node_pos=self.rotor.nodes_pos[node], ru_e=ru_e, rv_e=rv_e
3317-
)
3318-
amp, phase = orbit.calculate_amplitude(angle=angle)
3319-
amplitude.append(amp)
3309+
if node not in self.rotor.link_nodes:
3310+
ru_e, rv_e = response[:, speed_idx][
3311+
self.rotor.number_dof * node : self.rotor.number_dof * node + 2
3312+
]
3313+
orbit = Orbit(
3314+
node=node,
3315+
node_pos=self.rotor.nodes_pos[node],
3316+
ru_e=ru_e,
3317+
rv_e=rv_e,
3318+
)
3319+
amp, phase = orbit.calculate_amplitude(angle=angle)
3320+
3321+
else:
3322+
position_in_link = node - len(self.rotor.nodes_pos)
3323+
start_index = len(self.rotor.nodes_pos) * self.rotor.number_dof + (
3324+
position_in_link * 3
3325+
)
3326+
ru_e, rv_e = response[:, speed_idx][start_index : start_index + 2]
33203327

3328+
orbit = Orbit(
3329+
node=None,
3330+
node_pos=None,
3331+
ru_e=ru_e,
3332+
rv_e=rv_e,
3333+
)
3334+
3335+
amp, phase = orbit.calculate_amplitude(angle=angle)
3336+
3337+
amplitude.append(amp)
33213338
data[probe_tag] = Q_(amplitude, base_unit).to(amplitude_units).m
33223339

33233340
df = pd.DataFrame(data)
@@ -3400,21 +3417,42 @@ def data_phase(
34003417
try:
34013418
probe_tag = p[2]
34023419
except IndexError:
3403-
probe_tag = f"Probe {i+1} - Node {p[0]}"
3420+
probe_tag = f"Probe {i + 1} - Node {p[0]}"
34043421

34053422
phase_values = []
34063423
for speed_idx in range(len(self.speed_range)):
3407-
ru_e, rv_e = response[:, speed_idx][
3408-
self.rotor.number_dof * node : self.rotor.number_dof * node + 2
3409-
]
3410-
orbit = Orbit(
3411-
node=node, node_pos=self.rotor.nodes_pos[node], ru_e=ru_e, rv_e=rv_e
3412-
)
3413-
amp, phase = orbit.calculate_amplitude(angle=angle)
3424+
if node not in self.rotor.link_nodes:
3425+
ru_e, rv_e = response[:, speed_idx][
3426+
self.rotor.number_dof * node : self.rotor.number_dof * node + 2
3427+
]
3428+
orbit = Orbit(
3429+
node=node,
3430+
node_pos=self.rotor.nodes_pos[node],
3431+
ru_e=ru_e,
3432+
rv_e=rv_e,
3433+
)
3434+
3435+
amp, phase = orbit.calculate_amplitude(angle=angle)
3436+
3437+
else:
3438+
position_in_link = node - len(self.rotor.nodes_pos)
3439+
start_index = len(self.rotor.nodes_pos) * self.rotor.number_dof + (
3440+
position_in_link * 3
3441+
)
3442+
ru_e, rv_e = response[:, speed_idx][start_index : start_index + 2]
3443+
3444+
orbit = Orbit(
3445+
node=None,
3446+
node_pos=None,
3447+
ru_e=ru_e,
3448+
rv_e=rv_e,
3449+
)
3450+
3451+
amp, phase = orbit.calculate_amplitude(angle=angle)
3452+
34143453
phase_values.append(phase)
34153454

34163455
data[probe_tag] = Q_(phase_values, "rad").to(phase_units).m
3417-
34183456
df = pd.DataFrame(data)
34193457

34203458
return df
@@ -5454,7 +5492,7 @@ def data_time_response(
54545492
try:
54555493
probe_tag = p[2]
54565494
except IndexError:
5457-
probe_tag = f"Probe {i+1} - Node {p[0]}"
5495+
probe_tag = f"Probe {i + 1} - Node {p[0]}"
54585496

54595497
data[f"angle[{i}]"] = angle
54605498
data[f"probe_tag[{i}]"] = probe_tag

ross/rotor_assembly.py

Lines changed: 27 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -5,32 +5,33 @@
55
from copy import copy, deepcopy
66
from itertools import chain, cycle
77
from pathlib import Path
8-
from methodtools import lru_cache
98

109
import numpy as np
1110
import pandas as pd
1211
import toml
12+
from methodtools import lru_cache
1313
from plotly import express as px
1414
from plotly import graph_objects as go
1515
from scipy import io as sio
1616
from scipy import linalg as la
1717
from scipy import signal as signal
18+
from scipy.integrate import cumulative_trapezoid as integrate
19+
from scipy.linalg import lu_factor, lu_solve
1820
from scipy.optimize import newton
1921
from scipy.sparse import linalg as las
20-
from scipy.integrate import cumulative_trapezoid as integrate
2122

2223
from ross.bearing_seal_element import (
2324
BallBearingElement,
2425
BearingElement,
2526
BearingFluidFlow,
27+
CylindricalBearing,
2628
MagneticBearingElement,
2729
RollerBearingElement,
2830
SealElement,
29-
CylindricalBearing,
3031
)
31-
from ross.faults import Crack, MisalignmentFlex, MisalignmentRigid, Rubbing
32-
from ross.disk_element import DiskElement
3332
from ross.coupling_element import CouplingElement
33+
from ross.disk_element import DiskElement
34+
from ross.faults import Crack, MisalignmentFlex, MisalignmentRigid, Rubbing
3435
from ross.materials import Material, steel
3536
from ross.point_mass import PointMass
3637
from ross.results import (
@@ -46,17 +47,17 @@
4647
TimeResponseResults,
4748
UCSResults,
4849
)
50+
from ross.seals.labyrinth_seal import LabyrinthSeal
4951
from ross.shaft_element import ShaftElement
5052
from ross.units import Q_, check_units
5153
from ross.utils import (
52-
intersection,
53-
newmark,
5454
assemble_C_K_matrices,
55-
remove_dofs,
5655
convert_6dof_to_4dof,
5756
convert_6dof_to_torsional,
57+
intersection,
58+
newmark,
59+
remove_dofs,
5860
)
59-
from ross.seals.labyrinth_seal import LabyrinthSeal
6061

6162
__all__ = [
6263
"Rotor",
@@ -1666,7 +1667,7 @@ def _pseudo_modal(self, speed, num_modes):
16661667

16671668
return matrix_to_modal, vector_to_modal, vector_from_modal
16681669

1669-
def transfer_matrix(self, speed=None, frequency=None, modes=None):
1670+
def transfer_matrix(self, speed=None, frequency=None):
16701671
"""Calculate the fer matrix for the frequency response function (FRF).
16711672
16721673
Paramenters
@@ -1675,9 +1676,6 @@ def transfer_matrix(self, speed=None, frequency=None, modes=None):
16751676
Excitation frequency. Default is rotor speed.
16761677
speed : float, optional
16771678
Rotating speed. Default is rotor speed (frequency).
1678-
modes : list, optional
1679-
List with modes used to calculate the matrix.
1680-
(all modes will be used if a list is not given).
16811679
16821680
Returns
16831681
-------
@@ -1693,33 +1691,17 @@ def transfer_matrix(self, speed=None, frequency=None, modes=None):
16931691
if frequency is None:
16941692
frequency = speed
16951693

1696-
lti = self._lti(speed=speed)
1697-
B = lti.B
1698-
C = lti.C
1699-
D = lti.D
1694+
I = np.eye(self.M().shape[0])
17001695

1701-
# calculate eigenvalues and eigenvectors using la.eig to get
1702-
# left and right eigenvectors.
1703-
evals, psi = self._eigen(speed=speed, frequency=frequency)
1704-
1705-
psi_inv = la.inv(psi)
1696+
lu, piv = lu_factor(
1697+
-(frequency**2) * self.M(frequency=frequency)
1698+
+ 1j * frequency * (self.C(frequency=frequency) + frequency * self.G())
1699+
+ self.K(frequency=frequency)
1700+
)
1701+
H = lu_solve((lu, piv), I)
17061702

1707-
if modes is not None:
1708-
n = self.ndof # n dof -> number of modes
1709-
m = len(modes) # -> number of desired modes
1710-
# idx to get each evalue/evector and its conjugate
1711-
idx = np.zeros((2 * m), int)
1712-
idx[0:m] = modes # modes
1713-
idx[m:] = range(2 * n)[-m:] # conjugates (see how evalues are ordered)
1714-
evals = evals[np.ix_(idx)]
1715-
psi = psi[np.ix_(range(2 * n), idx)]
1716-
psi_inv = psi_inv[np.ix_(idx, range(2 * n))]
1717-
1718-
with np.errstate(divide="ignore", invalid="ignore"):
1719-
diag = np.diag([1 / (1j * frequency - lam) for lam in evals])
1720-
diag[np.isnan(diag)] = 0
1721-
1722-
H = C @ psi @ diag @ psi_inv @ B + D
1703+
if np.isnan(H).any():
1704+
H = np.zeros((H.shape))
17231705

17241706
return H
17251707

@@ -1788,12 +1770,12 @@ def run_freq_response(
17881770
--------
17891771
>>> import ross as rs
17901772
>>> rotor = rs.rotor_example()
1791-
>>> speed = np.linspace(0, 1000, 101)
1773+
>>> speed =np.linspace(0, 1000, 101)
17921774
>>> response = rotor.run_freq_response(speed_range=speed)
17931775
17941776
Return the response amplitude
17951777
>>> abs(response.freq_resp) # doctest: +ELLIPSIS
1796-
array([[[1.00000000e-06, 1.00261725e-06, 1.01076952e-06, ...
1778+
array([[[0.00000000e+00, 1.00261725e-06, 1.01076952e-06, ...
17971779
17981780
Return the response phase
17991781
>>> np.angle(response.freq_resp) # doctest: +ELLIPSIS
@@ -1810,7 +1792,7 @@ def run_freq_response(
18101792
Selecting the disirable modes, if you want a reduced model:
18111793
>>> response = rotor.run_freq_response(speed_range=speed, modes=[0, 1, 2, 3, 4])
18121794
>>> abs(response.freq_resp) # doctest: +ELLIPSIS
1813-
array([[[2.00154633e-07, 2.02422522e-07, 2.09522044e-07, ...
1795+
array([[[0.00000000e+00, 1.00261725e-06, 1.01076952e-06, ...
18141796
18151797
Plotting frequency response function:
18161798
>>> fig = response.plot(inp=13, out=13)
@@ -1877,14 +1859,12 @@ def _run_freq_response(
18771859
accl_resp = np.empty((self.ndof, self.ndof, len(speed_range)), dtype=complex)
18781860

18791861
if free_free:
1880-
transfer_matrix = lambda s, m: self.transfer_matrix(
1881-
speed=0, modes=m, frequency=s
1882-
)
1862+
transfer_matrix = lambda s: self.transfer_matrix(speed=0, frequency=s)
18831863
else:
1884-
transfer_matrix = lambda s, m: self.transfer_matrix(speed=s, modes=m)
1864+
transfer_matrix = lambda s: self.transfer_matrix(speed=s)
18851865

18861866
for i, speed in enumerate(speed_range):
1887-
H = transfer_matrix(speed, modes)
1867+
H = transfer_matrix(speed)
18881868
freq_resp[..., i] = H
18891869
velc_resp[..., i] = 1j * speed * H
18901870
accl_resp[..., i] = -(speed**2) * H
@@ -2006,7 +1986,6 @@ def run_forced_response(
20061986
forced_resp[:, i] = freq_resp.freq_resp[..., i] @ force[..., i]
20071987
velc_resp[:, i] = freq_resp.velc_resp[..., i] @ force[..., i]
20081988
accl_resp[:, i] = freq_resp.accl_resp[..., i] @ force[..., i]
2009-
20101989
forced_resp = ForcedResponseResults(
20111990
rotor=self,
20121991
forced_resp=forced_resp,
@@ -2210,7 +2189,7 @@ def run_unbalance_response(
22102189
22112190
Return the response phase
22122191
>>> np.angle(response.forced_resp) # doctest: +ELLIPSIS
2213-
array([[ 0.00000000e+00, ...
2192+
array([[ 0. , 0. , 0. , ...
22142193
22152194
Using clustered points option.
22162195
Set `cluster_points=True` and choose how many modes the method must search and

ross/tests/test_results.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -195,7 +195,7 @@ def test_orbit_calculate_amplitude():
195195
# create orbit with major axis at 45deg
196196
orb = Orbit(node=0, node_pos=0, ru_e=(2 + 1j), rv_e=(2 - 1j))
197197

198-
assert_allclose(orb.calculate_amplitude(Q_(0, "deg"))[0], 1.7949401413591568)
198+
assert_allclose(orb.calculate_amplitude(Q_(0, "deg"))[0], 2.23606797749979)
199199
assert_allclose(orb.calculate_amplitude(Q_(45, "deg"))[0], 2.8284271247461903)
200200
assert_allclose(
201201
orb.calculate_amplitude(Q_(135, "deg"))[0], 1.4142135623730947, rtol=1e-3

0 commit comments

Comments
 (0)