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 /// Reduces a phase to [0, 2*pi).
110 {
111 RealType reduced = std::fmod(phase, 2.0 * PI);
112 if (reduced < 0.0)
113 {
114 reduced += 2.0 * PI;
115 }
116 return reduced;
117 }
118
119 /// Populates waveform-dependent fields in an active streaming source.
120 void populateWaveformCache(ActiveStreamingSource& source, const fers_signal::RadarSignal* const signal,
121 const RealType segment_start)
122 {
123 if (signal == nullptr)
124 {
125 return;
126 }
127
128 source.carrier_freq = signal->getCarrier();
129 source.amplitude = std::sqrt(signal->getPower());
130 if (const auto* const fmcw = signal->getFmcwChirpSignal(); fmcw != nullptr)
131 {
133 source.is_fmcw = true;
134 source.fmcw = fmcw;
135 source.chirp_duration = source.fmcw->getChirpDuration();
136 source.chirp_period = source.fmcw->getChirpPeriod();
137 source.chirp_rate = source.fmcw->getChirpRate();
138 source.signed_chirp_rate = source.fmcw->getSignedChirpRate();
139 source.start_freq_off = source.fmcw->getStartFrequencyOffset();
140 source.two_pi_f0 = 2.0 * PI * source.start_freq_off;
141 source.s_pi_alpha = PI * source.signed_chirp_rate;
142 source.chirp_count = source.fmcw->getChirpCount();
143 if (source.chirp_count.has_value())
144 {
145 source.segment_end =
146 std::min(source.segment_end,
147 segment_start + static_cast<RealType>(*source.chirp_count) * source.chirp_period);
148 }
149 return;
150 }
151
152 if (const auto* const triangle = signal->getFmcwTriangleSignal(); triangle != nullptr)
153 {
155 source.is_fmcw = true;
156 source.triangle = triangle;
157 source.chirp_duration = triangle->getChirpDuration();
158 source.chirp_rate = triangle->getChirpRate();
159 source.start_freq_off = triangle->getStartFrequencyOffset();
160 source.triangle_period = triangle->getTrianglePeriod();
161 source.chirp_period = source.triangle_period;
162 source.two_pi_f0 = 2.0 * PI * source.start_freq_off;
163 source.two_pi_f0_plus_B = 2.0 * PI * (source.start_freq_off + triangle->getChirpBandwidth());
164 source.pi_alpha = PI * source.chirp_rate;
165 source.neg_pi_alpha = -source.pi_alpha;
166 source.mod_phi_up = positiveModuloTwoPi(triangle->getDeltaPhiUp());
167 source.mod_phi_tri = positiveModuloTwoPi(2.0 * source.mod_phi_up);
168 source.triangle_count = triangle->getTriangleCount();
169
170 const RealType raw_end = source.segment_end;
171 const RealType raw_duration = std::max<RealType>(0.0, raw_end - segment_start);
172 if (!std::isfinite(raw_duration))
173 {
174 if (source.triangle_count.has_value())
175 {
176 source.segment_end =
177 segment_start + static_cast<RealType>(*source.triangle_count) * source.triangle_period;
178 }
179 return;
180 }
181
182 const auto full_by_duration = static_cast<std::size_t>(
183 std::floor(raw_duration / source.triangle_period + kWaveformBoundaryIndexTolerance));
185 if (source.triangle_count.has_value())
186 {
187 emitted_triangles = std::min(emitted_triangles, *source.triangle_count);
188 }
189 source.segment_end = segment_start + static_cast<RealType>(emitted_triangles) * source.triangle_period;
190 return;
191 }
192 }
193 }
194
196 const RealType segment_end)
197 {
198 ActiveStreamingSource source{};
199 source.transmitter = tx;
200 source.segment_start = segment_start;
201 source.segment_end = segment_end;
202 if (tx == nullptr)
203 {
204 return source;
205 }
206
207 const auto* const signal = tx->getSignal();
208 if (signal == nullptr)
209 {
210 return source;
211 }
212
213 const RealType raw_segment_end = segment_end;
214 populateWaveformCache(source, signal, segment_start);
215 if (source.kind == StreamingWaveformKind::FmcwTriangle &&
217 {
218 const auto emitted_triangles = static_cast<std::uint64_t>(
219 std::max<RealType>(0.0, (source.segment_end - segment_start) / source.triangle_period));
221 "FMCW triangle transmitter '{}' segment [{}, {}] emits {} complete triangles and drops {} s of "
222 "leftover active time.",
223 tx->getName(), segment_start, raw_segment_end, emitted_triangles, raw_segment_end - source.segment_end);
224 }
225 return source;
226 }
227
229 const RealType segment_start, const RealType segment_end)
230 {
231 ActiveStreamingSource source{};
232 source.segment_start = segment_start;
233 source.segment_end = segment_end;
234 populateWaveformCache(source, signal, segment_start);
235 return source;
236 }
237
238 std::optional<RealType> firstFmcwChirpStart(const ActiveStreamingSource& source, const RealType active_start,
239 const RealType active_end)
240 {
242 if (!first_index.has_value())
243 {
244 return std::nullopt;
245 }
246 return source.segment_start + static_cast<RealType>(*first_index) * source.chirp_period;
247 }
248
250 const RealType active_end)
251 {
253 if (!first_index.has_value())
254 {
255 return 0;
256 }
257
258 const RealType clipped_end = std::min(active_end, source.segment_end);
259 const RealType first_start = source.segment_start + static_cast<RealType>(*first_index) * source.chirp_period;
261 if (!source.chirp_count.has_value())
262 {
263 return starts_in_interval;
264 }
265
266 const auto configured = static_cast<std::uint64_t>(*source.chirp_count);
267 return std::min(starts_in_interval, configured - *first_index);
268 }
269
270 std::optional<RealType> firstFmcwTriangleStart(const ActiveStreamingSource& source, const RealType active_start,
271 const RealType active_end)
272 {
274 if (!first_index.has_value())
275 {
276 return std::nullopt;
277 }
278 return source.segment_start + static_cast<RealType>(*first_index) * source.triangle_period;
279 }
280
282 const RealType active_end)
283 {
285 if (!first_index.has_value())
286 {
287 return 0;
288 }
289
290 const RealType clipped_end = std::min(active_end, source.segment_end);
291 const RealType first_start =
292 source.segment_start + static_cast<RealType>(*first_index) * source.triangle_period;
294 if (!source.triangle_count.has_value())
295 {
296 return starts_in_interval;
297 }
298
299 const auto configured = static_cast<std::uint64_t>(*source.triangle_count);
300 return std::min(starts_in_interval, configured - *first_index);
301 }
302}
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.
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.
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::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.