File indexing completed on 2026-05-01 07:33:37
0001
0002
0003
0004
0005
0006
0007
0008
0009 #include "ActsPlugins/Json/TrackingGeometryJsonConverter.hpp"
0010
0011 #include "Acts/Geometry/CompositePortalLink.hpp"
0012 #include "Acts/Geometry/ConeVolumeBounds.hpp"
0013 #include "Acts/Geometry/CuboidVolumeBounds.hpp"
0014 #include "Acts/Geometry/CutoutCylinderVolumeBounds.hpp"
0015 #include "Acts/Geometry/CylinderVolumeBounds.hpp"
0016 #include "Acts/Geometry/DiamondVolumeBounds.hpp"
0017 #include "Acts/Geometry/GenericCuboidVolumeBounds.hpp"
0018 #include "Acts/Geometry/GeometryContext.hpp"
0019 #include "Acts/Geometry/GeometryIdentifier.hpp"
0020 #include "Acts/Geometry/GridPortalLink.hpp"
0021 #include "Acts/Geometry/Portal.hpp"
0022 #include "Acts/Geometry/PortalLinkBase.hpp"
0023 #include "Acts/Geometry/TrackingGeometry.hpp"
0024 #include "Acts/Geometry/TrackingVolume.hpp"
0025 #include "Acts/Geometry/TrapezoidVolumeBounds.hpp"
0026 #include "Acts/Geometry/TrivialPortalLink.hpp"
0027 #include "Acts/Geometry/VolumeBounds.hpp"
0028 #include "Acts/Navigation/INavigationPolicy.hpp"
0029 #include "Acts/Navigation/MultiLayerNavigationPolicy.hpp"
0030 #include "Acts/Navigation/MultiNavigationPolicy.hpp"
0031 #include "Acts/Navigation/SurfaceArrayNavigationPolicy.hpp"
0032 #include "Acts/Navigation/TryAllNavigationPolicy.hpp"
0033 #include "Acts/Surfaces/RegularSurface.hpp"
0034 #include "Acts/Surfaces/SurfacePlacementBase.hpp"
0035 #include "Acts/Utilities/AnyGridView.hpp"
0036 #include "Acts/Utilities/Enumerate.hpp"
0037 #include "Acts/Utilities/IAxis.hpp"
0038 #include "Acts/Utilities/Logger.hpp"
0039 #include "ActsPlugins/Json/AlgebraJsonConverter.hpp"
0040 #include "ActsPlugins/Json/GeometryIdentifierJsonConverter.hpp"
0041 #include "ActsPlugins/Json/GridJsonConverter.hpp"
0042 #include "ActsPlugins/Json/SurfaceJsonConverter.hpp"
0043 #include "ActsPlugins/Json/UtilitiesJsonConverter.hpp"
0044
0045 #include <stdexcept>
0046 #include <unordered_set>
0047
0048 template <typename object_t, const char* kContext>
0049 struct Acts::TrackingGeometryJsonConverter::PointerToIdLookup {
0050
0051
0052
0053
0054
0055
0056
0057 bool emplace(const object_t& object, std::size_t objectId) {
0058 return m_objectIds.emplace(&object, objectId).second;
0059 }
0060
0061
0062
0063
0064
0065
0066
0067
0068 std::size_t at(const object_t& object) const {
0069 auto it = m_objectIds.find(&object);
0070 if (it == m_objectIds.end()) {
0071 throw std::invalid_argument("Pointer-to-ID lookup failed for " +
0072 std::string{kContext} +
0073 ": object is outside serialized hierarchy");
0074 }
0075 return it->second;
0076 }
0077
0078 private:
0079
0080 std::unordered_map<const object_t*, std::size_t> m_objectIds;
0081 };
0082
0083 template <typename object_t, typename pointer_t, const char* kContext>
0084 struct Acts::TrackingGeometryJsonConverter::IdToPointerLikeLookup {
0085
0086
0087
0088
0089
0090
0091 bool emplace(std::size_t objectId, pointer_t object) {
0092 return m_objects.emplace(objectId, std::move(object)).second;
0093 }
0094
0095
0096
0097
0098
0099
0100 pointer_t find(std::size_t objectId) const {
0101 auto it = m_objects.find(objectId);
0102 return it == m_objects.end() ? pointer_t{} : it->second;
0103 }
0104
0105
0106
0107
0108
0109
0110
0111
0112 const pointer_t& at(std::size_t objectId) const {
0113 auto it = m_objects.find(objectId);
0114 if (it == m_objects.end()) {
0115 throw std::invalid_argument("ID-to-pointer lookup failed for " +
0116 std::string{kContext} +
0117 ": unknown serialized object ID");
0118 }
0119 return it->second;
0120 }
0121
0122 private:
0123
0124 std::unordered_map<std::size_t, pointer_t> m_objects;
0125 };
0126
0127 namespace {
0128
0129
0130
0131
0132
0133
0134
0135
0136
0137
0138
0139
0140 constexpr const char* kHeaderKey = "acts-geometry-volumes";
0141 constexpr const char* kVersionKey = "format-version";
0142 constexpr const char* kScopeKey = "scope";
0143 constexpr const char* kScopeValue = "volumes-bounds-portals";
0144 constexpr int kFormatVersion = 1;
0145
0146 constexpr const char* kPortalsKey = "portals";
0147 constexpr const char* kSurfacesKey = "surfaces";
0148 constexpr const char* kVolumesKey = "volumes";
0149
0150 constexpr const char* kRootVolumeIdKey = "root_volume_id";
0151 constexpr const char* kVolumeIdKey = "volume_id";
0152 constexpr const char* kPortalIdKey = "portal_id";
0153 constexpr const char* kSurfaceIdKey = "surface_id";
0154
0155 constexpr const char* kNameKey = "name";
0156 constexpr const char* kGeometryIdKey = "geometry_id";
0157 constexpr const char* kTransformKey = "transform";
0158 constexpr const char* kBoundsKey = "bounds";
0159 constexpr const char* kChildrenKey = "children";
0160
0161 constexpr const char* kPortalIdsKey = "portal_ids";
0162 constexpr const char* kAlongNormalKey = "along_normal";
0163 constexpr const char* kOppositeNormalKey = "opposite_normal";
0164
0165 constexpr const char* kNavigationPolicyKey = "navigation_policy";
0166
0167 constexpr const char* kKindKey = "kind";
0168 constexpr const char* kValuesKey = "values";
0169 constexpr const char* kTargetVolumeIdKey = "target_volume_id";
0170 constexpr const char* kDirectionKey = "direction";
0171 constexpr const char* kAxesKey = "axes";
0172 constexpr const char* kBinsKey = "bins";
0173 constexpr const char* kLocalKey = "local";
0174 constexpr const char* kArtifactLinksKey = "artifact_links";
0175
0176
0177
0178
0179 template <typename bounds_t>
0180 std::string getPortalLinkKind() {
0181 if (std::is_same_v<bounds_t, Acts::TrivialPortalLink>) {
0182 return "Trivial";
0183 } else if (std::is_same_v<bounds_t, Acts::CompositePortalLink>) {
0184 return "Composite";
0185 } else if (std::is_same_v<bounds_t, Acts::GridPortalLink>) {
0186 return "Grid";
0187 } else {
0188 throw std::invalid_argument("Unknown portal link kind");
0189 }
0190 }
0191
0192 template <typename bounds_t>
0193 std::string getVolumeBoundsKind() {
0194 if (std::is_same_v<bounds_t, Acts::ConeVolumeBounds>) {
0195 return "Cone";
0196 } else if (std::is_same_v<bounds_t, Acts::CuboidVolumeBounds>) {
0197 return "Cuboid";
0198 } else if (std::is_same_v<bounds_t, Acts::CutoutCylinderVolumeBounds>) {
0199 return "CutoutCylinder";
0200 } else if (std::is_same_v<bounds_t, Acts::CylinderVolumeBounds>) {
0201 return "Cylinder";
0202 } else if (std::is_same_v<bounds_t, Acts::DiamondVolumeBounds>) {
0203 return "Diamond";
0204 } else if (std::is_same_v<bounds_t, Acts::GenericCuboidVolumeBounds>) {
0205 return "GenericCuboid";
0206 } else if (std::is_same_v<bounds_t, Acts::TrapezoidVolumeBounds>) {
0207 return "Trapezoid";
0208 } else {
0209 throw std::invalid_argument("Unknown volume bounds kind");
0210 }
0211 }
0212
0213 template <typename bounds_t>
0214 std::string getNavigationPolicyKind() {
0215 if (std::is_same_v<bounds_t, Acts::TryAllNavigationPolicy>) {
0216 return "TryAll";
0217 } else if (std::is_same_v<bounds_t, Acts::SurfaceArrayNavigationPolicy>) {
0218 return "SurfaceArray";
0219 } else if (std::is_same_v<bounds_t, Acts::MultiNavigationPolicy>) {
0220 return "MultiNavigation";
0221 } else if (std::is_same_v<bounds_t,
0222 Acts::Experimental::MultiLayerNavigationPolicy>) {
0223 return "MultiLayerNavigation";
0224 } else {
0225 throw std::invalid_argument("Unknown portal link kind");
0226 }
0227 }
0228
0229
0230
0231
0232 template <typename bounds_t>
0233 std::unique_ptr<Acts::VolumeBounds> decodeVolumeBoundsT(
0234 const nlohmann::json& jBounds) {
0235 constexpr std::size_t kValues = bounds_t::BoundValues::eSize;
0236 const auto values = jBounds.at(kValuesKey).get<std::vector<double>>();
0237 if (values.size() != kValues) {
0238 throw std::invalid_argument("Invalid number of values for volume bounds");
0239 }
0240 std::array<double, kValues> boundValues{};
0241 std::copy_n(values.begin(), kValues, boundValues.begin());
0242 return std::make_unique<bounds_t>(boundValues);
0243 }
0244
0245 template <typename bounds_t>
0246 nlohmann::json encodeVolumeBoundsT(const bounds_t& bounds) {
0247 nlohmann::json jBounds;
0248 jBounds["kind"] = getVolumeBoundsKind<bounds_t>();
0249 jBounds[kValuesKey] = bounds.values();
0250 return jBounds;
0251 }
0252
0253
0254
0255
0256 std::unique_ptr<Acts::INavigationPolicy> decodeMultiNavigationPolicy(
0257 const nlohmann::json& encoded, const Acts::GeometryContext& gctx,
0258 const Acts::TrackingGeometryJsonConverter& converter,
0259 const Acts::TrackingVolume& volume, const Acts::Logger& logger) {
0260 std::vector<std::unique_ptr<Acts::INavigationPolicy>> children;
0261 for (const auto& child : encoded.at(kChildrenKey)) {
0262 children.push_back(converter.navigationPolicyFromJson(
0263 gctx, child[kNavigationPolicyKey], volume, logger));
0264 }
0265 return std::make_unique<Acts::MultiNavigationPolicy>(std::move(children));
0266 }
0267
0268 std::unique_ptr<Acts::INavigationPolicy> decodeTryAllNavigationPolicy(
0269 const nlohmann::json& encoded, const Acts::GeometryContext& gctx,
0270 const Acts::TrackingGeometryJsonConverter& ,
0271 const Acts::TrackingVolume& volume, const Acts::Logger& logger) {
0272 Acts::TryAllNavigationPolicy::Config cfg;
0273 cfg.passives = encoded.at("passives").get<bool>();
0274 cfg.sensitives = encoded.at("sensitives").get<bool>();
0275 cfg.portals = encoded.at("portals").get<bool>();
0276
0277 return std::make_unique<Acts::TryAllNavigationPolicy>(gctx, volume, logger,
0278 cfg);
0279 }
0280
0281 std::unique_ptr<Acts::INavigationPolicy> decodeSurfaceArrayNavigationPolicy(
0282 const nlohmann::json& encoded, const Acts::GeometryContext& gctx,
0283 const Acts::TrackingGeometryJsonConverter& ,
0284 const Acts::TrackingVolume& volume, const Acts::Logger& logger) {
0285 Acts::SurfaceArrayNavigationPolicy::Config cfg;
0286 cfg.layerType = encoded.at("layerType")
0287 .get<Acts::SurfaceArrayNavigationPolicy::LayerType>();
0288 cfg.bins = {encoded.at("bins0").get<std::size_t>(),
0289 encoded.at("bins1").get<std::size_t>()};
0290
0291 return std::make_unique<Acts::SurfaceArrayNavigationPolicy>(gctx, volume,
0292 logger, cfg);
0293 }
0294
0295 nlohmann::json encodeMultiNavigationPolicy(
0296 const Acts::MultiNavigationPolicy& policy,
0297 const Acts::TrackingGeometryJsonConverter& converter) {
0298 nlohmann::json jPolicy;
0299 jPolicy[kKindKey] = getNavigationPolicyKind<Acts::MultiNavigationPolicy>();
0300 for (const auto& pol : policy.policies()) {
0301 nlohmann::json jPol;
0302 jPol["navigation_policy"] = converter.navigationPolicyToJson(*pol);
0303 jPolicy[kChildrenKey].push_back(jPol);
0304 }
0305 return jPolicy;
0306 }
0307
0308 nlohmann::json encodeTryAllNavigationPolicy(
0309 const Acts::TryAllNavigationPolicy& policy,
0310 const Acts::TrackingGeometryJsonConverter& ) {
0311 const auto& cfg = policy.config();
0312
0313 nlohmann::json jPolicy;
0314 jPolicy[kKindKey] = getNavigationPolicyKind<Acts::TryAllNavigationPolicy>();
0315 jPolicy["portals"] = cfg.portals;
0316 jPolicy["sensitives"] = cfg.sensitives;
0317 jPolicy["passives"] = cfg.passives;
0318 return jPolicy;
0319 }
0320
0321 nlohmann::json encodeSurfaceArrayNavigationPolicy(
0322 const Acts::SurfaceArrayNavigationPolicy& policy,
0323 const Acts::TrackingGeometryJsonConverter& ) {
0324 const auto& cfg = policy.config();
0325
0326 nlohmann::json jPolicy;
0327 jPolicy[kKindKey] =
0328 getNavigationPolicyKind<Acts::SurfaceArrayNavigationPolicy>();
0329 jPolicy["layerType"] = cfg.layerType;
0330 jPolicy["bins0"] = cfg.bins.first;
0331 jPolicy["bins1"] = cfg.bins.second;
0332 return jPolicy;
0333 }
0334
0335 nlohmann::json encodeMultiLayerNavigationPolicy(
0336 const Acts::Experimental::MultiLayerNavigationPolicy& policy,
0337 const Acts::TrackingGeometryJsonConverter& ) {
0338 nlohmann::json jPolicy;
0339 jPolicy[kKindKey] =
0340 getNavigationPolicyKind<Acts::Experimental::MultiLayerNavigationPolicy>();
0341
0342 const auto& grid = policy.indexedGrid();
0343 nlohmann::json jAxes;
0344 for (const auto* axis : grid.grid.axes()) {
0345 jAxes.push_back(Acts::AxisJsonConverter::toJson(*axis));
0346 }
0347 jPolicy["axes"] = jAxes;
0348
0349 const auto& casts = grid.casts;
0350 jPolicy["casts"] =
0351 std::vector<Acts::AxisDirection>(casts.begin(), casts.end());
0352 jPolicy["binExpansion"] = policy.config().binExpansion;
0353
0354 return jPolicy;
0355 }
0356
0357 std::unique_ptr<Acts::INavigationPolicy> decodeMultiLayerNavigationPolicy(
0358 const nlohmann::json& encoded, const Acts::GeometryContext& gctx,
0359 const Acts::TrackingGeometryJsonConverter& ,
0360 const Acts::TrackingVolume& volume, const Acts::Logger& logger) {
0361 const auto& jAxes = encoded.at("axes");
0362 std::array<double, 2> range0 = jAxes.at(0).at("range");
0363 std::size_t bins0 = jAxes.at(0).at("bins");
0364 std::array<double, 2> range1 = jAxes.at(1).at("range");
0365 std::size_t bins1 = jAxes.at(1).at("bins");
0366
0367 Acts::Axis<Acts::AxisType::Equidistant, Acts::AxisBoundaryType::Bound> axis0(
0368 range0[0], range0[1], bins0);
0369 Acts::Axis<Acts::AxisType::Equidistant, Acts::AxisBoundaryType::Bound> axis1(
0370 range1[0], range1[1], bins1);
0371 Acts::Experimental::MultiLayerNavigationPolicy::GridType grid(
0372 std::move(axis0), std::move(axis1));
0373
0374 std::vector<Acts::AxisDirection> castsVec =
0375 encoded.at("casts").get<std::vector<Acts::AxisDirection>>();
0376 std::array<Acts::AxisDirection, 2> casts = {castsVec.at(0), castsVec.at(1)};
0377
0378 Acts::Experimental::MultiLayerNavigationPolicy::IndexedUpdatorType
0379 indexedGrid(std::move(grid), casts);
0380
0381 Acts::Experimental::MultiLayerNavigationPolicy::Config config;
0382 config.binExpansion =
0383 encoded.at("binExpansion").get<std::vector<std::size_t>>();
0384
0385 return std::make_unique<Acts::Experimental::MultiLayerNavigationPolicy>(
0386 gctx, volume, logger, config, std::move(indexedGrid));
0387 }
0388
0389
0390
0391
0392 std::shared_ptr<Acts::RegularSurface> regularSurfaceFromJson(
0393 const nlohmann::json& jSurface) {
0394 auto surface = Acts::SurfaceJsonConverter::fromJson(jSurface);
0395 auto regular = std::dynamic_pointer_cast<Acts::RegularSurface>(surface);
0396 if (regular == nullptr) {
0397 throw std::invalid_argument("Portal link surface is not a RegularSurface");
0398 }
0399 return regular;
0400 }
0401
0402 std::unique_ptr<Acts::GridPortalLink> makeGridPortalLink(
0403 const std::shared_ptr<Acts::RegularSurface>& surface,
0404 Acts::AxisDirection direction, const Acts::IAxis& axis0,
0405 const Acts::IAxis* axis1) {
0406 std::unique_ptr<Acts::GridPortalLink> grid;
0407
0408 if (axis1 == nullptr) {
0409 axis0.visit([&](const auto& a0) {
0410 using axis_t = std::remove_cvref_t<decltype(a0)>;
0411 axis_t axisCopy = a0;
0412 grid =
0413 Acts::GridPortalLink::make(surface, direction, std::move(axisCopy));
0414 });
0415 } else {
0416 axis0.visit([&](const auto& a0) {
0417 using axis0_t = std::remove_cvref_t<decltype(a0)>;
0418 axis0_t axis0Copy = a0;
0419 axis1->visit([&](const auto& a1) {
0420 using axis1_t = std::remove_cvref_t<decltype(a1)>;
0421 axis1_t axis1Copy = a1;
0422 grid = Acts::GridPortalLink::make(surface, std::move(axis0Copy),
0423 std::move(axis1Copy));
0424 });
0425 });
0426 }
0427
0428 if (grid == nullptr) {
0429 throw std::invalid_argument("Could not construct GridPortalLink from axes");
0430 }
0431
0432 if (grid->direction() != direction) {
0433 throw std::invalid_argument(
0434 "Decoded grid direction does not match payload");
0435 }
0436
0437 return grid;
0438 }
0439
0440 nlohmann::json encodeTrivialPortalLink(
0441 const Acts::TrivialPortalLink& link, const Acts::GeometryContext& ,
0442 const Acts::TrackingGeometryJsonConverter& ,
0443 const Acts::TrackingGeometryJsonConverter::SurfaceIdLookup& surfaceIds,
0444 const Acts::TrackingGeometryJsonConverter::VolumeIdLookup& volumeIds) {
0445 nlohmann::json jLink;
0446 jLink[kKindKey] = getPortalLinkKind<Acts::TrivialPortalLink>();
0447 jLink[kSurfaceIdKey] = surfaceIds.at(link.surface());
0448 jLink[kTargetVolumeIdKey] = volumeIds.at(link.volume());
0449 return jLink;
0450 }
0451
0452 nlohmann::json encodeCompositePortalLink(
0453 const Acts::CompositePortalLink& link, const Acts::GeometryContext& gctx,
0454 const Acts::TrackingGeometryJsonConverter& converter,
0455 const Acts::TrackingGeometryJsonConverter::SurfaceIdLookup& surfaceIds,
0456 const Acts::TrackingGeometryJsonConverter::VolumeIdLookup& volumeIds) {
0457 nlohmann::json jLink;
0458 jLink[kKindKey] = getPortalLinkKind<Acts::CompositePortalLink>();
0459 jLink[kSurfaceIdKey] = surfaceIds.at(link.surface());
0460 jLink[kDirectionKey] = link.direction();
0461 jLink[kChildrenKey] = nlohmann::json::array();
0462
0463 for (const auto& child : link.links()) {
0464 jLink[kChildrenKey].push_back(
0465 converter.portalLinkToJson(gctx, child, surfaceIds, volumeIds));
0466 }
0467 return jLink;
0468 }
0469
0470 nlohmann::json encodeGridPortalLink(
0471 const Acts::GridPortalLink& link, const Acts::GeometryContext& gctx,
0472 const Acts::TrackingGeometryJsonConverter& converter,
0473 const Acts::TrackingGeometryJsonConverter::SurfaceIdLookup& surfaceIds,
0474 const Acts::TrackingGeometryJsonConverter::VolumeIdLookup& volumeIds) {
0475 nlohmann::json jLink;
0476 jLink[kKindKey] = getPortalLinkKind<Acts::GridPortalLink>();
0477 jLink[kDirectionKey] = link.direction();
0478 jLink[kSurfaceIdKey] = surfaceIds.at(link.surface());
0479 jLink[kAxesKey] = nlohmann::json::array();
0480
0481 for (const auto* axis : link.grid().axes()) {
0482 jLink[kAxesKey].push_back(Acts::AxisJsonConverter::toJson(*axis));
0483 }
0484
0485 Acts::AnyGridConstView<const Acts::TrackingVolume*> view(link.grid());
0486 const auto nBins = view.numLocalBins();
0487 const auto dim = view.dimensions();
0488
0489 jLink[kBinsKey] = nlohmann::json::array();
0490 if (dim == 1u) {
0491 for (std::size_t i0 = 0u; i0 <= nBins.at(0) + 1u; ++i0) {
0492 nlohmann::json jBin;
0493 jBin[kLocalKey] = std::vector<std::size_t>{i0};
0494 if (const auto* target = view.atLocalBins({i0}); target == nullptr) {
0495 jBin[kTargetVolumeIdKey] = nullptr;
0496 } else {
0497 jBin[kTargetVolumeIdKey] = volumeIds.at(*target);
0498 }
0499 jLink[kBinsKey].push_back(std::move(jBin));
0500 }
0501 } else if (dim == 2u) {
0502 for (std::size_t i0 = 0u; i0 <= nBins.at(0) + 1u; ++i0) {
0503 for (std::size_t i1 = 0u; i1 <= nBins.at(1) + 1u; ++i1) {
0504 nlohmann::json jBin;
0505 jBin[kLocalKey] = std::vector<std::size_t>{i0, i1};
0506 if (const auto* target = view.atLocalBins({i0, i1});
0507 target == nullptr) {
0508 jBin[kTargetVolumeIdKey] = nullptr;
0509 } else {
0510 jBin[kTargetVolumeIdKey] = volumeIds.at(*target);
0511 }
0512 jLink[kBinsKey].push_back(std::move(jBin));
0513 }
0514 }
0515 } else {
0516 throw std::invalid_argument("Unsupported GridPortalLink dimensionality");
0517 }
0518
0519 jLink[kArtifactLinksKey] = nlohmann::json::array();
0520 for (const auto& artifact : link.artifactPortalLinks()) {
0521 jLink[kArtifactLinksKey].push_back(
0522 converter.portalLinkToJson(gctx, artifact, surfaceIds, volumeIds));
0523 }
0524
0525 return jLink;
0526 }
0527
0528 std::unique_ptr<Acts::PortalLinkBase> decodeTrivialPortalLink(
0529 const nlohmann::json& encoded,
0530 const Acts::TrackingGeometryJsonConverter& ,
0531 const Acts::TrackingGeometryJsonConverter::SurfacePointerLookup& surfaces,
0532 const Acts::TrackingGeometryJsonConverter::VolumePointerLookup& volumes) {
0533 const auto linkSurfaceId = encoded.at(kSurfaceIdKey).get<std::size_t>();
0534 const auto targetVolumeId = encoded.at(kTargetVolumeIdKey).get<std::size_t>();
0535 return std::make_unique<Acts::TrivialPortalLink>(surfaces.at(linkSurfaceId),
0536 *volumes.at(targetVolumeId));
0537 }
0538
0539 std::unique_ptr<Acts::PortalLinkBase> decodeCompositePortalLink(
0540 const nlohmann::json& encoded,
0541 const Acts::TrackingGeometryJsonConverter& converter,
0542 const Acts::TrackingGeometryJsonConverter::SurfacePointerLookup& surfaces,
0543 const Acts::TrackingGeometryJsonConverter::VolumePointerLookup& volumes) {
0544 const auto direction = encoded.at(kDirectionKey).get<Acts::AxisDirection>();
0545 std::vector<std::unique_ptr<Acts::PortalLinkBase>> children;
0546 for (const auto& child : encoded.at(kChildrenKey)) {
0547 children.push_back(converter.portalLinkFromJson(child, surfaces, volumes));
0548 }
0549 return std::make_unique<Acts::CompositePortalLink>(std::move(children),
0550 direction);
0551 }
0552
0553 std::unique_ptr<Acts::PortalLinkBase> decodeGridPortalLink(
0554 const nlohmann::json& encoded,
0555 const Acts::TrackingGeometryJsonConverter& converter,
0556 const Acts::TrackingGeometryJsonConverter::SurfacePointerLookup& surfaces,
0557 const Acts::TrackingGeometryJsonConverter::VolumePointerLookup& volumes) {
0558 auto linkSurfaceId = encoded.at(kSurfaceIdKey).get<std::size_t>();
0559 const auto direction = encoded.at(kDirectionKey).get<Acts::AxisDirection>();
0560
0561 std::vector<std::unique_ptr<Acts::IAxis>> axes;
0562 for (const auto& jAxis : encoded.at(kAxesKey)) {
0563 axes.push_back(Acts::AxisJsonConverter::fromJson(jAxis));
0564 }
0565 if (axes.empty() || axes.size() > 2u) {
0566 throw std::invalid_argument("GridPortalLink requires 1 or 2 axes");
0567 }
0568
0569 auto grid =
0570 makeGridPortalLink(surfaces.at(linkSurfaceId), direction, *axes.at(0),
0571 axes.size() == 2u ? axes.at(1).get() : nullptr);
0572
0573 Acts::AnyGridView<const Acts::TrackingVolume*> view(grid->grid());
0574 const auto dim = view.dimensions();
0575 for (const auto& jBin : encoded.at(kBinsKey)) {
0576 auto local = jBin.at(kLocalKey).get<std::vector<std::size_t>>();
0577 if (local.size() != dim) {
0578 throw std::invalid_argument("Grid bin dimensionality mismatch");
0579 }
0580 Acts::IGrid::AnyIndexType localIndices(local.begin(), local.end());
0581
0582 const Acts::TrackingVolume* target = nullptr;
0583 if (!jBin.at(kTargetVolumeIdKey).is_null()) {
0584 const auto targetVolumeId =
0585 jBin.at(kTargetVolumeIdKey).get<std::size_t>();
0586 target = volumes.at(targetVolumeId);
0587 }
0588 view.atLocalBins(localIndices) = target;
0589 }
0590
0591 std::vector<Acts::TrivialPortalLink> artifacts;
0592 if (encoded.contains(kArtifactLinksKey)) {
0593 for (const auto& jArtifact : encoded.at(kArtifactLinksKey)) {
0594 auto decodedArtifact =
0595 converter.portalLinkFromJson(jArtifact, surfaces, volumes);
0596 auto* trivial =
0597 dynamic_cast<Acts::TrivialPortalLink*>(decodedArtifact.get());
0598 if (trivial == nullptr) {
0599 throw std::invalid_argument(
0600 "GridPortalLink artifact link is not trivial");
0601 }
0602 artifacts.push_back(std::move(*trivial));
0603 }
0604 }
0605 grid->setArtifactPortalLinks(std::move(artifacts));
0606
0607 return grid;
0608 }
0609
0610
0611
0612
0613 struct SurfaceRecord {
0614 std::size_t surfaceId = 0u;
0615 nlohmann::json payload;
0616 };
0617
0618 struct PortalRecord {
0619 std::size_t portalId = 0u;
0620 nlohmann::json payload;
0621 };
0622
0623 struct VolumeRecord {
0624 std::size_t volumeId = 0u;
0625 std::string name;
0626 Acts::GeometryIdentifier::Value geometryId = 0u;
0627 Acts::Transform3 transform{Acts::Transform3::Identity()};
0628 nlohmann::json bounds;
0629 std::vector<std::size_t> children;
0630 std::vector<std::size_t> portalIds;
0631 std::vector<std::size_t> surfaceIds;
0632 nlohmann::json navigationPolicy;
0633 };
0634
0635
0636
0637
0638 void verifySchemaHeader(const nlohmann::json& encoded) {
0639 if (!encoded.contains(kHeaderKey)) {
0640 throw std::invalid_argument("Missing geometry JSON header");
0641 }
0642 const auto& header = encoded.at(kHeaderKey);
0643 if (header.at(kVersionKey).get<int>() != kFormatVersion) {
0644 throw std::invalid_argument("Unsupported geometry JSON format version");
0645 }
0646 if (header.at(kScopeKey).get<std::string>() != kScopeValue) {
0647 throw std::invalid_argument("Unexpected geometry JSON scope");
0648 }
0649 }
0650
0651 void ensureIdentifiers(Acts::TrackingVolume& volume,
0652 Acts::GeometryIdentifier::Value& nextVolumeId) {
0653 Acts::GeometryIdentifier volumeId = volume.geometryId();
0654 if (volumeId == Acts::GeometryIdentifier{}) {
0655 volumeId = Acts::GeometryIdentifier{}.withVolume(nextVolumeId++);
0656 volume.assignGeometryId(volumeId);
0657 }
0658
0659 for (const auto [ib, boundary] : Acts::enumerate(volume.boundarySurfaces())) {
0660
0661 auto& mutableBoundarySurface =
0662 const_cast<Acts::RegularSurface&>(boundary->surfaceRepresentation());
0663 if (mutableBoundarySurface.geometryId() == Acts::GeometryIdentifier{}) {
0664 mutableBoundarySurface.assignGeometryId(
0665 Acts::GeometryIdentifier(volumeId).withBoundary(ib + 1u));
0666 }
0667 }
0668
0669 std::size_t portalIndex = 0u;
0670 for (auto& portal : volume.portals()) {
0671 if (auto& mutablePortalSurface = portal.surface();
0672 mutablePortalSurface.geometryId() == Acts::GeometryIdentifier{}) {
0673 mutablePortalSurface.assignGeometryId(
0674 Acts::GeometryIdentifier(volumeId).withExtra(portalIndex + 1u));
0675 }
0676 ++portalIndex;
0677 }
0678
0679 for (auto& child : volume.volumes()) {
0680 ensureIdentifiers(child, nextVolumeId);
0681 }
0682 }
0683
0684 void collectGeometry(
0685 const Acts::TrackingVolume& volume,
0686 std::vector<const Acts::Surface*>& orderedSurfaces,
0687 std::vector<const Acts::Portal*>& orderedPortals,
0688 std::vector<const Acts::TrackingVolume*>& orderedVolumes,
0689 Acts::TrackingGeometryJsonConverter::SurfaceIdLookup& surfaceIds,
0690 Acts::TrackingGeometryJsonConverter::PortalIdLookup& portalIds,
0691 Acts::TrackingGeometryJsonConverter::VolumeIdLookup& volumeIds) {
0692 auto insertSurface = [&](const auto& surf) {
0693 if (surfaceIds.emplace(surf, orderedSurfaces.size())) {
0694 orderedSurfaces.push_back(&surf);
0695 }
0696 };
0697 auto insertPortal = [&](const auto& port) {
0698 if (portalIds.emplace(port, orderedPortals.size())) {
0699 orderedPortals.push_back(&port);
0700 }
0701 };
0702 auto insertVolume = [&](const auto& vol) {
0703 if (volumeIds.emplace(vol, orderedVolumes.size())) {
0704 orderedVolumes.push_back(&vol);
0705 }
0706 };
0707
0708 auto insertPortalLink = [&](const auto& link, auto&& self) {
0709 insertSurface(link->surface());
0710
0711 if (const auto* trivial =
0712 dynamic_cast<const Acts::TrivialPortalLink*>(link);
0713 trivial != nullptr) {
0714 return;
0715 }
0716
0717 if (const auto* composite =
0718 dynamic_cast<const Acts::CompositePortalLink*>(link);
0719 composite != nullptr) {
0720 const auto& children = composite->links();
0721 for (const auto& child : children) {
0722 self(&child, self);
0723 }
0724 return;
0725 }
0726
0727 const auto* grid = dynamic_cast<const Acts::GridPortalLink*>(link);
0728 if (grid != nullptr) {
0729 const auto& children = grid->artifactPortalLinks();
0730 for (const auto& child : children) {
0731 self(&child, self);
0732 }
0733 return;
0734 }
0735 };
0736
0737 insertVolume(volume);
0738
0739 for (const auto& portal : volume.portals()) {
0740 insertPortal(portal);
0741
0742 insertSurface(portal.surface());
0743
0744 if (const auto* along = portal.getLink(Acts::Direction::AlongNormal());
0745 along != nullptr) {
0746 insertPortalLink(along, insertPortalLink);
0747 }
0748
0749 if (const auto* opposite =
0750 portal.getLink(Acts::Direction::OppositeNormal());
0751 opposite != nullptr) {
0752 insertPortalLink(opposite, insertPortalLink);
0753 }
0754 }
0755 for (const auto& surf : volume.surfaces()) {
0756 insertSurface(surf);
0757 }
0758
0759 for (const auto& child : volume.volumes()) {
0760 collectGeometry(child, orderedSurfaces, orderedPortals, orderedVolumes,
0761 surfaceIds, portalIds, volumeIds);
0762 }
0763 }
0764
0765 }
0766
0767 Acts::TrackingGeometryJsonConverter::Config
0768 Acts::TrackingGeometryJsonConverter::Config::defaultConfig() {
0769 Config cfg;
0770
0771 cfg.encodeVolumeBounds.registerFunction(encodeVolumeBoundsT<ConeVolumeBounds>)
0772 .registerFunction(encodeVolumeBoundsT<CuboidVolumeBounds>)
0773 .registerFunction(encodeVolumeBoundsT<CutoutCylinderVolumeBounds>)
0774 .registerFunction(encodeVolumeBoundsT<CylinderVolumeBounds>)
0775 .registerFunction(encodeVolumeBoundsT<DiamondVolumeBounds>)
0776 .registerFunction(encodeVolumeBoundsT<GenericCuboidVolumeBounds>)
0777 .registerFunction(encodeVolumeBoundsT<TrapezoidVolumeBounds>);
0778
0779 cfg.encodeNavigationPolicy.registerFunction(encodeTryAllNavigationPolicy)
0780 .registerFunction(encodeSurfaceArrayNavigationPolicy)
0781 .registerFunction(encodeMultiNavigationPolicy)
0782 .registerFunction(encodeMultiLayerNavigationPolicy);
0783
0784 cfg.encodePortalLink.registerFunction(encodeTrivialPortalLink)
0785 .registerFunction(encodeCompositePortalLink)
0786 .registerFunction(encodeGridPortalLink);
0787
0788 cfg.decodePortalLink
0789 .registerKind(getPortalLinkKind<TrivialPortalLink>(),
0790 decodeTrivialPortalLink)
0791 .registerKind(getPortalLinkKind<CompositePortalLink>(),
0792 decodeCompositePortalLink)
0793 .registerKind(getPortalLinkKind<GridPortalLink>(), decodeGridPortalLink);
0794
0795 cfg.decodeVolumeBounds
0796 .registerKind(getVolumeBoundsKind<ConeVolumeBounds>(),
0797 decodeVolumeBoundsT<ConeVolumeBounds>)
0798 .registerKind(getVolumeBoundsKind<CuboidVolumeBounds>(),
0799 decodeVolumeBoundsT<CuboidVolumeBounds>)
0800 .registerKind(getVolumeBoundsKind<CutoutCylinderVolumeBounds>(),
0801 decodeVolumeBoundsT<CutoutCylinderVolumeBounds>)
0802 .registerKind(getVolumeBoundsKind<CylinderVolumeBounds>(),
0803 decodeVolumeBoundsT<CylinderVolumeBounds>)
0804 .registerKind(getVolumeBoundsKind<DiamondVolumeBounds>(),
0805 decodeVolumeBoundsT<DiamondVolumeBounds>)
0806 .registerKind(getVolumeBoundsKind<GenericCuboidVolumeBounds>(),
0807 decodeVolumeBoundsT<GenericCuboidVolumeBounds>)
0808 .registerKind(getVolumeBoundsKind<TrapezoidVolumeBounds>(),
0809 decodeVolumeBoundsT<TrapezoidVolumeBounds>);
0810
0811 cfg.decodeNavigationPolicy
0812 .registerKind(getNavigationPolicyKind<TryAllNavigationPolicy>(),
0813 decodeTryAllNavigationPolicy)
0814 .registerKind(getNavigationPolicyKind<SurfaceArrayNavigationPolicy>(),
0815 decodeSurfaceArrayNavigationPolicy)
0816 .registerKind(getNavigationPolicyKind<MultiNavigationPolicy>(),
0817 decodeMultiNavigationPolicy)
0818 .registerKind(
0819 getNavigationPolicyKind<Experimental::MultiLayerNavigationPolicy>(),
0820 decodeMultiLayerNavigationPolicy);
0821
0822 return cfg;
0823 }
0824
0825 Acts::TrackingGeometryJsonConverter::TrackingGeometryJsonConverter(
0826 Config config, std::unique_ptr<const Acts::Logger> logger)
0827 : m_cfg(std::move(config)), m_logger(std::move(logger)) {}
0828
0829 nlohmann::json Acts::TrackingGeometryJsonConverter::toJson(
0830 const GeometryContext& gctx, const TrackingGeometry& geometry,
0831 const Options& options) const {
0832 if (geometry.geometryVersion() != TrackingGeometry::GeometryVersion::Gen3) {
0833 throw std::invalid_argument(
0834 "Tracking geometry serialization is only implemented for Gen3 "
0835 "geometries");
0836 }
0837 ACTS_DEBUG("Serializing TrackingGeometry to JSON");
0838 return trackingVolumeToJson(gctx, *geometry.highestTrackingVolume(), options);
0839 }
0840
0841 nlohmann::json Acts::TrackingGeometryJsonConverter::portalLinkToJson(
0842 const GeometryContext& gctx, const PortalLinkBase& link,
0843 const SurfaceIdLookup& surfaceIds, const VolumeIdLookup& volumeIds) const {
0844 return m_cfg.encodePortalLink(link, gctx, *this, surfaceIds, volumeIds);
0845 }
0846
0847 std::unique_ptr<Acts::PortalLinkBase>
0848 Acts::TrackingGeometryJsonConverter::portalLinkFromJson(
0849 const nlohmann::json& encoded, const SurfacePointerLookup& surfaces,
0850 const VolumePointerLookup& volumes) const {
0851 return m_cfg.decodePortalLink(encoded, *this, surfaces, volumes);
0852 }
0853
0854 nlohmann::json Acts::TrackingGeometryJsonConverter::navigationPolicyToJson(
0855 const Acts::INavigationPolicy& policy) const {
0856 return m_cfg.encodeNavigationPolicy(policy, *this);
0857 }
0858
0859 std::unique_ptr<Acts::INavigationPolicy>
0860 Acts::TrackingGeometryJsonConverter::navigationPolicyFromJson(
0861 const Acts::GeometryContext& gctx, const nlohmann::json& encoded,
0862 const Acts::TrackingVolume& volume, const Acts::Logger& logger) const {
0863 return m_cfg.decodeNavigationPolicy(encoded, gctx, *this, volume, logger);
0864 }
0865
0866 nlohmann::json Acts::TrackingGeometryJsonConverter::trackingVolumeToJson(
0867 const GeometryContext& gctx, const TrackingVolume& world,
0868 const Options& ) const {
0869 nlohmann::json encoded;
0870 encoded[kHeaderKey] = nlohmann::json::object();
0871 encoded[kHeaderKey][kVersionKey] = kFormatVersion;
0872 encoded[kHeaderKey][kScopeKey] = kScopeValue;
0873
0874
0875 std::vector<const Surface*> orderedSurfaces;
0876 std::vector<const Portal*> orderedPortals;
0877 std::vector<const TrackingVolume*> orderedVolumes;
0878
0879 SurfaceIdLookup surfaceIds;
0880 PortalIdLookup portalIds;
0881 VolumeIdLookup volumeIds;
0882 collectGeometry(world, orderedSurfaces, orderedPortals, orderedVolumes,
0883 surfaceIds, portalIds, volumeIds);
0884
0885 ACTS_DEBUG("Encoding " << orderedVolumes.size() << " volumes, "
0886 << orderedPortals.size() << " portals, "
0887 << orderedSurfaces.size()
0888 << " surfaces from root volume '" << world.volumeName()
0889 << "'");
0890
0891 encoded[kSurfacesKey] = nlohmann::json::array();
0892 encoded[kPortalsKey] = nlohmann::json::array();
0893 encoded[kVolumesKey] = nlohmann::json::array();
0894
0895 encoded[kRootVolumeIdKey] = volumeIds.at(world);
0896
0897
0898 for (const auto* surf : orderedSurfaces) {
0899 nlohmann::json jSurface = SurfaceJsonConverter::toJson(gctx, *surf);
0900 jSurface[kSurfaceIdKey] = surfaceIds.at(*surf);
0901 encoded[kSurfacesKey].push_back(std::move(jSurface));
0902 }
0903
0904
0905 for (const auto* portal : orderedPortals) {
0906 nlohmann::json jPortal;
0907 jPortal[kPortalIdKey] = portalIds.at(*portal);
0908
0909 if (const auto* along = portal->getLink(Direction::AlongNormal());
0910 along != nullptr) {
0911 jPortal[kAlongNormalKey] =
0912 portalLinkToJson(gctx, *along, surfaceIds, volumeIds);
0913 } else {
0914 jPortal[kAlongNormalKey] = nullptr;
0915 }
0916
0917 if (const auto* opposite = portal->getLink(Direction::OppositeNormal());
0918 opposite != nullptr) {
0919 jPortal[kOppositeNormalKey] =
0920 portalLinkToJson(gctx, *opposite, surfaceIds, volumeIds);
0921 } else {
0922 jPortal[kOppositeNormalKey] = nullptr;
0923 }
0924
0925 jPortal[kSurfaceIdKey] = surfaceIds.at(portal->surface());
0926 encoded[kPortalsKey].push_back(std::move(jPortal));
0927 }
0928
0929
0930 for (const auto* volume : orderedVolumes) {
0931 nlohmann::json jVolume;
0932 const auto volumeId = volumeIds.at(*volume);
0933 ACTS_VERBOSE("Encoding volume '" << volume->volumeName()
0934 << "' (id=" << volumeId << ")");
0935 jVolume[kVolumeIdKey] = volumeId;
0936 jVolume[kNameKey] = volume->volumeName();
0937 jVolume[kGeometryIdKey] = nlohmann::json(volume->geometryId());
0938 jVolume[kTransformKey] =
0939 Transform3JsonConverter::toJson(volume->localToGlobalTransform(gctx));
0940 jVolume[kBoundsKey] = m_cfg.encodeVolumeBounds(volume->volumeBounds());
0941
0942 jVolume[kNavigationPolicyKey] =
0943 navigationPolicyToJson(*volume->navigationPolicy());
0944
0945 jVolume[kChildrenKey] = nlohmann::json::array();
0946 for (const auto& child : volume->volumes()) {
0947 jVolume[kChildrenKey].push_back(volumeIds.at(child));
0948 }
0949
0950 jVolume[kPortalIdsKey] = nlohmann::json::array();
0951 for (const auto& portal : volume->portals()) {
0952 jVolume[kPortalIdsKey].push_back(portalIds.at(portal));
0953 }
0954
0955 jVolume[kSurfaceIdKey] = nlohmann::json::array();
0956 for (const auto& surface : volume->surfaces()) {
0957 jVolume[kSurfaceIdKey].push_back(surfaceIds.at(surface));
0958 }
0959
0960 encoded[kVolumesKey].push_back(std::move(jVolume));
0961 }
0962
0963 return encoded;
0964 }
0965
0966 std::shared_ptr<Acts::TrackingVolume>
0967 Acts::TrackingGeometryJsonConverter::trackingVolumeFromJson(
0968 const GeometryContext& gctx, const nlohmann::json& encoded,
0969 const Options& ) const {
0970 verifySchemaHeader(encoded);
0971
0972 if (!encoded.contains(kVolumesKey) || !encoded.contains(kRootVolumeIdKey) ||
0973 !encoded.contains(kPortalsKey)) {
0974 throw std::invalid_argument(
0975 "Missing volume payload in tracking geometry JSON");
0976 }
0977
0978
0979 std::unordered_map<std::size_t, SurfaceRecord> surfaceRecords;
0980 for (const auto& jSurface : encoded.at(kSurfacesKey)) {
0981 SurfaceRecord record;
0982 record.surfaceId = jSurface.at(kSurfaceIdKey).get<std::size_t>();
0983 record.payload = jSurface;
0984 const auto [id, inserted] =
0985 surfaceRecords.try_emplace(record.surfaceId, std::move(record));
0986 if (!inserted) {
0987 throw std::invalid_argument("Duplicate serialized surface ID");
0988 }
0989 }
0990
0991
0992 std::unordered_map<std::size_t, PortalRecord> portalRecords;
0993 for (const auto& jPortal : encoded.at(kPortalsKey)) {
0994 PortalRecord record;
0995 record.portalId = jPortal.at(kPortalIdKey).get<std::size_t>();
0996 record.payload = jPortal;
0997 const auto [id, inserted] =
0998 portalRecords.try_emplace(record.portalId, std::move(record));
0999 if (!inserted) {
1000 throw std::invalid_argument("Duplicate serialized portal ID");
1001 }
1002 }
1003
1004
1005 std::unordered_map<std::size_t, VolumeRecord> volumeRecords;
1006 for (const auto& jVolume : encoded.at(kVolumesKey)) {
1007 VolumeRecord record;
1008 record.volumeId = jVolume.at(kVolumeIdKey).get<std::size_t>();
1009 record.name = jVolume.at(kNameKey).get<std::string>();
1010 record.transform =
1011 Transform3JsonConverter::fromJson(jVolume.at(kTransformKey));
1012 record.bounds = jVolume.at(kBoundsKey);
1013 record.children = jVolume.value(kChildrenKey, std::vector<std::size_t>{});
1014 record.portalIds = jVolume.value(kPortalIdsKey, std::vector<std::size_t>{});
1015 record.surfaceIds =
1016 jVolume.value(kSurfaceIdKey, std::vector<std::size_t>{});
1017 record.navigationPolicy = jVolume.at(kNavigationPolicyKey);
1018
1019 if (!jVolume["geometry_id"].is_null()) {
1020 GeometryIdentifier geoID =
1021 jVolume["geometry_id"].get<GeometryIdentifier>();
1022 record.geometryId = geoID.value();
1023 } else {
1024 record.geometryId = 0;
1025 }
1026
1027 const auto [id, inserted] =
1028 volumeRecords.try_emplace(record.volumeId, std::move(record));
1029 if (!inserted) {
1030 throw std::invalid_argument("Duplicate serialized volume ID");
1031 }
1032 }
1033
1034 const std::size_t rootVolumeId =
1035 encoded.at(kRootVolumeIdKey).get<std::size_t>();
1036
1037 if (!volumeRecords.contains(rootVolumeId)) {
1038 throw std::invalid_argument("Serialized root volume ID does not exist");
1039 }
1040
1041 ACTS_DEBUG("Decoding " << volumeRecords.size() << " volumes, "
1042 << portalRecords.size() << " portals, "
1043 << surfaceRecords.size()
1044 << " surfaces from root volume '"
1045 << volumeRecords.at(rootVolumeId).name << "'");
1046
1047
1048 SurfacePointerLookup surfacePointers;
1049
1050
1051 for (const auto& [surfaceId, record] : surfaceRecords) {
1052 auto surface = regularSurfaceFromJson(record.payload);
1053 surfacePointers.emplace(surfaceId, surface);
1054 }
1055
1056
1057 std::unordered_map<std::size_t, std::unique_ptr<TrackingVolume>>
1058 volumeStorage;
1059 VolumePointerLookup volumePointers;
1060
1061 for (const auto& [volumeId, record] : volumeRecords) {
1062 auto volumeBounds = m_cfg.decodeVolumeBounds(record.bounds);
1063 auto volume = std::make_unique<TrackingVolume>(
1064 record.transform, std::move(volumeBounds), record.name);
1065
1066 GeometryIdentifier geometryId(record.geometryId);
1067 if (geometryId == GeometryIdentifier{}) {
1068 geometryId = GeometryIdentifier{}.withVolume(volumeId + 1u);
1069 }
1070 volume->assignGeometryId(geometryId);
1071 volumePointers.emplace(volumeId, volume.get());
1072 volumeStorage.emplace(volumeId, std::move(volume));
1073 }
1074
1075 std::unordered_set<std::size_t> visiting;
1076 std::unordered_set<std::size_t> built;
1077
1078
1079 auto attachChildren = [&](std::size_t volumeId, auto&& self) {
1080 if (built.contains(volumeId)) {
1081 return;
1082 }
1083 ACTS_VERBOSE("Assembling volume '" << volumeRecords.at(volumeId).name
1084 << "' (id=" << volumeId << ")");
1085 if (!visiting.insert(volumeId).second) {
1086 throw std::invalid_argument(
1087 "Cycle detected in serialized volume hierarchy");
1088 }
1089
1090 const auto& parent = volumeStorage.at(volumeId);
1091 if (parent == nullptr) {
1092 throw std::invalid_argument("Volume was already moved unexpectedly");
1093 }
1094
1095 for (std::size_t childId : volumeRecords.at(volumeId).children) {
1096 self(childId, self);
1097 auto& child = volumeStorage.at(childId);
1098 if (child == nullptr) {
1099 throw std::invalid_argument(
1100 "Serialized child volume has already been attached");
1101 }
1102 parent->addVolume(std::move(child));
1103 }
1104
1105 visiting.erase(volumeId);
1106 built.insert(volumeId);
1107 };
1108
1109 attachChildren(rootVolumeId, attachChildren);
1110 ACTS_DEBUG("Volume hierarchy assembled (" << built.size() << " volumes)");
1111 if (built.size() != volumeRecords.size()) {
1112 ACTS_WARNING(volumeRecords.size() - built.size()
1113 << " volume(s) in the JSON are not reachable from the root "
1114 "and were not assembled into the hierarchy");
1115 }
1116
1117 auto decodePortal = [&](const nlohmann::json& jPortal) {
1118 std::unique_ptr<PortalLinkBase> along = nullptr;
1119 std::unique_ptr<PortalLinkBase> opposite = nullptr;
1120
1121 if (!jPortal.at(kAlongNormalKey).is_null()) {
1122 along = portalLinkFromJson(jPortal.at(kAlongNormalKey), surfacePointers,
1123 volumePointers);
1124 }
1125 if (!jPortal.at(kOppositeNormalKey).is_null()) {
1126 opposite = portalLinkFromJson(jPortal.at(kOppositeNormalKey),
1127 surfacePointers, volumePointers);
1128 }
1129 if (along == nullptr && opposite == nullptr) {
1130 throw std::invalid_argument("Portal has no links");
1131 }
1132
1133 auto portal =
1134 std::make_shared<Portal>(gctx, std::move(along), std::move(opposite));
1135 portal->surface().assignGeometryId(
1136 surfacePointers.at(jPortal.at(kSurfaceIdKey))->geometryId());
1137 return portal;
1138 };
1139
1140 PortalPointerLookup portalPointers;
1141 for (const auto& [portalId, record] : portalRecords) {
1142 const auto inserted =
1143 portalPointers.emplace(portalId, decodePortal(record.payload));
1144 if (!inserted) {
1145 throw std::invalid_argument("Portal pointer reconstruction failed");
1146 }
1147 }
1148 ACTS_DEBUG("Decoded " << portalRecords.size() << " portals");
1149
1150 for (const auto& [volumeId, record] : volumeRecords) {
1151 auto* volume = volumePointers.find(volumeId);
1152 if (volume == nullptr) {
1153 throw std::invalid_argument("Volume pointer reconstruction failed");
1154 }
1155
1156 ACTS_VERBOSE("Attaching " << record.portalIds.size() << " portals and "
1157 << record.surfaceIds.size()
1158 << " surfaces to volume '" << record.name << "'");
1159
1160 for (const std::size_t portalId : record.portalIds) {
1161 volume->addPortal(portalPointers.at(portalId));
1162 }
1163 for (const std::size_t surfaceId : record.surfaceIds) {
1164 volume->addSurface(surfacePointers.at(surfaceId));
1165 }
1166
1167 volume->setNavigationPolicy(navigationPolicyFromJson(
1168 gctx, record.navigationPolicy, *volume, logger()));
1169 }
1170
1171 auto root = std::move(volumeStorage.at(rootVolumeId));
1172 if (root == nullptr) {
1173 throw std::invalid_argument("Root volume reconstruction failed");
1174 }
1175
1176 return std::shared_ptr<TrackingVolume>(std::move(root));
1177 }
1178
1179 std::shared_ptr<Acts::TrackingGeometry>
1180 Acts::TrackingGeometryJsonConverter::fromJson(const GeometryContext& gctx,
1181 const nlohmann::json& encoded,
1182 const Options& options) const {
1183 ACTS_DEBUG("Reconstructing TrackingGeometry from JSON");
1184 auto world = trackingVolumeFromJson(gctx, encoded, options);
1185
1186 GeometryIdentifier::Value nextVolumeId = 1u;
1187 ensureIdentifiers(*world, nextVolumeId);
1188
1189 ACTS_DEBUG("TrackingGeometry reconstruction complete, root volume '"
1190 << world->volumeName() << "'");
1191 return std::make_shared<TrackingGeometry>(
1192 world, nullptr, GeometryIdentifierHook{}, getDummyLogger(), false);
1193 }