Back to home page

sPhenix code displayed by LXR

 
 

    


File indexing completed on 2026-07-16 08:09:04

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