FERS 1.0.0
The Flexible Extensible Radar Simulator
Loading...
Searching...
No Matches
core Namespace Reference

Classes

class  Config
 Configuration structure for the application. More...
 
struct  Event
 Represents a single event in the simulation's time-ordered queue. More...
 
struct  EventComparator
 A custom comparator for the event priority queue. More...
 
class  ProgressReporter
 A thread-safe wrapper for the simulation progress callback. More...
 
struct  RenderingJob
 A data packet containing all information needed to process one receive window. More...
 
struct  SimulationState
 Holds the dynamic global state of the simulation. More...
 
class  World
 The World class manages the simulator environment. More...
 

Enumerations

enum class  EventType {
  TX_PULSED_START , RX_PULSED_WINDOW_START , RX_PULSED_WINDOW_END , TX_CW_START ,
  TX_CW_END , RX_CW_START , RX_CW_END
}
 Enumerates the types of events that can occur in the simulation. More...
 

Functions

RealType besselJ1 (const RealType x) noexcept
 Computes the Bessel function of the first kind (order 1) for a given value.
 
unsigned countProcessors () noexcept
 Detects the number of CPUs on the machine.
 
std::string toString (const EventType type)
 Converts an EventType enum to its string representation.
 
void runEventDrivenSim (World *world, pool::ThreadPool &pool, const std::function< void(const std::string &, int, int)> &progress_callback)
 Runs the unified, event-driven radar simulation.
 
void showHelp (const char *programName) noexcept
 Displays the help message.
 
void showVersion () noexcept
 Displays the version information.
 
std::expected< Config, std::string > parseArguments (int argc, char *argv[]) noexcept
 Parses command-line arguments.
 

Enumeration Type Documentation

◆ EventType

enum class core::EventType
strong

Enumerates the types of events that can occur in the simulation.

Enumerator
TX_PULSED_START 

A pulsed transmitter begins emitting a pulse.

RX_PULSED_WINDOW_START 

A pulsed receiver opens its listening window.

RX_PULSED_WINDOW_END 

A pulsed receiver closes its listening window.

TX_CW_START 

A continuous-wave transmitter starts transmitting.

TX_CW_END 

A continuous-wave transmitter stops transmitting.

RX_CW_START 

A continuous-wave receiver starts listening.

RX_CW_END 

A continuous-wave receiver stops listening.

Definition at line 27 of file sim_events.h.

28 {
29 TX_PULSED_START, ///< A pulsed transmitter begins emitting a pulse.
30 RX_PULSED_WINDOW_START, ///< A pulsed receiver opens its listening window.
31 RX_PULSED_WINDOW_END, ///< A pulsed receiver closes its listening window.
32 TX_CW_START, ///< A continuous-wave transmitter starts transmitting.
33 TX_CW_END, ///< A continuous-wave transmitter stops transmitting.
34 RX_CW_START, ///< A continuous-wave receiver starts listening.
35 RX_CW_END, ///< A continuous-wave receiver stops listening.
36 };
@ RX_PULSED_WINDOW_START
A pulsed receiver opens its listening window.
@ RX_PULSED_WINDOW_END
A pulsed receiver closes its listening window.
@ TX_CW_START
A continuous-wave transmitter starts transmitting.
@ TX_CW_END
A continuous-wave transmitter stops transmitting.
@ TX_PULSED_START
A pulsed transmitter begins emitting a pulse.
@ RX_CW_END
A continuous-wave receiver stops listening.
@ RX_CW_START
A continuous-wave receiver starts listening.

Function Documentation

◆ besselJ1()

RealType core::besselJ1 ( const RealType  x)
noexcept

Computes the Bessel function of the first kind (order 1) for a given value.

Parameters
xThe value for which the Bessel function is to be computed.
Returns
The computed value of the Bessel function of the first kind (order 1).

Definition at line 28 of file portable_utils.h.

28{ return j1(x); }

◆ countProcessors()

unsigned core::countProcessors ( )
noexcept

Detects the number of CPUs on the machine.

Returns
The number of CPUs detected, or 1 if detection fails.

Definition at line 35 of file portable_utils.h.

36 {
37 if (const unsigned hardware_threads = std::thread::hardware_concurrency(); hardware_threads > 0)
38 {
39 return hardware_threads;
40 }
41 LOG(logging::Level::ERROR, "Unable to get CPU count, assuming 1.");
42 return 1;
43 }
#define LOG(level,...)
Definition logging.h:19
@ ERROR
Error level for error events.

References logging::ERROR, and LOG.

◆ parseArguments()

std::expected< Config, std::string > core::parseArguments ( int  argc,
char *  argv[] 
)
noexcept

Parses command-line arguments.

Processes the command-line arguments, validating them and extracting configurations like script file, logging level, and thread count.

Parameters
argcThe argument count.
argvThe argument vector.
Returns
std::expected<Config, std::string> Parsed configuration or an error message.

Definition at line 201 of file arg_parser.cpp.

208 :
209 <scriptfile> Path to the simulation script file (XML)
210
211Example:
212 )" << programName
213 << R"( simulation.fersxml --log-level=DEBUG --log-file=output.log -n=4
214
215This program runs radar simulations based on an XML script file.
216Make sure the script file follows the correct format to avoid errors.
217)";
218 }
219
220 void showVersion() noexcept
221 {
222 std::cout << R"(
223/------------------------------------------------\
224| FERS - The Flexible Extensible Radar Simulator |
225| Version 1.0.0 |

Referenced by main().

+ Here is the caller graph for this function:

◆ runEventDrivenSim()

void core::runEventDrivenSim ( World world,
pool::ThreadPool pool,
const std::function< void(const std::string &, int, int)> &  progress_callback 
)

Runs the unified, event-driven radar simulation.

This function is the core engine of the simulator. It advances time by processing events from a global priority queue. It handles both pulsed and continuous-wave (CW) physics, dispatching finalization tasks to worker threads for asynchronous processing.

Parameters
worldA pointer to the simulation world containing all entities and state.
poolA reference to the thread pool for executing tasks.
progress_callbackAn optional callback function for reporting progress.

Definition at line 57 of file sim_threading.cpp.

59 {
60 auto& event_queue = world->getEventQueue();
61 auto& [t_current, active_cw_transmitters] = world->getSimulationState();
62 const RealType end_time = params::endTime();
63 const RealType dt_sim = 1.0 / (params::rate() * params::oversampleRatio());
64
65 // Create shared thread-safe reporter
66 auto reporter = std::make_shared<ProgressReporter>(progress_callback);
67
68 if (progress_callback)
69 {
70 reporter->report("Initializing event-driven simulation...", 0, 100);
71 }
72
73 // Start dedicated finalizer threads for each pulsed receiver. This creates a
74 // one-thread-per-receiver pipeline for asynchronous data processing.
75 std::vector<std::jthread> finalizer_threads;
76 for (const auto& receiver_ptr : world->getReceivers())
77 {
78 if (receiver_ptr->getMode() == OperationMode::PULSED_MODE)
79 {
80 finalizer_threads.emplace_back(processing::runPulsedFinalizer, receiver_ptr.get(), &world->getTargets(),
81 reporter);
82 }
83 }
84
85 LOG(Level::INFO, "Starting unified event-driven simulation loop.");
86
87 // Throttling state for the main loop
88 auto last_report_time = std::chrono::steady_clock::now();
89 int last_reported_percent = -1;
90 const auto report_interval = std::chrono::milliseconds(100); // Max 10 updates/sec
91
92 // Main Simulation Loop
93 while (!event_queue.empty() && t_current <= end_time)
94 {
95 // Advance Clock to the next scheduled event
96 const auto [timestamp, event_type, source_object] = event_queue.top();
97 event_queue.pop();
98
99 const RealType t_event = timestamp;
100
101 // Before processing the event, run a time-stepped inner loop to calculate
102 // physics for any active continuous-wave systems. This handles the "continuous"
103 // part of the simulation between discrete events.
104 if (t_event > t_current)
105 {
106 const RealType t_next_event = t_event;
107
108 const auto start_index = static_cast<size_t>(std::ceil((t_current - params::startTime()) / dt_sim));
109 const auto end_index = static_cast<size_t>(std::ceil((t_next_event - params::startTime()) / dt_sim));
110
111 for (size_t sample_index = start_index; sample_index < end_index; ++sample_index)
112 {
113 const RealType t_step = params::startTime() + static_cast<RealType>(sample_index) * dt_sim;
114
115 for (const auto& receiver_ptr : world->getReceivers())
116 {
117 if (receiver_ptr->getMode() == OperationMode::CW_MODE && receiver_ptr->isActive())
118 {
119 ComplexType total_sample{0.0, 0.0};
120 // Query active CW sources
121 for (const auto& cw_source : active_cw_transmitters)
122 {
123 // Calculate direct and reflected paths
124 if (!receiver_ptr->checkFlag(Receiver::RecvFlag::FLAG_NODIRECT))
125 {
127 cw_source, receiver_ptr.get(), t_step);
128 }
129 for (const auto& target_ptr : world->getTargets())
130 {
132 cw_source, receiver_ptr.get(), target_ptr.get(), t_step);
133 }
134 }
135 // Store sample in the receiver's main buffer using the correct index
136 receiver_ptr->setCwSample(sample_index, total_sample);
137 }
138 }
139 }
140 }
141
142 t_current = t_event;
143
144 // Process the discrete event
145 switch (event_type)
146 {
147 case EventType::TX_PULSED_START:
148 {
149 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-static-cast-downcast): Type is guaranteed by event_type
150 auto* tx = static_cast<Transmitter*>(source_object);
151 // For each pulse, calculate its interaction with every receiver and target.
152 for (const auto& rx_ptr : world->getReceivers())
153 {
154 // Calculate unique Response objects for direct and reflected paths.
155 if (!rx_ptr->checkFlag(Receiver::RecvFlag::FLAG_NODIRECT))
156 {
157 if (auto response =
158 simulation::calculateResponse(tx, rx_ptr.get(), tx->getSignal(), t_event))
159 {
160 if (rx_ptr->getMode() == OperationMode::PULSED_MODE)
161 {
162 rx_ptr->addResponseToInbox(std::move(response));
163 }
164 else
165 {
166 rx_ptr->addInterferenceToLog(std::move(response));
167 }
168 }
169 }
170 for (const auto& target_ptr : world->getTargets())
171 {
172 if (auto response = simulation::calculateResponse(tx, rx_ptr.get(), tx->getSignal(),
173 t_event, target_ptr.get()))
174 {
175 if (rx_ptr->getMode() == OperationMode::PULSED_MODE)
176 {
177 rx_ptr->addResponseToInbox(std::move(response));
178 }
179 else
180 {
181 rx_ptr->addInterferenceToLog(std::move(response));
182 }
183 }
184 }
185 }
186 // Schedule next pulse
187 const RealType next_theoretical_time = t_event + 1.0 / tx->getPrf();
188
189 // Use schedule to determine actual next time (handles gaps in schedule)
190 if (const auto next_pulse_opt = tx->getNextPulseTime(next_theoretical_time);
191 next_pulse_opt && *next_pulse_opt <= end_time)
192 {
193 event_queue.push({*next_pulse_opt, EventType::TX_PULSED_START, tx});
194 }
195 break;
196 }
197 case EventType::RX_PULSED_WINDOW_START:
198 {
199 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-static-cast-downcast): Type is guaranteed by event_type
200 auto* rx = static_cast<Receiver*>(source_object);
201 rx->setActive(true);
202 event_queue.push({t_event + rx->getWindowLength(), EventType::RX_PULSED_WINDOW_END, rx});
203 break;
204 }
205 case EventType::RX_PULSED_WINDOW_END:
206 {
207 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-static-cast-downcast): Type is guaranteed by event_type
208 auto* rx = static_cast<Receiver*>(source_object);
209 rx->setActive(false);
210 // The receive window is over. Package all received data into a RenderingJob.
211 RenderingJob job{.ideal_start_time = t_event - rx->getWindowLength(),
212 .duration = rx->getWindowLength(),
213 .responses = rx->drainInbox(),
214 .active_cw_sources = active_cw_transmitters};
215 // Offload the job to the dedicated finalizer thread for this receiver.
216 rx->enqueueFinalizerJob(std::move(job));
217
218 // Schedule the start of the next receive window based on schedule
219 const RealType next_theoretical = t_event - rx->getWindowLength() + 1.0 / rx->getWindowPrf();
220
221 if (const auto next_start = rx->getNextWindowTime(next_theoretical);
222 next_start && *next_start <= end_time)
223 {
224 event_queue.push({*next_start, EventType::RX_PULSED_WINDOW_START, rx});
225 }
226 break;
227 }
228 case EventType::TX_CW_START:
229 {
230 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-static-cast-downcast): Type is guaranteed by event_type
231 auto* tx = static_cast<Transmitter*>(source_object);
232 active_cw_transmitters.push_back(tx);
233 break;
234 }
235 case EventType::TX_CW_END:
236 {
237 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-static-cast-downcast): Type is guaranteed by event_type
238 auto* tx = static_cast<Transmitter*>(source_object);
239 std::erase(active_cw_transmitters, tx);
240 break;
241 }
242 case EventType::RX_CW_START:
243 {
244 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-static-cast-downcast): Type is guaranteed by event_type
245 auto* rx = static_cast<Receiver*>(source_object);
246 rx->setActive(true);
247 break;
248 }
249 case EventType::RX_CW_END:
250 {
251 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-static-cast-downcast): Type is guaranteed by event_type
252 auto* rx = static_cast<Receiver*>(source_object);
253 rx->setActive(false);
254 // Do NOT finalize here. CW data is a continuous buffer; 'Ending' a schedule period
255 // just means we stop recording active signal (leaving zeros/silence in the buffer).
256 break;
257 }
258 }
259
260 // Throttled Progress Reporting
261 if (progress_callback)
262 {
263 const int progress = static_cast<int>(t_current / end_time * 100.0);
264
265 // Only report if percentage changed significantly OR enough time elapsed
266 if (const auto now = std::chrono::steady_clock::now();
267 progress != last_reported_percent || now - last_report_time >= report_interval)
268 {
269 reporter->report(std::format("Simulating... {:.2f}s / {:.2f}s", t_current, end_time), progress,
270 100);
271 last_reported_percent = progress;
272 last_report_time = now;
273 }
274 }
275 }
276
277 // Shutdown Phase
278 LOG(Level::INFO, "Main simulation loop finished. Waiting for finalization tasks...");
279 reporter->report("Main simulation finished. Waiting for data export...", 100, 100);
280
281 // 1. Queue CW Finalization Tasks
282 // We finalize CW receivers here to ensure the full timeline (including all schedule periods) is exported once.
283 for (const auto& receiver_ptr : world->getReceivers())
284 {
285 if (receiver_ptr->getMode() == OperationMode::CW_MODE)
286 {
287 pool.enqueue(processing::finalizeCwReceiver, receiver_ptr.get(), &pool, reporter);
288 }
289 }
290
291 // 2. Signal pulsed finalizer threads to shut down
292 for (const auto& receiver_ptr : world->getReceivers())
293 {
294 if (receiver_ptr->getMode() == OperationMode::PULSED_MODE)
295 {
296 RenderingJob shutdown_job{.duration = -1.0};
297 receiver_ptr->enqueueFinalizerJob(std::move(shutdown_job));
298 }
299 }
300
301 // Wait for any remaining CW finalization tasks in the main pool to complete.
302 pool.wait();
303
304 // The std::jthread finalizer threads are automatically joined here at the end of scope.
305 LOG(Level::INFO, "All finalization tasks complete.");
306
307 if (progress_callback)
308 {
309 reporter->report("Simulation complete", 100, 100);
310 }
311 LOG(Level::INFO, "Event-driven simulation loop finished.");
312 }
const std::vector< std::unique_ptr< radar::Target > > & getTargets() const noexcept
Retrieves the list of radar targets.
Definition world.h:143
SimulationState & getSimulationState() noexcept
Gets a mutable reference to the global simulation state.
Definition world.h:228
std::priority_queue< Event, std::vector< Event >, EventComparator > & getEventQueue() noexcept
Gets a mutable reference to the global event queue.
Definition world.h:219
Manages radar signal reception and response processing.
Definition receiver.h:36
void setActive(const bool active) noexcept
Sets the active state of the receiver.
Definition receiver.h:163
Represents a radar transmitter system.
Definition transmitter.h:32
RealType getPrf() const noexcept
Retrieves the pulse repetition frequency (PRF).
Definition transmitter.h:61
double RealType
Type for real numbers.
Definition config.h:27
std::complex< RealType > ComplexType
Type for complex numbers.
Definition config.h:35
RealType endTime() noexcept
Get the end time for the simulation.
Definition parameters.h:97
RealType rate() noexcept
Get the rendering sample rate.
Definition parameters.h:109
RealType startTime() noexcept
Get the start time for the simulation.
Definition parameters.h:91
unsigned oversampleRatio() noexcept
Get the oversampling ratio.
Definition parameters.h:139
void runPulsedFinalizer(radar::Receiver *receiver, const std::vector< std::unique_ptr< radar::Target > > *targets, std::shared_ptr< core::ProgressReporter > reporter)
The main function for a dedicated pulsed-mode receiver finalizer thread.
Definition finalizer.cpp:70
void finalizeCwReceiver(radar::Receiver *receiver, pool::ThreadPool *pool, std::shared_ptr< core::ProgressReporter > reporter)
The finalization task for a continuous-wave (CW) mode receiver.
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...
std::unique_ptr< serial::Response > calculateResponse(const Transmitter *trans, const Receiver *recv, const RadarSignal *signal, const RealType startTime, const Target *targ)
Creates a Response object by simulating a signal's interaction over its duration.
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...

References simulation::calculateDirectPathContribution(), simulation::calculateReflectedPathContribution(), simulation::calculateResponse(), core::RenderingJob::duration, params::endTime(), processing::finalizeCwReceiver(), core::World::getEventQueue(), radar::Transmitter::getPrf(), core::World::getReceivers(), core::World::getSimulationState(), core::World::getTargets(), core::RenderingJob::ideal_start_time, LOG, params::oversampleRatio(), params::rate(), processing::runPulsedFinalizer(), RX_CW_END, RX_CW_START, RX_PULSED_WINDOW_END, RX_PULSED_WINDOW_START, radar::Receiver::setActive(), params::startTime(), TX_CW_END, TX_CW_START, and TX_PULSED_START.

Referenced by fers_run_simulation().

+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ showHelp()

void core::showHelp ( const char *  programName)
noexcept

Displays the help message.

Parameters
programNameThe name of the program.

Definition at line 189 of file arg_parser.cpp.

190 {
191 std::cout << R"(/------------------------------------------------\
192| FERS - The Flexible Extensible Radar Simulator |
193| Version 1.0.0 |
194\------------------------------------------------/

◆ showVersion()

void core::showVersion ( )
noexcept

Displays the version information.

Definition at line 196 of file arg_parser.cpp.

198 :
199 --help, -h Show this help message and exit

◆ toString()

std::string core::toString ( const EventType  type)

Converts an EventType enum to its string representation.

Parameters
typeThe event type.
Returns
A string representing the event type.

Definition at line 72 of file sim_events.h.

73 {
74 switch (type)
75 {
76 case EventType::TX_PULSED_START:
77 return "TxPulsedStart";
78 case EventType::RX_PULSED_WINDOW_START:
79 return "RxPulsedWindowStart";
80 case EventType::RX_PULSED_WINDOW_END:
81 return "RxPulsedWindowEnd";
82 case EventType::TX_CW_START:
83 return "TxCwStart";
84 case EventType::TX_CW_END:
85 return "TxCwEnd";
86 case EventType::RX_CW_START:
87 return "RxCwStart";
88 case EventType::RX_CW_END:
89 return "RxCwEnd";
90 default:
91 return "UnknownEvent";
92 }
93 }

References RX_CW_END, RX_CW_START, RX_PULSED_WINDOW_END, RX_PULSED_WINDOW_START, TX_CW_END, TX_CW_START, and TX_PULSED_START.

Referenced by core::World::dumpEventQueue().

+ Here is the caller graph for this function: