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 }
248
250 const RealType segment_end)
251 {
252 ActiveStreamingSource source{};
253 source.transmitter = tx;
254 source.segment_start = segment_start;
255 source.segment_end = segment_end;
256 if (tx == nullptr)
257 {
258 return source;
259 }
260
261 const auto* const signal = tx->getSignal();
262 if (signal == nullptr)
263 {
264 return source;
265 }
266
267 const RealType raw_segment_end = segment_end;
268 populateWaveformCache(source, signal, segment_start);
269 if (source.kind == StreamingWaveformKind::FmcwTriangle &&
271 {
272 const auto emitted_triangles = static_cast<std::uint64_t>(
273 std::max<RealType>(0.0, (source.segment_end - segment_start) / source.triangle_period));
275 "FMCW triangle transmitter '{}' segment [{}, {}] emits {} complete triangles and drops {} s of "
276 "leftover active time.",
277 tx->getName(), segment_start, raw_segment_end, emitted_triangles, raw_segment_end - source.segment_end);
278 }
279 return source;
280 }
281
283 const RealType segment_start, const RealType segment_end)
284 {
285 ActiveStreamingSource source{};
286 source.segment_start = segment_start;
287 source.segment_end = segment_end;
288 populateWaveformCache(source, signal, segment_start);
289 return source;
290 }
291
292 std::optional<RealType> firstFmcwChirpStart(const ActiveStreamingSource& source, const RealType active_start,
293 const RealType active_end)
294 {
296 if (!first_index.has_value())
297 {
298 return std::nullopt;
299 }
300 return source.segment_start + static_cast<RealType>(*first_index) * source.chirp_period;
301 }
302
304 const RealType active_end)
305 {
307 if (!first_index.has_value())
308 {
309 return 0;
310 }
311
312 const RealType clipped_end = std::min(active_end, source.segment_end);
313 const RealType first_start = source.segment_start + static_cast<RealType>(*first_index) * source.chirp_period;
315 if (!source.chirp_count.has_value())
316 {
317 return starts_in_interval;
318 }
319
320 const auto configured = static_cast<std::uint64_t>(*source.chirp_count);
321 return std::min(starts_in_interval, configured - *first_index);
322 }
323
324 std::optional<RealType> firstFmcwTriangleStart(const ActiveStreamingSource& source, const RealType active_start,
325 const RealType active_end)
326 {
328 if (!first_index.has_value())
329 {
330 return std::nullopt;
331 }
332 return source.segment_start + static_cast<RealType>(*first_index) * source.triangle_period;
333 }
334
336 const RealType active_end)
337 {
339 if (!first_index.has_value())
340 {
341 return 0;
342 }
343
344 const RealType clipped_end = std::min(active_end, source.segment_end);
345 const RealType first_start =
346 source.segment_start + static_cast<RealType>(*first_index) * source.triangle_period;
348 if (!source.triangle_count.has_value())
349 {
350 return starts_in_interval;
351 }
352
353 const auto configured = static_cast<std::uint64_t>(*source.triangle_count);
354 return std::min(starts_in_interval, configured - *first_index);
355 }
356
357 std::optional<RealType> firstSfcwStepStart(const ActiveStreamingSource& source, const RealType active_start,
358 const RealType active_end)
359 {
361 if (!first_index.has_value())
362 {
363 return std::nullopt;
364 }
365 return source.segment_start + static_cast<RealType>(*first_index) * source.sfcw_step_period;
366 }
367
369 const RealType active_end)
370 {
372 if (!first_index.has_value())
373 {
374 return 0;
375 }
376
377 const RealType clipped_end = std::min(active_end, source.segment_end);
378 const RealType first_start =
379 source.segment_start + static_cast<RealType>(*first_index) * source.sfcw_step_period;
381 if (!source.sfcw_sweep_count.has_value())
382 {
383 return starts_in_interval;
384 }
385
386 const auto configured = static_cast<std::uint64_t>(*source.sfcw_sweep_count) * source.sfcw_step_count;
387 return std::min(starts_in_interval, configured - *first_index);
388 }
389}
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.