visualdynamics.core.transform¶
transform
¶
Transform physical responses through a shape set to modal responses, and back.
The relationship is u = Φq. Physical to modal is q = Φ⁺u, the
least-squares fit over the DOFs the data and the set share; modal to
physical is u = Φq, exact by definition. Any shape set can be used: a
fitted set, an eigensolution, or the six rigid-body shapes of a
geometry, which gives the virtual point transformation of the
substructuring literature with no special case in the code.
Each kind of data goes through the same two rules, in the form its kind
needs. A time history or a spectrum is rows, one per channel, and is
transformed as q = Φ⁺u line by line, complex where the rows are. A
cross-spectral matrix is a quadratic form, S_qq = Φ⁺ S_uu Φ⁺ᴴ, so it
needs every cross term between the shared channels: the phase between
two channels is what tells a translation from a rotation. A set of
autospectra alone is refused, not completed with assumed cross terms;
the user computes or defines the cross terms when they are needed. An
FRF is a matrix from references to responses. Its response rows
transform through Φ⁺ like any response, and its reference columns
through the force rule, f = (Φᵀ)⁺ f_m, when the set covers the
drives, which gives the virtual point's FRF in both halves. References
the set does not cover stay physical, and the report says so. A shock
response spectrum (a maximum), a coherence (a ratio) and a sine
level (read tone by tone beside a noise floor) do not transform at
all, and the refusal says to transform the time history and compute
them again.
Records are matched to shape columns by DOF, within each quantity, with
the sign honored: a 101Z- channel against a 101Z+ column is
negated. Every shape column is a displacement today.
Each record's quantity sets how it transforms. Motions (acceleration,
velocity, displacement) transform as responses, q = Φ⁺u, each
quantity separately with its own unit. Forces transform the other way:
a force is work-conjugate to a motion, so the modal force is Φᵀf,
with no pseudo-inverse and no rank condition. Every other quantity
(temperature, voltage, pressure, strain) is left out and reported,
since a displacement shape set says nothing about it. A record whose
units are not defined is left out and reported too, since its values
are raw numbers.
The unit rule is [q] = [u]/[Φ]. Unit rigid-body shapes have
dimensionless translations and length-per-radian rotations, so the
translational modal responses keep the data's quantity and the
rotational ones come out per radian (rad/s², or a moment for a force).
A mass-normalized set is in 1/√kg, so its modal responses carry a half
power of mass, the modal_* dimensions. Any other set with no declared
mass unit gives modal responses with no unit, hinted with the modal_*
dimension for Define Units to declare. The expansion applies the same
rule the other way, so a round trip returns the original quantity and
unit.
A specification's bands carry through when every channel has the same band. The transform is linear, so a band that is the same number of decibels on every channel at a line is the same on every modal channel there. Bands that differ between channels have no single answer, and the transform refuses them.
A modal coordinate is named M and the mode's 1-based index (M1 …
M6 for a rigid set, M1 … M22 for a fitted one), one rule for
every set (validate.modal_coordinate). The names are visibly not
nodes and do not change when a set is renamed; which set a coordinate
belongs to is in the object's provenance and in each record's comment,
which names the mode as the set describes it.
Classes:
| Name | Description |
|---|---|
TransformReport |
What a transform used, left out, and left unexplained. |
MatrixAt |
A shape set's matrix at a choice of DOFs, in one of |
Functions:
| Name | Description |
|---|---|
modal_dofs |
The DOF names a set's modal coordinates take: |
reads_as |
Which way a transform of |
modal_records |
The records of |
expands |
Whether |
to_modal |
Transform physical responses through the set into modal |
to_physical |
Expand modal responses back through the set, |
matrix_at |
A shape set's matrix at |
carried_modes |
The modal DOFs an expansion carried, in mode order, or an empty |
Classes¶
TransformReport
dataclass
¶
TransformReport(shared: dict[str, list[str]] = dict(), dropped: list[str] = list(), skipped: dict[str, int] = dict(), rank: dict[str, tuple[int, int]] = dict(), residual: dict[str, float] = dict(), notes: list[str] = list())
What a transform used, left out, and left unexplained.
Attributes:
| Name | Type | Description |
|---|---|---|
shared |
dict of str to list of str
|
The DOFs matched to shape columns, per quantity. |
dropped |
list of str
|
The DOFs of transformable quantities the set has no column for, such as a shaker's synthetic drive DOF or a node the set never covered. |
skipped |
dict of str to int
|
How many records of each quantity that does not transform were
left out ( |
rank |
dict of str to (int, int)
|
Per motion quantity, the rank of the shared shape matrix and the number of modes. |
residual |
dict of str to float
|
Per shared motion DOF, the fraction of its RMS (or of its power, for a density) the modes do not explain; 0 when the fit is exact. |
notes |
list of str
|
Anything else worth a sentence, such as references kept physical or cross-quantity terms left out. |
Methods:
| Name | Description |
|---|---|
describe |
One line saying what was shared, dropped, skipped and left |
Attributes¶
worst_residual
property
¶
The largest per-DOF residual fraction, or 0 with none.
Methods:¶
describe
¶
One line saying what was shared, dropped, skipped and left unexplained, worded as the status line shows it.
Source code in src/visualdynamics/core/transform.py
MatrixAt
dataclass
¶
MatrixAt(form: str, rows: list[str], columns: list[str], values: ndarray, missing: list[str] = list())
A shape set's matrix at a choice of DOFs, in one of FORMS.
Attributes:
| Name | Type | Description |
|---|---|---|
form |
str
|
'shapes', 'response' or 'reference'. |
rows, columns |
list of str
|
The labels: DOFs down and modes across for the shapes, modes down and DOFs across for the two transformations. |
values |
ndarray
|
|
missing |
list of str
|
The DOFs asked for that the set has no column for. |
Functions:¶
modal_dofs
¶
modal_dofs(shapes: ShapeSet) -> list[str]
The DOF names a set's modal coordinates take: M1 … Mn.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
shapes
|
ShapeSet
|
The set. |
required |
Returns:
| Type | Description |
|---|---|
list of str
|
One DOF string per mode, in mode order. |
Source code in src/visualdynamics/core/transform.py
reads_as
¶
Which way a transform of data through shapes would go.
'physical' when any of the data's DOFs is on the set's
coordinates, 'modal' when any is one of the set's modal names, and
None when neither, or when the kind of data does not transform at
all.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data
|
DataArray
|
The object to read. |
required |
shapes
|
ShapeSet
|
The set to read it against. |
required |
records
|
sequence of int
|
Read only these records' DOFs; every record's when omitted. |
None
|
Returns:
| Type | Description |
|---|---|
str or None
|
Which way a transform would go. |
Source code in src/visualdynamics/core/transform.py
modal_records
¶
The records of data at the set's modal DOFs.
A time history can hold physical channels and modal coordinates
side by side: the forces of a test merged with the modal responses
computed from them (Project.compute_modal_responses, then
Project.merge).
Such a record reads as physical as a whole; these are the records
an expansion of it reads. Only records that stand alone (a time
history, a spectrum) are counted: a density's or an FRF's records
are terms of a matrix and are expanded together or not at all.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data
|
DataArray
|
The object to read. |
required |
shapes
|
ShapeSet
|
The set to read it against. |
required |
Returns:
| Type | Description |
|---|---|
list of int
|
The record indices, in order; empty when there are none, or when the kind of data does not transform this way. |
Source code in src/visualdynamics/core/transform.py
expands
¶
Whether to_physical can expand data through shapes: it
reads as modal, or it holds modal records beside physical ones
(modal_records).
Modal forces do not expand (many force distributions give one modal force), so a record holding only those is not expandable.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data
|
DataArray
|
The object to read. |
required |
shapes
|
ShapeSet
|
The set to read it against. |
required |
Returns:
| Type | Description |
|---|---|
bool
|
True when there is something modal to expand. |
Source code in src/visualdynamics/core/transform.py
to_modal
¶
to_modal(data: DataArray, shapes: ShapeSet, records: Sequence[int] | None = None) -> tuple[DataArray, TransformReport]
Transform physical responses through the set into modal
responses at the set's modal DOFs M1 … Mn.
The result takes the form the data's kind needs: rows for a time history or spectrum, the matrix for a density or specification, and both halves for an FRF. A time history's averaging frames and shock windows are carried onto the result.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data
|
DataArray
|
The physical record. Motions are fitted, forces projected, anything else left out and reported. A density needs every cross term between the shared channels; an SRS, a coherence or a sine level does not transform at all. |
required |
shapes
|
ShapeSet
|
The set to transform through. Complex sets are refused: real data cannot carry a complex modal coordinate. |
required |
records
|
sequence of int
|
Which records to carry through, the channels picked in the tree. All of them when omitted. |
None
|
Returns:
| Type | Description |
|---|---|
tuple of (DataArray, TransformReport)
|
The modal responses, and what was shared, dropped and left
unexplained. A density or FRF comes back as the source's own
class; a time history or spectrum as a |
Raises:
| Type | Description |
|---|---|
ValueError
|
When the kind of data does not transform, the set is complex or has no modes, the shared DOFs of a motion quantity resolve fewer modes than the set has, cross terms or FRF pairs are missing, the bands differ between channels, or no motion or force record is on a DOF the shapes cover. |
Source code in src/visualdynamics/core/transform.py
to_physical
¶
to_physical(data: DataArray, shapes: ShapeSet, records: Sequence[int] | None = None) -> tuple[DataArray, TransformReport]
Expand modal responses back through the set, u = Φq, at every
DOF the set covers.
The result is summed over the modes present, in the form the data's kind takes. With every mode it is the motion of the structure; with a few (the records picked in the tree) it is those modes' contribution to it, as in a modal contribution plot. The result's comments say which modes it carries. Modal forces are left out, because many force distributions give the same modal force.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data
|
DataArray
|
The modal record, its DOFs the set's modal names. |
required |
shapes
|
ShapeSet
|
The set the record was transformed through. |
required |
records
|
sequence of int
|
Which modal records to expand, the modes picked in the tree.
All of them when omitted; in a record that holds physical
channels too ( |
None
|
Returns:
| Type | Description |
|---|---|
tuple of (DataArray, TransformReport)
|
The physical responses, and which modal DOFs were used. |
Raises:
| Type | Description |
|---|---|
ValueError
|
When the kind of data does not transform, the set is complex or has no modes, the record's DOFs are not this set's modal coordinates, cross terms or FRF pairs are missing, the bands differ between modal channels, or no motion record is at one of the set's modal DOFs. |
Source code in src/visualdynamics/core/transform.py
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 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 | |
matrix_at
¶
A shape set's matrix at dofs, as the transform uses it.
'shapes' is Φ at the DOFs, a row per DOF and a column per mode:
how each modal coordinate moves each DOF, u = Φq, each value as
stored, complex where the set is. 'response' is Φ⁺, a row per mode
and a column per DOF, what to_modal applies to motions,
q = Φ⁺u. 'reference' is Φᵀ, what it applies to forces,
f_m = Φᵀf, and what a controller applies to its drive signals.
The names are the roles a controller gives the two matrices; what
the transform applies follows a channel's quantity, not its role, so
a motion reference (a base-excitation accelerometer) transforms by
the response transformation and a force response by the reference
one. The two transformations are over the real shapes, as the
transform takes them, and the pseudo-inverse is refused when the
DOFs resolve fewer modes than the set has.
A DOF is matched to the set's column as the transform matches a
channel, its sign honored: 2X- is the negative of the set's 2X+.
A DOF the set has no column for is kept, its row or column NaN, and
listed in missing. With dofs left out, every DOF of the set.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
shapes
|
ShapeSet
|
The set. |
required |
dofs
|
sequence of str
|
The DOFs, in the order they are wanted. |
None
|
form
|
str
|
One of |
'shapes'
|
Returns:
| Type | Description |
|---|---|
MatrixAt
|
The labels, the values and the DOFs not in the set. |
Raises:
| Type | Description |
|---|---|
ValueError
|
For a form not in |
Source code in src/visualdynamics/core/transform.py
1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 | |
carried_modes
¶
carried_modes(report: TransformReport, shapes: ShapeSet) -> list[str]
The modal DOFs an expansion carried, in mode order, or an empty list when it carried every mode, so a name lists the modes only when that matters.