FERS 1.0.0
The Flexible Extensible Radar Simulator
Loading...
Searching...
No Matches
channel_model.cpp
Go to the documentation of this file.
1// SPDX-License-Identifier: GPL-2.0-only
2//
3// Copyright (c) 2006-2008 Marc Brooker and Michael Inggs
4// Copyright (c) 2008-present FERS Contributors (see AUTHORS.md).
5//
6// See the GNU GPLv2 LICENSE file in the FERS project root for more information.
7
8/**
9 * @file channel_model.cpp
10 * @brief Implementation of radar channel propagation and interaction models.
11 *
12 * This file provides the implementations for the functions that model the radar
13 * channel, as declared in channel_model.h. It contains the core physics calculations
14 * that determine signal properties based on geometry, velocity, and object characteristics.
15 */
16
17#include "channel_model.h"
18
19#include <cmath>
20
21#include "core/logging.h"
22#include "core/parameters.h"
23#include "core/sim_id.h"
24#include "core/world.h"
26#include "math/geometry_ops.h"
27#include "radar/radar_obj.h"
28#include "radar/receiver.h"
29#include "radar/target.h"
30#include "radar/transmitter.h"
31#include "serial/response.h"
32#include "signal/radar_signal.h"
33#include "timing/timing.h"
34
36using logging::Level;
37using math::SVec3;
38using math::Vec3;
39using radar::Receiver;
40using radar::Target;
42
43namespace
44{
45 /**
46 * @struct LinkGeometry
47 * @brief Holds geometric properties of a path segment between two points.
48 */
49 struct LinkGeometry
50 {
51 Vec3 u_vec; ///< Unit vector pointing from Source to Destination.
52 RealType dist{}; ///< Distance between Source and Destination.
53 };
54
55 /**
56 * @brief Computes the geometry (distance and direction) between two points.
57 * @param p_from Starting position.
58 * @param p_to Ending position.
59 * @return LinkGeometry containing distance and unit vector.
60 * @throws RangeError if the distance is too small (<= EPSILON).
61 */
62 LinkGeometry computeLink(const Vec3& p_from, const Vec3& p_to)
63 {
64 const Vec3 vec = p_to - p_from;
65 const RealType dist = vec.length();
66
67 if (dist <= EPSILON)
68 {
69 // LOG(Level::FATAL) is handled by the caller or generic exception handler if needed,
70 // but for RangeError strictly we just throw here to keep it pure.
71 // However, existing code logged FATAL before throwing.
72 // We'll throw RangeError, and let callers decide if they want to log or return 0.
74 }
75
76 return {vec / dist, dist};
77 }
78
79 /**
80 * @brief Calculates the antenna gain for a specific direction and time.
81 * @param radar The radar object (Transmitter or Receiver).
82 * @param direction_vec The unit vector pointing AWAY from the antenna towards the target/receiver.
83 * @param time The simulation time for rotation lookup.
84 * @param lambda The signal wavelength.
85 * @return The linear gain value.
86 */
87 RealType computeAntennaGain(const radar::Radar* radar, const Vec3& direction_vec, RealType time, RealType lambda)
88 {
89 return radar->getGain(SVec3(direction_vec), radar->getRotation(time), lambda);
90 }
91
92 /**
93 * @brief Computes the power scaling factor for a direct path (Friis Transmission Equation).
94 * @param tx_gain Transmitter gain (linear).
95 * @param rx_gain Receiver gain (linear).
96 * @param lambda Wavelength (meters).
97 * @param dist Distance (meters).
98 * @param no_prop_loss If true, distance-based attenuation is ignored.
99 * @return The power scaling factor (Pr / Pt).
100 */
101 RealType computeDirectPathPower(RealType tx_gain, RealType rx_gain, RealType lambda, RealType dist,
102 bool no_prop_loss)
103 {
104 const RealType numerator = tx_gain * rx_gain * lambda * lambda;
105 RealType denominator = 16.0 * PI * PI; // (4 * PI)^2
106
107 if (!no_prop_loss)
108 {
109 denominator *= dist * dist;
110 }
111
112 return numerator / denominator;
113 }
114
115 /**
116 * @brief Computes the power scaling factor for a reflected path (Bistatic Radar Range Equation).
117 * @param tx_gain Transmitter gain (linear).
118 * @param rx_gain Receiver gain (linear).
119 * @param rcs Target Radar Cross Section (m^2).
120 * @param lambda Wavelength (meters).
121 * @param r_tx Distance from Transmitter to Target.
122 * @param r_rx Distance from Target to Receiver.
123 * @param no_prop_loss If true, distance-based attenuation is ignored.
124 * @return The power scaling factor (Pr / Pt).
125 */
126 RealType computeReflectedPathPower(RealType tx_gain, RealType rx_gain, RealType rcs, RealType lambda, RealType r_tx,
127 RealType r_rx, bool no_prop_loss)
128 {
129 const RealType numerator = tx_gain * rx_gain * rcs * lambda * lambda;
130 RealType denominator = 64.0 * PI * PI * PI; // (4 * PI)^3
131
132 if (!no_prop_loss)
133 {
134 denominator *= r_tx * r_tx * r_rx * r_rx;
135 }
136
137 return numerator / denominator;
138 }
139
140 /**
141 * @brief Computes the non-coherent phase shift due to timing offsets.
142 *
143 * Used for CW simulation where LO effects are modeled analytically.
144 *
145 * @param tx The transmitter.
146 * @param rx The receiver.
147 * @param time The current simulation time.
148 * @return The phase shift in radians.
149 */
150 RealType computeTimingPhase(const Transmitter* tx, const Receiver* rx, RealType time)
151 {
152 const auto tx_timing = tx->getTiming();
153 const auto rx_timing = rx->getTiming();
154 const RealType delta_f = tx_timing->getFreqOffset() - rx_timing->getFreqOffset();
155 const RealType delta_phi = tx_timing->getPhaseOffset() - rx_timing->getPhaseOffset();
156 return 2 * PI * delta_f * time + delta_phi;
157 }
158
159 // Helper to check noise floor threshold (Signal > kTB)
160 bool isSignalStrong(RealType power_watts, RealType temp_kelvin)
161 {
162 // Use configured rate or default to 1Hz if unconfigured to prevent divide-by-zero or silly values
163 const RealType bw = params::rate() > 0 ? params::rate() : 1.0;
164 const RealType noise_floor = params::boltzmannK() * (temp_kelvin > 0 ? temp_kelvin : 290.0) * bw;
165 return power_watts > noise_floor;
166 }
167
168 /**
169 * @brief Converts power in watts to decibels milliwatts (dBm).
170 *
171 * @param watts Power in watts.
172 * @return Power in dBm. Returns -999.0 dBm for non-positive input.
173 */
174 RealType wattsToDbm(RealType watts)
175 {
176 if (watts <= 0)
177 {
178 return -999.0;
179 }
180 return 10.0 * std::log10(watts * 1000.0);
181 }
182
183 /**
184 * @brief Converts power in watts to decibels (dB).
185 *
186 * @param watts Power in watts.
187 * @return Power in decibels (dB). Returns -999.0 dB for non-positive input.
188 */
189 RealType wattsToDb(RealType watts)
190 {
191 if (watts <= 0)
192 {
193 return -999.0;
194 }
195 return 10.0 * std::log10(watts);
196 }
197
198 /**
199 * @brief Checks if a component is active at the given time based on its schedule.
200 *
201 * @param schedule The component's operating schedule.
202 * @param time The current simulation time.
203 * @return true If the schedule is empty (implied always on) or if time is within a period.
204 * @return false If the schedule is populated but the time is outside all periods.
205 */
206 bool isComponentActive(const std::vector<radar::SchedulePeriod>& schedule, RealType time)
207 {
208 if (schedule.empty())
209 {
210 return true;
211 }
212 for (const auto& period : schedule)
213 {
214 if (time >= period.start && time <= period.end)
215 {
216 return true;
217 }
218 }
219 return false;
220 }
221}
222
223namespace simulation
224{
225 void solveRe(const Transmitter* trans, const Receiver* recv, const Target* targ,
226 const std::chrono::duration<RealType>& time, const RadarSignal* wave, ReResults& results)
227 {
228 // Note: RangeError log messages are handled by the original catch block in calculateResponse
229 // or explicitly here if strict adherence to original logging is required.
230 // Using the helper logic which throws RangeError on epsilon check.
231
232 const RealType t_val = time.count();
233 const auto p_tx = trans->getPosition(t_val);
234 const auto p_rx = recv->getPosition(t_val);
235 const auto p_tgt = targ->getPosition(t_val);
236
237 // Link 1: Tx -> Target
238 LinkGeometry link_tx_tgt;
239 // Link 2: Target -> Rx (Note: Vector for calculation is Tgt->Rx)
240 LinkGeometry link_tgt_rx;
241
242 try
243 {
244 link_tx_tgt = computeLink(p_tx, p_tgt);
245 link_tgt_rx = computeLink(p_tgt, p_rx); // Vector Tgt -> Rx
246 }
247 catch (const RangeError&)
248 {
249 LOG(Level::INFO, "Transmitter or Receiver too close to Target for accurate simulation");
250 throw;
251 }
252
253 results.delay = (link_tx_tgt.dist + link_tgt_rx.dist) / params::c();
254
255 // Calculate RCS
256 // Note: getRcs expects (InAngle, OutAngle).
257 // InAngle: Tx -> Tgt (link_tx_tgt.u_vec)
258 // OutAngle: Rx -> Tgt (Opposite of Tgt->Rx, so -link_tgt_rx.u_vec)
259 // This matches existing logic.
260 SVec3 in_angle(link_tx_tgt.u_vec);
261 SVec3 out_angle(-link_tgt_rx.u_vec);
262 const auto rcs = targ->getRcs(in_angle, out_angle, t_val);
263
264 const auto wavelength = params::c() / wave->getCarrier();
265
266 // Tx Gain: Direction Tx -> Tgt
267 const auto tx_gain = computeAntennaGain(trans, link_tx_tgt.u_vec, t_val, wavelength);
268 // Rx Gain: Direction Rx -> Tgt (Opposite of Tgt->Rx).
269 // Time is time + delay.
270 const auto rx_gain = computeAntennaGain(recv, -link_tgt_rx.u_vec, results.delay + t_val, wavelength);
271
272 const bool no_loss = recv->checkFlag(Receiver::RecvFlag::FLAG_NOPROPLOSS);
273 results.power =
274 computeReflectedPathPower(tx_gain, rx_gain, rcs, wavelength, link_tx_tgt.dist, link_tgt_rx.dist, no_loss);
275
276 results.phase = -results.delay * 2 * PI * wave->getCarrier();
277 }
278
279 void solveReDirect(const Transmitter* trans, const Receiver* recv, const std::chrono::duration<RealType>& time,
280 const RadarSignal* wave, ReResults& results)
281 {
282 const RealType t_val = time.count();
283 const auto p_tx = trans->getPosition(t_val);
284 const auto p_rx = recv->getPosition(t_val);
285
286 LinkGeometry link;
287 try
288 {
289 link = computeLink(p_tx, p_rx); // Vector Tx -> Rx
290 }
291 catch (const RangeError&)
292 {
293 LOG(Level::INFO, "Transmitter or Receiver too close for accurate simulation");
294 throw;
295 }
296
297 results.delay = link.dist / params::c();
298 const RealType wavelength = params::c() / wave->getCarrier();
299
300 // Discrepancy Fix: Original code used (Rx - Tx) for Receiver Gain but (Tx - Rx) logic for Transmitter gain
301 // was ambiguous/incorrect (using `tpos - rpos` which is Rx->Tx).
302 // Per `calculateDirectPathContribution` preference:
303 // Tx Gain uses Vector Tx -> Rx.
304 // Rx Gain uses Vector Rx -> Tx.
305
306 const auto tx_gain = computeAntennaGain(trans, link.u_vec, t_val, wavelength);
307 const auto rx_gain = computeAntennaGain(recv, -link.u_vec, t_val + results.delay, wavelength);
308
309 const bool no_loss = recv->checkFlag(Receiver::RecvFlag::FLAG_NOPROPLOSS);
310 results.power = computeDirectPathPower(tx_gain, rx_gain, wavelength, link.dist, no_loss);
311
312 results.phase = -results.delay * 2 * PI * wave->getCarrier();
313 }
314
316 {
317 // Check for co-location to prevent singularities.
318 // If they share the same platform, we assume they are isolated (no leakage) or explicit
319 // monostatic handling is required (which is not modeled via the far-field path).
320 if (trans->getPlatform() == recv->getPlatform())
321 {
322 return {0.0, 0.0};
323 }
324
325 const auto p_tx = trans->getPlatform()->getPosition(timeK);
326 const auto p_rx = recv->getPlatform()->getPosition(timeK);
327
328 LinkGeometry link;
329 try
330 {
331 link = computeLink(p_tx, p_rx);
332 }
333 catch (const RangeError&)
334 {
335 return {0.0, 0.0};
336 }
337
338 const RealType tau = link.dist / params::c();
339 auto* const signal = trans->getSignal();
340 const RealType carrier_freq = signal->getCarrier();
341 const RealType lambda = params::c() / carrier_freq;
342
343 // Tx Gain: Direction Tx -> Rx
344 const RealType tx_gain = computeAntennaGain(trans, link.u_vec, timeK, lambda);
345 // Rx Gain: Direction Rx -> Tx (-u_vec)
346 const RealType rx_gain = computeAntennaGain(recv, -link.u_vec, timeK + tau, lambda);
347
348 const bool no_loss = recv->checkFlag(Receiver::RecvFlag::FLAG_NOPROPLOSS);
349 const RealType scaling_factor = computeDirectPathPower(tx_gain, rx_gain, lambda, link.dist, no_loss);
350
351 // Include Signal Power
352 const RealType amplitude = std::sqrt(signal->getPower() * scaling_factor);
353
354 // Carrier Phase
355 const RealType phase = -2 * PI * carrier_freq * tau;
356 ComplexType contribution = std::polar(amplitude, phase);
357
358 // Non-coherent Local Oscillator Effects
359 const RealType non_coherent_phase = computeTimingPhase(trans, recv, timeK);
360 contribution *= std::polar(1.0, non_coherent_phase);
361
362 return contribution;
363 }
364
366 const RealType timeK)
367 {
368 // Check for co-location involving the target.
369 // We do not model a platform tracking itself (R=0) or illuminating itself (R=0).
370 if (trans->getPlatform() == targ->getPlatform() || recv->getPlatform() == targ->getPlatform())
371 {
372 return {0.0, 0.0};
373 }
374
375 const auto p_tx = trans->getPlatform()->getPosition(timeK);
376 const auto p_rx = recv->getPlatform()->getPosition(timeK);
377 const auto p_tgt = targ->getPlatform()->getPosition(timeK);
378
379 LinkGeometry link_tx_tgt;
380 LinkGeometry link_tgt_rx;
381
382 try
383 {
384 link_tx_tgt = computeLink(p_tx, p_tgt);
385 link_tgt_rx = computeLink(p_tgt, p_rx);
386 }
387 catch (const RangeError&)
388 {
389 return {0.0, 0.0};
390 }
391
392 const RealType tau = (link_tx_tgt.dist + link_tgt_rx.dist) / params::c();
393 auto* const signal = trans->getSignal();
394 const RealType carrier_freq = signal->getCarrier();
395 const RealType lambda = params::c() / carrier_freq;
396
397 // RCS Lookups: In (Tx->Tgt), Out (Rx->Tgt = - (Tgt->Rx))
398 SVec3 in_angle(link_tx_tgt.u_vec);
399 SVec3 out_angle(-link_tgt_rx.u_vec);
400 const RealType rcs = targ->getRcs(in_angle, out_angle, timeK);
401
402 // Tx Gain: Direction Tx -> Tgt
403 const RealType tx_gain = computeAntennaGain(trans, link_tx_tgt.u_vec, timeK, lambda);
404 // Rx Gain: Direction Rx -> Tgt (- (Tgt->Rx)). Time: timeK + tau.
405 const RealType rx_gain = computeAntennaGain(recv, -link_tgt_rx.u_vec, timeK + tau, lambda);
406
407 const bool no_loss = recv->checkFlag(Receiver::RecvFlag::FLAG_NOPROPLOSS);
408 const RealType scaling_factor =
409 computeReflectedPathPower(tx_gain, rx_gain, rcs, lambda, link_tx_tgt.dist, link_tgt_rx.dist, no_loss);
410
411 // Include Signal Power
412 const RealType amplitude = std::sqrt(signal->getPower() * scaling_factor);
413
414 const RealType phase = -2 * PI * carrier_freq * tau;
415 ComplexType contribution = std::polar(amplitude, phase);
416
417 // Non-coherent Local Oscillator Effects
418 const RealType non_coherent_phase = computeTimingPhase(trans, recv, timeK);
419 contribution *= std::polar(1.0, non_coherent_phase);
420
421 return contribution;
422 }
423
424 std::unique_ptr<serial::Response> calculateResponse(const Transmitter* trans, const Receiver* recv,
425 const RadarSignal* signal, const RealType startTime,
426 const Target* targ)
427 {
428 // If calculating direct path (no target) and components are co-located:
429 // 1. If explicitly attached (monostatic), skip (internal leakage handled elsewhere).
430 // 2. If independent but on the same platform, distance is 0. Far-field logic (1/R^2)
431 // diverges. We skip calculation to avoid RangeError crashes, assuming
432 // no direct coupling/interference for co-located far-field antennas.
433 if (targ == nullptr && (trans->getAttached() == recv || trans->getPlatform() == recv->getPlatform()))
434 {
435 return nullptr;
436 }
437
438 // If calculating reflected path and target is co-located with either Tx or Rx:
439 // Skip to avoid singularity. Simulating a radar tracking its own platform
440 // requires near-field clutter models, not point-target RCS models.
441 if (targ != nullptr &&
442 (targ->getPlatform() == trans->getPlatform() || targ->getPlatform() == recv->getPlatform()))
443 {
444 LOG(Level::TRACE,
445 "Skipping reflected path calculation for Target {} co-located with Transmitter {} or Receiver {}",
446 targ->getName(), trans->getName(), recv->getName());
447 return nullptr;
448 }
449
450 const auto start_time_chrono = std::chrono::duration<RealType>(startTime);
451 const auto end_time_chrono = start_time_chrono + std::chrono::duration<RealType>(signal->getLength());
452 const auto sample_time_chrono = std::chrono::duration<RealType>(1.0 / params::simSamplingRate());
453 const int point_count = static_cast<int>(std::ceil(signal->getLength() / sample_time_chrono.count()));
454
455 if ((targ != nullptr) && point_count == 0)
456 {
457 LOG(Level::FATAL, "No time points are available for execution!");
458 throw std::runtime_error("No time points are available for execution!");
459 }
460
461 auto response = std::make_unique<serial::Response>(signal, trans);
462
463 try
464 {
465 for (int i = 0; i <= point_count; ++i)
466 {
467 const auto current_time =
468 i < point_count ? start_time_chrono + i * sample_time_chrono : end_time_chrono;
469
470 ReResults results{};
471 if (targ != nullptr)
472 {
473 solveRe(trans, recv, targ, current_time, signal, results);
474 }
475 else
476 {
477 solveReDirect(trans, recv, current_time, signal, results);
478 }
479
480 interp::InterpPoint point{.power = results.power,
481 .time = current_time.count() + results.delay,
482 .delay = results.delay,
483 .phase = results.phase};
484 response->addInterpPoint(point);
485 }
486 }
487 catch (const RangeError&)
488 {
489 LOG(Level::INFO, "Receiver or Transmitter too close for accurate simulation");
490 throw; // Re-throw to be caught by the runner
491 }
492
493 return response;
494 }
495
496 std::vector<PreviewLink> calculatePreviewLinks(const core::World& world, const RealType time)
497 {
498 std::vector<PreviewLink> links;
499
500 // Default wavelength (1GHz) if no waveform is attached, to allow geometric visualization
501 const RealType lambda_default = 0.3;
502
503 for (const auto& tx : world.getTransmitters())
504 {
505 // 1. Check Transmitter Schedule
506 if (!isComponentActive(tx->getSchedule(), time))
507 {
508 continue;
509 }
510
511 const auto p_tx = tx->getPosition(time);
512 const auto* waveform = tx->getSignal();
513 const RealType pt = (waveform != nullptr) ? waveform->getPower() : 0.0;
514 const RealType lambda = (waveform != nullptr) ? (params::c() / waveform->getCarrier()) : lambda_default;
515
516 // --- PRE-CALCULATE ILLUMINATOR PATHS (Tx -> Tgt) ---
517 // These depend only on the Transmitter and Targets. We calculate them once per Tx
518 // to avoid duplication when multiple receivers are present.
519 // We do NOT filter out Monostatic transmitters here. Even in a monostatic setup,
520 // the "Illuminator" link provides distinct info (Power Density at Target) compared
521 // to the "Monostatic" link (Received Power at Rx). By being outside the Rx loop,
522 // it is guaranteed to be unique per Target.
523 for (const auto& tgt : world.getTargets())
524 {
525 const auto p_tgt = tgt->getPosition(time);
526 const Vec3 vec_tx_tgt = p_tgt - p_tx;
527 const RealType r1 = vec_tx_tgt.length();
528
529 if (r1 <= EPSILON)
530 continue;
531
532 const Vec3 u_tx_tgt = vec_tx_tgt / r1;
533 // Tx Gain: Tx -> Tgt
534 const RealType gt = computeAntennaGain(tx.get(), u_tx_tgt, time, lambda);
535
536 // Power Density at Target: S = (Pt * Gt) / (4 * pi * R1^2)
537 const RealType p_density = (pt * gt) / (4.0 * PI * r1 * r1);
538
539 links.push_back({.type = LinkType::BistaticTxTgt,
540 .quality = LinkQuality::Strong,
541 .label = std::format("{:.1f} dBW/m\u00B2", wattsToDb(p_density)),
542 .source_id = tx->getId(),
543 .dest_id = tgt->getId(),
544 .origin_id = tx->getId()});
545 }
546
547 for (const auto& rx : world.getReceivers())
548 {
549 // 2. Check Receiver Schedule
550 if (!isComponentActive(rx->getSchedule(), time))
551 {
552 continue;
553 }
554
555 const auto p_rx = rx->getPosition(time);
556 const bool is_monostatic = (tx->getAttached() == rx.get());
557 const bool no_loss = rx->checkFlag(Receiver::RecvFlag::FLAG_NOPROPLOSS);
558
559 if (is_monostatic)
560 {
561 // --- Monostatic Case ---
562 // Calculates the round-trip Received Power (dBm)
563 for (const auto& tgt : world.getTargets())
564 {
565 const auto p_tgt = tgt->getPosition(time);
566
567 // Use computeLink helper logic, but catch exception manually by checking dist
568 // to avoid try/catch overhead in render loop
569 const Vec3 vec_tx_tgt = p_tgt - p_tx;
570 const RealType dist = vec_tx_tgt.length();
571
572 if (dist <= EPSILON)
573 continue;
574
575 const Vec3 u_tx_tgt = vec_tx_tgt / dist; // Unit vec Tx -> Tgt
576
577 // Reusing internal helpers
578 // Tx Gain: Direction Tx -> Tgt
579 const RealType gt = computeAntennaGain(tx.get(), u_tx_tgt, time, lambda);
580 // Rx Gain: Direction Rx -> Tgt (which is -u_tx_tgt because Rx is at Tx)
581 const RealType gr = computeAntennaGain(rx.get(), u_tx_tgt, time, lambda);
582
583 // RCS: In = Tx->Tgt, Out = Tgt->Rx (which is -(Tx->Tgt))
584 SVec3 in_angle(u_tx_tgt);
585 SVec3 out_angle(-u_tx_tgt);
586 const RealType rcs = tgt->getRcs(in_angle, out_angle, time);
587
588 // Reusing Reflected Path Helper
589 // r_tx = dist, r_rx = dist
590 const RealType power_ratio =
591 computeReflectedPathPower(gt, gr, rcs, lambda, dist, dist, no_loss);
592
593 const RealType pr_watts = pt * power_ratio;
594
595 links.push_back(
596 {.type = LinkType::Monostatic,
597 .quality = isSignalStrong(pr_watts, rx->getNoiseTemperature()) ? LinkQuality::Strong
599 .label = std::format("{:.1f} dBm (RCS: {:.1f}m\u00B2)", wattsToDbm(pr_watts), rcs),
600 .source_id = tx->getId(), // Monostatic implies Tx/Rx is same platform/system
601 .dest_id = tgt->getId(),
602 .origin_id = tx->getId()});
603 }
604 }
605 else
606 {
607 // --- Bistatic Case ---
608
609 // 1. Direct Path (Interference)
610 if (!rx->checkFlag(Receiver::RecvFlag::FLAG_NODIRECT))
611 {
612 const Vec3 vec_direct = p_rx - p_tx;
613 const RealType dist = vec_direct.length();
614
615 if (dist > EPSILON)
616 {
617 const Vec3 u_tx_rx = vec_direct / dist;
618
619 // Tx Gain: Tx -> Rx
620 const RealType gt = computeAntennaGain(tx.get(), u_tx_rx, time, lambda);
621 // Rx Gain: Rx -> Tx (which is -u_tx_rx)
622 const RealType gr = computeAntennaGain(rx.get(), -u_tx_rx, time, lambda);
623
624 const RealType power_ratio = computeDirectPathPower(gt, gr, lambda, dist, no_loss);
625 const RealType pr_watts = pt * power_ratio;
626
627 links.push_back({.type = LinkType::DirectTxRx,
628 .quality = LinkQuality::Strong,
629 .label = std::format("Direct: {:.1f} dBm", wattsToDbm(pr_watts)),
630 .source_id = tx->getId(),
631 .dest_id = rx->getId(),
632 .origin_id = tx->getId()});
633 }
634 }
635
636 // 2. Reflected Path (Scattered Leg: Tgt -> Rx)
637 for (const auto& tgt : world.getTargets())
638 {
639 const auto p_tgt = tgt->getPosition(time);
640 const Vec3 vec_tx_tgt = p_tgt - p_tx;
641 const Vec3 vec_tgt_rx = p_rx - p_tgt; // Vector Tgt -> Rx
642
643 const RealType r1 = vec_tx_tgt.length();
644 const RealType r2 = vec_tgt_rx.length();
645
646 if (r1 <= EPSILON || r2 <= EPSILON)
647 continue;
648
649 const Vec3 u_tx_tgt = vec_tx_tgt / r1;
650 const Vec3 u_tgt_rx = vec_tgt_rx / r2;
651
652 // Tx Gain: Tx -> Tgt
653 const RealType gt = computeAntennaGain(tx.get(), u_tx_tgt, time, lambda);
654 // Rx Gain: Rx -> Tgt (Vector is Tgt->Rx, so look vector is -u_tgt_rx)
655 const RealType gr = computeAntennaGain(rx.get(), -u_tgt_rx, time, lambda);
656
657 // RCS
658 SVec3 in_angle(u_tx_tgt);
659 SVec3 out_angle(-u_tgt_rx); // Out angle is usually defined from Target OUTWARDS
660 const RealType rcs = tgt->getRcs(in_angle, out_angle, time);
661
662 const RealType power_ratio = computeReflectedPathPower(gt, gr, rcs, lambda, r1, r2, no_loss);
663 const RealType pr_watts = pt * power_ratio;
664
665 // Note: Illuminator leg (Tx->Tgt) was handled in the outer loop.
666
667 // Leg 2: Scattered
668 links.push_back({.type = LinkType::BistaticTgtRx,
669 .quality = isSignalStrong(pr_watts, rx->getNoiseTemperature())
672 .label = std::format("{:.1f} dBm", wattsToDbm(pr_watts)),
673 .source_id = tgt->getId(),
674 .dest_id = rx->getId(),
675 .origin_id = tx->getId()});
676 }
677 }
678 }
679 }
680 return links;
681 }
682}
Header for radar channel propagation and interaction models.
The World class manages the simulator environment.
Definition world.h:39
const std::vector< std::unique_ptr< radar::Target > > & getTargets() const noexcept
Retrieves the list of radar targets.
Definition world.h:200
const std::vector< std::unique_ptr< radar::Transmitter > > & getTransmitters() const noexcept
Retrieves the list of radar transmitters.
Definition world.h:220
const std::vector< std::unique_ptr< radar::Receiver > > & getReceivers() const noexcept
Retrieves the list of radar receivers.
Definition world.h:210
Class representing a radar signal with associated properties.
RealType getCarrier() const noexcept
Gets the carrier frequency of the radar signal.
RealType getLength() const noexcept
Gets the length of the radar signal.
RealType getPower() const noexcept
Gets the power of the radar signal.
A class representing a vector in spherical coordinates.
A class representing a vector in rectangular coordinates.
RealType length() const noexcept
Calculates the length (magnitude) of the vector.
const std::string & getName() const noexcept
Retrieves the name of the object.
Definition object.h:79
Platform * getPlatform() const noexcept
Retrieves the associated platform of the object.
Definition object.h:65
math::Vec3 getPosition(const RealType time) const
Retrieves the position of the object.
Definition object.h:50
math::Vec3 getPosition(const RealType time) const
Gets the position of the platform at a specific time.
Definition platform.h:75
Represents a radar system on a platform.
Definition radar_obj.h:47
const Radar * getAttached() const noexcept
Retrieves the attached radar object.
Definition radar_obj.h:76
std::shared_ptr< timing::Timing > getTiming() const
Retrieves the timing source for the radar.
Definition radar_obj.cpp:66
Manages radar signal reception and response processing.
Definition receiver.h:37
bool checkFlag(RecvFlag flag) const noexcept
Checks if a specific flag is set.
Definition receiver.h:88
const std::vector< SchedulePeriod > & getSchedule() const noexcept
Retrieves the list of active reception periods.
Definition receiver.h:277
SimId getId() const noexcept
Retrieves the unique ID of the receiver.
Definition receiver.h:95
RealType getNoiseTemperature() const noexcept
Retrieves the noise temperature of the receiver.
Definition receiver.h:102
Base class for radar targets.
Definition target.h:118
virtual RealType getRcs(math::SVec3 &inAngle, math::SVec3 &outAngle, RealType time) const =0
Gets the RCS value for the target.
Represents a radar transmitter system.
Definition transmitter.h:33
SimId getId() const noexcept
Retrieves the unique ID of the transmitter.
Definition transmitter.h:78
fers_signal::RadarSignal * getSignal() const noexcept
Retrieves the radar signal currently being transmitted.
Definition transmitter.h:71
const std::vector< SchedulePeriod > & getSchedule() const noexcept
Retrieves the list of active transmission periods.
Exception thrown when a range calculation fails, typically due to objects being too close.
double RealType
Type for real numbers.
Definition config.h:27
constexpr RealType EPSILON
Machine epsilon for real numbers.
Definition config.h:51
std::complex< RealType > ComplexType
Type for complex numbers.
Definition config.h:35
constexpr RealType PI
Mathematical constant π (pi).
Definition config.h:43
Classes and operations for 3D geometry.
Defines a structure to store interpolation point data for signal processing.
Header file for the logging system.
#define LOG(level,...)
Definition logging.h:19
RealType simSamplingRate() noexcept
Get the simulation sampling rate.
Definition parameters.h:115
RealType rate() noexcept
Get the rendering sample rate.
Definition parameters.h:121
RealType boltzmannK() noexcept
Get the Boltzmann constant.
Definition parameters.h:97
RealType c() noexcept
Get the speed of light.
Definition parameters.h:91
@ Weak
SNR < 0 dB (Geometric line of sight, but below noise floor)
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...
void solveReDirect(const Transmitter *trans, const Receiver *recv, const std::chrono::duration< RealType > &time, const RadarSignal *wave, ReResults &results)
Solves the radar equation for a direct path (Tx -> Rx).
void solveRe(const Transmitter *trans, const Receiver *recv, const Target *targ, const std::chrono::duration< RealType > &time, const RadarSignal *wave, ReResults &results)
Solves the bistatic radar equation for a reflected path (Tx -> Tgt -> Rx).
std::vector< PreviewLink > calculatePreviewLinks(const core::World &world, const RealType time)
Calculates all visual links for the current world state at a specific time.
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...
@ DirectTxRx
Interference path.
@ Monostatic
Combined Tx/Rx path.
@ BistaticTgtRx
Scattered path.
@ BistaticTxTgt
Illuminator path.
Defines the Parameters struct and provides methods for managing simulation parameters.
Defines the Radar class and associated functionality.
Classes for handling radar waveforms and signals.
Radar Receiver class for managing signal reception and response handling.
Classes for managing radar signal responses.
Stores data for an interpolation point.
RealType power
Power level of the signal at the interpolation point.
Stores the intermediate results of a radar equation calculation for a single time point.
RealType delay
Signal propagation delay in seconds.
RealType power
Power scaling factor (dimensionless, relative to transmitted power).
RealType phase
Phase shift in radians due to propagation delay.
Defines classes for radar targets and their Radar Cross-Section (RCS) models.
Timing source for simulation objects.
Header file for the Transmitter class in the radar namespace.
Header file for the World class in the simulator.