visualdynamics.core.sine_profile¶
sine_profile
¶
A sine profile written the way test documents write one: segments of constant displacement, velocity or acceleration, with the crossovers between them solved and checked.
A sine profile is a short table of segments, each holding one quantity constant between two frequencies. At the low end the quantity is usually displacement, since held at a constant acceleration a tone's displacement grows as one over the frequency squared and the stroke runs out; in the middle it is sometimes velocity; above that it is acceleration. The three are tied together at every frequency by one relation. For a tone of frequency f and peak displacement x,
v = 2 pi f x, a = (2 pi f)**2 x,
so a quantity of power n (0 for acceleration, 1 for velocity, 2 for
displacement) held at a peak level L is the acceleration
a = (2 pi f)**n L (convert). Where a segment of one quantity meets a
segment of another, the two give the same amplitude at one frequency,
the crossover:
2 pi f_c = (L_2 / L_1)**(1 / (n_1 - n_2)) (`crossover`)
which for a displacement x below an acceleration a is the familiar f_c = sqrt(a / x) / (2 pi). With x in inches peak to peak and a in g peak it is the hand form f_c = sqrt(a / (0.0511 x)): 0.0511 is (2 pi)2 / 2 / 386.09, the half from peak to peak and the 386.09 in/s2 of one g.
SineProfile takes the segments as the document writes them: each one's
quantity and level in its own unit and, for a displacement, its
convention (peak or peak to peak), which is asked for rather than
assumed because a double amplitude read as peak doubles the motion. A
boundary between two segments may be written or left to be solved.
Where it is written and the segments are of different quantities, the
crossover is solved anyway and checked against it: a written breakpoint
more than CROSSOVER_TOLERANCE from the solved crossover is a level or a
convention typed wrong, and the check names it with the step the profile
would take there. A double amplitude entered as peak on a displacement
of 0.20 in below a 2 g segment moves the crossover from 14.0 Hz to
9.9 Hz; 0.91 mm read as 0.91 in moves 33 Hz to 6.6 Hz. Two segments of
the same quantity meet in a step and have no crossover; their boundary
must be written, and the step is reported.
The acceleration the profile asks for is what a controller runs, and
SineProfile.tone writes it as a SineTone. A tone interpolates its
amplitude linearly in sweep time between breakpoints (SineTone.target),
which follows a constant acceleration exactly but not a constant
displacement or velocity, so such a segment is written as enough
breakpoints that the interpolated level stays within ACCURACY of it.
The tone is built on the solved crossovers, which the written figures
round, so it is continuous; a profile whose written breakpoints do not
check, or that steps, is refused there, since a sweep cannot jump.
read_profile goes the other way: a tone already written in breakpoints
(a controller's file, or one built here) is read back as segments, each
stretch classified by the slope of its acceleration on log axes (12 dB
per octave for constant displacement, 6 for velocity, flat for
acceleration), and its breakpoints checked as crossovers like any
written profile.
Everything is stored in SI: levels as peak values, displacement in m, velocity in m/s and acceleration in m/s**2.
References
The relation between the three quantities of a tone and the crossover frequency between segments are set out in these.
- Piersol, A. G., & Paez, T. L. (Eds.) (2010). Harris' Shock and Vibration Handbook, 6th ed. McGraw-Hill. Sinusoidal motion: peak displacement, velocity and acceleration of a tone as one amplitude times successive powers of its angular frequency.
- Department of Defense (2019). MIL-STD-810H, Environmental Engineering Considerations and Laboratory Tests. Method 514.8, Annex E, Figure 514.8E-2: a sinusoidal profile written in segments of displacement and acceleration (0.20 in double amplitude from 5 to 14 Hz, 2 g from 14 to 33 Hz, 0.036 in to 52.1 Hz, 5 g to 500 Hz), each crossover where the two levels meet.
Classes:
| Name | Description |
|---|---|
ProfileSegment |
One segment of a sine profile: a quantity held at a peak level. |
Crossing |
Where segment |
SineProfile |
A sine profile as segments of constant displacement, velocity or |
Functions:
| Name | Description |
|---|---|
convert |
A tone's peak level in one quantity as its peak level in another, |
crossover |
The frequency at which two segments, each (quantity, peak level in |
displacement |
A segment of constant displacement, as the document writes it. |
velocity |
A segment of constant peak velocity, as the document writes it. |
acceleration |
A segment of constant peak acceleration, as the document writes |
interpolated |
A tone's own level as it runs it, carried to acceleration: its |
read_profile |
Read a tone already written as breakpoints back as segments of |
Classes¶
ProfileSegment
dataclass
¶
ProfileSegment(*, quantity: str, level: float, start: float | None = None, end: float | None = None, written: str = '')
One segment of a sine profile: a quantity held at a peak level.
quantity is 'displacement', 'velocity' or 'acceleration' and
level its peak value in SI (m, m/s, m/s**2): a displacement written
peak to peak is stored as half of it. start and end are the
frequencies, Hz, the document writes it between, None where a
boundary is left to be solved. written is the level as the document
wrote it, unit and convention included, for messages and the table.
Raises ValueError for an unknown quantity, a level that is not a
finite number more than zero, or a frequency that is not, and when
start is not below end. Build one with displacement,
velocity or acceleration, which convert from the document's unit
and refuse a unit of the wrong quantity.
Methods:
| Name | Description |
|---|---|
acceleration |
The peak acceleration this segment asks for at |
describe |
The segment as the document wrote it, such as '0.2 in peak to |
Crossing
dataclass
¶
Crossing(*, index: int, lower: ProfileSegment, upper: ProfileSegment, written: float | None, solved: float | None, used: float, step: float, tolerance: float)
Where segment index meets segment index + 1.
written is the boundary the document wrote, Hz, or None; solved
the crossover the two levels give, or None for two segments of one
quantity, which step rather than cross. used is where the profile
changes segment: the solved crossover wherever there is one, so the
profile is continuous, else the written boundary. step is the
ratio of the upper segment's acceleration to the lower's at the
written boundary (at used where nothing is written): 1 where they
meet, more than 1 where the profile would jump up. tolerance is
the share of the solved crossover a written boundary may stand from
it.
Methods:
| Name | Description |
|---|---|
describe |
The crossing in one line, such as 'crossover at 13.93 Hz, |
Attributes:
| Name | Type | Description |
|---|---|---|
kind |
str
|
'crossover' where the two quantities differ, else 'step'. |
off |
float | None
|
How far the written boundary stands from the solved crossover, |
checks |
bool
|
Whether the written boundary is within |
Attributes¶
off
property
¶
How far the written boundary stands from the solved crossover, as a share of it; None where either is missing.
checks
property
¶
Whether the written boundary is within tolerance of the
solved crossover. A boundary left to be solved checks, and so
does a step, which has no crossover to check against (it is
reported as a step instead).
Methods:¶
describe
¶
The crossing in one line, such as 'crossover at 13.93 Hz, written 14 Hz: checks' or 'crossover at 9.889 Hz, written 14 Hz (+41.6 %): does not check, a step of 0.499 there'.
Source code in src/visualdynamics/core/sine_profile.py
SineProfile
¶
SineProfile(segments: Sequence[ProfileSegment], *, tolerance: float = CROSSOVER_TOLERANCE)
A sine profile as segments of constant displacement, velocity or acceleration, with the crossovers solved and checked.
The first segment's start and the last's end must be written; a
boundary between two segments may be written on either side, both
(when they agree), or neither, to be solved. Raises ValueError when
there are no segments, an outer frequency is missing, the two sides
of a boundary are written differently, two segments of one quantity
meet with no boundary written, or the boundaries are not in order
(two levels whose crossover falls outside the band between them do
not meet there), and TypeError for an item that is not a
ProfileSegment.
Attributes:
segments: The ProfileSegment list, low to high.
crossings: One Crossing per boundary, low to high.
tolerance: The share of a solved crossover a written boundary
may stand from it (CROSSOVER_TOLERANCE).
Methods:
| Name | Description |
|---|---|
segment_of |
The index of the segment each frequency falls in, -1 outside |
level |
What the profile asks for at each frequency in |
maxima |
The most the profile asks for in each quantity and where: |
breakpoints |
The profile as acceleration breakpoints a tone interpolates |
tone |
The tone a controller runs for this profile: its acceleration |
specification |
The profile as a |
describe |
One line per segment and per crossing, low to high, ending with |
Attributes:
| Name | Type | Description |
|---|---|---|
bounds |
ndarray
|
The frequencies the segments change at, Hz, low to high, the |
band |
tuple[float, float]
|
(low, high), Hz. |
checks |
bool
|
Whether every written breakpoint checks against its crossover |
continuous |
bool
|
Whether the profile changes segment without a step anywhere: |
Source code in src/visualdynamics/core/sine_profile.py
Attributes¶
bounds
property
¶
The frequencies the segments change at, Hz, low to high, the band's two ends included: the solved crossovers where there are any, the written boundaries of steps.
checks
property
¶
Whether every written breakpoint checks against its crossover
(Crossing.checks).
continuous
property
¶
Whether the profile changes segment without a step anywhere: every boundary a crossover (not two segments of one quantity at different levels) and every written one checks.
Methods:¶
segment_of
¶
The index of the segment each frequency falls in, -1 outside the band. A frequency on a boundary belongs to the segment above it, and the band's top to the last segment.
Source code in src/visualdynamics/core/sine_profile.py
level
¶
What the profile asks for at each frequency in quantity, SI:
each segment's own level carried to quantity by convert. Not
a number outside the band, where the profile asks for nothing.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
frequencies
|
array - like
|
Hz. |
required |
quantity
|
str
|
'displacement', 'velocity' or 'acceleration'. |
'acceleration'
|
convention
|
str
|
For a displacement, 'peak' or 'peak to peak'; ignored for the other two. |
'peak'
|
Returns:
| Type | Description |
|---|---|
ndarray
|
The level, the shape of |
Source code in src/visualdynamics/core/sine_profile.py
maxima
¶
The most the profile asks for in each quantity and where: {'displacement': (Hz, m peak to peak), 'velocity': (Hz, m/s peak), 'acceleration': (Hz, m/s**2 peak)}. These are what go against a shaker's stroke, velocity and force ratings. Each quantity is a power law within a segment, so its largest value is at one of the segment's ends; both sides of a step are counted.
Source code in src/visualdynamics/core/sine_profile.py
breakpoints
¶
The profile as acceleration breakpoints a tone interpolates
within accuracy of it: (Hz, m/s**2 peak), ascending.
A tone interpolates its amplitude linearly in sweep time, which is
linear in log frequency on a logarithmic sweep and in frequency on
a linear one (SineTone.target). A constant acceleration needs
only its two ends, and so does a constant velocity on a linear
sweep; any other segment is cut into equal steps of the sweep's
own frequency axis, as few as keep the interpolated level within
accuracy of the profile everywhere along it. Raises ValueError
for a law not in LAWS, an accuracy that is not more than zero,
or a profile that is not continuous (continuous), since a
sweep cannot jump.
Source code in src/visualdynamics/core/sine_profile.py
tone
¶
tone(name: str, rate: float, *, law: str = 'log', descending: bool = False, start_time: float = 0.0, channels: int = 1, accuracy: float = ACCURACY) -> SineTone
The tone a controller runs for this profile: its acceleration
as breakpoints (breakpoints) swept at one rate.
Raises ValueError as breakpoints does, and for a rate that is
not a finite number more than zero or a channel count under one.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name
|
str
|
The tone's name. |
required |
rate
|
float
|
oct/min on a logarithmic sweep, Hz/s on a linear one; more than zero, whichever way it sweeps. |
required |
law
|
str
|
'log' or 'linear'. |
'log'
|
descending
|
bool
|
Sweep from the top of the band down. |
False
|
start_time
|
float
|
Seconds after the environment starts that the tone turns on. |
0.0
|
channels
|
int
|
How many control channels the level is asked of, each the same. |
1
|
accuracy
|
float
|
As for |
`ACCURACY`
|
Returns:
| Type | Description |
|---|---|
SineTone
|
Acceleration breakpoints in m/s**2. |
Source code in src/visualdynamics/core/sine_profile.py
specification
¶
specification(response_dof: Sequence[str], rate: float, *, name: str = 'Sine Tone 1', law: str = 'log', descending: bool = False, accuracy: float = ACCURACY, comment: str = '') -> SineSweepSpecification
The profile as a SineSweepSpecification of one tone
(tone) asked of every control channel in response_dof, in
m/s**2.
Raises ValueError as tone does, and when response_dof is
empty.
Source code in src/visualdynamics/core/sine_profile.py
describe
¶
One line per segment and per crossing, low to high, ending with the verdict: 'every crossover checks' or how many do not.
Source code in src/visualdynamics/core/sine_profile.py
Functions:¶
convert
¶
A tone's peak level in one quantity as its peak level in another,
at its frequency: value (2 pi f)**(n_source - n_target).
The one place the relation between displacement, velocity and acceleration of a tone is written; every reading of a profile goes through it. A level that is not a number stays not a number.
Raises ValueError for a quantity that is not one of POWERS or a
frequency that is not more than zero.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
value
|
float or array - like
|
The peak level, SI. |
required |
frequency
|
float or array - like
|
Hz, more than zero; broadcast against |
required |
source
|
str
|
'displacement', 'velocity' or 'acceleration'. |
required |
target
|
str
|
'displacement', 'velocity' or 'acceleration'. |
required |
Returns:
| Type | Description |
|---|---|
ndarray
|
The peak level in |
Source code in src/visualdynamics/core/sine_profile.py
crossover
¶
The frequency at which two segments, each (quantity, peak level in SI), ask for the same amplitude; None when they hold the same quantity, which meet in a step rather than cross.
Raises ValueError for a level that is not a finite number more than zero.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
first
|
tuple of (str, float)
|
Each segment's quantity and its peak level, SI. |
required |
second
|
tuple of (str, float)
|
Each segment's quantity and its peak level, SI. |
required |
Returns:
| Type | Description |
|---|---|
float or None
|
Hz. |
Source code in src/visualdynamics/core/sine_profile.py
displacement
¶
displacement(value: float, unit: str, convention: str, *, start: float | None = None, end: float | None = None) -> ProfileSegment
A segment of constant displacement, as the document writes it.
The convention is required, never assumed: test documents write
displacement as double amplitude (peak to peak) as often as peak, and
the one read as the other doubles or halves the motion and moves
every crossover it takes part in. Raises ValueError for a unit that
is not a length, a convention that is not one of CONVENTIONS, or a
level or frequency that is not a finite number more than zero.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
value
|
float
|
The displacement as written. |
required |
unit
|
str
|
Its unit, a length ('in', 'mm', 'm'). |
required |
convention
|
str
|
'peak' or 'peak to peak' (double amplitude). |
required |
start
|
float
|
Hz, where the document writes the segment as starting and ending; left out where the boundary is to be solved. |
None
|
end
|
float
|
Hz, where the document writes the segment as starting and ending; left out where the boundary is to be solved. |
None
|
Returns:
| Type | Description |
|---|---|
ProfileSegment
|
The segment, its level the peak displacement in m. |
Source code in src/visualdynamics/core/sine_profile.py
velocity
¶
velocity(value: float, unit: str, *, start: float | None = None, end: float | None = None) -> ProfileSegment
A segment of constant peak velocity, as the document writes it.
Raises ValueError for a unit that is not a velocity, or a level or frequency that is not a finite number more than zero.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
value
|
float
|
The peak velocity as written. |
required |
unit
|
str
|
Its unit, a velocity ('in/s', 'm/s'). |
required |
start
|
float
|
Hz, as for |
None
|
end
|
float
|
Hz, as for |
None
|
Returns:
| Type | Description |
|---|---|
ProfileSegment
|
The segment, its level in m/s. |
Source code in src/visualdynamics/core/sine_profile.py
acceleration
¶
acceleration(value: float, unit: str, *, start: float | None = None, end: float | None = None) -> ProfileSegment
A segment of constant peak acceleration, as the document writes it.
Raises ValueError for a unit that is not an acceleration, or a level or frequency that is not a finite number more than zero.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
value
|
float
|
The peak acceleration as written. |
required |
unit
|
str
|
Its unit, an acceleration ('g', 'm/s**2'). |
required |
start
|
float
|
Hz, as for |
None
|
end
|
float
|
Hz, as for |
None
|
Returns:
| Type | Description |
|---|---|
ProfileSegment
|
The segment, its level in m/s**2. |
Source code in src/visualdynamics/core/sine_profile.py
interpolated
¶
interpolated(specification: SineSweepSpecification, tone: str | None = None, channel: str | None = None, lines: int = 400) -> tuple[ndarray, ndarray]
A tone's own level as it runs it, carried to acceleration: its
breakpoints interpolated linearly in sweep time (SineTone.target)
at lines frequencies evenly in log across its band. Beside the
profile read_profile reads from the same tone, it shows where the
breakpoints are too far apart for the segment they write.
Raises ValueError as read_profile does for the units, the tone and
the channel, and for fewer than two lines.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
specification
|
SineSweepSpecification
|
Declared units. |
required |
tone
|
str
|
The tone's name; the first by default. |
None
|
channel
|
str
|
The control channel whose column is read; the first by default. |
None
|
lines
|
int
|
How many frequencies. |
400
|
Returns:
| Type | Description |
|---|---|
tuple of ndarray
|
(Hz, m/s**2 peak). |
Source code in src/visualdynamics/core/sine_profile.py
read_profile
¶
read_profile(specification: SineSweepSpecification, tone: str | None = None, channel: str | None = None, *, slope_tolerance: float = SLOPE_TOLERANCE, tolerance: float = CROSSOVER_TOLERANCE) -> SineProfile
Read a tone already written as breakpoints back as segments of constant displacement, velocity or acceleration, its breakpoints written where it changes from one to the next.
Each stretch between two breakpoints is classified by the slope of
its acceleration on log axes: 12.04 dB per octave holds the
displacement constant, 6.02 the velocity and a flat stretch the
acceleration, each within slope_tolerance. A stretch that holds none
of them is refused by name with its slope: a breakpoint typed at the
wrong frequency or level bends the stretches either side of it off
every one of the three, so that refusal is the check a tone's own
breakpoints get. The level a stretch holds is read at its geometric
middle, so a slope a little off splits the difference between its
ends, and the crossovers the levels solve to are checked against the
breakpoints as for any profile.
Consecutive stretches holding the same quantity at the same level,
within tolerance, are one segment, as a built tone writes a
displacement in several breakpoints. The breakpoints where one
segment gives way to another are the profile's written boundaries.
Raises ValueError when the specification's units are not declared or
its quantity is not one of POWERS, for a tone or channel it does not
hold, for a tone that turns back or holds a frequency (a dwell is no
profile), for a level at a breakpoint that is not a finite number
more than zero, for a slope tolerance that is not a finite number
of zero or more, and for a stretch whose slope holds none of the
three quantities, naming it in dB per octave.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
specification
|
SineSweepSpecification
|
Declared units. |
required |
tone
|
str
|
The tone's name; the first by default. |
None
|
channel
|
str
|
The control channel whose column is read; the first by default. |
None
|
slope_tolerance
|
float
|
dB per octave. |
`SLOPE_TOLERANCE`
|
tolerance
|
float
|
As for |
`CROSSOVER_TOLERANCE`
|
Returns:
| Type | Description |
|---|---|
SineProfile
|
Every boundary written, from the tone's breakpoints. |
Source code in src/visualdynamics/core/sine_profile.py
853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 | |