Diagnostic Normalisation¶
MIF-016 bounds dirty diagnostic channels before they enter AER encoding. Each
calibrated channel declares its physical unit interval, affine offset, scale,
clip policy, provenance, and optional AER address. Samples are mapped into
[-1, 1] by
The runtime never leaves out-of-range handling implicit. clip saturates at
the nearest endpoint and records the channel in the clip mask; reject fails
closed with a deterministic error. Every output vector is a read-only
float64 array bounded in [-1, 1].
Calibration validation also checks the derived affine coefficients, not only
the raw endpoint values. A finite endpoint pair is rejected if the physical
span or scale would become non-finite. The midpoint is computed as x_min +
0.5 * (x_max - x_min) so finite endpoints with a finite positive span keep a
finite offset instead of overflowing through x_min + x_max.
Python API¶
normalisation
¶
Bound physical diagnostic channels before AER encoding (MIF-016).
Each channel is mapped from its calibrated physical interval
[physical_min, physical_max] into [-1, 1] using the affine map
.. math::
x_\mathrm{norm} = 2 \frac{x - x_\min}{x_\max - x_\min} - 1.
Out-of-range behaviour is explicit per channel: clip saturates
deterministically at the endpoint and records a clip mask, while reject
raises. The resulting feature vectors are read-only float64 NumPy arrays
so downstream AER front-ends cannot observe overflow beyond [-1, 1].
Finite endpoint pairs are also rejected when the derived affine span or scale
would be non-finite. The stable midpoint offset remains finite whenever both
endpoints and the span are finite.
DiagnosticChannelCalibration(name, unit, physical_min, physical_max, clip_policy, provenance, aer_address=None)
dataclass
¶
Calibration record for one physical diagnostic channel.
Parameters are deliberately stored in physical units. The derived
offset and scale properties are included in manifests so an AER
ingestion chain can reproduce the exact affine mapping without inferring
it from opaque data.
offset
property
¶
Physical midpoint subtracted before applying scale.
scale
property
¶
Multiplicative factor from physical units into the normalized interval.
__post_init__()
¶
Validate channel labels, physical range, clip policy, and AER address.
normalise_value(value)
¶
Return (normalised_value, clipped) for a single channel sample.
to_manifest_row()
¶
Return the durable manifest row for this channel.
NormalisedDiagnosticSample(channel_names, features, clip_mask, out_of_range_channels, sample_period_ns=None)
dataclass
¶
NormalisedDiagnosticMatrix(channel_names, features, clip_mask, clipped_counts, sample_period_ns=None)
dataclass
¶
Read-only samples x channels normalisation result with aggregate clipping.
The throughput counterpart of :class:NormalisedDiagnosticSample: one
result for a positional row-major batch, carrying per-channel clipped-sample
counts instead of per-row channel-name lists.
Attributes¶
channel_names : tuple of str
Ordered channel names matching each feature row.
features : FloatArray
Read-only (samples, channels) bounded feature matrix.
clip_mask : numpy.ndarray
Read-only (samples, channels) boolean clip mask.
clipped_counts : tuple of int
Per-channel count of clipped samples across the batch.
sample_period_ns : int or None
Optional nominal sample period carried from the state.
DiagnosticNormalisationState(calibrations, *, sample_period_ns=None)
¶
Deterministic ordered normalisation state for a diagnostic vector.
calibrations
property
¶
Ordered immutable channel calibrations.
channel_names
property
¶
Ordered channel names matching feature-vector order.
sample_period_ns
property
¶
Nominal sample period associated with the diagnostic frame.
normalise_sample(sample)
¶
Normalise a mapping of physical channel samples into [-1, 1].
normalise_vector(values)
¶
Normalise a positional vector in calibration order.
normalise_batch(samples)
¶
Return a read-only (n_samples, n_channels) matrix of bounded features.
normalise_matrix(values)
¶
Normalise a positional row-major samples x channels matrix in one call.
The positional counterpart of :meth:normalise_batch and the batch
counterpart of :meth:normalise_vector: each row is normalised with
exactly the per-sample arithmetic, so a matrix row is bit-identical to
the corresponding :meth:normalise_vector features, while clipping is
aggregated into per-channel counts.
Parameters¶
values : ArrayLike
Two-dimensional (samples, channels) matrix of raw physical
values, at least one row, column count equal to the calibrated
channel count.
Returns¶
NormalisedDiagnosticMatrix The bounded feature matrix, the clip mask, and per-channel clipped-sample counts.
calibration_manifest()
¶
Return the explicit calibration manifest required by MIF-016.
fit_diagnostic_calibrations(observations, *, units, provenance, clip_policy='clip', aer_addresses=None)
¶
Fit min/max calibrations from observed physical samples.
Channel order follows the order of units. Every observation must
contain every declared channel, and each channel must span a non-zero
physical range.
Manifest Contract¶
DiagnosticNormalisationState.calibration_manifest() records:
schema_version,kernel,sample_period_ns, and output range;- one row per channel with physical range, offset, scale, clip policy, provenance, and AER address;
deterministic_mapping = trueso downstream AER replay can reproduce the exact transform.
Dispatch¶
Use scpn_mif_core.diagnostics.dispatched_normalisation_state(...) for the
fastest available measured backend:
The Python reference remains the canonical manifest surface. Rust mirrors the affine kernel through PyO3, and Julia mirrors the reference behaviour for calibration/scaling audit scripts.
Matrix batch surface¶
normalise_matrix(values) is the positional row-major batch counterpart of
normalise_vector: one call normalises a (samples, channels) matrix and
returns a NormalisedDiagnosticMatrix — the bounded feature matrix, the clip
mask, and per-channel clipped-sample counts (aggregates, not per-row
channel-name lists). Each matrix row is bit-identical to the corresponding
normalise_vector features. On the Rust backend the whole matrix crosses the
FFI boundary once as a zero-copy 2-D NumPy view and the outputs come back as
NumPy arrays, so the per-call bridge overhead of the 4 096-call loop
disappears: median 128 µs for the 4 096×4 batch versus 3.18 ms through the
per-call loop and 37.5 ms for the Python matrix reference (non-isolated local
comparison, 2026-07-04).
Validation¶
The committed tests verify:
- exact affine mapping and manifest fields;
- deterministic clipping and bounded AER features;
- reject-policy failure semantics;
- invalid range, non-finite endpoint, non-finite affine-span, subnormal-scale, missing-channel, and zero-span fit guards;
- Python/Rust parity across 16 seeded random vectors;
- matrix rows bit-identical to single-vector calls on both backends, matrix Python↔Rust bit-exact parity with agreeing clipped counts, read-only outputs, and shape/reject failure semantics;
- Julia reference behaviour in
julia/SCPNMIFCore/test/runtests.jl.
End-to-end ControlObservation cosimulation remains downstream of MIF-015. This surface supplies the bounded feature vector and manifest required by that later integration.
Benchmarks¶
The benchmark harness ships at bench/kernels/bench_diagnostic_normalisation.py.
It measures one four-channel frame and a 4 096-frame batch for the allocated
Python, Rust, and Julia surfaces. The committed benchmark is local comparison
evidence, not CPU-isolated production latency evidence; host load, governor,
and runtime versions are recorded in
bench/results/diagnostic_normalisation.json.