Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2026-05-08 08:01:24

0001 // This file is part of the ACTS project.
0002 //
0003 // Copyright (C) 2016 CERN for the benefit of the ACTS project
0004 //
0005 // This Source Code Form is subject to the terms of the Mozilla Public
0006 // License, v. 2.0. If a copy of the MPL was not distributed with this
0007 // file, You can obtain one at https://mozilla.org/MPL/2.0/.
0008 
0009 #include <boost/test/tools/old/interface.hpp>
0010 #include <boost/test/tree/test_unit.hpp>
0011 #include <boost/test/unit_test.hpp>
0012 
0013 #include "Acts/Definitions/Algebra.hpp"
0014 #include "Acts/Definitions/Direction.hpp"
0015 #include "Acts/Geometry/Blueprint.hpp"
0016 #include "Acts/Geometry/CompositePortalLink.hpp"
0017 #include "Acts/Geometry/CuboidVolumeBounds.hpp"
0018 #include "Acts/Geometry/CylinderVolumeBounds.hpp"
0019 #include "Acts/Geometry/CylinderVolumeHelper.hpp"
0020 #include "Acts/Geometry/GeometryContext.hpp"
0021 #include "Acts/Geometry/GridPortalLink.hpp"
0022 #include "Acts/Geometry/LayerBlueprintNode.hpp"
0023 #include "Acts/Geometry/Portal.hpp"
0024 #include "Acts/Geometry/TrackingGeometry.hpp"
0025 #include "Acts/Geometry/TrackingVolume.hpp"
0026 #include "Acts/Geometry/TrivialPortalLink.hpp"
0027 #include "Acts/MagneticField/ConstantBField.hpp"
0028 #include "Acts/Navigation/MultiLayerNavigationPolicy.hpp"
0029 #include "Acts/Navigation/TryAllNavigationPolicy.hpp"
0030 #include "Acts/Propagator/EigenStepper.hpp"
0031 #include "Acts/Propagator/Navigator.hpp"
0032 #include "Acts/Propagator/Propagator.hpp"
0033 #include "Acts/Surfaces/PlaneSurface.hpp"
0034 #include "Acts/Surfaces/RectangleBounds.hpp"
0035 #include "Acts/Surfaces/Surface.hpp"
0036 #include "Acts/Utilities/AnyGridView.hpp"
0037 #include "Acts/Utilities/Axis.hpp"
0038 #include "Acts/Utilities/AxisDefinitions.hpp"
0039 #include "Acts/Utilities/Logger.hpp"
0040 #include "ActsPlugins/Json/TrackingGeometryJsonConverter.hpp"
0041 #include "ActsTests/CommonHelpers/CylindricalTrackingGeometry.hpp"
0042 #include "ActsTests/CommonHelpers/TemporaryDirectory.hpp"
0043 
0044 #include <cstddef>
0045 #include <fstream>
0046 #include <functional>
0047 #include <memory>
0048 #include <utility>
0049 #include <vector>
0050 
0051 namespace ActsTests {
0052 
0053 using namespace Acts;
0054 using namespace Experimental;
0055 using namespace UnitLiterals;
0056 using enum CylinderVolumeBounds::Face;
0057 using enum AxisDirection;
0058 
0059 using ConstantFieldStepper = Acts::EigenStepper<>;
0060 using ConstantFieldPropagator =
0061     Acts::Propagator<ConstantFieldStepper, Acts::Navigator>;
0062 
0063 template <typename navigator_t = Navigator,
0064           typename stepper_t = ConstantFieldStepper>
0065 struct StateCollector {
0066   struct this_result {
0067     std::vector<typename navigator_t::State> navigation;
0068     std::vector<typename stepper_t::State> stepping;
0069   };
0070 
0071   using result_type = this_result;
0072 
0073   template <typename propagator_state_t>
0074   Result<void> act(propagator_state_t& state, const stepper_t& /*stepper*/,
0075                    const navigator_t& /*navigator*/, result_type& result,
0076                    const Logger& /*logger*/) const {
0077     result.navigation.push_back(state.navigation);
0078     result.stepping.push_back(state.stepping);
0079     return {};
0080   }
0081 };
0082 
0083 void checkHierarchy(const GeometryContext& gctx,
0084                     const TrackingVolume::VolumeRange& volsA,
0085                     const TrackingVolume::VolumeRange& volsB) {
0086   auto checkSurfaces = [&](const auto& surfA, const auto& surfB) {
0087     BOOST_CHECK_EQUAL(surfA.type(), surfB.type());
0088     BOOST_CHECK_EQUAL(surfA.geometryId(), surfB.geometryId());
0089     BOOST_CHECK_EQUAL(surfA.bounds(), surfB.bounds());
0090 
0091     BOOST_CHECK_LT((surfA.localToGlobalTransform(gctx).matrix() -
0092                     surfB.localToGlobalTransform(gctx).matrix())
0093                        .norm(),
0094                    1e-10);
0095     const auto* matA = surfA.surfaceMaterial();
0096     const auto* matB = surfB.surfaceMaterial();
0097     if (matA != nullptr) {
0098       BOOST_REQUIRE_NE(matB, nullptr);
0099       BOOST_CHECK_EQUAL(surfA.surfaceMaterial()->materialSlab(Vector2{0, 0}),
0100                         surfB.surfaceMaterial()->materialSlab(Vector2{0, 0}));
0101     }
0102   };
0103   std::function<void(const Acts::PortalLinkBase* linkA,
0104                      const Acts::PortalLinkBase* linkB)>
0105       checkLinks = [&](const auto* linkA, const auto* linkB) {
0106         const auto* trivialA =
0107             dynamic_cast<const Acts::TrivialPortalLink*>(linkA);
0108         const auto* trivialB =
0109             dynamic_cast<const Acts::TrivialPortalLink*>(linkB);
0110         if (trivialA != nullptr) {
0111           BOOST_REQUIRE_NE(trivialB, nullptr);
0112           BOOST_CHECK(trivialA->volume() == trivialB->volume());
0113           checkSurfaces(trivialA->surface(), trivialB->surface());
0114         }
0115 
0116         const auto* gridA = dynamic_cast<const Acts::GridPortalLink*>(linkA);
0117         const auto* gridB = dynamic_cast<const Acts::GridPortalLink*>(linkB);
0118         if (gridA != nullptr) {
0119           BOOST_REQUIRE_NE(gridB, nullptr);
0120           BOOST_CHECK_EQUAL(gridA->dim(), gridB->dim());
0121           BOOST_CHECK_EQUAL(gridA->direction(), gridB->direction());
0122           BOOST_CHECK(gridA->grid() == gridB->grid());
0123           checkSurfaces(gridA->surface(), gridB->surface());
0124 
0125           const auto& childrenA = gridA->artifactPortalLinks();
0126           const auto& childrenB = gridA->artifactPortalLinks();
0127           for (std::size_t i = 0; i < childrenA.size(); i++) {
0128             const auto& childA = *(childrenA.begin() + i);
0129             const auto& childB = *(childrenB.begin() + i);
0130             checkLinks(&childA, &childB);
0131           }
0132         }
0133 
0134         const auto* compositeA =
0135             dynamic_cast<const Acts::CompositePortalLink*>(linkA);
0136         const auto* compositeB =
0137             dynamic_cast<const Acts::CompositePortalLink*>(linkB);
0138         if (compositeA != nullptr) {
0139           BOOST_REQUIRE_NE(compositeB, nullptr);
0140           const auto& childrenA = compositeA->links();
0141           const auto& childrenB = compositeB->links();
0142           for (std::size_t i = 0; i < childrenA.size(); i++) {
0143             const auto& childA = childrenA.at(i);
0144             const auto& childB = childrenB.at(i);
0145             checkLinks(&childA, &childB);
0146           }
0147         }
0148       };
0149 
0150   BOOST_CHECK_EQUAL(volsA.size(), volsB.size());
0151   for (std::size_t i = 0; i < volsA.size(); i++) {
0152     const auto& volA = volsA.at(i);
0153     const auto& volB = volsB.at(i);
0154     BOOST_CHECK(volA == volB);
0155 
0156     const auto& surfsA = volA.surfaces();
0157     const auto& surfsB = volB.surfaces();
0158     BOOST_CHECK_EQUAL(surfsA.size(), surfsB.size());
0159     for (std::size_t j = 0; j < surfsA.size(); j++) {
0160       checkSurfaces(surfsA.at(i), surfsB.at(i));
0161     }
0162 
0163     const auto& portsA = volA.portals();
0164     const auto& portsB = volB.portals();
0165     BOOST_CHECK_EQUAL(portsA.size(), portsB.size());
0166     for (std::size_t j = 0; j < portsA.size(); j++) {
0167       const auto& portA = portsA.at(j);
0168       const auto& portB = portsB.at(j);
0169 
0170       const auto& surfA = portA.surface();
0171       const auto& surfB = portB.surface();
0172       checkSurfaces(surfA, surfB);
0173 
0174       const auto* alongA = portA.getLink(Acts::Direction::AlongNormal());
0175       const auto* alongB = portB.getLink(Acts::Direction::AlongNormal());
0176       checkLinks(alongA, alongB);
0177 
0178       const auto* oppositeA = portA.getLink(Acts::Direction::OppositeNormal());
0179       const auto* oppositeB = portB.getLink(Acts::Direction::OppositeNormal());
0180       checkLinks(oppositeA, oppositeB);
0181     }
0182     checkHierarchy(gctx, volA.volumes(), volB.volumes());
0183   }
0184 }
0185 
0186 auto logger = getDefaultLogger("UnitTests", Logging::INFO);
0187 
0188 BOOST_AUTO_TEST_SUITE(JsonSuite)
0189 
0190 BOOST_AUTO_TEST_CASE(TrackingGeometryJsonConverterRoundTrip) {
0191   using namespace Acts;
0192 
0193   GeometryContext gctx = GeometryContext::dangerouslyDefaultConstruct();
0194 
0195   TryAllNavigationPolicy::Config tryAllConfig;
0196   tryAllConfig.portals = true;
0197   tryAllConfig.sensitives = false;
0198   std::unique_ptr<NavigationPolicyFactory> navPolicyFactory =
0199       NavigationPolicyFactory{}
0200           .add<TryAllNavigationPolicy>(tryAllConfig)
0201           .asUniquePtr();
0202 
0203   auto root = std::make_shared<TrackingVolume>(
0204       Transform3::Identity(), std::make_shared<CuboidVolumeBounds>(5., 5., 5.),
0205       "root");
0206   root->assignGeometryId(GeometryIdentifier{}.withVolume(1u));
0207   TrackingVolume* rootPtr = root.get();
0208   rootPtr->setNavigationPolicy(
0209       navPolicyFactory->build(gctx, *rootPtr, *logger));
0210 
0211   Transform3 childTransform = Transform3::Identity();
0212   childTransform.pretranslate(Vector3{1., 0., 0.});
0213   auto child = std::make_unique<TrackingVolume>(
0214       childTransform, std::make_shared<CylinderVolumeBounds>(0.5, 1.0, 2.0),
0215       "child");
0216   child->assignGeometryId(GeometryIdentifier{}.withVolume(2u));
0217   TrackingVolume* childPtr = child.get();
0218   childPtr->setNavigationPolicy(
0219       navPolicyFactory->build(gctx, *childPtr, *logger));
0220   root->addVolume(std::move(child));
0221 
0222   auto trivialBounds = std::make_shared<const RectangleBounds>(1., 1.);
0223   auto trivialSurface =
0224       Surface::makeShared<PlaneSurface>(Transform3::Identity(), trivialBounds);
0225   trivialSurface->assignGeometryId(
0226       GeometryIdentifier{}.withVolume(1u).withExtra(11u));
0227   auto trivialLink =
0228       std::make_unique<TrivialPortalLink>(trivialSurface, *childPtr);
0229   root->addPortal(std::make_shared<Portal>(Direction::AlongNormal(),
0230                                            std::move(trivialLink)));
0231 
0232   auto compositeBounds = std::make_shared<const RectangleBounds>(1., 1.);
0233   Transform3 compositeTransformA = Transform3::Identity();
0234   compositeTransformA.pretranslate(Vector3{-1., 0., 0.});
0235   Transform3 compositeTransformB = Transform3::Identity();
0236   compositeTransformB.pretranslate(Vector3{1., 0., 0.});
0237   auto compositeSurfaceA =
0238       Surface::makeShared<PlaneSurface>(compositeTransformA, compositeBounds);
0239   auto compositeSurfaceB =
0240       Surface::makeShared<PlaneSurface>(compositeTransformB, compositeBounds);
0241   compositeSurfaceA->assignGeometryId(
0242       GeometryIdentifier{}.withVolume(1u).withExtra(12u));
0243   compositeSurfaceB->assignGeometryId(
0244       GeometryIdentifier{}.withVolume(1u).withExtra(13u));
0245   auto compositeLinkA =
0246       std::make_unique<TrivialPortalLink>(compositeSurfaceA, *childPtr);
0247   auto compositeLinkB =
0248       std::make_unique<TrivialPortalLink>(compositeSurfaceB, *rootPtr);
0249   auto compositeLink = std::make_unique<CompositePortalLink>(
0250       std::move(compositeLinkA), std::move(compositeLinkB),
0251       AxisDirection::AxisX);
0252   root->addPortal(std::make_shared<Portal>(Direction::AlongNormal(),
0253                                            std::move(compositeLink)));
0254 
0255   auto gridBounds = std::make_shared<const RectangleBounds>(2., 1.);
0256   auto gridSurface =
0257       Surface::makeShared<PlaneSurface>(Transform3::Identity(), gridBounds);
0258   gridSurface->assignGeometryId(
0259       GeometryIdentifier{}.withVolume(1u).withExtra(14u));
0260   auto gridLink = GridPortalLink::make(gridSurface, AxisDirection::AxisX,
0261                                        Axis{AxisBound, -2., 2., 2});
0262   AnyGridView<const TrackingVolume*> gridView(gridLink->grid());
0263   gridView.atLocalBins({0u}) = rootPtr;
0264   gridView.atLocalBins({1u}) = childPtr;
0265   gridView.atLocalBins({2u}) = rootPtr;
0266   gridView.atLocalBins({3u}) = childPtr;
0267 
0268   std::vector<TrivialPortalLink> artifacts;
0269   auto artifactSurface =
0270       Surface::makeShared<PlaneSurface>(Transform3::Identity(), gridBounds);
0271   artifactSurface->assignGeometryId(
0272       GeometryIdentifier{}.withVolume(1u).withExtra(15u));
0273   artifacts.emplace_back(artifactSurface, *childPtr);
0274   gridLink->setArtifactPortalLinks(std::move(artifacts));
0275 
0276   root->addPortal(
0277       std::make_shared<Portal>(Direction::AlongNormal(), std::move(gridLink)));
0278 
0279   auto sharedPortalBounds = std::make_shared<const RectangleBounds>(0.75, 0.75);
0280   auto sharedPortalSurface = Surface::makeShared<PlaneSurface>(
0281       Transform3::Identity(), sharedPortalBounds);
0282   sharedPortalSurface->assignGeometryId(
0283       GeometryIdentifier{}.withVolume(1u).withExtra(16u));
0284 
0285   auto sharedPortal = std::make_shared<Portal>(
0286       gctx, std::make_unique<TrivialPortalLink>(sharedPortalSurface, *childPtr),
0287       std::make_unique<TrivialPortalLink>(sharedPortalSurface, *rootPtr));
0288   root->addPortal(sharedPortal);
0289   childPtr->addPortal(sharedPortal);
0290 
0291   TrackingGeometryJsonConverter converter;
0292   nlohmann::json encoded = converter.trackingVolumeToJson(gctx, *root);
0293   TemporaryDirectory tmpDir{};
0294   auto jsonPath = tmpDir.path() / "tracking_geometry_roundtrip.json";
0295   {
0296     std::ofstream out(jsonPath);
0297     BOOST_REQUIRE(out.good());
0298     out << encoded.dump(2);
0299   }
0300 
0301   nlohmann::json encodedFromFile;
0302   {
0303     std::ifstream in(jsonPath);
0304     BOOST_REQUIRE(in.good());
0305     in >> encodedFromFile;
0306   }
0307 
0308   auto decodedRoot = converter.trackingVolumeFromJson(gctx, encodedFromFile);
0309 
0310   BOOST_REQUIRE(decodedRoot != nullptr);
0311   BOOST_CHECK_EQUAL(decodedRoot->volumeName(), "root");
0312   BOOST_CHECK_EQUAL(decodedRoot->volumeBounds().type(), VolumeBounds::eCuboid);
0313 
0314   std::vector<TrackingVolume*> decodedChildren;
0315   for (auto& decodedChild : decodedRoot->volumes()) {
0316     decodedChildren.push_back(&decodedChild);
0317   }
0318   BOOST_REQUIRE_EQUAL(decodedChildren.size(), 1u);
0319   BOOST_CHECK_EQUAL(decodedChildren.front()->volumeName(), "child");
0320   BOOST_CHECK_EQUAL(decodedChildren.front()->volumeBounds().type(),
0321                     VolumeBounds::eCylinder);
0322 
0323   std::vector<Portal*> decodedPortals;
0324   for (auto& portal : decodedRoot->portals()) {
0325     decodedPortals.push_back(&portal);
0326   }
0327   BOOST_REQUIRE_EQUAL(decodedPortals.size(), 4u);
0328 
0329   const auto* decodedTrivial = dynamic_cast<const TrivialPortalLink*>(
0330       decodedPortals.at(0)->getLink(Direction::AlongNormal()));
0331   BOOST_REQUIRE(decodedTrivial != nullptr);
0332   BOOST_CHECK_EQUAL(decodedTrivial->volume().volumeName(), "child");
0333 
0334   const auto* decodedComposite = dynamic_cast<const CompositePortalLink*>(
0335       decodedPortals.at(1)->getLink(Direction::AlongNormal()));
0336   BOOST_REQUIRE(decodedComposite != nullptr);
0337   BOOST_CHECK_EQUAL(decodedComposite->size(), 2u);
0338   BOOST_CHECK_EQUAL(decodedComposite->direction(), AxisDirection::AxisX);
0339 
0340   const auto* decodedGrid = dynamic_cast<const GridPortalLink*>(
0341       decodedPortals.at(2)->getLink(Direction::AlongNormal()));
0342   BOOST_REQUIRE(decodedGrid != nullptr);
0343   BOOST_CHECK_EQUAL(decodedGrid->dim(), 1u);
0344   BOOST_CHECK_EQUAL(decodedGrid->artifactPortalLinks().size(), 1u);
0345 
0346   AnyGridConstView<const TrackingVolume*> decodedGridView(decodedGrid->grid());
0347   BOOST_REQUIRE(decodedGridView.atLocalBins({0u}) != nullptr);
0348   BOOST_REQUIRE(decodedGridView.atLocalBins({1u}) != nullptr);
0349   BOOST_REQUIRE(decodedGridView.atLocalBins({2u}) != nullptr);
0350   BOOST_REQUIRE(decodedGridView.atLocalBins({3u}) != nullptr);
0351 
0352   BOOST_CHECK_EQUAL(decodedGridView.atLocalBins({0u})->volumeName(), "root");
0353   BOOST_CHECK_EQUAL(decodedGridView.atLocalBins({1u})->volumeName(), "child");
0354   BOOST_CHECK_EQUAL(decodedGridView.atLocalBins({2u})->volumeName(), "root");
0355   BOOST_CHECK_EQUAL(decodedGridView.atLocalBins({3u})->volumeName(), "child");
0356 
0357   std::vector<Portal*> decodedChildPortals;
0358   for (auto& portal : decodedChildren.front()->portals()) {
0359     decodedChildPortals.push_back(&portal);
0360   }
0361   BOOST_REQUIRE_EQUAL(decodedChildPortals.size(), 1u);
0362 
0363   bool sharedPortalPreserved = false;
0364   for (Portal* rootPortal : decodedPortals) {
0365     if (rootPortal == decodedChildPortals.front()) {
0366       sharedPortalPreserved = true;
0367       break;
0368     }
0369   }
0370   BOOST_CHECK(sharedPortalPreserved);
0371 
0372   auto decodedGeometry = converter.fromJson(gctx, encodedFromFile);
0373   BOOST_REQUIRE(decodedGeometry != nullptr);
0374   BOOST_REQUIRE(decodedGeometry->highestTrackingVolume() != nullptr);
0375   BOOST_CHECK_EQUAL(decodedGeometry->highestTrackingVolume()->volumeName(),
0376                     "root");
0377 
0378   const auto* htvSource = root.get();
0379   const auto* htvDecoded = decodedGeometry->highestTrackingVolume();
0380   BOOST_CHECK(*root == *htvDecoded);
0381   checkHierarchy(gctx, htvSource->volumes(), htvDecoded->volumes());
0382 }
0383 
0384 BOOST_AUTO_TEST_CASE(TrackingGeometryJsonConverterNavigation) {
0385   GeometryContext gctx = GeometryContext::dangerouslyDefaultConstruct();
0386   MagneticFieldContext mctx;
0387 
0388   auto field = std::make_shared<ConstantBField>(Vector3{0, 0, 0_T});
0389   EigenStepper<> stepper{field};
0390 
0391   BoundTrackParameters start = BoundTrackParameters::createCurvilinear(
0392       Vector4::Zero(), Vector3::UnitX(), 1. / 1_GeV, std::nullopt,
0393       ParticleHypothesis::pion());
0394 
0395   // Action list and abort list
0396   using EndOfWorld = EndOfWorldReached;
0397   using ReferenceActorList = ActorList<StateCollector<>, EndOfWorld>;
0398   using PropagatorOptions =
0399       typename ConstantFieldPropagator::template Options<ReferenceActorList>;
0400 
0401   // Options definition
0402   PropagatorOptions options(gctx, mctx);
0403 
0404   // Build geometry
0405   CylindricalTrackingGeometry cylindricalGeometryBuilder(gctx, true);
0406   auto sourceGeometry = cylindricalGeometryBuilder();
0407 
0408   TrackingGeometryJsonConverter converter;
0409   nlohmann::json encoded = converter.toJson(gctx, *sourceGeometry);
0410   // TemporaryDirectory tmpDir{};
0411   auto jsonPath = "tracking_geometry_roundtrip.json";
0412   {
0413     std::ofstream out(jsonPath);
0414     BOOST_REQUIRE(out.good());
0415     out << encoded.dump(2);
0416   }
0417 
0418   nlohmann::json encodedFromFile;
0419   {
0420     std::ifstream in(jsonPath);
0421     BOOST_REQUIRE(in.good());
0422     in >> encodedFromFile;
0423   }
0424 
0425   auto decodedGeometry = converter.fromJson(gctx, encodedFromFile);
0426 
0427   const auto* htvSource = sourceGeometry->highestTrackingVolume();
0428   const auto* htvDecoded = decodedGeometry->highestTrackingVolume();
0429   BOOST_CHECK(*htvSource == *htvDecoded);
0430 
0431   checkHierarchy(gctx, htvSource->volumes(), htvDecoded->volumes());
0432 
0433   // -----------------------------------------------------
0434   // Compare propagation
0435   Navigator::Config sourceNavCfg;
0436   sourceNavCfg.trackingGeometry =
0437       std::make_shared<TrackingGeometry>(*sourceGeometry);
0438   sourceNavCfg.resolveSensitive = true;
0439   sourceNavCfg.resolveMaterial = true;
0440   sourceNavCfg.resolvePassive = false;
0441   Navigator sourceNavigator{
0442       sourceNavCfg,
0443       Acts::getDefaultLogger("SourceNavigator", Acts::Logging::INFO)};
0444   ConstantFieldPropagator sourcePropagator(stepper, sourceNavigator);
0445 
0446   auto sourceRes = sourcePropagator.propagate(start, options).value();
0447 
0448   Navigator::Config decodedNavCfg;
0449   decodedNavCfg.trackingGeometry =
0450       std::make_shared<TrackingGeometry>(*decodedGeometry);
0451   decodedNavCfg.resolveSensitive = true;
0452   decodedNavCfg.resolveMaterial = true;
0453   decodedNavCfg.resolvePassive = false;
0454   Navigator decodedNavigator{
0455       sourceNavCfg,
0456       Acts::getDefaultLogger("DecodedNavigator", Acts::Logging::INFO)};
0457   ConstantFieldPropagator decodedPropagator(stepper, decodedNavigator);
0458 
0459   auto decodedRes = decodedPropagator.propagate(start, options).value();
0460 
0461   auto& sourceStates = sourceRes.template get<StateCollector<>::result_type>();
0462   auto& decodedStates =
0463       decodedRes.template get<StateCollector<>::result_type>();
0464   for (std::size_t i = 0; i < sourceStates.navigation.size(); i++) {
0465     auto& navigationA = sourceStates.navigation.at(i);
0466     auto& navigationB = decodedStates.navigation.at(i);
0467     if (!navigationA.navigationBreak) {
0468       BOOST_CHECK(*navigationA.currentVolume == *navigationB.currentVolume);
0469       BOOST_CHECK(*navigationA.startVolume == *navigationB.startVolume);
0470       BOOST_CHECK(*navigationA.currentSurface == *navigationB.currentSurface);
0471       BOOST_CHECK(*navigationA.startSurface == *navigationB.startSurface);
0472     } else {
0473       BOOST_CHECK_EQUAL(navigationA.currentVolume, nullptr);
0474       BOOST_CHECK_EQUAL(navigationB.currentVolume, nullptr);
0475     }
0476 
0477     auto& steppingA = sourceStates.stepping.at(i);
0478     auto& steppingB = decodedStates.stepping.at(i);
0479     BOOST_CHECK(steppingA.pars == steppingB.pars);
0480     BOOST_CHECK(steppingA.nSteps == steppingB.nSteps);
0481     BOOST_CHECK(steppingA.pathAccumulated == steppingB.pathAccumulated);
0482   }
0483 }
0484 
0485 BOOST_AUTO_TEST_SUITE_END()
0486 
0487 BOOST_AUTO_TEST_SUITE(MultiLayerNavigationPolicySuite)
0488 
0489 namespace {
0490 
0491 auto makeMultiLayerVolume() {
0492   auto tVolume = std::make_shared<TrackingVolume>(
0493       Transform3::Identity(),
0494       std::make_shared<CuboidVolumeBounds>(20., 20., 5.), "CuboidVolume");
0495 
0496   auto boundsRect = std::make_shared<Acts::RectangleBounds>(2., 2.);
0497   for (int ix = -1; ix <= 1; ++ix) {
0498     for (int iy = -1; iy <= 1; ++iy) {
0499       Transform3 trf = Transform3::Identity();
0500       trf.translation() = Acts::Vector3(4. * ix, 4. * iy, 0.);
0501       auto surface =
0502           Acts::Surface::makeShared<Acts::PlaneSurface>(trf, boundsRect);
0503       surface->assignIsSensitive(true);
0504       tVolume->addSurface(surface);
0505     }
0506   }
0507   return tVolume;
0508 }
0509 
0510 auto makeMultiLayerPolicy(const GeometryContext& gctx,
0511                           const TrackingVolume& volume, const Logger& logger) {
0512   Acts::Axis<AxisType::Equidistant, AxisBoundaryType::Bound> axisX(-10., 10.,
0513                                                                    5);
0514   Acts::Axis<AxisType::Equidistant, AxisBoundaryType::Bound> axisY(-10., 10.,
0515                                                                    5);
0516   Acts::Grid gridXY(Acts::Type<std::vector<std::size_t>>, std::move(axisX),
0517                     std::move(axisY));
0518 
0519   Experimental::MultiLayerNavigationPolicy::IndexedUpdatorType indexedGrid(
0520       std::move(gridXY), {AxisX, AxisY});
0521 
0522   Experimental::MultiLayerNavigationPolicy::Config cfg;
0523   cfg.binExpansion = {1u, 1u};
0524   return Experimental::MultiLayerNavigationPolicy(gctx, volume, logger, cfg,
0525                                                   std::move(indexedGrid));
0526 }
0527 
0528 }  // namespace
0529 
0530 BOOST_AUTO_TEST_CASE(MultiLayerNavigationPolicyToJson) {
0531   auto tContext = GeometryContext::dangerouslyDefaultConstruct();
0532   auto tLogger =
0533       Acts::getDefaultLogger("MultiLayerNavigationJsonTest", Logging::INFO);
0534 
0535   auto tVolume = makeMultiLayerVolume();
0536   auto policy = makeMultiLayerPolicy(tContext, *tVolume, *tLogger);
0537 
0538   TrackingGeometryJsonConverter conv;
0539   nlohmann::json j = conv.navigationPolicyToJson(policy);
0540 
0541   BOOST_CHECK(j.contains("kind"));
0542   BOOST_CHECK(j.contains("axes"));
0543   BOOST_CHECK(j.contains("casts"));
0544   BOOST_CHECK(j.contains("binExpansion"));
0545   BOOST_CHECK_EQUAL(j["kind"], "MultiLayerNavigation");
0546   BOOST_CHECK_EQUAL(j["axes"].size(), 2u);
0547   BOOST_CHECK_EQUAL(j["casts"].size(), 2u);
0548   BOOST_CHECK(!j.contains("surfaces"));
0549 }
0550 
0551 BOOST_AUTO_TEST_CASE(MultiLayerNavigationPolicyRoundTrip) {
0552   auto tContext = GeometryContext::dangerouslyDefaultConstruct();
0553   auto tLogger =
0554       Acts::getDefaultLogger("MultiLayerNavigationJsonTest", Logging::INFO);
0555 
0556   auto tVolume = makeMultiLayerVolume();
0557   auto policy = makeMultiLayerPolicy(tContext, *tVolume, *tLogger);
0558 
0559   TrackingGeometryJsonConverter conv;
0560   nlohmann::json j = conv.navigationPolicyToJson(policy);
0561 
0562   auto policyPtr =
0563       conv.navigationPolicyFromJson(tContext, j, *tVolume, *tLogger);
0564   auto& restored =
0565       dynamic_cast<Experimental::MultiLayerNavigationPolicy&>(*policyPtr);
0566 
0567   const auto& origGrid = policy.indexedGrid().grid;
0568   const auto& restGrid = restored.indexedGrid().grid;
0569   BOOST_CHECK_EQUAL(origGrid.axes()[0]->getNBins(),
0570                     restGrid.axes()[0]->getNBins());
0571   BOOST_CHECK_EQUAL(origGrid.axes()[1]->getNBins(),
0572                     restGrid.axes()[1]->getNBins());
0573   BOOST_CHECK_CLOSE(origGrid.axes()[0]->getMin(), restGrid.axes()[0]->getMin(),
0574                     1e-6);
0575   BOOST_CHECK_CLOSE(origGrid.axes()[0]->getMax(), restGrid.axes()[0]->getMax(),
0576                     1e-6);
0577 
0578   BOOST_CHECK_EQUAL(policy.indexedGrid().casts[0],
0579                     restored.indexedGrid().casts[0]);
0580   BOOST_CHECK_EQUAL(policy.indexedGrid().casts[1],
0581                     restored.indexedGrid().casts[1]);
0582 
0583   BOOST_CHECK(policy.config().binExpansion == restored.config().binExpansion);
0584 }
0585 
0586 BOOST_AUTO_TEST_SUITE_END()
0587 
0588 }  // namespace ActsTests