Skip to content

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 "doppler/ber/ber_core.h"
  • #include "doppler/clib_common.h"
  • #include "doppler/detection/detection_core.h"
  • #include "doppler/dp_state.h"
  • #include "doppler/jm_perf.h"
  • #include "doppler/dp_complex.h"
  • #include <stdint.h>

Classes

Type Name
struct dp_ber_meter_state_t
BerMeter state.

Public Functions

Type Name
ber_align_t dp_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 dp_ber_confidence (size_t errors, size_t symbols, double conf)
int dp_ber_meter_align (dp_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 dp_ber_meter_ber (const dp_ber_meter_state_t * state)
Gray-coded bit error rate over the scored bits, with its interval.
dp_ber_meter_state_t * dp_ber_meter_create (int m, size_t target_errors, double conf)
Error counters accumulated across as many bursts as it takes.
void dp_ber_meter_destroy (dp_ber_meter_state_t * state)
ber_align_t dp_ber_meter_detect (const dp_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 dp_ber_meter_get_align_margin_db (const dp_ber_meter_state_t * state)
size_t dp_ber_meter_get_align_occurrences (const dp_ber_meter_state_t * state)
int dp_ber_meter_get_align_ok (const dp_ber_meter_state_t * state)
double dp_ber_meter_get_align_runner_db (const dp_ber_meter_state_t * state)
int dp_ber_meter_get_align_saturated (const dp_ber_meter_state_t * state)
size_t dp_ber_meter_get_align_slips (const dp_ber_meter_state_t * state)
double dp_ber_meter_get_align_stat (const dp_ber_meter_state_t * state)
size_t dp_ber_meter_get_bit_errors (const dp_ber_meter_state_t * state)
size_t dp_ber_meter_get_bits (const dp_ber_meter_state_t * state)
double dp_ber_meter_get_conf (const dp_ber_meter_state_t * state)
int dp_ber_meter_get_enough (const dp_ber_meter_state_t * state)
Has the error target been reached? The inverse-binomial stop.
size_t dp_ber_meter_get_errors (const dp_ber_meter_state_t * state)
int dp_ber_meter_get_lag (const dp_ber_meter_state_t * state)
int dp_ber_meter_get_m (const dp_ber_meter_state_t * state)
double dp_ber_meter_get_phase (const dp_ber_meter_state_t * state)
size_t dp_ber_meter_get_skipped (const dp_ber_meter_state_t * state)
void dp_ber_meter_get_state (const dp_ber_meter_state_t * state, void * blob)
size_t dp_ber_meter_get_symbols (const dp_ber_meter_state_t * state)
size_t dp_ber_meter_get_target_errors (const dp_ber_meter_state_t * state)
ber_interval_t dp_ber_meter_interval (const dp_ber_meter_state_t * state, size_t errors, size_t symbols)
Exact confidence interval for error/trial counts from ELSEWHERE.
void dp_ber_meter_reset (dp_ber_meter_state_t * state)
Zero the running counters; keep the configuration and the truth.
size_t dp_ber_meter_score (dp_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 dp_ber_meter_ser (const dp_ber_meter_state_t * state)
Symbol error rate over the scored symbols, with its exact interval.
void dp_ber_meter_set_align (dp_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 dp_ber_align_detect() on a different buffer), with the marker geometry that produced it, sodp_ber_meter_score() can use it.
int dp_ber_meter_set_state (dp_ber_meter_state_t * state, const void * blob)
int dp_ber_meter_set_truth (dp_ber_meter_state_t * state, const uint8_t * truth, size_t truth_len)
Install the transmitted symbol sequence this meter scores against.
size_t dp_ber_meter_state_bytes (const dp_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:

dp_ber_meter_state_t *m = dp_ber_meter_create (4, 200, 0.99);
dp_ber_meter_set_truth (m, truth, nsym);
while (!dp_ber_meter_get_enough (m))
  {
    size_t n = run_receiver (rx);
    ber_align_t a = dp_ber_meter_align (m, rx, n, t0, 0, 0, 0, 0.0);
    if (a.ok)
      dp_ber_meter_score (m, rx, n, lo, n, a.lag, a.phase, t0, 0, 0,
                       a.occurrences);
  }
ber_interval_t ser = dp_ber_meter_ser (m);
dp_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 dp_ber_meter_score() is DETECTED by dp_ber_meter_align(), never searched by minimising the error count.

Public Functions Documentation

function dp_ber_align_detect

Exact confidence interval for a run stopped on an ERROR count.

ber_align_t dp_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.

The threshold is set on the statistic's null, which depends on m. For m >= 3 a marker against unrelated symbols is circular, and the null is marcum_q(K, 0, R). At m = 2 both are +/-1 on one axis, the null is rank-one chi-square with K degrees of freedom on R^2/2, heavier-tailed at the same mean and the threshold is set on that, exactly for one occurrence or an even count and conservatively for an odd count above one. Set as circular, a BPSK search false-alarmed about a thousand times its stated Pfa (doppler#2496).

Parameters:

  • rx Recovered symbols.
  • rx_len How many.
  • truth Transmitted symbol indices (0..m-1).
  • truth_len How many.
  • m Constellation order.
  • t0 Truth index of the marker's first occurrence.
  • n_marker Marker length in symbols; 0 selects BER_SYNC_SYMS.
  • period Repeat period in symbols; 0 for a single occurrence.
  • lag_span Search half-width; 0 selects BER_LAG_SPAN.
  • pfa Whole-search false-alarm probability; 0 selects 1e-6.

Returns:

The alignment, with ok saying whether to believe it.


function dp_ber_confidence

ber_interval_t dp_ber_confidence (
    size_t errors,
    size_t symbols,
    double conf
) 

function dp_ber_meter_align

Detect where rx sits against truth and REMEMBER that alignment.

int dp_ber_meter_align (
    dp_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:

  • state Must be non-NULL, with truth installed by set_truth().
  • rx Recovered symbols to align against the truth.
  • rx_len How many recovered symbols.
  • t0 Truth index of the marker's first occurrence.
  • n_marker Marker length in symbols; 0 selects BER_SYNC_SYMS.
  • period Repeat period in symbols; 0 for a single occurrence.
  • lag_span Search half-width in symbols; 0 selects BER_LAG_SPAN.
  • pfa Whole-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 dp_ber_meter_ber

Gray-coded bit error rate over the scored bits, with its interval.

ber_interval_t dp_ber_meter_ber (
    const dp_ber_meter_state_t * state
) 

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:

  • state Must 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 dp_ber_meter_create

Error counters accumulated across as many bursts as it takes.

dp_ber_meter_state_t * dp_ber_meter_create (
    int m,
    size_t target_errors,
    double conf
) 

Create a meter for constellation m stopping at target_errors.

Parameters:

  • m Constellation order (2, 4, 8).
  • target_errors Inverse-binomial stop condition; 0 selects 200.
  • conf Two-sided confidence level; 0 selects 0.99.

function dp_ber_meter_destroy

void dp_ber_meter_destroy (
    dp_ber_meter_state_t * state
) 

function dp_ber_meter_detect

Pure detection: returns the alignment without touching state.

ber_align_t dp_ber_meter_detect (
    const dp_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
) 

dp_ber_meter_align() is the stateful spelling the Python binding uses. The marker comes from the truth installed by dp_ber_meter_set_truth().

Parameters:

  • state Must be non-NULL, with truth installed.
  • rx Recovered symbols.
  • rx_len How many.
  • t0 Truth index of the marker's first occurrence.
  • n_marker Marker length in symbols; 0 selects BER_SYNC_SYMS.
  • period Repeat period in symbols; 0 for a single occurrence.
  • lag_span Search half-width; 0 selects BER_LAG_SPAN.
  • pfa Whole-search false-alarm probability; 0 selects 1e-6.

Returns:

The alignment, with ok saying whether to believe it.


function dp_ber_meter_get_align_margin_db

double dp_ber_meter_get_align_margin_db (
    const dp_ber_meter_state_t * state
) 

function dp_ber_meter_get_align_occurrences

size_t dp_ber_meter_get_align_occurrences (
    const dp_ber_meter_state_t * state
) 

function dp_ber_meter_get_align_ok

int dp_ber_meter_get_align_ok (
    const dp_ber_meter_state_t * state
) 

function dp_ber_meter_get_align_runner_db

double dp_ber_meter_get_align_runner_db (
    const dp_ber_meter_state_t * state
) 

function dp_ber_meter_get_align_saturated

int dp_ber_meter_get_align_saturated (
    const dp_ber_meter_state_t * state
) 

function dp_ber_meter_get_align_slips

size_t dp_ber_meter_get_align_slips (
    const dp_ber_meter_state_t * state
) 

function dp_ber_meter_get_align_stat

double dp_ber_meter_get_align_stat (
    const dp_ber_meter_state_t * state
) 

function dp_ber_meter_get_bit_errors

size_t dp_ber_meter_get_bit_errors (
    const dp_ber_meter_state_t * state
) 

function dp_ber_meter_get_bits

size_t dp_ber_meter_get_bits (
    const dp_ber_meter_state_t * state
) 

function dp_ber_meter_get_conf

double dp_ber_meter_get_conf (
    const dp_ber_meter_state_t * state
) 

function dp_ber_meter_get_enough

Has the error target been reached? The inverse-binomial stop.

int dp_ber_meter_get_enough (
    const dp_ber_meter_state_t * state
) 


function dp_ber_meter_get_errors

size_t dp_ber_meter_get_errors (
    const dp_ber_meter_state_t * state
) 

function dp_ber_meter_get_lag

int dp_ber_meter_get_lag (
    const dp_ber_meter_state_t * state
) 

function dp_ber_meter_get_m

int dp_ber_meter_get_m (
    const dp_ber_meter_state_t * state
) 

function dp_ber_meter_get_phase

double dp_ber_meter_get_phase (
    const dp_ber_meter_state_t * state
) 

function dp_ber_meter_get_skipped

size_t dp_ber_meter_get_skipped (
    const dp_ber_meter_state_t * state
) 

function dp_ber_meter_get_state

void dp_ber_meter_get_state (
    const dp_ber_meter_state_t * state,
    void * blob
) 

function dp_ber_meter_get_symbols

size_t dp_ber_meter_get_symbols (
    const dp_ber_meter_state_t * state
) 

function dp_ber_meter_get_target_errors

size_t dp_ber_meter_get_target_errors (
    const dp_ber_meter_state_t * state
) 

function dp_ber_meter_interval

Exact confidence interval for error/trial counts from ELSEWHERE.

ber_interval_t dp_ber_meter_interval (
    const dp_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:

  • state Must be non-NULL.
  • errors Errors counted, r.
  • symbols Trials 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 dp_ber_meter_reset

Zero the running counters; keep the configuration and the truth.

void dp_ber_meter_reset (
    dp_ber_meter_state_t * state
) 

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:

  • state Must 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 dp_ber_meter_score

Score rx[lo .. hi) against the truth and accumulate the counters.

size_t dp_ber_meter_score (
    dp_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:

  • state Must be non-NULL, with an alignment set by align().
  • rx Recovered symbols to score.
  • rx_len How many recovered symbols.
  • lo First symbol index to score (inclusive).
  • hi One past the last symbol index to score; clamped to rx_len. hi = 0 scores 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 dp_ber_meter_ser

Symbol error rate over the scored symbols, with its exact interval.

ber_interval_t dp_ber_meter_ser (
    const dp_ber_meter_state_t * state
) 

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:

  • state Must 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 dp_ber_meter_set_align

Install an alignment detected elsewhere (e.g. by dp_ber_align_detect() on a different buffer), with the marker geometry that produced it, sodp_ber_meter_score() can use it.

void dp_ber_meter_set_align (
    dp_ber_meter_state_t * state,
    ber_align_t align,
    size_t t0,
    size_t n_marker,
    size_t period
) 

The stateful dp_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 dp_ber_meter_set_state

int dp_ber_meter_set_state (
    dp_ber_meter_state_t * state,
    const void * blob
) 

function dp_ber_meter_set_truth

Install the transmitted symbol sequence this meter scores against.

int dp_ber_meter_set_truth (
    dp_ber_meter_state_t * state,
    const uint8_t * truth,
    size_t truth_len
) 

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:

  • state Must be non-NULL.
  • truth Transmitted symbol indices, each in 0..m-1.
  • truth_len How 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 dp_ber_meter_state_bytes

size_t dp_ber_meter_state_bytes (
    const dp_ber_meter_state_t * state
) 

Macro Definition Documentation

define BER_METER_STATE_MAGIC

#define BER_METER_STATE_MAGIC `DP_FOURCC ('B', 'E', 'R', 'M')`

define BER_METER_STATE_VERSION

#define BER_METER_STATE_VERSION `1u`


The documentation for this class was generated from the following file native/inc/doppler/ber_meter/ber_meter_core.h