"""Three-dimensional complex mode-shape plotting.
A complex mode shape carries both magnitude and phase per degree of freedom. This
view renders the most active degrees of freedom as phasors in three dimensions:
the horizontal axis orders the degrees of freedom by participation while the other
two axes are the real and imaginary parts of each component, so the spread out of
the real plane shows how non-proportional (complex) the mode is.
"""
from __future__ import annotations
from typing import TYPE_CHECKING
import numpy as np
import plotly.graph_objects as go
if TYPE_CHECKING:
from vane.modal.eigensolver import ModalSolution
__all__ = ["plot_mode_3d"]
[docs]
def plot_mode_3d(
solution: ModalSolution, mode_index: int, *, top_n: int = 12
) -> go.Figure:
"""Plot a mode's most active degrees of freedom as 3-D complex phasors.
Parameters
----------
solution : ModalSolution
A solution whose ``dof_descriptions`` are populated.
mode_index : int
Index of the mode to plot.
top_n : int, optional
Number of highest-magnitude degrees of freedom to show.
Returns
-------
plotly.graph_objects.Figure
The 3-D phasor figure.
Raises
------
ValueError
If the solution has no DOF descriptions, ``mode_index`` is out of range, or
``top_n`` is not positive.
"""
if not solution.dof_descriptions:
msg = "ModalSolution has no dof_descriptions; cannot plot mode shape"
raise ValueError(msg)
if not 0 <= mode_index < solution.n_modes:
msg = f"mode_index {mode_index} out of range [0, {solution.n_modes})"
raise ValueError(msg)
if top_n < 1:
msg = f"top_n must be at least 1, got {top_n}"
raise ValueError(msg)
shape = solution.mode_shapes[:, mode_index]
order = np.argsort(np.abs(shape))[::-1][:top_n]
figure = go.Figure()
for rank, dof in enumerate(order.tolist()):
component = shape[dof]
figure.add_trace(
go.Scatter3d(
x=[float(rank), float(rank)],
y=[0.0, float(component.real)],
z=[0.0, float(component.imag)],
mode="lines+markers",
name=solution.dof_descriptions[dof],
)
)
figure.update_layout(
title=f"Mode {mode_index} - "
f"{solution.natural_frequencies_hz[mode_index]:.3f} Hz",
scene={
"xaxis_title": "DOF (by participation)",
"yaxis_title": "Real",
"zaxis_title": "Imaginary",
},
)
return figure