visualdynamics.core.replication_segments¶
replication_segments
¶
A replicated record judged segment by segment, each by the rule for its kind of motion.
replication.compare reads a replication the way a controller's screen
does: one waveform error over the whole record, one worst deviation of
a maximax spectrum, one level. Over a long record those figures are
dominated by whatever carries most of its mean square, and a short
event carries very little of it. A shock a fraction of a second long
in a minute of road can be missed by several decibels across part of
its spectrum while the whole-record error still reads under five
percent, because the shock holds a percent or two of the record's
mean square. The whole-record spectrum cannot see it either: a
maximax over the record keeps each oscillator's largest response
anywhere in it, and that may come from a different event.
So the record is cut into segments, each marked as one of three kinds of motion, and each is held to the tolerance of its kind, the way test documents commonly write a replication's tolerance:
- stationary: a stretch whose statistics hold steady (a rough road at constant speed). Judged in frequency by its ASD against the reference's, line by line within a band (±3 dB by default), and in time by its mean square about zero against the reference's (within +10 percent by default; an undertest shows in the ASD band).
- transient: a long non-stationary stretch whose spectrum holds its shape while its level changes (braking, a run-up). Judged by its short-time rms envelope, window by window, against the reference's (within 1 dB by default), and by its normalized ASD, the spectrum with the segment's own mean square divided out, within a band (±3 dB by default).
- shock: a short event. Judged by its shock response spectrum in each polarity against the reference's, point by point (−6/+3 dB by default), and by its major peaks, the reference's largest crests and troughs, each read against the control at the same sample (within 20 percent by default).
The tolerances are examples of what test documents ask for, not any one document's requirement; every one is an argument.
A stationary or transient segment is weighted by a tapered cosine window before its ASD and its mean square are computed (10 percent of it by default, half at each end), so the segment's abrupt ends do not enter its spectrum as content the record does not have. A shock's SRS is computed on the segment as recorded: the SRS is a peak response, not an average, and a mark drawn tight on the event would put the event's first and largest cycles under the taper, weighting down the reference and the control alike there and hiding a miss in the ratio. The two local readings, the envelope and the peaks, are read on the segment as recorded too: they are ratios of the two traces at one place, from which a weight applied to both cancels.
A segment is judged only on samples that are numbers. A trace holding a NaN or an infinity is refused, and a control that is zero where the reference asks for something reads minus infinity decibels there and fails, so a dead channel never reads as a pass.
The comparison assumes the two traces are on like terms. The reference
and the control should be band-limited alike to the test band
(core.filters) before they are judged; replication_segments aligns
them by the one integer shift replication.lag_of finds, and
judge_segments takes arrays already aligned. The whole-record rms
error is reported beside the segments, so the reading that hides a
segment's miss is on the page next to the one that shows it.
References
The segments and the forms of their tolerances follow the first of
these; the readings are standard estimators, set out in the others.
Each shock segment's spectra come from core.srs, which cites its
filter.
- Department of Defense (2019). MIL-STD-810H, Environmental Engineering Considerations and Laboratory Tests. Method 525.2, paragraph 4.2.2c: a replication judged segment by segment, each stretch stationary, a short transient or a long transient, a stationary or long one tapered at its ends by a 10 percent cosine window, and each held to tolerances of the forms used here.
- Welch, P. D. (1967). "The Use of Fast Fourier Transform for the
Estimation of Power Spectra: A Method Based on Time Averaging Over
Short, Modified Periodograms." IEEE Transactions on Audio and
Electroacoustics, 15(2), 70-73.
doi:10.1109/TAU.1967.1161901
The averaged, windowed periodogram each ASD here is, the estimator
TimeHistory.compute_psdscomputes. - Harris, F. J. (1978). "On the Use of Windows for Harmonic Analysis with the Discrete Fourier Transform." Proceedings of the IEEE, 66(1), 51-83. doi:10.1109/PROC.1978.10837 The tapered cosine window a stationary or transient segment is weighted by.
Classes:
| Name | Description |
|---|---|
Segment |
One stretch of the record and the kind of motion it is. |
SegmentCheck |
One reading of one segment against its tolerance. |
SegmentResult |
One segment judged: where it is, what share of the record it |
SegmentedReplication |
A replicated record judged segment by segment. |
Functions:
| Name | Description |
|---|---|
band_text |
A tolerance in words: '±3 dB', '−6/+3 dB', 'up to +10 %'. |
tapered_cosine |
The weights of a tapered cosine (Tukey) window over |
major_peaks |
The samples of the reference's major peaks: its largest crests |
as_segments |
Segment marks as |
judge_segments |
A replicated record judged segment by segment, for a reference |
aligned_traces |
(time, reference, control, channel, lag): one channel of a run |
replication_segments |
A replicated run judged segment by segment against the reference |
Classes¶
Segment
¶
Bases: NamedTuple
One stretch of the record and the kind of motion it is.
start and end are in seconds on the reference's own clock, from
its first sample. kind is one of SEGMENT_KINDS. name is what
the readout calls it; when empty it is called by its kind and its
place among the segments of that kind ('shock 1').
SegmentCheck
dataclass
¶
SegmentCheck(reading: str, unit: str, tolerance: tuple[float, float], where: ndarray, values: ndarray, outside: int, passed: bool | None, needed: int | None = None)
One reading of one segment against its tolerance.
reading names it ('ASD', 'variance', 'envelope', 'normalized ASD',
'SRS positive', 'SRS negative', 'major peaks'). where and values
are each judged item and its reading: lines in Hz for the spectra,
window starts in seconds for the envelope, peak times in seconds
for the peaks (both on the reference's clock), and one item for the
variance; values are in unit, 'dB' or '%', as distances from
the reference. low and high are the lowest and highest reading,
tolerance the band it was held to, outside how many items fell
outside it. passed is None when nothing could be judged.
Methods:
| Name | Description |
|---|---|
describe |
The check in a line: the reading, its range, the tolerance, |
Attributes:
| Name | Type | Description |
|---|---|---|
judged |
int
|
How many items were read. |
low |
float
|
The lowest reading, NaN when there is none. |
high |
float
|
The highest reading, NaN when there is none. |
Attributes¶
Methods:¶
describe
¶
The check in a line: the reading, its range, the tolerance, and the verdict, as in 'SRS negative −7.4 to +0.1 dB (−6/+3 dB), 4 of 65 outside: fails'.
Source code in src/visualdynamics/core/replication_segments.py
SegmentResult
dataclass
¶
SegmentResult(segment: Segment, label: str, first: int, samples: int, share: float, rms_error: float, checks: tuple[SegmentCheck, ...], passed: bool | None)
One segment judged: where it is, what share of the record it holds, and each of its checks.
first and samples place it in the aligned traces. share is its
share of the reference's mean square over the whole record, and
rms_error its own rms error, both in percent and both read on the
segment as recorded: together they say how much the segment can
move the whole-record figure. passed is False when any check
fails, None when none fails and one or more could not be judged,
and True only when every check passes.
Methods:
| Name | Description |
|---|---|
describe |
The segment in a line: its name, kind, span and verdict, and |
Methods:¶
describe
¶
The segment in a line: its name, kind, span and verdict, and which checks failed.
Source code in src/visualdynamics/core/replication_segments.py
SegmentedReplication
dataclass
¶
SegmentedReplication(segments: tuple[SegmentResult, ...], rms_error: float, sample_rate: float, samples: int, passed: bool | None, settings: dict[str, Any] = dict(), channel: str | None = None, lag: int = 0)
A replicated record judged segment by segment.
segments are the results in the order the marks were given.
rms_error is the whole record's rms error in percent, the figure a
controller reports, kept beside the segments because it is the one
that hides a segment's miss. passed is False when any segment
fails, None when none fails and one or more could not be judged (or
none is marked), and True only when every segment passes. channel and lag say which record was
read and how far it was shifted to align it (None and 0 for arrays
given directly). settings are the tolerances it was judged under,
as judge_segments took them.
Methods:
| Name | Description |
|---|---|
describe |
The record in a line: the whole-record rms error, and the |
Attributes:
| Name | Type | Description |
|---|---|---|
failed |
list[SegmentResult]
|
The segments that failed, in order. |
not_judged |
list[SegmentResult]
|
The segments that could not be judged, in order. |
Attributes¶
not_judged
property
¶
not_judged: list[SegmentResult]
The segments that could not be judged, in order.
Methods:¶
describe
¶
The record in a line: the whole-record rms error, and the verdict by segment. The counts are of the segments judged; a segment that could not be judged is named on its own.
Source code in src/visualdynamics/core/replication_segments.py
Functions:¶
band_text
¶
A tolerance in words: '±3 dB', '−6/+3 dB', 'up to +10 %'.
Source code in src/visualdynamics/core/replication_segments.py
tapered_cosine
¶
The weights of a tapered cosine (Tukey) window over samples
samples.
percent of the window is under the cosine, half rising from zero
at the start and half falling to zero at the end; the middle is
weighted by one. 0 is a rectangle and 100 a Hann window. This is
the symmetric form, the one a segment cut from a record is weighted
by, where averaging.window_shape gives the periodic form spectral
frames use.
Raises:
| Type | Description |
|---|---|
ValueError
|
For fewer than one sample, or a share outside 0 to 100. |
Source code in src/visualdynamics/core/replication_segments.py
major_peaks
¶
major_peaks(reference: ArrayLike, each: int = PEAKS, share_percent: float = MAJOR_SHARE_PERCENT) -> ndarray
The samples of the reference's major peaks: its largest crests
(local maxima above zero) and deepest troughs (local minima below
zero), up to each of each, largest first, crests then troughs.
No more than share_percent of all its peaks are counted as major,
and never fewer than one each way where there is one, so a segment
with few peaks is read on its very largest.
Source code in src/visualdynamics/core/replication_segments.py
as_segments
¶
as_segments(marks: Sequence[Any]) -> list[Segment]
Segment marks as Segments: each a Segment, or a tuple of
(start, end, kind) with an optional name.
Raises:
| Type | Description |
|---|---|
ValueError
|
For a kind that is not one of |
Source code in src/visualdynamics/core/replication_segments.py
judge_segments
¶
judge_segments(reference: ArrayLike, control: ArrayLike, sample_rate: float, segments: Sequence[Any], *, taper_percent: float = TAPER_PERCENT, band: tuple[float, float] | None = None, lines: float = LINES_HZ, asd_db: tuple[float, float] = ASD_DB, asd_percent: float = 100.0, variance_percent: tuple[float, float] = VARIANCE_PERCENT, envelope_db: tuple[float, float] = ENVELOPE_DB, window: float | None = None, srs_db: tuple[float, float] = SRS_DB, q: float | None = None, per_octave: int | None = None, peak_percent: float = PEAK_PERCENT, peaks: int = PEAKS) -> SegmentedReplication
A replicated record judged segment by segment, for a reference
and a control trace held as plain arrays, aligned: the array form
of replication_segments.
result = judge_segments(reference, control, 2048.0, [
(6.0, 30.0, 'stationary', 'rough road'),
(31.9, 32.7, 'shock', 'pothole'),
(41.0, 51.0, 'transient', 'braking')], band=(5.0, 400.0))
result.rms_error, result.passed, result.failed
Each segment is held to the rule for its kind (see the module): a stationary one by its ASD and its mean square, a transient one by its short-time rms envelope and its normalized ASD, a shock by its SRS in each polarity and its major peaks. Every tolerance is an example of what test documents ask for and is an argument here.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
reference
|
array - like
|
The two traces, one value per sample, aligned, in the same units. Ratios are taken, so the units do not matter. |
required |
control
|
array - like
|
The two traces, one value per sample, aligned, in the same units. Ratios are taken, so the units do not matter. |
required |
sample_rate
|
float
|
Samples per second. |
required |
segments
|
sequence
|
The marks, each a |
required |
taper_percent
|
float
|
The share of a stationary or transient segment under its tapered cosine window's ends, before its ASD and mean square are computed. A shock's SRS is read untapered. |
`TAPER_PERCENT`
|
band
|
(float, float)
|
The test band in Hz: the lines an ASD is judged at and the
natural frequencies an SRS is computed at. By default an ASD is
judged at every line above DC and an SRS from one cycle in the
segment up to a fifth of the sample rate. Either way an SRS
stops at a fifth of the sample rate, and a line or a point more
than |
None
|
lines
|
float
|
The ASD's line spacing in Hz; a segment shorter than one frame is one frame. |
`LINES_HZ`
|
asd_db
|
tuple[float, float]
|
The ASD band in dB, for a stationary segment's ASD and a transient segment's normalized ASD, and the share of the judged lines that must lie inside it (every one by default). |
ASD_DB
|
asd_percent
|
tuple[float, float]
|
The ASD band in dB, for a stationary segment's ASD and a transient segment's normalized ASD, and the share of the judged lines that must lie inside it (every one by default). |
ASD_DB
|
variance_percent
|
(float, float)
|
A stationary segment's mean square against the reference's, in percent below and above. |
`VARIANCE_PERCENT`
|
envelope_db
|
(float, float)
|
A transient segment's short-time rms against the reference's in every window, in dB. |
`ENVELOPE_DB`
|
window
|
float
|
The envelope's window and step in seconds;
|
None
|
srs_db
|
(float, float)
|
A shock segment's SRS against the reference's, each polarity, at every natural frequency judged. |
`SRS_DB`
|
q
|
float | None
|
The SRS's quality factor and spacing, |
None
|
per_octave
|
float | None
|
The SRS's quality factor and spacing, |
None
|
peak_percent
|
float
|
How far each major peak may be from the reference's, in
percent, and how many are read each way ( |
PEAK_PERCENT
|
peaks
|
float
|
How far each major peak may be from the reference's, in
percent, and how many are read each way ( |
PEAK_PERCENT
|
Returns:
| Type | Description |
|---|---|
SegmentedReplication
|
|
Raises:
| Type | Description |
|---|---|
ValueError
|
For traces of different lengths or holding samples that are not numbers, a segment outside the record or too short to judge, an unknown kind, or a tolerance that is not a band. |
Source code in src/visualdynamics/core/replication_segments.py
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 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 | |
aligned_traces
¶
aligned_traces(measured: TimeHistory, specification: DataArray, channel: str | None = None, lag: int | None = None, playing: int = 0) -> tuple[ndarray, ndarray, ndarray, str, int]
(time, reference, control, channel, lag): one channel of a run and of the reference it was controlled to, aligned, over one playing.
The reference is the specification's record at channel (its first
DOF by default), the control the run's record at the same DOF and
dimension. The two are aligned by replication.lag_of's one shift,
unless lag is given, and the control is cut to the playing'th
whole playing of the reference (replication.playings): for a
reference as long as the run, the one playing there is. time is
the reference's own clock, from zero, in seconds. Values are as
stored (SI).
Raises:
| Type | Description |
|---|---|
ValueError
|
When the reference names no such channel, the run has no record at it, or the run does not hold that playing. |
Source code in src/visualdynamics/core/replication_segments.py
replication_segments
¶
replication_segments(measured: TimeHistory, specification: DataArray, segments: Sequence[Any], *, channel: str | None = None, lag: int | None = None, playing: int = 0, **settings: Any) -> SegmentedReplication
A replicated run judged segment by segment against the reference it was controlled to.
reference = TransientSpecification(...) # the field record
result = replication_segments(run, reference, [
(6.0, 30.0, 'stationary', 'rough road'),
(31.9, 32.7, 'shock', 'pothole'),
(41.0, 51.0, 'transient', 'braking')], band=(5.0, 400.0))
print(result.describe())
for segment in result.segments:
print(segment.describe())
replication.compare reads the run as a whole, which hides a short
event's miss; this reads each marked segment by the rule for its
kind (judge_segments) and reports the whole-record rms error
beside them. The two traces are taken at channel (the reference's
first DOF by default), over the playing'th whole playing of the
reference in the run, aligned by replication.lag_of unless lag
is given (aligned_traces). Segment times are seconds on the
reference's clock, from its first sample.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
measured
|
TimeHistory
|
The run. |
required |
specification
|
TransientSpecification or TimeHistory
|
The reference it was controlled to. |
required |
segments
|
sequence
|
The marks, each a |
required |
channel
|
str | None
|
As for |
None
|
lag
|
str | None
|
As for |
None
|
playing
|
str | None
|
As for |
None
|
**settings
|
Any
|
The tolerances, as |
{}
|
Returns:
| Type | Description |
|---|---|
SegmentedReplication
|
With |