From 09f143fe077e6442d150784b369ddcfd7106b0a0 Mon Sep 17 00:00:00 2001 From: Carles Fernandez Date: Tue, 29 Sep 2026 10:10:24 +0200 Subject: [PATCH] Summarize comments --- .../gnuradio_blocks/pcps_acquisition.cc | 170 +++--------------- .../gnuradio_blocks/pcps_acquisition.h | 103 ++--------- src/algorithms/acquisition/libs/acq_conf.h | 29 +-- 3 files changed, 45 insertions(+), 257 deletions(-) diff --git a/src/algorithms/acquisition/gnuradio_blocks/pcps_acquisition.cc b/src/algorithms/acquisition/gnuradio_blocks/pcps_acquisition.cc index 8dfdc6179..628aafd80 100644 --- a/src/algorithms/acquisition/gnuradio_blocks/pcps_acquisition.cc +++ b/src/algorithms/acquisition/gnuradio_blocks/pcps_acquisition.cc @@ -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(d_num_doppler_bins / 2) * static_cast(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(1U, static_cast(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 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((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(doppler_wipeoff_data(candidate_count), d_fft_size), static_cast(d_doppler_bias + d_doppler_center + static_cast(d_doppler_max))); if (d_num_reference_rows_active > 1U) { @@ -557,20 +512,8 @@ void pcps_acquisition::dump_results(const AcquisitionResult& result) std::array dims_1d{1, 1}; std::array 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((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(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(dump_candidate_count), matfp, dims_1d); write_matlab_var<1>("positive_acq", static_cast(result.positive_acq ? 1 : 0), matfp, dims_1d); write_matlab_var<1>("acq_doppler_hz", static_cast(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(std::accumulate(magnitude_grid, magnitude_grid + d_effective_fft_size, static_cast(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((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(d_doppler_center_step_two - (static_cast(bin_count) / 2.0) * doppler_step) : half_span * static_cast(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(candidate_bins / 2) * static_cast(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; diff --git a/src/algorithms/acquisition/gnuradio_blocks/pcps_acquisition.h b/src/algorithms/acquisition/gnuradio_blocks/pcps_acquisition.h index a5d75eb25..581e14ba4 100644 --- a/src/algorithms/acquisition/gnuradio_blocks/pcps_acquisition.h +++ b/src/algorithms/acquisition/gnuradio_blocks/pcps_acquisition.h @@ -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 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; diff --git a/src/algorithms/acquisition/libs/acq_conf.h b/src/algorithms/acquisition/libs/acq_conf.h index 2b081559c..faf13f32e 100644 --- a/src/algorithms/acquisition/libs/acq_conf.h +++ b/src/algorithms/acquisition/libs/acq_conf.h @@ -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_.full_grid_search). bool full_grid_search{false}; // Evaluate the PCPS grid on a CUDA GPU (requires ENABLE_CUDA at build time)