FERS 0.1.0
The Flexible Extensible Radar Simulator
Loading...
Searching...
No Matches
fmcw_validation.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
7#include "fmcw_validation.h"
8
9#include <algorithm>
10#include <cmath>
11#include <cstdint>
12#include <format>
13
14#include "core/logging.h"
15#include "core/parameters.h"
16#include "signal/radar_signal.h"
17
19{
20 namespace
21 {
22 /// Emits a warning when a streaming scenario may allocate a large I/Q buffer.
23 void warnStreamingMemory(const std::string& owner, const std::string_view mode_name)
24 {
25 const RealType duration = std::max<RealType>(0.0, params::endTime() - params::startTime());
27 const auto estimated_samples = static_cast<std::uint64_t>(std::ceil(duration * effective_rate));
28 const std::uint64_t estimated_bytes = estimated_samples * sizeof(ComplexType);
29 constexpr std::uint64_t warning_threshold_bytes = 2ULL * 1024ULL * 1024ULL * 1024ULL;
31 {
32 return;
33 }
34
35 const double estimated_gib = static_cast<double>(estimated_bytes) / (1024.0 * 1024.0 * 1024.0);
37 "{} will buffer about {:.2f} GiB of {} streaming output for simulation_duration={} s at "
38 "effective_rate={} Hz. Large simulation_time * bandwidth/rate products need chunking.",
40 }
41
42 /// Returns schedule periods, or the full simulation as one implicit active period.
43 std::vector<radar::SchedulePeriod> effectiveSchedule(const std::vector<radar::SchedulePeriod>& schedule)
44 {
45 if (!schedule.empty())
46 {
47 return schedule;
48 }
50 }
51
52 /// Validates common FMCW sweep constraints.
53 void validateCommonSweep(const fers_signal::RadarSignal& wave, const std::string& owner,
54 const RealType chirp_duration, const RealType chirp_bandwidth,
56 {
57 if (chirp_duration <= 0.0)
58 {
59 throw_error(owner + " has FMCW chirp_duration <= 0.");
60 }
61 if (chirp_bandwidth <= 0.0)
62 {
63 throw_error(owner + " has FMCW chirp_bandwidth <= 0.");
64 }
65
66 const RealType f_low = std::min(sweep_start, sweep_end);
67 const RealType f_high = std::max(sweep_start, sweep_end);
69 const RealType max_baseband = std::max(std::abs(f_low), std::abs(f_high));
71 {
72 throw_error(owner + " violates FMCW baseband aliasing constraint.");
73 }
74 if (max_baseband > 0.0 && effective_rate < 1.1 * max_baseband)
75 {
77 "{} is within 10% of the FMCW aliasing limit: effective_rate={} Hz, max_baseband={} Hz.", owner,
79 }
80
81 const RealType min_rf = wave.getCarrier() + f_low;
82 if (min_rf <= 0.0)
83 {
84 throw_error(owner + " yields a non-positive RF-equivalent frequency.");
85 }
86 }
87
88 void validateSfcwWaveform(const fers_signal::RadarSignal& wave, const std::string& owner,
90 {
91 if (sfcw.getStepCount() < 1U)
92 {
93 throw_error(owner + " has SFCW step_count < 1.");
94 }
95 if (sfcw.getStepSize() == 0.0 || !std::isfinite(sfcw.getStepSize()))
96 {
97 throw_error(owner + " has invalid SFCW step_size.");
98 }
99 if (sfcw.getDwellTime() <= 0.0 || !std::isfinite(sfcw.getDwellTime()))
100 {
101 throw_error(owner + " has SFCW dwell_time <= 0.");
102 }
103 if (sfcw.getStepPeriod() <= 0.0 || !std::isfinite(sfcw.getStepPeriod()))
104 {
105 throw_error(owner + " has SFCW step_period <= 0.");
106 }
107 if (sfcw.getDwellTime() > sfcw.getStepPeriod())
108 {
109 throw_error(owner + " has SFCW dwell_time longer than step_period.");
110 }
111
112 const RealType f_low =
113 std::min(sfcw.firstFrequency(wave.getCarrier()), sfcw.lastFrequency(wave.getCarrier()));
114 if (f_low <= 0.0)
115 {
116 throw_error(owner + " yields a non-positive SFCW RF step frequency.");
117 }
118
120 if (output_samples_per_dwell < 1.0)
121 {
122 LOG(logging::Level::WARNING, "{} has fewer than one output sample per SFCW dwell.", owner);
123 }
125 params::rate() * static_cast<RealType>(params::oversampleRatio()) * sfcw.getDwellTime();
127 {
128 LOG(logging::Level::WARNING, "{} has fewer than one internal sample per SFCW dwell.", owner);
129 }
130 warnStreamingMemory(owner, "SFCW");
131 }
132 }
133
135 {
136 if (const auto* fmcw = wave.getFmcwChirpSignal(); fmcw != nullptr)
137 {
138 if (fmcw->getChirpPeriod() < fmcw->getChirpDuration())
139 {
140 throw_error(owner + " has chirp_period shorter than chirp_duration; FMCW requires T_rep >= T_c.");
141 }
142
143 const RealType sweep_start = fmcw->getStartFrequencyOffset();
144 const RealType sweep_end =
145 sweep_start + (fmcw->isDownChirp() ? -fmcw->getChirpBandwidth() : fmcw->getChirpBandwidth());
146 validateCommonSweep(wave, owner, fmcw->getChirpDuration(), fmcw->getChirpBandwidth(), sweep_start,
148 warnStreamingMemory(owner, "FMCW");
149 return;
150 }
151
152 if (const auto* triangle = wave.getFmcwTriangleSignal(); triangle != nullptr)
153 {
154 const RealType sweep_start = triangle->getStartFrequencyOffset();
155 const RealType sweep_end = sweep_start + triangle->getChirpBandwidth();
156 validateCommonSweep(wave, owner, triangle->getChirpDuration(), triangle->getChirpBandwidth(), sweep_start,
158 warnStreamingMemory(owner, "FMCW");
159 return;
160 }
161
162 if (const auto* sfcw = wave.getSteppedFrequencySignal(); sfcw != nullptr)
163 {
165 }
166 }
167
169 const std::string& owner, const Thrower& throw_error)
170 {
171 const bool is_pulsed = !wave.isCw() && !wave.isFmcwFamily() && !wave.isSteppedFrequency();
172 const bool matches = (mode == radar::OperationMode::PULSED_MODE && is_pulsed) ||
173 (mode == radar::OperationMode::CW_MODE && wave.isCw()) ||
174 (mode == radar::OperationMode::FMCW_MODE && wave.isFmcwFamily()) ||
175 (mode == radar::OperationMode::SFCW_MODE && wave.isSteppedFrequency());
176 if (!matches)
177 {
178 throw_error(owner + " mode does not match waveform '" + wave.getName() + "'.");
179 }
180 }
181
182 void validateSchedule(const std::vector<radar::SchedulePeriod>& schedule, const fers_signal::FmcwChirpSignal& fmcw,
183 const std::string& owner, const Thrower& throw_error)
184 {
185 for (const auto& period : effectiveSchedule(schedule))
186 {
187 const RealType duration = period.end - period.start;
188 if (duration < fmcw.getChirpDuration())
189 {
190 throw_error(std::format(
191 "{} has schedule period [{}, {}] duration {} s shorter than FMCW chirp_duration T_c={} s.", owner,
192 period.start, period.end, duration, fmcw.getChirpDuration()));
193 }
194 if (duration < fmcw.getChirpPeriod())
195 {
196 LOG(logging::Level::WARNING, "{} has a schedule period [{}, {}] shorter than FMCW chirp_period ({}s).",
197 owner, period.start, period.end, fmcw.getChirpPeriod());
198 }
199 }
200 }
201
202 void validateSchedule(const std::vector<radar::SchedulePeriod>& schedule, const fers_signal::RadarSignal& wave,
203 const std::string& owner, const Thrower& throw_error)
204 {
205 if (const auto* fmcw = wave.getFmcwChirpSignal(); fmcw != nullptr)
206 {
208 return;
209 }
210
211 if (const auto* sfcw = wave.getSteppedFrequencySignal(); sfcw != nullptr)
212 {
213 for (const auto& period : effectiveSchedule(schedule))
214 {
215 const RealType duration = period.end - period.start;
216 if (duration < sfcw->getDwellTime())
217 {
218 LOG(logging::Level::WARNING, "{} has schedule period [{}, {}] shorter than one SFCW dwell ({}s).",
219 owner, period.start, period.end, sfcw->getDwellTime());
220 }
221 if (duration < sfcw->getSweepPeriod())
222 {
223 LOG(logging::Level::WARNING, "{} has schedule period [{}, {}] shorter than one SFCW sweep ({}s).",
224 owner, period.start, period.end, sfcw->getSweepPeriod());
225 }
226 }
227 return;
228 }
229
230 const auto* triangle = wave.getFmcwTriangleSignal();
231 if (triangle == nullptr)
232 {
233 return;
234 }
235
236 const RealType T_tri = triangle->getTrianglePeriod();
237 for (const auto& period : effectiveSchedule(schedule))
238 {
239 const RealType duration = period.end - period.start;
240 if (duration < T_tri)
241 {
242 throw_error(std::format(
243 "{} has schedule period [{}, {}] duration {} s shorter than FMCW triangle_period T_tri={} s.",
244 owner, period.start, period.end, duration, T_tri));
245 }
246 const RealType full_triangles = std::floor(duration / T_tri);
248 const RealType leftover = duration - used;
249 if (leftover > 1.0e-12)
250 {
252 "{} has schedule period [{}, {}] that is not an integer multiple of FMCW triangle_period ({}s); "
253 "{}s will be silent after the last complete triangle.",
254 owner, period.start, period.end, T_tri, leftover);
255 }
256 }
257 }
258}
FMCW linear chirp signal implementation.
RealType getChirpDuration() const noexcept
Gets the chirp duration in seconds.
RealType getChirpPeriod() const noexcept
Gets the chirp period in seconds.
Class representing a radar signal with associated properties.
Stepped-frequency continuous-wave signal implementation.
RealType getStepSize() const noexcept
Gets the uniform frequency step in hertz.
RealType lastFrequency(RealType carrier_frequency) const noexcept
Gets final-step RF frequency in hertz.
RealType firstFrequency(RealType carrier_frequency) const noexcept
Gets first-step RF frequency in hertz.
std::size_t getStepCount() const noexcept
Gets the number of steps per sweep.
RealType getStepPeriod() const noexcept
Gets step repetition period in seconds.
RealType getDwellTime() const noexcept
Gets active dwell time per step in seconds.
double RealType
Type for real numbers.
Definition config.h:27
std::complex< RealType > ComplexType
Type for complex numbers.
Definition config.h:35
Header file for the logging system.
#define LOG(level,...)
Definition logging.h:19
@ WARNING
Warning level for potentially harmful situations.
RealType endTime() noexcept
Get the end time for the simulation.
Definition parameters.h:109
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
OperationMode
Defines the operational mode of a radar component.
Definition radar_obj.h:39
@ SFCW_MODE
The component operates in a stepped-frequency CW streaming mode.
@ PULSED_MODE
The component operates in a pulsed mode.
@ CW_MODE
The component operates in a continuous-wave mode.
@ FMCW_MODE
The component operates in an FMCW streaming mode.
void validateWaveform(const fers_signal::RadarSignal &wave, const std::string &owner, const Thrower &throw_error)
Validates that a waveform is compatible with FMCW streaming constraints.
std::function< void(const std::string &)> Thrower
Callback used by validators to report an error through the caller's mechanism.
void validateSchedule(const std::vector< radar::SchedulePeriod > &schedule, const fers_signal::FmcwChirpSignal &fmcw, const std::string &owner, const Thrower &throw_error)
Validates that an FMCW waveform schedule can emit complete chirps.
void validateWaveformModeMatch(const fers_signal::RadarSignal &wave, const radar::OperationMode mode, const std::string &owner, const Thrower &throw_error)
Validates that a waveform and radar operation mode are compatible.
Defines the Parameters struct and provides methods for managing simulation parameters.
Classes for handling radar waveforms and signals.
math::Vec3 max
Represents a time period during which the transmitter is active.
RealType start
Period start time in seconds.