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");
196 const std::string&
owner)
204 if (value <= 0.0 || !std::isfinite(value))
206 throw std::runtime_error(
owner +
" " +
key_string +
" must be a finite positive value.");
213 return if_chain.sample_rate_hz.has_value() ||
if_chain.filter_bandwidth_hz.has_value() ||
214 if_chain.filter_transition_width_hz.has_value();
219 const std::string&
owner)
221 if (
mode_json.contains(
"dechirp_reference"))
223 throw std::runtime_error(
owner +
" declares dechirp_reference while dechirp_mode is 'none'.");
227 throw std::runtime_error(
owner +
" declares IF-chain fields while dechirp_mode is 'none'.");
233 if ((
if_chain.filter_bandwidth_hz.has_value() ||
if_chain.filter_transition_width_hz.has_value()) &&
234 !
if_chain.sample_rate_hz.has_value())
236 throw std::runtime_error(
owner +
" IF filter fields require if_sample_rate.");
238 if (
if_chain.sample_rate_hz.has_value())
243 throw std::runtime_error(
owner +
" if_sample_rate must not exceed the simulation sample rate.");
246 if (
if_chain.sample_rate_hz.has_value() &&
if_chain.filter_bandwidth_hz.has_value() &&
249 throw std::runtime_error(
owner +
" if_filter_bandwidth must be less than half if_sample_rate.");
254 const std::string&
owner,
const std::string_view context)
257 auto value =
object.at(
key_string).get<std::string>();
260 throw std::runtime_error(
owner +
" " + std::string(context) +
" has an empty " +
key_string +
".");
266 const std::string&
owner)
268 if (!
mode_json.contains(
"dechirp_reference") || !
mode_json.at(
"dechirp_reference").is_object())
270 throw std::runtime_error(
owner +
" enables dechirping but does not declare dechirp_reference.");
277 throw std::runtime_error(
owner +
" dechirp_reference requires source.");
290 throw std::runtime_error(
owner +
291 " attached dechirp_reference must not set transmitter_name or waveform_name.");
297 throw std::runtime_error(
owner +
" transmitter dechirp_reference requires transmitter_name only.");
305 throw std::runtime_error(
owner +
" custom dechirp_reference requires waveform_name only.");
311 throw std::runtime_error(
owner +
" dechirp_reference source must be attached, transmitter, or custom.");
319 const std::string&
owner)
330 throw std::runtime_error(
owner +
" fmcw_mode must be an object.");
333 {
"dechirp_mode",
"dechirp_reference",
"if_sample_rate",
"if_filter_bandwidth",
334 "if_filter_transition_width"});
365 nlohmann::json
mode_json = nlohmann::json::object();
374 if (
if_chain.sample_rate_hz.has_value())
378 if (
if_chain.filter_bandwidth_hz.has_value())
382 if (
if_chain.filter_transition_width_hz.has_value())
405 j = {{
"x",
v.
x}, {
"y",
v.
y}, {
"z",
v.
z}};
410 j.at(
"x").get_to(
v.
x);
411 j.at(
"y").get_to(
v.
y);
412 j.at(
"z").get_to(
v.
z);
417 j = {{
"time",
c.t}, {
"x",
c.pos.
x}, {
"y",
c.pos.y}, {
"altitude",
c.pos.z}};
422 j.at(
"time").get_to(
c.t);
423 j.at(
"x").get_to(
c.pos.x);
424 j.at(
"y").get_to(
c.pos.y);
425 j.at(
"altitude").get_to(
c.pos.z);
438 j.at(
"time").get_to(
rc.t);
450 void to_json(nlohmann::json&
j,
const Path&
p)
452 j = {{
"interpolation",
p.getType()}, {
"positionwaypoints",
p.getCoords()}};
458 for (
const auto waypoints =
j.at(
"positionwaypoints").get<std::vector<Coord>>();
const auto&
wp :
waypoints)
472 void to_json(nlohmann::json&
j,
const RotationPath&
p)
474 j[
"interpolation"] =
p.getType();
485 j[
"startelevation"] =
494 j[
"rotationwaypoints"] =
p.getCoords();
501 for (
const auto waypoints =
j.at(
"rotationwaypoints").get<std::vector<RotationCoord>>();
516 {
"name",
pt.getName()},
517 {
"frequency",
pt.getFrequency()},
518 {
"synconpulse",
pt.getSyncOnPulse()}};
520 if (
pt.getFreqOffset().has_value())
522 j[
"freq_offset"] =
pt.getFreqOffset().value();
524 if (
pt.getRandomFreqOffsetStdev().has_value())
526 j[
"random_freq_offset_stdev"] =
pt.getRandomFreqOffsetStdev().value();
528 if (
pt.getPhaseOffset().has_value())
530 j[
"phase_offset"] =
pt.getPhaseOffset().value();
532 if (
pt.getRandomPhaseOffsetStdev().has_value())
534 j[
"random_phase_offset_stdev"] =
pt.getRandomPhaseOffsetStdev().value();
537 std::vector<RealType>
alphas;
543 for (
size_t i = 0; i <
alphas.size(); ++i)
553 pt.setFrequency(
j.at(
"frequency").get<
RealType>());
554 if (
j.value(
"synconpulse",
false))
560 pt.clearSyncOnPulse();
563 if (
j.contains(
"freq_offset"))
565 pt.setFreqOffset(
j.at(
"freq_offset").get<
RealType>());
568 pt.clearFreqOffset();
569 if (
j.contains(
"random_freq_offset_stdev"))
571 pt.setRandomFreqOffsetStdev(
j.at(
"random_freq_offset_stdev").get<
RealType>());
574 pt.clearRandomFreqOffsetStdev();
575 if (
j.contains(
"phase_offset"))
577 pt.setPhaseOffset(
j.at(
"phase_offset").get<
RealType>());
580 pt.clearPhaseOffset();
581 if (
j.contains(
"random_phase_offset_stdev"))
583 pt.setRandomPhaseOffsetStdev(
j.at(
"random_phase_offset_stdev").get<
RealType>());
586 pt.clearRandomPhaseOffsetStdev();
588 pt.clearNoiseEntries();
589 if (
j.contains(
"noise_entries"))
591 for (
const auto&
entry :
j.at(
"noise_entries"))
604 {
"name",
rs.getName()},
605 {
"power",
rs.getPower()},
606 {
"carrier_frequency",
rs.getCarrier()}};
607 if (
dynamic_cast<const CwSignal*
>(
rs.getSignal()) !=
nullptr)
609 j[
"cw"] = nlohmann::json::object();
611 else if (
const auto* fmcw =
rs.getFmcwChirpSignal(); fmcw !=
nullptr)
614 {
"chirp_bandwidth", fmcw->getChirpBandwidth()},
615 {
"chirp_duration", fmcw->getChirpDuration()},
616 {
"chirp_period", fmcw->getChirpPeriod()}};
617 if (std::abs(fmcw->getStartFrequencyOffset()) >
EPSILON)
619 j[
"fmcw_linear_chirp"][
"start_frequency_offset"] = fmcw->getStartFrequencyOffset();
621 if (fmcw->getChirpCount().has_value())
623 j[
"fmcw_linear_chirp"][
"chirp_count"] = *fmcw->getChirpCount();
626 else if (
const auto* triangle =
rs.getFmcwTriangleSignal(); triangle !=
nullptr)
628 j[
"fmcw_triangle"] = {{
"chirp_bandwidth", triangle->getChirpBandwidth()},
629 {
"chirp_duration", triangle->getChirpDuration()}};
630 if (std::abs(triangle->getStartFrequencyOffset()) >
EPSILON)
632 j[
"fmcw_triangle"][
"start_frequency_offset"] = triangle->getStartFrequencyOffset();
634 if (triangle->getTriangleCount().has_value())
636 j[
"fmcw_triangle"][
"triangle_count"] = *triangle->getTriangleCount();
643 j[
"pulsed_from_file"] = {{
"filename", *
filename}};
647 throw std::logic_error(
"Attempted to serialize a file-based waveform named '" +
rs.getName() +
648 "' without a source filename.");
653 void from_json(
const nlohmann::json&
j, std::unique_ptr<RadarSignal>&
rs)
655 const auto name =
j.at(
"name").get<std::string>();
657 const auto power =
j.at(
"power").get<
RealType>();
660 if (
j.contains(
"cw"))
662 auto cw_signal = std::make_unique<CwSignal>();
666 else if (
j.contains(
"fmcw_linear_chirp"))
668 const auto&
fmcw_json =
j.at(
"fmcw_linear_chirp");
670 std::optional<std::size_t> chirp_count;
676 throw std::runtime_error(
"Waveform '" + name +
"' has an invalid chirp_count.");
681 auto fmcw_signal = std::make_unique<FmcwChirpSignal>(
689 else if (
j.contains(
"fmcw_triangle"))
691 const auto&
fmcw_json =
j.at(
"fmcw_triangle");
692 std::optional<std::size_t> triangle_count;
693 if (
fmcw_json.contains(
"triangle_count"))
698 throw std::runtime_error(
"Waveform '" + name +
"' has an invalid triangle_count.");
703 throw std::runtime_error(
"Waveform '" + name +
"' has an invalid triangle_count.");
705 triangle_count =
static_cast<std::size_t
>(
parsed_count);
708 auto fmcw_signal = std::make_unique<FmcwTriangleSignal>(
710 fmcw_json.value(
"start_frequency_offset", 0.0), triangle_count);
715 else if (
j.contains(
"pulsed_from_file"))
717 const auto& pulsed_file =
j.at(
"pulsed_from_file");
718 const auto filename = pulsed_file.value(
"filename",
"");
728 throw std::runtime_error(
"Unsupported waveform type in from_json for '" + name +
"'");
737 j = {{
"id",
sim_id_to_json(
a.getId())}, {
"name",
a.getName()}, {
"efficiency",
a.getEfficiencyFactor()}};
739 if (
const auto* sinc =
dynamic_cast<const Sinc*
>(&
a))
741 j[
"pattern"] =
"sinc";
742 j[
"alpha"] = sinc->getAlpha();
743 j[
"beta"] = sinc->getBeta();
744 j[
"gamma"] = sinc->getGamma();
748 j[
"pattern"] =
"gaussian";
749 j[
"azscale"] =
gaussian->getAzimuthScale();
750 j[
"elscale"] =
gaussian->getElevationScale();
752 else if (
const auto*
sh =
dynamic_cast<const SquareHorn*
>(&
a))
754 j[
"pattern"] =
"squarehorn";
755 j[
"diameter"] =
sh->getDimension();
759 j[
"pattern"] =
"parabolic";
764 j[
"pattern"] =
"xml";
765 j[
"filename"] =
xml->getFilename();
767 else if (
const auto*
h5 =
dynamic_cast<const H5Antenna*
>(&
a))
769 j[
"pattern"] =
"file";
770 j[
"filename"] =
h5->getFilename();
774 j[
"pattern"] =
"isotropic";
780 const auto name =
j.at(
"name").get<std::string>();
782 const auto pattern =
j.value(
"pattern",
"isotropic");
786 ant = std::make_unique<Isotropic>(name,
id);
793 else if (
pattern ==
"gaussian")
796 std::make_unique<Gaussian>(name,
j.at(
"azscale").get<
RealType>(),
j.at(
"elscale").get<
RealType>(),
id);
798 else if (
pattern ==
"squarehorn")
800 ant = std::make_unique<SquareHorn>(name,
j.at(
"diameter").get<
RealType>(),
id);
802 else if (
pattern ==
"parabolic")
804 ant = std::make_unique<Parabolic>(name,
j.at(
"diameter").get<
RealType>(),
id);
808 const auto filename =
j.value(
"filename",
"");
814 ant = std::make_unique<XmlAntenna>(name,
filename,
id);
818 const auto filename =
j.value(
"filename",
"");
824 ant = std::make_unique<H5Antenna>(name,
filename,
id);
828 throw std::runtime_error(
"Unsupported antenna pattern in from_json: " +
pattern);
831 ant->setEfficiencyFactor(
j.value(
"efficiency", 1.0));
839 j = {{
"start",
p.start}, {
"end",
p.end}};
844 j.at(
"start").get_to(
p.start);
845 j.at(
"end").get_to(
p.end);
858 j[
"pulsed_mode"] = {{
"prf", t.
getPrf()}};
862 j[
"fmcw_mode"] = nlohmann::json::object();
866 j[
"cw_mode"] = nlohmann::json::object();
877 {
"name",
r.getName()},
878 {
"noise_temp",
r.getNoiseTemperature()},
879 {
"antenna",
sim_id_to_json((
r.getAntenna() !=
nullptr) ?
r.getAntenna()->getId() : 0)},
887 {
"prf",
r.getWindowPrf()}, {
"window_skip",
r.getWindowSkip()}, {
"window_length",
r.getWindowLength()}};
895 j[
"cw_mode"] = nlohmann::json::object();
897 if (!
r.getSchedule().empty())
899 j[
"schedule"] =
r.getSchedule();
908 if (
const auto*
iso =
dynamic_cast<const IsoTarget*
>(&t))
939 j = {{
"id",
sim_id_to_json(
p.getId())}, {
"name",
p.getName()}, {
"motionpath", *
p.getMotionPath()}};
943 j[
"fixedrotation"] = *
p.getRotationPath();
947 j[
"rotationpath"] = *
p.getRotationPath();
962 void to_json(nlohmann::json&
j,
const Parameters&
p)
964 j = nlohmann::json{{
"starttime",
p.start},
968 {
"simSamplingRate",
p.sim_sampling_rate},
969 {
"adc_bits",
p.adc_bits},
970 {
"oversample",
p.oversample_ratio},
971 {
"rotationangleunit",
p.rotation_angle_unit}};
973 if (
p.random_seed.has_value())
975 j[
"randomseed"] =
p.random_seed.value();
979 {
"latitude",
p.origin_latitude}, {
"longitude",
p.origin_longitude}, {
"altitude",
p.origin_altitude}};
981 j[
"coordinatesystem"] = {{
"frame",
p.coordinate_frame}};
984 j[
"coordinatesystem"][
"zone"] =
p.utm_zone;
985 j[
"coordinatesystem"][
"hemisphere"] =
p.utm_north_hemisphere ?
"N" :
"S";
989 void from_json(
const nlohmann::json&
j, Parameters&
p)
995 p.sim_sampling_rate =
j.value(
"simSamplingRate", 1000.0);
996 p.adc_bits =
j.value(
"adc_bits", 0
u);
997 p.oversample_ratio =
j.value(
"oversample", 1u);
1000 p.random_seed =
j.value<std::optional<unsigned>>(
"randomseed", std::nullopt);
1002 const auto&
origin =
j.at(
"origin");
1003 p.origin_latitude =
origin.at(
"latitude").get<
double>();
1004 p.origin_longitude =
origin.at(
"longitude").get<
double>();
1005 p.origin_altitude =
origin.at(
"altitude").get<
double>();
1007 const auto&
cs =
j.at(
"coordinatesystem");
1011 p.utm_zone =
cs.at(
"zone").get<
int>();
1012 p.utm_north_hemisphere =
cs.at(
"hemisphere").get<std::string>() ==
"N";
1034 {
"window_skip",
receiver.getWindowSkip()},
1035 {
"window_length",
receiver.getWindowLength()}};
1070 for (
const auto&
transmitter : world.getTransmitters())
1089 for (
const auto&
receiver : world.getReceivers())
1100 for (
const auto&
target : world.getTargets())
1113 plat_json[
"components"] = nlohmann::json::array();
1131 if (
sim.at(
"parameters").contains(
"randomseed"))
1149 if (
sim.contains(
"waveforms"))
1151 for (
auto waveforms =
sim.at(
"waveforms").get<std::vector<std::unique_ptr<fers_signal::RadarSignal>>>();
1152 auto& waveform : waveforms)
1157 world.
add(std::move(waveform));
1162 if (
sim.contains(
"antennas"))
1164 for (
auto antennas =
sim.at(
"antennas").get<std::vector<std::unique_ptr<antenna::Antenna>>>();
1175 if (
sim.contains(
"timings"))
1179 auto name =
timing_json.at(
"name").get<std::string>();
1197 const auto name =
element.at(
"name").get<std::string>();
1201 throw std::runtime_error(
"Duplicate name '" + name +
"' found for " + std::string(kind) +
1202 "; previously used by " +
iter->second +
".");
1207 const std::string_view
key,
const std::string_view kind)
1222 if (!
platform.contains(
"components") || !
platform.at(
"components").is_array())
1242 if (!
sim.contains(
"platforms"))
1267 return static_cast<std::size_t
>(
comp_json.contains(
"pulsed_mode")) +
1268 static_cast<std::size_t
>(
comp_json.contains(
"fmcw_mode")) +
1269 static_cast<std::size_t
>(
comp_json.contains(
"cw_mode"));
1278 " must have at most one of 'pulsed_mode', 'cw_mode', or 'fmcw_mode'.");
1298 throw std::runtime_error(
error_context +
" must have a 'pulsed_mode', 'cw_mode', or 'fmcw_mode' block.");
1335 throw std::runtime_error(
"Transmitter component '" +
comp_json.value(
"name",
"Unnamed") +
1336 "' fmcw_mode must not contain dechirp configuration.");
1349 "Transmitter component '" +
comp_json.value(
"name",
"Unnamed") +
"'");
1350 trans->setWave(waveform);
1360 auto raw =
comp_json.at(
"schedule").get<std::vector<radar::SchedulePeriod>>();
1368 if (waveform->isFmcwFamily())
1374 else if (waveform->isFmcwFamily())
1418 recv->setNoiseTemperature(
comp_json.value(
"noise_temp", 0.0));
1431 if (
comp_json.value(
"nopropagationloss",
false))
1438 auto raw =
comp_json.at(
"schedule").get<std::vector<radar::SchedulePeriod>>();
1442 pri = 1.0 /
recv->getWindowPrf();
1449 "Receiver component '" +
comp_json.value(
"name",
"Unnamed") +
"'");
1483 throw std::runtime_error(
"Unsupported target RCS type: " +
rcs_type);
1495 auto model = std::make_unique<radar::RcsChiSquare>(world.
getTargets().back()->getRngEngine(),
1501 world.
getTargets().back()->setFluctuationModel(std::make_unique<radar::RcsConst>());
1505 throw std::runtime_error(
"Unsupported fluctuation model type: " +
model_type);
1555 "Monostatic component '" +
comp_json.value(
"name",
"Unnamed") +
"'");
1556 trans->setWave(waveform);
1570 recv->setWindowProperties(
mode_json.value(
"window_length", 0.0),
1574 recv->setNoiseTemperature(
comp_json.value(
"noise_temp", 0.0));
1586 if (
comp_json.value(
"nopropagationloss",
false))
1592 auto raw =
comp_json.at(
"schedule").get<std::vector<radar::SchedulePeriod>>();
1602 if (waveform->isFmcwFamily())
1605 "Monostatic component '" +
comp_json.value(
"name",
"Unnamed") +
"'");
1611 else if (waveform->isFmcwFamily())
1614 "Monostatic component '" +
comp_json.value(
"name",
"Unnamed") +
"'");
1621 "Monostatic component '" +
comp_json.value(
"name",
"Unnamed") +
"'");
1630 auto name =
plat_json.at(
"name").get<std::string>();
1632 auto plat = std::make_unique<radar::Platform>(name, platform_id);
1669 const auto&
sim =
j.at(
"simulation");
1675 if (
sim.contains(
"platforms"))
1693 std::unique_ptr<antenna::Antenna>
ant;
1700 std::unique_ptr<fers_signal::RadarSignal>
wf;
1707 auto timing = std::make_unique<timing::PrototypeTiming>(
j.at(
"name").get<std::string>(),
id);
1715 sim[
"parameters"] =
j;
1724 const auto name =
j.value(
"name", std::string{});
1725 const auto pattern =
j.value(
"pattern",
"isotropic");
1726 throw std::runtime_error(
"Cannot update antenna '" + name +
"' to pattern '" +
pattern +
1727 "' without a filename.");
1769 sinc->setAlpha(
j.value(
"alpha", 1.0));
1770 sinc->setBeta(
j.value(
"beta", 1.0));
1771 sinc->setGamma(
j.value(
"gamma", 2.0));
1775 gauss->setAzimuthScale(
j.value(
"azscale", 1.0));
1776 gauss->setElevationScale(
j.value(
"elscale", 1.0));
1780 horn->setDimension(
j.value(
"diameter", 0.5));
1784 para->setDiameter(
j.value(
"diameter", 0.5));
1788 if (
xml->getFilename() !=
j.value(
"filename",
""))
1795 if (
h5->getFilename() !=
j.value(
"filename",
""))
1804 const auto new_pattern =
j.value(
"pattern",
"isotropic");
1811 ant->setName(
j.at(
"name").get<std::string>());
1812 ant->setEfficiencyFactor(
j.value(
"efficiency", 1.0));
1818 if (
j.contains(
"motionpath"))
1820 auto path = std::make_unique<math::Path>();
1821 j.at(
"motionpath").get_to(*path);
1822 plat->setMotionPath(std::move(path));
1824 if (
j.contains(
"rotationpath"))
1826 auto rot_path = std::make_unique<math::RotationPath>();
1835 const std::string
owner =
1840 "JSON",
owner,
"azimuth");
1843 "JSON",
owner,
"elevation");
1852 else if (
j.contains(
"fixedrotation"))
1854 auto rot_path = std::make_unique<math::RotationPath>();
1860 const std::string
owner = std::format(
"platform '{}' fixedrotation",
plat->getName());
1864 owner,
"startazimuth");
1867 owner,
"startelevation");
1870 owner,
"azimuthrate");
1873 owner,
"elevationrate");
1879 rot_path->setConstantRate(start, rate);
1888 if (
j.contains(
"pulsed_mode"))
1891 tx.setPrf(
j.at(
"pulsed_mode").value(
"prf", 0.0));
1893 else if (
j.contains(
"fmcw_mode"))
1897 throw std::runtime_error(
"Transmitter '" +
tx.getName() +
1898 "' fmcw_mode must not contain dechirp configuration.");
1902 else if (
j.contains(
"cw_mode"))
1910 if (!
j.contains(
"waveform"))
1918 throw std::runtime_error(
"Waveform ID " + std::to_string(
id) +
" not found.");
1927 if (!
j.contains(
"antenna"))
1935 throw std::runtime_error(
"Antenna ID " + std::to_string(
id) +
" not found.");
1942 if (!
j.contains(
"timing"))
1950 throw std::runtime_error(
"Timing ID " + std::to_string(
timing_id) +
" not found.");
1952 unsigned const seed =
tx.getTiming() ?
tx.getTiming()->getSeed() : 0;
1960 if (
tx.getSignal() ==
nullptr)
1966 if (
tx.getSignal()->isFmcwFamily())
1973 const std::string&
owner)
1975 if (!
j.contains(
"schedule"))
1979 auto raw =
j.at(
"schedule").get<std::vector<radar::SchedulePeriod>>();
1983 if (
tx.getSignal() !=
nullptr &&
tx.getSignal()->isFmcwFamily())
1993 if (
j.contains(
"name"))
1994 tx->setName(
j.at(
"name").get<std::string>());
1996 const std::string
owner =
"Transmitter '" +
tx->getName() +
"'";
2008 if (
j.contains(
"pulsed_mode"))
2015 else if (
j.contains(
"fmcw_mode"))
2019 else if (
j.contains(
"cw_mode"))
2027 if (
j.contains(
"noise_temp"))
2028 rx.setNoiseTemperature(
j.value(
"noise_temp", 0.0));
2030 if (
j.contains(
"nodirect"))
2032 if (
j.value(
"nodirect",
false))
2037 if (
j.contains(
"nopropagationloss"))
2039 if (
j.value(
"nopropagationloss",
false))
2048 if (!
j.contains(
"antenna"))
2056 throw std::runtime_error(
"Antenna ID " + std::to_string(
id) +
" not found.");
2063 if (!
j.contains(
"timing"))
2071 throw std::runtime_error(
"Timing ID " + std::to_string(
timing_id) +
" not found.");
2073 unsigned const seed =
rx.getTiming() ?
rx.getTiming()->getSeed() : 0;
2081 if (!
j.contains(
"schedule"))
2085 auto raw =
j.at(
"schedule").get<std::vector<radar::SchedulePeriod>>();
2087 const RealType pri = pulsed ? 1.0 /
rx.getWindowPrf() : 0.0;
2094 if (
j.contains(
"name"))
2095 rx->setName(
j.at(
"name").get<std::string>());
2102 if (
j.contains(
"fmcw_mode"))
2126 if (
j.contains(
"name"))
2127 rx.setName(
j.at(
"name").get<std::string>());
2128 rx.setMode(
tx.getMode());
2132 rx.setWindowProperties(
mode_json.value(
"window_length", 0.0),
tx.getPrf(),
2136 if (
j.contains(
"antenna"))
2145 if (!
j.contains(
"timing"))
2153 throw std::runtime_error(
"Timing ID " + std::to_string(
timing_id) +
" not found.");
2155 unsigned const seed =
rx.getTiming() ?
rx.getTiming()->getSeed() : 0;
2164 if (!
j.contains(
"schedule"))
2168 auto raw =
j.at(
"schedule").get<std::vector<radar::SchedulePeriod>>();
2172 if (
tx.getSignal() !=
nullptr &&
tx.getSignal()->isFmcwFamily())
2190 if (
j.contains(
"fmcw_mode"))
2220 throw std::runtime_error(
"Unsupported target RCS type: " +
rcs_type);
2223 if (
j.contains(
"model"))
2235 target_obj->setFluctuationModel(std::make_unique<radar::RcsConst>());
2247 throw std::runtime_error(
"Timing ID " + std::to_string(
id) +
" not found.");
2251 if (!
patched.contains(
"name"))
2266 sim_json[
"waveforms"] = nlohmann::json::array();
2267 for (
const auto& waveform : world.
getWaveforms() | std::views::values)
2269 sim_json[
"waveforms"].push_back(*waveform);
2272 sim_json[
"antennas"] = nlohmann::json::array();
2278 sim_json[
"timings"] = nlohmann::json::array();
2284 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.
@ 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.