FERS 0.1.0
The Flexible Extensible Radar Simulator
Loading...
Searching...
No Matches
simulation_state.cpp
Go to the documentation of this file.
1// SPDX-License-Identifier: GPL-2.0-only
2//
3// Copyright (c) 2025-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 "simulation_state.h"
8
9#include <algorithm>
10#include <cmath>
11#include <cstdint>
12
13#include "core/logging.h"
14#include "signal/radar_signal.h"
15
16namespace core
17{
18 namespace
19 {
20 /// Absorbs division roundoff when an expected chirp/triangle boundary lands just below an integer index.
21 constexpr RealType kWaveformBoundaryIndexTolerance = 1.0e-12;
22 /// Ignores sub-picosecond schedule leftovers caused by arithmetic at exact triangle boundaries.
24
25 /// Rounds a non-negative floating-point value up to an unsigned integer.
26 std::uint64_t ceilToUint(const RealType value)
27 {
28 if (value <= 0.0)
29 {
30 return 0;
31 }
32
33 const RealType nearest = std::round(value);
34 const RealType tolerance = kWaveformBoundaryIndexTolerance * std::max<RealType>(1.0, std::abs(nearest));
35 if (std::abs(value - nearest) <= tolerance)
36 {
37 return static_cast<std::uint64_t>(nearest);
38 }
39 return static_cast<std::uint64_t>(std::ceil(value));
40 }
41
42 /// Returns the first FMCW chirp index that can contribute inside an interval.
43 std::optional<std::uint64_t> firstFmcwChirpIndex(const ActiveStreamingSource& source,
45 {
46 if (source.kind != StreamingWaveformKind::FmcwLinear || source.chirp_period <= 0.0)
47 {
48 return std::nullopt;
49 }
50
51 const RealType clipped_start = std::max(active_start, source.segment_start);
52 const RealType clipped_end = std::min(active_end, source.segment_end);
54 {
55 return std::nullopt;
56 }
57
58 const auto first_index = clipped_start <= source.segment_start
59 ? std::uint64_t{0}
60 : ceilToUint((clipped_start - source.segment_start) / source.chirp_period);
61 if (source.chirp_count.has_value() && first_index >= *source.chirp_count)
62 {
63 return std::nullopt;
64 }
65
66 const RealType first_start =
67 source.segment_start + static_cast<RealType>(first_index) * source.chirp_period;
69 {
70 return std::nullopt;
71 }
72 return first_index;
73 }
74
75 /// Returns the first FMCW triangle index that can contribute inside an interval.
76 std::optional<std::uint64_t> firstFmcwTriangleIndex(const ActiveStreamingSource& source,
78 {
79 if (source.kind != StreamingWaveformKind::FmcwTriangle || source.triangle_period <= 0.0)
80 {
81 return std::nullopt;
82 }
83
84 const RealType clipped_start = std::max(active_start, source.segment_start);
85 const RealType clipped_end = std::min(active_end, source.segment_end);
87 {
88 return std::nullopt;
89 }
90
91 const auto first_index = clipped_start <= source.segment_start
92 ? std::uint64_t{0}
93 : ceilToUint((clipped_start - source.segment_start) / source.triangle_period);
94 if (source.triangle_count.has_value() && first_index >= *source.triangle_count)
95 {
96 return std::nullopt;
97 }
98
99 const RealType first_start =
100 source.segment_start + static_cast<RealType>(first_index) * source.triangle_period;
102 {
103 return std::nullopt;
104 }
105 return first_index;
106 }
107
108 /// Returns the first SFCW flat step index that can contribute inside an interval.
109 std::optional<std::uint64_t> firstSfcwStepIndex(const ActiveStreamingSource& source,
111 {
112 if (source.kind != StreamingWaveformKind::Sfcw || source.sfcw_step_period <= 0.0 ||
113 source.sfcw_step_count == 0)
114 {
115 return std::nullopt;
116 }
117
118 const RealType clipped_start = std::max(active_start, source.segment_start);
119 const RealType clipped_end = std::min(active_end, source.segment_end);
121 {
122 return std::nullopt;
123 }
124
125 const auto first_index = clipped_start <= source.segment_start
126 ? std::uint64_t{0}
127 : ceilToUint((clipped_start - source.segment_start) / source.sfcw_step_period);
128 if (source.sfcw_sweep_count.has_value() &&
129 first_index >= static_cast<std::uint64_t>(*source.sfcw_sweep_count) * source.sfcw_step_count)
130 {
131 return std::nullopt;
132 }
133
134 const RealType first_start =
135 source.segment_start + static_cast<RealType>(first_index) * source.sfcw_step_period;
137 {
138 return std::nullopt;
139 }
140 return first_index;
141 }
142
143 /// Reduces a phase to [0, 2*pi).
145 {
146 RealType reduced = std::fmod(phase, 2.0 * PI);
147 if (reduced < 0.0)
148 {
149 reduced += 2.0 * PI;
150 }
151 return reduced;
152 }
153
154 /// Populates waveform-dependent fields in an active streaming source.
155 void populateWaveformCache(ActiveStreamingSource& source, const fers_signal::RadarSignal* const signal,
156 const RealType segment_start)
157 {
158 if (signal == nullptr)
159 {
160 return;
161 }
162
163 source.carrier_freq = signal->getCarrier();
164 source.amplitude = std::sqrt(signal->getPower());
165 if (const auto* const fmcw = signal->getFmcwChirpSignal(); fmcw != nullptr)
166 {
168 source.is_fmcw = true;
169 source.fmcw = fmcw;
170 source.chirp_duration = source.fmcw->getChirpDuration();
171 source.chirp_period = source.fmcw->getChirpPeriod();
172 source.chirp_rate = source.fmcw->getChirpRate();
173 source.signed_chirp_rate = source.fmcw->getSignedChirpRate();
174 source.start_freq_off = source.fmcw->getStartFrequencyOffset();
175 source.two_pi_f0 = 2.0 * PI * source.start_freq_off;
176 source.s_pi_alpha = PI * source.signed_chirp_rate;
177 source.chirp_count = source.fmcw->getChirpCount();
178 if (source.chirp_count.has_value())
179 {
180 source.segment_end =
181 std::min(source.segment_end,
182 segment_start + static_cast<RealType>(*source.chirp_count) * source.chirp_period);
183 }
184 return;
185 }
186
187 if (const auto* const sfcw = signal->getSteppedFrequencySignal(); sfcw != nullptr)
188 {
189 source.kind = StreamingWaveformKind::Sfcw;
190 source.is_sfcw = true;
191 source.sfcw = sfcw;
192 source.start_freq_off = sfcw->getStartFrequencyOffset();
193 source.sfcw_step_size = sfcw->getStepSize();
194 source.sfcw_step_count = sfcw->getStepCount();
195 source.sfcw_dwell_time = sfcw->getDwellTime();
196 source.sfcw_step_period = sfcw->getStepPeriod();
197 source.sfcw_sweep_period = sfcw->getSweepPeriod();
198 source.sfcw_sweep_count = sfcw->getSweepCount();
199 if (const auto duration = sfcw->totalDuration(); duration.has_value())
200 {
201 source.segment_end = std::min(source.segment_end, segment_start + *duration);
202 }
203 return;
204 }
205
206 if (const auto* const triangle = signal->getFmcwTriangleSignal(); triangle != nullptr)
207 {
209 source.is_fmcw = true;
210 source.triangle = triangle;
211 source.chirp_duration = triangle->getChirpDuration();
212 source.chirp_rate = triangle->getChirpRate();
213 source.start_freq_off = triangle->getStartFrequencyOffset();
214 source.triangle_period = triangle->getTrianglePeriod();
215 source.chirp_period = source.triangle_period;
216 source.two_pi_f0 = 2.0 * PI * source.start_freq_off;
217 source.two_pi_f0_plus_B = 2.0 * PI * (source.start_freq_off + triangle->getChirpBandwidth());
218 source.pi_alpha = PI * source.chirp_rate;
219 source.neg_pi_alpha = -source.pi_alpha;
220 source.mod_phi_up = positiveModuloTwoPi(triangle->getDeltaPhiUp());
221 source.mod_phi_tri = positiveModuloTwoPi(2.0 * source.mod_phi_up);
222 source.triangle_count = triangle->getTriangleCount();
223
224 const RealType raw_end = source.segment_end;
225 const RealType raw_duration = std::max<RealType>(0.0, raw_end - segment_start);
226 if (!std::isfinite(raw_duration))
227 {
228 if (source.triangle_count.has_value())
229 {
230 source.segment_end =
231 segment_start + static_cast<RealType>(*source.triangle_count) * source.triangle_period;
232 }
233 return;
234 }
235
236 const auto full_by_duration = static_cast<std::size_t>(
237 std::floor(raw_duration / source.triangle_period + kWaveformBoundaryIndexTolerance));
239 if (source.triangle_count.has_value())
240 {
241 emitted_triangles = std::min(emitted_triangles, *source.triangle_count);
242 }
243 source.segment_end = segment_start + static_cast<RealType>(emitted_triangles) * source.triangle_period;
244 return;
245 }
246
247 if (const auto* const file = signal->getFileSignal();
248 file != nullptr && file->getKind() != fers_signal::FileWaveformKind::Pulsed)
249 {
250 source.file = file;
251 source.file_duration = signal->getLength();
252 source.is_fmcw = file->getKind() == fers_signal::FileWaveformKind::Fmcw;
253 source.kind = source.is_fmcw ? StreamingWaveformKind::FileFmcw : StreamingWaveformKind::FileCw;
254 source.segment_end = std::min(source.segment_end, segment_start + source.file_duration);
255 }
256 }
257 }
258
260 const RealType segment_end)
261 {
262 ActiveStreamingSource source{};
263 source.transmitter = tx;
264 source.segment_start = segment_start;
265 source.segment_end = segment_end;
266 if (tx == nullptr)
267 {
268 return source;
269 }
270
271 const auto* const signal = tx->getSignal();
272 if (signal == nullptr)
273 {
274 return source;
275 }
276
277 const RealType raw_segment_end = segment_end;
278 populateWaveformCache(source, signal, segment_start);
279 if (source.kind == StreamingWaveformKind::FmcwTriangle &&
281 {
282 const auto emitted_triangles = static_cast<std::uint64_t>(
283 std::max<RealType>(0.0, (source.segment_end - segment_start) / source.triangle_period));
285 "FMCW triangle transmitter '{}' segment [{}, {}] emits {} complete triangles and drops {} s of "
286 "leftover active time.",
287 tx->getName(), segment_start, raw_segment_end, emitted_triangles, raw_segment_end - source.segment_end);
288 }
289 return source;
290 }
291
293 const RealType segment_start, const RealType segment_end)
294 {
295 ActiveStreamingSource source{};
296 source.segment_start = segment_start;
297 source.segment_end = segment_end;
298 populateWaveformCache(source, signal, segment_start);
299 return source;
300 }
301
302 std::optional<RealType> firstFmcwChirpStart(const ActiveStreamingSource& source, const RealType active_start,
303 const RealType active_end)
304 {
306 if (!first_index.has_value())
307 {
308 return std::nullopt;
309 }
310 return source.segment_start + static_cast<RealType>(*first_index) * source.chirp_period;
311 }
312
314 const RealType active_end)
315 {
317 if (!first_index.has_value())
318 {
319 return 0;
320 }
321
322 const RealType clipped_end = std::min(active_end, source.segment_end);
323 const RealType first_start = source.segment_start + static_cast<RealType>(*first_index) * source.chirp_period;
325 if (!source.chirp_count.has_value())
326 {
327 return starts_in_interval;
328 }
329
330 const auto configured = static_cast<std::uint64_t>(*source.chirp_count);
331 return std::min(starts_in_interval, configured - *first_index);
332 }
333
334 std::optional<RealType> firstFmcwTriangleStart(const ActiveStreamingSource& source, const RealType active_start,
335 const RealType active_end)
336 {
338 if (!first_index.has_value())
339 {
340 return std::nullopt;
341 }
342 return source.segment_start + static_cast<RealType>(*first_index) * source.triangle_period;
343 }
344
346 const RealType active_end)
347 {
349 if (!first_index.has_value())
350 {
351 return 0;
352 }
353
354 const RealType clipped_end = std::min(active_end, source.segment_end);
355 const RealType first_start =
356 source.segment_start + static_cast<RealType>(*first_index) * source.triangle_period;
358 if (!source.triangle_count.has_value())
359 {
360 return starts_in_interval;
361 }
362
363 const auto configured = static_cast<std::uint64_t>(*source.triangle_count);
364 return std::min(starts_in_interval, configured - *first_index);
365 }
366
367 std::optional<RealType> firstSfcwStepStart(const ActiveStreamingSource& source, const RealType active_start,
368 const RealType active_end)
369 {
371 if (!first_index.has_value())
372 {
373 return std::nullopt;
374 }
375 return source.segment_start + static_cast<RealType>(*first_index) * source.sfcw_step_period;
376 }
377
379 const RealType active_end)
380 {
382 if (!first_index.has_value())
383 {
384 return 0;
385 }
386
387 const RealType clipped_end = std::min(active_end, source.segment_end);
388 const RealType first_start =
389 source.segment_start + static_cast<RealType>(*first_index) * source.sfcw_step_period;
391 if (!source.sfcw_sweep_count.has_value())
392 {
393 return starts_in_interval;
394 }
395
396 const auto configured = static_cast<std::uint64_t>(*source.sfcw_sweep_count) * source.sfcw_step_count;
397 return std::min(starts_in_interval, configured - *first_index);
398 }
399}
Class representing a radar signal with associated properties.
Represents a radar transmitter system.
Definition transmitter.h:34
double RealType
Type for real numbers.
Definition config.h:27
constexpr RealType PI
Mathematical constant π (pi).
Definition config.h:43
Header file for the logging system.
#define LOG(level,...)
Definition logging.h:19
std::uint64_t countFmcwTriangleStarts(const ActiveStreamingSource &source, const RealType active_start, const RealType active_end)
Counts FMCW triangles that start inside the absolute interval.
std::uint64_t countSfcwStepStarts(const ActiveStreamingSource &source, const RealType active_start, const RealType active_end)
Counts SFCW active dwells that start inside the absolute interval.
ActiveStreamingSource makeActiveSource(const radar::Transmitter *const tx, const RealType segment_start, const RealType segment_end)
Builds an active-source cache from a streaming transmitter and segment bounds.
std::uint64_t countFmcwChirpStarts(const ActiveStreamingSource &source, const RealType active_start, const RealType active_end)
Counts FMCW chirps that start inside the absolute interval.
std::optional< RealType > firstFmcwTriangleStart(const ActiveStreamingSource &source, const RealType active_start, const RealType active_end)
Returns the first FMCW triangle start inside the absolute interval, if one exists.
std::optional< RealType > firstSfcwStepStart(const ActiveStreamingSource &source, const RealType active_start, const RealType active_end)
Returns the first SFCW step start inside the absolute interval, if one exists.
ActiveStreamingSource makeActiveSourceFromWaveform(const fers_signal::RadarSignal *const signal, const RealType segment_start, const RealType segment_end)
Builds an active-source cache directly from a waveform for receiver-local LO references.
std::optional< RealType > firstFmcwChirpStart(const ActiveStreamingSource &source, const RealType active_start, const RealType active_end)
Returns the first FMCW chirp start inside the absolute interval, if one exists.
@ WARNING
Warning level for potentially harmful situations.
Classes for handling radar waveforms and signals.
math::Vec3 max
Defines the global state for the event-driven simulation engine.
Cached description of an active streaming transmitter segment.
RealType triangle_period
Cached full triangle period in seconds.
RealType segment_start
Segment start time in seconds.
const radar::Transmitter * transmitter
Transmitter active during this segment.
RealType chirp_period
Cached FMCW chirp period in seconds.
std::size_t sfcw_step_count
Cached SFCW steps per sweep.
std::optional< std::size_t > sfcw_sweep_count
Optional finite SFCW sweep count for the segment.
std::optional< std::size_t > chirp_count
Optional finite chirp count for the segment.
std::optional< std::size_t > triangle_count
Optional finite triangle count for the segment.
RealType segment_end
Segment end time in seconds.
RealType sfcw_step_period
Cached SFCW step period in seconds.