Summarize comments

This commit is contained in:
Carles Fernandez
2026-09-29 10:10:24 +02:00
parent f34c297a8e
commit 09f143fe07
3 changed files with 45 additions and 257 deletions
@@ -139,10 +139,7 @@ pcps_acquisition::pcps_acquisition(const Acq_Conf& conf_)
// helper reads d_use_CFAR_algorithm_flag, declared (so initialized)
// later in this same list; see its own doc comment in the header.
d_full_grid_reference_needs_extra_row(conf_.use_CFAR_algorithm_flag && (static_cast<float>(d_num_doppler_bins / 2) * static_cast<float>(d_doppler_step) < d_min_reference_separation_hz)),
// A full grid worth needing a dedicated reference row for is always
// > 1 candidate bin in practice (see d_num_reference_rows_active's doc
// comment for why the count differs at exactly 1 candidate) -- so this
// ceiling reserves 2 rows, not 1, whenever the row is needed at all.
// Reserve two reference rows for a full grid that needs them.
d_num_doppler_bins_full_grid_active(d_num_doppler_bins + (d_full_grid_reference_needs_extra_row ? (d_num_doppler_bins > 1U ? 2U : 1U) : 0U)),
d_num_doppler_bins_capacity(d_num_doppler_bins + 2U),
d_threshold_step_two(conf_.pfa2 > 0.0 ? compute_threshold(conf_.pfa2, d_effective_fft_size, d_num_doppler_bins_step2, conf_.bit_transition_flag ? 1 : conf_.max_dwells) : conf_.threshold),
@@ -213,17 +210,8 @@ pcps_acquisition::pcps_acquisition(const Acq_Conf& conf_)
}
#endif
// While idle (not actively searching), general_work() only drains its input to avoid
// stalling the upstream block, producing no output. Without a batching hint the TPB
// scheduler wakes this block's thread for every small burst of new input (observed:
// tens of thousands of calls/s, a few hundred/thousand samples each), which is pure
// scheduling overhead. Requiring a larger noutput_items granularity forces the
// scheduler to accumulate more input per wakeup, cutting call frequency without
// changing behavior (production while idle is still always 0 either way).
// One PRN code period (1 ms) at this signal's own decimated rate is the natural
// lower bound: the algorithm never does anything meaningful below that granularity,
// so batching to it (instead of a fixed sample count) self-scales with fs_in/decimation
// across signals/configs instead of being tuned for one particular sample rate.
// Batch idle input by one millisecond at the decimated rate to reduce
// scheduler wakeups; idle acquisition produces no output.
const auto output_multiple_samples = std::max<uint32_t>(1U, static_cast<uint32_t>(std::lround(conf_.samples_per_ms)));
this->set_output_multiple(output_multiple_samples);
}
@@ -405,16 +393,7 @@ void pcps_acquisition::update_local_carrier(own::span<gr_complex> carrier_vector
void pcps_acquisition::update_grid_doppler_wipeoffs()
{
// Symmetric, centered placement: candidate index k decodes back to
// d_doppler_center + (k - half_span) * d_doppler_step -- rather than
// "-doppler_max + step*index", which biases every bin low by up to half a
// doppler_step and, at 1 candidate bin, misses d_doppler_center entirely
// (would test doppler_center - doppler_max instead). One shared loop for
// every degree of Doppler uncertainty -- the full grid, an assisted
// narrowed window, or an exactly-known single bin -- not a separate
// branch per case: they differ only in candidate_count, which
// set_doppler_num_bins() already resolved into d_num_doppler_bins_active/
// d_num_reference_rows_active.
// Center all candidate grids on d_doppler_center, including single-bin searches.
const uint32_t candidate_count = d_num_doppler_bins_active - d_num_reference_rows_active;
const auto half_span = static_cast<int32_t>((candidate_count - 1U) / 2U);
for (uint32_t doppler_index = 0; doppler_index < candidate_count; doppler_index++)
@@ -424,33 +403,9 @@ void pcps_acquisition::update_grid_doppler_wipeoffs()
}
if (d_num_reference_rows_active > 0U)
{
// The in-grid wraparound max_to_input_power_statistic() would otherwise
// use (candidate_count/2 bins away) can't clear
// reference_bin_min_sidelobes at this candidate count and step (see
// needs_extra_reference_row()) -- add one or two dedicated extra
// reference rows instead, at exactly +/-doppler_max. NEVER pushed
// further out to try to clear reference_bin_min_sidelobes (an
// earlier version of this code did that, and it was wrong:
// doppler_max is the receiver-validated edge of the search/filter
// passband the rest of the acquisition chain is designed for, and a
// reference sample placed beyond it can land somewhere the
// decimation/anti-alias response is no longer flat, corrupting the
// noise estimate rather than cleaning it up -- confirmed live:
// pushing E5a's reference from 3740 Hz to a sidelobe-derived
// 4500 Hz cost real satellites at hot start). If doppler_max itself
// doesn't clear the floor, that's an accepted, pre-existing limit
// of this reference technique at a tight search -- not something to
// fix by searching outside the validated range.
//
// With more than one real candidate, both +doppler_max and
// -doppler_max are computed (candidate_count and candidate_count+1)
// so max_to_input_power_statistic() can pick whichever sits opposite
// the winning candidate's side of center -- a candidate riding near
// one edge of the search range never ends up right next to (or on
// top of) its own reference sample. With exactly one real candidate
// (always at offset 0 -- no side to be opposite of), a single row at
// +doppler_max is all d_num_reference_rows_active allocates; see its
// doc comment.
// Keep references at center +/- doppler_max; extending beyond the validated
// passband can bias noise estimates. Use both sides for multiple candidates
// so CFAR can select the reference opposite the winning peak.
update_local_carrier(own::span<gr_complex>(doppler_wipeoff_data(candidate_count), d_fft_size), static_cast<float>(d_doppler_bias + d_doppler_center + static_cast<int32_t>(d_doppler_max)));
if (d_num_reference_rows_active > 1U)
{
@@ -557,20 +512,8 @@ void pcps_acquisition::dump_results(const AcquisitionResult& result)
std::array<size_t, 2> dims_1d{1, 1};
std::array<size_t, 2> dims_2d{d_effective_fft_size, d_num_doppler_bins_active};
// acq_grid's candidate columns -- all of them for a plain full-grid
// search, or just the requested candidate count when narrowed
// (the trailing column there is the noise-reference bin at
// doppler_center + d_doppler_max, not representable in this linear
// encoding) -- are placed symmetrically around doppler_center, matching
// compute_statistics(): column k decodes to
// doppler_center + (k - half_span)*d_doppler_step. Writing
// dump_doppler_max = half_span*d_doppler_step lets the standard
// "-doppler_max + doppler_center + doppler_step*col" formula do that
// mapping for offline post-processing tools; doppler_narrowed flags
// whether this was a narrowed/assisted search (fewer candidates than
// the full configured grid) -- NOT whether a trailing reference
// column is present, since a plain full grid can have one too (CFAR,
// wraparound too tight) without being a narrowed search.
// Encode candidate k as center + (k - half_span) * step for dump readers.
// Narrowing describes the candidate count, independently of reference rows.
const uint32_t dump_candidate_count = d_num_doppler_bins_active - d_num_reference_rows_active;
const bool dump_narrowed = dump_candidate_count < d_num_doppler_bins;
const auto dump_half_span = static_cast<int32_t>((dump_candidate_count - 1U) / 2U);
@@ -582,10 +525,8 @@ void pcps_acquisition::dump_results(const AcquisitionResult& result)
write_matlab_var<1>("doppler_step", dump_doppler_step, matfp, dims_1d);
write_matlab_var<1>("doppler_center", d_doppler_center, matfp, dims_1d);
write_matlab_var<1>("doppler_narrowed", static_cast<int32_t>(dump_narrowed ? 1 : 0), matfp, dims_1d);
// Number of leading acq_grid columns that are real Doppler candidates.
// Any remaining trailing columns (0, 1 or 2) are noise-reference rows
// at doppler_center +/- the configured doppler_max, outside the linear
// Doppler axis above, and must be skipped by readers.
// Trailing columns are noise references at center +/- configured doppler_max;
// readers must exclude them from the linear candidate axis.
write_matlab_var<1>("doppler_num_candidates", static_cast<int32_t>(dump_candidate_count), matfp, dims_1d);
write_matlab_var<1>("positive_acq", static_cast<int32_t>(result.positive_acq ? 1 : 0), matfp, dims_1d);
write_matlab_var<1>("acq_doppler_hz", static_cast<float>(d_gnss_synchro->Acq_doppler_hz), matfp, dims_1d);
@@ -614,10 +555,7 @@ void pcps_acquisition::dump_results(const AcquisitionResult& result)
void pcps_acquisition::ensure_dump_grid_allocated()
{
// Sized to the currently *active* bin count (2 when narrowed), not the full
// configured d_num_doppler_bins -- matches what copy_magnitude_grid_to_dump_grid()
// actually writes each cycle, and this size check doubles as the resize trigger
// whenever narrowed/full mode changes between dumps (either direction).
// Resize the dump grid when the active row count changes.
if ((d_grid.n_rows != d_effective_fft_size) || (d_grid.n_cols != d_num_doppler_bins_active))
{
d_grid.zeros(d_effective_fft_size, d_num_doppler_bins_active);
@@ -702,28 +640,8 @@ pcps_acquisition::AcquisitionResult pcps_acquisition::max_to_input_power_statist
{
if (d_num_reference_rows_active > 0U)
{
// Any candidate count whose natural wraparound distance couldn't
// clear reference_bin_min_sidelobes (see needs_extra_reference_row())
// -- from an exactly-known single bin up through a plain full grid
// too small to clear it on its own -- has one or two fixed,
// dedicated noise-only reference rows right after the candidate
// bins (see update_grid_doppler_wipeoffs()), not the "opposite
// side of the grid" the wraparound branch below uses -- with more
// than one candidate bin, that generic wraparound would pick
// whichever bin happens to sit halfway around from the winning
// candidate, sometimes another *candidate* bin (or, at a small
// enough grid, a bin still within the signal's own sidelobe
// skirt), not a genuinely noise-only reference.
//
// With two reference rows (d_num_reference_rows_active > 1, i.e.
// more than one real candidate), pick whichever of the two sits
// opposite the winning candidate's side of center: a winner at or
// beyond +doppler_max/2 or so would otherwise sit right next to
// (or on top of) a reference fixed at +doppler_max, sampling its
// own sidelobe skirt instead of genuine noise. winner_offset uses
// the exact same "-doppler_max + doppler_step*index" mapping as
// result.doppler below, so its sign matches which side of center
// index_doppler actually decodes to.
// Use a dedicated noise reference opposite the winning candidate to avoid
// its sidelobes. Reference rows follow the candidate rows.
uint32_t reference_index = candidate_count;
if (d_num_reference_rows_active > 1U)
{
@@ -735,10 +653,7 @@ pcps_acquisition::AcquisitionResult pcps_acquisition::max_to_input_power_statist
}
else
{
// Reached only when the wraparound distance (num_doppler_bins/2
// bins) already clears reference_bin_min_sidelobes at this
// candidate count (see needs_extra_reference_row()), so this
// in-grid bin is a genuinely noise-only sample.
// The full-grid wraparound distance satisfies the reference-separation target.
const auto index_opp = (index_doppler + num_doppler_bins / 2) % num_doppler_bins;
const auto* magnitude_grid = magnitude_grid_data(index_opp);
d_input_power = static_cast<float>(std::accumulate(magnitude_grid, magnitude_grid + d_effective_fft_size, static_cast<float>(0.0)) / d_effective_fft_size / 2.0 / d_num_noncoherent_integrations_counter);
@@ -897,18 +812,9 @@ void pcps_acquisition::doppler_grid_cpu(const gr_complex* in)
pcps_acquisition::AcquisitionResult pcps_acquisition::compute_statistics()
{
const auto bin_count = d_step_two ? d_num_doppler_bins_step2 : d_num_doppler_bins_active;
// Whenever reference rows are active (see needs_extra_reference_row()),
// bin_count computed rows exist (candidates + one or two noise references)
// but only the candidates are real Doppler candidates -- the reference
// rows must never be selected as the acquisition result (see the two
// statistic functions).
// Reference rows contribute to statistics but cannot win acquisition.
const auto candidate_count = d_step_two ? bin_count : (bin_count - d_num_reference_rows_active);
// Both assisted and plain full-grid search place their candidate_count bins
// symmetrically around doppler_center (see update_grid_doppler_wipeoffs()):
// candidate index k decodes back to center + (k - half_span) * doppler_step.
// Setting doppler_max_used = half_span * doppler_step lets the shared
// "-doppler_max + center + doppler_step * index" decoding below do that
// mapping for either case without duplicating it.
// Use the centered grid's half-span to decode candidate indices into Doppler.
const auto half_span = static_cast<int32_t>((candidate_count - 1U) / 2U);
const auto doppler_step = d_step_two ? d_acq_parameters.doppler_step2 : d_doppler_step;
const auto doppler_max = d_step_two ? static_cast<int32_t>(d_doppler_center_step_two - (static_cast<float>(bin_count) / 2.0) * doppler_step) : half_span * static_cast<int32_t>(d_doppler_step);
@@ -1025,12 +931,7 @@ void pcps_acquisition::acquisition_core(uint64_t sample_count)
{
if (d_acq_parameters.full_grid_search)
{
// Search the entire acquisition grid (accumulate through the full max_dwells)
// before deciding accept/reject, instead of exiting as soon as any single dwell's
// (possibly still noisy, partially non-coherently accumulated) grid crosses
// threshold -- a later dwell's fuller integration can reveal a different, genuinely
// stronger peak elsewhere in the same grid that an early exit never gets the chance
// to compare against.
// Accumulate all dwells before thresholding so a later, stronger peak can win.
if (d_num_noncoherent_integrations_counter == d_acq_parameters.max_dwells)
{
if (result.test_statistics > get_threshold())
@@ -1102,21 +1003,10 @@ bool pcps_acquisition::needs_extra_reference_row(uint32_t candidate_bins) const
{
if (candidate_bins < d_num_doppler_bins)
{
// Narrowed/assisted search (fewer candidates than the full configured
// grid): the in-grid "opposite bin" wraparound distance shrinks along
// with candidate_bins and, at these small counts, essentially never
// clears d_min_reference_separation_hz -- always use a dedicated
// reference row here, same as today's narrowed mode assumed
// unconditionally, regardless of which statistic (CFAR or
// peak-ratio) is active. This keeps a narrowed dump self-describing
// either way, and lets first_vs_second_peak_statistic() exclude the
// reference bin from candidacy under peak-ratio too, not just CFAR.
// Narrowed grids always retain reference rows, including in peak-ratio mode.
return true;
}
// Full grid: only pay for a dedicated reference row when CFAR needs a
// genuinely noise-only sample and the in-grid wraparound can't supply one
// on its own -- first_vs_second_peak_statistic() has no such reference
// concept, so a full (non-narrowed) peak-ratio search never needs one.
// Full grids need extra references only for CFAR with insufficient wraparound separation.
return d_use_CFAR_algorithm_flag && (static_cast<float>(candidate_bins / 2) * static_cast<float>(d_doppler_step) < d_min_reference_separation_hz);
}
@@ -1136,31 +1026,17 @@ void pcps_acquisition::set_doppler_center(int32_t doppler_center)
void pcps_acquisition::set_doppler_num_bins(uint32_t num_doppler_bins)
{
gr::thread::scoped_lock lock(d_setlock); // require mutex with work function called by the scheduler
// 0 is the only value a caller can't just supply directly -- the full
// grid's own candidate count is otherwise private to this class -- so it
// means "the full configured range" here, same as always. Any other
// value is taken literally and unconditionally: an assisted, exactly-
// known Doppler (num_doppler_bins == 1) always searches exactly 1 bin,
// no config-driven widening -- a caller that wants a safety margin
// around its own estimate should just ask for that many bins directly.
// Zero restores the full grid; positive counts request explicit search widths.
uint32_t candidate_bins = (num_doppler_bins == 0U) ? d_num_doppler_bins : num_doppler_bins;
candidate_bins = std::min(candidate_bins, d_num_doppler_bins);
// See d_num_reference_rows_active's doc comment: 2 dedicated rows
// (opposite-sign pair) whenever a reference is needed and there's more
// than one real candidate to be opposite of; 1 (the historical fixed
// +doppler_max placement) at exactly one real candidate, since it always
// sits at offset 0 -- no side for a second, mirrored row to usefully
// cover.
// One reference for a single candidate, otherwise a pair on opposite sides.
const uint32_t num_reference_rows = needs_extra_reference_row(candidate_bins) ? (candidate_bins > 1U ? 2U : 1U) : 0U;
const uint32_t num_doppler_bins_active = candidate_bins + num_reference_rows;
if (num_reference_rows != d_num_reference_rows_active || num_doppler_bins_active != d_num_doppler_bins_active)
{
d_num_reference_rows_active = num_reference_rows;
d_num_doppler_bins_active = num_doppler_bins_active;
// See d_threshold_active's doc comment: recalibrated for whatever
// candidate count is active now, excluding the reference row
// itself (it's never a candidate result, so it isn't one of the
// hypotheses being tested for a false alarm either).
// Recalibrate PFA for candidate bins only, excluding noise references.
d_threshold_active = d_acq_parameters.pfa > 0.0 ? compute_threshold(d_acq_parameters.pfa, d_effective_fft_size, candidate_bins, d_acq_parameters.bit_transition_flag ? 1 : d_acq_parameters.max_dwells) : d_acq_parameters.threshold;
update_grid_doppler_wipeoffs();
DLOG(INFO) << " Doppler bin count for Channel: " << d_channel << " => active Doppler bins: " << d_num_doppler_bins_active << ", Doppler center: " << d_doppler_center;
@@ -216,24 +216,9 @@ private:
bool positive_acq{false};
};
// Whether a search of candidate_bins Doppler bins needs a dedicated
// noise-only reference row. Two cases: a narrowed/assisted search
// (candidate_bins < d_num_doppler_bins) always needs one, unconditionally
// -- same as today's narrowed mode assumed, and regardless of which
// statistic is active, so a narrowed dump stays self-describing either
// way; a plain full grid (candidate_bins == d_num_doppler_bins) needs one
// only when CFAR is active and the in-grid wraparound distance
// max_to_input_power_statistic() would otherwise use (candidate_bins/2
// bins away) can't clear d_min_reference_separation_hz at this candidate
// count and d_doppler_step -- a large enough full grid may already clear
// it via wraparound and need no extra row at all, and peak-ratio has no
// such reference concept to begin with. Reads
// d_use_CFAR_algorithm_flag, so -- unlike d_full_grid_reference_needs_extra_row's
// own copy of this same formula, evaluated inline in the member-initializer
// list against the conf_ constructor parameter directly -- this is only
// safe to call once construction has actually finished (d_use_CFAR_algorithm_flag
// is declared, so initialized, after d_full_grid_reference_needs_extra_row):
// only set_doppler_num_bins() calls this, never the constructor.
// Narrowed grids always need reference rows; full grids need them only
// for CFAR when wraparound separation is insufficient.
// Call after construction: d_use_CFAR_algorithm_flag is initialized later.
bool needs_extra_reference_row(uint32_t candidate_bins) const;
void update_local_carrier(own::span<gr_complex> carrier_vector, float freq) const;
@@ -264,15 +249,8 @@ private:
const float* magnitude_grid_data(uint32_t doppler_index) const;
bool is_fdma();
float get_threshold() const;
// candidate_count: number of computed grid rows eligible to be selected as the
// acquisition result -- narrowed mode, and a plain full grid whose natural
// reference distance can't clear reference_bin_min_sidelobes, both compute
// d_num_reference_rows_active (1 or 2) extra trailing rows (see
// d_full_grid_reference_needs_extra_row) that are never real candidates, so
// candidate_count excludes them. max_to_input_power_statistic additionally
// takes num_doppler_bins (>= candidate_count), the full computed row count,
// for its CFAR reference-bin lookup -- first_vs_second_peak_statistic has no
// such reference concept, so it only needs candidate_count.
// candidate_count excludes trailing noise-reference rows.
// CFAR also takes the total computed row count for reference lookup.
AcquisitionResult first_vs_second_peak_statistic(uint32_t candidate_count, int32_t doppler_max, int32_t doppler_step);
AcquisitionResult max_to_input_power_statistic(uint32_t num_doppler_bins, uint32_t candidate_count, int32_t doppler_max, int32_t doppler_step);
void wait_if_active();
@@ -290,39 +268,15 @@ private:
const uint32_t d_num_doppler_bins;
const uint32_t d_num_doppler_bins_step2;
const uint32_t d_dump_channel;
// (reference_bin_min_sidelobes + 0.5) / coherent_integration_time_seconds: the
// Doppler-domain target a CFAR noise-reference bin should clear from the
// search grid's candidate span, in Hz -- used to decide, for whatever
// candidate bin count is currently active, WHETHER it needs a dedicated
// extra reference row (see needs_extra_reference_row() below); never to
// place that row beyond d_doppler_max. See reference_bin_min_sidelobes'
// doc comment in acq_conf.h for why exceeding d_doppler_max is
// deliberately never done.
// CFAR reference separation target (Hz): (sidelobes + 0.5) / integration time.
// Selects extra reference rows; never extends them beyond d_doppler_max.
const float d_min_reference_separation_hz;
// True when a plain full grid's natural wraparound reference distance
// (d_num_doppler_bins/2 bins, i.e. what max_to_input_power_statistic's
// "opposite bin" formula would reach) can't clear d_min_reference_separation_hz
// -- computed once at construction from d_num_doppler_bins/d_doppler_step, which
// never change afterward. This is exactly needs_extra_reference_row(d_num_doppler_bins)
// (see below), kept as its own const member only because it's needed before
// construction finishes, to size d_num_doppler_bins_full_grid_active and
// therefore d_magnitude_grid/d_grid_doppler_wipeoffs -- every other caller,
// at any candidate bin count, goes through needs_extra_reference_row(). Only
// says whether a dedicated reference row is needed at all -- see
// d_num_reference_rows_active for how many (1 or 2).
// Full-grid CFAR wraparound separation is insufficient.
// Computed from conf_ during construction, before the runtime flag exists.
const bool d_full_grid_reference_needs_extra_row;
// d_num_doppler_bins, plus reference rows (see d_num_reference_rows_active --
// 2 here, since a full grid worth searching a dedicated reference row for is
// always > 1 candidate bin in practice) when d_full_grid_reference_needs_extra_row
// is true. This (not d_num_doppler_bins) is the full grid's row count -- both
// the constructed value of d_num_doppler_bins_active and what
// set_doppler_num_bins(0) (the "full range" sentinel) restores it to.
// Full-grid candidate count plus reference rows; restored by set_doppler_num_bins(0).
const uint32_t d_num_doppler_bins_full_grid_active;
// Step-1 row capacity of d_magnitude_grid/d_grid_doppler_wipeoffs (and of the
// CUDA engine). A narrowed search of N < d_num_doppler_bins candidates adds up
// to 2 reference rows, so N = d_num_doppler_bins - 1 needs d_num_doppler_bins + 1
// rows even when the full grid itself needs none; d_num_doppler_bins + 2 covers
// every row count set_doppler_num_bins() can produce.
// Capacity includes two reference rows, even when only narrowed grids need them.
const uint32_t d_num_doppler_bins_capacity;
const float d_threshold_step_two;
const bool d_cshort;
@@ -337,37 +291,14 @@ private:
int32_t d_state;
int32_t d_doppler_center;
int32_t d_doppler_bias;
// Number of step-1 Doppler grid rows actually searched right now: the
// candidate bin count last passed to set_doppler_num_bins() (or the full
// grid's candidate count, if it was called with the 0 sentinel), plus
// d_num_reference_rows_active. Always <= d_num_doppler_bins_capacity, the row
// count d_magnitude_grid/d_grid_doppler_wipeoffs are allocated for.
// Active candidate and reference rows; bounded by d_num_doppler_bins_capacity.
uint32_t d_num_doppler_bins_active;
// How many of d_num_doppler_bins_active's trailing rows are dedicated
// noise-only reference rows (see needs_extra_reference_row()) rather than
// real Doppler candidates -- set directly by set_doppler_num_bins() for
// whatever candidate count is currently active. 0 when no reference row
// is needed (plain full grid, wraparound clears reference_bin_min_sidelobes
// on its own). 1 when needed but there's only a single real candidate (an
// exactly-known Doppler, always at offset 0 from center -- no sign for a
// second, opposite-side row to usefully mirror), same fixed placement at
// doppler_center + doppler_max as always. 2 when needed and there's more
// than one real candidate: a row at doppler_center + doppler_max (index
// candidate_count) AND one at doppler_center - doppler_max (index
// candidate_count + 1) -- max_to_input_power_statistic() picks whichever
// of the two sits on the opposite side of center from the winning
// candidate, so the reference is never on the same side as (and closer to)
// a signal that happens to be riding near the edge of the search range.
// Trailing noise-reference rows: 0 if unnecessary, 1 at center + doppler_max
// for a single candidate, otherwise 2 at center +/- doppler_max.
// CFAR uses the reference opposite the winning candidate.
uint32_t d_num_reference_rows_active;
// Detection threshold for the currently active candidate bin count (see
// d_num_doppler_bins_active) -- compute_threshold() folds the number of
// bins being searched into the false-alarm probability (more bins tested
// means more chances to false-alarm at a fixed per-bin PFA), so this must
// be recalculated by set_doppler_num_bins() every time that count
// changes; reusing a threshold calibrated for a different bin count would
// over- or under-detect. Excludes the reference row itself from the count
// passed to compute_threshold() -- it's never a candidate result, so it
// isn't one of the hypotheses being tested for a false alarm either.
// CFAR threshold for active candidates, excluding reference rows.
// Recompute when the candidate count changes to preserve the requested PFA.
float d_threshold_active;
uint32_t d_buffer_sample_count;
uint32_t d_channel;
+5 -24
View File
@@ -74,32 +74,13 @@ public:
bool make_2_steps{false};
bool use_automatic_resampler{false};
bool enable_monitor_output{false};
// Target number of correlation sidelobes (in Doppler) the CFAR noise-floor
// reference bin should clear from the search grid's own candidate span, used
// only to decide WHETHER a plain full grid needs a dedicated extra reference
// row instead of reusing an in-grid candidate (see
// d_full_grid_reference_needs_extra_row in pcps_acquisition.h) -- never to
// place that row (or narrowed mode's own reference row) beyond the configured
// doppler_max. doppler_max is the receiver-validated edge of the search/filter
// passband the rest of the acquisition chain is designed for; searching beyond
// it to chase a theoretical sidelobe target risks sampling "noise" from a
// region the decimation/anti-alias response is no longer flat, corrupting the
// estimate instead of cleaning it up (this cost real satellites at hot start
// in an earlier version that did push beyond doppler_max -- see
// update_grid_doppler_wipeoffs()'s narrowed-branch comment). Sidelobe spacing
// is set by the coherent integration time (~1/sampled_ms), not by doppler_step
// or the bin count, so at a small enough grid or doppler_max, this target
// simply won't be met -- accepted, not something to fix by exceeding
// doppler_max. Only takes effect when use_CFAR_algorithm_flag is set (the
// non-CFAR peak-ratio statistic never uses a Doppler-domain reference).
// CFAR reference separation target, in correlation sidelobes (~1/T Hz).
// Determines whether a full grid needs extra reference rows. References stay
// within doppler_max: the filter response beyond it can bias noise estimates.
uint32_t reference_bin_min_sidelobes{4U};
// Accumulate through the full max_dwells before deciding accept/reject, instead
// of exiting as soon as any single dwell's (possibly still noisy, partially
// accumulated) grid crosses threshold -- a later dwell's fuller integration can
// reveal a different, genuinely stronger peak elsewhere in the grid that an early
// exit never gets the chance to compare against. Opt-in: off by default, enable
// per-implementation in the .conf (e.g. Acquisition_1B.full_grid_search = true).
// Opt-in accumulation through max_dwells before thresholding, allowing a
// later, stronger peak to win (Acquisition_<signal>.full_grid_search).
bool full_grid_search{false};
// Evaluate the PCPS grid on a CUDA GPU (requires ENABLE_CUDA at build time)