FERS 1.0.0
The Flexible Extensible Radar Simulator
Loading...
Searching...
No Matches
finalizer_pipeline.cpp
Go to the documentation of this file.
1// SPDX-License-Identifier: GPL-2.0-only
2//
3// Copyright (c) 2026-present FERS Contributors (see AUTHORS.md).
4//
5// See the GNU GPLv2 LICENSE file in the FERS project root for more information.
6
8
9#include <algorithm>
10#include <cmath>
11#include <highfive/highfive.hpp>
12#include <ranges>
13
14#include "core/logging.h"
16#include "core/parameters.h"
18#include "radar/receiver.h"
19#include "radar/target.h"
20#include "radar/transmitter.h"
21#include "serial/hdf5_handler.h"
22#include "serial/response.h"
23#include "signal/dsp_filters.h"
25#include "timing/timing.h"
26
28{
29 void advanceTimingModel(timing::Timing* timing_model, const radar::Receiver* receiver, const RealType rate)
30 {
31
32 if ((timing_model == nullptr) || !timing_model->isEnabled())
33 {
34 return;
35 }
36
37 // Convert the duration-derived sample advance to a signed temporary first.
38 // The physical calculation can legitimately produce a negative or zero result
39 // (for example due to floor() or an inter-pulse gap that is not positive),
40 // but skipSamples() models advancing by a non-negative sample count only.
41 // After validating that the computed value is > 0, cast to std::size_t for
42 // the skipSamples() call chain:
43 // Timing::skipSamples -> ClockModelGenerator::skipSamples ->
44 // MultirateGenerator::skipSamples.
45 // This preserves the sign until validation and avoids passing invalid negative
46 // counts into the timing/noise generators.
47 if (timing_model->getSyncOnPulse())
48 {
49 timing_model->reset();
50 const auto skip_samples = static_cast<long long>(std::floor(rate * receiver->getWindowSkip()));
51 if (skip_samples > 0)
52 {
53 timing_model->skipSamples(static_cast<std::size_t>(skip_samples));
54 }
55 }
56 else
57 {
58 const RealType inter_pulse_skip_duration = 1.0 / receiver->getWindowPrf() - receiver->getWindowLength();
59 const auto samples_to_skip = static_cast<long long>(std::floor(rate * inter_pulse_skip_duration));
60 if (samples_to_skip > 0)
61 {
62 timing_model->skipSamples(static_cast<std::size_t>(samples_to_skip));
63 }
64 }
65 }
66
67 std::tuple<RealType, RealType> calculateJitteredStart(const RealType ideal_start, const RealType first_phase_noise,
68 const RealType carrier_freq, const RealType rate)
69 {
70 const RealType actual_start = ideal_start + first_phase_noise / (2.0 * PI * carrier_freq);
71 const RealType rounded_start = std::round(actual_start * rate) / rate;
72 const RealType fractional_delay = actual_start * rate - std::round(actual_start * rate);
73 return {rounded_start, fractional_delay};
74 }
75
76 void applyCwInterference(std::span<ComplexType> window, const RealType actual_start, const RealType dt,
77 const radar::Receiver* receiver, const std::vector<radar::Transmitter*>& cw_sources,
78 const std::vector<std::unique_ptr<radar::Target>>* targets)
79 {
80 RealType t_sample = actual_start;
81 for (auto& window_sample : window)
82 {
83 ComplexType cw_interference_sample{0.0, 0.0};
84 for (const auto* cw_source : cw_sources)
85 {
87 {
88 cw_interference_sample +=
89 simulation::calculateDirectPathContribution(cw_source, receiver, t_sample);
90 }
91 for (const auto& target_ptr : *targets)
92 {
93 cw_interference_sample +=
94 simulation::calculateReflectedPathContribution(cw_source, receiver, target_ptr.get(), t_sample);
95 }
96 }
97 window_sample += cw_interference_sample;
98 t_sample += dt;
99 }
100 }
101
102 void applyPulsedInterference(std::vector<ComplexType>& iq_buffer,
103 const std::vector<std::unique_ptr<serial::Response>>& interference_log)
104 {
105 for (const auto& response : interference_log)
106 {
107 unsigned psize;
108 RealType prate;
109 const auto rendered_pulse = response->renderBinary(prate, psize, 0.0);
110
111 const RealType dt_sim = 1.0 / prate;
112 const auto start_index = static_cast<size_t>((response->startTime() - params::startTime()) / dt_sim);
113
114 for (size_t i = 0; i < psize; ++i)
115 {
116 if (start_index + i < iq_buffer.size())
117 {
118 iq_buffer[start_index + i] += rendered_pulse[i];
119 }
120 }
121 }
122 }
123
124 void addPhaseNoiseToWindow(std::span<const RealType> noise, std::span<ComplexType> window)
125 {
126 for (auto [n, w] : std::views::zip(noise, window))
127 {
128 w *= std::polar(1.0, n);
129 }
130 }
131
132 RealType applyDownsamplingAndQuantization(std::vector<ComplexType>& buffer)
133 {
134 if (params::oversampleRatio() > 1)
135 {
136 buffer = std::move(fers_signal::downsample(buffer));
137 }
138 return quantizeAndScaleWindow(buffer);
139 }
140
141 void exportCwToHdf5(const std::string& filename, const std::vector<ComplexType>& iq_buffer,
142 const RealType fullscale, const RealType ref_freq, const core::OutputFileMetadata* metadata)
143 {
144 std::scoped_lock lock(serial::hdf5_global_mutex);
145 try
146 {
147 HighFive::File file(filename, HighFive::File::Truncate);
148
149 std::vector<RealType> i_data(iq_buffer.size());
150 std::vector<RealType> q_data(iq_buffer.size());
151 std::ranges::transform(iq_buffer, i_data.begin(), [](const auto& c) { return c.real(); });
152 std::ranges::transform(iq_buffer, q_data.begin(), [](const auto& c) { return c.imag(); });
153
154 HighFive::DataSet i_dataset = file.createDataSet<RealType>("I_data", HighFive::DataSpace::From(i_data));
155 i_dataset.write(i_data);
156 HighFive::DataSet q_dataset = file.createDataSet<RealType>("Q_data", HighFive::DataSpace::From(q_data));
157 q_dataset.write(q_data);
158
159 file.createAttribute("sampling_rate", params::rate());
160 file.createAttribute("start_time", params::startTime());
161 file.createAttribute("fullscale", fullscale);
162 file.createAttribute("reference_carrier_frequency", ref_freq);
163 if (metadata != nullptr)
164 {
166 }
167
168 LOG(logging::Level::INFO, "Successfully exported CW data to '{}'", filename);
169 }
170 catch (const HighFive::Exception& err)
171 {
172 LOG(logging::Level::FATAL, "Error writing CW data to HDF5 file '{}': {}", filename, err.what());
173 }
174 }
175}
Header for radar channel propagation and interaction models.
Manages radar signal reception and response processing.
Definition receiver.h:37
bool checkFlag(RecvFlag flag) const noexcept
Checks if a specific flag is set.
Definition receiver.h:88
RealType getWindowPrf() const noexcept
Retrieves the pulse repetition frequency (PRF) of the radar window.
Definition receiver.h:116
RealType getWindowSkip() const noexcept
Retrieves the window skip time.
Definition receiver.h:123
RealType getWindowLength() const noexcept
Retrieves the radar window length.
Definition receiver.h:109
Represents a timing source for simulation.
Definition timing.h:36
void reset() noexcept
Resets the timing model.
Definition timing.h:136
void skipSamples(std::size_t samples) noexcept
Skips a number of samples in the timing model.
Definition timing.cpp:31
bool getSyncOnPulse() const noexcept
Checks if the timing source synchronizes on pulse.
Definition timing.h:90
bool isEnabled() const noexcept
Checks if the timing source is enabled.
Definition timing.h:116
double RealType
Type for real numbers.
Definition config.h:27
std::complex< RealType > ComplexType
Type for complex numbers.
Definition config.h:35
constexpr RealType PI
Mathematical constant π (pi).
Definition config.h:43
Header file for Digital Signal Processing (DSP) filters and upsampling/downsampling functionality.
Declares focused, testable pipeline steps for receiver finalization.
Header file for HDF5 data export and import functions.
Header file for the logging system.
#define LOG(level,...)
Definition logging.h:19
std::vector< ComplexType > downsample(std::span< const ComplexType > in)
Downsamples a signal by a given ratio.
@ FATAL
Fatal level for severe error events.
@ INFO
Info level for informational messages.
RealType rate() noexcept
Get the rendering sample rate.
Definition parameters.h:121
RealType startTime() noexcept
Get the start time for the simulation.
Definition parameters.h:103
unsigned oversampleRatio() noexcept
Get the oversampling ratio.
Definition parameters.h:151
void addPhaseNoiseToWindow(std::span< const RealType > noise, std::span< ComplexType > window)
Applies a pre-generated sequence of phase noise samples to an I/Q buffer.
RealType applyDownsamplingAndQuantization(std::vector< ComplexType > &buffer)
Downsamples and quantizes an IQ buffer.
void advanceTimingModel(timing::Timing *timing_model, const radar::Receiver *receiver, const RealType rate)
Advances the receiver's timing model to the start of the next processing window.
void exportCwToHdf5(const std::string &filename, const std::vector< ComplexType > &iq_buffer, const RealType fullscale, const RealType ref_freq, const core::OutputFileMetadata *metadata)
Exports a finalized continuous-wave IQ buffer to an HDF5 file.
std::tuple< RealType, RealType > calculateJitteredStart(const RealType ideal_start, const RealType first_phase_noise, const RealType carrier_freq, const RealType rate)
Calculates the jittered start time and fractional delay from a phase noise sample.
void applyPulsedInterference(std::vector< ComplexType > &iq_buffer, const std::vector< std::unique_ptr< serial::Response > > &interference_log)
Renders and applies pulsed interference to a continuous-wave IQ buffer.
void applyCwInterference(std::span< ComplexType > window, const RealType actual_start, const RealType dt, const radar::Receiver *receiver, const std::vector< radar::Transmitter * > &cw_sources, const std::vector< std::unique_ptr< radar::Target > > *targets)
Applies continuous-wave interference to a time window.
RealType quantizeAndScaleWindow(std::span< ComplexType > window)
Simulates ADC quantization and scales a window of complex I/Q samples.
std::mutex hdf5_global_mutex
Global mutex to protect all HDF5 C-library calls, which are not thread-safe.
void writeOutputFileMetadataAttributes(HighFive::File &file, const core::OutputFileMetadata &metadata)
Writes additive FERS output metadata attributes to an open HDF5 file.
ComplexType calculateDirectPathContribution(const Transmitter *trans, const Receiver *recv, const RealType timeK)
Calculates the complex envelope contribution for a direct propagation path (Tx -> Rx) at a specific t...
ComplexType calculateReflectedPathContribution(const Transmitter *trans, const Receiver *recv, const Target *targ, const RealType timeK)
Calculates the complex envelope contribution for a reflected path (Tx -> Tgt -> Rx) at a specific tim...
Defines the Parameters struct and provides methods for managing simulation parameters.
Radar Receiver class for managing signal reception and response handling.
Classes for managing radar signal responses.
Header for receiver-side signal processing and rendering.
Defines classes for radar targets and their Radar Cross-Section (RCS) models.
Timing source for simulation objects.
Header file for the Transmitter class in the radar namespace.