File ber_meter_core.h¶
FileList > ber_meter > ber_meter_core.h
Go to the source code of this file
BerMeter — the error-rate accumulator. More...
#include "ber/ber_core.h"#include "clib_common.h"#include "detection/detection_core.h"#include "dp_state.h"#include "jm_perf.h"#include <complex.h>#include <stdint.h>
Classes¶
| Type | Name |
|---|---|
| struct | ber_meter_state_t BerMeter state. |
Public Functions¶
| Type | Name |
|---|---|
| ber_align_t | ber_align_detect (const float _Complex * rx, size_t rx_len, const uint8_t * truth, size_t truth_len, int m, size_t t0, size_t n_marker, size_t period, int lag_span, double pfa) Exact confidence interval for a run stopped on an ERROR count. |
| ber_interval_t | ber_confidence (size_t errors, size_t symbols, double conf) |
| int | ber_meter_align (ber_meter_state_t * state, const float _Complex * rx, size_t rx_len, size_t t0, size_t n_marker, size_t period, int lag_span, double pfa) Detect where rx sits against truth and REMEMBER that alignment. |
| ber_interval_t | ber_meter_ber (const ber_meter_state_t * state) Gray-coded bit error rate over the scored bits, with its interval. |
| ber_meter_state_t * | ber_meter_create (int m, size_t target_errors, double conf) Error counters accumulated across as many bursts as it takes. |
| void | ber_meter_destroy (ber_meter_state_t * state) |
| ber_align_t | ber_meter_detect (const ber_meter_state_t * state, const float _Complex * rx, size_t rx_len, size_t t0, size_t n_marker, size_t period, int lag_span, double pfa) Pure detection: returns the alignment without touching state. |
| double | ber_meter_get_align_margin_db (const ber_meter_state_t * state) |
| size_t | ber_meter_get_align_occurrences (const ber_meter_state_t * state) |
| int | ber_meter_get_align_ok (const ber_meter_state_t * state) |
| double | ber_meter_get_align_runner_db (const ber_meter_state_t * state) |
| int | ber_meter_get_align_saturated (const ber_meter_state_t * state) |
| size_t | ber_meter_get_align_slips (const ber_meter_state_t * state) |
| double | ber_meter_get_align_stat (const ber_meter_state_t * state) |
| size_t | ber_meter_get_bit_errors (const ber_meter_state_t * state) |
| size_t | ber_meter_get_bits (const ber_meter_state_t * state) |
| double | ber_meter_get_conf (const ber_meter_state_t * state) |
| int | ber_meter_get_enough (const ber_meter_state_t * state) Has the error target been reached? The inverse-binomial stop. |
| size_t | ber_meter_get_errors (const ber_meter_state_t * state) |
| int | ber_meter_get_lag (const ber_meter_state_t * state) |
| int | ber_meter_get_m (const ber_meter_state_t * state) |
| double | ber_meter_get_phase (const ber_meter_state_t * state) |
| size_t | ber_meter_get_skipped (const ber_meter_state_t * state) |
| void | ber_meter_get_state (const ber_meter_state_t * state, void * blob) |
| size_t | ber_meter_get_symbols (const ber_meter_state_t * state) |
| size_t | ber_meter_get_target_errors (const ber_meter_state_t * state) |
| ber_interval_t | ber_meter_interval (const ber_meter_state_t * state, size_t errors, size_t symbols) Exact confidence interval for error/trial counts from ELSEWHERE. |
| void | ber_meter_reset (ber_meter_state_t * state) Zero the running counters; keep the configuration and the truth. |
| size_t | ber_meter_score (ber_meter_state_t * state, const float _Complex * rx, size_t rx_len, size_t lo, size_t hi) Score rx[lo .. hi) against the truth and accumulate the counters. |
| ber_interval_t | ber_meter_ser (const ber_meter_state_t * state) Symbol error rate over the scored symbols, with its exact interval. |
| void | ber_meter_set_align (ber_meter_state_t * state, ber_align_t align, size_t t0, size_t n_marker, size_t period) Install an alignment detected elsewhere (e.g. by ber_align_detect() on a different buffer), with the marker geometry that produced it, sober_meter_score() can use it. |
| int | ber_meter_set_state (ber_meter_state_t * state, const void * blob) |
| int | ber_meter_set_truth (ber_meter_state_t * state, const uint8_t * truth, size_t truth_len) Install the transmitted symbol sequence this meter scores against. |
| size_t | ber_meter_state_bytes (const ber_meter_state_t * state) |
Macros¶
| Type | Name |
|---|---|
| define | BER_METER_STATE_MAGIC [**DP\_FOURCC**](dp__state_8h.md#define-dp_fourcc) ('B', 'E', 'R', 'M') |
| define | BER_METER_STATE_VERSION 1u |
Detailed Description¶
Owns the transmitted reference, the running error counters, the marker-based alignment detector and the exact confidence interval. A caller loops bursts until the ERROR target is met and reads a defensible rate off the end:
ber_meter_state_t *m = ber_meter_create (4, 200, 0.99);
ber_meter_set_truth (m, truth, nsym);
while (!ber_meter_get_enough (m))
{
size_t n = run_receiver (rx);
ber_align_t a = ber_meter_align (m, rx, n, t0, 0, 0, 0, 0.0);
if (a.ok)
ber_meter_score (m, rx, n, lo, n, a.lag, a.phase, t0, 0, 0,
a.occurrences);
}
ber_interval_t ser = ber_meter_ser (m);
ber_meter_destroy (m);
The three gates a result has to pass, and why each exists, are on ber/ber_core.h. The one rule this file enforces by construction: the alignment handed to ber_meter_score() is DETECTED by ber_meter_align(), never searched by minimising the error count.
Public Functions Documentation¶
function ber_align_detect¶
Exact confidence interval for a run stopped on an ERROR count.
ber_align_t ber_align_detect (
const float _Complex * rx,
size_t rx_len,
const uint8_t * truth,
size_t truth_len,
int m,
size_t t0,
size_t n_marker,
size_t period,
int lag_span,
double pfa
)
Not a module free function because jm free functions cannot return a record; reach it from Python as BerMeter.ser() / .ber().
Both quantiles come from doppler's own inverse regularized incomplete gamma rather than a second copy of one: det_threshold_noncoherent(q, r) returns the b with marcum_q(r, 0, b) = q, and marcum_q(r, 0, b) = Q(r, b^2/2), so chi2_q(2r)/2 = 0.5 * det_threshold_noncoherent(1-q, r)^2. At r = 1 that reduces to the closed form and the interval is [-ln(1-a/2)/N, -ln(a/2)/N] — no normal approximation anywhere, so it stays honest at the small error counts where a Wald interval is worst. Verified bit-identical to SciPy's chi2.ppf at r = 1, 2, 20, 200 and 1000.
Detect the (lag, phase) alignment of rx against a known marker.
The free-function form, needing only the truth sequence — BerMeter.align() is the stateful spelling. The marker is truth[t0 .. t0 + n_marker), optionally repeating every period symbols; repeats are combined NON-COHERENTLY, which raises the processing gain and exposes cycle slips. The noise floor is estimated from the off-peak lags themselves (a CFAR reference), so nothing needs to know the Es/N0.
The peak's phase is the ABSOLUTE constellation rotation, so there is no residual M-fold ambiguity left to search: the marker resolves it. That is what removes the min over rotation bias from an error count. The processing gain is sqrt(2*K*L), so a marker too short to identify an alignment simply cannot clear the threshold and reports ok = 0 — the intended behaviour, and the opposite of returning a plausible wrong lag.
Parameters:
rxRecovered symbols.rx_lenHow many.truthTransmitted symbol indices (0..m-1).truth_lenHow many.mConstellation order.t0Truth index of the marker's first occurrence.n_markerMarker length in symbols; 0 selects BER_SYNC_SYMS.periodRepeat period in symbols; 0 for a single occurrence.lag_spanSearch half-width; 0 selects BER_LAG_SPAN.pfaWhole-search false-alarm probability; 0 selects 1e-6.
Returns:
The alignment, with ok saying whether to believe it.
function ber_confidence¶
function ber_meter_align¶
Detect where rx sits against truth and REMEMBER that alignment.
int ber_meter_align (
ber_meter_state_t * state,
const float _Complex * rx,
size_t rx_len,
size_t t0,
size_t n_marker,
size_t period,
int lag_span,
double pfa
)
Correlates the known marker truth[t0 .. t0 + n_marker) against rx over a span of lags, gates the peak with a false-alarm probability, and stores the winning lag, absolute carrier phase and marker geometry on the meter so score() later uses exactly this detection — never a lag searched to minimise the error count. The peak's phase is the ABSOLUTE constellation rotation, so no M-fold ambiguity is left to resolve; a marker too short to clear the gate reports failure rather than a plausible wrong lag. Read the outcome through align_ok, lag, phase and align_margin_db.
Parameters:
stateMust be non-NULL, with truth installed by set_truth().rxRecovered symbols to align against the truth.rx_lenHow many recovered symbols.t0Truth index of the marker's first occurrence.n_markerMarker length in symbols; 0 selects BER_SYNC_SYMS.periodRepeat period in symbols; 0 for a single occurrence.lag_spanSearch half-width in symbols; 0 selects BER_LAG_SPAN.pfaWhole-search false-alarm probability; 0 selects 1e-6.
Returns:
1 when the detection passed its false-alarm gate, else 0.
>>> import numpy as np
>>> from doppler.ber import BerMeter
>>> rng = np.random.default_rng(0)
>>> truth = rng.integers(0, 4, size=600).astype(np.uint8)
>>> ang = 2 * np.pi * truth / 4 + np.pi / 4
>>> rx = np.exp(1j * ang).astype(np.complex64)
>>> met = BerMeter(m=4)
>>> met.set_truth(truth)
>>> met.align(rx, n_marker=64) # correlate a 64-symbol marker
1
>>> met.lag, met.align_ok # detected, so score() is valid
(0, 1)
function ber_meter_ber¶
Gray-coded bit error rate over the scored bits, with its interval.
The same exact statistics as ser(), counted over Gray-coded bits rather than symbols, so a QPSK/8PSK symbol error contributes as many bit errors as its Gray labels differ by. Assert on lo, never on p_hat.
Parameters:
stateMust be non-NULL.
Returns:
A BerInterval record — (p_hat, lo, hi, rel, conf, errors, symbols) — the bit error rate with its exact two-sided limits (errors and symbols are bit counts here).
>>> import numpy as np
>>> from doppler.ber import BerMeter
>>> rng = np.random.default_rng(1)
>>> truth = rng.integers(0, 4, size=800).astype(np.uint8)
>>> ang = 2 * np.pi * truth / 4 + np.pi / 4
>>> rx = np.exp(1j * ang).astype(np.complex64)
>>> rx[200:260:5] *= -1 # corrupt 12 symbols
>>> met = BerMeter(m=4)
>>> met.set_truth(truth)
>>> met.align(rx, n_marker=64)
1
>>> _ = met.score(rx, hi=truth.size)
>>> r = met.ber() # as ser(), but over bits
>>> r.errors, r.symbols
(24, 1472)
>>> round(r.lo, 4)
0.009
function ber_meter_create¶
Error counters accumulated across as many bursts as it takes.
Create a meter for constellation m stopping at target_errors.
Parameters:
mConstellation order (2, 4, 8).target_errorsInverse-binomial stop condition; 0 selects 200.confTwo-sided confidence level; 0 selects 0.99.
function ber_meter_destroy¶
function ber_meter_detect¶
Pure detection: returns the alignment without touching state.
ber_align_t ber_meter_detect (
const ber_meter_state_t * state,
const float _Complex * rx,
size_t rx_len,
size_t t0,
size_t n_marker,
size_t period,
int lag_span,
double pfa
)
ber_meter_align() is the stateful spelling the Python binding uses. The marker comes from the truth installed by ber_meter_set_truth().
Parameters:
stateMust be non-NULL, with truth installed.rxRecovered symbols.rx_lenHow many.t0Truth index of the marker's first occurrence.n_markerMarker length in symbols; 0 selects BER_SYNC_SYMS.periodRepeat period in symbols; 0 for a single occurrence.lag_spanSearch half-width; 0 selects BER_LAG_SPAN.pfaWhole-search false-alarm probability; 0 selects 1e-6.
Returns:
The alignment, with ok saying whether to believe it.
function ber_meter_get_align_margin_db¶
function ber_meter_get_align_occurrences¶
function ber_meter_get_align_ok¶
function ber_meter_get_align_runner_db¶
function ber_meter_get_align_saturated¶
function ber_meter_get_align_slips¶
function ber_meter_get_align_stat¶
function ber_meter_get_bit_errors¶
function ber_meter_get_bits¶
function ber_meter_get_conf¶
function ber_meter_get_enough¶
Has the error target been reached? The inverse-binomial stop.
function ber_meter_get_errors¶
function ber_meter_get_lag¶
function ber_meter_get_m¶
function ber_meter_get_phase¶
function ber_meter_get_skipped¶
function ber_meter_get_state¶
function ber_meter_get_symbols¶
function ber_meter_get_target_errors¶
function ber_meter_interval¶
Exact confidence interval for error/trial counts from ELSEWHERE.
ber_interval_t ber_meter_interval (
const ber_meter_state_t * state,
size_t errors,
size_t symbols
)
The pure-function face of the meter's statistics, at this meter's own confidence level: hand it any error and trial counts and it returns the same exact Gamma/chi-square interval ser() would, with quantiles from doppler's own inverse regularized incomplete gamma rather than a normal approximation, so it stays honest at the small error counts where a Wald interval is worst. Assert on lo, never on p_hat.
Parameters:
stateMust be non-NULL.errorsErrors counted,r.symbolsTrials counted,N(symbols, or bits for a BER).
Returns:
A BerInterval record — (p_hat, lo, hi, rel, conf, errors, symbols) — the unbiased rate with its exact two-sided limits.
>>> from doppler.ber import BerMeter
>>> met = BerMeter(m=4, conf=0.99)
>>> ci = met.interval(errors=8, symbols=20000) # external counts
>>> round(ci.p_hat, 6), round(ci.lo, 6), round(ci.hi, 6)
(0.00035, 0.000129, 0.000857)
function ber_meter_reset¶
Zero the running counters; keep the configuration and the truth.
Returns the meter to a fresh count while preserving m, the error target, the confidence level and the installed truth sequence, so one meter can measure independent captures back to back without reinstalling truth. The last detected alignment is left untouched; call align() again for the next capture before scoring it.
Parameters:
stateMust be non-NULL.>>> import numpy as np >>> from doppler.ber import BerMeter >>> rng = np.random.default_rng(0) >>> truth = rng.integers(0, 4, size=400).astype(np.uint8) >>> ang = 2 * np.pi * truth / 4 + np.pi / 4 >>> rx = np.exp(1j * ang).astype(np.complex64) >>> met = BerMeter(m=4) >>> met.set_truth(truth) >>> met.align(rx, n_marker=64) 1 >>> met.score(rx, hi=truth.size) 336 >>> met.symbols 336 >>> met.reset() # reuse the meter for the next capture >>> (met.errors, met.symbols) (0, 0)
function ber_meter_score¶
Score rx[lo .. hi) against the truth and accumulate the counters.
size_t ber_meter_score (
ber_meter_state_t * state,
const float _Complex * rx,
size_t rx_len,
size_t lo,
size_t hi
)
Demodulates each symbol in the window under the alignment the last align() detected — its lag and absolute phase — and tallies symbol and Gray-coded bit errors against the installed truth. The alignment is used VERBATIM: no lag search, no rotation search, no minimisation of any kind over the answer. Symbols covered by a marker occurrence are excluded, as are any whose truth index falls outside the installed sequence; both land in skipped.
Parameters:
stateMust be non-NULL, with an alignment set by align().rxRecovered symbols to score.rx_lenHow many recovered symbols.loFirst symbol index to score (inclusive).hiOne past the last symbol index to score; clamped to rx_len.hi = 0scores nothing, so pass the window's true end.
Returns:
Symbols actually scored (window length minus skipped symbols).
>>> import numpy as np
>>> from doppler.ber import BerMeter
>>> rng = np.random.default_rng(1)
>>> truth = rng.integers(0, 4, size=800).astype(np.uint8)
>>> ang = 2 * np.pi * truth / 4 + np.pi / 4
>>> rx = np.exp(1j * ang).astype(np.complex64)
>>> met = BerMeter(m=4)
>>> met.set_truth(truth)
>>> met.align(rx, n_marker=64)
1
>>> met.score(rx, hi=truth.size) # the 64 marker symbols are excluded
736
>>> met.errors, met.skipped
(0, 64)
function ber_meter_ser¶
Symbol error rate over the scored symbols, with its exact interval.
Divides the accumulated symbol-error count by the symbols scored and wraps it in the exact Gamma/chi-square interval for inverse binomial sampling — no normal approximation anywhere. Assert on lo, never on p_hat: comparing the lower limit against a spec is the form that cannot flake on counting noise.
Parameters:
stateMust be non-NULL.
Returns:
A BerInterval record — (p_hat, lo, hi, rel, conf, errors, symbols) — the symbol error rate with its exact two-sided limits.
>>> import numpy as np
>>> from doppler.ber import BerMeter
>>> rng = np.random.default_rng(1)
>>> truth = rng.integers(0, 4, size=800).astype(np.uint8)
>>> ang = 2 * np.pi * truth / 4 + np.pi / 4
>>> rx = np.exp(1j * ang).astype(np.complex64)
>>> rx[200:260:5] *= -1 # corrupt 12 symbols (pi rotation)
>>> met = BerMeter(m=4)
>>> met.set_truth(truth)
>>> met.align(rx, n_marker=64)
1
>>> _ = met.score(rx, hi=truth.size)
>>> r = met.ser()
>>> r.errors, r.symbols
(12, 736)
>>> round(r.lo, 4) # assert on lo, never on p_hat
0.0067
function ber_meter_set_align¶
Install an alignment detected elsewhere (e.g. by ber_align_detect() on a different buffer), with the marker geometry that produced it, sober_meter_score() can use it.
void ber_meter_set_align (
ber_meter_state_t * state,
ber_align_t align,
size_t t0,
size_t n_marker,
size_t period
)
The stateful ber_meter_align() is the usual path; this exists for the case where detection and scoring run over different buffers. It is deliberately the ONLY way to set an alignment other than detecting one — score() never takes a lag from its caller, because a lag that was passed in is a lag that could have been searched for.
function ber_meter_set_state¶
function ber_meter_set_truth¶
Install the transmitted symbol sequence this meter scores against.
Copied, so the caller's buffer need not outlive the call, and reused across every burst. Values are symbol INDICES in 0..m-1 (not Gray labels): the meter Gray-encodes each side itself when it counts bit errors, so handing it Gray labels would double-encode and inflate the rate.
Parameters:
stateMust be non-NULL.truthTransmitted symbol indices, each in0..m-1.truth_lenHow many symbols the reference holds.
Returns:
DP_OK, or DP_ERR_INVALID if any index is outside 0..m-1.
>>> import numpy as np
>>> from doppler.ber import BerMeter
>>> met = BerMeter(m=4)
>>> truth = np.array(
... [0, 3, 1, 2, 2, 0], dtype=np.uint8) # indices, 0..3
>>> met.set_truth(truth)
>>> met.set_truth(np.array([9], dtype=np.uint8)) # 9 is not in 0..3
Traceback (most recent call last):
ValueError: set_truth failed (rc=-4)
function ber_meter_state_bytes¶
Macro Definition Documentation¶
define BER_METER_STATE_MAGIC¶
define BER_METER_STATE_VERSION¶
The documentation for this class was generated from the following file native/inc/ber_meter/ber_meter_core.h