visualdynamics.core.signals¶
signals
¶
Generated reference signals: random, burst random, periodic random, an impulse or classical pulse, sine, and a shock synthesized to a shock response spectrum.
Each generator returns a TimeHistory at the reference DOFs, in the
quantity a specification is the density of (a force, a voltage, an
acceleration), in SI. They are the inputs a response is later computed
from, and the drives a test plan is tried with before the test.
Random signals are shaped to a power spectral density. A frame of N samples is built in the frequency domain: at line k, a complex amplitude of magnitude N·sqrt(G_k·df/2) with a random phase, so that the frame's one-sided PSD is G_k and, by the central limit theorem over many lines, its samples are Gaussian. DC and the Nyquist line are left at zero. A stationary record is made of independent frames, each windowed by a half-cycle sine and overlapped by half a frame: the squares of two overlapping sine windows sum to one at every sample, so the variance is the same throughout, there is no seam, and the record does not repeat. The window smooths the spectrum over about three lines, so a target is drawn as it would be read with a frame of the same length.
Several DOFs are drawn from a cross-spectral density matrix S at each
line. With S factored as F Fᴴ (its eigenvectors scaled by the square
root of their eigenvalues, so a matrix of lower rank is drawn too), the
amplitudes F·z for independent unit-magnitude random phases z have the
cross spectrum F Fᴴ. This package stores a CPSD record (a, b) as
conj(X_a)·X_b (TimeHistory.compute_cpsds), so the matrix factored is
the conjugate of the one stored. Uncorrelated DOFs are the diagonal
matrix, the same rule.
Each record carries the averaging it is meant to be read with, so the analysis settings opened on it match it: random in frames of the length it was drawn at, under a Hann window, overlapped by half; burst random in one rectangular frame per burst, and periodic random in one per period, so each is one leakage-free average; an impulse or a pulse as the whole record in one rectangular frame. A sine has none of its own.
An impulse is a half-sine of a chosen width, the force a hammer tip
makes: a pulse with a finite width has a finite bandwidth, where a
single sample is flat to the Nyquist frequency whatever the sample rate.
Its spectrum, |F(f)| = (2PT/π)·|cos(πfT)/(1 - 4f²T²)| for a peak P and
a width T, is flat at low frequency and falls 3 dB by 0.59/T, 10 dB by
1.02/T, and to its first zero at 1.5/T (impulse_rolloff), so the width
is chosen for the band the impulse should excite.
A shock specification gives a shock synthesized to its spectrum, a sum
of wavelets a shaker can play, checked against the shaker's shock
ratings (core.shock_synthesis).
A specification is read between its breakpoints as it is everywhere in
the package (author.read_at_lines: the magnitude log-log, the phase
linearly), or per bin when it holds a controller's lines, and is zero
outside the band it is written on.
References
.. [1] J. S. Bendat and A. G. Piersol, Random Data: Analysis and Measurement Procedures, 4th ed., Wiley, 2010, ch. 5 and 11. .. [2] D. O. Smallwood and T. L. Paez, "A frequency domain method for the generation of partially coherent normal stationary time domain signals", Shock and Vibration, vol. 1, no. 1, pp. 45–53, 1993.
Classes:
| Name | Description |
|---|---|
SignalSettings |
Everything a generated signal is made from, besides its source. |
Functions:
| Name | Description |
|---|---|
random_history |
Stationary Gaussian random at the reference DOFs, shaped to a PSD |
burst_random_history |
Burst random: |
periodic_random_history |
Periodic random: one random frame repeated, so that a frame of |
impulse_history |
An impulse, or a classical pulse, at the same instant at every DOF. |
impulse_rolloff |
Where a half-sine impulse |
impulse_spectrum |
The magnitude of a record's Fourier transform, |X(f)| = |Σ x_n |
sine_history |
A sine specification's tone as the signal it describes, at each of |
flat_spectrum |
A flat target over a band at every DOF, uncorrelated, for a |
kinds_for |
The kinds of signal |
reference_unit |
The unit a typed level and peak are read in for a channel |
generate |
The signal |
target_of |
The spectrum a random signal is drawn to, for drawing beside its |
preview |
The signal |
frame_used |
The frame length the random kinds are drawn with: the setting's, |
suggested |
Settings to open a pane with: the first kind the source makes, |
target_at |
A spectrum's autospectra read at |
Classes¶
SignalSettings
dataclass
¶
SignalSettings(kind: str = 'random', duration: float = 10.0, sample_rate: float = 8192.0, frame_length: int | None = None, seed: int | None = None, correlated: bool = True, dofs: tuple[str, ...] | None = None, bursts: int = 10, burst: float = 0.5, taper: float = BURST_TAPER, periods: int = 10, repeats: int | None = None, at: float = 0.0, width: float = 0.001, shape: str = 'half-sine', peak: float = 1.0, pulse_duration: float = 0.011, edge: float | None = None, band: tuple[float, float] = (20.0, 2000.0), level: float = 1.0, tone: str | None = None, half_cycles: int = HALF_CYCLES, stroke: float | None = None, velocity: float | None = None, force: float | None = None, moving_mass: float | None = None, body_mass: float | None = None)
Everything a generated signal is made from, besides its source.
One record for every kind; each reads the fields it needs and the
rest are kept as they were, so switching kinds in a pane and back
loses nothing. Stored with the object it made (Project.generate_signal
records it, with the seed), so the same signal can be made again.
Methods:
| Name | Description |
|---|---|
rating |
The shaker's shock ratings these settings give, as a |
to_dict |
The settings as plain values, for a recipe or a file. |
from_dict |
Settings from |
describe |
The settings in words, as '10 s of random at 8192 Hz'. |
Methods:¶
rating
¶
The shaker's shock ratings these settings give, as a
core.shaker.ShakerRating; a rating at zero is none, as the
stroke reading takes it.
Source code in src/visualdynamics/core/signals.py
to_dict
¶
The settings as plain values, for a recipe or a file.
from_dict
classmethod
¶
from_dict(values: Mapping[str, Any]) -> SignalSettings
Settings from to_dict's values. A field this version does not
know is left out, and one it knows that is missing keeps its
default, so a recipe written by a later or an earlier version
still reads.
Source code in src/visualdynamics/core/signals.py
describe
¶
The settings in words, as '10 s of random at 8192 Hz'.
Source code in src/visualdynamics/core/signals.py
Functions:¶
random_history
¶
random_history(spectrum: Psd, *, duration: float, sample_rate: float, frame_length: int | None = None, dofs: Sequence[str] | None = None, correlated: bool = True, seed: int | Generator | None = None) -> TimeHistory
Stationary Gaussian random at the reference DOFs, shaped to a PSD or a full CPSD.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
spectrum
|
Psd or Specification
|
The target, in declared units: an autospectrum per DOF, and the cross terms between them for correlated signals. |
required |
duration
|
float
|
Seconds. |
required |
sample_rate
|
float
|
Samples per second, above twice the highest frequency written. |
required |
frame_length
|
int
|
Samples per frame, even, which sets the line spacing the target is drawn at; by default a power of two with lines no coarser than 1 Hz or a quarter of the lowest frequency written. |
None
|
dofs
|
sequence of str
|
Which of the spectrum's DOFs to draw, in this order; all of them by default. |
None
|
correlated
|
bool
|
Draw the cross terms the spectrum holds. False draws each DOF on its own, uncorrelated, whatever cross terms it holds; a spectrum of autospectra alone is drawn uncorrelated either way. |
True
|
seed
|
int or Generator
|
The same seed gives the same record. |
None
|
Returns:
| Type | Description |
|---|---|
TimeHistory
|
One record per DOF, from time zero. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If the spectrum is not a density in declared units, holds no autospectrum at a DOF asked for, is zero everywhere, is written at or above the Nyquist frequency, or is a matrix no set of signals has at some line; or if the duration is shorter than two samples. |
Source code in src/visualdynamics/core/signals.py
burst_random_history
¶
burst_random_history(spectrum: Psd, *, bursts: int, sample_rate: float, frame_length: int, burst: float = 0.5, taper: float = BURST_TAPER, dofs: Sequence[str] | None = None, correlated: bool = True, seed: int | Generator | None = None) -> TimeHistory
Burst random: bursts frames, each random for the first burst
share of the frame and quiet for the rest.
The level while a burst is on is the target's; averaged over the whole frame the PSD is the target times the share the burst is on (less its tapers). Each frame holds one burst, from the frame's start, so a capture of the frame's length holds the burst and the response's decay, and is read leakage-free with a rectangular window once the response has died away.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
spectrum
|
Psd or Specification
|
The target, as for |
required |
bursts
|
int
|
How many frames. |
required |
sample_rate
|
float
|
Samples per second. |
required |
frame_length
|
int
|
Samples per frame: the capture length the bursts are aligned to. |
required |
burst
|
float
|
The share of each frame the burst is on, above 0 and at most 1. |
0.5
|
taper
|
float
|
The share of the burst each cosine ramp takes, at either end. |
`BURST_TAPER`
|
dofs
|
Sequence[str] | None
|
As for |
None
|
correlated
|
Sequence[str] | None
|
As for |
None
|
seed
|
Sequence[str] | None
|
As for |
None
|
Returns:
| Type | Description |
|---|---|
TimeHistory
|
|
Raises:
| Type | Description |
|---|---|
ValueError
|
As |
Source code in src/visualdynamics/core/signals.py
periodic_random_history
¶
periodic_random_history(spectrum: Psd, *, periods: int, sample_rate: float, frame_length: int, repeats: int | None = None, dofs: Sequence[str] | None = None, correlated: bool = True, seed: int | Generator | None = None) -> TimeHistory
Periodic random: one random frame repeated, so that a frame of the same length read with a rectangular window has no leakage.
With repeats omitted the one frame is repeated periods times
(pseudo-random). With repeats r, a new frame is drawn every r
periods (periodic random), so averages over many realizations are
taken from a record whose every frame is still periodic; the first
period of each realization carries the change from the last and is
the one to discard once a response is computed from it.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
spectrum
|
Psd or Specification
|
The target, as for |
required |
periods
|
int
|
How many frames in all. |
required |
sample_rate
|
float
|
Samples per second. |
required |
frame_length
|
int
|
Samples per period. |
required |
repeats
|
int
|
Periods per realization; all of them by default. |
None
|
dofs
|
Sequence[str] | None
|
As for |
None
|
correlated
|
Sequence[str] | None
|
As for |
None
|
seed
|
Sequence[str] | None
|
As for |
None
|
Returns:
| Type | Description |
|---|---|
TimeHistory
|
|
Raises:
| Type | Description |
|---|---|
ValueError
|
As |
Source code in src/visualdynamics/core/signals.py
impulse_history
¶
impulse_history(dofs: str | Sequence[str], *, quantity: str, duration: float, sample_rate: float, at: float = 0.0, peak: float = 1.0, width: float = 0.001, pulse: ClassicalPulse | None = None) -> TimeHistory
An impulse, or a classical pulse, at the same instant at every DOF.
With no pulse, an impulse: a half-sine of peak and width from
at, whose spectrum rolls off as impulse_rolloff gives. With a
pulse, its shape, peak and duration from at (ClassicalPulse.at).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
dofs
|
str or sequence of str
|
The reference DOFs. |
required |
quantity
|
str
|
What the signal is, such as 'force' or 'acceleration'. |
required |
duration
|
float
|
Seconds of record. |
required |
sample_rate
|
float
|
Samples per second. |
required |
at
|
float
|
When the impulse or the pulse starts, in seconds. |
0.0
|
peak
|
float
|
The impulse's peak, signed, in SI; not used with a pulse. |
1.0
|
width
|
float
|
The impulse's width, the half-sine's half cycle, in seconds; not used with a pulse. At least two samples. |
1e-3
|
pulse
|
ClassicalPulse
|
A half-sine, sawtooth or trapezoid instead of the impulse. |
None
|
Returns:
| Type | Description |
|---|---|
TimeHistory
|
One identical record per DOF. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If the start is outside the record or the impulse or pulse runs past its end, the peak is not a number other than zero, the impulse is narrower than two samples, or the duration is shorter than two samples. |
Source code in src/visualdynamics/core/signals.py
impulse_rolloff
¶
Where a half-sine impulse width seconds wide rolls off: the
frequencies, in Hz, at which its spectrum is 3 dB and 10 dB below its
level at low frequency, and its first zero, 1.5/width.
The ratio |cos(πfT)/(1 - 4f²T²)| falls monotonically to its first zero, so each level is solved for once on that span. The rule of thumb that a hammer excites up to about one over its contact time is the 10 dB point, 1.02/T.
Source code in src/visualdynamics/core/signals.py
impulse_spectrum
¶
impulse_spectrum(history: TimeHistory, record: int = 0) -> Spectrum
The magnitude of a record's Fourier transform, |X(f)| = |Σ x_n e^(-i2πf n Δt)|·Δt, in the record's quantity times seconds (N·s for a force): what a transient puts into each frequency, flat at its integral at low frequency.
The record is zero-padded to at least SPECTRUM_LINES lines, so a
short impulse is drawn as a smooth curve and its rolloff and zeros
can be read off it. Read from the samples, so it is what the record
holds, sampling included.
Source code in src/visualdynamics/core/signals.py
sine_history
¶
sine_history(specification: SineSweepSpecification, *, sample_rate: float, tone: str | None = None) -> TimeHistory
A sine specification's tone as the signal it describes, at each of its control DOFs.
The phase is the tone's own (SineTone.argument), so the frequency
follows its segments at their rates, dwells included. The amplitude
and the phase of each DOF are interpolated linearly in time between
the tone's breakpoints, which is the target the controller follows
(SineTone.target). The record starts at the tone's start.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
specification
|
SineSweepSpecification
|
The tones and their control DOFs, in declared units. |
required |
sample_rate
|
float
|
Samples per second, above twice the tone's highest frequency. |
required |
tone
|
str
|
Which tone; the first by default. |
None
|
Returns:
| Type | Description |
|---|---|
TimeHistory
|
One record per control DOF. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If the tone is not one the specification holds, its units are not declared, or it reaches the Nyquist frequency. |
Source code in src/visualdynamics/core/signals.py
flat_spectrum
¶
flat_spectrum(dofs: str | Sequence[str], *, quantity: str, band: tuple[float, float], level: float, unit: str | None = None) -> Specification
A flat target over a band at every DOF, uncorrelated, for a signal typed in with no specification to read.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
dofs
|
str or sequence of str
|
The reference DOFs. |
required |
quantity
|
str
|
What the signal is, such as 'force'; the target is its density. |
required |
band
|
(float, float)
|
Lowest and highest frequency, Hz. |
required |
level
|
float or sequence of float
|
The density across the band, in |
required |
unit
|
str
|
The density's unit, such as 'N**2/Hz'; SI when omitted. |
None
|
Returns:
| Type | Description |
|---|---|
Specification
|
Two breakpoints per DOF. |
Source code in src/visualdynamics/core/signals.py
kinds_for
¶
The kinds of signal source can make, in KINDS order: random
kinds from a spectrum, sine from a sine specification, an SRS
synthesis from a shock specification, and from a channel table every
kind but sine and the synthesis, at its reference channels.
Source code in src/visualdynamics/core/signals.py
reference_unit
¶
The unit a typed level and peak are read in for a channel table's reference channels: the one unit they share, SI's where none is written, or '' when they differ (each is then read in its own channel's unit).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
table
|
ChannelTable
|
The table. |
required |
dofs
|
sequence of str
|
Which reference DOFs; all of them when omitted. |
None
|
Returns:
| Type | Description |
|---|---|
str
|
The unit, such as 'lbf', or ''. |
Raises:
| Type | Description |
|---|---|
ValueError
|
As |
Source code in src/visualdynamics/core/signals.py
generate
¶
generate(source: Any, settings: SignalSettings, *, progress: Any = None) -> TimeHistory
The signal settings describe, made from source.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
source
|
(Psd, Specification, SineSweepSpecification, ShockSpecification or ChannelTable)
|
A spectrum's DOFs and shape for random kinds; a sine
specification's tone; a shock specification's spectrum for an
SRS synthesis; a channel table's reference channels for an
impulse, a pulse, or random to the typed |
required |
settings
|
SignalSettings
|
The kind and its settings. |
required |
progress
|
callable
|
Called with |
None
|
Returns:
| Type | Description |
|---|---|
TimeHistory
|
One record per DOF. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If the kind is not one |
Source code in src/visualdynamics/core/signals.py
938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 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 | |
target_of
¶
target_of(source: Any, settings: SignalSettings) -> Psd | None
The spectrum a random signal is drawn to, for drawing beside its own: the source itself, or the typed target at a channel table's references; None for the kinds with no spectrum to meet.
Source code in src/visualdynamics/core/signals.py
preview
¶
preview(source: Any, settings: SignalSettings) -> TimeHistory
The signal settings describe, cut short to PREVIEW_SAMPLES so
that it answers while a setting is being typed: fewer seconds, bursts
or periods, every other setting as given. A sine is drawn whole only
when it fits; otherwise its first PREVIEW_SAMPLES. An SRS
synthesis is iterated whole, as it is made: a shorter one would meet
a different spectrum.
Source code in src/visualdynamics/core/signals.py
frame_used
¶
frame_used(source: Any, settings: SignalSettings) -> int | None
The frame length the random kinds are drawn with: the setting's,
or the one random_history chooses when it is None; None for the
kinds that are not drawn in frames.
Source code in src/visualdynamics/core/signals.py
suggested
¶
suggested(source: Any) -> SignalSettings
Settings to open a pane with: the first kind the source makes,
and a sample rate 2.56 times the highest frequency it holds, as an
analyzer's span is set, rounded up to a multiple of 1024; for an SRS
synthesis, core.shock_synthesis.suggested_rate.
Source code in src/visualdynamics/core/signals.py
target_at
¶
A spectrum's autospectra read at lines the way the random kinds
draw them (between breakpoints log-log, a controller's lines per
bin), NaN outside the band each is written on: the line the signal
is drawn to, to plot beside it.