20#include <nlohmann/json.hpp>
45 nlohmann::json sim_id_to_json(
const SimId id) {
return std::to_string(
id); }
47 SimId parse_json_id(
const nlohmann::json& j,
const std::string& key,
const std::string& owner)
51 throw std::runtime_error(
"Missing required '" + key +
"' for " + owner +
".");
55 if (j.at(key).is_number_unsigned())
57 return j.at(key).get<
SimId>();
59 if (j.at(key).is_number_integer())
61 const auto value = j.at(key).get<
long long>();
64 throw std::runtime_error(
"negative id");
66 return static_cast<SimId>(value);
68 if (j.at(key).is_string())
70 const auto str = j.at(key).get<std::string>();
72 const unsigned long long parsed = std::stoull(str, &idx, 10);
73 if (idx != str.size())
75 throw std::runtime_error(
"trailing characters");
77 return static_cast<SimId>(parsed);
80 catch (
const std::exception& e)
82 throw std::runtime_error(
"Invalid '" + key +
"' for " + owner +
": " + e.what());
84 throw std::runtime_error(
"Invalid '" + key +
"' type for " + owner +
".");
92 j = {{
"x", v.
x}, {
"y", v.
y}, {
"z", v.
z}};
97 j.at(
"x").get_to(v.
x);
98 j.at(
"y").get_to(v.
y);
99 j.at(
"z").get_to(v.
z);
104 j = {{
"time", c.t}, {
"x", c.pos.x}, {
"y", c.pos.y}, {
"altitude", c.pos.z}};
109 j.at(
"time").get_to(c.t);
110 j.at(
"x").get_to(c.pos.x);
111 j.at(
"y").get_to(c.pos.y);
112 j.at(
"altitude").get_to(c.pos.z);
125 j.at(
"time").get_to(rc.
t);
137 void to_json(nlohmann::json& j,
const Path& p)
139 j = {{
"interpolation", p.getType()}, {
"positionwaypoints", p.getCoords()}};
145 for (
const auto waypoints = j.at(
"positionwaypoints").get<std::vector<Coord>>();
const auto& wp : waypoints)
159 void to_json(nlohmann::json& j,
const RotationPath& p)
161 j[
"interpolation"] = p.getType();
172 j[
"startelevation"] =
181 j[
"rotationwaypoints"] = p.getCoords();
188 for (
const auto waypoints = j.at(
"rotationwaypoints").get<std::vector<RotationCoord>>();
189 const auto& wp : waypoints)
202 j = nlohmann::json{{
"id", sim_id_to_json(pt.
getId())},
224 std::vector<RealType> alphas;
225 std::vector<RealType> weights;
229 nlohmann::json noise_entries = nlohmann::json::array();
230 for (
size_t i = 0; i < alphas.size(); ++i)
232 noise_entries.push_back({{
"alpha", alphas[i]}, {
"weight", weights[i]}});
234 j[
"noise_entries"] = noise_entries;
241 if (j.value(
"synconpulse",
false))
250 if (j.contains(
"freq_offset"))
256 if (j.contains(
"random_freq_offset_stdev"))
262 if (j.contains(
"phase_offset"))
268 if (j.contains(
"random_phase_offset_stdev"))
276 if (j.contains(
"noise_entries"))
278 for (
const auto& entry : j.at(
"noise_entries"))
290 j = nlohmann::json{{
"id", sim_id_to_json(rs.
getId())},
296 j[
"cw"] = nlohmann::json::object();
300 if (
const auto& filename = rs.
getFilename(); filename.has_value())
302 j[
"pulsed_from_file"] = {{
"filename", *filename}};
306 throw std::logic_error(
"Attempted to serialize a file-based waveform named '" + rs.
getName() +
307 "' without a source filename.");
312 void from_json(
const nlohmann::json& j, std::unique_ptr<RadarSignal>& rs)
314 const auto name = j.at(
"name").get<std::string>();
315 const auto id = parse_json_id(j,
"id",
"waveform");
316 const auto power = j.at(
"power").get<
RealType>();
317 const auto carrier = j.at(
"carrier_frequency").get<
RealType>();
319 if (j.contains(
"cw"))
321 auto cw_signal = std::make_unique<CwSignal>();
323 std::move(cw_signal),
id);
325 else if (j.contains(
"pulsed_from_file"))
327 const auto& pulsed_file = j.at(
"pulsed_from_file");
328 const auto filename = pulsed_file.value(
"filename",
"");
329 if (filename.empty())
338 throw std::runtime_error(
"Unsupported waveform type in from_json for '" + name +
"'");
349 if (
const auto* sinc =
dynamic_cast<const Sinc*
>(&a))
351 j[
"pattern"] =
"sinc";
352 j[
"alpha"] = sinc->getAlpha();
353 j[
"beta"] = sinc->getBeta();
354 j[
"gamma"] = sinc->getGamma();
356 else if (
const auto* gaussian =
dynamic_cast<const Gaussian*
>(&a))
358 j[
"pattern"] =
"gaussian";
359 j[
"azscale"] = gaussian->getAzimuthScale();
360 j[
"elscale"] = gaussian->getElevationScale();
362 else if (
const auto* sh =
dynamic_cast<const SquareHorn*
>(&a))
364 j[
"pattern"] =
"squarehorn";
365 j[
"diameter"] = sh->getDimension();
367 else if (
const auto* parabolic =
dynamic_cast<const Parabolic*
>(&a))
369 j[
"pattern"] =
"parabolic";
370 j[
"diameter"] = parabolic->getDiameter();
372 else if (
const auto* xml =
dynamic_cast<const XmlAntenna*
>(&a))
374 j[
"pattern"] =
"xml";
375 j[
"filename"] = xml->getFilename();
377 else if (
const auto* h5 =
dynamic_cast<const H5Antenna*
>(&a))
379 j[
"pattern"] =
"file";
380 j[
"filename"] = h5->getFilename();
384 j[
"pattern"] =
"isotropic";
388 void from_json(
const nlohmann::json& j, std::unique_ptr<Antenna>& ant)
390 const auto name = j.at(
"name").get<std::string>();
391 const auto id = parse_json_id(j,
"id",
"Antenna");
392 const auto pattern = j.value(
"pattern",
"isotropic");
394 if (pattern ==
"isotropic")
396 ant = std::make_unique<Isotropic>(name,
id);
398 else if (pattern ==
"sinc")
400 ant = std::make_unique<Sinc>(name, j.at(
"alpha").get<
RealType>(), j.at(
"beta").get<
RealType>(),
403 else if (pattern ==
"gaussian")
406 std::make_unique<Gaussian>(name, j.at(
"azscale").get<
RealType>(), j.at(
"elscale").get<
RealType>(),
id);
408 else if (pattern ==
"squarehorn")
410 ant = std::make_unique<SquareHorn>(name, j.at(
"diameter").get<
RealType>(),
id);
412 else if (pattern ==
"parabolic")
414 ant = std::make_unique<Parabolic>(name, j.at(
"diameter").get<
RealType>(),
id);
416 else if (pattern ==
"xml")
418 const auto filename = j.value(
"filename",
"");
419 if (filename.empty())
424 ant = std::make_unique<XmlAntenna>(name, filename,
id);
426 else if (pattern ==
"file")
428 const auto filename = j.value(
"filename",
"");
429 if (filename.empty())
434 ant = std::make_unique<H5Antenna>(name, filename,
id);
438 throw std::runtime_error(
"Unsupported antenna pattern in from_json: " + pattern);
441 ant->setEfficiencyFactor(j.value(
"efficiency", 1.0));
449 j = {{
"start", p.
start}, {
"end", p.
end}};
454 j.at(
"start").get_to(p.
start);
455 j.at(
"end").get_to(p.
end);
460 j = nlohmann::json{{
"id", sim_id_to_json(t.
getId())},
468 j[
"pulsed_mode"] = {{
"prf", t.
getPrf()}};
472 j[
"cw_mode"] = nlohmann::json::object();
482 j = nlohmann::json{{
"id", sim_id_to_json(r.
getId())},
497 j[
"cw_mode"] = nlohmann::json::object();
507 j[
"id"] = sim_id_to_json(t.
getId());
509 nlohmann::json rcs_json;
510 if (
const auto* iso =
dynamic_cast<const IsoTarget*
>(&t))
512 rcs_json[
"type"] =
"isotropic";
513 rcs_json[
"value"] = iso->getConstRcs();
515 else if (
const auto* file =
dynamic_cast<const FileTarget*
>(&t))
517 rcs_json[
"type"] =
"file";
518 rcs_json[
"filename"] = file->getFilename();
525 nlohmann::json model_json;
526 if (
const auto* chi_model =
dynamic_cast<const RcsChiSquare*
>(model_base))
528 model_json[
"type"] =
"chisquare";
529 model_json[
"k"] = chi_model->getK();
533 model_json[
"type"] =
"constant";
535 j[
"model"] = model_json;
564 void to_json(nlohmann::json& j,
const Parameters& p)
566 j = nlohmann::json{{
"starttime", p.start},
570 {
"simSamplingRate", p.sim_sampling_rate},
571 {
"adc_bits", p.adc_bits},
572 {
"oversample", p.oversample_ratio},
573 {
"rotationangleunit", p.rotation_angle_unit}};
575 if (p.random_seed.has_value())
577 j[
"randomseed"] = p.random_seed.value();
581 {
"latitude", p.origin_latitude}, {
"longitude", p.origin_longitude}, {
"altitude", p.origin_altitude}};
583 j[
"coordinatesystem"] = {{
"frame", p.coordinate_frame}};
586 j[
"coordinatesystem"][
"zone"] = p.utm_zone;
587 j[
"coordinatesystem"][
"hemisphere"] = p.utm_north_hemisphere ?
"N" :
"S";
591 void from_json(
const nlohmann::json& j, Parameters& p)
593 p.start = j.at(
"starttime").get<
RealType>();
594 p.end = j.at(
"endtime").get<
RealType>();
595 p.rate = j.at(
"rate").get<
RealType>();
597 p.sim_sampling_rate = j.value(
"simSamplingRate", 1000.0);
598 p.adc_bits = j.value(
"adc_bits", 0u);
599 p.oversample_ratio = j.value(
"oversample", 1u);
601 p.random_seed = j.value<std::optional<unsigned>>(
"randomseed", std::nullopt);
603 const auto& origin = j.at(
"origin");
604 p.origin_latitude = origin.at(
"latitude").get<
double>();
605 p.origin_longitude = origin.at(
"longitude").get<
double>();
606 p.origin_altitude = origin.at(
"altitude").get<
double>();
608 const auto& cs = j.at(
"coordinatesystem");
612 p.utm_zone = cs.at(
"zone").get<
int>();
613 p.utm_north_hemisphere = cs.at(
"hemisphere").get<std::string>() ==
"N";
622 nlohmann::json plat_json = *p;
625 plat_json[
"components"] = nlohmann::json::array();
628 for (
const auto& t : world.getTransmitters())
630 if (t->getPlatform() == p)
632 if (t->getAttached() !=
nullptr)
634 nlohmann::json monostatic_comp;
635 monostatic_comp[
"name"] = t->getName();
636 monostatic_comp[
"tx_id"] = sim_id_to_json(t->getId());
637 monostatic_comp[
"rx_id"] = sim_id_to_json(t->getAttached()->getId());
638 monostatic_comp[
"waveform"] =
639 sim_id_to_json((t->getSignal() !=
nullptr) ? t->getSignal()->getId() : 0);
640 monostatic_comp[
"antenna"] =
641 sim_id_to_json((t->getAntenna() !=
nullptr) ? t->getAntenna()->getId() : 0);
642 monostatic_comp[
"timing"] = sim_id_to_json(t->getTiming() ? t->getTiming()->getId() : 0);
644 if (
const auto* recv =
dynamic_cast<const radar::Receiver*
>(t->getAttached()))
646 monostatic_comp[
"noise_temp"] = recv->getNoiseTemperature();
648 monostatic_comp[
"nopropagationloss"] =
651 if (!t->getSchedule().empty())
653 monostatic_comp[
"schedule"] = t->getSchedule();
658 monostatic_comp[
"pulsed_mode"] = {{
"prf", t->getPrf()},
659 {
"window_skip", recv->getWindowSkip()},
660 {
"window_length", recv->getWindowLength()}};
664 monostatic_comp[
"cw_mode"] = nlohmann::json::object();
667 plat_json[
"components"].push_back(nlohmann::json{{
"monostatic", monostatic_comp}});
671 plat_json[
"components"].push_back(nlohmann::json{{
"transmitter", *t}});
677 for (
const auto& r : world.getReceivers())
679 if (r->getPlatform() == p)
682 if (r->getAttached() ==
nullptr)
684 plat_json[
"components"].push_back(nlohmann::json{{
"receiver", *r}});
690 for (
const auto& target : world.getTargets())
692 if (target->getPlatform() == p)
694 plat_json[
"components"].push_back(nlohmann::json{{
"target", *target}});
701 void parse_parameters(
const nlohmann::json& sim, std::mt19937& masterSeeder)
708 if (sim.at(
"parameters").contains(
"randomseed"))
723 void parse_assets(
const nlohmann::json& sim,
core::World& world)
725 if (sim.contains(
"waveforms"))
727 for (
auto waveforms = sim.at(
"waveforms").get<std::vector<std::unique_ptr<fers_signal::RadarSignal>>>();
728 auto& waveform : waveforms)
733 world.
add(std::move(waveform));
738 if (sim.contains(
"antennas"))
740 for (
auto antennas = sim.at(
"antennas").get<std::vector<std::unique_ptr<antenna::Antenna>>>();
751 if (sim.contains(
"timings"))
753 for (
const auto& timing_json : sim.at(
"timings"))
755 auto name = timing_json.at(
"name").get<std::string>();
756 const auto timing_id = parse_json_id(timing_json,
"id",
"Timing");
757 auto timing_obj = std::make_unique<timing::PrototypeTiming>(name, timing_id);
758 timing_json.get_to(*timing_obj);
759 world.
add(std::move(timing_obj));
764 radar::OperationMode parse_mode(
const nlohmann::json& comp_json,
const std::string& error_context)
766 if (comp_json.contains(
"pulsed_mode"))
770 if (comp_json.contains(
"cw_mode"))
774 throw std::runtime_error(error_context +
" must have a 'pulsed_mode' or 'cw_mode' block.");
778 std::mt19937& masterSeeder)
782 const auto wave_id = parse_json_id(comp_json,
"waveform",
"Transmitter");
783 const auto timing_id = parse_json_id(comp_json,
"timing",
"Transmitter");
784 const auto antenna_id = parse_json_id(comp_json,
"antenna",
"Transmitter");
789 comp_json.value(
"name",
"Unnamed"), comp_json.value(
"waveform",
""));
795 comp_json.value(
"name",
"Unnamed"), comp_json.value(
"timing",
""));
801 comp_json.value(
"name",
"Unnamed"), comp_json.value(
"antenna",
""));
806 parse_mode(comp_json,
"Transmitter component '" + comp_json.value(
"name",
"Unnamed") +
"'");
808 const auto trans_id = parse_json_id(comp_json,
"id",
"Transmitter");
809 auto trans = std::make_unique<radar::Transmitter>(plat, comp_json.value(
"name",
"Unnamed"), mode, trans_id);
812 trans->setPrf(comp_json.at(
"pulsed_mode").value(
"prf", 0.0));
818 if (
auto*
const timing_proto = world.
findTiming(timing_id))
821 std::make_shared<timing::Timing>(timing_proto->getName(), masterSeeder(), timing_proto->getId());
822 timing->initializeModel(timing_proto);
826 if (comp_json.contains(
"schedule"))
828 auto raw = comp_json.at(
"schedule").get<std::vector<radar::SchedulePeriod>>();
832 pri = 1.0 / trans->getPrf();
838 world.
add(std::move(trans));
842 std::mt19937& masterSeeder)
846 const auto timing_id = parse_json_id(comp_json,
"timing",
"Receiver");
847 const auto antenna_id = parse_json_id(comp_json,
"antenna",
"Receiver");
852 comp_json.value(
"name",
"Unnamed"), comp_json.value(
"timing",
""));
859 comp_json.value(
"name",
"Unnamed"), comp_json.value(
"antenna",
""));
864 parse_mode(comp_json,
"Receiver component '" + comp_json.value(
"name",
"Unnamed") +
"'");
866 const auto recv_id = parse_json_id(comp_json,
"id",
"Receiver");
868 std::make_unique<radar::Receiver>(plat, comp_json.value(
"name",
"Unnamed"), masterSeeder(), mode, recv_id);
871 const auto& mode_json = comp_json.at(
"pulsed_mode");
872 recv->setWindowProperties(mode_json.value(
"window_length", 0.0), mode_json.value(
"prf", 0.0),
873 mode_json.value(
"window_skip", 0.0));
876 recv->setNoiseTemperature(comp_json.value(
"noise_temp", 0.0));
880 if (
auto*
const timing_proto = world.
findTiming(timing_id))
883 std::make_shared<timing::Timing>(timing_proto->getName(), masterSeeder(), timing_proto->getId());
884 timing->initializeModel(timing_proto);
888 if (comp_json.value(
"nodirect",
false))
892 if (comp_json.value(
"nopropagationloss",
false))
897 if (comp_json.contains(
"schedule"))
899 auto raw = comp_json.at(
"schedule").get<std::vector<radar::SchedulePeriod>>();
903 pri = 1.0 / recv->getWindowPrf();
909 world.
add(std::move(recv));
913 std::mt19937& masterSeeder)
915 const auto& rcs_json = comp_json.at(
"rcs");
916 const auto rcs_type = rcs_json.at(
"type").get<std::string>();
917 std::unique_ptr<radar::Target> target_obj;
919 if (rcs_type ==
"isotropic")
921 const auto target_id = parse_json_id(comp_json,
"id",
"Target");
923 rcs_json.at(
"value").get<
RealType>(),
924 static_cast<unsigned>(masterSeeder()), target_id);
926 else if (rcs_type ==
"file")
928 const auto filename = rcs_json.value(
"filename",
"");
929 if (filename.empty())
932 comp_json.value(
"name",
"Unknown"));
935 const auto target_id = parse_json_id(comp_json,
"id",
"Target");
937 static_cast<unsigned>(masterSeeder()), target_id);
941 throw std::runtime_error(
"Unsupported target RCS type: " + rcs_type);
943 world.
add(std::move(target_obj));
946 if (comp_json.contains(
"model"))
948 const auto& model_json = comp_json.at(
"model");
949 const auto model_type = model_json.at(
"type").get<std::string>();
951 if (model_type ==
"chisquare" || model_type ==
"gamma")
953 auto model = std::make_unique<radar::RcsChiSquare>(world.
getTargets().back()->getRngEngine(),
954 model_json.at(
"k").get<
RealType>());
955 world.
getTargets().back()->setFluctuationModel(std::move(model));
957 else if (model_type ==
"constant")
959 world.
getTargets().back()->setFluctuationModel(std::make_unique<radar::RcsConst>());
963 throw std::runtime_error(
"Unsupported fluctuation model type: " + model_type);
969 std::mt19937& masterSeeder)
975 const auto wave_id = parse_json_id(comp_json,
"waveform",
"Monostatic");
976 const auto timing_id = parse_json_id(comp_json,
"timing",
"Monostatic");
977 const auto antenna_id = parse_json_id(comp_json,
"antenna",
"Monostatic");
982 comp_json.value(
"name",
"Unnamed"), comp_json.value(
"waveform",
""));
988 comp_json.value(
"name",
"Unnamed"), comp_json.value(
"timing",
""));
994 comp_json.value(
"name",
"Unnamed"), comp_json.value(
"antenna",
""));
999 parse_mode(comp_json,
"Monostatic component '" + comp_json.value(
"name",
"Unnamed") +
"'");
1002 const auto tx_id = parse_json_id(comp_json,
"tx_id",
"Monostatic");
1003 auto trans = std::make_unique<radar::Transmitter>(plat, comp_json.value(
"name",
"Unnamed"), mode, tx_id);
1006 trans->setPrf(comp_json.at(
"pulsed_mode").value(
"prf", 0.0));
1011 auto*
const tx_timing_proto = world.
findTiming(timing_id);
1012 if (tx_timing_proto !=
nullptr)
1014 const auto tx_timing =
1015 std::make_shared<timing::Timing>(tx_timing_proto->getName(), masterSeeder(), tx_timing_proto->getId());
1016 tx_timing->initializeModel(tx_timing_proto);
1017 trans->setTiming(tx_timing);
1021 const auto rx_id = parse_json_id(comp_json,
"rx_id",
"Monostatic");
1023 std::make_unique<radar::Receiver>(plat, comp_json.value(
"name",
"Unnamed"), masterSeeder(), mode, rx_id);
1026 const auto& mode_json = comp_json.at(
"pulsed_mode");
1027 recv->setWindowProperties(mode_json.value(
"window_length", 0.0),
1029 mode_json.value(
"window_skip", 0.0));
1031 recv->setNoiseTemperature(comp_json.value(
"noise_temp", 0.0));
1034 auto*
const rx_timing_proto = world.
findTiming(timing_id);
1035 if (rx_timing_proto !=
nullptr)
1037 const auto rx_timing =
1038 std::make_shared<timing::Timing>(rx_timing_proto->getName(), masterSeeder(), rx_timing_proto->getId());
1039 rx_timing->initializeModel(rx_timing_proto);
1040 recv->setTiming(rx_timing);
1043 if (comp_json.value(
"nodirect",
false))
1047 if (comp_json.value(
"nopropagationloss",
false))
1051 if (comp_json.contains(
"schedule"))
1053 auto raw = comp_json.at(
"schedule").get<std::vector<radar::SchedulePeriod>>();
1057 pri = 1.0 / trans->getPrf();
1064 trans->setSchedule(processed_schedule);
1065 recv->setSchedule(processed_schedule);
1069 trans->setAttached(recv.get());
1070 recv->setAttached(trans.get());
1071 world.
add(std::move(trans));
1072 world.
add(std::move(recv));
1075 void parse_platform(
const nlohmann::json& plat_json,
core::World& world, std::mt19937& masterSeeder)
1077 auto name = plat_json.at(
"name").get<std::string>();
1078 const auto platform_id = parse_json_id(plat_json,
"id",
"Platform");
1079 auto plat = std::make_unique<radar::Platform>(name, platform_id);
1084 if (plat_json.contains(
"components"))
1086 for (
const auto& comp_json_outer : plat_json.at(
"components"))
1088 if (comp_json_outer.contains(
"transmitter"))
1090 parse_transmitter(comp_json_outer.at(
"transmitter"), plat.get(), world, masterSeeder);
1092 else if (comp_json_outer.contains(
"receiver"))
1094 parse_receiver(comp_json_outer.at(
"receiver"), plat.get(), world, masterSeeder);
1096 else if (comp_json_outer.contains(
"target"))
1098 parse_target(comp_json_outer.at(
"target"), plat.get(), world, masterSeeder);
1100 else if (comp_json_outer.contains(
"monostatic"))
1102 parse_monostatic(comp_json_outer.at(
"monostatic"), plat.get(), world, masterSeeder);
1107 world.
add(std::move(plat));
1115 std::unique_ptr<antenna::Antenna> ant;
1122 std::unique_ptr<fers_signal::RadarSignal> wf;
1129 auto timing = std::make_unique<timing::PrototypeTiming>(j.at(
"name").get<std::string>(),
id);
1137 sim[
"parameters"] = j;
1138 parse_parameters(sim, masterSeeder);
1143 auto new_pattern = j.value(
"pattern",
"isotropic");
1144 bool type_changed =
false;
1146 const auto parse_required_antenna = [&j]()
1149 if (parsed ==
nullptr)
1151 const auto name = j.value(
"name", std::string{});
1152 const auto pattern = j.value(
"pattern",
"isotropic");
1153 throw std::runtime_error(
"Cannot update antenna '" + name +
"' to pattern '" + pattern +
1154 "' without a filename.");
1159 if (new_pattern ==
"isotropic" && (
dynamic_cast<antenna::Isotropic*
>(ant) ==
nullptr))
1160 type_changed =
true;
1161 else if (new_pattern ==
"sinc" && (
dynamic_cast<antenna::Sinc*
>(ant) ==
nullptr))
1162 type_changed =
true;
1163 else if (new_pattern ==
"gaussian" && (
dynamic_cast<antenna::Gaussian*
>(ant) ==
nullptr))
1164 type_changed =
true;
1165 else if (new_pattern ==
"squarehorn" && (
dynamic_cast<antenna::SquareHorn*
>(ant) ==
nullptr))
1166 type_changed =
true;
1167 else if (new_pattern ==
"parabolic" && (
dynamic_cast<antenna::Parabolic*
>(ant) ==
nullptr))
1168 type_changed =
true;
1170 type_changed =
true;
1171 else if (new_pattern ==
"file" && (
dynamic_cast<antenna::H5Antenna*
>(ant) ==
nullptr))
1172 type_changed =
true;
1176 world.
replace(parse_required_antenna());
1180 ant->
setName(j.at(
"name").get<std::string>());
1185 sinc->setAlpha(j.value(
"alpha", 1.0));
1186 sinc->setBeta(j.value(
"beta", 1.0));
1187 sinc->setGamma(j.value(
"gamma", 2.0));
1191 gauss->setAzimuthScale(j.value(
"azscale", 1.0));
1192 gauss->setElevationScale(j.value(
"elscale", 1.0));
1196 horn->setDimension(j.value(
"diameter", 0.5));
1200 para->setDiameter(j.value(
"diameter", 0.5));
1204 if (xml->getFilename() != j.value(
"filename",
""))
1206 world.
replace(parse_required_antenna());
1211 if (h5->getFilename() != j.value(
"filename",
""))
1213 world.
replace(parse_required_antenna());
1220 if (j.contains(
"motionpath"))
1222 auto path = std::make_unique<math::Path>();
1223 j.at(
"motionpath").get_to(*path);
1226 if (j.contains(
"rotationpath"))
1228 auto rot_path = std::make_unique<math::RotationPath>();
1229 const auto& rotation_json = j.at(
"rotationpath");
1231 unsigned waypoint_index = 0;
1232 for (
const auto& waypoint_json : rotation_json.at(
"rotationwaypoints"))
1237 const std::string owner =
1238 std::format(
"platform '{}' rotation waypoint {}", plat->
getName(), waypoint_index);
1242 "JSON", owner,
"azimuth");
1245 "JSON", owner,
"elevation");
1251 rot_path->finalize();
1254 else if (j.contains(
"fixedrotation"))
1256 auto rot_path = std::make_unique<math::RotationPath>();
1257 const auto& fixed_json = j.at(
"fixedrotation");
1258 const RealType start_az_deg = fixed_json.at(
"startazimuth").get<
RealType>();
1259 const RealType start_el_deg = fixed_json.at(
"startelevation").get<
RealType>();
1260 const RealType rate_az_deg_s = fixed_json.at(
"azimuthrate").get<
RealType>();
1261 const RealType rate_el_deg_s = fixed_json.at(
"elevationrate").get<
RealType>();
1262 const std::string owner = std::format(
"platform '{}' fixedrotation", plat->
getName());
1266 owner,
"startazimuth");
1269 owner,
"startelevation");
1272 owner,
"azimuthrate");
1275 owner,
"elevationrate");
1281 rot_path->setConstantRate(start, rate);
1282 rot_path->finalize();
1290 if (j.contains(
"name"))
1291 tx->
setName(j.at(
"name").get<std::string>());
1293 if (j.contains(
"pulsed_mode"))
1296 tx->
setPrf(j.at(
"pulsed_mode").value(
"prf", 0.0));
1298 else if (j.contains(
"cw_mode"))
1303 if (j.contains(
"waveform"))
1305 auto id = parse_json_id(j,
"waveform",
"Transmitter");
1308 throw std::runtime_error(
"Waveform ID " + std::to_string(
id) +
" not found.");
1312 if (j.contains(
"antenna"))
1314 auto id = parse_json_id(j,
"antenna",
"Transmitter");
1317 throw std::runtime_error(
"Antenna ID " + std::to_string(
id) +
" not found.");
1321 if (j.contains(
"timing"))
1323 auto timing_id = parse_json_id(j,
"timing",
"Transmitter");
1324 if (
auto*
const timing_proto = world.
findTiming(timing_id))
1327 auto timing = std::make_shared<timing::Timing>(timing_proto->getName(), seed, timing_proto->getId());
1328 timing->initializeModel(timing_proto);
1333 throw std::runtime_error(
"Timing ID " + std::to_string(timing_id) +
" not found.");
1336 if (j.contains(
"schedule"))
1338 auto raw = j.at(
"schedule").get<std::vector<radar::SchedulePeriod>>();
1341 pri = 1.0 / tx->
getPrf();
1350 if (j.contains(
"name"))
1351 rx->
setName(j.at(
"name").get<std::string>());
1353 if (j.contains(
"pulsed_mode"))
1356 const auto& mode_json = j.at(
"pulsed_mode");
1357 rx->
setWindowProperties(mode_json.value(
"window_length", 0.0), mode_json.value(
"prf", 0.0),
1358 mode_json.value(
"window_skip", 0.0));
1360 else if (j.contains(
"cw_mode"))
1365 if (j.contains(
"noise_temp"))
1368 if (j.contains(
"nodirect"))
1370 if (j.value(
"nodirect",
false))
1375 if (j.contains(
"nopropagationloss"))
1377 if (j.value(
"nopropagationloss",
false))
1383 if (j.contains(
"antenna"))
1385 auto id = parse_json_id(j,
"antenna",
"Receiver");
1388 throw std::runtime_error(
"Antenna ID " + std::to_string(
id) +
" not found.");
1392 if (j.contains(
"timing"))
1394 auto timing_id = parse_json_id(j,
"timing",
"Receiver");
1395 if (
auto*
const timing_proto = world.
findTiming(timing_id))
1398 auto timing = std::make_shared<timing::Timing>(timing_proto->getName(), seed, timing_proto->getId());
1399 timing->initializeModel(timing_proto);
1404 throw std::runtime_error(
"Timing ID " + std::to_string(timing_id) +
" not found.");
1407 if (j.contains(
"schedule"))
1409 auto raw = j.at(
"schedule").get<std::vector<radar::SchedulePeriod>>();
1423 if (j.contains(
"name"))
1424 rx->
setName(j.at(
"name").get<std::string>());
1428 const auto& mode_json = j.at(
"pulsed_mode");
1430 mode_json.value(
"window_skip", 0.0));
1432 if (j.contains(
"noise_temp"))
1434 if (j.contains(
"nodirect"))
1436 if (j.value(
"nodirect",
false))
1441 if (j.contains(
"nopropagationloss"))
1443 if (j.value(
"nopropagationloss",
false))
1448 if (j.contains(
"antenna"))
1450 if (j.contains(
"timing"))
1452 auto timing_id = parse_json_id(j,
"timing",
"Monostatic");
1453 if (
auto*
const timing_proto = world.
findTiming(timing_id))
1456 auto rx_timing = std::make_shared<timing::Timing>(timing_proto->getName(), seed, timing_proto->getId());
1457 rx_timing->initializeModel(timing_proto);
1465 if (j.contains(
"schedule"))
1467 auto raw = j.at(
"schedule").get<std::vector<radar::SchedulePeriod>>();
1470 pri = 1.0 / tx->
getPrf();
1482 const auto& rcs_json = j.at(
"rcs");
1483 const auto rcs_type = rcs_json.at(
"type").get<std::string>();
1484 std::unique_ptr<radar::Target> target_obj;
1486 const auto target_id = existing_tgt->
getId();
1487 const auto name = j.value(
"name", existing_tgt->
getName());
1488 unsigned seed = existing_tgt->
getSeed();
1490 if (rcs_type ==
"isotropic")
1494 else if (rcs_type ==
"file")
1496 const auto filename = rcs_json.value(
"filename",
"");
1501 throw std::runtime_error(
"Unsupported target RCS type: " + rcs_type);
1504 if (j.contains(
"model"))
1506 const auto& model_json = j.at(
"model");
1507 const auto model_type = model_json.at(
"type").get<std::string>();
1508 if (model_type ==
"chisquare" || model_type ==
"gamma")
1511 std::make_unique<radar::RcsChiSquare>(target_obj->getRngEngine(), model_json.value(
"k", 1.0));
1512 target_obj->setFluctuationModel(std::move(model));
1514 else if (model_type ==
"constant")
1516 target_obj->setFluctuationModel(std::make_unique<radar::RcsConst>());
1520 world.
replace(std::move(target_obj));
1526 if (existing ==
nullptr)
1528 throw std::runtime_error(
"Timing ID " + std::to_string(
id) +
" not found.");
1532 if (!patched.contains(
"name"))
1534 patched[
"name"] = existing->getName();
1542 nlohmann::json sim_json;
1547 sim_json[
"waveforms"] = nlohmann::json::array();
1548 for (
const auto& waveform : world.
getWaveforms() | std::views::values)
1550 sim_json[
"waveforms"].push_back(*waveform);
1553 sim_json[
"antennas"] = nlohmann::json::array();
1556 sim_json[
"antennas"].push_back(*
antenna);
1559 sim_json[
"timings"] = nlohmann::json::array();
1562 sim_json[
"timings"].push_back(*
timing);
1565 sim_json[
"platforms"] = nlohmann::json::array();
1568 sim_json[
"platforms"].push_back(serialize_platform(p.get(), world));
1571 return {{
"simulation", sim_json}};
1580 const auto& sim = j.at(
"simulation");
1582 parse_parameters(sim, masterSeeder);
1583 parse_assets(sim, world);
1586 if (sim.contains(
"platforms"))
1588 for (
const auto& plat_json : sim.at(
"platforms"))
1590 parse_platform(plat_json, world, masterSeeder);
1600 const auto num_samples =
static_cast<size_t>(std::ceil((end_time - start_time) / dt_sim));
1606 receiver->prepareCwData(num_samples);
Header file defining various types of antennas and their gain patterns.
Abstract base class representing an antenna.
SimId getId() const noexcept
Retrieves the unique ID of the antenna.
void setEfficiencyFactor(RealType loss) noexcept
Sets the efficiency factor of the antenna.
RealType getEfficiencyFactor() const noexcept
Retrieves the efficiency factor of the antenna.
void setName(std::string name) noexcept
Sets the name of the antenna.
std::string getName() const noexcept
Retrieves the name 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.
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.
const std::vector< std::unique_ptr< radar::Platform > > & getPlatforms() const noexcept
Retrieves the list of platforms.
const std::vector< std::unique_ptr< radar::Receiver > > & getReceivers() const noexcept
Retrieves the list of radar receivers.
Class representing a radar signal with associated properties.
SimId getId() const noexcept
Gets the unique ID of the radar signal.
const std::optional< std::string > & getFilename() const noexcept
Gets the filename associated with this signal.
const std::string & getName() const noexcept
Gets the name of the radar signal.
RealType getCarrier() const noexcept
Gets the carrier frequency of the radar signal.
const Signal * getSignal() const noexcept
Gets the underlying signal object.
RealType getPower() const noexcept
Gets the power of the radar signal.
Represents a path with coordinates and allows for various interpolation methods.
void setInterp(InterpType settype) noexcept
Changes the interpolation type.
InterpType
Types of interpolation supported by the Path class.
void addCoord(const Coord &coord) noexcept
Adds a coordinate to the path.
void finalize()
Finalizes the path, preparing it for interpolation.
Manages rotational paths with different interpolation techniques.
void finalize()
Finalizes the rotation path for interpolation.
InterpType getType() const noexcept
Gets the interpolation type of the path.
void setInterp(InterpType setinterp) noexcept
Sets the interpolation type for the path.
InterpType
Enumeration for types of interpolation.
void addCoord(const RotationCoord &coord) noexcept
Adds a rotation coordinate to the path.
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.
Platform * getPlatform() const noexcept
Retrieves the associated platform of the object.
void setName(std::string name) noexcept
Sets the name of the object.
void setAntenna(const antenna::Antenna *ant)
Sets the antenna for the radar.
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.
void setTiming(const std::shared_ptr< timing::Timing > &tim)
Sets the timing source for the radar.
Chi-square distributed RCS model.
Manages radar signal reception and response processing.
void setMode(OperationMode mode) noexcept
Sets the operational mode of the receiver.
bool checkFlag(RecvFlag flag) const noexcept
Checks if a specific flag is set.
void setFlag(RecvFlag flag) noexcept
Sets a receiver flag.
const std::vector< SchedulePeriod > & getSchedule() const noexcept
Retrieves the list of active reception periods.
void clearFlag(RecvFlag flag) noexcept
Clears a receiver flag.
void setSchedule(std::vector< SchedulePeriod > schedule)
Sets the active schedule for the receiver.
SimId getId() const noexcept
Retrieves the unique ID of the receiver.
RealType getNoiseTemperature() const noexcept
Retrieves the noise temperature of the receiver.
OperationMode getMode() const noexcept
Gets the operational mode of the receiver.
RealType getWindowPrf() const noexcept
Retrieves the pulse repetition frequency (PRF) of the radar window.
RealType getWindowSkip() const noexcept
Retrieves the window skip time.
void setWindowProperties(RealType length, RealType prf, RealType skip) noexcept
Sets the properties for radar windows.
void setNoiseTemperature(RealType temp)
Sets the noise temperature of the receiver.
RealType getWindowLength() const noexcept
Retrieves the radar window length.
Base class for radar targets.
unsigned getSeed() const noexcept
Gets the initial seed used for the target's RNG.
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.
void setWave(fers_signal::RadarSignal *pulse) noexcept
Sets the radar signal wave to be transmitted.
void setMode(OperationMode mode) noexcept
Sets the operational mode of the transmitter.
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.
void setSchedule(std::vector< SchedulePeriod > schedule)
Sets the active schedule for the transmitter.
void setPrf(RealType mprf) noexcept
Sets the pulse repetition frequency (PRF) of the transmitter.
Manages timing properties such as frequency, offsets, and synchronization.
void clearNoiseEntries() noexcept
Clears all noise entries.
void setSyncOnPulse() noexcept
Enables synchronization on pulse.
void clearPhaseOffset() noexcept
Clears the phase offset.
void setAlpha(RealType alpha, RealType weight) noexcept
Sets an alpha and weight value.
void setRandomPhaseOffsetStdev(RealType stdev) noexcept
Sets a random phase offset standard deviation.
std::optional< RealType > getRandomPhaseOffsetStdev() const noexcept
bool getSyncOnPulse() const noexcept
Checks if synchronization on pulse is enabled.
void clearSyncOnPulse() noexcept
Disables synchronization on pulse.
std::string getName() const
Gets the name of the timing source.
void setFreqOffset(RealType offset) noexcept
Sets the frequency offset.
std::optional< RealType > getRandomFreqOffsetStdev() const noexcept
void setPhaseOffset(RealType offset) noexcept
Sets the phase offset.
void setRandomFreqOffsetStdev(RealType stdev) noexcept
Sets a random frequency offset standard deviation.
RealType getFrequency() const noexcept
Gets the current frequency.
void setFrequency(const RealType freq) noexcept
Sets the frequency value.
void clearRandomPhaseOffsetStdev() noexcept
Clears the random phase offset standard deviation.
std::optional< RealType > getPhaseOffset() const noexcept
Gets the phase offset.
void clearFreqOffset() noexcept
Clears the frequency offset.
void copyAlphas(std::vector< RealType > &alphas, std::vector< RealType > &weights) const noexcept
Copies the alphas and weights vectors.
SimId getId() const noexcept
Gets the unique ID of the timing source.
void clearRandomFreqOffsetStdev() noexcept
Clears the random frequency offset standard deviation.
std::optional< RealType > getFreqOffset() const noexcept
Gets the frequency offset.
double RealType
Type 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)
void from_json(const nlohmann::json &j, std::unique_ptr< RadarSignal > &rs)
void to_json(nlohmann::json &j, const RadarSignal &rs)
@ 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.
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.
CoordinateFrame
Defines the coordinate systems supported for scenario definition.
@ UTM
Universal Transverse Mercator.
@ ENU
East-North-Up local tangent plane (default)
@ ECEF
Earth-Centered, Earth-Fixed.
NLOHMANN_JSON_SERIALIZE_ENUM(CoordinateFrame, {{CoordinateFrame::ENU, "ENU"}, {CoordinateFrame::UTM, "UTM"}, {CoordinateFrame::ECEF, "ECEF"}}) NLOHMANN_JSON_SERIALIZE_ENUM(RotationAngleUnit
RotationAngleUnit rotationAngleUnit() noexcept
std::unique_ptr< Target > createIsoTarget(Platform *platform, std::string name, RealType rcs, unsigned seed, const SimId id=0)
Creates an isotropic target.
void to_json(nlohmann::json &j, const SchedulePeriod &p)
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.
std::vector< SchedulePeriod > processRawSchedule(std::vector< SchedulePeriod > periods, const std::string &ownerName, const bool isPulsed, const RealType pri)
Processes a raw list of schedule periods.
OperationMode
Defines the operational mode of a radar component.
@ PULSED_MODE
The component operates in a pulsed mode.
@ CW_MODE
The component operates in a continuous-wave mode.
RealType internal_elevation_to_external(const RealType elevation, const params::RotationAngleUnit unit) noexcept
RealType internal_azimuth_rate_to_external(const RealType azimuth_rate, const params::RotationAngleUnit unit) noexcept
math::RotationCoord external_rotation_to_internal(const RealType azimuth, const RealType elevation, const RealType time, const params::RotationAngleUnit unit) noexcept
RealType internal_elevation_rate_to_external(const RealType elevation_rate, const params::RotationAngleUnit unit) noexcept
RealType internal_azimuth_to_external(const RealType azimuth, const params::RotationAngleUnit unit) noexcept
math::RotationCoord external_rotation_rate_to_internal(const RealType azimuth_rate, const RealType elevation_rate, const RealType time, const params::RotationAngleUnit unit) noexcept
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)
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 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_timing_from_json(const nlohmann::json &j, core::World &world, const SimId id)
Updates a timing source from JSON without full context recreation.
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_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.
std::unique_ptr< timing::PrototypeTiming > parse_timing_from_json(const nlohmann::json &j, const SimId id)
Parses a timing prototype from JSON.
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.
RealType elevation
Elevation angle.
RealType azimuth
Azimuth angle.
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)
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.