21#include <initializer_list>
22#include <nlohmann/json.hpp>
27#include <unordered_map>
53 using TimingInstanceMap = std::unordered_map<SimId, std::shared_ptr<timing::Timing>>;
63 throw std::runtime_error(
"Missing required '" +
key +
"' for " +
owner +
".");
67 if (
j.at(
key).is_number_unsigned())
71 if (
j.at(
key).is_number_integer())
73 const auto value =
j.at(
key).get<
long long>();
76 throw std::runtime_error(
"negative id");
78 return static_cast<SimId>(value);
80 if (
j.at(
key).is_string())
82 const auto str =
j.at(
key).get<std::string>();
84 const unsigned long long parsed = std::stoull(
str, &
idx, 10);
87 throw std::runtime_error(
"trailing characters");
92 catch (
const std::exception&
e)
94 throw std::runtime_error(
"Invalid '" +
key +
"' for " +
owner +
": " +
e.what());
96 throw std::runtime_error(
"Invalid '" +
key +
"' type for " +
owner +
".");
103 if (
const auto it = timing_instances.find(
timing_id);
it != timing_instances.end())
124 if (!
j.contains(
key) ||
j.at(
key).is_null())
128 if (
j.at(
key).is_string())
130 return j.at(
key).get<std::string>();
132 return j.at(
key).dump();
146 const std::string&
owner)
160 const std::initializer_list<std::string_view>
allowed_keys)
179 message +=
" contains unsupported key '";
182 throw std::runtime_error(
message);
190 return mode_json.contains(
"dechirp_mode") ||
mode_json.contains(
"dechirp_reference") ||
192 mode_json.contains(
"if_filter_transition_width");
200 throw std::runtime_error(
owner +
" sfcw_mode must be an empty object.");
205 const std::string&
owner)
213 if (value <= 0.0 || !std::isfinite(value))
215 throw std::runtime_error(
owner +
" " +
key_string +
" must be a finite positive value.");
222 return if_chain.sample_rate_hz.has_value() ||
if_chain.filter_bandwidth_hz.has_value() ||
223 if_chain.filter_transition_width_hz.has_value();
228 const std::string&
owner)
230 if (
mode_json.contains(
"dechirp_reference"))
232 throw std::runtime_error(
owner +
" declares dechirp_reference while dechirp_mode is 'none'.");
236 throw std::runtime_error(
owner +
" declares IF-chain fields while dechirp_mode is 'none'.");
242 if ((
if_chain.filter_bandwidth_hz.has_value() ||
if_chain.filter_transition_width_hz.has_value()) &&
243 !
if_chain.sample_rate_hz.has_value())
245 throw std::runtime_error(
owner +
" IF filter fields require if_sample_rate.");
247 if (
if_chain.sample_rate_hz.has_value())
252 throw std::runtime_error(
owner +
" if_sample_rate must not exceed the simulation sample rate.");
255 if (
if_chain.sample_rate_hz.has_value() &&
if_chain.filter_bandwidth_hz.has_value() &&
258 throw std::runtime_error(
owner +
" if_filter_bandwidth must be less than half if_sample_rate.");
263 const std::string&
owner,
const std::string_view context)
266 auto value =
object.at(
key_string).get<std::string>();
269 throw std::runtime_error(
owner +
" " + std::string(context) +
" has an empty " +
key_string +
".");
275 const std::string&
owner)
277 if (!
mode_json.contains(
"dechirp_reference") || !
mode_json.at(
"dechirp_reference").is_object())
279 throw std::runtime_error(
owner +
" enables dechirping but does not declare dechirp_reference.");
286 throw std::runtime_error(
owner +
" dechirp_reference requires source.");
299 throw std::runtime_error(
owner +
300 " attached dechirp_reference must not set transmitter_name or waveform_name.");
306 throw std::runtime_error(
owner +
" transmitter dechirp_reference requires transmitter_name only.");
314 throw std::runtime_error(
owner +
" custom dechirp_reference requires waveform_name only.");
320 throw std::runtime_error(
owner +
" dechirp_reference source must be attached, transmitter, or custom.");
328 const std::string&
owner)
339 throw std::runtime_error(
owner +
" fmcw_mode must be an object.");
342 {
"dechirp_mode",
"dechirp_reference",
"if_sample_rate",
"if_filter_bandwidth",
343 "if_filter_transition_width"});
374 nlohmann::json
mode_json = nlohmann::json::object();
383 if (
if_chain.sample_rate_hz.has_value())
387 if (
if_chain.filter_bandwidth_hz.has_value())
391 if (
if_chain.filter_transition_width_hz.has_value())
414 j = {{
"x",
v.
x}, {
"y",
v.
y}, {
"z",
v.
z}};
419 j.at(
"x").get_to(
v.
x);
420 j.at(
"y").get_to(
v.
y);
421 j.at(
"z").get_to(
v.
z);
426 j = {{
"time",
c.t}, {
"x",
c.pos.
x}, {
"y",
c.pos.y}, {
"altitude",
c.pos.z}};
431 j.at(
"time").get_to(
c.t);
432 j.at(
"x").get_to(
c.pos.x);
433 j.at(
"y").get_to(
c.pos.y);
434 j.at(
"altitude").get_to(
c.pos.z);
447 j.at(
"time").get_to(
rc.t);
459 void to_json(nlohmann::json&
j,
const Path&
p)
461 j = {{
"interpolation",
p.getType()}, {
"positionwaypoints",
p.getCoords()}};
467 for (
const auto waypoints =
j.at(
"positionwaypoints").get<std::vector<Coord>>();
const auto&
wp :
waypoints)
481 void to_json(nlohmann::json&
j,
const RotationPath&
p)
483 j[
"interpolation"] =
p.getType();
494 j[
"startelevation"] =
503 j[
"rotationwaypoints"] =
p.getCoords();
510 for (
const auto waypoints =
j.at(
"rotationwaypoints").get<std::vector<RotationCoord>>();
525 {
"name",
pt.getName()},
526 {
"frequency",
pt.getFrequency()},
527 {
"synconpulse",
pt.getSyncOnPulse()}};
529 if (
pt.getFreqOffset().has_value())
531 j[
"freq_offset"] =
pt.getFreqOffset().value();
533 if (
pt.getRandomFreqOffsetStdev().has_value())
535 j[
"random_freq_offset_stdev"] =
pt.getRandomFreqOffsetStdev().value();
537 if (
pt.getPhaseOffset().has_value())
539 j[
"phase_offset"] =
pt.getPhaseOffset().value();
541 if (
pt.getRandomPhaseOffsetStdev().has_value())
543 j[
"random_phase_offset_stdev"] =
pt.getRandomPhaseOffsetStdev().value();
546 std::vector<RealType>
alphas;
552 for (
size_t i = 0; i <
alphas.size(); ++i)
562 pt.setFrequency(
j.at(
"frequency").get<
RealType>());
563 if (
j.value(
"synconpulse",
false))
569 pt.clearSyncOnPulse();
572 if (
j.contains(
"freq_offset"))
574 pt.setFreqOffset(
j.at(
"freq_offset").get<
RealType>());
577 pt.clearFreqOffset();
578 if (
j.contains(
"random_freq_offset_stdev"))
580 pt.setRandomFreqOffsetStdev(
j.at(
"random_freq_offset_stdev").get<
RealType>());
583 pt.clearRandomFreqOffsetStdev();
584 if (
j.contains(
"phase_offset"))
586 pt.setPhaseOffset(
j.at(
"phase_offset").get<
RealType>());
589 pt.clearPhaseOffset();
590 if (
j.contains(
"random_phase_offset_stdev"))
592 pt.setRandomPhaseOffsetStdev(
j.at(
"random_phase_offset_stdev").get<
RealType>());
595 pt.clearRandomPhaseOffsetStdev();
597 pt.clearNoiseEntries();
598 if (
j.contains(
"noise_entries"))
600 for (
const auto&
entry :
j.at(
"noise_entries"))
613 {
"name",
rs.getName()},
614 {
"power",
rs.getPower()},
615 {
"carrier_frequency",
rs.getCarrier()}};
616 if (
dynamic_cast<const CwSignal*
>(
rs.getSignal()) !=
nullptr)
618 j[
"cw"] = nlohmann::json::object();
620 else if (
const auto* sfcw =
rs.getSteppedFrequencySignal(); sfcw !=
nullptr)
622 j[
"stepped_frequency"] = {{
"start_frequency_offset", sfcw->getStartFrequencyOffset()},
623 {
"step_size", sfcw->getStepSize()},
624 {
"step_count", sfcw->getStepCount()},
625 {
"dwell_time", sfcw->getDwellTime()},
626 {
"step_period", sfcw->getStepPeriod()}};
627 if (sfcw->getSweepCount().has_value())
629 j[
"stepped_frequency"][
"sweep_count"] = *sfcw->getSweepCount();
632 else if (
const auto* fmcw =
rs.getFmcwChirpSignal(); fmcw !=
nullptr)
635 {
"chirp_bandwidth", fmcw->getChirpBandwidth()},
636 {
"chirp_duration", fmcw->getChirpDuration()},
637 {
"chirp_period", fmcw->getChirpPeriod()}};
638 if (std::abs(fmcw->getStartFrequencyOffset()) >
EPSILON)
640 j[
"fmcw_linear_chirp"][
"start_frequency_offset"] = fmcw->getStartFrequencyOffset();
642 if (fmcw->getChirpCount().has_value())
644 j[
"fmcw_linear_chirp"][
"chirp_count"] = *fmcw->getChirpCount();
647 else if (
const auto* triangle =
rs.getFmcwTriangleSignal(); triangle !=
nullptr)
649 j[
"fmcw_triangle"] = {{
"chirp_bandwidth", triangle->getChirpBandwidth()},
650 {
"chirp_duration", triangle->getChirpDuration()}};
651 if (std::abs(triangle->getStartFrequencyOffset()) >
EPSILON)
653 j[
"fmcw_triangle"][
"start_frequency_offset"] = triangle->getStartFrequencyOffset();
655 if (triangle->getTriangleCount().has_value())
657 j[
"fmcw_triangle"][
"triangle_count"] = *triangle->getTriangleCount();
664 j[
"pulsed_from_file"] = {{
"filename", *
filename}};
668 throw std::logic_error(
"Attempted to serialize a file-based waveform named '" +
rs.getName() +
669 "' without a source filename.");
674 void from_json(
const nlohmann::json&
j, std::unique_ptr<RadarSignal>&
rs)
676 const auto name =
j.at(
"name").get<std::string>();
678 const auto power =
j.at(
"power").get<
RealType>();
681 if (
j.contains(
"cw"))
683 auto cw_signal = std::make_unique<CwSignal>();
687 else if (
j.contains(
"stepped_frequency"))
689 const auto&
sfcw_json =
j.at(
"stepped_frequency");
690 const auto step_count =
sfcw_json.at(
"step_count").get<
long long>();
693 throw std::runtime_error(
"Waveform '" + name +
"' has an invalid step_count.");
695 std::optional<std::size_t> sweep_count;
701 throw std::runtime_error(
"Waveform '" + name +
"' has an invalid sweep_count.");
705 auto sfcw_signal = std::make_unique<SteppedFrequencySignal>(
707 static_cast<std::size_t
>(step_count),
sfcw_json.at(
"dwell_time").get<
RealType>(),
713 else if (
j.contains(
"fmcw_linear_chirp"))
715 const auto&
fmcw_json =
j.at(
"fmcw_linear_chirp");
717 std::optional<std::size_t> chirp_count;
723 throw std::runtime_error(
"Waveform '" + name +
"' has an invalid chirp_count.");
728 auto fmcw_signal = std::make_unique<FmcwChirpSignal>(
736 else if (
j.contains(
"fmcw_triangle"))
738 const auto&
fmcw_json =
j.at(
"fmcw_triangle");
739 std::optional<std::size_t> triangle_count;
740 if (
fmcw_json.contains(
"triangle_count"))
745 throw std::runtime_error(
"Waveform '" + name +
"' has an invalid triangle_count.");
750 throw std::runtime_error(
"Waveform '" + name +
"' has an invalid triangle_count.");
752 triangle_count =
static_cast<std::size_t
>(
parsed_count);
755 auto fmcw_signal = std::make_unique<FmcwTriangleSignal>(
757 fmcw_json.value(
"start_frequency_offset", 0.0), triangle_count);
762 else if (
j.contains(
"pulsed_from_file"))
764 const auto& pulsed_file =
j.at(
"pulsed_from_file");
765 const auto filename = pulsed_file.value(
"filename",
"");
775 throw std::runtime_error(
"Unsupported waveform type in from_json for '" + name +
"'");
784 j = {{
"id",
sim_id_to_json(
a.getId())}, {
"name",
a.getName()}, {
"efficiency",
a.getEfficiencyFactor()}};
786 if (
const auto* sinc =
dynamic_cast<const Sinc*
>(&
a))
788 j[
"pattern"] =
"sinc";
789 j[
"alpha"] = sinc->getAlpha();
790 j[
"beta"] = sinc->getBeta();
791 j[
"gamma"] = sinc->getGamma();
795 j[
"pattern"] =
"gaussian";
796 j[
"azscale"] =
gaussian->getAzimuthScale();
797 j[
"elscale"] =
gaussian->getElevationScale();
799 else if (
const auto*
sh =
dynamic_cast<const SquareHorn*
>(&
a))
801 j[
"pattern"] =
"squarehorn";
802 j[
"diameter"] =
sh->getDimension();
806 j[
"pattern"] =
"parabolic";
811 j[
"pattern"] =
"xml";
812 j[
"filename"] =
xml->getFilename();
814 else if (
const auto*
h5 =
dynamic_cast<const H5Antenna*
>(&
a))
816 j[
"pattern"] =
"file";
817 j[
"filename"] =
h5->getFilename();
821 j[
"pattern"] =
"isotropic";
827 const auto name =
j.at(
"name").get<std::string>();
829 const auto pattern =
j.value(
"pattern",
"isotropic");
833 ant = std::make_unique<Isotropic>(name,
id);
840 else if (
pattern ==
"gaussian")
843 std::make_unique<Gaussian>(name,
j.at(
"azscale").get<
RealType>(),
j.at(
"elscale").get<
RealType>(),
id);
845 else if (
pattern ==
"squarehorn")
847 ant = std::make_unique<SquareHorn>(name,
j.at(
"diameter").get<
RealType>(),
id);
849 else if (
pattern ==
"parabolic")
851 ant = std::make_unique<Parabolic>(name,
j.at(
"diameter").get<
RealType>(),
id);
855 const auto filename =
j.value(
"filename",
"");
861 ant = std::make_unique<XmlAntenna>(name,
filename,
id);
865 const auto filename =
j.value(
"filename",
"");
871 ant = std::make_unique<H5Antenna>(name,
filename,
id);
875 throw std::runtime_error(
"Unsupported antenna pattern in from_json: " +
pattern);
878 ant->setEfficiencyFactor(
j.value(
"efficiency", 1.0));
886 j = {{
"start",
p.start}, {
"end",
p.end}};
891 j.at(
"start").get_to(
p.start);
892 j.at(
"end").get_to(
p.end);
905 j[
"pulsed_mode"] = {{
"prf", t.
getPrf()}};
909 j[
"fmcw_mode"] = nlohmann::json::object();
913 j[
"sfcw_mode"] = nlohmann::json::object();
917 j[
"cw_mode"] = nlohmann::json::object();
928 {
"name",
r.getName()},
929 {
"noise_temp",
r.getNoiseTemperature()},
930 {
"antenna",
sim_id_to_json((
r.getAntenna() !=
nullptr) ?
r.getAntenna()->getId() : 0)},
938 {
"prf",
r.getWindowPrf()}, {
"window_skip",
r.getWindowSkip()}, {
"window_length",
r.getWindowLength()}};
946 j[
"sfcw_mode"] = nlohmann::json::object();
950 j[
"cw_mode"] = nlohmann::json::object();
952 if (!
r.getSchedule().empty())
954 j[
"schedule"] =
r.getSchedule();
963 if (
const auto*
iso =
dynamic_cast<const IsoTarget*
>(&t))
994 j = {{
"id",
sim_id_to_json(
p.getId())}, {
"name",
p.getName()}, {
"motionpath", *
p.getMotionPath()}};
998 j[
"fixedrotation"] = *
p.getRotationPath();
1002 j[
"rotationpath"] = *
p.getRotationPath();
1017 void to_json(nlohmann::json&
j,
const Parameters&
p)
1019 j = nlohmann::json{{
"starttime",
p.start},
1023 {
"simSamplingRate",
p.sim_sampling_rate},
1024 {
"adc_bits",
p.adc_bits},
1025 {
"oversample",
p.oversample_ratio},
1026 {
"rotationangleunit",
p.rotation_angle_unit}};
1028 if (
p.random_seed.has_value())
1030 j[
"randomseed"] =
p.random_seed.value();
1034 {
"latitude",
p.origin_latitude}, {
"longitude",
p.origin_longitude}, {
"altitude",
p.origin_altitude}};
1036 j[
"coordinatesystem"] = {{
"frame",
p.coordinate_frame}};
1039 j[
"coordinatesystem"][
"zone"] =
p.utm_zone;
1040 j[
"coordinatesystem"][
"hemisphere"] =
p.utm_north_hemisphere ?
"N" :
"S";
1044 void from_json(
const nlohmann::json&
j, Parameters&
p)
1050 p.sim_sampling_rate =
j.value(
"simSamplingRate", 1000.0);
1051 p.adc_bits =
j.value(
"adc_bits", 0
u);
1052 p.oversample_ratio =
j.value(
"oversample", 1u);
1055 p.random_seed =
j.value<std::optional<unsigned>>(
"randomseed", std::nullopt);
1057 const auto&
origin =
j.at(
"origin");
1058 p.origin_latitude =
origin.at(
"latitude").get<
double>();
1059 p.origin_longitude =
origin.at(
"longitude").get<
double>();
1060 p.origin_altitude =
origin.at(
"altitude").get<
double>();
1062 const auto&
cs =
j.at(
"coordinatesystem");
1066 p.utm_zone =
cs.at(
"zone").get<
int>();
1067 p.utm_north_hemisphere =
cs.at(
"hemisphere").get<std::string>() ==
"N";
1089 {
"window_skip",
receiver.getWindowSkip()},
1090 {
"window_length",
receiver.getWindowLength()}};
1129 for (
const auto&
transmitter : world.getTransmitters())
1148 for (
const auto&
receiver : world.getReceivers())
1159 for (
const auto&
target : world.getTargets())
1172 plat_json[
"components"] = nlohmann::json::array();
1190 if (
sim.at(
"parameters").contains(
"randomseed"))
1208 if (
sim.contains(
"waveforms"))
1210 for (
auto waveforms =
sim.at(
"waveforms").get<std::vector<std::unique_ptr<fers_signal::RadarSignal>>>();
1211 auto& waveform : waveforms)
1216 world.
add(std::move(waveform));
1221 if (
sim.contains(
"antennas"))
1223 for (
auto antennas =
sim.at(
"antennas").get<std::vector<std::unique_ptr<antenna::Antenna>>>();
1234 if (
sim.contains(
"timings"))
1238 auto name =
timing_json.at(
"name").get<std::string>();
1256 const auto name =
element.at(
"name").get<std::string>();
1260 throw std::runtime_error(
"Duplicate name '" + name +
"' found for " + std::string(kind) +
1261 "; previously used by " +
iter->second +
".");
1266 const std::string_view
key,
const std::string_view kind)
1281 if (!
platform.contains(
"components") || !
platform.at(
"components").is_array())
1301 if (!
sim.contains(
"platforms"))
1326 return static_cast<std::size_t
>(
comp_json.contains(
"pulsed_mode")) +
1327 static_cast<std::size_t
>(
comp_json.contains(
"fmcw_mode")) +
1328 static_cast<std::size_t
>(
comp_json.contains(
"cw_mode")) +
1329 static_cast<std::size_t
>(
comp_json.contains(
"sfcw_mode"));
1338 " must have at most one of 'pulsed_mode', 'cw_mode', "
1339 "'fmcw_mode', or 'sfcw_mode'.");
1364 " must have a 'pulsed_mode', 'cw_mode', or 'fmcw_mode' block, or an 'sfcw_mode' "
1403 throw std::runtime_error(
"Transmitter component '" +
comp_json.value(
"name",
"Unnamed") +
1404 "' fmcw_mode must not contain dechirp configuration.");
1417 "Transmitter component '" +
comp_json.value(
"name",
"Unnamed") +
"'");
1418 trans->setWave(waveform);
1428 auto raw =
comp_json.at(
"schedule").get<std::vector<radar::SchedulePeriod>>();
1436 if (waveform->isFmcwFamily() || waveform->isSteppedFrequency())
1442 else if (waveform->isFmcwFamily() || waveform->isSteppedFrequency())
1487 recv->setNoiseTemperature(
comp_json.value(
"noise_temp", 0.0));
1500 if (
comp_json.value(
"nopropagationloss",
false))
1507 auto raw =
comp_json.at(
"schedule").get<std::vector<radar::SchedulePeriod>>();
1511 pri = 1.0 /
recv->getWindowPrf();
1518 "Receiver component '" +
comp_json.value(
"name",
"Unnamed") +
"'");
1552 throw std::runtime_error(
"Unsupported target RCS type: " +
rcs_type);
1564 auto model = std::make_unique<radar::RcsChiSquare>(world.
getTargets().back()->getRngEngine(),
1570 world.
getTargets().back()->setFluctuationModel(std::make_unique<radar::RcsConst>());
1574 throw std::runtime_error(
"Unsupported fluctuation model type: " +
model_type);
1625 "Monostatic component '" +
comp_json.value(
"name",
"Unnamed") +
"'");
1626 trans->setWave(waveform);
1640 recv->setWindowProperties(
mode_json.value(
"window_length", 0.0),
1644 recv->setNoiseTemperature(
comp_json.value(
"noise_temp", 0.0));
1656 if (
comp_json.value(
"nopropagationloss",
false))
1662 auto raw =
comp_json.at(
"schedule").get<std::vector<radar::SchedulePeriod>>();
1672 if (waveform->isFmcwFamily() || waveform->isSteppedFrequency())
1675 "Monostatic component '" +
comp_json.value(
"name",
"Unnamed") +
"'");
1681 else if (waveform->isFmcwFamily() || waveform->isSteppedFrequency())
1684 "Monostatic component '" +
comp_json.value(
"name",
"Unnamed") +
"'");
1691 "Monostatic component '" +
comp_json.value(
"name",
"Unnamed") +
"'");
1700 auto name =
plat_json.at(
"name").get<std::string>();
1702 auto plat = std::make_unique<radar::Platform>(name, platform_id);
1739 const auto&
sim =
j.at(
"simulation");
1745 if (
sim.contains(
"platforms"))
1763 std::unique_ptr<antenna::Antenna>
ant;
1770 std::unique_ptr<fers_signal::RadarSignal>
wf;
1777 auto timing = std::make_unique<timing::PrototypeTiming>(
j.at(
"name").get<std::string>(),
id);
1785 sim[
"parameters"] =
j;
1794 const auto name =
j.value(
"name", std::string{});
1795 const auto pattern =
j.value(
"pattern",
"isotropic");
1796 throw std::runtime_error(
"Cannot update antenna '" + name +
"' to pattern '" +
pattern +
1797 "' without a filename.");
1839 sinc->setAlpha(
j.value(
"alpha", 1.0));
1840 sinc->setBeta(
j.value(
"beta", 1.0));
1841 sinc->setGamma(
j.value(
"gamma", 2.0));
1845 gauss->setAzimuthScale(
j.value(
"azscale", 1.0));
1846 gauss->setElevationScale(
j.value(
"elscale", 1.0));
1850 horn->setDimension(
j.value(
"diameter", 0.5));
1854 para->setDiameter(
j.value(
"diameter", 0.5));
1858 if (
xml->getFilename() !=
j.value(
"filename",
""))
1865 if (
h5->getFilename() !=
j.value(
"filename",
""))
1874 const auto new_pattern =
j.value(
"pattern",
"isotropic");
1881 ant->setName(
j.at(
"name").get<std::string>());
1882 ant->setEfficiencyFactor(
j.value(
"efficiency", 1.0));
1888 if (
j.contains(
"motionpath"))
1890 auto path = std::make_unique<math::Path>();
1891 j.at(
"motionpath").get_to(*path);
1892 plat->setMotionPath(std::move(path));
1894 if (
j.contains(
"rotationpath"))
1896 auto rot_path = std::make_unique<math::RotationPath>();
1905 const std::string
owner =
1910 "JSON",
owner,
"azimuth");
1913 "JSON",
owner,
"elevation");
1922 else if (
j.contains(
"fixedrotation"))
1924 auto rot_path = std::make_unique<math::RotationPath>();
1930 const std::string
owner = std::format(
"platform '{}' fixedrotation",
plat->getName());
1934 owner,
"startazimuth");
1937 owner,
"startelevation");
1940 owner,
"azimuthrate");
1943 owner,
"elevationrate");
1949 rot_path->setConstantRate(start, rate);
1958 if (
j.contains(
"pulsed_mode"))
1961 tx.setPrf(
j.at(
"pulsed_mode").value(
"prf", 0.0));
1963 else if (
j.contains(
"fmcw_mode"))
1967 throw std::runtime_error(
"Transmitter '" +
tx.getName() +
1968 "' fmcw_mode must not contain dechirp configuration.");
1972 else if (
j.contains(
"sfcw_mode"))
1977 else if (
j.contains(
"cw_mode"))
1985 if (!
j.contains(
"waveform"))
1993 throw std::runtime_error(
"Waveform ID " + std::to_string(
id) +
" not found.");
2002 if (!
j.contains(
"antenna"))
2010 throw std::runtime_error(
"Antenna ID " + std::to_string(
id) +
" not found.");
2017 if (!
j.contains(
"timing"))
2025 throw std::runtime_error(
"Timing ID " + std::to_string(
timing_id) +
" not found.");
2027 unsigned const seed =
tx.getTiming() ?
tx.getTiming()->getSeed() : 0;
2035 if (
tx.getSignal() ==
nullptr)
2041 if (
tx.getSignal()->isFmcwFamily() ||
tx.getSignal()->isSteppedFrequency())
2048 const std::string&
owner)
2050 if (!
j.contains(
"schedule"))
2054 auto raw =
j.at(
"schedule").get<std::vector<radar::SchedulePeriod>>();
2058 if (
tx.getSignal() !=
nullptr && (
tx.getSignal()->isFmcwFamily() ||
tx.getSignal()->isSteppedFrequency()))
2068 if (
j.contains(
"name"))
2069 tx->setName(
j.at(
"name").get<std::string>());
2071 const std::string
owner =
"Transmitter '" +
tx->getName() +
"'";
2083 if (
j.contains(
"pulsed_mode"))
2090 else if (
j.contains(
"fmcw_mode"))
2094 else if (
j.contains(
"sfcw_mode"))
2099 else if (
j.contains(
"cw_mode"))
2107 if (
j.contains(
"noise_temp"))
2108 rx.setNoiseTemperature(
j.value(
"noise_temp", 0.0));
2110 if (
j.contains(
"nodirect"))
2112 if (
j.value(
"nodirect",
false))
2117 if (
j.contains(
"nopropagationloss"))
2119 if (
j.value(
"nopropagationloss",
false))
2128 if (!
j.contains(
"antenna"))
2136 throw std::runtime_error(
"Antenna ID " + std::to_string(
id) +
" not found.");
2143 if (!
j.contains(
"timing"))
2151 throw std::runtime_error(
"Timing ID " + std::to_string(
timing_id) +
" not found.");
2153 unsigned const seed =
rx.getTiming() ?
rx.getTiming()->getSeed() : 0;
2161 if (!
j.contains(
"schedule"))
2165 auto raw =
j.at(
"schedule").get<std::vector<radar::SchedulePeriod>>();
2167 const RealType pri = pulsed ? 1.0 /
rx.getWindowPrf() : 0.0;
2174 if (
j.contains(
"name"))
2175 rx->setName(
j.at(
"name").get<std::string>());
2182 if (
j.contains(
"fmcw_mode"))
2206 if (
j.contains(
"name"))
2207 rx.setName(
j.at(
"name").get<std::string>());
2208 rx.setMode(
tx.getMode());
2212 rx.setWindowProperties(
mode_json.value(
"window_length", 0.0),
tx.getPrf(),
2216 if (
j.contains(
"antenna"))
2225 if (!
j.contains(
"timing"))
2233 throw std::runtime_error(
"Timing ID " + std::to_string(
timing_id) +
" not found.");
2235 unsigned const seed =
rx.getTiming() ?
rx.getTiming()->getSeed() : 0;
2244 if (!
j.contains(
"schedule"))
2248 auto raw =
j.at(
"schedule").get<std::vector<radar::SchedulePeriod>>();
2252 if (
tx.getSignal() !=
nullptr && (
tx.getSignal()->isFmcwFamily() ||
tx.getSignal()->isSteppedFrequency()))
2270 if (
j.contains(
"fmcw_mode"))
2300 throw std::runtime_error(
"Unsupported target RCS type: " +
rcs_type);
2303 if (
j.contains(
"model"))
2315 target_obj->setFluctuationModel(std::make_unique<radar::RcsConst>());
2327 throw std::runtime_error(
"Timing ID " + std::to_string(
id) +
" not found.");
2331 if (!
patched.contains(
"name"))
2346 sim_json[
"waveforms"] = nlohmann::json::array();
2347 for (
const auto& waveform : world.
getWaveforms() | std::views::values)
2349 sim_json[
"waveforms"].push_back(*waveform);
2352 sim_json[
"antennas"] = nlohmann::json::array();
2358 sim_json[
"timings"] = nlohmann::json::array();
2364 sim_json[
"platforms"] = nlohmann::json::array();
Header file defining various types of antennas and their gain patterns.
const Transmitter & transmitter
const Receiver & receiver
Abstract base class representing an antenna.
SimId getId() const noexcept
Retrieves the unique ID of the antenna.
Represents a Gaussian-shaped antenna gain pattern.
Represents an antenna whose gain pattern is loaded from a HDF5 file.
Represents an isotropic antenna with uniform gain in all directions.
Represents a parabolic reflector antenna.
Represents a sinc function-based antenna gain pattern.
Represents a square horn antenna.
Represents an antenna whose gain pattern is defined by an XML file.
The World class manages the simulator environment.
void scheduleInitialEvents()
Populates the event queue with the initial events for the simulation.
void add(std::unique_ptr< radar::Platform > plat) noexcept
Adds a radar platform to the simulation world.
void replace(std::unique_ptr< radar::Target > target)
Replaces an existing target, updating internal pointers.
fers_signal::RadarSignal * findWaveform(const SimId id)
Finds a radar signal by ID.
const std::vector< std::unique_ptr< radar::Target > > & getTargets() const noexcept
Retrieves the list of radar targets.
const std::unordered_map< SimId, std::unique_ptr< antenna::Antenna > > & getAntennas() const noexcept
Retrieves the map of antennas.
void clear() noexcept
Clears all objects and assets from the simulation world.
void resolveReceiverDechirpReferences()
Resolves and validates receiver FMCW dechirp references after all components are loaded.
const std::unordered_map< SimId, std::unique_ptr< fers_signal::RadarSignal > > & getWaveforms() const noexcept
Retrieves the map of radar signals (waveforms).
timing::PrototypeTiming * findTiming(const SimId id)
Finds a timing source by ID.
antenna::Antenna * findAntenna(const SimId id)
Finds an antenna by ID.
const std::unordered_map< SimId, std::unique_ptr< timing::PrototypeTiming > > & getTimings() const noexcept
Retrieves the map of timing prototypes.
void swap(World &other) noexcept
Exchanges all owned world state with another world.
const std::vector< std::unique_ptr< radar::Platform > > & getPlatforms() const noexcept
Retrieves the list of platforms.
Continuous-wave signal implementation.
Class representing a radar signal with associated properties.
SimId getId() const noexcept
Gets the unique ID of the radar signal.
Represents a path with coordinates and allows for various interpolation methods.
InterpType
Types of interpolation supported by the Path class.
@ INTERP_STATIC
Hold the first coordinate for all query times.
@ INTERP_LINEAR
Linearly interpolate between neighboring coordinates.
@ INTERP_CUBIC
Cubically interpolate between neighboring coordinates.
Manages rotational paths with different interpolation techniques.
InterpType
Enumeration for types of interpolation.
@ INTERP_STATIC
Hold the first rotation for all query times.
@ INTERP_LINEAR
Linearly interpolate between neighboring rotations.
@ INTERP_CONSTANT
Hold the most recent rotation sample.
@ INTERP_CUBIC
Cubically interpolate between neighboring rotations.
A class representing a vector in rectangular coordinates.
RealType x
The x component of the vector.
RealType z
The z component of the vector.
RealType y
The y component of the vector.
const std::string & getName() const noexcept
Retrieves the name of the object.
const antenna::Antenna * getAntenna() const noexcept
Gets the antenna associated with this radar.
std::shared_ptr< timing::Timing > getTiming() const
Retrieves the timing source for the radar.
Chi-square distributed RCS model.
Manages radar signal reception and response processing.
@ Transmitter
Use a named transmitter.
@ Attached
Use the attached transmitter.
@ None
No reference configured.
@ Custom
Use a named top-level waveform with the receiver schedule.
DechirpMode
Receiver-side FMCW dechirping mode.
@ None
Output raw pre-mix streaming IQ.
@ FLAG_NODIRECT
Disable direct-path reception.
@ FLAG_NOPROPLOSS
Disable propagation-loss scaling.
Base class for radar targets.
SimId getId() const noexcept
Gets the unique ID of the target.
const RcsModel * getFluctuationModel() const
Gets the RCS fluctuation model.
Represents a radar transmitter system.
SimId getId() const noexcept
Retrieves the unique ID of the transmitter.
RealType getPrf() const noexcept
Retrieves the pulse repetition frequency (PRF).
fers_signal::RadarSignal * getSignal() const noexcept
Retrieves the radar signal currently being transmitted.
const std::vector< SchedulePeriod > & getSchedule() const noexcept
Retrieves the list of active transmission periods.
OperationMode getMode() const noexcept
Gets the operational mode of the transmitter.
Manages timing properties such as frequency, offsets, and synchronization.
double RealType
Type for real numbers.
constexpr RealType EPSILON
Machine epsilon for real numbers.
Coordinate and rotation structure operations.
Provides functions to serialize and deserialize the simulation world to/from JSON.
void to_json(nlohmann::json &j, const Antenna &a)
void from_json(const nlohmann::json &j, std::unique_ptr< Antenna > &ant)
FmcwChirpDirection parseFmcwChirpDirection(const std::string_view direction)
Parses a schema chirp direction token.
void from_json(const nlohmann::json &j, std::unique_ptr< RadarSignal > &rs)
void to_json(nlohmann::json &j, const RadarSignal &rs)
std::string_view fmcwChirpDirectionToken(const FmcwChirpDirection direction) noexcept
Converts a chirp direction to the schema token.
@ WARNING
Warning level for potentially harmful situations.
@ INFO
Info level for informational messages.
NLOHMANN_JSON_SERIALIZE_ENUM(Path::InterpType, {{Path::InterpType::INTERP_STATIC, "static"}, {Path::InterpType::INTERP_LINEAR, "linear"}, {Path::InterpType::INTERP_CUBIC, "cubic"}}) void to_json(nlohmann
void to_json(nlohmann::json &j, const Vec3 &v)
void from_json(const nlohmann::json &j, Vec3 &v)
RealType endTime() noexcept
Get the end time for the simulation.
RealType rate() noexcept
Get the rendering sample rate.
RealType startTime() noexcept
Get the start time for the simulation.
unsigned oversampleRatio() noexcept
Get the oversampling ratio.
NLOHMANN_JSON_SERIALIZE_ENUM(CoordinateFrame, {{CoordinateFrame::ENU, "ENU"}, {CoordinateFrame::UTM, "UTM"}, {CoordinateFrame::ECEF, "ECEF"}}) NLOHMANN_JSON_SERIALIZE_ENUM(RotationAngleUnit
CoordinateFrame
Defines the coordinate systems supported for KML/geospatial export.
@ UTM
Universal Transverse Mercator.
@ ENU
East-North-Up local tangent plane (default)
@ ECEF
Earth-Centered, Earth-Fixed.
RotationAngleUnit rotationAngleUnit() noexcept
Gets the external rotation angle unit.
void validateOversampleRatio(const unsigned ratio)
Validates that an oversampling ratio is supported.
RotationAngleUnit
Defines the units used at external rotation-path boundaries.
@ Radians
Compass azimuth and elevation expressed in radians.
@ Degrees
Compass azimuth and elevation expressed in degrees.
Parameters params
Global simulation parameter state.
std::string_view dechirpReferenceSourceToken(const Receiver::DechirpReferenceSource source) noexcept
Converts a dechirp reference source to its scenario token.
std::unique_ptr< Target > createIsoTarget(Platform *platform, std::string name, RealType rcs, unsigned seed, const SimId id=0)
Creates an isotropic target.
OperationMode
Defines the operational mode of a radar component.
@ SFCW_MODE
The component operates in a stepped-frequency CW streaming mode.
@ PULSED_MODE
The component operates in a pulsed mode.
@ CW_MODE
The component operates in a continuous-wave mode.
@ FMCW_MODE
The component operates in an FMCW streaming mode.
void to_json(nlohmann::json &j, const SchedulePeriod &p)
Receiver::DechirpReferenceSource parseDechirpReferenceSourceToken(const std::string_view token)
Parses a dechirp reference source scenario token.
std::string_view dechirpModeToken(const Receiver::DechirpMode mode) noexcept
Converts a dechirp mode to its scenario token.
void from_json(const nlohmann::json &j, SchedulePeriod &p)
std::unique_ptr< Target > createFileTarget(Platform *platform, std::string name, const std::string &filename, unsigned seed, const SimId id=0)
Creates a file-based target.
Receiver::DechirpMode parseDechirpModeToken(const std::string_view token)
Parses a dechirp mode scenario token.
std::vector< SchedulePeriod > processRawSchedule(const std::vector< SchedulePeriod > &periods, const std::string &ownerName, const bool isPulsed, const RealType pri)
Processes a raw list of schedule periods.
void validateWaveform(const fers_signal::RadarSignal &wave, const std::string &owner, const Thrower &throw_error)
Validates that a waveform is compatible with FMCW streaming constraints.
void validateSchedule(const std::vector< radar::SchedulePeriod > &schedule, const fers_signal::FmcwChirpSignal &fmcw, const std::string &owner, const Thrower &throw_error)
Validates that an FMCW waveform schedule can emit complete chirps.
void validateWaveformModeMatch(const fers_signal::RadarSignal &wave, const radar::OperationMode mode, const std::string &owner, const Thrower &throw_error)
Validates that a waveform and radar operation mode are compatible.
RealType internal_elevation_to_external(const RealType elevation, const params::RotationAngleUnit unit) noexcept
Converts an internal elevation angle to the external unit.
RealType internal_azimuth_rate_to_external(const RealType azimuth_rate, const params::RotationAngleUnit unit) noexcept
Converts an internal azimuth rate to the external compass convention.
math::RotationCoord external_rotation_to_internal(const RealType azimuth, const RealType elevation, const RealType time, const params::RotationAngleUnit unit) noexcept
Converts external compass azimuth/elevation into internal rotation coordinates.
RealType internal_elevation_rate_to_external(const RealType elevation_rate, const params::RotationAngleUnit unit) noexcept
Converts an internal elevation rate to the external unit.
RealType internal_azimuth_to_external(const RealType azimuth, const params::RotationAngleUnit unit) noexcept
Converts an internal azimuth angle to the external compass convention.
math::RotationCoord external_rotation_rate_to_internal(const RealType azimuth_rate, const RealType elevation_rate, const RealType time, const params::RotationAngleUnit unit) noexcept
Converts external compass azimuth/elevation rates into internal rotation rates.
void maybe_warn_about_rotation_value(const RealType value, const params::RotationAngleUnit declared_unit, const ValueKind kind, const std::string_view source, const std::string_view owner, const std::string_view field)
Emits or captures a warning when a rotation value likely uses the wrong unit.
@ Angle
Absolute rotation angle.
void update_platform_paths_from_json(const nlohmann::json &j, radar::Platform *plat)
Updates a platform's motion and rotation paths from JSON.
void update_parameters_from_json(const nlohmann::json &j, std::mt19937 &masterSeeder)
Updates global simulation parameters from JSON.
void update_existing_antenna_pattern_fields(const nlohmann::json &j, antenna::Antenna *ant, core::World &world)
void update_transmitter_waveform_from_json(const nlohmann::json &j, radar::Transmitter &tx, core::World &world)
std::unique_ptr< antenna::Antenna > parse_required_update_antenna(const nlohmann::json &j)
void update_receiver_mode_from_json(const nlohmann::json &j, radar::Receiver &rx)
void json_to_world(const nlohmann::json &j, core::World &world, std::mt19937 &masterSeeder)
Deserializes a nlohmann::json object and reconstructs the simulation world.
void update_receiver_from_json(const nlohmann::json &j, radar::Receiver *rx, core::World &world, std::mt19937 &)
Updates a receiver from JSON without full context recreation.
void update_receiver_schedule_from_json(const nlohmann::json &j, radar::Receiver &rx)
bool antenna_pattern_requires_replacement(const std::string_view pattern, const antenna::Antenna *ant) noexcept
void update_timing_from_json(const nlohmann::json &j, core::World &world, const SimId id)
Updates a timing source from JSON without full context recreation.
void update_transmitter_schedule_from_json(const nlohmann::json &j, radar::Transmitter &tx, const std::string &owner)
std::unique_ptr< RadarSignal > loadWaveformFromFile(const std::string &name, const std::string &filename, const RealType power, const RealType carrierFreq, const SimId id)
Loads a radar waveform from a file and returns a RadarSignal object.
void update_monostatic_from_json(const nlohmann::json &j, radar::Transmitter *tx, radar::Receiver *rx, core::World &world, std::mt19937 &masterSeeder)
Updates a monostatic radar from JSON without full context recreation.
void update_monostatic_receiver_basics(const nlohmann::json &j, const radar::Transmitter &tx, radar::Receiver &rx, core::World &world)
void update_transmitter_timing_from_json(const nlohmann::json &j, radar::Transmitter &tx, core::World &world)
void update_monostatic_schedule_from_json(const nlohmann::json &j, radar::Transmitter &tx, radar::Receiver &rx)
void update_receiver_noise_and_flags_from_json(const nlohmann::json &j, radar::Receiver &rx)
void update_transmitter_from_json(const nlohmann::json &j, radar::Transmitter *tx, core::World &world, std::mt19937 &)
Updates a transmitter from JSON without full context recreation.
void update_antenna_from_json(const nlohmann::json &j, antenna::Antenna *ant, core::World &world)
Updates an antenna from JSON without full context recreation.
std::unique_ptr< antenna::Antenna > parse_antenna_from_json(const nlohmann::json &j)
Parses an Antenna from JSON.
void update_target_from_json(const nlohmann::json &j, radar::Target *existing_tgt, core::World &world, std::mt19937 &)
Updates a target from JSON without full context recreation.
nlohmann::json world_to_json(const core::World &world)
Serializes the entire simulation world into a nlohmann::json object.
void update_receiver_timing_from_json(const nlohmann::json &j, radar::Receiver &rx, core::World &world)
void update_monostatic_timing_from_json(const nlohmann::json &j, radar::Transmitter &tx, radar::Receiver &rx, core::World &world)
std::unique_ptr< timing::PrototypeTiming > parse_timing_from_json(const nlohmann::json &j, const SimId id)
Parses a timing prototype from JSON.
void validate_transmitter_signal_state(const radar::Transmitter &tx, const std::string &owner)
void update_receiver_antenna_from_json(const nlohmann::json &j, radar::Receiver &rx, core::World &world)
nlohmann::json monostatic_transmitter_json(const nlohmann::json &j)
void update_transmitter_mode_from_json(const nlohmann::json &j, radar::Transmitter &tx)
void update_transmitter_antenna_from_json(const nlohmann::json &j, radar::Transmitter &tx, core::World &world)
std::unique_ptr< fers_signal::RadarSignal > parse_waveform_from_json(const nlohmann::json &j)
Parses a Waveform from JSON.
void from_json(const nlohmann::json &j, PrototypeTiming &pt)
void to_json(nlohmann::json &j, const PrototypeTiming &pt)
Defines the Parameters struct and provides methods for managing simulation parameters.
Provides the definition and functionality of the Path class for handling coordinate-based paths with ...
Header file for the PrototypeTiming class.
Classes for handling radar waveforms and signals.
Radar Receiver class for managing signal reception and response handling.
Defines the RotationPath class for handling rotational paths with different interpolation types.
uint64_t SimId
64-bit Unique Simulation ID.
Represents a position in 3D space with an associated time.
Represents a rotation in terms of azimuth, elevation, and time.
Struct to hold simulation parameters.
std::optional< unsigned > random_seed
Random seed for simulation.
std::string simulation_name
The name of the simulation, from the XML.
static constexpr RealType DEFAULT_C
Speed of light (m/s)
Parsed and resolved dechirp reference details.
Receiver-local FMCW IF-chain request parsed from scenario input.
Represents a time period during which the transmitter is active.
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.