visualdynamics.core.signature¶
signature
¶
Two signatures of one article compared mode by mode.
A structural signature is a low-level measurement of the article at one response point (an FRF, a transmissibility, or the level a tracked sine sweep reads), taken the same way before a test and after it. Laid over each other, the two say whether the article changed: a mode that has dropped in frequency has lost stiffness, and one that has lost height has gained damping or moved its motion elsewhere. An overlay shows that; a criterion decides it. Test documents write the criterion per mode, as a limit on how far each mode that counts may move in frequency and in amplitude, and they name which modes count (usually the global modes, the ones with a large share of the article's mass moving; a local mode, a bracket or a cable, moves when a clamp is re-seated and means little).
compare_signatures does that for two records, and signature_shifts
for two curves held as plain arrays. Each mode is named by a frequency
near it. The mode is found on each curve as the tallest line inside a
window about that frequency, its top placed between lines and its
half-power width read, and the two readings give the mode's frequency
shift and amplitude shift in percent of the reading before. A mode that
counts is judged against both limits and passes or is flagged; a mode
that does not count is reported with its shifts and not judged, because
the reader still wants to see it move. find_modes names the modes when
nobody has: the most prominent peaks of the curve before the test,
ranked by the rule the modal fit's suggestion ranks them by; a shape
set the modal fit has confirmed names its modes too.
The limits default to plus or minus 5 percent in frequency and plus or minus 20 percent in amplitude, an example of what test documents ask for and not any one document's requirement; the test plan states the numbers, and either limit can be written as a pair when the plan holds a drop and a rise to different values. A shift on a limit's edge is inside it.
The two readings are of the curve, not of a fitted model: a peak read on a transmissibility sits a little below the mode's natural frequency (by about one part in 4 Q squared), and it sits there the same way before and after, so the shift between the two is the mode's. A mode that moves out of its window between the two signatures is read as whatever peak is tallest in the window after; the window is wide (a fifth of the frequency either side by default) and is narrowed to stop halfway, on a log scale, to the next mode named, so two modes named are never read as one. What a reading cannot be trusted for is said in its notes, and the comparison gathers them: a peak on its window's edge, a top placed outside the window, lines that hold no value, a half-power width that cannot be read, and two modes read on one peak. Two signatures of different quantities (an accelerance and a transmissibility) are refused, since their heights are not one scale.
References
The peak reading is a standard method, and the criterion is of the kind test documents write; these are where each is described.
- Piersol, A. G., & Paez, T. L. (Eds.) (2010). Harris' Shock and Vibration Handbook, 6th ed. McGraw-Hill. The half-power reading of a resonance, its Q as the peak frequency over the width between the points 3 dB down, which each mode here is read with before and after.
- ECSS-E-ST-10-03C Rev.1 (2022), Space Engineering: Testing. Requirement 0750143: the resonance search before and after a test, passed when each mode with an effective mass over 10 percent moves less than 5 percent in frequency and 20 percent in amplitude.
Classes:
| Name | Description |
|---|---|
SignatureMode |
A mode named for the comparison: a frequency near it, and |
PeakReading |
One mode read off one curve: the peak's frequency and height, |
ModeShift |
One mode before and after, its two shifts and the verdict. |
SignatureComparison |
Every mode named, compared: what |
Functions:
| Name | Description |
|---|---|
as_modes |
The modes named, as |
mode_windows |
The window each mode is read in, in Hz, in the order given. |
read_peak |
The tallest peak inside a band, read by its top and its |
signature_shifts |
Two signatures held as plain arrays, compared mode by mode: the |
matching_record |
The record of |
compare_signatures |
One record of a signature before the test and the same record |
find_modes |
The frequencies of the |
Classes¶
SignatureMode
dataclass
¶
SignatureMode(frequency: float, counts: bool = True, within: tuple[float, float] | None = None, name: str = '')
A mode named for the comparison: a frequency near it, and whether it counts.
frequency is in Hz and need not be exact; the mode is read as the
tallest peak in a window about it. within, a (low, high) band in
Hz, sets that window outright, for a mode with a taller neighbor
close by. name labels the mode in the table ('first bending',
'panel'); empty, the mode is labeled by its frequency.
Methods:
| Name | Description |
|---|---|
label |
What the table calls the mode: its name, or its frequency. |
PeakReading
dataclass
¶
PeakReading(frequency: float, height: float, low: float | None, high: float | None, notes: tuple[str, ...] = ())
One mode read off one curve: the peak's frequency and height, and its half-power crossings.
low and high are None where no half-power crossing can be read
on that side, and q is then None. notes says what the reading
cannot be trusted for, as text.
Attributes:
| Name | Type | Description |
|---|---|---|
q |
float | None
|
Q: the peak frequency over the half-power width. |
ModeShift
dataclass
¶
ModeShift(mode: SignatureMode, window: tuple[float, float], before: PeakReading, after: PeakReading, frequency_limit: tuple[float, float], amplitude_limit: tuple[float, float], notes: tuple[str, ...] = ())
One mode before and after, its two shifts and the verdict.
The shifts are in percent of the reading before the test, negative
for a drop. The limits are (below, above) in percent, as the
comparison was made with them. passed is None for a mode that
does not count, which is reported and not judged. notes holds the
two readings' notes, each said 'before the test' or 'after the
test', and a note when this mode is read on the same peak as
another.
Methods:
| Name | Description |
|---|---|
box |
The window the limits draw about the mode before the test: |
describe |
The mode in one line, as the table and the status line give |
Attributes:
| Name | Type | Description |
|---|---|---|
frequency_shift |
float
|
The frequency shift in percent of the frequency before. |
amplitude_shift |
float
|
The amplitude shift in percent of the height before. |
frequency_inside |
bool
|
Whether the frequency shift is inside its limit. |
amplitude_inside |
bool
|
Whether the amplitude shift is inside its limit. |
passed |
bool | None
|
True inside both limits, False outside either, None for a |
verdict |
str
|
'pass', 'flag', or 'not judged' for a mode that does not |
Attributes¶
frequency_shift
property
¶
The frequency shift in percent of the frequency before.
amplitude_shift
property
¶
The amplitude shift in percent of the height before.
passed
property
¶
True inside both limits, False outside either, None for a mode that does not count.
Methods:¶
box
¶
The window the limits draw about the mode before the test: (low frequency, high frequency, low height, high height). The mode after the test passes when its peak is inside it.
Source code in src/visualdynamics/core/signature.py
describe
¶
The mode in one line, as the table and the status line give it: '84.0 to 81.2 Hz (−3.3 %), height 18.0 to 13.0 (−27.6 %, outside): flag'. A shift past its limit on a mode that counts is marked 'outside'.
Source code in src/visualdynamics/core/signature.py
SignatureComparison
dataclass
¶
SignatureComparison(shifts: tuple[ModeShift, ...], frequency_limit: tuple[float, float], amplitude_limit: tuple[float, float], dof: str = '', notes: tuple[str, ...] = ())
Every mode named, compared: what compare_signatures and
signature_shifts return.
shifts holds one ModeShift per mode, in ascending frequency.
dof names the record compared when the comparison was of records,
and is empty for plain arrays. notes gathers every mode's notes,
each led by the mode's label.
Methods:
| Name | Description |
|---|---|
describe |
The verdict in one line: 'fails: 1 of 1 mode that counts |
Attributes:
| Name | Type | Description |
|---|---|---|
judged |
tuple[ModeShift, ...]
|
The modes that count. |
flagged |
tuple[ModeShift, ...]
|
The modes that count and are outside a limit. |
passed |
bool | None
|
True when every mode that counts is inside both limits, |
Attributes¶
passed
property
¶
True when every mode that counts is inside both limits, False when any is flagged, None when no mode counts.
Methods:¶
describe
¶
The verdict in one line: 'fails: 1 of 1 mode that counts flagged; 1 not judged', and how many notes the readings carry ('; 2 notes').
Source code in src/visualdynamics/core/signature.py
Functions:¶
as_modes
¶
as_modes(modes: Any) -> list[SignatureMode]
The modes named, as SignatureModes in ascending frequency.
Each may be a SignatureMode, a frequency in Hz (a mode that
counts), or a (frequency, counts) pair. A shape set (anything with a
frequency array, as the modal fit makes) names every mode it
holds, all counting, except a rigid-body mode at 0 Hz, which has no
peak to compare and is left out. A mode named by number at a
frequency that is not positive is refused.
Source code in src/visualdynamics/core/signature.py
mode_windows
¶
mode_windows(modes: Sequence[SignatureMode], search: float = SEARCH) -> list[tuple[float, float]]
The window each mode is read in, in Hz, in the order given.
A mode's own within is its window. Otherwise the window reaches
search of the frequency either side, and stops halfway, on a log
scale, to the next mode named on either side, so two modes named
close together are each read on their own peak.
Source code in src/visualdynamics/core/signature.py
read_peak
¶
read_peak(frequencies: ArrayLike, magnitude: ArrayLike, within: tuple[float, float]) -> PeakReading
The tallest peak inside a band, read by its top and its half-power width.
The reading is core.half_power.read_half_power's for the band:
the peak is the tallest line between within's two frequencies,
its top is placed between lines by the parabola through the
logarithms of that line and its two neighbors, and the two
crossings of 0.707 of that height are interpolated linearly, on the
whole curve, not only inside the band. Its notes are kept, and
three more are said here. A tallest line on the band's edge is a
peak outside it still rising. A top placed outside the band, which
a peak on the edge can have, is a reading of something outside the
window. A line that holds no value (NaN or infinite) is no part of
the peak, and one inside the band or between the crossings is
said, since the top or a crossing beside it is placed without it.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
frequencies
|
array - like
|
The curve's frequencies in Hz, ascending. |
required |
magnitude
|
array - like
|
The curve; complex values are read by their magnitude. |
required |
within
|
tuple of float
|
The band (low, high) in Hz the peak is in. |
required |
Returns:
| Type | Description |
|---|---|
PeakReading
|
The peak's frequency, height and crossings. |
Raises:
| Type | Description |
|---|---|
ValueError
|
When no line of the curve lies inside the band, or no line there stands above zero, so there is no peak to read. |
Source code in src/visualdynamics/core/signature.py
357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 | |
signature_shifts
¶
signature_shifts(frequencies: ArrayLike, before: ArrayLike, after: ArrayLike, modes: Any, *, after_frequencies: ArrayLike | None = None, frequency_limit: Any = FREQUENCY_LIMIT, amplitude_limit: Any = AMPLITUDE_LIMIT, search: float = SEARCH) -> SignatureComparison
Two signatures held as plain arrays, compared mode by mode: the
array form of compare_signatures.
result = signature_shifts(f, before, after, [84.0, (412.0, False)])
result.passed, [s.describe() for s in result.shifts]
Each mode is read on each curve in its window (mode_windows) by
read_peak; its frequency shift and amplitude shift are in percent
of the reading before, and a mode that counts passes when both are
inside their limits. The two curves may be on different
frequencies (a sine sweep run at another rate, a random at another
resolution); neither needs to be ascending.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
frequencies
|
array - like
|
The frequencies of |
required |
before
|
array - like
|
The two curves; complex values are read by their magnitude. |
required |
after
|
array - like
|
The two curves; complex values are read by their magnitude. |
required |
modes
|
float, SignatureMode, (float, bool), sequence, or shape set
|
The modes compared, as |
required |
after_frequencies
|
array - like
|
The frequencies of |
None
|
frequency_limit
|
float or (float, float)
|
The limits in percent: a number is that far either way, a pair
is (below, above). |
FREQUENCY_LIMIT
|
amplitude_limit
|
float or (float, float)
|
The limits in percent: a number is that far either way, a pair
is (below, above). |
FREQUENCY_LIMIT
|
search
|
float
|
How far either side of its frequency a mode's window reaches, as a share of the frequency. |
`SEARCH`
|
Returns:
| Type | Description |
|---|---|
SignatureComparison
|
One |
Raises:
| Type | Description |
|---|---|
ValueError
|
For a limit that does not hold zero, a mode at a frequency that is not positive, curves whose lengths do not match their frequencies, or a mode's window with no line of a curve in it or no line above zero. |
Source code in src/visualdynamics/core/signature.py
452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 | |
matching_record
¶
The record of other at the same response and reference DOF as
record record of data, else the same index.
Source code in src/visualdynamics/core/signature.py
compare_signatures
¶
compare_signatures(before: Any, after: Any, modes: Any, *, record: int = 0, after_record: int | None = None, frequency_limit: Any = FREQUENCY_LIMIT, amplitude_limit: Any = AMPLITUDE_LIMIT, search: float = SEARCH) -> SignatureComparison
One record of a signature before the test and the same record after it, compared mode by mode against frequency and amplitude limits.
result = compare_signatures(before, after, [84.0, (412.0, False)])
result.describe() # 'fails: 1 of 1 mode that counts ...'
before and after are any curves of one quantity against
frequency: two FRFs, two transmissibilities, or the levels two
tracked sine sweeps read. The record after is the one at the same
response (and reference) DOF as the record before, or the same
index when there is none. The comparison itself is
signature_shifts's, which says how each mode is read and judged;
find_modes names the modes from the curve before when the test
plan does not. The two records must be of one quantity: an
accelerance and a transmissibility are not one scale, and a pair
whose declared dimensions differ is refused. A record whose units
are undeclared is taken as the other's.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
before
|
DataArray
|
The two signatures. |
required |
after
|
DataArray
|
The two signatures. |
required |
modes
|
float, SignatureMode, (float, bool), sequence, or shape set
|
The modes compared, as |
required |
record
|
int
|
Which record of |
0
|
after_record
|
int
|
Which record of |
None
|
frequency_limit
|
Any
|
As for |
FREQUENCY_LIMIT
|
amplitude_limit
|
Any
|
As for |
FREQUENCY_LIMIT
|
search
|
Any
|
As for |
FREQUENCY_LIMIT
|
Returns:
| Type | Description |
|---|---|
SignatureComparison
|
One |
Raises:
| Type | Description |
|---|---|
ValueError
|
For the two records' declared dimensions differing, and for
anything |
Source code in src/visualdynamics/core/signature.py
find_modes
¶
find_modes(data: Any, record: int = 0, count: int = 2, *, bounds: tuple[float, float] | None = None) -> list[float]
The frequencies of the count most prominent peaks of one
record, ascending: the modes to compare when the test plan does not
name them.
Peaks are ranked as the modal fit's suggestion ranks them
(modal_fit.prominence): by how far each stands above the median
of its surroundings on a log scale, not by height, so a curve that
rises with frequency still offers its first mode first. A peak
inside the half-power width of one already taken is the same mode
and is passed over. The ranking reads the magnitude alone, so a
tracked sweep's level, which carries no phase to fit, is read the
same way as an FRF. To compare the modes a modal fit has confirmed
instead, pass its shape set as the modes (Project.fit_modes).
These are suggestions; which modes count is the test plan's to say.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data
|
DataArray
|
The signature, usually the one before the test. |
required |
record
|
int
|
Which record. |
0
|
count
|
int
|
How many modes to find. |
2
|
bounds
|
tuple of float
|
(low, high) in Hz to find them within; the whole curve when omitted. |
None
|
Returns:
| Type | Description |
|---|---|
list of float
|
The peaks' frequencies in Hz, placed between lines, ascending.
Fewer than |