visualdynamics.core.matrix_comparison¶
matrix_comparison
¶
A measured cross-spectral density matrix judged against the one it was controlled to, cell by cell.
A multi-input random test is controlled to a matrix, not to a set of separate spectra. The diagonal holds each control channel's autospectrum, its level; the cross terms hold how each pair of channels moves together. A run can hold every level inside its band and still not be the test that was asked for, if two channels that were to move together moved independently, or moved in opposition. So the matrix is judged in two parts, each in the quantity it is specified in.
The diagonal
Each autospectrum is compared with its specification exactly as a
single channel is (compliance.compare, through compliance.compare_all):
the measured power against the specified power cell by cell, the share
of the band outside the abort limits, and the RMS error in dB. A level
is out by the same two states the channel's figure shows
(compliance.lines_state, compliance.rms_state), and the diagonal as
a whole passes or fails by the same rule a set of channels does
(compliance.verdict).
The cross terms
A cross term is complex, and it is not a level: its real part is not the power of either channel, and it moves with the phase. It is judged as the two quantities it is written in. The ordinary coherence of a pair,
gamma^2 = |S_pq|^2 / (S_pp S_qq),
is the share of either channel's power that moves linearly with the other, 0 to 1 and free of units. Its phase,
phi = arg S_pq,
is the angle by which they move apart, in degrees. Both are read from the specification and from the measurement at every measured line. The specification is read the way it is drawn: between breakpoints the magnitude of each term log-log and its phase linearly, the way the authoring sheet reads a cross term, and on a specification of lines or bands the value of the bin the line falls in. A pair of terms that are power laws with one coherence between breakpoints keeps that coherence exactly at every line between them.
A line is out when the measured coherence is further than the coherence
tolerance from the specified one (an absolute difference, since
coherence is already a ratio), or when the phase differs by more than
the phase tolerance, the difference taken the short way round the
circle. The phase is judged only where the specified coherence is at
least the floor: two channels that are asked to be nearly independent
have no phase to hold, and the estimate of it scatters without bound
as the coherence falls (its random error is sqrt(1 - gamma^2) /
(|gamma| sqrt(2 n)) radians at n averages). The coherence estimate
scatters too, by sqrt(2) (1 - gamma^2) / (|gamma| sqrt(n)) of its value:
about 0.05 at a coherence of 0.5 and a hundred averages, so a run that
is exactly its specification puts a few percent of its lines past a
tolerance of 0.1 by chance. A cross term is therefore out when either
reading is out on more than compliance.LINES_PERCENT of the lines it
judged, the share a level is allowed outside its abort limits, and not
on its worst line. And where the tolerance is inside twice the
estimate's scatter at the measurement's averages (SCATTER_SIGMAS),
the line is not judged at all: with too few averages for the tolerance
asked, a run that is its specification fails by chance, and the test
needs more averages, not a verdict.
The defaults (COHERENCE_TOLERANCE of 0.1 either way, PHASE_TOLERANCE
of 10 degrees either way, judged at a specified coherence of at least
COHERENCE_FLOOR, 0.5) are examples of what test documents ask for.
The test plan sets its own.
A pair the specification writes no cross term for is not judged and is
said to be not specified: an unstated pair is not a statement that the
channels are independent (SpecificationDraft makes the same rule).
The whole matrix passes when the diagonal passes and no cross term is
out, and has no verdict when nothing was judged.
The per-line summary
For watching a run, or a step-up, at a glance, each measured line
carries the RMS over the control channels of their level errors in dB
(each channel's error read from its own comparison cells,
compliance.judge, and spread over the lines each cell covers), and
how many cross terms are out on either reading at that line.
compare_matrix reads a specification and a CPSD (compute_cpsds)
and returns a MatrixComparison; cross_term_lines is the per-line
judgment of one cross term on plain arrays.
References
The cross-spectral density matrix, the ordinary coherence and the random errors of coherence and phase estimates are Bendat and Piersol's.
- Bendat, J. S., & Piersol, A. G. (2010). Random Data: Analysis and Measurement Procedures, 4th ed. Wiley. The cross-spectral density matrix of several records, the ordinary coherence read from it, and the normalized random errors of the coherence and the phase estimates.
Classes:
| Name | Description |
|---|---|
LevelCheck |
One control channel's autospectrum against its specification, |
CrossCheck |
One pair of control channels' cross term, its coherence and its |
MatrixCell |
What one cell of the grid shows: its place, which reading it is |
MatrixComparison |
A measured cross-spectral matrix judged against its |
Functions:
| Name | Description |
|---|---|
level_text |
A level error as the grid and the table write it, '+0.4 dB', |
wrapped_degrees |
An angle in degrees taken the short way round the circle, into |
cross_term_lines |
One cross term judged line by line, on plain arrays: the array |
record_grid |
The records of a spectral matrix laid out as the matrix: its |
compare_matrix |
A measured cross-spectral matrix judged against its |
Classes¶
LevelCheck
dataclass
¶
LevelCheck(channel: str, spec_record: int, measured_record: int | None, result: dict[str, Any], state: str, reason: str = '', error_db: ndarray = (lambda: zeros(0))(), lines_out: ndarray | None = None)
One control channel's autospectrum against its specification,
the diagonal entry of a MatrixComparison.
result is compliance.compare's, empty when the channel was not
measured. state is 'in' or 'out' (by compliance.lines_state and
compliance.rms_state), 'not judged' when nothing was compared,
'not measured' when the measurement holds no autospectrum for the
channel, or 'refused' with the reason in reason. error_db is the
channel's level error at every measured line, measured over
specified in dB, not a number where it was not judged. lines_out
marks the measured lines outside the abort limits, as the channel's
own comparison marks them (compliance.exceedances), None where the
channel was not compared.
Methods:
| Name | Description |
|---|---|
words |
The verdict in words, as the panel lists it: 'in: 2.1 % of |
Methods:¶
words
¶
The verdict in words, as the panel lists it: 'in: 2.1 % of
lines outside abort, +0.4 dB rms', the two numbers the
channel's own figure carries (compliance.readouts).
Source code in src/visualdynamics/core/matrix_comparison.py
CrossCheck
dataclass
¶
CrossCheck(row: int, column: int, label: str, spec_record: int | None, measured_record: int | None, coherence_state: str, phase_state: str, reason: str = '', specified_coherence: ndarray = (lambda: zeros(0))(), measured_coherence: ndarray = (lambda: zeros(0))(), specified_phase: ndarray = (lambda: zeros(0))(), measured_phase: ndarray = (lambda: zeros(0))(), lines: dict[str, ndarray] = dict(), notes: tuple[str, ...] = (), averages: float | None = None)
One pair of control channels' cross term, its coherence and its phase, judged at every measured line.
row and column are the pair's channels (row < column);
above the diagonal the grid shows the coherence and below it the
phase, of this one cross term, which is the term of row against
column (the term the other way round is its conjugate, with the
phase reversed). The arrays are one value per measured line: the
specified and measured coherence and phase (degrees) and what
cross_term_lines made of them.
coherence_state and phase_state are 'in' or 'out' (out when
more than compliance.LINES_PERCENT of the lines judged are out),
'not judged' when no line was (the reason in reason), 'not
specified' when the specification writes no cross term for the
pair, 'not measured' when the measurement holds none, or 'refused'.
notes say what was not judged on lines that were. averages is
the number of averages the measured cross term holds, None when the
measurement does not say.
Methods:
| Name | Description |
|---|---|
words |
One reading's verdict in words, as the grid writes it over |
Attributes:
| Name | Type | Description |
|---|---|---|
coherence_lines |
int
|
How many lines the coherence was judged on. |
phase_lines |
int
|
How many lines the phase was judged on. |
coherence_percent |
float
|
The share of the lines judged whose coherence is out, in |
phase_percent |
float
|
The share of the lines judged whose phase is out, in |
state |
str
|
The pair's one state: 'out' when either reading is, 'in' |
Attributes¶
coherence_percent
property
¶
The share of the lines judged whose coherence is out, in percent; not a number when none was judged.
phase_percent
property
¶
The share of the lines judged whose phase is out, in percent; not a number when none was judged.
state
property
¶
The pair's one state: 'out' when either reading is, 'in' when a reading was judged and none is out, else the coherence's state (why nothing was judged).
Methods:¶
words
¶
words(reading: str, coherence_tolerance: float = COHERENCE_TOLERANCE, phase_tolerance: float = PHASE_TOLERANCE, coherence_floor: float = COHERENCE_FLOOR) -> str
One reading's verdict in words, as the grid writes it over
the cell: 'coherence in: 0 of 397 lines past ±0.10', 'phase out:
23.4 % of 397 lines past ±10°', 'phase not judged: specified
coherence under 0.50'. reading is 'coherence' or 'phase'.
Source code in src/visualdynamics/core/matrix_comparison.py
MatrixCell
dataclass
¶
What one cell of the grid shows: its place, which reading it is ('level' on the diagonal, 'coherence' above it, 'phase' below it), its state and its verdict in words.
MatrixComparison
dataclass
¶
MatrixComparison(channels: tuple[str, ...], levels: tuple[LevelCheck, ...], pairs: tuple[CrossCheck, ...], frequency: ndarray, scale_db: float = 0.0, coherence_tolerance: float = COHERENCE_TOLERANCE, phase_tolerance: float = PHASE_TOLERANCE, coherence_floor: float = COHERENCE_FLOOR, notes: tuple[str, ...] = ())
A measured cross-spectral matrix judged against its
specification: what compare_matrix returns.
channels are the control channels the two share, in the
specification's order, labeled (channel_labels); levels holds
one LevelCheck per channel and pairs one CrossCheck per pair,
row by row above the diagonal. frequency is the measured lines,
and the per-line summary is level_rms_db (the RMS over the
channels of their level errors in dB) and cross_out (how many
cross terms are out on either reading at each line).
Methods:
| Name | Description |
|---|---|
pair |
The cross term of two channels, either way round. |
cell |
What one cell of the grid shows: on the diagonal the |
with_tolerances |
The same matrix with its cross terms judged again at other |
table |
The summary table: one row per reading of the matrix, the |
describe |
The verdict in one line: 'fails: levels pass on 3 channels; |
Attributes:
| Name | Type | Description |
|---|---|---|
size |
int
|
How many channels: the grid is this many on a side. |
level_rms_db |
ndarray
|
The RMS over the control channels of their level errors in |
cross_out |
ndarray
|
How many cross terms are out on either reading at each |
level_verdict |
dict[str, Any]
|
The diagonal's verdict by the rule a set of channels is |
passed |
bool | None
|
True when the diagonal passes and no cross term judged is |
Attributes¶
level_rms_db
property
¶
The RMS over the control channels of their level errors in dB, at each measured line; not a number where no channel was judged.
cross_out
property
¶
How many cross terms are out on either reading at each measured line.
level_verdict
property
¶
The diagonal's verdict by the rule a set of channels is
judged by (compliance.verdict).
passed
property
¶
True when the diagonal passes and no cross term judged is out, False when either fails, None when nothing was judged.
Methods:¶
pair
¶
pair(row: int, column: int) -> CrossCheck
The cross term of two channels, either way round.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
row
|
int
|
The two channels, by position; not the same one. |
required |
column
|
int
|
The two channels, by position; not the same one. |
required |
Returns:
| Type | Description |
|---|---|
CrossCheck
|
The pair's judgment. |
Source code in src/visualdynamics/core/matrix_comparison.py
cell
¶
cell(row: int, column: int) -> MatrixCell
What one cell of the grid shows: on the diagonal the channel's level, above it the pair's coherence, below it the pair's phase.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
row
|
int
|
The cell, by position. |
required |
column
|
int
|
The cell, by position. |
required |
Returns:
| Type | Description |
|---|---|
MatrixCell
|
Its reading, state and words. |
Source code in src/visualdynamics/core/matrix_comparison.py
with_tolerances
¶
with_tolerances(coherence_tolerance: float | None = None, phase_tolerance: float | None = None, coherence_floor: float | None = None) -> MatrixComparison
The same matrix with its cross terms judged again at other tolerances.
Nothing is read again: the levels and every coherence and phase are kept, and only the judgment moves, so a tolerance changed in the app redraws without comparing the levels a second time.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
coherence_tolerance
|
float
|
The new settings; this comparison's for any not given. |
None
|
phase_tolerance
|
float
|
The new settings; this comparison's for any not given. |
None
|
coherence_floor
|
float
|
The new settings; this comparison's for any not given. |
None
|
Returns:
| Type | Description |
|---|---|
MatrixComparison
|
The comparison at those tolerances. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If a setting is out of range. |
Source code in src/visualdynamics/core/matrix_comparison.py
table
¶
The summary table: one row per reading of the matrix, the levels first, then each pair's coherence and phase.
Returns:
| Type | Description |
|---|---|
list of dict of str to str
|
Each with 'label', 'reading' ('level', 'coherence' or 'phase'), 'value' (the RMS error, or the share of lines out, as shown), 'state' and 'words'. |
Source code in src/visualdynamics/core/matrix_comparison.py
describe
¶
The verdict in one line: 'fails: levels pass on 3 channels; 1 of 3 cross terms out (coherence 1, phase 0); 1 pair not specified'.
Source code in src/visualdynamics/core/matrix_comparison.py
Functions:¶
level_text
¶
A level error as the grid and the table write it, '+0.4 dB', rounded before its sign is read so that -0.04 is not written '-0.0 dB'; '—' for one that was not read.
Source code in src/visualdynamics/core/matrix_comparison.py
wrapped_degrees
¶
An angle in degrees taken the short way round the circle, into -180 up to but not including 180: a phase 350 degrees off is 10 degrees off.
Source code in src/visualdynamics/core/matrix_comparison.py
cross_term_lines
¶
cross_term_lines(specified_coherence: Any, specified_phase: Any, measured_coherence: Any, measured_phase: Any, *, coherence_tolerance: float = COHERENCE_TOLERANCE, phase_tolerance: float = PHASE_TOLERANCE, coherence_floor: float = COHERENCE_FLOOR, averages: float | None = None) -> dict[str, ndarray]
One cross term judged line by line, on plain arrays: the array
form of the judgment compare_matrix makes of each pair.
A line's coherence is judged where both coherences are numbers, and
is out when they differ by more than coherence_tolerance. Its
phase is judged where the specified coherence is at least
coherence_floor, the measured coherence is a number and both
phases are, and is out when the two differ by more than
phase_tolerance the short way round (wrapped_degrees). A
reading exactly on its tolerance is inside.
With averages, the number of averages the measured estimate holds
(half its statistical degrees of freedom), a line is judged only
where each tolerance is at least SCATTER_SIGMAS times the
estimate's random error at the specified coherence: sqrt(2)
(1 - gamma^2) |gamma| / sqrt(n) for the coherence and sqrt(1 -
gamma^2) / (|gamma| sqrt(2 n)) radians for the phase.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
specified_coherence
|
array - like
|
The ordinary coherence at each line, 0 to 1; not a number where it is undefined. |
required |
measured_coherence
|
array - like
|
The ordinary coherence at each line, 0 to 1; not a number where it is undefined. |
required |
specified_phase
|
array - like
|
The phase at each line, in degrees. |
required |
measured_phase
|
array - like
|
The phase at each line, in degrees. |
required |
coherence_tolerance
|
float
|
How far the coherence may be off, either way. |
`COHERENCE_TOLERANCE`
|
phase_tolerance
|
float
|
How far the phase may be off, in degrees either way. |
`PHASE_TOLERANCE`
|
coherence_floor
|
float
|
The specified coherence the phase is judged from. |
`COHERENCE_FLOOR`
|
averages
|
float
|
The averages the measurement holds; when omitted no line is set aside for the scatter of its estimates. |
None
|
Returns:
| Type | Description |
|---|---|
dict of str to numpy.ndarray
|
'coherence_error' (measured less specified) and 'phase_error' (measured less specified, wrapped), each not a number where it cannot be read; and the booleans 'coherence_judged', 'coherence_out', 'phase_judged' and 'phase_out', and 'coherence_scattered' and 'phase_scattered', the lines set aside because the tolerance is inside the estimate's scatter. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If a tolerance or the floor is out of range, the averages are not a positive number, or the four arrays are not one shape. |
Source code in src/visualdynamics/core/matrix_comparison.py
195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 | |
record_grid
¶
record_grid(data: Any, records: Sequence[int] | None = None) -> tuple[list[tuple[str, str]], list[list[int | None]]]
The records of a spectral matrix laid out as the matrix: its channels, and which record holds each (row, column) entry.
The channels are the autospectra's, (DOF, quantity) in record order; a cross term between two of them takes the entry at its row and column, and one that names a channel with no autospectrum is left out. An entry no record holds is None. Two records for one entry are refused: which of them is the matrix is not in the data.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data
|
Psd or Specification
|
The matrix's records. |
required |
records
|
sequence of int
|
Which records; all of them when omitted. |
None
|
Returns:
| Type | Description |
|---|---|
tuple of (list of (str, str), list of list of int or None)
|
The channels, and the record at each entry, row by row. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If two records hold one entry. |
Source code in src/visualdynamics/core/matrix_comparison.py
compare_matrix
¶
compare_matrix(specification: Any, measured: Any, *, coherence_tolerance: float = COHERENCE_TOLERANCE, phase_tolerance: float = PHASE_TOLERANCE, coherence_floor: float = COHERENCE_FLOOR, spec_records: Sequence[int] | None = None, measured_records: Sequence[int] | None = None, scale_db: float | None = None, progress: Callable[[int, int], None] | None = None) -> MatrixComparison
A measured cross-spectral matrix judged against its specification: the levels on the diagonal, the coherence and phase of every cross term the specification writes.
The control channels are the specification's autospectra, in its
order, paired with the measurement's records by DOF and quantity
(compliance.matched_records). Each level is
compliance.compare_all's, at one scale for the whole matrix
(resolved and warned as compare_all does). Each cross term is
judged line by line on the measured lines (cross_term_lines) and
is out when more than compliance.LINES_PERCENT of the lines
judged are out on either reading.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
specification
|
Specification
|
What the run was controlled to: its autospectra and the cross terms written for any pairs. |
required |
measured
|
Psd
|
The run's cross-spectral matrix ( |
required |
coherence_tolerance
|
float
|
How far the coherence may be off, either way. |
`COHERENCE_TOLERANCE`
|
phase_tolerance
|
float
|
How far the phase may be off, in degrees either way. |
`PHASE_TOLERANCE`
|
coherence_floor
|
float
|
The specified coherence a phase is judged from. |
`COHERENCE_FLOOR`
|
spec_records
|
sequence of int
|
Which records of each to use; all when omitted. |
None
|
measured_records
|
sequence of int
|
Which records of each to use; all when omitted. |
None
|
scale_db
|
float
|
The comparison's scale in dB, added to the measured levels
(coherence and phase do not change with it); resolved as
|
None
|
progress
|
callable
|
Told |
None
|
Returns:
| Type | Description |
|---|---|
MatrixComparison
|
Every cell's judgment, the per-line summary and the verdict. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If the measurement holds no cross terms, either selection of
records is empty, the two cannot be compared
( |
Source code in src/visualdynamics/core/matrix_comparison.py
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 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 | |