Skip to content

Commit ab269a9

Browse files
authored
Merge pull request #1212 from jguarato/dev/model-reduction
Model Reduction
2 parents 4d992b0 + 9ae3d8a commit ab269a9

File tree

11 files changed

+549
-149
lines changed

11 files changed

+549
-149
lines changed

docs/user_guide/example_10.ipynb

Lines changed: 8 additions & 8 deletions
Large diffs are not rendered by default.

docs/user_guide/example_11.ipynb

Lines changed: 9 additions & 8 deletions
Large diffs are not rendered by default.

docs/user_guide/example_12.ipynb

Lines changed: 10 additions & 9 deletions
Large diffs are not rendered by default.

docs/user_guide/example_15.ipynb

Lines changed: 10 additions & 9 deletions
Large diffs are not rendered by default.

ross/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
from ross.seals.holepattern_seal import *
2525
from ross.bearings.thrust_pad import *
2626
from ross.bearings.tilting_pad import *
27-
27+
from ross.model_reduction import *
2828

2929
_pio.templates.default = "ross"
3030
if "ipykernel" in sys.modules:

ross/model_reduction.py

Lines changed: 365 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,365 @@
1+
import warnings
2+
import numpy as np
3+
from numpy import linalg as la
4+
from scipy.linalg import eigh
5+
6+
7+
class ModelReduction:
8+
subclasses = {}
9+
10+
def __init_subclass__(cls, **kwargs):
11+
super().__init_subclass__(**kwargs)
12+
ModelReduction.subclasses[cls.__name__.lower()] = cls
13+
14+
def __new__(cls, method="guyan", **kwargs):
15+
if cls is ModelReduction:
16+
subcls = ModelReduction.subclasses.get(method.lower())
17+
18+
if subcls is None:
19+
raise ValueError(f"Method {method} not exists in ModelReduction.")
20+
21+
return super().__new__(subcls)
22+
23+
else:
24+
return super().__new__(cls)
25+
26+
27+
class PseudoModal(ModelReduction):
28+
"""Pseudo-modal method.
29+
30+
This method can be used to apply modal transformation to reduce model
31+
of the rotor system.
32+
33+
Parameters
34+
----------
35+
rotor: rs.Rotor
36+
The rotor object.
37+
speed : float
38+
Rotor speed.
39+
num_modes : int
40+
The number of eigenvectors to consider in the modal transformation
41+
with model reduction. Default is 24.
42+
43+
Examples
44+
--------
45+
>>> import ross as rs
46+
>>> rotor = rs.rotor_example()
47+
>>> size = 10000
48+
>>> node = 3
49+
>>> speed = 500.0
50+
>>> t = np.linspace(0, 10, size)
51+
>>> F = np.zeros((size, rotor.ndof))
52+
>>> F[:, rotor.number_dof * node + 0] = 10 * np.cos(2 * t)
53+
>>> F[:, rotor.number_dof * node + 1] = 10 * np.sin(2 * t)
54+
>>> mr = ModelReduction(rotor=rotor, speed=speed, method="pseudomodal", num_modes=12)
55+
>>> F_modal = mr.reduce_vector(F.T).T
56+
>>> la.norm(F_modal) # doctest: +ELLIPSIS
57+
195.466...
58+
"""
59+
60+
def __init__(self, rotor, speed, num_modes=24, **kwargs):
61+
self.num_modes = num_modes
62+
self.bearings = [
63+
b for b in rotor.bearing_elements if b.n not in rotor.link_nodes
64+
]
65+
self.M = rotor.M(speed)
66+
self.K = rotor.K(speed)
67+
self.transf_matrix = self.get_transformation_matrix(speed)
68+
69+
def get_transformation_matrix(self, speed):
70+
"""Build modal matrix
71+
72+
Parameters
73+
----------
74+
speed: np.ndarray
75+
Rotor speed
76+
77+
Returns
78+
-------
79+
modal_matrix : np.ndarray
80+
Modal matrix for the pseudo-modal method.
81+
"""
82+
M = self.M
83+
K_aux = self.K.copy()
84+
85+
# Eliminate cross-coupled coefficients of bearing stiffness matrix
86+
for elm in self.bearings:
87+
dofs = list(elm.dof_global_index.values())
88+
elim_factor = 1 - np.eye(len(dofs))
89+
K_aux[np.ix_(dofs, dofs)] -= elm.K(speed) * elim_factor
90+
91+
_, modal_matrix = eigh(K_aux, M)
92+
modal_matrix = modal_matrix[:, : self.num_modes]
93+
94+
return modal_matrix
95+
96+
def reduce_matrix(self, array):
97+
"""Transform a square matrix from physical to modal space.
98+
99+
Parameters
100+
----------
101+
array: np.ndarray
102+
Square matrix to be transformed.
103+
104+
Returns
105+
-------
106+
array_reduced : np.ndarray
107+
Reduced matrix.
108+
"""
109+
return self.transf_matrix.T @ array @ self.transf_matrix
110+
111+
def reduce_vector(self, array):
112+
"""Transform a vector from physical to modal space.
113+
114+
Parameters
115+
----------
116+
array: np.ndarray
117+
Vector to be transformed.
118+
119+
Returns
120+
-------
121+
array_reduced : np.ndarray
122+
Reduced vector.
123+
"""
124+
return self.transf_matrix.T @ array
125+
126+
def revert_vector(self, array_reduced):
127+
"""Transform a vector from modal to physical space.
128+
129+
Parameters
130+
----------
131+
array_reduced: np.ndarray
132+
Reduced vector to be reverted.
133+
134+
Returns
135+
-------
136+
array : np.ndarray
137+
Vector in physical space.
138+
"""
139+
return self.transf_matrix @ array_reduced
140+
141+
142+
class Guyan(ModelReduction):
143+
"""Guyan reduction method.
144+
145+
This method can be used to reduce model of the rotor system
146+
to a defined list of degrees of freedom (DOF).
147+
148+
Parameters
149+
----------
150+
rotor: rs.Rotor
151+
The rotor object.
152+
speed : float
153+
Rotor speed.
154+
include_nodes : list of int, optional
155+
List of the nodes to be included in the reduction.
156+
dof_mapping : list of str, optional
157+
List of the local DOFs to be considered in the reduction.
158+
Valid values are: 'x', 'y', 'z', 'alpha', 'beta', 'theta', corresponding to:
159+
- 'x' and 'y': lateral translations
160+
- 'z': axial translation
161+
- 'alpha': rotation about the x-axis
162+
- 'beta': rotation about the y-axis
163+
- 'theta': torsional rotation (about the z-axis)
164+
Default is ['x', 'y'].
165+
include_dofs : list of int, optional
166+
List of DOFs to be included in the reduction,
167+
e.g., DOFs with applied forces or probe locations.
168+
169+
Examples
170+
--------
171+
>>> import ross as rs
172+
>>> rotor = rs.rotor_example()
173+
>>> size = 10000
174+
>>> node = 3
175+
>>> speed = 500.0
176+
>>> t = np.linspace(0, 10, size)
177+
>>> F = np.zeros((size, rotor.ndof))
178+
>>> dofx = rotor.number_dof * node + 0
179+
>>> dofy = rotor.number_dof * node + 1
180+
>>> F[:, dofx] = 10 * np.cos(2 * t)
181+
>>> F[:, dofy] = 10 * np.sin(2 * t)
182+
>>> mr = ModelReduction(
183+
... rotor=rotor,
184+
... speed=speed,
185+
... method="guyan",
186+
... include_dofs=[dofx, dofy]
187+
... )
188+
>>> F_reduct = mr.reduce_vector(F.T).T
189+
>>> np.where(F_reduct[5000, :] != 0)[0] # doctest: +ELLIPSIS
190+
array([4, 5])
191+
"""
192+
193+
def __init__(
194+
self,
195+
rotor,
196+
speed,
197+
include_nodes=None,
198+
dof_mapping=None,
199+
include_dofs=None,
200+
**kwargs,
201+
):
202+
self.rotor = rotor
203+
self.ndof = rotor.ndof
204+
self.number_dof = rotor.number_dof
205+
self.M = rotor.M(speed)
206+
self.K = rotor.K(speed)
207+
208+
if include_nodes is None:
209+
include_nodes = []
210+
if dof_mapping is None:
211+
dof_mapping = ["x", "y"]
212+
if include_dofs is None:
213+
include_dofs = []
214+
215+
dof_dict = {"x": 0, "y": 1, "z": 2, "alpha": 3, "beta": 4, "theta": 5}
216+
local_dofs = [dof_dict[dof] for dof in dof_mapping]
217+
218+
self.selected_dofs, self.ignored_dofs = self._separate_dofs(
219+
include_nodes, local_dofs, include_dofs
220+
)
221+
222+
self.reordering = self.selected_dofs + self.ignored_dofs
223+
self.transf_matrix = self.get_transformation_matrix()
224+
225+
def _select_elem_dofs(self, local_dofs):
226+
"""Select DOFs from rotor bearings and disks"""
227+
selected_dofs = []
228+
elements = self.rotor.bearing_elements + self.rotor.disk_elements
229+
230+
for elm in elements:
231+
if elm.n in self.rotor.link_nodes:
232+
dofs = np.array(list(elm.dof_global_index.values()))
233+
local_dofs_l = list(filter(lambda dof: dof < 3, local_dofs))
234+
include_dofs = dofs[local_dofs_l]
235+
for dof in include_dofs:
236+
selected_dofs.append(dof)
237+
else:
238+
for i in local_dofs:
239+
selected_dofs.append(elm.n * self.number_dof + i)
240+
241+
return selected_dofs
242+
243+
def _separate_dofs(self, include_nodes=None, local_dofs=None, include_dofs=None):
244+
"""Separate the selected DOFs from the ignored DOFs."""
245+
if include_nodes is None:
246+
include_nodes = []
247+
if include_dofs is None:
248+
include_dofs = []
249+
if not local_dofs:
250+
local_dofs = [0, 1]
251+
252+
selected_dofs = set()
253+
selected_dofs.update(include_dofs)
254+
selected_dofs.update(self._select_elem_dofs(local_dofs))
255+
256+
for n in include_nodes:
257+
dofs = n * self.number_dof + np.array(local_dofs)
258+
selected_dofs.update(dofs)
259+
260+
ignored_dofs = sorted(set(range(self.ndof)) - selected_dofs)
261+
selected_dofs = sorted(selected_dofs)
262+
263+
return selected_dofs, ignored_dofs
264+
265+
def get_transformation_matrix(self):
266+
"""Build transformation matrix
267+
268+
Returns
269+
-------
270+
Tg : np.ndarray
271+
Transformation matrix for Guyan method.
272+
"""
273+
K = self.K
274+
275+
n_selected = len(self.selected_dofs)
276+
I = np.eye(n_selected)
277+
278+
Kss = K[np.ix_(self.ignored_dofs, self.ignored_dofs)]
279+
Ksm = K[np.ix_(self.ignored_dofs, self.selected_dofs)]
280+
281+
# Compute transformation matrix
282+
try:
283+
inv_Kss = la.inv(Kss)
284+
except np.linalg.LinAlgError as err:
285+
warnings.warn(
286+
f"{err} error. Using the pseudo-inverse to proceed.", UserWarning
287+
)
288+
inv_Kss = la.pinv(Kss)
289+
290+
Tg = np.vstack((I, -inv_Kss @ Ksm))
291+
292+
return Tg
293+
294+
def _rearrange_matrix(self, matrix):
295+
"""Rearrange matrix based on selected and ignored DOFs"""
296+
return np.block(
297+
[
298+
[
299+
matrix[np.ix_(self.selected_dofs, self.selected_dofs)],
300+
matrix[np.ix_(self.selected_dofs, self.ignored_dofs)],
301+
],
302+
[
303+
matrix[np.ix_(self.ignored_dofs, self.selected_dofs)],
304+
matrix[np.ix_(self.ignored_dofs, self.ignored_dofs)],
305+
],
306+
]
307+
)
308+
309+
def reduce_matrix(self, array):
310+
"""Transform a square matrix from complete to reduced model.
311+
312+
Parameters
313+
----------
314+
array: np.ndarray
315+
Square matrix to be transformed.
316+
317+
Returns
318+
-------
319+
array_reduced : np.ndarray
320+
Reduced matrix.
321+
"""
322+
return self.transf_matrix.T @ self._rearrange_matrix(array) @ self.transf_matrix
323+
324+
def reduce_vector(self, array):
325+
"""Transform a vector from complete to reduced model.
326+
327+
Parameters
328+
----------
329+
array: np.ndarray
330+
Vector to be transformed.
331+
332+
Returns
333+
-------
334+
array_reduced : np.ndarray
335+
Reduced vector.
336+
"""
337+
if array.ndim == 1:
338+
array_reduced = self.transf_matrix.T @ array[self.reordering]
339+
else:
340+
array_reduced = self.transf_matrix.T @ array[self.reordering, :]
341+
342+
return array_reduced
343+
344+
def revert_vector(self, array_reduced):
345+
"""Transform a vector from reduced to complete model.
346+
347+
Parameters
348+
----------
349+
array_reduced: np.ndarray
350+
Reduced vector to be reverted.
351+
352+
Returns
353+
-------
354+
array : np.ndarray
355+
Vector of complete model.
356+
"""
357+
array_transf = self.transf_matrix @ array_reduced
358+
array = np.zeros_like(array_transf)
359+
360+
if array_transf.ndim == 1:
361+
array[self.reordering] = array_transf
362+
else:
363+
array[self.reordering, :] = array_transf
364+
365+
return array

0 commit comments

Comments
 (0)