FERS 0.1.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 <algorithm>
20#include <cmath>
21#include <limits>
22#include <string_view>
23#include <unordered_map>
24
25#include "core/logging.h"
26#include "core/parameters.h"
27#include "core/sim_id.h"
28#include "core/world.h"
30#include "math/geometry_ops.h"
31#include "radar/radar_obj.h"
32#include "radar/receiver.h"
33#include "radar/target.h"
34#include "radar/transmitter.h"
35#include "serial/response.h"
36#include "signal/radar_signal.h"
37#include "timing/timing.h"
38
40using logging::Level;
41using math::SVec3;
42using math::Vec3;
43using radar::Receiver;
44using radar::Target;
46
47namespace
48{
49 /// Initializes an FMCW chirp tracker from a retarded time.
52 {
53 tracker.initialized = true;
54 if (t_ret >= source.segment_start)
55 {
57 tracker.n_current = static_cast<std::size_t>(std::floor(time_since_segment_start / source.chirp_period));
58 tracker.t_n = source.segment_start + static_cast<RealType>(tracker.n_current) * source.chirp_period;
59 return;
60 }
61
62 tracker.n_current = 0;
63 tracker.t_n = source.segment_start;
64 }
65
66 /// Initializes an FMCW triangle tracker from a retarded time.
69 {
70 tracker.triangle_initialized = true;
71 if (t_ret < source.segment_start)
72 {
73 tracker.triangle_index = 0;
74 tracker.triangle_leg = 0;
75 tracker.triangle_t_leg = source.segment_start;
76 tracker.triangle_phi_base = 0.0;
77 return;
78 }
79
80 const RealType delta = t_ret - source.segment_start;
81 tracker.triangle_index = static_cast<std::size_t>(std::floor(delta / source.triangle_period));
83 delta - static_cast<RealType>(tracker.triangle_index) * source.triangle_period;
84 tracker.triangle_leg = local_triangle_time < source.chirp_duration ? 0U : 1U;
85 tracker.triangle_t_leg = source.segment_start +
86 static_cast<RealType>(tracker.triangle_index) * source.triangle_period +
87 (tracker.triangle_leg == 1U ? source.chirp_duration : 0.0);
88 tracker.triangle_phi_base =
89 std::fmod(static_cast<RealType>(tracker.triangle_index) * source.mod_phi_tri, 2.0 * PI) +
90 (tracker.triangle_leg == 1U ? source.mod_phi_up : 0.0);
91 if (tracker.triangle_phi_base >= 2.0 * PI)
92 {
93 tracker.triangle_phi_base -= 2.0 * PI;
94 }
95 if (tracker.triangle_phi_base < 0.0)
96 {
97 tracker.triangle_phi_base += 2.0 * PI;
98 }
99 }
100
101 /**
102 * @struct LinkGeometry
103 * @brief Holds geometric properties of a path segment between two points.
104 */
105 struct LinkGeometry
106 {
107 Vec3 u_vec; ///< Unit vector pointing from Source to Destination.
108 RealType dist{}; ///< Distance between Source and Destination.
109 };
110
111 struct StreamingWaveformEvaluation
112 {
113 RealType phase = 0.0;
114 RealType rf_frequency = 0.0;
115 ComplexType envelope{1.0, 0.0};
116 };
117
118 /**
119 * @brief Computes the geometry (distance and direction) between two points.
120 * @param p_from Starting position.
121 * @param p_to Ending position.
122 * @return LinkGeometry containing distance and unit vector.
123 * @throws RangeError if the distance is too small (<= EPSILON).
124 */
125 LinkGeometry computeLink(const Vec3& p_from, const Vec3& p_to)
126 {
127 const Vec3 vec = p_to - p_from;
128 const RealType dist = vec.length();
129
130 if (dist <= EPSILON)
131 {
132 // LOG(Level::FATAL) is handled by the caller or generic exception handler if needed,
133 // but for RangeError strictly we just throw here to keep it pure.
134 // However, existing code logged FATAL before throwing.
135 // We'll throw RangeError, and let callers decide if they want to log or return 0.
137 }
138
139 return {vec / dist, dist};
140 }
141
142 /**
143 * @brief Calculates the antenna gain for a specific direction and time.
144 * @param radar The radar object (Transmitter or Receiver).
145 * @param direction_vec The unit vector pointing AWAY from the antenna towards the target/receiver.
146 * @param time The simulation time for rotation lookup.
147 * @param lambda The signal wavelength.
148 * @return The linear gain value.
149 */
151 {
152 return radar->getGain(SVec3(direction_vec), radar->getRotation(time), lambda);
153 }
154
155 /**
156 * @brief Computes the power scaling factor for a direct path (Friis Transmission Equation).
157 * @param tx_gain Transmitter gain (linear).
158 * @param rx_gain Receiver gain (linear).
159 * @param lambda Wavelength (meters).
160 * @param dist Distance (meters).
161 * @param no_prop_loss If true, distance-based attenuation is ignored.
162 * @return The power scaling factor (Pr / Pt).
163 */
165 bool no_prop_loss)
166 {
167 const RealType numerator = tx_gain * rx_gain * lambda * lambda;
168 RealType denominator = 16.0 * PI * PI; // (4 * PI)^2
169
170 if (!no_prop_loss)
171 {
172 denominator *= dist * dist;
173 }
174
175 return numerator / denominator;
176 }
177
178 /**
179 * @brief Computes the power scaling factor for a reflected path (Bistatic Radar Range Equation).
180 * @param tx_gain Transmitter gain (linear).
181 * @param rx_gain Receiver gain (linear).
182 * @param rcs Target Radar Cross Section (m^2).
183 * @param lambda Wavelength (meters).
184 * @param r_tx Distance from Transmitter to Target.
185 * @param r_rx Distance from Target to Receiver.
186 * @param no_prop_loss If true, distance-based attenuation is ignored.
187 * @return The power scaling factor (Pr / Pt).
188 */
191 {
192 const RealType numerator = tx_gain * rx_gain * rcs * lambda * lambda;
193 RealType denominator = 64.0 * PI * PI * PI; // (4 * PI)^3
194
195 if (!no_prop_loss)
196 {
197 denominator *= r_tx * r_tx * r_rx * r_rx;
198 }
199
200 return numerator / denominator;
201 }
202
203 /**
204 * @brief Computes the non-coherent phase shift due to timing offsets.
205 *
206 * Used for CW simulation where LO effects are modeled analytically.
207 *
208 * @param tx The transmitter.
209 * @param rx The receiver.
210 * @param time The current simulation time.
211 * @return The phase shift in radians.
212 */
214 {
215 const auto tx_timing = tx->getTiming();
216 const auto rx_timing = rx->getTiming();
217 const RealType delta_f = tx_timing->getFreqOffset() - rx_timing->getFreqOffset();
218 const RealType delta_phi = tx_timing->getPhaseOffset() - rx_timing->getPhaseOffset();
219 return 2 * PI * delta_f * time + delta_phi;
220 }
221
222 /// Computes deterministic timing phase for one timing source when no lookup is available.
224 {
225 if (timing == nullptr)
226 {
227 return 0.0;
228 }
229 return 2.0 * PI * timing->getFreqOffset() * time + timing->getPhaseOffset();
230 }
231
232 /// Computes timing phase with an optional lookup and streaming phase-application mode.
234 const RealType tx_time, const simulation::CwPhaseNoiseLookup* phase_noise_lookup,
236 {
238 {
239 return 0.0;
240 }
242 {
243 if (phase_noise_lookup == nullptr)
244 {
245 return computeSingleTimingPhase(tx->getTiming().get(), tx_time);
246 }
247 return phase_noise_lookup->sample(tx->getTiming().get(), tx_time);
248 }
249 if (phase_noise_lookup == nullptr)
250 {
252 }
253 return phase_noise_lookup->phaseDifference(rx->getTiming().get(), rx_time, tx->getTiming().get(), tx_time);
254 }
255
256 /// Checks whether received power is above the thermal noise floor.
258 {
259 // Use configured rate or default to 1Hz if unconfigured to prevent divide-by-zero or silly values
260 const RealType bw = params::rate() > 0 ? params::rate() : 1.0;
261 const RealType noise_floor = params::boltzmannK() * (temp_kelvin > 0 ? temp_kelvin : 290.0) * bw;
262 return power_watts > noise_floor;
263 }
264
265 /**
266 * @brief Converts power in watts to decibels milliwatts (dBm).
267 *
268 * @param watts Power in watts.
269 * @return Power in dBm. Returns -999.0 dBm for non-positive input.
270 */
272 {
273 if (watts <= 0)
274 {
275 return -999.0;
276 }
277 return 10.0 * std::log10(watts * 1000.0);
278 }
279
280 /**
281 * @brief Converts power in watts to decibels (dB).
282 *
283 * @param watts Power in watts.
284 * @return Power in decibels (dB). Returns -999.0 dB for non-positive input.
285 */
287 {
288 if (watts <= 0)
289 {
290 return -999.0;
291 }
292 return 10.0 * std::log10(watts);
293 }
294
295 /**
296 * @brief Checks if a component is active at the given time based on its schedule.
297 *
298 * @param schedule The component's operating schedule.
299 * @param time The current simulation time.
300 * @return true If the schedule is empty (implied always on) or if time is within a period.
301 * @return false If the schedule is populated but the time is outside all periods.
302 */
303 bool isComponentActive(const std::vector<radar::SchedulePeriod>& schedule, RealType time)
304 {
305 if (schedule.empty())
306 {
307 return true;
308 }
309 for (const auto& period : schedule)
310 {
311 if (time >= period.start && time <= period.end)
312 {
313 return true;
314 }
315 }
316 return false;
317 }
318
319 /// Builds a compatibility streaming-source cache for classic CW paths.
321 {
322 auto source = core::makeActiveSource(trans, params::startTime(), std::numeric_limits<RealType>::max());
324 {
325 source.segment_start = std::numeric_limits<RealType>::lowest();
326 }
327 return source;
328 }
329
332 {
334 const auto chirp_index = static_cast<std::size_t>(std::floor(time_since_segment_start / source.chirp_period));
335 if (source.chirp_count.has_value() && chirp_index >= *source.chirp_count)
336 {
337 return false;
338 }
341 {
342 return false;
343 }
344 phase_out = -2.0 * PI * source.carrier_freq * tau + source.two_pi_f0 * chirp_time +
346 return true;
347 }
348
352 {
353 if (!chirp_tracker.initialized)
354 {
356 }
357
358 while (t_ret >= chirp_tracker.t_n + source.chirp_period)
359 {
360 chirp_tracker.t_n += source.chirp_period;
361 ++chirp_tracker.n_current;
362 }
363
364 if (source.chirp_count.has_value() && chirp_tracker.n_current >= *source.chirp_count)
365 {
366 return false;
367 }
368
369 const RealType u_ret = t_ret - chirp_tracker.t_n;
371 {
372 return false;
373 }
374
375 phase_out =
376 -2.0 * PI * source.carrier_freq * tau + source.two_pi_f0 * u_ret + source.s_pi_alpha * u_ret * u_ret;
377 return true;
378 }
379
382 {
383 const RealType delta = t_ret - source.segment_start;
384 const auto triangle_index = static_cast<std::size_t>(std::floor(delta / source.triangle_period));
385 if (source.triangle_count.has_value() && triangle_index >= *source.triangle_count)
386 {
387 return false;
388 }
389 const RealType local_triangle_time = delta - static_cast<RealType>(triangle_index) * source.triangle_period;
390 const bool down_leg = local_triangle_time >= source.chirp_duration;
393 {
394 return false;
395 }
396 const RealType phi_base = std::fmod(static_cast<RealType>(triangle_index) * source.mod_phi_tri, 2.0 * PI) +
397 (down_leg ? source.mod_phi_up : 0.0);
398 const RealType modular_phi_base = phi_base >= 2.0 * PI ? phi_base - 2.0 * PI : phi_base;
399 const RealType linear_coeff = down_leg ? source.two_pi_f0_plus_B : source.two_pi_f0;
400 const RealType quad_coeff = down_leg ? source.neg_pi_alpha : source.pi_alpha;
403 return true;
404 }
405
408 {
409 while (t_ret >= chirp_tracker.triangle_t_leg + source.chirp_duration)
410 {
411 chirp_tracker.triangle_t_leg += source.chirp_duration;
412 chirp_tracker.triangle_leg = 1U - chirp_tracker.triangle_leg;
413 if (chirp_tracker.triangle_leg == 0U)
414 {
415 ++chirp_tracker.triangle_index;
416 }
417 chirp_tracker.triangle_phi_base += source.mod_phi_up;
418 if (chirp_tracker.triangle_phi_base >= 2.0 * PI)
419 {
420 chirp_tracker.triangle_phi_base -= 2.0 * PI;
421 }
422 }
423 }
424
428 {
429 if (!chirp_tracker.triangle_initialized)
430 {
432 }
433
435 if (source.triangle_count.has_value() && chirp_tracker.triangle_index >= *source.triangle_count)
436 {
437 return false;
438 }
439
440 const RealType u_ret = t_ret - chirp_tracker.triangle_t_leg;
442 {
443 return false;
444 }
445
446 const bool down_leg = chirp_tracker.triangle_leg == 1U;
447 const RealType linear_coeff = down_leg ? source.two_pi_f0_plus_B : source.two_pi_f0;
448 const RealType quad_coeff = down_leg ? source.neg_pi_alpha : source.pi_alpha;
449 phase_out = -2.0 * PI * source.carrier_freq * tau + chirp_tracker.triangle_phi_base + linear_coeff * u_ret +
451 return true;
452 }
453
454 /// Computes streaming waveform phase and active RF at a receiver time.
457 StreamingWaveformEvaluation& eval)
458 {
459 if (source.carrier_freq <= 0.0)
460 {
461 return false;
462 }
463
464 const RealType t_ret = rx_time - tau;
466 {
467 return false;
468 }
469
471 {
472 eval.rf_frequency = source.carrier_freq;
473 if (chirp_tracker == nullptr)
474 {
475 return computeLinearFmcwPhaseWithoutTracker(source, t_ret, tau, eval.phase);
476 }
478 }
479
481 {
482 eval.rf_frequency = source.carrier_freq;
483 if (chirp_tracker == nullptr)
484 {
486 }
488 }
489
491 {
492 if (source.sfcw == nullptr)
493 {
494 return false;
495 }
496 const auto step = source.sfcw->activeStepAt(t_ret - source.segment_start, source.carrier_freq);
497 if (!step.has_value() || step->rf_frequency <= 0.0)
498 {
499 return false;
500 }
501 eval.rf_frequency = step->rf_frequency;
502 eval.phase = -2.0 * PI * step->rf_frequency * tau;
503 return true;
504 }
505
507 {
508 if (source.file == nullptr || source.file_duration <= 0.0)
509 {
510 return false;
511 }
512 eval.rf_frequency = source.carrier_freq;
513 eval.phase = -2.0 * PI * source.carrier_freq * tau;
514 eval.envelope = source.file->sampleAt(t_ret - source.segment_start);
515 return true;
516 }
517
518 eval.rf_frequency = source.carrier_freq;
519 eval.phase = -2.0 * PI * source.carrier_freq * tau;
520 return true;
521 }
522
523 /// Returns average radiated power for preview visualization.
525 {
526 if (waveform == nullptr)
527 {
528 return 0.0;
529 }
530 if (const auto* fmcw = waveform->getFmcwChirpSignal(); fmcw != nullptr)
531 {
532 return waveform->getPower() * (fmcw->getChirpDuration() / fmcw->getChirpPeriod());
533 }
534 if (waveform->isFmcwTriangle())
535 {
536 return waveform->getPower();
537 }
538 return waveform->getPower();
539 }
540
541 /// Formats received or radiated power as a dBm preview label.
542 std::string formatPreviewDbmLabel(const RealType watts, const std::string_view prefix = {})
543 {
544 return std::format("{}{:.1f} dBm", prefix, wattsToDbm(watts));
545 }
546
547 /// Formats target illumination density as a dBW-per-square-meter preview label.
549 {
550 return std::format("{:.1f} dBW/m\u00B2", wattsToDb(watts));
551 }
552}
553
554namespace simulation
555{
557 {
558 if (samples.empty())
559 {
560 return 0.0;
561 }
562 if ((time <= start_time) || (samples.size() == 1) || (dt <= 0.0))
563 {
564 return samples.front();
565 }
566
567 const RealType position = (time - start_time) / dt;
568 const auto last_index = static_cast<RealType>(samples.size() - 1);
569 if (position >= last_index)
570 {
571 return samples.back();
572 }
573
574 const auto lower_index = static_cast<std::size_t>(position);
575 const RealType fraction = position - static_cast<RealType>(lower_index);
576 return samples[lower_index] + fraction * (samples[lower_index + 1] - samples[lower_index]);
577 }
578
579 CwPhaseNoiseLookup CwPhaseNoiseLookup::build(const std::span<const std::shared_ptr<timing::Timing>> timings,
580 const RealType start_time, const RealType end_time)
581 {
583 lookup.start_time = start_time;
584 lookup.end_time = std::max(start_time, end_time);
585
586 const RealType sample_rate = params::rate() * params::oversampleRatio();
587 lookup.dt = sample_rate > 0.0 ? (1.0 / sample_rate) : 1.0;
588
589 const auto sample_count =
590 static_cast<std::size_t>(std::ceil((lookup.end_time - lookup.start_time) / lookup.dt) + 1.0);
591 constexpr std::size_t phase_noise_warning_threshold_bytes = 500ULL * 1024ULL * 1024ULL;
592 const auto bytes_per_buffer = sample_count * sizeof(RealType);
593
594 for (const auto& timing : timings)
595 {
596 if (!timing || lookup.buffers.contains(timing->getId()))
597 {
598 continue;
599 }
600
602 buffer.start_time = lookup.start_time;
603 buffer.dt = lookup.dt;
604 if (timing->isEnabled())
605 {
606 auto timing_clone = timing->clone();
607 if (lookup.start_time > 0.0)
608 {
609 const auto skip_count = static_cast<std::size_t>(std::llround(lookup.start_time / lookup.dt));
610 timing_clone->skipSamples(skip_count);
611 }
612 // TODO: Replace whole-simulation CW lookup generation with chunked/streaming generation.
614 {
615 LOG(Level::WARNING,
616 "CW phase-noise lookup for timing '{}' allocates {} bytes; large scenarios need chunked "
617 "streaming.",
618 timing->getName(), bytes_per_buffer);
619 }
620 buffer.samples.resize(sample_count);
621 std::ranges::generate(buffer.samples, [&] { return timing_clone->getNextSample(); });
622 }
623
624 lookup.buffers.emplace(timing->getId(), std::move(buffer));
625 }
626
627 return lookup;
628 }
629
630 RealType CwPhaseNoiseLookup::sample(const timing::Timing* const timing, const RealType time) const noexcept
631 {
632 if (timing == nullptr)
633 {
634 return 0.0;
635 }
636 const auto it = buffers.find(timing->getId());
637 if (it == buffers.end())
638 {
639 return 0.0;
640 }
641 return it->second.sampleAt(time);
642 }
643
645 const timing::Timing* const tx_timing,
646 const RealType tx_time) const noexcept
647 {
648 return sample(tx_timing, tx_time) - sample(rx_timing, rx_time);
649 }
650
651 void solveRe(const Transmitter* trans, const Receiver* recv, const Target* targ,
652 const std::chrono::duration<RealType>& time, const RadarSignal* wave, ReResults& results)
653 {
654 // Note: RangeError log messages are handled by the original catch block in calculateResponse
655 // or explicitly here if strict adherence to original logging is required.
656 // Using the helper logic which throws RangeError on epsilon check.
657
658 const RealType t_val = time.count();
659 const auto p_tx = trans->getPosition(t_val);
660 const auto p_rx = recv->getPosition(t_val);
661 const auto p_tgt = targ->getPosition(t_val);
662
663 // Link 1: Tx -> Target
664 LinkGeometry link_tx_tgt;
665 // Link 2: Target -> Rx (Note: Vector for calculation is Tgt->Rx)
666 LinkGeometry link_tgt_rx;
667
668 try
669 {
671 link_tgt_rx = computeLink(p_tgt, p_rx); // Vector Tgt -> Rx
672 }
673 catch (const RangeError&)
674 {
675 LOG(Level::INFO, "Transmitter or Receiver too close to Target for accurate simulation");
676 throw;
677 }
678
679 results.delay = (link_tx_tgt.dist + link_tgt_rx.dist) / params::c();
680
681 // Calculate RCS
682 // Note: getRcs expects (InAngle, OutAngle).
683 // InAngle: Tx -> Tgt (link_tx_tgt.u_vec)
684 // OutAngle: Rx -> Tgt (Opposite of Tgt->Rx, so -link_tgt_rx.u_vec)
685 // This matches existing logic.
688 const auto rcs = targ->getRcs(in_angle, out_angle, t_val);
689
690 const auto wavelength = params::c() / wave->getCarrier();
691
692 // Tx Gain: Direction Tx -> Tgt
694 // Rx Gain: Direction Rx -> Tgt (Opposite of Tgt->Rx).
695 // Time is time + delay.
696 const auto rx_gain = computeAntennaGain(recv, -link_tgt_rx.u_vec, results.delay + t_val, wavelength);
697
698 const bool no_loss = recv->checkFlag(Receiver::RecvFlag::FLAG_NOPROPLOSS);
699 results.power =
701
702 results.phase = -results.delay * 2 * PI * wave->getCarrier();
703 }
704
705 void solveReDirect(const Transmitter* trans, const Receiver* recv, const std::chrono::duration<RealType>& time,
707 {
708 const RealType t_val = time.count();
709 const auto p_tx = trans->getPosition(t_val);
710 const auto p_rx = recv->getPosition(t_val);
711
712 LinkGeometry link;
713 try
714 {
715 link = computeLink(p_tx, p_rx); // Vector Tx -> Rx
716 }
717 catch (const RangeError&)
718 {
719 LOG(Level::INFO, "Transmitter or Receiver too close for accurate simulation");
720 throw;
721 }
722
723 results.delay = link.dist / params::c();
724 const RealType wavelength = params::c() / wave->getCarrier();
725
726 // Discrepancy Fix: Original code used (Rx - Tx) for Receiver Gain but (Tx - Rx) logic for Transmitter gain
727 // was ambiguous/incorrect (using `tpos - rpos` which is Rx->Tx).
728 // Per `calculateDirectPathContribution` preference:
729 // Tx Gain uses Vector Tx -> Rx.
730 // Rx Gain uses Vector Rx -> Tx.
731
732 const auto tx_gain = computeAntennaGain(trans, link.u_vec, t_val, wavelength);
733 const auto rx_gain = computeAntennaGain(recv, -link.u_vec, t_val + results.delay, wavelength);
734
735 const bool no_loss = recv->checkFlag(Receiver::RecvFlag::FLAG_NOPROPLOSS);
737
738 results.phase = -results.delay * 2 * PI * wave->getCarrier();
739 }
740
742 const CwPhaseNoiseLookup* const phase_noise_lookup)
743 {
745 phase_noise_lookup);
746 }
747
749 const Receiver* recv, const RealType timeK,
750 const CwPhaseNoiseLookup* const phase_noise_lookup,
753 {
754 const auto* const trans = source.transmitter;
755 if (trans == nullptr)
756 {
757 return {0.0, 0.0};
758 }
759 // Check for co-location to prevent singularities.
760 // If they share the same platform, we assume they are isolated (no leakage) or explicit
761 // monostatic handling is required (which is not modeled via the far-field path).
762 if (trans->getPlatform() == recv->getPlatform())
763 {
764 return {0.0, 0.0};
765 }
766
767 const auto p_tx = trans->getPlatform()->getPosition(timeK);
768 const auto p_rx = recv->getPlatform()->getPosition(timeK);
769
770 LinkGeometry link;
771 try
772 {
774 }
775 catch (const RangeError&)
776 {
777 return {0.0, 0.0};
778 }
779
780 const RealType tau = link.dist / params::c();
781 StreamingWaveformEvaluation eval;
783 {
784 return {0.0, 0.0};
785 }
786 const RealType lambda = params::c() / eval.rf_frequency;
787
788 // Tx Gain: Direction Tx -> Rx
790 // Rx Gain: Direction Rx -> Tx (-u_vec)
792
793 const bool no_loss = recv->checkFlag(Receiver::RecvFlag::FLAG_NOPROPLOSS);
795
796 // Include Signal Power
797 const RealType amplitude = source.amplitude * std::sqrt(scaling_factor);
798
799 // Carrier Phase
800 ComplexType contribution = amplitude * eval.envelope * std::polar(1.0, eval.phase);
801
802 // Non-coherent Local Oscillator Effects
804 computeTimingPhase(trans, recv, timeK, timeK - tau, phase_noise_lookup, timing_phase_mode);
805 contribution *= std::polar(1.0, non_coherent_phase);
806
807 return contribution;
808 }
809
812 {
813 StreamingWaveformEvaluation eval;
815 {
816 return false;
817 }
818 phase_out = eval.phase;
819 if (std::abs(eval.envelope) > 0.0)
820 {
821 phase_out += std::arg(eval.envelope);
822 }
823 return true;
824 }
825
828 {
829 StreamingWaveformEvaluation eval;
831 {
832 return false;
833 }
834 sample_out = eval.envelope * std::polar(1.0, eval.phase);
835 return true;
836 }
837
839 const RealType timeK,
840 const CwPhaseNoiseLookup* const phase_noise_lookup)
841 {
843 phase_noise_lookup);
844 }
845
847 const Receiver* recv, const Target* targ,
848 const RealType timeK,
849 const CwPhaseNoiseLookup* const phase_noise_lookup,
852 {
853 const auto* const trans = source.transmitter;
854 if (trans == nullptr)
855 {
856 return {0.0, 0.0};
857 }
858 // Check for co-location involving the target.
859 // We do not model a platform tracking itself (R=0) or illuminating itself (R=0).
860 if (trans->getPlatform() == targ->getPlatform() || recv->getPlatform() == targ->getPlatform())
861 {
862 return {0.0, 0.0};
863 }
864
865 const auto p_tx = trans->getPlatform()->getPosition(timeK);
866 const auto p_rx = recv->getPlatform()->getPosition(timeK);
867 const auto p_tgt = targ->getPlatform()->getPosition(timeK);
868
869 LinkGeometry link_tx_tgt;
870 LinkGeometry link_tgt_rx;
871
872 try
873 {
876 }
877 catch (const RangeError&)
878 {
879 return {0.0, 0.0};
880 }
881
882 const RealType tau = (link_tx_tgt.dist + link_tgt_rx.dist) / params::c();
883 StreamingWaveformEvaluation eval;
885 {
886 return {0.0, 0.0};
887 }
888 const RealType lambda = params::c() / eval.rf_frequency;
889
890 // RCS Lookups: In (Tx->Tgt), Out (Rx->Tgt = - (Tgt->Rx))
893 const RealType rcs = targ->getRcs(in_angle, out_angle, timeK);
894
895 // Tx Gain: Direction Tx -> Tgt
897 // Rx Gain: Direction Rx -> Tgt (- (Tgt->Rx)). Time: timeK + tau.
899
900 const bool no_loss = recv->checkFlag(Receiver::RecvFlag::FLAG_NOPROPLOSS);
903
904 // Include Signal Power
905 const RealType amplitude = source.amplitude * std::sqrt(scaling_factor);
906
907 ComplexType contribution = amplitude * eval.envelope * std::polar(1.0, eval.phase);
908
909 // Non-coherent Local Oscillator Effects
911 computeTimingPhase(trans, recv, timeK, timeK - tau, phase_noise_lookup, timing_phase_mode);
912 contribution *= std::polar(1.0, non_coherent_phase);
913
914 return contribution;
915 }
916
917 std::unique_ptr<serial::Response> calculateResponse(const Transmitter* trans, const Receiver* recv,
918 const RadarSignal* signal, const RealType startTime,
919 const Target* targ)
920 {
921 // If calculating direct path (no target) and components are co-located:
922 // 1. If explicitly attached (monostatic), skip (internal leakage handled elsewhere).
923 // 2. If independent but on the same platform, distance is 0. Far-field logic (1/R^2)
924 // diverges. We skip calculation to avoid RangeError crashes, assuming
925 // no direct coupling/interference for co-located far-field antennas.
926 if (targ == nullptr && (trans->getAttached() == recv || trans->getPlatform() == recv->getPlatform()))
927 {
928 return nullptr;
929 }
930
931 // If calculating reflected path and target is co-located with either Tx or Rx:
932 // Skip to avoid singularity. Simulating a radar tracking its own platform
933 // requires near-field clutter models, not point-target RCS models.
934 if (targ != nullptr &&
935 (targ->getPlatform() == trans->getPlatform() || targ->getPlatform() == recv->getPlatform()))
936 {
937 LOG(Level::TRACE,
938 "Skipping reflected path calculation for Target {} co-located with Transmitter {} or Receiver {}",
939 targ->getName(), trans->getName(), recv->getName());
940 return nullptr;
941 }
942
943 const auto start_time_chrono = std::chrono::duration<RealType>(startTime);
944 const auto end_time_chrono = start_time_chrono + std::chrono::duration<RealType>(signal->getLength());
945 const auto sample_time_chrono = std::chrono::duration<RealType>(1.0 / params::simSamplingRate());
946 const int point_count = static_cast<int>(std::ceil(signal->getLength() / sample_time_chrono.count()));
947
948 if ((targ != nullptr) && point_count == 0)
949 {
950 LOG(Level::FATAL, "No time points are available for execution!");
951 throw std::runtime_error("No time points are available for execution!");
952 }
953
954 auto response = std::make_unique<serial::Response>(signal, trans);
955
956 try
957 {
958 for (int i = 0; i <= point_count; ++i)
959 {
960 const auto current_time =
962
964 if (targ != nullptr)
965 {
967 }
968 else
969 {
971 }
972
973 interp::InterpPoint const point{.power = results.power,
974 .time = current_time.count() + results.delay,
975 .delay = results.delay,
976 .phase = results.phase};
977 response->addInterpPoint(point);
978 }
979 }
980 catch (const RangeError&)
981 {
982 LOG(Level::INFO, "Receiver or Transmitter too close for accurate simulation");
983 throw; // Re-throw to be caught by the runner
984 }
985
986 return response;
987 }
988
989 namespace
990 {
991 struct PreviewTransmitterContext
992 {
997 };
998
999 struct PreviewReceiverContext
1000 {
1002 Vec3 position;
1004 };
1005
1006 PreviewTransmitterContext makePreviewTransmitterContext(const Transmitter& transmitter, const RealType time)
1007 {
1008 const auto* waveform = transmitter.getSignal();
1009 return PreviewTransmitterContext{.transmitter = transmitter,
1010 .position = transmitter.getPosition(time),
1011 .radiated_power = previewRadiatedPower(waveform),
1012 .lambda =
1013 (waveform != nullptr) ? (params::c() / waveform->getCarrier()) : 0.3};
1014 }
1015
1016 PreviewReceiverContext makePreviewReceiverContext(const Receiver& receiver, const RealType time)
1017 {
1018 return PreviewReceiverContext{.receiver = receiver,
1019 .position = receiver.getPosition(time),
1020 .no_loss = receiver.checkFlag(Receiver::RecvFlag::FLAG_NOPROPLOSS)};
1021 }
1022
1023 void addIlluminatorLinks(std::vector<PreviewLink>& links, const PreviewTransmitterContext& tx_ctx,
1024 const core::World& world, const RealType time)
1025 {
1026 for (const auto& target : world.getTargets())
1027 {
1028 const auto target_position = target->getPosition(time);
1029 const Vec3 vec_tx_tgt = target_position - tx_ctx.position;
1030 const RealType range = vec_tx_tgt.length();
1031 if (range <= EPSILON)
1032 {
1033 continue;
1034 }
1035
1036 const Vec3 u_tx_tgt = vec_tx_tgt / range;
1037 const RealType gain = computeAntennaGain(&tx_ctx.transmitter, u_tx_tgt, time, tx_ctx.lambda);
1038 const RealType power_density = (tx_ctx.radiated_power * gain) / (4.0 * PI * range * range);
1039 links.push_back({.type = LinkType::BistaticTxTgt,
1040 .quality = LinkQuality::Strong,
1042 .display_value = wattsToDb(power_density),
1043 .source_id = tx_ctx.transmitter.getId(),
1044 .dest_id = target->getId(),
1045 .origin_id = tx_ctx.transmitter.getId()});
1046 }
1047 }
1048
1049 void addMonostaticLinks(std::vector<PreviewLink>& links, const PreviewTransmitterContext& tx_ctx,
1050 const PreviewReceiverContext& rx_ctx, const core::World& world, const RealType time)
1051 {
1052 for (const auto& target : world.getTargets())
1053 {
1054 const auto target_position = target->getPosition(time);
1055 const Vec3 vec_tx_tgt = target_position - tx_ctx.position;
1056 const RealType range = vec_tx_tgt.length();
1057 if (range <= EPSILON)
1058 {
1059 continue;
1060 }
1061
1062 const Vec3 u_tx_tgt = vec_tx_tgt / range;
1063 const RealType gt = computeAntennaGain(&tx_ctx.transmitter, u_tx_tgt, time, tx_ctx.lambda);
1064 const RealType gr = computeAntennaGain(&rx_ctx.receiver, u_tx_tgt, time, tx_ctx.lambda);
1067 const RealType rcs = target->getRcs(in_angle, out_angle, time);
1068 const RealType power_ratio =
1069 computeReflectedPathPower(gt, gr, rcs, tx_ctx.lambda, range, range, rx_ctx.no_loss);
1070 const RealType pr_watts = tx_ctx.radiated_power * power_ratio;
1071 const RealType pr_unit_watts = tx_ctx.radiated_power *
1072 computeReflectedPathPower(gt, gr, 1.0, tx_ctx.lambda, range, range, rx_ctx.no_loss);
1073
1074 links.push_back({.type = LinkType::Monostatic,
1075 .quality = isSignalStrong(pr_unit_watts, rx_ctx.receiver.getNoiseTemperature())
1079 .display_value = wattsToDbm(pr_unit_watts),
1080 .source_id = tx_ctx.transmitter.getId(),
1081 .dest_id = target->getId(),
1082 .origin_id = tx_ctx.transmitter.getId(),
1083 .rcs = rcs,
1084 .actual_power_dbm = wattsToDbm(pr_watts)});
1085 }
1086 }
1087
1088 void addDirectLink(std::vector<PreviewLink>& links, const PreviewTransmitterContext& tx_ctx,
1089 const PreviewReceiverContext& rx_ctx, const RealType time)
1090 {
1091 if (rx_ctx.receiver.checkFlag(Receiver::RecvFlag::FLAG_NODIRECT))
1092 {
1093 return;
1094 }
1095
1096 const Vec3 vec_direct = rx_ctx.position - tx_ctx.position;
1097 const RealType range = vec_direct.length();
1098 if (range <= EPSILON)
1099 {
1100 return;
1101 }
1102
1103 const Vec3 u_tx_rx = vec_direct / range;
1104 const RealType gt = computeAntennaGain(&tx_ctx.transmitter, u_tx_rx, time, tx_ctx.lambda);
1105 const RealType gr = computeAntennaGain(&rx_ctx.receiver, -u_tx_rx, time, tx_ctx.lambda);
1106 const RealType power_ratio = computeDirectPathPower(gt, gr, tx_ctx.lambda, range, rx_ctx.no_loss);
1107 const RealType pr_watts = tx_ctx.radiated_power * power_ratio;
1108
1109 links.push_back({.type = LinkType::DirectTxRx,
1110 .quality = LinkQuality::Strong,
1111 .label = formatPreviewDbmLabel(pr_watts, "Direct: "),
1112 .display_value = wattsToDbm(pr_watts),
1113 .source_id = tx_ctx.transmitter.getId(),
1114 .dest_id = rx_ctx.receiver.getId(),
1115 .origin_id = tx_ctx.transmitter.getId()});
1116 }
1117
1118 void addBistaticTargetReceiverLinks(std::vector<PreviewLink>& links, const PreviewTransmitterContext& tx_ctx,
1119 const PreviewReceiverContext& rx_ctx, const core::World& world,
1120 const RealType time)
1121 {
1122 for (const auto& target : world.getTargets())
1123 {
1124 const auto target_position = target->getPosition(time);
1125 const Vec3 vec_tx_tgt = target_position - tx_ctx.position;
1126 const Vec3 vec_tgt_rx = rx_ctx.position - target_position;
1127 const RealType r1 = vec_tx_tgt.length();
1128 const RealType r2 = vec_tgt_rx.length();
1129 if (r1 <= EPSILON || r2 <= EPSILON)
1130 {
1131 continue;
1132 }
1133
1134 const Vec3 u_tx_tgt = vec_tx_tgt / r1;
1135 const Vec3 u_tgt_rx = vec_tgt_rx / r2;
1136 const RealType gt = computeAntennaGain(&tx_ctx.transmitter, u_tx_tgt, time, tx_ctx.lambda);
1137 const RealType gr = computeAntennaGain(&rx_ctx.receiver, -u_tgt_rx, time, tx_ctx.lambda);
1140 const RealType rcs = target->getRcs(in_angle, out_angle, time);
1141 const RealType power_ratio =
1142 computeReflectedPathPower(gt, gr, rcs, tx_ctx.lambda, r1, r2, rx_ctx.no_loss);
1143 const RealType pr_watts = tx_ctx.radiated_power * power_ratio;
1144 const RealType pr_unit_watts = tx_ctx.radiated_power *
1145 computeReflectedPathPower(gt, gr, 1.0, tx_ctx.lambda, r1, r2, rx_ctx.no_loss);
1146
1147 links.push_back({.type = LinkType::BistaticTgtRx,
1148 .quality = isSignalStrong(pr_unit_watts, rx_ctx.receiver.getNoiseTemperature())
1152 .display_value = wattsToDbm(pr_unit_watts),
1153 .source_id = target->getId(),
1154 .dest_id = rx_ctx.receiver.getId(),
1155 .origin_id = tx_ctx.transmitter.getId(),
1156 .rcs = rcs,
1157 .actual_power_dbm = wattsToDbm(pr_watts)});
1158 }
1159 }
1160
1161 void addReceiverLinks(std::vector<PreviewLink>& links, const PreviewTransmitterContext& tx_ctx,
1162 const PreviewReceiverContext& rx_ctx, const core::World& world, const RealType time)
1163 {
1164 if (tx_ctx.transmitter.getAttached() == &rx_ctx.receiver)
1165 {
1166 addMonostaticLinks(links, tx_ctx, rx_ctx, world, time);
1167 return;
1168 }
1169
1170 addDirectLink(links, tx_ctx, rx_ctx, time);
1171 addBistaticTargetReceiverLinks(links, tx_ctx, rx_ctx, world, time);
1172 }
1173 }
1174
1175 std::vector<PreviewLink> calculatePreviewLinks(const core::World& world, const RealType time)
1176 {
1177 std::vector<PreviewLink> links;
1178
1179 for (const auto& tx : world.getTransmitters())
1180 {
1181 if (!isComponentActive(tx->getSchedule(), time))
1182 {
1183 continue;
1184 }
1185
1186 const auto tx_ctx = makePreviewTransmitterContext(*tx, time);
1187 addIlluminatorLinks(links, tx_ctx, world, time);
1188
1189 for (const auto& rx : world.getReceivers())
1190 {
1191 if (!isComponentActive(rx->getSchedule(), time))
1192 {
1193 continue;
1194 }
1195 const auto rx_ctx = makePreviewReceiverContext(*rx, time);
1196 addReceiverLinks(links, tx_ctx, rx_ctx, world, time);
1197 }
1198 }
1199 return links;
1200 }
1201}
const Transmitter & transmitter
const Receiver & receiver
Vec3 position
RealType lambda
RealType radiated_power
bool no_loss
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::Transmitter > > & getTransmitters() const noexcept
Retrieves the list of radar transmitters.
Definition world.h:246
const std::vector< std::unique_ptr< radar::Receiver > > & getReceivers() const noexcept
Retrieves the list of radar receivers.
Definition world.h:236
Class representing a radar signal with associated properties.
bool isFmcwTriangle() const noexcept
Returns true when this signal is an FMCW triangular modulation signal.
RealType getCarrier() const noexcept
Gets the carrier frequency of the radar signal.
const class FmcwChirpSignal * getFmcwChirpSignal() const noexcept
Gets the FMCW chirp implementation, if this signal owns one.
RealType getPower() const noexcept
Gets the power of the radar signal.
ComplexType sampleAt(RealType time_since_start) const noexcept
Samples the finite stored complex envelope using the render interpolation filter.
std::optional< StepState > activeStepAt(RealType time_since_segment_start, RealType carrier_frequency) const noexcept
Returns the active step for a time since the segment start.
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.
math::Vec3 getPosition(const RealType time) const
Retrieves the position of the object.
Definition object.h:50
Represents a radar system on a platform.
Definition radar_obj.h:51
Manages radar signal reception and response processing.
Definition receiver.h:47
bool checkFlag(RecvFlag flag) const noexcept
Checks if a specific flag is set.
Definition receiver.h:137
Base class for radar targets.
Definition target.h:118
Represents a radar transmitter system.
Definition transmitter.h:34
fers_signal::RadarSignal * getSignal() const noexcept
Retrieves the radar signal currently being transmitted.
Definition transmitter.h:72
Exception thrown when a range calculation fails, typically due to objects being too close.
Represents a timing source for simulation.
Definition timing.h:36
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
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.
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 startTime() noexcept
Get the start time for the simulation.
Definition parameters.h:103
RealType boltzmannK() noexcept
Get the Boltzmann constant.
Definition parameters.h:97
unsigned oversampleRatio() noexcept
Get the oversampling ratio.
Definition parameters.h:151
RealType c() noexcept
Get the speed of light.
Definition parameters.h:91
ComplexType calculateStreamingDirectPathContribution(const core::ActiveStreamingSource &source, const Receiver *recv, const RealType timeK, const CwPhaseNoiseLookup *const phase_noise_lookup, core::FmcwChirpBoundaryTracker *const chirp_tracker, const StreamingTimingPhaseMode timing_phase_mode)
Calculates a direct-path contribution from a cached streaming source.
@ DirectTxRx
Interference path.
@ Monostatic
Combined Tx/Rx path.
@ BistaticTgtRx
Scattered path.
@ BistaticTxTgt
Illuminator path.
ComplexType calculateReflectedPathContribution(const Transmitter *trans, const Receiver *recv, const Target *targ, const RealType timeK, const CwPhaseNoiseLookup *const phase_noise_lookup)
Calculates the complex envelope contribution for a reflected path (Tx -> Tgt -> Rx) at a specific tim...
@ Weak
SNR < 0 dB (Geometric line of sight, but below noise floor)
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).
bool calculateStreamingReferenceSample(const core::ActiveStreamingSource &source, const RealType timeK, core::FmcwChirpBoundaryTracker *const chirp_tracker, ComplexType &sample_out)
Evaluates the complete complex reference envelope, including file-backed amplitude modulation.
bool calculateStreamingReferencePhase(const core::ActiveStreamingSource &source, const RealType timeK, core::FmcwChirpBoundaryTracker *const chirp_tracker, RealType &phase_out)
Evaluates a receive-time streaming waveform phase for receiver LO/dechirp references.
StreamingTimingPhaseMode
Selects how timing phase noise is applied to streaming channel contributions.
@ TransmitterOnly
Incoming RF/baseband signal before receiver LO subtraction.
@ None
Ignore timing phase noise entirely.
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).
ComplexType calculateStreamingReflectedPathContribution(const core::ActiveStreamingSource &source, const Receiver *recv, const Target *targ, const RealType timeK, const CwPhaseNoiseLookup *const phase_noise_lookup, core::FmcwChirpBoundaryTracker *const chirp_tracker, const StreamingTimingPhaseMode timing_phase_mode)
Calculates a reflected-path contribution from a cached streaming source.
ComplexType calculateDirectPathContribution(const Transmitter *trans, const Receiver *recv, const RealType timeK, const CwPhaseNoiseLookup *const phase_noise_lookup)
Calculates the complex envelope contribution for a direct propagation path (Tx -> Rx) at a specific t...
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.
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.
math::Vec3 max
Cached description of an active streaming transmitter segment.
RealType s_pi_alpha
Cached signed pi-scaled FMCW chirp-rate factor.
RealType triangle_period
Cached full triangle period in seconds.
RealType amplitude
Cached emitted signal amplitude.
RealType carrier_freq
Cached carrier frequency in hertz.
const fers_signal::SteppedFrequencySignal * sfcw
Stable pointer to the stepped-frequency waveform, if any.
RealType two_pi_f0
Cached two-pi carrier angular frequency factor.
RealType neg_pi_alpha
Triangle down-leg quadratic coefficient.
RealType two_pi_f0_plus_B
Triangle down-leg linear coefficient.
RealType mod_phi_tri
Triangle period phase increment modulo 2*pi.
RealType mod_phi_up
Triangle leg phase increment modulo 2*pi.
RealType segment_start
Segment start time in seconds.
RealType file_duration
Duration of the finite file waveform in seconds.
RealType pi_alpha
Triangle up-leg quadratic coefficient.
const radar::Transmitter * transmitter
Transmitter active during this segment.
RealType chirp_duration
Cached FMCW chirp duration in seconds.
RealType chirp_period
Cached FMCW chirp period in seconds.
StreamingWaveformKind kind
Cached streaming waveform shape.
const fers_signal::FileSignal * file
Stable pointer to the finite sampled waveform, if any.
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.
Tracks the current FMCW chirp boundary for a streaming path.
Stores data for an interpolation point.
Sampled phase-noise buffer for one timing source.
RealType sampleAt(RealType time) const noexcept
Returns the interpolated phase-noise sample at the specified time.
Lookup table for CW phase noise across timing sources.
static CwPhaseNoiseLookup build(std::span< const std::shared_ptr< timing::Timing > > timings, RealType start_time, RealType end_time)
Builds a phase-noise lookup for the requested timing sources and time range.
RealType end_time
Lookup end time in seconds.
RealType start_time
Lookup start time in seconds.
RealType sample(const timing::Timing *timing, RealType time) const noexcept
Samples phase noise for one timing source at the specified time.
RealType phaseDifference(const timing::Timing *rx_timing, RealType rx_time, const timing::Timing *tx_timing, RealType tx_time) const noexcept
Computes receiver-minus-transmitter phase noise at two propagation times.
Stores the intermediate results of a radar equation calculation for a single time point.
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.