visualdynamics.core.shapes¶
shapes
¶
Mode shapes.
A ShapeSet holds a set of modes over a common set of DOFs: modal frequency and damping per mode, plus a shape coefficient per DOF. Shapes may be real (normal modes) or complex (damped/experimental modes).
Units follow the same rule as the rest of visualdynamics. Frequency is Hz and damping is a fraction of critical, both always known. Shape coefficients are mass-normalized (phi^T M phi = I), so they carry units of 1/sqrt(mass) — a shape normalized against a kg mass matrix is not the same number as one normalized against slinch. Files do not record which was used, so the mass unit starts undefined and the user declares it; values are then stored in SI, 1/sqrt(kg).
The half power puts this outside the integer-power dimension algebra in visualdynamics.units, so a ShapeSet carries its mass unit directly rather than a dimension expression.
Classes:
| Name | Description |
|---|---|
ScaleComparison |
Whether two shape sets are scaled alike, and what it means if not. |
ShapeSet |
Mode shapes over a shared set of DOFs. |
Functions:
| Name | Description |
|---|---|
mac_matrix |
The Modal Assurance Criterion between two shape matrices. |
cross_mac |
MAC between two shape sets, on the DOFs they share. |
alignment_factor |
The unit factor that phase-aligns b's mode j with a's mode i. |
apply_alignment |
|
aligned_mode |
b's mode j, phase-aligned to move with a's mode i. |
scale_ratios |
How much bigger b's mode is than a's, one answer per pair. |
compare_scaling |
|
synthesize_overlay |
The modal model's prediction of |
Classes¶
ScaleComparison
¶
Whether two shape sets are scaled alike, and what it means if not.
Overlaying two mode shapes scales each to its own peak, so that a sparse test set and a dense model swing comparably and the comparison is one of shape. That is what overlaying is for — and it also means a reader cannot see that one set is thirty times the other, which is exactly the thing they would want to know. This is the sentence that says so.
Two readings, because the two faults look different:
- a constant factor. Every matched pair off by the same amount is a unit or a normalization convention, not a difference in the structure. Mode shapes go as one over the square root of mass, so a set normalized in grams sits about 31.6 times one normalized in kilograms.
- a factor that changes mode by mode. That is not one scaling applied wrongly, it is two different normalizations — unit- normalized shapes (each mode's peak set to 1) beside mass-normalized ones being the usual pair.
ratios is second / first per matched pair, measured as the norm
over the DOFs the two sets share so that a sparse set and a dense
one are compared on the same footing.
Methods:
| Name | Description |
|---|---|
message |
What to tell the reader, or None when there is nothing to say. |
Attributes:
| Name | Type | Description |
|---|---|---|
factor |
float
|
How much bigger the second set is, typically. NaN with no |
spread |
float
|
Largest ratio over smallest: 1.0 is perfectly consistent. |
consistent |
bool
|
Do the two sets carry one scaling, whatever it is? |
equal |
bool
|
Are they scaled alike — same convention and same size? |
Source code in src/visualdynamics/core/shapes.py
Attributes¶
factor
property
¶
How much bigger the second set is, typically. NaN with no pairs to measure.
Methods:¶
message
¶
What to tell the reader, or None when there is nothing to say.
Deliberately a description and not an accusation: a scale difference is sometimes exactly what was intended, and the person reading knows which case they are in. What they cannot do is see it, because the overlay has normalized it away.
Source code in src/visualdynamics/core/shapes.py
ShapeSet
¶
ShapeSet(
frequency: ArrayLike,
damping: ArrayLike,
coordinate: Sequence[str],
shape_matrix: ArrayLike,
modal_mass: ArrayLike | None = None,
comment: str | Sequence[str] | None = None,
mass_unit: str | None = None,
description: Sequence[str] | None = None,
unscaled: bool = False,
modal_damping: ArrayLike | None = None,
)
Mode shapes over a shared set of DOFs.
shape_matrix is (modes, dofs). coordinate lists the DOF strings the
columns correspond to ('101X+').
A fitted set is also the record of the fit: reopening one in the app (Edit Fit) reconstructs the session that produced it, which is why the description and the scaling flag ride along with the numbers.
Attributes:
frequency: Hz per mode. A rigid-body mode is exactly 0.0 — the
FRF synthesis cancels its 0/0 by testing for that, so 'very
small' is not the same thing.
damping: Fraction of critical per mode, so 2% is 0.02.
coordinate: The DOF string of each column of shape_matrix.
shape_matrix: (modes, dofs). Complex for a complex mode; the
overlay and MAC machinery handles either.
modal_mass: Per mode. 1.0 throughout for a mass-normalized set,
which is what an eigensolution here produces. Complex when
an imported source carried complex modal mass — kept as
measured, never squeezed real.
modal_damping: Complex modal damping per mode where a source
carried one (I-DEAS ADFs do), or None. Distinct from
damping, the viscous fraction of critical: this is the
complex-mode estimate as the identifying tool reported it.
mass_unit: What modal_mass is in, or None when undeclared.
description: Free text per mode — what the shape is, filled in
while reading the table ('first torsion').
comment: One line about the set as a whole.
unscaled: True when the fit had no drive point to pin the
mass-normalized scale. Shapes and MACs are unaffected;
modal masses are then a convention rather than physics, and
comparisons refuse to read a scale factor out of them.
Methods:
| Name | Description |
|---|---|
auto_mac |
MAC of every mode against every other; the diagonal is 1. |
covers |
Does the shape set have a coefficient at this DOF? |
synthesize_frf |
FRFs from the modal model, one row per DOF pair. |
delete_modes |
Remove the given modes in place; the last one is refused. |
define_units |
Declare the mass unit the shapes were normalized against. |
undefine_units |
Take the declaration back, restoring the file's raw coefficients. |
display_shapes |
Coefficients in the display system's 1/sqrt(mass); undefined pass |
unit_label |
'1/√kg' for the stored unit, or the display system's. |
mode_label |
'Mode 3 — 12.4 Hz, 2.0% damping'. |
save |
Write the shape set to a file of its own. |
plot_mac |
The MAC grid: this set against itself, or against |
animate |
This mode moving on a geometry, as the GUI animates it. |
plot |
The set's own reading: its auto-MAC, or one mode animated |
Attributes:
| Name | Type | Description |
|---|---|---|
num_shapes |
int
|
How many mode shapes the set holds. |
num_dofs |
int
|
How many degrees of freedom each shape covers. |
is_complex |
bool
|
Whether these are complex modes. Real normal modes move |
units_defined |
bool
|
Whether the shapes carry a mass unit, without which a |
Source code in src/visualdynamics/core/shapes.py
Attributes¶
is_complex
property
¶
Whether these are complex modes. Real normal modes move every DOF in phase; complex ones do not, which is what a damped or non-proportionally damped structure produces.
units_defined
property
¶
Whether the shapes carry a mass unit, without which a modal mass is a number with no scale behind it.
Methods:¶
auto_mac
¶
covers
¶
Does the shape set have a coefficient at this DOF?
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
dof
|
str
|
A degree of freedom, such as '101Z+'. |
required |
Returns:
| Type | Description |
|---|---|
bool
|
Whether the shapes include it. |
Source code in src/visualdynamics/core/shapes.py
synthesize_frf
¶
synthesize_frf(
frequencies: ArrayLike,
response_dof: Sequence[str],
reference_dof: Sequence[str],
modes: Sequence[int] | None = None,
power: int = 0,
) -> ndarray
FRFs from the modal model, one row per DOF pair.
H_jk(f) = sum_r (iw)^power phi_jr phi_kr
/ (m_r (w_r^2 - w^2 + 2i z_r w_r w))
with w = 2pif — the residue form for mass-normalized shapes, with
modal_mass carrying any other scaling. power picks the response
quantity: 0 displacement per force, 1 velocity, 2 acceleration. It
applies inside the sum because a rigid-body mode's denominator is
exactly -w^2: at w = 0 its accelerance cancels to the finite
residue, where an after-the-fact multiply is 0/0 and a screenful
of warnings. Its displacement and velocity there are genuinely
unbounded and come back as nan.
modes restricts the sum; a truncated synthesis beside the
measurement is what shows which modes the measurement actually
contains. Raises ValueError for a DOF the shapes do not cover;
covers says so in advance.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
frequencies
|
array_like
|
The lines to synthesise at, in Hz. |
required |
response_dof
|
sequence of str
|
The response degrees of freedom. |
required |
reference_dof
|
sequence of str
|
The drive degrees of freedom. |
required |
modes
|
sequence of int
|
Which modes to include. All of them when omitted. |
None
|
power
|
int
|
0 receptance, 1 mobility, 2 accelerance. |
0
|
Returns:
| Type | Description |
|---|---|
ndarray
|
The synthesised FRFs, one row per response and drive pair. |
Source code in src/visualdynamics/core/shapes.py
delete_modes
¶
Remove the given modes in place; the last one is refused.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
indices
|
sequence of int
|
Which modes to remove. |
required |
Returns:
| Type | Description |
|---|---|
None
|
|
Source code in src/visualdynamics/core/shapes.py
define_units
¶
define_units(mass_unit: str) -> ShapeSet
Declare the mass unit the shapes were normalized against.
Re-declaring reinterprets the file's values rather than scaling twice, so a wrong guess can be corrected.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
mass_unit
|
str
|
The unit modal mass is in. |
required |
Returns:
| Type | Description |
|---|---|
ShapeSet
|
Self, converted to SI in place. |
Source code in src/visualdynamics/core/shapes.py
undefine_units
¶
undefine_units() -> ShapeSet
Take the declaration back, restoring the file's raw coefficients.
Source code in src/visualdynamics/core/shapes.py
display_shapes
¶
display_shapes(unit_system: UnitSystem) -> ndarray
Coefficients in the display system's 1/sqrt(mass); undefined pass through unchanged.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
unit_system
|
UnitSystem
|
The units to present in. |
required |
Returns:
| Type | Description |
|---|---|
ndarray
|
The shape matrix in display units. |
Source code in src/visualdynamics/core/shapes.py
unit_label
¶
unit_label(unit_system: UnitSystem | None = None) -> str
'1/√kg' for the stored unit, or the display system's.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
unit_system
|
UnitSystem
|
Units to label in. |
None
|
Returns:
| Type | Description |
|---|---|
str
|
How the shapes' own unit reads. |
Source code in src/visualdynamics/core/shapes.py
mode_label
¶
'Mode 3 — 12.4 Hz, 2.0% damping'.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
i
|
int
|
Which mode. |
required |
Returns:
| Type | Description |
|---|---|
str
|
A short label: its frequency, and its damping when known. |
Source code in src/visualdynamics/core/shapes.py
save
¶
Write the shape set to a file of its own.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
path
|
str or PathLike
|
Where to write it. |
required |
Returns:
| Type | Description |
|---|---|
None
|
|
Source code in src/visualdynamics/core/shapes.py
plot_mac
¶
plot_mac(
other: ShapeSet | None = None, **kwargs: Any
) -> Any
The MAC grid: this set against itself, or against other.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
other
|
ShapeSet
|
The set to compare against. This set against itself when omitted, which is how repeated modes show up. |
None
|
**kwargs
|
Any
|
Passed through to the plotting layer. |
{}
|
Returns:
| Type | Description |
|---|---|
object
|
The plot widget. |
Source code in src/visualdynamics/core/shapes.py
animate
¶
animate(
geometry: Geometry, mode: int = 0, **kwargs: Any
) -> Any
This mode moving on a geometry, as the GUI animates it.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
geometry
|
Geometry
|
The geometry to move. |
required |
mode
|
int
|
Which mode, by index. |
0
|
**kwargs
|
Any
|
Passed through to the scene. |
{}
|
Returns:
| Type | Description |
|---|---|
object
|
The plot widget or plotter. |
Source code in src/visualdynamics/core/shapes.py
plot
¶
plot(
geometry: Geometry | None = None,
mode: int = 0,
**kwargs: Any,
) -> Any
The set's own reading: its auto-MAC, or one mode animated when a geometry says where to put it.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
geometry
|
Geometry
|
The geometry to draw on. |
None
|
mode
|
int
|
Which mode. |
0
|
**kwargs
|
Any
|
Passed through to the scene. |
{}
|
Returns:
| Type | Description |
|---|---|
object
|
The plot widget or plotter. |
Source code in src/visualdynamics/core/shapes.py
Functions:¶
mac_matrix
¶
The Modal Assurance Criterion between two shape matrices.
MAC_ij = |phi_i^H psi_j|^2 / ((phi_i^H phi_i)(psi_j^H psi_j)) — 1 for the same shape in any scaling, near 0 for independent ones. With one argument it is the auto-MAC, whose off-diagonals say how distinct the set's own modes are.
Source code in src/visualdynamics/core/shapes.py
cross_mac
¶
MAC between two shape sets, on the DOFs they share.
The sets need not cover the same DOFs or list them in the same order;
each shape is restricted to the shared set before comparing. Rows are
a's modes, columns are b's. Raises when nothing is shared —
a MAC over no DOFs is not a number.
Source code in src/visualdynamics/core/shapes.py
alignment_factor
¶
The unit factor that phase-aligns b's mode j with a's mode i.
A real normal mode is defined up to sign and a complex one up to a rotation; the factor undoing that is the phase of the inner product on the shared DOFs — the MAC numerator's own. 1.0 when the sets share nothing (there is no alignment to speak of). Useful on its own when the factor, measured between comparable sets (a set and a projection), must be applied to a third (the projection's dense original).
Source code in src/visualdynamics/core/shapes.py
apply_alignment
¶
shape times the alignment factor, kept real when it was.
Source code in src/visualdynamics/core/shapes.py
aligned_mode
¶
b's mode j, phase-aligned to move with a's mode i.
Returns the full shape vector over b's coordinates.
Source code in src/visualdynamics/core/shapes.py
scale_ratios
¶
scale_ratios(
a: ShapeSet,
b: ShapeSet,
pairs: Sequence[Sequence[int]] | None = None,
) -> list[float | None]
How much bigger b's mode is than a's, one answer per pair.
Each ratio is the norm of b's mode over the norm of a's, both restricted to the DOFs the two sets share — a sparse test set and a dense model would otherwise differ by the square root of the DOF count and nothing else.
pairs is [(a's mode, b's mode)]; without it the modes are taken in
order, as far as the shorter set goes.
One entry per pair given, in order, None where there is no
answer: a mode index past the end of its set, a mode that is zero
over the shared DOFs (which is a mode that set does not see there),
or two sets sharing no DOFs at all. Aligned rather than filtered
because a table puts these in rows beside the pairs that produced
them, and a shorter list would silently slide up the column.
Source code in src/visualdynamics/core/shapes.py
compare_scaling
¶
compare_scaling(
a: ShapeSet,
b: ShapeSet,
pairs: Sequence[Sequence[int]] | None = None,
) -> ScaleComparison
ScaleComparison of two shape sets over their matched pairs.
The set-wide reading of scale_ratios: the pairs that have an
answer, summarised into one factor and a spread. See that function
for what a ratio is and when there is none.
Source code in src/visualdynamics/core/shapes.py
synthesize_overlay
¶
synthesize_overlay(
shapes: ShapeSet,
frf: Any,
records: Sequence[int] | None = None,
modes: Sequence[int] | None = None,
) -> Any
The modal model's prediction of frf, as an Frf to draw over it.
Same DOF pairs, same frequencies, residue form over modes (all of
them by default). Each record copies its original's dimension and
units, so the pair lands on one axis, and is scaled to the
original's quantity by powers of iω — accelerance assumed when
nothing said what the measurement is, the modal-test norm. Records
at DOFs the shapes do not cover are left out; None when that is all
of them.
The result carries synthesized (drawn dashed) and
mode_frequencies (the fitting screen's bookmarks), so a plot can
tell it from the measurement wherever it is handed one.