File indexing completed on 2025-11-04 09:24:23
0001 
0002 
0003 
0004 
0005 
0006 
0007 
0008 
0009 #include <boost/test/unit_test.hpp>
0010 
0011 #include "Acts/Definitions/Algebra.hpp"
0012 #include "Acts/Geometry/CuboidVolumeBounds.hpp"
0013 #include "Acts/Geometry/CylinderVolumeBounds.hpp"
0014 #include "Acts/Geometry/DetectorElementBase.hpp"
0015 #include "Acts/Geometry/GeometryIdentifier.hpp"
0016 #include "Acts/Geometry/Portal.hpp"
0017 #include "Acts/Geometry/PortalLinkBase.hpp"
0018 #include "Acts/Geometry/TrackingGeometry.hpp"
0019 #include "Acts/Geometry/TrackingVolume.hpp"
0020 #include "Acts/Geometry/TrapezoidVolumeBounds.hpp"
0021 #include "Acts/Geometry/TrivialPortalLink.hpp"
0022 #include "Acts/Geometry/VolumeBounds.hpp"
0023 #include "Acts/Surfaces/AnnulusBounds.hpp"
0024 #include "Acts/Surfaces/BoundaryTolerance.hpp"
0025 #include "Acts/Surfaces/CylinderBounds.hpp"
0026 #include "Acts/Surfaces/PlaneSurface.hpp"
0027 #include "Acts/Surfaces/RadialBounds.hpp"
0028 #include "Acts/Surfaces/RectangleBounds.hpp"
0029 #include "Acts/Surfaces/TrapezoidBounds.hpp"
0030 #include "Acts/Utilities/Logger.hpp"
0031 #include "Acts/Visualization/ObjVisualization3D.hpp"
0032 #include "ActsPlugins/Detray/DetrayConversionUtils.hpp"
0033 #include "ActsPlugins/Detray/DetrayGeometryConverter.hpp"
0034 #include "ActsPlugins/Detray/DetrayPayloadConverter.hpp"
0035 #include "ActsTests/CommonHelpers/CylindricalTrackingGeometry.hpp"
0036 #include "ActsTests/CommonHelpers/DetectorElementStub.hpp"
0037 #include "ActsTests/CommonHelpers/FloatComparisons.hpp"
0038 
0039 #include <memory>
0040 #include <numbers>
0041 
0042 #include <detray/io/backend/geometry_reader.hpp>
0043 #include <detray/io/backend/geometry_writer.hpp>
0044 #include <detray/io/backend/homogeneous_material_reader.hpp>
0045 #include <detray/io/backend/material_map_reader.hpp>
0046 #include <detray/io/backend/surface_grid_reader.hpp>
0047 #include <detray/io/frontend/definitions.hpp>
0048 #include <detray/io/frontend/payloads.hpp>
0049 #include <detray/io/json/json_io.hpp>
0050 #include <detray/plugins/svgtools/illustrator.hpp>
0051 #include <detray/plugins/svgtools/writer.hpp>
0052 #include <detray/utils/consistency_checker.hpp>
0053 #include <detray/utils/grid/detail/concepts.hpp>
0054 #include <nlohmann/json_fwd.hpp>
0055 #include <vecmem/memory/host_memory_resource.hpp>
0056 #include <vecmem/memory/memory_resource.hpp>
0057 
0058 #include "detray/geometry/tracking_volume.hpp"
0059 
0060 auto logger = Acts::getDefaultLogger("Test", Acts::Logging::INFO);
0061 
0062 using namespace Acts;
0063 using namespace ActsPlugins;
0064 
0065 namespace ActsTests {
0066 
0067 BOOST_AUTO_TEST_SUITE(DetrayConversion)
0068 
0069 BOOST_AUTO_TEST_CASE(DetrayTransformConversion) {
0070   auto transform = Transform3::Identity();
0071   transform.pretranslate(Vector3(1., 2., 3.));
0072   transform.rotate(Eigen::AngleAxisd(std::numbers::pi / 2., Vector3::UnitZ()));
0073 
0074   detray::io::transform_payload payload =
0075       DetrayConversionUtils::convertTransform(transform);
0076   
0077   CHECK_CLOSE_ABS(payload.tr[0u], 1., std::numeric_limits<double>::epsilon());
0078   CHECK_CLOSE_ABS(payload.tr[1u], 2., std::numeric_limits<double>::epsilon());
0079   CHECK_CLOSE_ABS(payload.tr[2u], 3., std::numeric_limits<double>::epsilon());
0080   
0081   RotationMatrix3 rotation = transform.rotation().transpose();
0082   CHECK_CLOSE_ABS(payload.rot[0u], rotation(0, 0),
0083                   std::numeric_limits<double>::epsilon());
0084   CHECK_CLOSE_ABS(payload.rot[1u], rotation(0, 1),
0085                   std::numeric_limits<double>::epsilon());
0086   CHECK_CLOSE_ABS(payload.rot[2u], rotation(0, 2),
0087                   std::numeric_limits<double>::epsilon());
0088   CHECK_CLOSE_ABS(payload.rot[3u], rotation(1, 0),
0089                   std::numeric_limits<double>::epsilon());
0090   CHECK_CLOSE_ABS(payload.rot[4u], rotation(1, 1),
0091                   std::numeric_limits<double>::epsilon());
0092   CHECK_CLOSE_ABS(payload.rot[5u], rotation(1, 2),
0093                   std::numeric_limits<double>::epsilon());
0094   CHECK_CLOSE_ABS(payload.rot[6u], rotation(2, 0),
0095                   std::numeric_limits<double>::epsilon());
0096   CHECK_CLOSE_ABS(payload.rot[7u], rotation(2, 1),
0097                   std::numeric_limits<double>::epsilon());
0098   CHECK_CLOSE_ABS(payload.rot[8u], rotation(2, 2),
0099                   std::numeric_limits<double>::epsilon());
0100 }
0101 
0102 BOOST_AUTO_TEST_CASE(DetrayMaskConversion) {
0103   
0104   {
0105     Acts::AnnulusBounds annulus(10., 20., 0.5, 1.5, {1., 2.}, 1.);
0106     auto payload = DetrayPayloadConverter::convertMask(annulus, false);
0107     BOOST_CHECK(payload.shape == detray::io::shape_id::annulus2);
0108     using enum detray::annulus2D::boundaries;
0109     CHECK_CLOSE_ABS(payload.boundaries[e_min_r], 10., 1e-10);  
0110     CHECK_CLOSE_ABS(payload.boundaries[e_max_r], 20., 1e-10);  
0111     CHECK_CLOSE_ABS(payload.boundaries[e_min_phi_rel], 0.5,
0112                     1e-10);  
0113     CHECK_CLOSE_ABS(payload.boundaries[e_max_phi_rel], 1.5,
0114                     1e-10);  
0115     CHECK_CLOSE_ABS(payload.boundaries[e_average_phi], 1.,
0116                     1e-10);                                     
0117     CHECK_CLOSE_ABS(payload.boundaries[e_shift_x], 1., 1e-10);  
0118     CHECK_CLOSE_ABS(payload.boundaries[e_shift_y], 2., 1e-10);  
0119   }
0120 
0121   
0122   {
0123     Acts::RectangleBounds rectangle(5., 10.);  
0124     auto payload = DetrayPayloadConverter::convertMask(rectangle, false);
0125     BOOST_CHECK(payload.shape == detray::io::shape_id::rectangle2);
0126     using enum detray::rectangle2D::boundaries;
0127     CHECK_CLOSE_ABS(payload.boundaries[e_half_x], 5., 1e-10);   
0128     CHECK_CLOSE_ABS(payload.boundaries[e_half_y], 10., 1e-10);  
0129   }
0130 
0131   
0132   {
0133     Acts::CylinderBounds cylinder(50., 100.);
0134     auto payload = DetrayPayloadConverter::convertMask(cylinder, false);
0135     BOOST_CHECK(payload.shape == detray::io::shape_id::cylinder2);
0136     using enum detray::concentric_cylinder2D::boundaries;
0137     CHECK_CLOSE_ABS(payload.boundaries[e_r], 50., 1e-10);          
0138     CHECK_CLOSE_ABS(payload.boundaries[e_lower_z], -100., 1e-10);  
0139     CHECK_CLOSE_ABS(payload.boundaries[e_upper_z], 100., 1e-10);   
0140 
0141     
0142     auto portalPayload = DetrayPayloadConverter::convertMask(cylinder, true);
0143     BOOST_CHECK(portalPayload.shape == detray::io::shape_id::portal_cylinder2);
0144     CHECK_CLOSE_ABS(portalPayload.boundaries[e_r], 50., 1e-10);  
0145     CHECK_CLOSE_ABS(portalPayload.boundaries[e_lower_z], -100.,
0146                     1e-10);  
0147     CHECK_CLOSE_ABS(portalPayload.boundaries[e_upper_z], 100.,
0148                     1e-10);  
0149   }
0150 
0151   
0152   {
0153     Acts::TrapezoidBounds trapezoid(4., 8., 10.,
0154                                     0.);  
0155     auto payload = DetrayPayloadConverter::convertMask(trapezoid, false);
0156     BOOST_CHECK(payload.shape == detray::io::shape_id::trapezoid2);
0157     using enum detray::trapezoid2D::boundaries;
0158     CHECK_CLOSE_ABS(payload.boundaries[e_half_length_0], 4.,
0159                     1e-10);  
0160     CHECK_CLOSE_ABS(payload.boundaries[e_half_length_1], 8.,
0161                     1e-10);  
0162     CHECK_CLOSE_ABS(payload.boundaries[e_half_length_2], 10.,
0163                     1e-10);  
0164     CHECK_CLOSE_ABS(payload.boundaries[e_divisor], 1. / (2. * 10.),
0165                     1e-10);  
0166   }
0167 
0168   
0169   {
0170     Acts::RadialBounds radial(5.,
0171                               15);  
0172     auto payload = DetrayPayloadConverter::convertMask(radial, false);
0173     BOOST_CHECK(payload.shape == detray::io::shape_id::ring2);
0174     using enum detray::ring2D::boundaries;
0175     CHECK_CLOSE_ABS(payload.boundaries[e_inner_r], 5., 1e-10);   
0176     CHECK_CLOSE_ABS(payload.boundaries[e_outer_r], 15., 1e-10);  
0177   }
0178 }
0179 
0180 BOOST_AUTO_TEST_CASE(DetrayMaskConversionErrors) {
0181   
0182   
0183   
0184   {
0185     std::array<double, Acts::RectangleBounds::eSize> values = {
0186         -2., -3., 5., 4.};  
0187     Acts::RectangleBounds asymmetricRect(values);
0188     BOOST_CHECK_THROW(
0189         DetrayPayloadConverter::convertMask(asymmetricRect, false),
0190         std::runtime_error);
0191   }
0192 
0193   
0194   {
0195     Acts::RadialBounds partialRadial(5., 15., 0.5 * std::numbers::pi);
0196     BOOST_CHECK_THROW(DetrayPayloadConverter::convertMask(partialRadial, false),
0197                       std::runtime_error);
0198   }
0199 
0200   {
0201     Acts::RadialBounds partialRadial(
0202         5., 15., std::numbers::pi,
0203         0.75 * std::numbers::pi);  
0204     BOOST_CHECK_THROW(DetrayPayloadConverter::convertMask(partialRadial, false),
0205                       std::runtime_error);
0206   }
0207 
0208   
0209   {
0210     class MockDiscBounds final : public Acts::SurfaceBounds {
0211      public:
0212       BoundsType type() const final { return BoundsType::eDisc; }
0213       bool isCartesian() const final { return true; }
0214       SquareMatrix2 boundToCartesianJacobian(
0215           const Vector2& ) const final {
0216         return SquareMatrix2::Identity();
0217       }
0218       SquareMatrix2 boundToCartesianMetric(
0219           const Vector2& ) const final {
0220         return SquareMatrix2::Identity();
0221       }
0222       std::vector<double> values() const final { return {}; }
0223       Vector2 center() const final { return Vector2::Zero(); }
0224       bool inside(const Vector2& ) const final { return false; }
0225       Vector2 closestPoint(const Vector2& lposition,
0226                            const SquareMatrix2& ) const final {
0227         return lposition;
0228       }
0229       bool inside(const Acts::Vector2& ,
0230                   const Acts::BoundaryTolerance& ) const final {
0231         return false;
0232       }
0233       std::ostream& toStream(std::ostream& sl) const final { return sl; }
0234     };
0235     MockDiscBounds mockDisc;
0236     BOOST_CHECK_THROW(DetrayPayloadConverter::convertMask(mockDisc, false),
0237                       std::runtime_error);
0238   }
0239 
0240   
0241   {
0242     class MockUnknownBounds final : public Acts::SurfaceBounds {
0243      public:
0244       BoundsType type() const final {
0245         return static_cast<BoundsType>(999);
0246       }  
0247       bool isCartesian() const final { return true; }
0248       SquareMatrix2 boundToCartesianJacobian(
0249           const Vector2& ) const final {
0250         return SquareMatrix2::Identity();
0251       }
0252       SquareMatrix2 boundToCartesianMetric(
0253           const Vector2& ) const final {
0254         return SquareMatrix2::Identity();
0255       }
0256       std::vector<double> values() const final { return {}; }
0257       Vector2 center() const final { return Vector2::Zero(); }
0258       bool inside(const Vector2& ) const final { return false; }
0259       Vector2 closestPoint(const Vector2& lposition,
0260                            const SquareMatrix2& ) const final {
0261         return lposition;
0262       }
0263       bool inside(const Acts::Vector2& ,
0264                   const Acts::BoundaryTolerance& ) const final {
0265         return false;
0266       }
0267       std::ostream& toStream(std::ostream& sl) const final { return sl; }
0268     };
0269     MockUnknownBounds mockUnknown;
0270     auto payload = DetrayPayloadConverter::convertMask(mockUnknown, false);
0271     BOOST_CHECK(payload.shape == detray::io::shape_id::unknown);
0272   }
0273 }
0274 
0275 BOOST_AUTO_TEST_CASE(DetraySurfaceConversionTests) {
0276   GeometryContext gctx;
0277 
0278   
0279   Transform3 transform = Transform3::Identity();
0280   transform.pretranslate(Vector3(1., 2., 3.));
0281   transform.rotate(Eigen::AngleAxisd(0.5 * std::numbers::pi, Vector3::UnitZ()));
0282 
0283   
0284   auto bounds = std::make_shared<RectangleBounds>(5., 10.);
0285 
0286   
0287   auto surface = Surface::makeShared<PlaneSurface>(transform, bounds);
0288 
0289   
0290   {
0291     DetrayPayloadConverter::Config cfg;
0292     cfg.sensitiveStrategy =
0293         DetrayPayloadConverter::Config::SensitiveStrategy::Identifier;
0294 
0295     DetrayPayloadConverter converter(cfg);
0296 
0297     
0298     {
0299       auto payload = converter.convertSurface(gctx, *surface);
0300 
0301       
0302       BOOST_CHECK(payload.type == detray::surface_id::e_passive);
0303 
0304       
0305       CHECK_CLOSE_ABS(payload.transform.tr[0], 1., 1e-10);
0306       CHECK_CLOSE_ABS(payload.transform.tr[1], 2., 1e-10);
0307       CHECK_CLOSE_ABS(payload.transform.tr[2], 3., 1e-10);
0308 
0309       
0310       BOOST_CHECK(payload.masks.at(0).shape ==
0311                   detray::io::shape_id::rectangle2);
0312       using enum detray::rectangle2D::boundaries;
0313       CHECK_CLOSE_ABS(payload.masks.at(0).boundaries[e_half_x], 5., 1e-10);
0314       CHECK_CLOSE_ABS(payload.masks.at(0).boundaries[e_half_y], 10., 1e-10);
0315     }
0316 
0317     
0318     {
0319       auto sensitiveSurface =
0320           Surface::makeShared<PlaneSurface>(transform, bounds);
0321       sensitiveSurface->assignGeometryId(GeometryIdentifier().withSensitive(1));
0322 
0323       auto payload = converter.convertSurface(gctx, *sensitiveSurface);
0324 
0325       BOOST_CHECK(payload.type == detray::surface_id::e_sensitive);
0326     }
0327   }
0328 
0329   
0330   {
0331     DetrayPayloadConverter::Config cfg;
0332     cfg.sensitiveStrategy =
0333         DetrayPayloadConverter::Config::SensitiveStrategy::DetectorElement;
0334 
0335     DetrayPayloadConverter converter(cfg);
0336 
0337     
0338     {
0339       auto payload = converter.convertSurface(gctx, *surface);
0340       BOOST_CHECK(payload.type == detray::surface_id::e_passive);
0341     }
0342 
0343     
0344     {
0345       
0346       auto detElement =
0347           std::make_shared<DetectorElementStub>(transform, bounds, 1.0);
0348 
0349       
0350       auto sensitiveSurface =
0351           Surface::makeShared<PlaneSurface>(bounds, *detElement);
0352 
0353       auto payload = converter.convertSurface(gctx, *sensitiveSurface);
0354       BOOST_CHECK(payload.type == detray::surface_id::e_sensitive);
0355     }
0356   }
0357 }
0358 
0359 BOOST_AUTO_TEST_CASE(DetrayPortalConversionTests) {
0360   GeometryContext gctx;
0361 
0362   
0363   Transform3 transform = Transform3::Identity();
0364   transform.pretranslate(Vector3(1., 2., 3.));
0365   transform.rotate(Eigen::AngleAxisd(0.5 * std::numbers::pi, Vector3::UnitZ()));
0366 
0367   
0368   auto bounds = std::make_shared<RectangleBounds>(5., 10.);
0369 
0370   auto cvlBounds = std::make_shared<CylinderVolumeBounds>(5., 10., 10.);
0371   TrackingVolume tv(transform, cvlBounds);
0372 
0373   
0374   auto surface = Surface::makeShared<PlaneSurface>(transform, bounds);
0375 
0376   
0377   auto portal = std::make_shared<Portal>(Direction::AlongNormal(), surface, tv);
0378 
0379   
0380   {
0381     DetrayPayloadConverter::Config cfg;
0382     DetrayPayloadConverter converter(cfg);
0383     auto payload = converter.convertSurface(gctx, portal->surface(), true);
0384 
0385     
0386     BOOST_CHECK(payload.type == detray::surface_id::e_portal);
0387 
0388     
0389     CHECK_CLOSE_ABS(payload.transform.tr[0], 1., 1e-10);
0390     CHECK_CLOSE_ABS(payload.transform.tr[1], 2., 1e-10);
0391     CHECK_CLOSE_ABS(payload.transform.tr[2], 3., 1e-10);
0392 
0393     
0394     
0395     BOOST_CHECK(payload.masks.at(0).shape == detray::io::shape_id::rectangle2);
0396     using enum detray::rectangle2D::boundaries;
0397     CHECK_CLOSE_ABS(payload.masks.at(0).boundaries[e_half_x], 5., 1e-10);
0398     CHECK_CLOSE_ABS(payload.masks.at(0).boundaries[e_half_y], 10., 1e-10);
0399   }
0400 }
0401 
0402 BOOST_AUTO_TEST_CASE(DetrayVolumeConversionTests) {
0403   
0404   Transform3 transform = Transform3::Identity();
0405   transform.pretranslate(Vector3(1., 2., 3.));
0406   transform.rotate(Eigen::AngleAxisd(0.5 * std::numbers::pi, Vector3::UnitZ()));
0407 
0408   DetrayPayloadConverter::Config cfg;
0409   DetrayPayloadConverter converter(cfg);
0410 
0411   
0412   {
0413     auto cvlBounds = std::make_shared<CylinderVolumeBounds>(5., 10., 10.);
0414     auto volume =
0415         std::make_shared<TrackingVolume>(transform, cvlBounds, "TestCylinder");
0416 
0417     auto payload = converter.convertVolume(*volume);
0418 
0419     
0420     BOOST_CHECK(payload.type == detray::volume_id::e_cylinder);
0421 
0422     
0423     BOOST_CHECK_EQUAL(payload.name, "TestCylinder");
0424 
0425     
0426     CHECK_CLOSE_ABS(payload.transform.tr[0], 1., 1e-10);
0427     CHECK_CLOSE_ABS(payload.transform.tr[1], 2., 1e-10);
0428     CHECK_CLOSE_ABS(payload.transform.tr[2], 3., 1e-10);
0429   }
0430 
0431   
0432   {
0433     auto cuboidBounds = std::make_shared<CuboidVolumeBounds>(5., 10., 15.);
0434     auto volume =
0435         std::make_shared<TrackingVolume>(transform, cuboidBounds, "TestCuboid");
0436 
0437     auto payload = converter.convertVolume(*volume);
0438 
0439     BOOST_CHECK(payload.type == detray::volume_id::e_cuboid);
0440     BOOST_CHECK_EQUAL(payload.name, "TestCuboid");
0441   }
0442 
0443   
0444   {
0445     auto trapBounds = std::make_shared<TrapezoidVolumeBounds>(4., 8., 10., 15.);
0446     auto volume = std::make_shared<TrackingVolume>(transform, trapBounds,
0447                                                    "TestTrapezoid");
0448 
0449     auto payload = converter.convertVolume(*volume);
0450 
0451     BOOST_CHECK(payload.type == detray::volume_id::e_trapezoid);
0452     BOOST_CHECK_EQUAL(payload.name, "TestTrapezoid");
0453   }
0454 
0455   
0456   {
0457     class MockVolumeBounds : public VolumeBounds {
0458      public:
0459       BoundsType type() const final { return BoundsType::eOther; }
0460       std::vector<double> values() const final { return {}; }
0461       bool inside(const Vector3& , double ) const final {
0462         return false;
0463       }
0464       std::ostream& toStream(std::ostream& sl) const final { return sl; }
0465 
0466       std::vector<OrientedSurface> orientedSurfaces(
0467           const Transform3& ) const final {
0468         return {};
0469       }
0470 
0471       Volume::BoundingBox boundingBox(const Transform3* ,
0472                                       const Vector3& ,
0473                                       const Volume* ) const final {
0474         return {nullptr, Vector3::Zero(), Vector3::Zero()};
0475       }
0476     };
0477 
0478     auto mockBounds = std::make_shared<MockVolumeBounds>();
0479     TrackingVolume tv(transform, mockBounds, "TestUnknown");
0480 
0481     auto volume =
0482         std::make_shared<TrackingVolume>(transform, mockBounds, "TestUnknown");
0483 
0484     auto payload = converter.convertVolume(*volume);
0485 
0486     BOOST_CHECK(payload.type == detray::volume_id::e_unknown);
0487     BOOST_CHECK_EQUAL(payload.name, "TestUnknown");
0488   }
0489 }
0490 
0491 
0492 
0493 
0494 
0495 
0496 
0497 
0498 
0499 
0500 
0501 
0502 
0503 
0504 
0505 
0506 
0507 
0508 
0509 
0510 
0511 
0512 
0513 
0514 
0515 
0516 
0517 
0518 
0519 
0520 
0521 
0522 
0523 
0524 
0525 
0526 
0527 
0528 
0529 
0530 
0531 
0532 
0533 
0534 
0535 
0536 
0537 
0538 
0539 
0540 
0541 
0542 
0543 
0544 
0545 
0546 
0547 
0548 
0549 
0550 
0551 
0552 
0553 
0554 
0555 
0556 
0557 
0558 
0559 
0560 
0561 
0562 
0563 
0564 
0565 
0566 
0567 
0568 
0569 
0570 
0571 
0572 
0573 
0574 
0575 BOOST_AUTO_TEST_CASE(DetrayTrackingGeometryConversionTests) {
0576   GeometryContext gctx;
0577   auto geoLogger = getDefaultLogger("Geo", Logging::VERBOSE);
0578 
0579   CylindricalTrackingGeometry cGeometry(gctx, true);
0580   auto tGeometry = cGeometry(*geoLogger);
0581 
0582   ObjVisualization3D obj;
0583 
0584   tGeometry->visualize(obj, gctx);
0585 
0586   obj.write("cylindrical.obj");
0587 
0588   vecmem::host_memory_resource mr;
0589 
0590   DetrayPayloadConverter::Config cfg;
0591 
0592   tGeometry->apply([&cfg](const TrackingVolume& volume) {
0593     if (volume.volumeName() == "Beampipe") {
0594       cfg.beampipeVolume = &volume;
0595     }
0596   });
0597 
0598   auto logger = getDefaultLogger("Cnv", Logging::DEBUG);
0599   DetrayPayloadConverter converter(cfg, std::move(logger));
0600   auto payloads = converter.convertTrackingGeometry(gctx, *tGeometry);
0601 
0602   const auto& detector = *payloads.detector;
0603   const auto& homogeneousMaterial = *payloads.homogeneousMaterial;
0604   auto& materialGrids = *payloads.materialGrids;
0605   const auto& surfaceGrids = *payloads.surfaceGrids;
0606 
0607   BOOST_CHECK_EQUAL(detector.volumes.size(), 6);
0608   for (const auto& volume : detector.volumes) {
0609     BOOST_CHECK(volume.type == detray::volume_id::e_cylinder);
0610   }
0611   BOOST_CHECK_EQUAL(detector.volumes.at(0).name, "Beampipe");
0612   BOOST_CHECK_EQUAL(detector.volumes.at(1).name, "World");
0613   BOOST_CHECK_EQUAL(detector.volumes.at(2).name, "L0");
0614   BOOST_CHECK_EQUAL(detector.volumes.at(3).name, "L1");
0615   BOOST_CHECK_EQUAL(detector.volumes.at(4).name, "L2");
0616   BOOST_CHECK_EQUAL(detector.volumes.at(5).name, "L3");
0617 
0618   
0619   
0620 
0621   BOOST_CHECK_EQUAL(homogeneousMaterial.volumes.size(), 6);
0622 
0623   for (const auto& volume : detector.volumes) {
0624     auto it = std::ranges::find_if(
0625         homogeneousMaterial.volumes, [&](const auto& hMat) {
0626           return hMat.volume_link.link == volume.index.link;
0627         });
0628 
0629     if (it == homogeneousMaterial.volumes.end()) {
0630       BOOST_FAIL("No material slab found for volume: " + volume.name);
0631       continue;
0632     }
0633 
0634     auto& hMat = *it;
0635 
0636     
0637     BOOST_CHECK_EQUAL(volume.surfaces.size(), hMat.mat_slabs.size());
0638   }
0639 
0640   BOOST_CHECK_EQUAL(materialGrids.grids.size(), 2);
0641 
0642   BOOST_CHECK_EQUAL(surfaceGrids.grids.size(), 4);
0643 
0644   
0645   const std::vector<std::pair<int, int>> kLayerBinning = {
0646       {16, 14}, {32, 14}, {52, 14}, {78, 14}};
0647 
0648   for (const auto& [idx, it] : enumerate(surfaceGrids.grids)) {
0649     const auto [b0, b1] = kLayerBinning.at(idx);
0650 
0651     const auto [key, grids] = it;
0652 
0653     
0654     BOOST_CHECK_EQUAL(grids.size(), 1);
0655 
0656     auto& grid = grids.front();
0657 
0658     
0659     BOOST_CHECK_EQUAL(grid.axes.size(), 2);
0660 
0661     BOOST_CHECK_EQUAL(b0, grid.axes.front().bins);
0662     BOOST_CHECK_EQUAL(b1, grid.axes.back().bins);
0663 
0664     std::size_t nBinsExp = grid.axes.front().bins * grid.axes.back().bins;
0665     BOOST_CHECK_EQUAL(grid.bins.size(), nBinsExp);
0666   }
0667 
0668   BOOST_CHECK_EQUAL(payloads.names.size(), 7);
0669 
0670   
0671 
0672   {
0673     detray::io::geo_header_payload header_data;
0674     header_data.common = detray::io::detail::basic_converter::to_payload(
0675         payloads.names.at(0), detray::io::geometry_writer::tag);
0676     header_data.sub_header.emplace();
0677     auto& geo_sub_header = header_data.sub_header.value();
0678     geo_sub_header.n_volumes = detector.volumes.size();
0679     geo_sub_header.n_surfaces = 0;
0680     for (const auto& volume : detector.volumes) {
0681       geo_sub_header.n_surfaces += volume.surfaces.size();
0682     }
0683 
0684     nlohmann::ordered_json out_json;
0685     out_json["header"] = header_data;
0686     out_json["data"] = detector;
0687 
0688     std::ofstream ofs{"Detector_geometry_direct.json"};
0689     ofs << out_json.dump(2) << std::endl;
0690   }
0691 
0692   {
0693     detray::io::homogeneous_material_header_payload header_data;
0694     header_data.common = detray::io::detail::basic_converter::to_payload(
0695         payloads.names.at(0), detray::io::homogeneous_material_writer::tag);
0696     header_data.sub_header.emplace();
0697     header_data.sub_header->n_rods = 0;
0698     header_data.sub_header->n_slabs = 0;
0699 
0700     for (const auto& hVol : homogeneousMaterial.volumes) {
0701       if (hVol.mat_rods.has_value()) {
0702         header_data.sub_header->n_rods += hVol.mat_rods->size();
0703       }
0704       header_data.sub_header->n_slabs += hVol.mat_slabs.size();
0705     }
0706 
0707     nlohmann::ordered_json out_json;
0708     out_json["header"] = header_data;
0709     out_json["data"] = homogeneousMaterial;
0710 
0711     std::ofstream ofs{"Detector_homogeneous_material_direct.json"};
0712     ofs << out_json.dump(2) << std::endl;
0713   }
0714 
0715   
0716 
0717   using detector_t =
0718       detray::detector<detray::default_metadata<detray::array<double>>>;
0719 
0720   
0721   detray::detector_builder<detector_t::metadata> detectorBuilder{};
0722   
0723   detray::io::geometry_reader::from_payload<detector_t>(detectorBuilder,
0724                                                         detector);
0725 
0726   detray::io::homogeneous_material_reader::from_payload<detector_t>(
0727       detectorBuilder, homogeneousMaterial);
0728 
0729   detray::io::material_map_reader<std::integral_constant<std::size_t, 2>>::
0730       from_payload<detector_t>(detectorBuilder, std::move(materialGrids));
0731 
0732   detray::io::surface_grid_reader<detector_t::surface_type,
0733                                   std::integral_constant<std::size_t, 0>,
0734                                   std::integral_constant<std::size_t, 2>>::
0735       from_payload<detector_t>(detectorBuilder, surfaceGrids);
0736 
0737   detector_t detrayDetector(detectorBuilder.build(mr));
0738 
0739   
0740   detray::detail::check_consistency(detrayDetector);
0741 
0742   detray::svgtools::illustrator illustrator(detrayDetector, payloads.names);
0743   illustrator.hide_eta_lines(true);
0744   illustrator.show_info(true);
0745 
0746   const auto svg_zr = illustrator.draw_detector(actsvg::views::z_r{});
0747   actsvg::style::stroke stroke_black = actsvg::style::stroke();
0748   auto zr_axis = actsvg::draw::x_y_axes("axes", {-250, 250}, {-250, 250},
0749                                         stroke_black, "z", "r");
0750   detray::svgtools::write_svg("test_svgtools_detector_zr", {
0751                                                                zr_axis,
0752                                                                svg_zr,
0753                                                            });
0754 
0755   const auto svg_xy = illustrator.draw_detector(actsvg::views::x_y{});
0756   auto xy_axis = actsvg::draw::x_y_axes("axes", {-250, 250}, {-250, 250},
0757                                         stroke_black, "x", "y");
0758   detray::svgtools::write_svg("test_svgtools_detector_xy", {
0759                                                                xy_axis,
0760                                                                svg_xy,
0761                                                            });
0762 
0763   auto writer_cfg = detray::io::detector_writer_config{}
0764                         .format(detray::io::format::json)
0765                         .replace_files(true);
0766 
0767   
0768   
0769 
0770   detray::io::write_detector(detrayDetector, payloads.names, writer_cfg);
0771 }
0772 
0773 BOOST_AUTO_TEST_SUITE_END()
0774 }