Tutorial - Modeling
Contents
Tutorial - Modeling#
This is a basic tutorial on how to use ROSS (rotordynamics open-source software), a Python library for rotordynamic analysis. Most of this code follows object-oriented paradigm, which is represented in this UML DIAGRAM.
Before starting the tutorial, it is worth noting some of ROSS’ design characteristics.
First, we can divide the use of ROSS in two steps:
Building the model;
Calculating the results.
We can build a model by instantiating elements such as beams (shaft), disks and bearings. These elements are all defined in classes with names such as ShaftElement
, BearingElement
and so on.
After instantiating some elements, we can then use these to build a rotor.
This tutorial is about building your rotor model. First, you will learn how to create and assign materials, how to instantiate the elements which composes the rotor and how to convert units in ROSS with pint library. This means that every time we call a function, we can use pint.Quantity as an argument for values that have units. If we give a float to the function ROSS will consider SI units as default.
In the following topics, we will discuss the most relevant classes for a quick start on how to use ROSS.
import os
from pathlib import Path
import ross as rs
import numpy as np
import plotly.graph_objects as go
import plotly.io as pio
pio.renderers.default = "notebook"
Section 1: Material Class#
There is a class called Material to hold material’s properties. Materials are applied to shaft and disk elements.
1.1 Creating a material#
To instantiate a Material class, you only need to give 2 out of
the following parameters: E
(Young’s Moduluds), G_s
(Shear
Modulus) ,Poisson
(Poisson Coefficient), and the material
density rho
.
# from E and G_s
steel = rs.Material(name="Steel", rho=7810, E=211e9, G_s=81.2e9)
# from E and Poisson
steel2 = rs.Material(name="Steel", rho=7810, E=211e9, Poisson=0.3)
# from G_s and Poisson
steel3 = rs.Material(name="Steel", rho=7810, G_s=81.2e9, Poisson=0.3)
print(steel)
# returning attributes
print("="*36)
print(f"Young's Modulus: {steel.E}")
print(f"Shear Modulus: {steel.G_s}")
Steel
-----------------------------------
Density (kg/m**3): 7810.0
Young`s modulus (N/m**2): 2.11e+11
Shear modulus (N/m**2): 8.12e+10
Poisson coefficient : 0.29926108
====================================
Young's Modulus: 211000000000.0
Shear Modulus: 81200000000.0
Note: Adding 3 arguments to the Material class raises an error.
1.2 Saving materials#
To save an already instantiated Material object, you need to use the following method.
steel.save_material()
1.3 Available materials#
Saved Materials are stored in a .toml file, which can be read as .txt. The file is placed on ROSS root file with name available_materials.toml
.
It’s possible to access the Material data from the file. With the file opened, you can:
modify the properties directly;
create new materials;
It’s important to keep the file structure to ensure the correct functioning of the class.
[Materials.Steel]
name = "Steel"
rho = 7810
E = 211000000000.0
Poisson = 0.2992610837438423
G_s = 81200000000.0
color = "#525252"
Do not change the dictionary keys and the order they’re built.
To check what materials are available, use the command:
rs.Material.available_materials()
['Steel', 'AISI4140', 'A216WCB']
1.4 Loading materials#
After checking the available materials, you should use the Material.use_material('name')
method with the name of the material as a parameter.
steel5 = rs.Material.load_material('Steel')
Section 2: ShaftElement Class#
ShaftElement
allows you to create cylindrical and conical shaft elements. It means you can set differents outer and inner diameters for each element node.
There are some ways in which you can choose the parameters to model this element:
Euler–Bernoulli beam Theory (
rotary_inertia=False, shear_effects=False
)Timoshenko beam Theory (
rotary_inertia=True, shear_effects=True
- used as default)
The matrices (mass, stiffness, damping and gyroscopic) will be defined considering the following local coordinate vector:
\([x_0, y_0, \alpha_0, \beta_0, x_1, y_1, \alpha_1, \beta_1]^T\) Where \(\alpha_0\) and \(\alpha_1\) are the bending on the yz plane \(\beta_0\) and \(\beta_1\) are the bending on the xz plane.
This element represents the rotor’s shaft, all the other elements are correlated with this one.
2.1 Creating shaft elements#
The next examples present different ways of how to create a ShaftElement object, from a single element to a list of several shaft elements with different properties.
When creating shaft elements, you don’t necessarily need to input a specific node. If n=None
, the Rotor
class will assign a value to the element when building a rotor model (see section 6).
You can also pass the same n
value to several shaft elements in the same rotor model.
2.1.1 Cylindrical shaft element#
As it’s been seen, a shaft element has 4 parameters for diameters. To simplify that, when creating a cylindrical element, you only need to give 2 of them: idl
and odl
. So the other 2 (idr
and odr
) get the same values.
Note: you can give all the 4 parameters, as long they match each other.
# Cylindrical shaft element
L = 0.25
i_d = 0
o_d = 0.05
cy_elem = rs.ShaftElement(
L=L,
idl=i_d,
odl=o_d,
material=steel,
shear_effects=True,
rotary_inertia=True,
gyroscopic=True,
)
print(cy_elem)
Element Number: None
Element Lenght (m): 0.25
Left Int. Diam. (m): 0.0
Left Out. Diam. (m): 0.05
Right Int. Diam. (m): 0.0
Right Out. Diam. (m): 0.05
-----------------------------------
Steel
-----------------------------------
Density (kg/m**3): 7810.0
Young`s modulus (N/m**2): 2.11e+11
Shear modulus (N/m**2): 8.12e+10
Poisson coefficient : 0.29926108
2.1.2 Conical shaft element#
To create a conical shaft elements, you must give all the 4 diamater parameters, and idl != idr
and/or odl != odr
.
# Conical shaft element
L = 0.25
idl = 0
idr = 0
odl = 0.05
odr = 0.07
co_elem = rs.ShaftElement(
L=L,
idl=idl,
idr=idr,
odl=odl,
odr=odr,
material=steel,
shear_effects=True,
rotary_inertia=True,
gyroscopic=True,
)
print(co_elem)
Element Number: None
Element Lenght (m): 0.25
Left Int. Diam. (m): 0.0
Left Out. Diam. (m): 0.05
Right Int. Diam. (m): 0.0
Right Out. Diam. (m): 0.07
-----------------------------------
Steel
-----------------------------------
Density (kg/m**3): 7810.0
Young`s modulus (N/m**2): 2.11e+11
Shear modulus (N/m**2): 8.12e+10
Poisson coefficient : 0.29926108
Returning element matrices#
Use one of this methods to return the matrices:
.M()
: returns the mass matrix.K(frequency)
: returns the stiffness matrix.C(frequency)
: returns the damping matrix.G()
: returns de gyroscopic matrix
# Mass matrix
# cy_elem.M()
# Stiffness matrix
# frequency = 0
# cy_elem.K(frequency)
# Damping matrix
# frequency = 0
# cy_elem.C(frequency)
# Gyroscopic matrix
# cy_elem.G()
2.1.3 List of elements - identical properties#
Now we leanrt how to create elements, let’s automate the process of creating multiple elements with identical properties.
In this example, we want 6 shaft elements with identical properties. This process can be done using a for
loop or a list comprehension.
# Creating a list of shaft elements
L = 0.25
i_d = 0
o_d = 0.05
N = 6
shaft_elements = [
rs.ShaftElement(
L=L,
idl=i_d,
odl=o_d,
material=steel,
shear_effects=True,
rotary_inertia=True,
gyroscopic=True,
)
for _ in range(N)
]
shaft_elements
[ShaftElement(L=0.25, idl=0.0, idr=0.0, odl=0.05, odr=0.05, material='Steel', n=None),
ShaftElement(L=0.25, idl=0.0, idr=0.0, odl=0.05, odr=0.05, material='Steel', n=None),
ShaftElement(L=0.25, idl=0.0, idr=0.0, odl=0.05, odr=0.05, material='Steel', n=None),
ShaftElement(L=0.25, idl=0.0, idr=0.0, odl=0.05, odr=0.05, material='Steel', n=None),
ShaftElement(L=0.25, idl=0.0, idr=0.0, odl=0.05, odr=0.05, material='Steel', n=None),
ShaftElement(L=0.25, idl=0.0, idr=0.0, odl=0.05, odr=0.05, material='Steel', n=None)]
2.1.4 List of elements - different properties#
Now we leanrt how to create elements, let’s automate the process of creating multiple elements with identical properties.
In this example, we want 6 shaft elements which properties may not be the same. This process can be done using a for
loop or a list comprehension, coupled with Python’s zip()
method.
We create lists for each property, where each term refers to a single element:
# OPTION No.1:
# Using zip() method
L = [0.20, 0.20, 0.10, 0.10, 0.20, 0.20]
i_d = [0.01, 0, 0, 0, 0, 0.01]
o_d = [0.05, 0.05, 0.06, 0.06, 0.05, 0.05]
shaft_elements = [
rs.ShaftElement(
L=l,
idl=idl,
odl=odl,
material=steel,
shear_effects=True,
rotary_inertia=True,
gyroscopic=True,
)
for l, idl, odl in zip(L, i_d, o_d)
]
shaft_elements
[ShaftElement(L=0.2, idl=0.01, idr=0.01, odl=0.05, odr=0.05, material='Steel', n=None),
ShaftElement(L=0.2, idl=0.0, idr=0.0, odl=0.05, odr=0.05, material='Steel', n=None),
ShaftElement(L=0.1, idl=0.0, idr=0.0, odl=0.06, odr=0.06, material='Steel', n=None),
ShaftElement(L=0.1, idl=0.0, idr=0.0, odl=0.06, odr=0.06, material='Steel', n=None),
ShaftElement(L=0.2, idl=0.0, idr=0.0, odl=0.05, odr=0.05, material='Steel', n=None),
ShaftElement(L=0.2, idl=0.01, idr=0.01, odl=0.05, odr=0.05, material='Steel', n=None)]
# OPTION No.2:
# Using list index
L = [0.20, 0.20, 0.10, 0.10, 0.20, 0.20]
i_d = [0.01, 0, 0, 0, 0, 0.01]
o_d = [0.05, 0.05, 0.06, 0.06, 0.05, 0.05]
N = len(L)
shaft_elements = [
rs.ShaftElement(
L=L[i],
idl=i_d[i],
odl=o_d[i],
material=steel,
shear_effects=True,
rotary_inertia=True,
gyroscopic=True,
)
for i in range(N)
]
shaft_elements
[ShaftElement(L=0.2, idl=0.01, idr=0.01, odl=0.05, odr=0.05, material='Steel', n=None),
ShaftElement(L=0.2, idl=0.0, idr=0.0, odl=0.05, odr=0.05, material='Steel', n=None),
ShaftElement(L=0.1, idl=0.0, idr=0.0, odl=0.06, odr=0.06, material='Steel', n=None),
ShaftElement(L=0.1, idl=0.0, idr=0.0, odl=0.06, odr=0.06, material='Steel', n=None),
ShaftElement(L=0.2, idl=0.0, idr=0.0, odl=0.05, odr=0.05, material='Steel', n=None),
ShaftElement(L=0.2, idl=0.01, idr=0.01, odl=0.05, odr=0.05, material='Steel', n=None)]
2.2 Creating shaft elements via Excel#
There is an option for creating a list of shaft elements via an Excel file. The classmethod .from_table()
reads an Excel file created and converts it to a list of shaft elements.
A header with the names of the columns is required. These names should match the names expected by the routine (usually the names of the parameters, but also similar ones). The program will read every row bellow the header until they end or it reaches a NaN, which means if the code reaches to an empty line, it stops iterating.
An example of Excel content can be found at ROSS GitHub repository at ross/tests/data/shaft_si.xls, spreadsheet “Model”.
You can load it using the following code.
shaft_file = Path("shaft_si.xls")
shaft = rs.ShaftElement.from_table(
file=shaft_file, sheet_type="Model", sheet_name="Model"
)
Section 3: DiskElement Class#
The class DiskElement
allows you to create disk elements, representing rotor equipments which can be considered only to add mass and inertia to the system, disregarding the stiffness.
ROSS offers 3 (three) ways to create a disk element:
Inputing mass and inertia data
Inputing geometrical and material data
From Excel table
3.1 Creating disk elements from inertia properties#
If you have access to the mass and inertia properties of a equipment, you can input the data directly to the element.
Disk elements are useful to represent equipments which mass and inertia are significant, but the stiffness can be neglected.
3.1.1 Creating a single disk element#
This example below shows how to instantiate a disk element according to the mass and inertia properties:
disk = rs.DiskElement(
n=0,
m=32.58,
Ip=0.178,
Id=0.329,
tag="Disk"
)
disk
DiskElement(Id=0.329, Ip=0.178, m=32.58, color='Firebrick', n=0, scale_factor=1.0, tag='Disk')
3.1.2 Creating a list of disk element#
This example below shows how to create a list of disk element according to the mass and inertia properties. The logic is the same applied to shaft elements.
# OPTION No.1:
# Using zip() method
n_list = [2, 4]
m_list = [32.6, 35.8]
Id_list = [0.17808928, 0.17808928]
Ip_list = [0.32956362, 0.38372842]
disk_elements = [
rs.DiskElement(
n=n,
m=m,
Id=Id,
Ip=Ip,
)
for n, m, Id, Ip in zip(n_list, m_list, Id_list, Ip_list)
]
disk_elements
[DiskElement(Id=0.17809, Ip=0.32956, m=32.6, color='Firebrick', n=2, scale_factor=1.0, tag=None),
DiskElement(Id=0.17809, Ip=0.38373, m=35.8, color='Firebrick', n=4, scale_factor=1.0, tag=None)]
# OPTION No.2:
# Using list index
n_list = [2, 4]
m_list = [32.6, 35.8]
Id_list = [0.17808928, 0.17808928]
Ip_list = [0.32956362, 0.38372842]
N = len(n_list)
disk_elements = [
rs.DiskElement(
n=n_list[i],
m=m_list[i],
Id=Id_list[i],
Ip=Ip_list[i],
)
for i in range(N)
]
disk_elements
[DiskElement(Id=0.17809, Ip=0.32956, m=32.6, color='Firebrick', n=2, scale_factor=1.0, tag=None),
DiskElement(Id=0.17809, Ip=0.38373, m=35.8, color='Firebrick', n=4, scale_factor=1.0, tag=None)]
3.2 Creating disk elements from geometrical properties#
Besides the instantiation previously explained, there is a way to instantiate a DiskElement with only geometrical parameters (an approximation for cylindrical disks) and the disk’s material, as we can see in the following code. In this case, there’s a class method (rs.DiskElement.from_geometry()
) which you can use.
ROSS will take geometrical parameters (outer and inner diameters, and width) and convert them into mass and inertia data. Once again, considering the disk as a cylinder.
3.2.1 Creating a single disk element#
This example below shows how to instantiate a disk element according to the geometrical and material properties:
disk1 = rs.DiskElement.from_geometry(
n=4,
material=steel,
width=0.07,
i_d=0.05,
o_d=0.28
)
print(disk1)
print("="*76)
print(f"Disk mass: {disk1.m}")
print(f"Disk polar inertia: {disk1.Ip}")
print(f"Disk diametral inertia: {disk1.Id}")
Tag: None
Node: 4
Mass (kg): 32.59
Diam. inertia (kg*m**2): 0.17809
Polar. inertia (kg*m**2): 0.32956
============================================================================
Disk mass: 32.58972765304033
Disk polar inertia: 0.32956362089137037
Disk diametral inertia: 0.17808928257067666
3.2.2 Creating a list of disk element#
This example below shows how to create a list of disk element according to the geometrical and material properties. The logic is the same applied to shaft elements.
# OPTION No.1:
# Using zip() method
n_list = [2, 4]
width_list = [0.7, 0.7]
i_d_list = [0.05, 0.05]
o_d_list = [0.15, 0.18]
disk_elements = [
rs.DiskElement.from_geometry(
n=n,
material=steel,
width=width,
i_d=i_d,
o_d=o_d,
)
for n, width, i_d, o_d in zip(n_list, width_list, i_d_list, o_d_list)
]
disk_elements
[DiskElement(Id=3.6408, Ip=0.26836, m=85.875, color='Firebrick', n=2, scale_factor=1.0, tag=None),
DiskElement(Id=5.5224, Ip=0.56007, m=128.38, color='Firebrick', n=4, scale_factor=1.0, tag=None)]
# OPTION No.2:
# Using list index
n_list = [2, 4]
width_list = [0.7, 0.7]
i_d_list = [0.05, 0.05]
o_d_list = [0.15, 0.18]
N = len(n_list)
disk_elements = [
rs.DiskElement.from_geometry(
n=n_list[i],
material=steel,
width=width_list[i],
i_d=i_d_list[i],
o_d=o_d_list[i],
)
for i in range(N)
]
disk_elements
[DiskElement(Id=3.6408, Ip=0.26836, m=85.875, color='Firebrick', n=2, scale_factor=1.0, tag=None),
DiskElement(Id=5.5224, Ip=0.56007, m=128.38, color='Firebrick', n=4, scale_factor=1.0, tag=None)]
3.3 Creating disk elements via Excel#
The third option for creating disk elements is via an Excel file. The classmethod .from_table()
reads an Excel file created and converts it to a list of disk elements. This method accepts only mass and inertia inputs.
A header with the names of the columns is required. These names should match the names expected by the routine (usually the names of the parameters, but also similar ones). The program will read every row bellow the header until they end or it reaches a NaN, which means if the code reaches to an empty line, it stops iterating.
You can take advantage of the excel file used to assemble shaft elements, to assemble disk elements, just add a new spreadsheet to your Excel file and specify the correct sheet_name
.
An example of Excel content can be found at diretory ross/tests/data/shaft_si.xls, spreadsheet “More”.
file_path = Path("shaft_si.xls")
list_of_disks = rs.DiskElement.from_table(file=file_path, sheet_name="More")
list_of_disks
[DiskElement(Id=0.0, Ip=0.0, m=15.12, color='Firebrick', n=3, scale_factor=1, tag=None),
DiskElement(Id=0.025, Ip=0.047, m=6.91, color='Firebrick', n=20, scale_factor=1, tag=None),
DiskElement(Id=0.025, Ip=0.047, m=6.93, color='Firebrick', n=23, scale_factor=1, tag=None),
DiskElement(Id=0.025, Ip=0.048, m=6.95, color='Firebrick', n=26, scale_factor=1, tag=None),
DiskElement(Id=0.025, Ip=0.048, m=6.98, color='Firebrick', n=29, scale_factor=1, tag=None),
DiskElement(Id=0.025, Ip=0.048, m=6.94, color='Firebrick', n=32, scale_factor=1, tag=None),
DiskElement(Id=0.025, Ip=0.048, m=6.96, color='Firebrick', n=35, scale_factor=1, tag=None)]
Section 4: Bearing and Seal Classes#
ROSS has a serie of classe to represent element that adds stiffness and / or damping to a rotor system. They’re suitable to represent mainly bearings, supports and seals. Each one aims to represent some types of bearing and seal.
All the class will return four stiffness coefficients (\(k_{xx}\), \(k_{xy}\), \(k_{yx}\), \(k_{yy}\)) and four damping coefficients (\(c_{xx}\), \(c_{xy}\), \(c_{yx}\), \(c_{yy}\)), which will be used to assemble the stiffness and damping matrices.
The main difference between these classes are the arguments the user must input to create the element.
Available bearing classes and class methods:
BearingElement
: represents a general (journal) bearing element.
SealElement
: represents a general seal element.
BallBearingElement
: A bearing element for ball bearings
RollerBearingElement
: A bearing element for roller bearings.
MagneticBearingElement
: A bearing element for magnetic bearings.
5.1.
param_to_coef
: A bearing element for magnetic bearings from electromagnetic parameters
The classes from item 2 to 5 inherits from BearingElement
class. It means, you can use the same methods and commands, set up to BearingElement
, in the other classes.
4.1 BearingElement Class#
This class will create a bearing element. Bearings are elements that only add stiffness and damping properties to the rotor system. These parameters are defined by 8 dynamics coefficients (4 stiffness coefficients and 4 damping coefficients).
Parameters can be a constant value or speed dependent. For speed dependent parameters, each argument should be passed as an array and the correspondent speed values should also be passed as an array. Values for each parameter will be interpolated for the speed.
Bearing elements are single node elements and linked to “ground”, but it’s possible to create a new node with n_link
argument to introduce a link with other elements. Useful to add bearings in series or co-axial rotors.
4.1.1 Bearing with constant coefficients#
Bearings can have a constant value for each coefficient. In this case, it’s not necessary to give a value to frequency
argument.
The next example shows how to instantiate a single bearing with constant coefficients:
stfx = 1e6
stfy = 0.8e6
bearing1 = rs.BearingElement(n=0, kxx=stfx, kyy=stfy, cxx=1e3)
print(bearing1)
print("="*55)
print(f"Kxx coefficient: {bearing1.kxx}")
BearingElement(n=0, n_link=None,
kxx=[1000000.0], kxy=[0],
kyx=[0], kyy=[800000.0],
cxx=[1000.0], cxy=[0],
cyx=[0], cyy=[1000.0],
mxx=[0], mxy=[0],
myx=[0], myy=[0],
frequency=None, tag=None)
=======================================================
Kxx coefficient: [1000000.0]
4.1.2 Bearing with varying coefficients#
The coefficients could be an array with different values for different rotation speeds, in that case you only have to give a parameter ‘frequency’ which is a array with the same size as the coefficients array.
The next example shows how to instantiate a single bearing with speed dependent parameters:
bearing2 = rs.BearingElement(
n=0,
kxx=np.array([0.5e6, 1.0e6, 2.5e6]),
kyy=np.array([1.5e6, 2.0e6, 3.5e6]),
cxx=np.array([0.5e3, 1.0e3, 1.5e3]),
frequency=np.array([0, 1000, 2000]),
)
print(bearing2)
print("="*79)
print(f"Kxx coefficient: {bearing2.kxx}")
BearingElement(n=0, n_link=None,
kxx=[ 500000. 1000000. 2500000.], kxy=[0, 0, 0],
kyx=[0, 0, 0], kyy=[1500000. 2000000. 3500000.],
cxx=[ 500. 1000. 1500.], cxy=[0, 0, 0],
cyx=[0, 0, 0], cyy=[ 500. 1000. 1500.],
mxx=[0, 0, 0], mxy=[0, 0, 0],
myx=[0, 0, 0], myy=[0, 0, 0],
frequency=[ 0. 1000. 2000.], tag=None)
===============================================================================
Kxx coefficient: [ 500000. 1000000. 2500000.]
If the size of coefficient and frequency arrays do not match, an ValueError
is raised
The next example shows the instantiate of a bearing with odd parameters:
bearing_odd = rs.BearingElement( # odd dimensions
n=0,
kxx=np.array([0.5e6, 1.0e6, 2.5e6]),
kyy=np.array([1.5e6, 2.0e6, 3.5e6]),
cxx=np.array([0.5e3, 1.0e3, 1.5e3]),
frequency=np.array([0, 1000, 2000, 3000])
)
4.1.3 Inserting bearing elements in series#
Bearing and seal elements are 1-node element, which means the element attaches to a given node from the rotor shaft and it’s connect to the “ground”. However, there’s an option to couple multiple elements in series, using the n_link
argument. This is very useful to simulate structures which support the machine, for example.
n_link
opens a new node to the rotor system, or it can be associated to another rotor node (useful in co-axial rotor models). Then, the new BearingElement node, is set equal to the n_link
from the previous element.
stfx = 1e6
stfy = 0.8e6
bearing3 = rs.BearingElement(n=0, kxx=stfx, kyy=stfy, cxx=1e3, n_link=1, tag="journal_bearing")
bearing4 = rs.BearingElement(n=1, kxx=1e7, kyy=1e9, cxx=10, tag="support")
print(bearing3)
print(bearing4)
BearingElement(n=0, n_link=1,
kxx=[1000000.0], kxy=[0],
kyx=[0], kyy=[800000.0],
cxx=[1000.0], cxy=[0],
cyx=[0], cyy=[1000.0],
mxx=[0], mxy=[0],
myx=[0], myy=[0],
frequency=None, tag='journal_bearing')
BearingElement(n=1, n_link=None,
kxx=[10000000.0], kxy=[0],
kyx=[0], kyy=[1000000000.0],
cxx=[10], cxy=[0],
cyx=[0], cyy=[10],
mxx=[0], mxy=[0],
myx=[0], myy=[0],
frequency=None, tag='support')
4.1.4 Visualizing coefficients graphically#
If you want to visualize how the coefficients varies with speed, you can select a specific coefficient and use the .plot()
method.
Let’s return to the example done in 4.1.2 and check how \(k_{yy}\) and \(c_{yy}\) varies. You can check for all the 8 dynamic coefficients as you like.
bearing2.plot('kyy')