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 (
const auto* file =
rs.getFileSignal(); file !=
nullptr)
618 std::string_view
key =
"pulsed_from_file";
621 key =
"cw_from_file";
625 key =
"fmcw_from_file";
633 throw std::logic_error(
"Attempted to serialize a file-based waveform named '" +
rs.getName() +
634 "' without a source filename.");
637 else if (
dynamic_cast<const CwSignal*
>(
rs.getSignal()) !=
nullptr)
639 j[
"cw"] = nlohmann::json::object();
641 else if (
const auto* sfcw =
rs.getSteppedFrequencySignal(); sfcw !=
nullptr)
643 j[
"stepped_frequency"] = {{
"start_frequency_offset", sfcw->getStartFrequencyOffset()},
644 {
"step_size", sfcw->getStepSize()},
645 {
"step_count", sfcw->getStepCount()},
646 {
"dwell_time", sfcw->getDwellTime()},
647 {
"step_period", sfcw->getStepPeriod()}};
648 if (sfcw->getSweepCount().has_value())
650 j[
"stepped_frequency"][
"sweep_count"] = *sfcw->getSweepCount();
653 else if (
const auto* fmcw =
rs.getFmcwChirpSignal(); fmcw !=
nullptr)
656 {
"chirp_bandwidth", fmcw->getChirpBandwidth()},
657 {
"chirp_duration", fmcw->getChirpDuration()},
658 {
"chirp_period", fmcw->getChirpPeriod()}};
659 if (std::abs(fmcw->getStartFrequencyOffset()) >
EPSILON)
661 j[
"fmcw_linear_chirp"][
"start_frequency_offset"] = fmcw->getStartFrequencyOffset();
663 if (fmcw->getChirpCount().has_value())
665 j[
"fmcw_linear_chirp"][
"chirp_count"] = *fmcw->getChirpCount();
668 else if (
const auto* triangle =
rs.getFmcwTriangleSignal(); triangle !=
nullptr)
670 j[
"fmcw_triangle"] = {{
"chirp_bandwidth", triangle->getChirpBandwidth()},
671 {
"chirp_duration", triangle->getChirpDuration()}};
672 if (std::abs(triangle->getStartFrequencyOffset()) >
EPSILON)
674 j[
"fmcw_triangle"][
"start_frequency_offset"] = triangle->getStartFrequencyOffset();
676 if (triangle->getTriangleCount().has_value())
678 j[
"fmcw_triangle"][
"triangle_count"] = *triangle->getTriangleCount();
685 j[
"pulsed_from_file"] = {{
"filename", *
filename}};
689 throw std::logic_error(
"Attempted to serialize a file-based waveform named '" +
rs.getName() +
690 "' without a source filename.");
695 void from_json(
const nlohmann::json&
j, std::unique_ptr<RadarSignal>&
rs)
697 const auto name =
j.at(
"name").get<std::string>();
699 const auto power =
j.at(
"power").get<
RealType>();
702 if (
j.contains(
"cw"))
704 auto cw_signal = std::make_unique<CwSignal>();
708 else if (
j.contains(
"stepped_frequency"))
710 const auto&
sfcw_json =
j.at(
"stepped_frequency");
711 const auto step_count =
sfcw_json.at(
"step_count").get<
long long>();
714 throw std::runtime_error(
"Waveform '" + name +
"' has an invalid step_count.");
716 std::optional<std::size_t> sweep_count;
722 throw std::runtime_error(
"Waveform '" + name +
"' has an invalid sweep_count.");
726 auto sfcw_signal = std::make_unique<SteppedFrequencySignal>(
728 static_cast<std::size_t
>(step_count),
sfcw_json.at(
"dwell_time").get<
RealType>(),
734 else if (
j.contains(
"fmcw_linear_chirp"))
736 const auto&
fmcw_json =
j.at(
"fmcw_linear_chirp");
738 std::optional<std::size_t> chirp_count;
744 throw std::runtime_error(
"Waveform '" + name +
"' has an invalid chirp_count.");
749 auto fmcw_signal = std::make_unique<FmcwChirpSignal>(
757 else if (
j.contains(
"fmcw_triangle"))
759 const auto&
fmcw_json =
j.at(
"fmcw_triangle");
760 std::optional<std::size_t> triangle_count;
761 if (
fmcw_json.contains(
"triangle_count"))
766 throw std::runtime_error(
"Waveform '" + name +
"' has an invalid triangle_count.");
771 throw std::runtime_error(
"Waveform '" + name +
"' has an invalid triangle_count.");
773 triangle_count =
static_cast<std::size_t
>(
parsed_count);
776 auto fmcw_signal = std::make_unique<FmcwTriangleSignal>(
778 fmcw_json.value(
"start_frequency_offset", 0.0), triangle_count);
783 else if (
j.contains(
"pulsed_from_file"))
785 const auto& pulsed_file =
j.at(
"pulsed_from_file");
786 const auto filename = pulsed_file.value(
"filename",
"");
794 else if (
j.contains(
"cw_from_file"))
796 const auto filename =
j.at(
"cw_from_file").value(
"filename",
"");
804 else if (
j.contains(
"fmcw_from_file"))
806 const auto filename =
j.at(
"fmcw_from_file").value(
"filename",
"");
816 throw std::runtime_error(
"Unsupported waveform type in from_json for '" + name +
"'");
825 j = {{
"id",
sim_id_to_json(
a.getId())}, {
"name",
a.getName()}, {
"efficiency",
a.getEfficiencyFactor()}};
827 if (
const auto* sinc =
dynamic_cast<const Sinc*
>(&
a))
829 j[
"pattern"] =
"sinc";
830 j[
"alpha"] = sinc->getAlpha();
831 j[
"beta"] = sinc->getBeta();
832 j[
"gamma"] = sinc->getGamma();
836 j[
"pattern"] =
"gaussian";
837 j[
"azscale"] =
gaussian->getAzimuthScale();
838 j[
"elscale"] =
gaussian->getElevationScale();
840 else if (
const auto*
sh =
dynamic_cast<const SquareHorn*
>(&
a))
842 j[
"pattern"] =
"squarehorn";
843 j[
"diameter"] =
sh->getDimension();
847 j[
"pattern"] =
"parabolic";
852 j[
"pattern"] =
"xml";
853 j[
"filename"] =
xml->getFilename();
855 else if (
const auto*
h5 =
dynamic_cast<const H5Antenna*
>(&
a))
857 j[
"pattern"] =
"file";
858 j[
"filename"] =
h5->getFilename();
862 j[
"pattern"] =
"isotropic";
868 const auto name =
j.at(
"name").get<std::string>();
870 const auto pattern =
j.value(
"pattern",
"isotropic");
874 ant = std::make_unique<Isotropic>(name,
id);
881 else if (
pattern ==
"gaussian")
884 std::make_unique<Gaussian>(name,
j.at(
"azscale").get<
RealType>(),
j.at(
"elscale").get<
RealType>(),
id);
886 else if (
pattern ==
"squarehorn")
888 ant = std::make_unique<SquareHorn>(name,
j.at(
"diameter").get<
RealType>(),
id);
890 else if (
pattern ==
"parabolic")
892 ant = std::make_unique<Parabolic>(name,
j.at(
"diameter").get<
RealType>(),
id);
896 const auto filename =
j.value(
"filename",
"");
902 ant = std::make_unique<XmlAntenna>(name,
filename,
id);
906 const auto filename =
j.value(
"filename",
"");
912 ant = std::make_unique<H5Antenna>(name,
filename,
id);
916 throw std::runtime_error(
"Unsupported antenna pattern in from_json: " +
pattern);
919 ant->setEfficiencyFactor(
j.value(
"efficiency", 1.0));
927 j = {{
"start",
p.start}, {
"end",
p.end}};
932 j.at(
"start").get_to(
p.start);
933 j.at(
"end").get_to(
p.end);
946 j[
"pulsed_mode"] = {{
"prf", t.
getPrf()}};
950 j[
"fmcw_mode"] = nlohmann::json::object();
954 j[
"sfcw_mode"] = nlohmann::json::object();
958 j[
"cw_mode"] = nlohmann::json::object();
969 {
"name",
r.getName()},
970 {
"noise_temp",
r.getNoiseTemperature()},
971 {
"antenna",
sim_id_to_json((
r.getAntenna() !=
nullptr) ?
r.getAntenna()->getId() : 0)},
979 {
"prf",
r.getWindowPrf()}, {
"window_skip",
r.getWindowSkip()}, {
"window_length",
r.getWindowLength()}};
987 j[
"sfcw_mode"] = nlohmann::json::object();
991 j[
"cw_mode"] = nlohmann::json::object();
993 if (!
r.getSchedule().empty())
995 j[
"schedule"] =
r.getSchedule();
1004 if (
const auto*
iso =
dynamic_cast<const IsoTarget*
>(&t))
1009 else if (
const auto* file =
dynamic_cast<const FileTarget*
>(&t))
1012 rcs_json[
"filename"] = file->getFilename();
1035 j = {{
"id",
sim_id_to_json(
p.getId())}, {
"name",
p.getName()}, {
"motionpath", *
p.getMotionPath()}};
1039 j[
"fixedrotation"] = *
p.getRotationPath();
1043 j[
"rotationpath"] = *
p.getRotationPath();
1058 void to_json(nlohmann::json&
j,
const Parameters&
p)
1060 j = nlohmann::json{{
"starttime",
p.start},
1064 {
"simSamplingRate",
p.sim_sampling_rate},
1065 {
"adc_bits",
p.adc_bits},
1066 {
"oversample",
p.oversample_ratio},
1067 {
"rotationangleunit",
p.rotation_angle_unit}};
1069 if (
p.random_seed.has_value())
1071 j[
"randomseed"] =
p.random_seed.value();
1075 {
"latitude",
p.origin_latitude}, {
"longitude",
p.origin_longitude}, {
"altitude",
p.origin_altitude}};
1077 j[
"coordinatesystem"] = {{
"frame",
p.coordinate_frame}};
1080 j[
"coordinatesystem"][
"zone"] =
p.utm_zone;
1081 j[
"coordinatesystem"][
"hemisphere"] =
p.utm_north_hemisphere ?
"N" :
"S";
1085 void from_json(
const nlohmann::json&
j, Parameters&
p)
1091 p.sim_sampling_rate =
j.value(
"simSamplingRate", 1000.0);
1092 p.adc_bits =
j.value(
"adc_bits", 0
u);
1093 p.oversample_ratio =
j.value(
"oversample", 1u);
1096 p.random_seed =
j.value<std::optional<unsigned>>(
"randomseed", std::nullopt);
1098 const auto&
origin =
j.at(
"origin");
1099 p.origin_latitude =
origin.at(
"latitude").get<
double>();
1100 p.origin_longitude =
origin.at(
"longitude").get<
double>();
1101 p.origin_altitude =
origin.at(
"altitude").get<
double>();
1103 const auto&
cs =
j.at(
"coordinatesystem");
1107 p.utm_zone =
cs.at(
"zone").get<
int>();
1108 p.utm_north_hemisphere =
cs.at(
"hemisphere").get<std::string>() ==
"N";
1130 {
"window_skip",
receiver.getWindowSkip()},
1131 {
"window_length",
receiver.getWindowLength()}};
1170 for (
const auto&
transmitter : world.getTransmitters())
1189 for (
const auto&
receiver : world.getReceivers())
1200 for (
const auto&
target : world.getTargets())
1213 plat_json[
"components"] = nlohmann::json::array();
1231 if (
sim.at(
"parameters").contains(
"randomseed"))
1249 if (
sim.contains(
"waveforms"))
1251 for (
auto waveforms =
sim.at(
"waveforms").get<std::vector<std::unique_ptr<fers_signal::RadarSignal>>>();
1252 auto& waveform : waveforms)
1257 world.
add(std::move(waveform));
1262 if (
sim.contains(
"antennas"))
1264 for (
auto antennas =
sim.at(
"antennas").get<std::vector<std::unique_ptr<antenna::Antenna>>>();
1275 if (
sim.contains(
"timings"))
1279 auto name =
timing_json.at(
"name").get<std::string>();
1297 const auto name =
element.at(
"name").get<std::string>();
1301 throw std::runtime_error(
"Duplicate name '" + name +
"' found for " + std::string(kind) +
1302 "; previously used by " +
iter->second +
".");
1307 const std::string_view
key,
const std::string_view kind)
1322 if (!
platform.contains(
"components") || !
platform.at(
"components").is_array())
1342 if (!
sim.contains(
"platforms"))
1367 return static_cast<std::size_t
>(
comp_json.contains(
"pulsed_mode")) +
1368 static_cast<std::size_t
>(
comp_json.contains(
"fmcw_mode")) +
1369 static_cast<std::size_t
>(
comp_json.contains(
"cw_mode")) +
1370 static_cast<std::size_t
>(
comp_json.contains(
"sfcw_mode"));
1379 " must have at most one of 'pulsed_mode', 'cw_mode', "
1380 "'fmcw_mode', or 'sfcw_mode'.");
1405 " must have a 'pulsed_mode', 'cw_mode', or 'fmcw_mode' block, or an 'sfcw_mode' "
1444 throw std::runtime_error(
"Transmitter component '" +
comp_json.value(
"name",
"Unnamed") +
1445 "' fmcw_mode must not contain dechirp configuration.");
1458 "Transmitter component '" +
comp_json.value(
"name",
"Unnamed") +
"'");
1459 trans->setWave(waveform);
1469 auto raw =
comp_json.at(
"schedule").get<std::vector<radar::SchedulePeriod>>();
1477 if (waveform->isFmcwFamily() || waveform->isSteppedFrequency())
1483 else if (waveform->isFmcwFamily() || waveform->isSteppedFrequency())
1528 recv->setNoiseTemperature(
comp_json.value(
"noise_temp", 0.0));
1541 if (
comp_json.value(
"nopropagationloss",
false))
1548 auto raw =
comp_json.at(
"schedule").get<std::vector<radar::SchedulePeriod>>();
1552 pri = 1.0 /
recv->getWindowPrf();
1559 "Receiver component '" +
comp_json.value(
"name",
"Unnamed") +
"'");
1593 throw std::runtime_error(
"Unsupported target RCS type: " +
rcs_type);
1605 auto model = std::make_unique<radar::RcsChiSquare>(world.
getTargets().back()->getRngEngine(),
1611 world.
getTargets().back()->setFluctuationModel(std::make_unique<radar::RcsConst>());
1615 throw std::runtime_error(
"Unsupported fluctuation model type: " +
model_type);
1666 "Monostatic component '" +
comp_json.value(
"name",
"Unnamed") +
"'");
1667 trans->setWave(waveform);
1681 recv->setWindowProperties(
mode_json.value(
"window_length", 0.0),
1685 recv->setNoiseTemperature(
comp_json.value(
"noise_temp", 0.0));
1697 if (
comp_json.value(
"nopropagationloss",
false))
1703 auto raw =
comp_json.at(
"schedule").get<std::vector<radar::SchedulePeriod>>();
1713 if (waveform->isFmcwFamily() || waveform->isSteppedFrequency())
1716 "Monostatic component '" +
comp_json.value(
"name",
"Unnamed") +
"'");
1722 else if (waveform->isFmcwFamily() || waveform->isSteppedFrequency())
1725 "Monostatic component '" +
comp_json.value(
"name",
"Unnamed") +
"'");
1732 "Monostatic component '" +
comp_json.value(
"name",
"Unnamed") +
"'");
1741 auto name =
plat_json.at(
"name").get<std::string>();
1743 auto plat = std::make_unique<radar::Platform>(name, platform_id);
1780 const auto&
sim =
j.at(
"simulation");
1786 if (
sim.contains(
"platforms"))
1804 std::unique_ptr<antenna::Antenna>
ant;
1811 std::unique_ptr<fers_signal::RadarSignal>
wf;
1818 auto timing = std::make_unique<timing::PrototypeTiming>(
j.at(
"name").get<std::string>(),
id);
1826 sim[
"parameters"] =
j;
1835 const auto name =
j.value(
"name", std::string{});
1836 const auto pattern =
j.value(
"pattern",
"isotropic");
1837 throw std::runtime_error(
"Cannot update antenna '" + name +
"' to pattern '" +
pattern +
1838 "' without a filename.");
1880 sinc->setAlpha(
j.value(
"alpha", 1.0));
1881 sinc->setBeta(
j.value(
"beta", 1.0));
1882 sinc->setGamma(
j.value(
"gamma", 2.0));
1886 gauss->setAzimuthScale(
j.value(
"azscale", 1.0));
1887 gauss->setElevationScale(
j.value(
"elscale", 1.0));
1891 horn->setDimension(
j.value(
"diameter", 0.5));
1895 para->setDiameter(
j.value(
"diameter", 0.5));
1899 if (
xml->getFilename() !=
j.value(
"filename",
""))
1906 if (
h5->getFilename() !=
j.value(
"filename",
""))
1915 const auto new_pattern =
j.value(
"pattern",
"isotropic");
1922 ant->setName(
j.at(
"name").get<std::string>());
1923 ant->setEfficiencyFactor(
j.value(
"efficiency", 1.0));
1929 if (
j.contains(
"motionpath"))
1931 auto path = std::make_unique<math::Path>();
1932 j.at(
"motionpath").get_to(*path);
1933 plat->setMotionPath(std::move(path));
1935 if (
j.contains(
"rotationpath"))
1937 auto rot_path = std::make_unique<math::RotationPath>();
1946 const std::string
owner =
1951 "JSON",
owner,
"azimuth");
1954 "JSON",
owner,
"elevation");
1963 else if (
j.contains(
"fixedrotation"))
1965 auto rot_path = std::make_unique<math::RotationPath>();
1971 const std::string
owner = std::format(
"platform '{}' fixedrotation",
plat->getName());
1975 owner,
"startazimuth");
1978 owner,
"startelevation");
1981 owner,
"azimuthrate");
1984 owner,
"elevationrate");
1990 rot_path->setConstantRate(start, rate);
1999 if (
j.contains(
"pulsed_mode"))
2002 tx.setPrf(
j.at(
"pulsed_mode").value(
"prf", 0.0));
2004 else if (
j.contains(
"fmcw_mode"))
2008 throw std::runtime_error(
"Transmitter '" +
tx.getName() +
2009 "' fmcw_mode must not contain dechirp configuration.");
2013 else if (
j.contains(
"sfcw_mode"))
2018 else if (
j.contains(
"cw_mode"))
2026 if (!
j.contains(
"waveform"))
2034 throw std::runtime_error(
"Waveform ID " + std::to_string(
id) +
" not found.");
2043 if (!
j.contains(
"antenna"))
2051 throw std::runtime_error(
"Antenna ID " + std::to_string(
id) +
" not found.");
2058 if (!
j.contains(
"timing"))
2066 throw std::runtime_error(
"Timing ID " + std::to_string(
timing_id) +
" not found.");
2068 unsigned const seed =
tx.getTiming() ?
tx.getTiming()->getSeed() : 0;
2076 if (
tx.getSignal() ==
nullptr)
2082 if (
tx.getSignal()->isFmcwFamily() ||
tx.getSignal()->isSteppedFrequency())
2089 const std::string&
owner)
2091 if (!
j.contains(
"schedule"))
2095 auto raw =
j.at(
"schedule").get<std::vector<radar::SchedulePeriod>>();
2099 if (
tx.getSignal() !=
nullptr && (
tx.getSignal()->isFmcwFamily() ||
tx.getSignal()->isSteppedFrequency()))
2109 if (
j.contains(
"name"))
2110 tx->setName(
j.at(
"name").get<std::string>());
2112 const std::string
owner =
"Transmitter '" +
tx->getName() +
"'";
2124 if (
j.contains(
"pulsed_mode"))
2131 else if (
j.contains(
"fmcw_mode"))
2135 else if (
j.contains(
"sfcw_mode"))
2140 else if (
j.contains(
"cw_mode"))
2148 if (
j.contains(
"noise_temp"))
2149 rx.setNoiseTemperature(
j.value(
"noise_temp", 0.0));
2151 if (
j.contains(
"nodirect"))
2153 if (
j.value(
"nodirect",
false))
2158 if (
j.contains(
"nopropagationloss"))
2160 if (
j.value(
"nopropagationloss",
false))
2169 if (!
j.contains(
"antenna"))
2177 throw std::runtime_error(
"Antenna ID " + std::to_string(
id) +
" not found.");
2184 if (!
j.contains(
"timing"))
2192 throw std::runtime_error(
"Timing ID " + std::to_string(
timing_id) +
" not found.");
2194 unsigned const seed =
rx.getTiming() ?
rx.getTiming()->getSeed() : 0;
2202 if (!
j.contains(
"schedule"))
2206 auto raw =
j.at(
"schedule").get<std::vector<radar::SchedulePeriod>>();
2208 const RealType pri = pulsed ? 1.0 /
rx.getWindowPrf() : 0.0;
2215 if (
j.contains(
"name"))
2216 rx->setName(
j.at(
"name").get<std::string>());
2223 if (
j.contains(
"fmcw_mode"))
2247 if (
j.contains(
"name"))
2248 rx.setName(
j.at(
"name").get<std::string>());
2249 rx.setMode(
tx.getMode());
2253 rx.setWindowProperties(
mode_json.value(
"window_length", 0.0),
tx.getPrf(),
2257 if (
j.contains(
"antenna"))
2266 if (!
j.contains(
"timing"))
2274 throw std::runtime_error(
"Timing ID " + std::to_string(
timing_id) +
" not found.");
2276 unsigned const seed =
rx.getTiming() ?
rx.getTiming()->getSeed() : 0;
2285 if (!
j.contains(
"schedule"))
2289 auto raw =
j.at(
"schedule").get<std::vector<radar::SchedulePeriod>>();
2293 if (
tx.getSignal() !=
nullptr && (
tx.getSignal()->isFmcwFamily() ||
tx.getSignal()->isSteppedFrequency()))
2311 if (
j.contains(
"fmcw_mode"))
2341 throw std::runtime_error(
"Unsupported target RCS type: " +
rcs_type);
2344 if (
j.contains(
"model"))
2356 target_obj->setFluctuationModel(std::make_unique<radar::RcsConst>());
2368 throw std::runtime_error(
"Timing ID " + std::to_string(
id) +
" not found.");
2372 if (!
patched.contains(
"name"))
2387 sim_json[
"waveforms"] = nlohmann::json::array();
2388 for (
const auto& waveform : world.
getWaveforms() | std::views::values)
2390 sim_json[
"waveforms"].push_back(*waveform);
2393 sim_json[
"antennas"] = nlohmann::json::array();
2399 sim_json[
"timings"] = nlohmann::json::array();
2405 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< RadarSignal > loadWaveformFromFile(const std::string &name, const std::string &filename, const RealType power, const RealType carrierFreq, const SimId id, const FileWaveformKind kind)
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)
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.