Skip to content

Commit caf0ade

Browse files
committed
Merge branch 'main' into update/readme
2 parents 89da300 + 418e44d commit caf0ade

File tree

9 files changed

+1709
-534
lines changed

9 files changed

+1709
-534
lines changed

docs/user_guide/tutorial_part_4.ipynb

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

ross/bearing_seal_element.py

Lines changed: 269 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -760,6 +760,64 @@ def G(self):
760760

761761
return G
762762

763+
def _hover_info(self, frequency=None):
764+
"""Generate hover information for bearing element.
765+
766+
This method can be overridden by subclasses to customize the hover
767+
information displayed when hovering over the bearing element in plots.
768+
769+
Parameters
770+
----------
771+
frequency : float, optional
772+
Frequency at which to display coefficients (rad/s).
773+
Not used - displays coefficients at first and last frequencies.
774+
775+
Returns
776+
-------
777+
customdata : list
778+
Data to attach to hover trace.
779+
hovertemplate : str
780+
Template string for hover display with HTML formatting.
781+
782+
Examples
783+
--------
784+
>>> bearing = bearing_example()
785+
>>> customdata, hovertemplate = bearing._hover_info()
786+
>>> customdata[0] # node number
787+
0
788+
"""
789+
# Get first and last frequencies
790+
if self.frequency is not None:
791+
if hasattr(self.frequency, "__iter__"):
792+
freq_0 = self.frequency[0]
793+
freq_1 = self.frequency[-1]
794+
else:
795+
freq_0 = freq_1 = self.frequency
796+
else:
797+
freq_0 = freq_1 = 0
798+
799+
# Convert frequencies to RPM
800+
freq_0_rpm = Q_(freq_0, "rad/s").to("RPM").m
801+
freq_1_rpm = Q_(freq_1, "rad/s").to("RPM").m
802+
803+
# Build hover template directly without intermediate list
804+
hovertemplate = f"Bearing at Node: {self.n}<br>"
805+
if self.tag is not None:
806+
hovertemplate = f"Tag: {self.tag}<br>" + hovertemplate
807+
808+
hovertemplate += f"Frequency: {freq_0_rpm:.2f} ... {freq_1_rpm:.2f} RPM<br>"
809+
hovertemplate += (
810+
f"Kxx: {self.kxx_interpolated(freq_0):.3e} ... {self.kxx_interpolated(freq_1):.3e} N/m<br>"
811+
f"Kyy: {self.kyy_interpolated(freq_0):.3e} ... {self.kyy_interpolated(freq_1):.3e} N/m<br>"
812+
f"Cxx: {self.cxx_interpolated(freq_0):.3e} ... {self.cxx_interpolated(freq_1):.3e} N·s/m<br>"
813+
f"Cyy: {self.cyy_interpolated(freq_0):.3e} ... {self.cyy_interpolated(freq_1):.3e} N·s/m<br>"
814+
)
815+
816+
# customdata is still needed for plotly, but can be minimal
817+
customdata = [self.n]
818+
819+
return customdata, hovertemplate
820+
763821
def _patch(self, position, fig):
764822
"""Bearing element patch.
765823
@@ -806,6 +864,29 @@ def _patch(self, position, fig):
806864
fig.add_trace(go.Scatter(x=x_bot, y=np.add(yl_bot, yc_pos), **default_values))
807865
fig.add_trace(go.Scatter(x=x_bot, y=np.add(yu_bot, yc_pos), **default_values))
808866

867+
# Add hover information marker at the center of bottom base
868+
customdata, hovertemplate = self._hover_info()
869+
# Scale marker size proportionally to the bearing icon height
870+
# icon_h already includes the scale_factor effect from rotor assembly
871+
marker_size = icon_h * 200 # proportional to actual bearing size
872+
hover_marker_values_top = dict(
873+
mode="markers",
874+
x=[zpos],
875+
y=[ypos + icon_h / 2],
876+
marker=dict(size=marker_size, color=self.color, opacity=0),
877+
customdata=[customdata],
878+
hovertemplate=hovertemplate,
879+
hoverinfo="text",
880+
name=self.tag,
881+
legendgroup="bearings",
882+
showlegend=False,
883+
)
884+
fig.add_trace(go.Scatter(**hover_marker_values_top))
885+
# copy the customdata and hovertemplate from the top marker just multiplying the y value by -1
886+
hover_marker_values_bottom = hover_marker_values_top.copy()
887+
hover_marker_values_bottom["y"] = [-1 * hover_marker_values_top["y"][0]]
888+
fig.add_trace(go.Scatter(**hover_marker_values_bottom))
889+
809890
# plot top base
810891
x_top = [zpos, zpos, zs0, zs1]
811892
yl_top = [
@@ -1363,6 +1444,63 @@ def __init__(
13631444
# make seals with half the bearing size as a default
13641445
self.scale_factor = scale_factor if scale_factor else self.scale_factor / 2
13651446

1447+
def _hover_info(self, frequency=None):
1448+
"""Generate hover information for seal element.
1449+
1450+
Overrides the base class method to include seal-specific information
1451+
such as cross-coupled coefficients and seal leakage.
1452+
1453+
Parameters
1454+
----------
1455+
frequency : float, optional
1456+
Frequency at which to display coefficients (rad/s).
1457+
Not used - displays coefficients at first and last frequencies.
1458+
1459+
Returns
1460+
-------
1461+
customdata : list
1462+
Data to attach to hover trace.
1463+
hovertemplate : str
1464+
Template string for hover display with HTML formatting.
1465+
"""
1466+
# Get first and last frequencies
1467+
if self.frequency is not None:
1468+
if hasattr(self.frequency, "__iter__"):
1469+
freq_0 = self.frequency[0]
1470+
freq_1 = self.frequency[-1]
1471+
else:
1472+
freq_0 = freq_1 = self.frequency
1473+
else:
1474+
freq_0 = freq_1 = 0
1475+
1476+
# Convert frequencies to RPM
1477+
freq_0_rpm = Q_(freq_0, "rad/s").to("RPM").m
1478+
freq_1_rpm = Q_(freq_1, "rad/s").to("RPM").m
1479+
1480+
# Build hover template directly
1481+
hovertemplate = f"Seal at Node: {self.n}<br>"
1482+
if self.tag is not None:
1483+
hovertemplate = f"Tag: {self.tag}<br>" + hovertemplate
1484+
1485+
hovertemplate += f"Frequency: {freq_0_rpm:.2f} ... {freq_1_rpm:.2f} RPM<br>"
1486+
hovertemplate += (
1487+
f"Kxx: {self.kxx_interpolated(freq_0):.3e} ... {self.kxx_interpolated(freq_1):.3e} N/m<br>"
1488+
f"Kyy: {self.kyy_interpolated(freq_0):.3e} ... {self.kyy_interpolated(freq_1):.3e} N/m<br>"
1489+
f"Kxy: {self.kxy_interpolated(freq_0):.3e} ... {self.kxy_interpolated(freq_1):.3e} N/m<br>"
1490+
f"Kyx: {self.kyx_interpolated(freq_0):.3e} ... {self.kyx_interpolated(freq_1):.3e} N/m<br>"
1491+
f"Cxx: {self.cxx_interpolated(freq_0):.3e} ... {self.cxx_interpolated(freq_1):.3e} N·s/m<br>"
1492+
f"Cyy: {self.cyy_interpolated(freq_0):.3e} ... {self.cyy_interpolated(freq_1):.3e} N·s/m<br>"
1493+
f"Cxy: {self.cxy_interpolated(freq_0):.3e} ... {self.cxy_interpolated(freq_1):.3e} N·s/m<br>"
1494+
f"Cyx: {self.cyx_interpolated(freq_0):.3e} ... {self.cyx_interpolated(freq_1):.3e} N·s/m<br>"
1495+
)
1496+
1497+
if self.seal_leakage is not None:
1498+
hovertemplate += f"Seal Leakage: {self.seal_leakage:.3e}<br>"
1499+
1500+
customdata = [self.n]
1501+
1502+
return customdata, hovertemplate
1503+
13661504

13671505
class BallBearingElement(BearingElement):
13681506
"""A bearing element for ball bearings.
@@ -1490,6 +1628,44 @@ def __init__(
14901628
color=color,
14911629
)
14921630

1631+
def _hover_info(self, frequency=None):
1632+
"""Generate hover information for ball bearing element.
1633+
1634+
Overrides the base class method to include ball bearing-specific
1635+
geometric and operating parameters.
1636+
1637+
Parameters
1638+
----------
1639+
frequency : float, optional
1640+
Frequency at which to display coefficients (rad/s).
1641+
Not used for ball bearings (frequency-independent).
1642+
1643+
Returns
1644+
-------
1645+
customdata : list
1646+
Data to attach to hover trace.
1647+
hovertemplate : str
1648+
Template string for hover display with HTML formatting.
1649+
"""
1650+
hovertemplate = f"Ball Bearing at Node: {self.n}<br>"
1651+
if self.tag is not None:
1652+
hovertemplate = f"Tag: {self.tag}<br>" + hovertemplate
1653+
1654+
hovertemplate += (
1655+
f"Number of Balls: {self.n_balls:.0f}<br>"
1656+
f"Ball Diameter: {self.d_balls:.4f} m<br>"
1657+
f"Static Load: {self.fs:.2f} N<br>"
1658+
f"Contact Angle: {self.alpha:.3f} rad<br>"
1659+
f"Kxx: {self.kxx[0]:.3e} N/m<br>"
1660+
f"Kyy: {self.kyy[0]:.3e} N/m<br>"
1661+
f"Cxx: {self.cxx[0]:.3e} N·s/m<br>"
1662+
f"Cyy: {self.cyy[0]:.3e} N·s/m<br>"
1663+
)
1664+
1665+
customdata = [self.n]
1666+
1667+
return customdata, hovertemplate
1668+
14931669

14941670
class RollerBearingElement(BearingElement):
14951671
"""A bearing element for roller bearings.
@@ -1805,6 +1981,46 @@ def __init__(
18051981
color=color,
18061982
)
18071983

1984+
def _hover_info(self, frequency=None):
1985+
"""Generate hover information for magnetic bearing element.
1986+
1987+
Overrides the base class method to include magnetic bearing-specific
1988+
electromagnetic and control parameters.
1989+
1990+
Parameters
1991+
----------
1992+
frequency : float, optional
1993+
Frequency at which to display coefficients (rad/s).
1994+
Not used for magnetic bearings (frequency-independent).
1995+
1996+
Returns
1997+
-------
1998+
customdata : list
1999+
Data to attach to hover trace.
2000+
hovertemplate : str
2001+
Template string for hover display with HTML formatting.
2002+
"""
2003+
hovertemplate = f"Magnetic Bearing at Node: {self.n}<br>"
2004+
if self.tag is not None:
2005+
hovertemplate = f"Tag: {self.tag}<br>" + hovertemplate
2006+
2007+
hovertemplate += (
2008+
f"Air Gap (g0): {self.g0:.4e} m<br>"
2009+
f"Bias Current (i0): {self.i0:.2f} A<br>"
2010+
f"Pole Area: {self.ag:.4e} m²<br>"
2011+
f"Windings: {self.nw:.0f}<br>"
2012+
f"PID Kp: {self.kp_pid:.3e}<br>"
2013+
f"PID Kd: {self.kd_pid:.3e}<br>"
2014+
f"Kxx: {self.kxx[0]:.3e} N/m<br>"
2015+
f"Kyy: {self.kyy[0]:.3e} N/m<br>"
2016+
f"Cxx: {self.cxx[0]:.3e} N·s/m<br>"
2017+
f"Cyy: {self.cyy[0]:.3e} N·s/m<br>"
2018+
)
2019+
2020+
customdata = [self.n]
2021+
2022+
return customdata, hovertemplate
2023+
18082024
def compute_pid_amb(self, dt, current_offset, setpoint, disp, dof_index):
18092025
"""Compute PID control force for a single AMB DoF (x or y).
18102026
@@ -2080,6 +2296,59 @@ def __init__(
20802296
**kwargs,
20812297
)
20822298

2299+
def _hover_info(self, frequency=None):
2300+
"""Generate hover information for cylindrical bearing element.
2301+
2302+
Overrides the base class method to include cylindrical bearing-specific
2303+
fluid film parameters and operating conditions.
2304+
2305+
Parameters
2306+
----------
2307+
frequency : float, optional
2308+
Frequency at which to display coefficients (rad/s).
2309+
Not used - displays coefficients at first and last speeds.
2310+
2311+
Returns
2312+
-------
2313+
customdata : list
2314+
Data to attach to hover trace.
2315+
hovertemplate : str
2316+
Template string for hover display with HTML formatting.
2317+
"""
2318+
# Get first and last speeds
2319+
freq_0 = self.speed[0]
2320+
freq_1 = self.speed[-1]
2321+
idx_0 = 0
2322+
idx_1 = -1
2323+
2324+
# Convert speeds to RPM
2325+
freq_0_rpm = Q_(freq_0, "rad/s").to("RPM").m
2326+
freq_1_rpm = Q_(freq_1, "rad/s").to("RPM").m
2327+
2328+
hovertemplate = f"Cylindrical Bearing at Node: {self.n}<br>"
2329+
if self.tag is not None:
2330+
hovertemplate = f"Tag: {self.tag}<br>" + hovertemplate
2331+
2332+
hovertemplate += (
2333+
f"Length: {self.bearing_length:.4f} m<br>"
2334+
f"Journal Diameter: {self.journal_diameter:.4f} m<br>"
2335+
f"Radial Clearance: {self.radial_clearance:.4e} m<br>"
2336+
f"Oil Viscosity: {self.oil_viscosity:.4f} Pa·s<br>"
2337+
f"Load: {self.weight:.2f} N<br>"
2338+
f"Speed: {freq_0_rpm:.2f} ... {freq_1_rpm:.2f} RPM<br>"
2339+
f"Eccentricity: {self.eccentricity[idx_0]:.4f} ... {self.eccentricity[idx_1]:.4f}<br>"
2340+
f"Attitude Angle: {self.attitude_angle[idx_0]:.4f} ... {self.attitude_angle[idx_1]:.4f} rad<br>"
2341+
f"Sommerfeld: {self.sommerfeld[idx_0]:.3e} ... {self.sommerfeld[idx_1]:.3e}<br>"
2342+
f"Kxx: {self.kxx_interpolated(freq_0):.3e} ... {self.kxx_interpolated(freq_1):.3e} N/m<br>"
2343+
f"Kyy: {self.kyy_interpolated(freq_0):.3e} ... {self.kyy_interpolated(freq_1):.3e} N/m<br>"
2344+
f"Cxx: {self.cxx_interpolated(freq_0):.3e} ... {self.cxx_interpolated(freq_1):.3e} N·s/m<br>"
2345+
f"Cyy: {self.cyy_interpolated(freq_0):.3e} ... {self.cyy_interpolated(freq_1):.3e} N·s/m<br>"
2346+
)
2347+
2348+
customdata = [self.n]
2349+
2350+
return customdata, hovertemplate
2351+
20832352

20842353
def bearing_example():
20852354
"""Create an example of bearing element.

ross/disk_element.py

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -436,6 +436,22 @@ def _patch(self, position, fig):
436436

437437
return fig
438438

439+
@staticmethod
440+
def calculate_mass(rho, width, i_d, o_d):
441+
return rho * np.pi * width * (o_d**2 - i_d**2) / 4
442+
443+
@staticmethod
444+
def calculate_Ip(m, i_d, o_d):
445+
return m * (o_d**2 + i_d**2) / 8
446+
447+
@staticmethod
448+
def calculate_Id(Ip, m, width):
449+
return 1 / 2 * Ip + 1 / 12 * m * width**2
450+
451+
@staticmethod
452+
def calculate_width(rho, m, i_d, o_d):
453+
return 4 * m / (rho * np.pi * (o_d**2 - i_d**2))
454+
439455
@classmethod
440456
@check_units
441457
def from_geometry(
@@ -500,11 +516,9 @@ def from_geometry(
500516
>>> disk.Ip
501517
0.32956362089137037
502518
"""
503-
m = material.rho * np.pi * width * (o_d**2 - i_d**2) / 4
504-
Ip = m * (o_d**2 + i_d**2) / 8
505-
Id = 1 / 2 * Ip + 1 / 12 * m * width**2
506-
507-
tag = tag
519+
m = cls.calculate_mass(material.rho, width, i_d, o_d)
520+
Ip = cls.calculate_Ip(m, i_d, o_d)
521+
Id = cls.calculate_Id(Ip, m, width)
508522

509523
return cls(n, m, Id, Ip, tag, scale_factor, color)
510524

0 commit comments

Comments
 (0)