visualdynamics.core.shapes¶
shapes
¶
Mode shapes.
A ShapeSet holds a set of modes over a common set of DOFs: the modal frequency and damping of each mode, and a shape coefficient at each DOF. Shapes may be real (normal modes) or complex (modes of a damped structure, often from a test).
Units follow the same rule as the rest of visualdynamics. Frequency
is in Hz and damping is a fraction of critical, and both are always
known. Shape coefficients are mass-normalized (phi^T M phi = I), so
their unit is 1/sqrt(mass), and a shape normalized against a mass matrix
in kg is a different number from one normalized against a mass matrix in
slinch. Files do not record which unit was used, so the mass unit starts
undefined and is declared with ShapeSet.define_units; the values are
then stored in SI, 1/sqrt(kg).
Because of the half power, this unit cannot be written in the
integer-power dimensions of visualdynamics.units, so a ShapeSet stores
its mass unit directly instead of a dimension expression.
Classes:
| Name | Description |
|---|---|
ScaleComparison |
Whether two shape sets are scaled the same way, and what it means |
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. |
paired_mode |
b's mode j over b's own coordinates, phase-aligned to move with |
pair_scale_note |
One line giving the size of this pair's second shape relative to |
pair_caption |
The caption in the corner of an overlay. |
scale_ratios |
The size of b's mode relative to a's, one value per pair. |
compare_scaling |
|
synthesize_overlay |
The modal model's prediction of |
Classes¶
ScaleComparison
¶
Whether two shape sets are scaled the same way, and what it means if they are not.
An overlay of two mode shapes scales each to its own peak, so a
sparse test set and a dense model are drawn at a comparable size and
the comparison is of shape only. As a result a reader cannot see
that one set is thirty times the size of the other. message gives
that information as a sentence.
There are two cases, because the two kinds of difference look different:
- A constant factor. Every matched pair differing by the same amount is a unit or a normalization convention, not a difference in the structure. Mode shapes scale as one over the square root of mass, so sets normalized in grams and in kilograms differ by a factor of about 31.6.
- A factor that changes from mode to mode. This is two different normalizations, most often unit-normalized shapes (each mode's peak set to 1) beside mass-normalized ones.
ratios is second / first for each matched pair, measured as the
norm over the DOFs the two sets share, so a sparse set and a dense
one are compared over the same DOFs (scale_ratios). first and
second are the two shape sets, used by message to check their
mass units and whether either is unscaled.
Methods:
| Name | Description |
|---|---|
message |
A sentence describing the scale difference, or None when the |
Attributes:
| Name | Type | Description |
|---|---|---|
factor |
float
|
The median ratio of the second set to the first, or NaN when |
spread |
float
|
The largest ratio over the smallest; 1.0 when every pair has |
consistent |
bool
|
Whether the two sets have one scaling factor, whatever it is |
equal |
bool
|
Whether the sets are scaled the same: one factor ( |
Source code in src/visualdynamics/core/shapes.py
Attributes¶
factor
property
¶
The median ratio of the second set to the first, or NaN when there are no pairs.
spread
property
¶
The largest ratio over the smallest; 1.0 when every pair has the same ratio, or when fewer than two ratios are usable.
consistent
property
¶
Whether the two sets have one scaling factor, whatever it is
(spread at most 1.5).
equal
property
¶
Whether the sets are scaled the same: one factor (consistent)
and that factor within 10% of 1.
Methods:¶
message
¶
A sentence describing the scale difference, or None when the sets are scaled the same.
The message describes the difference and does not call it an error, because a scale difference is sometimes intended and the reader knows whether it is. The overlay cannot show it, because it scales each shape to its own peak. An unscaled set, or mass units that differ or are declared on only one set, are reported before the ratios.
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
that the columns correspond to ('101X+'). The modes are always stored
in ascending frequency, so rigid-body modes come first.
A fitted set is also the record of the fit: reopening one in the app (Edit Fit) restores the fitting session that produced it, which is why the descriptions and the scaling flag are stored with the numbers.
Attributes:
frequency: Hz per mode. A rigid-body mode is exactly 0.0, because
the FRF synthesis tests for exactly 0.0 to handle its 0/0; a
very small frequency is not treated the same way.
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 complex modes; the
overlay and the MAC handle either.
modal_mass: Per mode. 1.0 for every mode of a mass-normalized
set, which is what an eigensolution here produces. For
complex shapes it is modal A, the first-order scaling, and
synthesize_frf uses it that way. It is complex when an
imported file had it complex; the values are kept as read and
never converted to real.
modal_damping: Complex modal damping per mode where a file had
one (I-DEAS ADFs do), or None. It is different from
damping, the viscous fraction of critical: it is the
complex-mode estimate as the identification software
reported it.
mass_unit: The mass unit the shapes were normalized against, or
None when it has not been declared.
description: Free text per mode describing the shape, for
example 'first torsion'.
comment: One line per mode; a single string given to the
constructor is used for every mode.
unscaled: True when the fit had no drive point to set the
mass-normalized scale. Shapes and MACs are not affected, but
the modal masses are only a convention, and comparisons do
not report a scale factor from them.
Methods:
| Name | Description |
|---|---|
auto_mac |
The MAC of every mode against every other mode; the diagonal is 1. |
covers |
Whether the shape set has a coefficient at this DOF. |
synthesize_frf |
FRFs from the modal model, one row per DOF pair. |
modal_frf |
Each real mode's own frequency response, one row per mode: |
delete_modes |
Remove the given modes in place. |
define_units |
Declare the mass unit the shapes were normalized against. |
undefine_units |
Remove the mass unit and restore the original coefficients; returns self. |
display_shapes |
The coefficients in the display system's 1/sqrt(mass). |
unit_label |
The unit label of the coefficients, such as '1/√kg'. |
mode_label |
A short label for one mode, such as 'Mode 3 — 12.4 Hz, 2% damping'. |
save |
Write the shape set to a file of its own. |
plot_mac |
Plot the MAC: this set against itself, or against |
animate |
Animate one mode on a geometry, as the app does. |
animate_pair |
Animate this mode and a mode of another set overlaid, as the |
plot |
Plot the set: its auto-MAC, or one mode animated when a |
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. |
units_defined |
bool
|
Whether a mass unit has been declared. Without one, a modal |
Source code in src/visualdynamics/core/shapes.py
Attributes¶
is_complex
property
¶
Whether these are complex modes.
Real normal modes move every DOF in or out of phase; complex modes do not, which is the result of non-proportional damping. A set is complex when its shape matrix has a complex dtype.
units_defined
property
¶
Whether a mass unit has been declared. Without one, a modal mass is a number with no unit.
Methods:¶
auto_mac
¶
covers
¶
Whether the shape set has a coefficient at this DOF.
The sign is ignored, so '101Z-' is covered when '101Z+' is.
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.
For real shapes, the second-order residue form
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. It is exact for mass-normalized shapes, and
modal_mass holds any other scaling. For complex shapes, the
first-order form, with a pole and its conjugate:
H_jk(f) = sum_r (iw)^power [ psi_jr psi_kr / (A_r (iw - l_r))
+ conj(psi_jr psi_kr / A_r) / (iw - conj(l_r)) ]
with the pole l_r = -z_r w_r + i w_r sqrt(1 - z_r^2) and
modal_mass used as modal A, the scaling of a complex mode (1.0
when a set has none). The two forms give the same FRF for a real
mode whose modal A is 2i w_d m_r, where w_d is the damped natural
frequency.
The form is selected by the shapes alone: complex shapes use the
first-order form and real shapes the second-order form. The
second-order form cannot represent a complex mode, because it
gives the conjugate pole the residue -R where the structure has
conj(R), whatever modal_mass is set to. Modal A is conjugated
in the second term, which only matters when modal A is complex.
power selects the response quantity: 0 displacement per force,
1 velocity, 2 acceleration. It is applied inside the sum because
a real rigid-body mode's denominator is exactly -w^2: at w = 0
its accelerance reduces to the finite residue, while multiplying
afterward would give 0/0. Its displacement and velocity at w = 0
are unbounded and are returned as nan.
Both forms are checked to rounding error against the direct
inverse of the dynamic stiffness (proportionally damped for real
shapes, non-proportionally damped for complex ones) and against
the output of sdynpy's synthesis saved as test data
(tests/test_frf_synthesis_exact.py,
tests/test_modal_frf_oracle.py).
modes limits the sum to some modes; a synthesis from a subset
of modes plotted beside the measurement shows which modes the
measurement contains. Raises ValueError for a DOF the shapes do
not cover, which covers can check in advance.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
frequencies
|
array_like
|
The lines to synthesize 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 synthesized FRFs, one row per response and drive pair. |
References
- Ewins, D. J. (2000). Modal Testing: Theory, Practice and Application, 2nd ed. Research Studies Press. The residue form for real modes, and why non-proportional damping makes the modes complex.
- Maia, N. M. M., & Silva, J. M. M. (1997). Theoretical and Experimental Modal Analysis. Research Studies Press. The first-order (state-space) form, its conjugate pole pairs, and modal A as the scaling that goes with them.
Source code in src/visualdynamics/core/shapes.py
567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 | |
modal_frf
¶
Each real mode's own frequency response, one row per mode:
H_r(f) = (iw)^power / (m_r (w_r^2 - w^2 + 2i z_r w_r w))
with w = 2pif. It is the modal coordinate's response to a unit
modal force, and the term synthesize_frf sums over the modes,
so a physical FRF is phi_jr phi_kr H_r summed over r. power
goes inside, as there: a rigid mode's accelerance at 0 Hz is
1/m_r, and its displacement and velocity there are nan.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
frequencies
|
array_like
|
The lines, in Hz. |
required |
modes
|
sequence of int
|
Which modes, by index from zero, in increasing order; all of them when omitted. |
None
|
power
|
int
|
0 displacement, 1 velocity, 2 acceleration per modal force. |
0
|
Returns:
| Type | Description |
|---|---|
ndarray
|
(modes, lines), complex. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If the set is complex: a complex mode has no second-order modal coordinate. |
Source code in src/visualdynamics/core/shapes.py
delete_modes
¶
Remove the given modes in place.
Raises IndexError for an index out of range, and ValueError if every mode would be removed (delete the object instead).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
indices
|
sequence of int
|
Which modes to remove, by zero-based index. |
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.
The coefficients are converted to SI, 1/sqrt(kg). Declaring again reinterprets the original values instead of scaling twice, so a wrong unit can be corrected.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
mass_unit
|
str
|
The mass unit of the mass matrix the shapes were normalized against, such as 'kg'. |
required |
Returns:
| Type | Description |
|---|---|
ShapeSet
|
Self, converted to SI in place. |
Source code in src/visualdynamics/core/shapes.py
undefine_units
¶
undefine_units() -> ShapeSet
Remove the mass unit and restore the original coefficients; returns self.
Source code in src/visualdynamics/core/shapes.py
display_shapes
¶
display_shapes(unit_system: UnitSystem) -> ndarray
The coefficients in the display system's 1/sqrt(mass).
A set without a declared mass unit is returned 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
The unit label of the coefficients, such as '1/√kg'.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
unit_system
|
UnitSystem
|
The units to label in. The declared mass unit when omitted. |
None
|
Returns:
| Type | Description |
|---|---|
str
|
The label, or '' when no mass unit has been declared. |
Source code in src/visualdynamics/core/shapes.py
mode_label
¶
A short label for one mode, such as 'Mode 3 — 12.4 Hz, 2% damping'.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
i
|
int
|
Which mode, by zero-based index. |
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
Plot the MAC: this set against itself, or against other.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
other
|
ShapeSet
|
The set to compare against. When omitted, the plot is the auto-MAC, which shows repeated or similar modes. |
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
Animate one mode on a geometry, as the app does.
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
animate_pair
¶
animate_pair(geometry: Geometry, mode: int, other: ShapeSet, other_mode: int, other_geometry: Geometry | None = None, *, on_basis: bool = False, projected: ShapeSet | None = None, names: tuple[str, str] = ('first', 'second'), **kwargs: Any) -> Any
Animate this mode and a mode of another set overlaid, as the window does for a picked cell of the two sets' cross-MAC.
The other mode is phase-aligned to this one (paired_mode) and
each is scaled to its own peak, so the animation compares shape.
The caption names both modes and gives the ratio of their sizes
(pair_caption). When the sets are on two geometries, the other
set is projected onto this one's DOFs to measure the alignment
(correlate.project_shapes) and drawn on its own geometry, or,
with on_basis, the projection is drawn on this geometry.
Raises ValueError when either set has no DOFs on its geometry.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
geometry
|
Geometry
|
This set's geometry. |
required |
mode
|
int
|
This set's mode, by index. |
required |
other
|
ShapeSet
|
The set compared against. |
required |
other_mode
|
int
|
Its mode, by index. |
required |
other_geometry
|
Geometry
|
The other set's geometry; this one's when omitted. |
None
|
on_basis
|
bool
|
Draw the other set's projection on this geometry instead. |
False
|
projected
|
ShapeSet
|
The other set already projected onto this set's DOFs
( |
None
|
names
|
tuple of str
|
The names the caption uses for the two sets, by default ('first', 'second'). |
('first', 'second')
|
**kwargs
|
Any
|
Passed through to |
{}
|
Returns:
| Type | Description |
|---|---|
object
|
The plotter, the still, or the movie's path. |
Source code in src/visualdynamics/core/shapes.py
978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 | |
plot
¶
plot(geometry: Geometry | None = None, mode: int = 0, **kwargs: Any) -> Any
Plot the set: its auto-MAC, or one mode animated when a geometry is given.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
geometry
|
Geometry
|
The geometry to draw on. Without it the auto-MAC is plotted. |
None
|
mode
|
int
|
Which mode, when a geometry is given. |
0
|
**kwargs
|
Any
|
Passed through to |
{}
|
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)), which is 1 for the same shape at any scaling and near 0 for independent shapes. Each matrix has one shape per row. With one argument it is the auto-MAC, whose off-diagonal terms show 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 do not have to cover the same DOFs or list them in the same
order; each shape is limited to the shared DOFs before the
comparison. Rows are a's modes and columns are b's. Raises
ValueError when the sets share no DOFs.
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 only defined up to its sign, and a complex mode up to a rotation. The factor that removes the difference is computed from the phase of the inner product on the shared DOFs (the same inner product as in the MAC numerator). It is 1.0 when the sets share no DOFs or the inner product is zero. It is useful by itself when the factor is measured between two comparable sets (a set and a projection) and has to be applied to a third (the dense set the projection was made from).
Source code in src/visualdynamics/core/shapes.py
apply_alignment
¶
shape times the alignment factor, kept real if shape was real
and the factor has no imaginary part.
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
(alignment_factor applied to it).
Source code in src/visualdynamics/core/shapes.py
paired_mode
¶
b's mode j over b's own coordinates, phase-aligned to move with a's mode i when the two are overlaid.
Sets on one geometry share DOFs and are aligned directly. Sets on two
geometries share none, so the alignment is measured against
projected (b projected onto a's DOFs by correlate.project_shapes)
and applied to b's original shape. The window's overlay, the
report's overlay and ShapeSet.animate_pair all use this function.
Source code in src/visualdynamics/core/shapes.py
pair_scale_note
¶
One line giving the size of this pair's second shape relative to
its first, as in 'Test/Model = 1.02', or None when it cannot be
computed.
An overlay scales each shape to its own peak, which makes a sparse test set and a dense model comparable but also hides a set that is thirty times the size of the other. This line gives the number the overlay cannot show.
It is for the pair on screen, not for the whole sets. A constant
factor across every mode is a unit or a normalization convention,
but a factor that is 1.00 everywhere and 3.4 on one mode means that
mode was fitted poorly, and a figure averaged over the set would hide
that. ScaleComparison.message describes the whole set and is what
the report uses.
The sets are named by a_name and b_name rather than as first and
second, so a reader looking at two animations can tell which one is
larger. A set that is unscaled is named in parentheses after the
ratio, since its size has no physical meaning.
Source code in src/visualdynamics/core/shapes.py
pair_caption
¶
The caption in the corner of an overlay.
One line for each mode (set name, mode number, description,
frequency in Hz and damping in percent), then the scale note from
pair_scale_note when there is one.
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]
The size of b's mode relative to a's, one value per pair.
Each ratio is the norm of b's mode over the norm of a's, both limited to the DOFs the two sets share; otherwise a sparse test set and a dense model would differ by the square root of the ratio of their DOF counts.
pairs is a list of (a's mode, b's mode). Without it, the modes are
paired in order up to the length of the shorter set.
The list has one entry per pair, in order, with None where there is no value: a mode index past the end of its set, a mode that is zero over the shared DOFs, or two sets that share no DOFs. The entries are not filtered out, because a table puts them in rows beside the pairs they belong to, and a shorter list would put values in the wrong rows.
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 pairs that have a ratio in scale_ratios are summarized as one
factor and a spread. See scale_ratios for how a ratio is computed
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 plot over it.
The result has the same DOF pairs and frequencies as frf, computed
with ShapeSet.synthesize_frf over modes (all of them by default),
for the records in records (all by default). Each record copies its
original's dimension and units, so both are plotted on one axis, and
is computed for the original's quantity by powers of iω; accelerance
is used when the measurement's quantity is not known, as is usual in
a modal test. Records at DOFs the shapes do not cover are left out,
and the result is None when that is every record or frf has no
reference DOFs.
The result has synthesized set to True (it is drawn dashed) and
mode_frequencies (the frequencies marked on the fitting screen), so
a plot can tell it from a measurement.