File burst_acq_core.h¶
FileList > burst_acq > burst_acq_core.h
Go to the source code of this file
BurstAcquisition — thin forwarder onto acq_core.c's shared engine. More...
#include "doppler/acq/acq_core.h"#include "doppler/clib_common.h"#include "doppler/jm_perf.h"#include "doppler/cvt/cvt_core.h"
Classes¶
| Type | Name |
|---|---|
| struct | dp_burst_acq_state_t BurstAcquisition state: a pure wrapper around one shared dp_acq_state_t engine. |
Public Functions¶
| Type | Name |
|---|---|
| int | dp_burst_acq_configure_search_raw (dp_burst_acq_state_t * state, size_t doppler_bins, size_t n_noncoh) Pin the search grid directly, bypassing the auto-sizing search. |
| size_t | dp_burst_acq_consumed (const dp_burst_acq_state_t * state) Input samples the last dp_burst_acq_push() took: the embedded engine'sdp_acq_consumed() , per call (not the cumulativeacq_result_t::samples_consumed ). |
| dp_burst_acq_state_t * | dp_burst_acq_create (const float _Complex * preamble, size_t preamble_len, size_t reps, double fs, double cn0_dbhz, double doppler_uncertainty, double pfa, double pd, int noise_mode, double doppler_rate, size_t rate_hypotheses) Create a burst-mode acquisition engine for any repeated preamble, given as its samples (forwards to dp_acq_create_burst() __ see its doc comment inacq_core.h for the full physics). |
| void | dp_burst_acq_destroy (dp_burst_acq_state_t * state) Destroy and free an instance. |
| void | dp_burst_acq_get_state (const dp_burst_acq_state_t * state, void * blob) |
| size_t | dp_burst_acq_push (dp_burst_acq_state_t * state, const float _Complex * x, size_t n_in, acq_result_t * result, size_t max_results) Stream raw samples; emit one event per CFAR dump above threshold. |
| void | dp_burst_acq_reset (dp_burst_acq_state_t * state) Drain the input ring and reset the coherent accumulator. |
| int | dp_burst_acq_set_max_peaks (dp_burst_acq_state_t * state, size_t n) How many peaks a dwell may report: the peak list's capacity. |
| int | dp_burst_acq_set_state (dp_burst_acq_state_t * state, const void * blob) |
| size_t | dp_burst_acq_state_bytes (const dp_burst_acq_state_t * state) |
Detailed Description¶
Composes dp_acq_state_t (native/inc/doppler/acq/acq_core.h) as an embedded pointer, built via dp_acq_create_burst() the BURST front door onto the SAME shared engine Acquisition (acq_core.h) composes via dp_acq_create_continuous(). Every function here is a direct forward to the corresponding acq_* call; the entire algorithm lives in acq_core.c exactly once (see docs/design/async-dsss-receiver.md's Acquisition/BurstAcquisition split and CLAUDE.md's "every algorithm lives in C exactly once" rule).
// a 127-sample Zadoff-Chu preamble, 8 repetitions, at 1 MS/s
float _Complex zc[127];
for (int k = 0; k < 127; k++)
zc[k] = cexpf (-I * (float)(M_PI * 5.0 * k * (k + 1) / 127.0));
dp_burst_acq_state_t *obj = dp_burst_acq_create(zc, 127, 8, 1.0e6, 50.0,
0.0, 1e-3, 0.9, 0, 0.0, 1);
acq_result_t hits[64];
size_t nh = dp_burst_acq_push(obj, samples, n_samples, hits, 64);
dp_burst_acq_destroy(obj);
Public Functions Documentation¶
function dp_burst_acq_configure_search_raw¶
Pin the search grid directly, bypassing the auto-sizing search.
int dp_burst_acq_configure_search_raw (
dp_burst_acq_state_t * state,
size_t doppler_bins,
size_t n_noncoh
)
Forwards to dp_acq_configure_search_raw() on the embedded engine (see its doc comment in acq_core.h): resizes every grid-dependent buffer/plan, re-derives the threshold ladder for the pinned grid, and clears in-flight accumulation — call between push() calls, never a substitute for one.
Parameters:
stateAllocated engine (non-NULL).doppler_binsCoherent depth to pin, in[1, reps].n_noncohNon-coherent look count to pin, in[1, ACQ_N_NONCOH_SAFETY_CEILING].
Returns:
0 on success, -1 if either argument is out of range or an allocation fails (the engine keeps its prior grid on failure).
>>> import numpy as np
>>> from doppler.acquire import BurstAcquisition
>>> from doppler.wfm import PN, mls_poly
>>> code = np.asarray(PN(poly=mls_poly(5), seed=1,
... length=5).generate(31)).astype(np.uint8)
>>> s0 = np.repeat(np.where(code & 1, -1.0, 1.0), 4).astype(
... np.complex64)
>>> b = BurstAcquisition(s0, reps=8, fs=4e6, cn0_dbhz=50.0)
>>> b.configure_search_raw(doppler_bins=4, n_noncoh=2) # pin the grid
>>> b.doppler_bins, b.n_noncoh
(4, 2)
>>> burst = np.tile(np.roll(s0, 17), 8).astype(np.complex64)
>>> b.push(burst)[0][:2] # detects at the pinned grid
(0, 17)
function dp_burst_acq_consumed¶
Input samples the last dp_burst_acq_push() took: the embedded engine'sdp_acq_consumed() , per call (not the cumulativeacq_result_t::samples_consumed ).
Parameters:
stateMust be non-NULL.
function dp_burst_acq_create¶
Create a burst-mode acquisition engine for any repeated preamble, given as its samples (forwards to dp_acq_create_burst() __ see its doc comment inacq_core.h for the full physics).
dp_burst_acq_state_t * dp_burst_acq_create (
const float _Complex * preamble,
size_t preamble_len,
size_t reps,
double fs,
double cn0_dbhz,
double doppler_uncertainty,
double pfa,
double pd,
int noise_mode,
double doppler_rate,
size_t rate_hypotheses
)
One period of preamble_len complex samples at fs, repeated up to reps times: a chirp, a Zadoff-Chu sequence, shaped PSK, or a PN code mapped by dp_bin_to_nrz() and held spc samples each (at fs = chip_rate*spc). One chip is one sample: sf = preamble_len, spc = 1, chip_rate = fs, and code_phase is the delay into the repetition, in samples (for a Zadoff-Chu preamble, plus u^-1 mod n samples per native bin of Doppler the search did not resolve before correlating). fs of 1 is normalized units, where Doppler is in cycles/sample and cn0_dbhz is the per-sample SNR in dB.
Parameters:
preambleOne period of the preamble,preamble_lensamples; not all zero, every sample finite.preamble_lenSamples per repetition (>= 1).repsMax coherent repetitions (>= 1).fsSample rate in Hz (> 0); 1 for normalized units.cn0_dbhzDesign carrier-to-noise density in dB-Hz, of the preamble's mean power: any finite value, or NaN (ACQ_CN0_NONE) for no design point size for the whole preamble.doppler_uncertaintyOne-sided Doppler search half-range in Hz.pfaTarget system false-alarm probability (0,1).pdTarget detection probability (0,1).noise_modeCFAR mode index: 0=mean, 1=median, 2=min, 3=max.doppler_rateDoppler rate in Hz/s (>= 0) that caps the coherent depth atf_epoch/sqrt(2*doppler_rate)repetitions (doppler#1482); 0 is no bound.rate_hypothesesDoppler-rate hypotheses to search (0 = auto, else 1 to ACQ_RATE_HYPS_MAX); 1 searches none. Above 1 the engine COMPENSATES the rate rather than only capping the depth:doppler_ratebecomes the span[-R, +R], the coherent block is de-chirped at this many rates across it, and the depth is bounded by the residualR/(n - 1)see dp_acq_create_burst(), which owns the semantics, including 0 = auto (the engine chooses the count; read it back fromengine->rate_hyps). Needs a non-zerodoppler_rate.
Returns:
Heap-allocated state, or NULL on bad arguments / allocation failure.
>>> import numpy as np
>>> from doppler.cvt import bin_to_nrz
>>> from doppler.acquire import BurstAcquisition
>>> from doppler.wfm import PN, mls_poly
>>> code = np.asarray(PN(poly=mls_poly(5), seed=1,
... length=5).generate(31)).astype(np.uint8)
>>> nrz = np.zeros(31, np.float32)
>>> _ = bin_to_nrz(code & 1, nrz)
>>> s0 = np.repeat(nrz, 4).astype(np.complex64) # 4 samples a chip
>>> burst = np.tile(np.roll(s0, 17), 24)
>>> b = BurstAcquisition(s0, reps=8, fs=4e6, cn0_dbhz=50.0)
>>> b.push(burst)[0][:2] # detects (Doppler bin, delay in samples)
(0, 17)
Any repeated preamble is searched the same way -- here a 127-sample
Zadoff-Chu sequence, in normalized units:
>>> k = np.arange(127)
>>> zc = np.exp(-1j * np.pi * 5 * k * (k + 1) / 127).astype(
... np.complex64)
>>> z = BurstAcquisition(zc, reps=8)
>>> z.code_bins # samples per repetition
127
>>> z.push(np.tile(np.roll(zc, 40), 10))[0][:2]
(0, 40)
A carrier that sweeps caps the coherent depth; searching Doppler-rate
hypotheses compensates it instead, and buys the depth back:
>>> import warnings
>>> def make(**kw):
... with warnings.catch_warnings():
... warnings.simplefilter("ignore") # under-powered, below
... return BurstAcquisition(zc, reps=16, fs=1.0, cn0_dbhz=-12.5,
... doppler_rate=9e-7, **kw)
>>> make().doppler_bins, make().underpowered # the rate caps it
(5, True)
>>> c = make(rate_hypotheses=9)
>>> c.rate_hypotheses, c.doppler_bins, c.underpowered
(9, 9, False)
function dp_burst_acq_destroy¶
Destroy and free an instance.
Parameters:
stateMay be NULL.
function dp_burst_acq_get_state¶
function dp_burst_acq_push¶
Stream raw samples; emit one event per CFAR dump above threshold.
size_t dp_burst_acq_push (
dp_burst_acq_state_t * state,
const float _Complex * x,
size_t n_in,
acq_result_t * result,
size_t max_results
)
Forwards to dp_acq_push() on the embedded engine (see its doc comment in acq_core.h for the framing/CFAR mechanics). Each event carries the peak's Doppler bin and code phase (the two search axes), its CFAR statistic, and an estimated C/N0 — see acq_result_t.
Python's push() has room for 1024 events a call, with dp_acq_push()'s rule for what a push that fills it loses.
Parameters:
stateAllocated engine (non-NULL).xRaw input, interleaved CF32,n_incomplex samples.n_inNumber of complex input samples.resultOutput array for detection events.max_resultsCapacity ofresult:dp_acq_push()'s contract, so a fullresultnever loses input and dp_burst_acq_consumed() says where to resume.
Returns:
Number of events written (0 … max_results).
>>> import numpy as np
>>> from doppler.acquire import BurstAcquisition
>>> from doppler.wfm import PN, mls_poly
>>> code = np.asarray(PN(poly=mls_poly(5), seed=1,
... length=5).generate(31)).astype(np.uint8)
>>> s0 = np.repeat(np.where(code & 1, -1.0, 1.0), 4).astype(
... np.complex64)
>>> burst = np.tile(np.roll(s0, 17), 24).astype(np.complex64)
>>> b = BurstAcquisition(s0, reps=8, fs=4e6, cn0_dbhz=50.0)
>>> b.push(burst)[0][:2] # (Doppler bin, delay in samples)
(0, 17)
function dp_burst_acq_reset¶
Drain the input ring and reset the coherent accumulator.
Forwards to dp_acq_reset() on the embedded engine: discards any buffered samples that have not yet completed a frame and clears the non-coherent power accumulator and dwell bookkeeping, so the next push() begins a fresh search from an empty ring. Construction parameters are untouched.
Parameters:
stateMust be non-NULL.>>> import numpy as np >>> from doppler.acquire import BurstAcquisition >>> from doppler.wfm import PN, mls_poly >>> code = np.asarray(PN(poly=mls_poly(5), seed=1, ... length=5).generate(31)).astype(np.uint8) >>> s0 = np.repeat(np.where(code & 1, -1.0, 1.0), 4).astype( ... np.complex64) >>> burst = np.tile(np.roll(s0, 17), 24).astype(np.complex64) >>> b = BurstAcquisition(s0, reps=8, fs=4e6, cn0_dbhz=50.0) >>> _ = b.push(burst[:100]) # a partial frame, buffered mid-stream >>> b.reset() # drop it before it can bias a detection >>> b.push(burst)[0][:2] # (Doppler bin, delay in samples) (0, 17)
function dp_burst_acq_set_max_peaks¶
How many peaks a dwell may report: the peak list's capacity.
Forwards to dp_acq_set_max_peaks() on the embedded engine (see its doc comment in acq_core.h): one is the classic gated maximum; more is the list of docs/design/async-dsss-receiver.md §7.1 every peak above the same gate, strongest first, an exclusion zone of one Doppler bin by the reference's first autocorrelation null (one chip for a PN code) around each, and the two-epoch rule for a peak at an already-listed code phase. Each listed peak is one result from push().
Parameters:
stateAllocated engine (non-NULL).n1 … ACQ_MAX_PEAKS.
Returns:
0, or -1 (engine untouched) when n is out of range.
>>> import numpy as np
>>> from doppler.acquire import BurstAcquisition
>>> code = (np.arange(31) * 5 % 2).astype(np.uint8)
>>> s0 = np.repeat(np.where(code & 1, -1.0, 1.0), 4).astype(
... np.complex64)
>>> b = BurstAcquisition(s0, reps=8, fs=4e6, cn0_dbhz=50.0)
>>> b.set_max_peaks(4)
>>> b.max_peaks
4
function dp_burst_acq_set_state¶
function dp_burst_acq_state_bytes¶
The documentation for this class was generated from the following file native/inc/doppler/burst_acq/burst_acq_core.h