visualdynamics.core.drive_prediction¶
drive_prediction
¶
The drives a specification asks for, predicted from an FRF before the test, and what they ask of the amplifiers and the shakers.
A random test is controlled on spectra. The control channels' spectral density matrix is the drives' passed through the plant,
S_yy = conj(H) S_vv H^T,
at every line, where H is the FRF matrix from the drives to the
control channels (one row per control channel, one column per drive,
in g/V or (m/s^2)/V), S_vv the drives' spectral density matrix and
H^T the transpose of H. The conjugate is on the left because a
cross term between channels a and b is stored as conj(X_a) X_b
(TimeHistory.compute_cpsds), so outputs Y = H V have the matrix
E[conj(Y) Y^T] = conj(H) E[conj(V) V^T] H^T. Written the other way
round, H S_vv H^H gives other autospectra wherever the plant and the
cross terms are both complex. Turned round, the drives that
would produce a target S_ref are
S_vv = conj(Z) S_ref Z^T,
with Z an inverse of H. On one drive and one control channel the
matrices are single numbers and this is the drive autospectrum a
controller starts from, G_vv = G_ref / |H|^2. With several, H is
inverted line by line, and where it is ill-conditioned (two drives that
reach the control channels in nearly the same proportions, or a line
where the plant barely responds) the plain inverse is made of noise, so
it is regularized, in one of two stated ways:
- Truncated (the default): the singular values of
Hat a line belowtolerancetimes that line's largest are dropped, and the pseudo-inverse is built from the rest. With the default tolerance of one hundredth, a line whose condition number is over 100 loses its weakest direction, the target there is met only in the directions kept, and the reading says so. On one drive and one channel nothing is ever dropped, and the inverse is exactly1 / H. - Tikhonov:
Z = (H^H H + lambda I)^(-1) H^H, withlambdathe statedweighttimes the largest squared singular value ofHanywhere in the band, so the weight has no units and means the same for any plant. Where|H|^2is large againstlambdathis is the inverse; where the plant barely responds it stays small instead of growing without limit. On one channel it isH* / (|H|^2 + lambda).
Either way the prediction carries the control channels' response to
the drives it predicts, conj(H) S_vv H^T, and the lines where that misses
the target by more than UNMET_DB are the lines the regularization
gave up, named as bands. Any other channel the FRF holds against the
same drives (a force gauge, a current monitor, an accelerometer on the
article) is predicted the same way, its autospectrum the diagonal of
conj(H_u) S_vv H_u^T.
The prediction is open loop, made once, before anything is played: it is what a controller's first drive would be with this FRF as its plant estimate, and it does not iterate.
What it asks of the rig
Every quantity is a Gaussian random signal with a predicted
autospectrum, so its rms is the root of the area under it
(compliance.rms) and its expected largest peak over a run of
duration seconds is the rms times the Gaussian peak factor
sqrt(2 ln(2 nu0 T)), with nu0 its own crossing rate by Rice's formula
(time_statistics.rice_rate and expected_largest_peak), counted both
ways: a rating is reached by a peak of either sign, the drive's limit
and the travel each way alike, so the largest of either sign is the
one judged. A stated
peak_factor replaces that, for a controller that clips its drive at
a set multiple of the rms. Each quantity is judged on its peak:
- the drive at each drive, against
drive_limit, in the drive's unit (the volts a source or amplifier input accepts, or the newtons of a force-controlled drive); - for a drive given a
ShakerRating, its shaker, read at its table channel (the acceleration measured at the drive's own DOF, or the one control channel of a single-input test): the force, the moving mass times the peak acceleration; the velocity, from the table's acceleration autospectrum over (2 pi f)^2; and the travel each way from the middle of the stroke, from the autospectrum over (2 pi f)^4, with the body's motion counted. All three go throughcore.shaker's one rule; - any other channel given a peak
limitsentry, in that channel's unit: a force gauge in newtons, a current monitor in the volts its scale gives at the amplifier's current rating.
Each judged quantity names the line that contributes most to its mean square and the band of lines holding half of it, so the reading can say which drive or shaker runs out first and at what frequency.
References
The spectral relations between the inputs and outputs of a linear system with several inputs, and the Gaussian peak statistics, are Bendat and Piersol's; the singular value decomposition, the pseudo-inverse and its truncation are Golub and Van Loan's; the regularized inverse is Tikhonov's, in the form Hansen sets out.
- Bendat, J. S., & Piersol, A. G. (2010). Random Data: Analysis and Measurement Procedures, 4th ed. Wiley. The output spectral density of a multiple-input linear system and the expected largest peak of a stationary Gaussian process.
- Golub, G. H., & Van Loan, C. F. (2013). Matrix Computations, 4th ed. Johns Hopkins University Press. The singular value decomposition, the pseudo-inverse and its truncation at a relative tolerance.
- Hansen, P. C. (1998). Rank-Deficient and Discrete Ill-Posed
Problems: Numerical Aspects of Linear Inversion. SIAM. Tikhonov
regularization as a filter on the singular values,
sigma / (sigma^2 + lambda), beside truncation.
Classes:
| Name | Description |
|---|---|
Demand |
One quantity the predicted drives ask of the rig, against its |
DrivePrediction |
The drives a specification asks for through an FRF, the |
Functions:
| Name | Description |
|---|---|
regularized_inverse |
An inverse of the plant at every line, truncated or Tikhonov. |
inverse_from_svd |
|
through |
The plant's conjugate times |
predict_drives |
The drives that would make the specification at its control |
Classes¶
Demand
dataclass
¶
Demand(kind: str, channel: str, dimension: str, rms: float, peak: float, rating: float | None, share: float | None, frequency: float, band: tuple[float, float])
One quantity the predicted drives ask of the rig, against its rating.
kind is 'drive', 'force', 'velocity', 'stroke' (the travel each
way from the middle of the stroke) or 'response' (another channel
against its own limit). channel names the drive or the channel it
is read at, dimension the quantity of rms, peak and rating,
all in SI. For the force, the velocity and the travel, peak is
what core.shaker judges: the force at the peak acceleration, the
velocity and the travel against the body. share is the peak over
the rating, by core.shaker's rule for a shaker, None where no
rating was given. frequency is the line contributing most to the
mean square and band the span of the lines holding half of it.
Methods:
| Name | Description |
|---|---|
describe |
The demand in a sentence, in |
Attributes:
| Name | Type | Description |
|---|---|---|
exceeds |
bool | None
|
Whether the peak is over the rating; None without one. |
name |
str
|
The quantity and where, in words: 'travel each way at 1Z+'. |
Attributes¶
Methods:¶
describe
¶
describe(unit_system: UnitSystem | None = None) -> str
The demand in a sentence, in unit_system's units.
Source code in src/visualdynamics/core/drive_prediction.py
DrivePrediction
dataclass
¶
DrivePrediction(frequency: ndarray, drives: list[str], drive_quantity: str, rows: list[str], row_quantities: list[str], controls: list[int], drive_csd: ndarray, asd: ndarray, target: ndarray, control: ndarray, judged: ndarray, kept: ndarray, condition: ndarray, method: str, tolerance: float, weight: float, duration: float, peak_factor: float | None, demands: list[Demand] = list(), notes: list[str] = list(), row_channels: list[tuple[str, str]] = list(), drive_channels: list[tuple[str, str]] = list(), plain_notes: int = 0)
The drives a specification asks for through an FRF, the responses they make, and what they ask of the rig.
Everything is in SI, on the FRF's lines. drive_csd is the drives'
spectral density matrix (lines, drives, drives) and asd the
predicted autospectrum of every FRF row (lines, rows); target is
the specification's matrix on the control channels and control
the control channels' predicted matrix. judged marks the lines
predicted: above zero hertz, the plant finite and every control
autospectrum written; the arrays are not-a-number elsewhere.
kept is how many directions the inverse kept at each line and
condition the plant's condition number there. demands are the
quantities judged, notes what the prediction should be read with.
Methods:
| Name | Description |
|---|---|
with_ratings |
This prediction judged against other ratings, the drives and |
drive_asd |
One drive's predicted autospectrum, by index or label. |
drive_coherence |
The coherence between two predicted drives at every line: |
missed |
The lines where a control channel's prediction misses the |
describe |
The prediction in sentences: the verdict, each demand, the |
Attributes:
| Name | Type | Description |
|---|---|---|
single |
bool
|
One drive and one control channel. |
shortfall_db |
ndarray
|
The control channels' predicted autospectra over the target, |
unmet |
ndarray
|
The lines where some control channel's prediction misses the |
unmet_bands |
list[tuple[float, float]]
|
(first, last) frequency of each run of unmet lines. |
judged_demands |
list[Demand]
|
The demands with a rating, most of their rating first. |
first_out |
Demand | None
|
The demand nearest its rating, or furthest past it: what runs |
fits |
bool | None
|
Whether every rated demand is within its rating; None when |
headroom_db |
float | None
|
How far the level could rise before the first demand reaches |
title |
str
|
What was predicted, in a few words. |
Attributes¶
shortfall_db
property
¶
The control channels' predicted autospectra over the target, in dB, (lines, controls): zero where the inverse met it.
unmet
property
¶
The lines where some control channel's prediction misses the
target by more than UNMET_DB (missed).
unmet_bands
property
¶
(first, last) frequency of each run of unmet lines.
judged_demands
property
¶
judged_demands: list[Demand]
The demands with a rating, most of their rating first.
first_out
property
¶
first_out: Demand | None
The demand nearest its rating, or furthest past it: what runs out first as the level comes up. None when nothing is rated.
fits
property
¶
Whether every rated demand is within its rating; None when nothing is rated.
headroom_db
property
¶
How far the level could rise before the first demand reaches its rating, in dB on the test level (twenty times the log of one over its share: every peak scales with the level); negative when it is already past. None when nothing is rated.
Methods:¶
with_ratings
¶
with_ratings(*, drive_limit: float | Mapping[str, float] | None = None, shakers: ShakerRating | Mapping[str, ShakerRating] | None = None, tables: Mapping[str, str] | None = None, limits: Mapping[str, float] | None = None, duration: float = DURATION, peak_factor: float | None = None) -> DrivePrediction
This prediction judged against other ratings, the drives and
responses kept: the inverse does not depend on the ratings, so a
rating moved does not predict again. The ratings are taken
exactly as predict_drives takes them, and one not given here
is not judged, whatever the prediction was made with.
Returns:
| Type | Description |
|---|---|
DrivePrediction
|
A new prediction; this one is unchanged. |
Raises:
| Type | Description |
|---|---|
(ValueError, TypeError)
|
As |
Source code in src/visualdynamics/core/drive_prediction.py
drive_asd
¶
One drive's predicted autospectrum, by index or label.
Source code in src/visualdynamics/core/drive_prediction.py
drive_coherence
¶
The coherence between two predicted drives at every line: one where the specification makes them copies of one another, zero where it makes them independent.
Source code in src/visualdynamics/core/drive_prediction.py
missed
¶
The lines where a control channel's prediction misses the
target by more than UNMET_DB, by its place among the control
channels; with none, where any of them does. A line where the
prediction is not a number, or zero against a target, misses.
Source code in src/visualdynamics/core/drive_prediction.py
describe
¶
describe(unit_system: UnitSystem | None = None) -> list[str]
The prediction in sentences: the verdict, each demand, the unmet bands and the notes.
Source code in src/visualdynamics/core/drive_prediction.py
Functions:¶
regularized_inverse
¶
regularized_inverse(plant: Any, *, method: str = 'truncated', tolerance: float = TRUNCATION, weight: float = TIKHONOV, scale: float | None = None) -> tuple[ndarray, ndarray]
An inverse of the plant at every line, truncated or Tikhonov.
plant is (lines, rows, columns), one row per control channel and
one column per drive. The inverse is (lines, columns, rows), built
from the singular value decomposition at each line: the truncated
one keeps the singular values of at least tolerance times the
line's largest, and the Tikhonov one filters every singular value
sigma to sigma / (sigma^2 + lambda), with lambda weight times
scale, the largest squared singular value over every line when
scale is not given. A line holding a value that is not a number
has an inverse of not-a-number; a line that is zero has an inverse
of zero. The module docstring says how the two differ.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
plant
|
array_like
|
The FRF matrix, (lines, rows, columns), complex. |
required |
method
|
('truncated', 'tikhonov')
|
Which inverse. |
'truncated'
|
tolerance
|
float
|
The truncated inverse's relative threshold, between 0 and 1. |
`TRUNCATION`
|
weight
|
float
|
The Tikhonov weight, above zero. |
`TIKHONOV`
|
scale
|
float
|
The squared singular value the weight is a share of. |
None
|
Returns:
| Type | Description |
|---|---|
tuple of numpy.ndarray
|
The inverse, (lines, columns, rows), and the number of directions kept at each line: the singular values kept by the truncation, or those whose square is at least lambda. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
Source code in src/visualdynamics/core/drive_prediction.py
inverse_from_svd
¶
inverse_from_svd(u: Any, sigma: Any, vh: Any, *, method: str = 'truncated', tolerance: float = TRUNCATION, weight: float = TIKHONOV, scale: float | None = None) -> tuple[ndarray, ndarray]
regularized_inverse from the plant's singular value
decomposition, (lines, rows, k), (lines, k) and (lines, k, columns)
as numpy.linalg.svd returns them with full_matrices=False:
the prediction decomposes the plant once and reads its condition
number off the same singular values.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
u
|
array_like
|
The decomposition, every line finite. |
required |
sigma
|
array_like
|
The decomposition, every line finite. |
required |
vh
|
array_like
|
The decomposition, every line finite. |
required |
method
|
str
|
As |
'truncated'
|
tolerance
|
str
|
As |
'truncated'
|
weight
|
str
|
As |
'truncated'
|
scale
|
str
|
As |
'truncated'
|
Returns:
| Type | Description |
|---|---|
tuple of numpy.ndarray
|
The inverse, (lines, columns, rows), and the directions kept at each line. |
Source code in src/visualdynamics/core/drive_prediction.py
through
¶
The plant's conjugate times density times the plant's
transpose, conj(H) S H^T, at every line: the spectral density
matrix of the outputs of a linear system whose inputs have the
spectral density matrix density, each cross term stored as
compute_cpsds stores it, conj(X_a) X_b. With an inverse for
plant, the inputs that produce a target. Both (lines, ...)
arrays; the result is (lines, rows, rows).
Source code in src/visualdynamics/core/drive_prediction.py
predict_drives
¶
predict_drives(frf: Frf, specification: Psd, *, method: str = 'truncated', tolerance: float = TRUNCATION, weight: float = TIKHONOV, drive_limit: float | Mapping[str, float] | None = None, shakers: ShakerRating | Mapping[str, ShakerRating] | None = None, tables: Mapping[str, str] | None = None, limits: Mapping[str, float] | None = None, duration: float = DURATION, peak_factor: float | None = None, progress: Callable[[int, int], None] | None = None) -> DrivePrediction
The drives that would make the specification at its control channels through this FRF, the responses they would make at every channel the FRF holds, and what they ask of the amplifiers and the shakers.
prediction = predict_drives(
frf, specification, drive_limit=10.0,
shakers=ShakerRating(force=26700.0, stroke=0.0508,
velocity=1.8, moving_mass=60.0))
The FRF is from the drives (volts at the amplifier inputs, or force)
to the control channels and to any other channel measured against
the same drives; the specification names the control channels, a
full cross-spectral matrix when there are several and its
autospectrum when there is one. The drives are conj(Z) S_ref Z^T
at every line, with Z the regularized inverse method names, and
the module docstring says how each is regularized and why the
conjugate is on the left, how the peaks are read and what each
rating is judged against. The prediction is open
loop: what a controller's first drive would be with this FRF as its
plant, made once.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
frf
|
Frf
|
Drives to responses, units declared, every control channel measured against every drive. |
required |
specification
|
Psd
|
The target at the control channels (a |
required |
method
|
('truncated', 'tikhonov')
|
The inverse ( |
'truncated'
|
tolerance
|
float
|
The truncated inverse's share of each line's largest singular value. |
`TRUNCATION`
|
weight
|
float
|
The Tikhonov weight, a share of the largest squared singular value in the band. |
`TIKHONOV`
|
drive_limit
|
float or mapping
|
The peak drive each drive may reach, in the drive's SI unit (V, or N for a force drive); one number for every drive, or by drive. |
None
|
shakers
|
ShakerRating or mapping
|
The shaker behind each drive ( |
None
|
tables
|
mapping
|
The channel each shaker's table is measured at, by drive. By default the acceleration at the drive's own DOF, or the control channel of a single-input test. |
None
|
limits
|
mapping
|
A peak limit for any channel of the FRF, by channel, in its SI unit: a force gauge in N, a current monitor in its volts. |
None
|
duration
|
float
|
The run's length in seconds, for the expected largest peaks. |
`DURATION`
|
peak_factor
|
float
|
A stated peak over rms for every quantity, in place of the expected largest peak. |
None
|
progress
|
callable
|
Told |
None
|
Returns:
| Type | Description |
|---|---|
DrivePrediction
|
The drives' matrix, every channel's predicted autospectrum, the control channels' prediction beside the target, the demands against their ratings, and the notes. |
Raises:
| Type | Description |
|---|---|
TypeError
|
If |
ValueError
|
If either object holds no records or undeclared units, the drives are of two quantities, a control channel is missing from the FRF or held there as another quantity, a control channel is not measured against every drive, the specification lacks an autospectrum or (with several control channels) a cross term, the two share no line, the plant is zero or not a number at every line shared, a setting is out of range, or a rating names a drive or channel the FRF does not have or a shaker has no acceleration measured at its table. |
Source code in src/visualdynamics/core/drive_prediction.py
809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 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 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 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 | |