Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2025-12-16 09:25:32

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/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/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   GeometryContext gctx;
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   GeometryContext gctx;
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   // Create a transform with translation and rotation
0406   Transform3 transform = Transform3::Identity();
0407   transform.pretranslate(Vector3(1., 2., 3.));
0408   transform.rotate(Eigen::AngleAxisd(0.5 * std::numbers::pi, Vector3::UnitZ()));
0409 
0410   DetrayPayloadConverter::Config cfg;
0411   DetrayPayloadConverter converter(cfg);
0412 
0413   // Test cylinder volume
0414   {
0415     auto cvlBounds = std::make_shared<CylinderVolumeBounds>(5., 10., 10.);
0416     auto volume =
0417         std::make_shared<TrackingVolume>(transform, cvlBounds, "TestCylinder");
0418 
0419     auto payload = converter.convertVolume(*volume);
0420 
0421     // Check type
0422     BOOST_CHECK(payload.type == detray::volume_id::e_cylinder);
0423 
0424     // Check name
0425     BOOST_CHECK_EQUAL(payload.name, "TestCylinder");
0426 
0427     // Check transform
0428     CHECK_CLOSE_ABS(payload.transform.tr[0], 1., 1e-10);
0429     CHECK_CLOSE_ABS(payload.transform.tr[1], 2., 1e-10);
0430     CHECK_CLOSE_ABS(payload.transform.tr[2], 3., 1e-10);
0431   }
0432 
0433   // Test cuboid volume
0434   {
0435     auto cuboidBounds = std::make_shared<CuboidVolumeBounds>(5., 10., 15.);
0436     auto volume =
0437         std::make_shared<TrackingVolume>(transform, cuboidBounds, "TestCuboid");
0438 
0439     auto payload = converter.convertVolume(*volume);
0440 
0441     BOOST_CHECK(payload.type == detray::volume_id::e_cuboid);
0442     BOOST_CHECK_EQUAL(payload.name, "TestCuboid");
0443   }
0444 
0445   // Test trapezoid volume
0446   {
0447     auto trapBounds = std::make_shared<TrapezoidVolumeBounds>(4., 8., 10., 15.);
0448     auto volume = std::make_shared<TrackingVolume>(transform, trapBounds,
0449                                                    "TestTrapezoid");
0450 
0451     auto payload = converter.convertVolume(*volume);
0452 
0453     BOOST_CHECK(payload.type == detray::volume_id::e_trapezoid);
0454     BOOST_CHECK_EQUAL(payload.name, "TestTrapezoid");
0455   }
0456 
0457   // Test unknown volume type
0458   {
0459     class MockVolumeBounds : public VolumeBounds {
0460      public:
0461       BoundsType type() const final { return BoundsType::eOther; }
0462       std::vector<double> values() const final { return {}; }
0463       bool inside(const Vector3& /*pos*/, double /*tol*/) const final {
0464         return false;
0465       }
0466       std::ostream& toStream(std::ostream& sl) const final { return sl; }
0467 
0468       std::vector<OrientedSurface> orientedSurfaces(
0469           const Transform3& /*trf*/) const final {
0470         return {};
0471       }
0472 
0473       Volume::BoundingBox boundingBox(const Transform3* /*trf*/,
0474                                       const Vector3& /*envelope*/,
0475                                       const Volume* /*entity*/) const final {
0476         return {nullptr, Vector3::Zero(), Vector3::Zero()};
0477       }
0478     };
0479 
0480     auto mockBounds = std::make_shared<MockVolumeBounds>();
0481     TrackingVolume tv(transform, mockBounds, "TestUnknown");
0482 
0483     auto volume =
0484         std::make_shared<TrackingVolume>(transform, mockBounds, "TestUnknown");
0485 
0486     auto payload = converter.convertVolume(*volume);
0487 
0488     BOOST_CHECK(payload.type == detray::volume_id::e_unknown);
0489     BOOST_CHECK_EQUAL(payload.name, "TestUnknown");
0490   }
0491 }
0492 
0493 // namespace {
0494 //
0495 // namespace detail {
0496 //
0497 // /// A functor that retrieves an acceleration struct and prints it
0498 // struct accelerator_printer {
0499 //   /// Print an acceleration structure
0500 //   ///
0501 //   /// @param accel_coll collection of acceleration structs
0502 //   /// @param idx the specific grid to be checked
0503 //   /// @param id type id of the material grid collection
0504 //   template <typename accel_coll_t, typename index_t>
0505 //   DETRAY_HOST void operator()(const accel_coll_t& accel_coll, const index_t
0506 //   idx,
0507 //                               std::stringstream& os) const {
0508 //     // os << accel_coll[idx];
0509 //   }
0510 // };
0511 //
0512 // /// A functor that retrieves material and prints it
0513 // struct material_printer {
0514 //   /// Print material
0515 //   ///
0516 //   /// @param material_coll collection of material
0517 //   /// @param idx the specific grid to be checked
0518 //   /// @param id type id of the material grid collection
0519 //   template <typename material_coll_t, typename index_t>
0520 //   DETRAY_HOST void operator()(const material_coll_t& material_coll,
0521 //                               const index_t idx, std::stringstream& os) const
0522 //                               {
0523 //     if constexpr (!detray::concepts::grid<
0524 //                       typename material_coll_t::value_type>) {
0525 //       os << material_coll[idx];
0526 //     }
0527 //   }
0528 // };
0529 //
0530 // }  // namespace detail
0531 //
0532 // template <typename detector_t>
0533 // inline std::string print_detector(
0534 //     const detector_t& det, const typename detector_t::name_map& names = {}) {
0535 //   // Gathers navigation information across navigator update calls
0536 //   std::stringstream debug_stream{};
0537 //
0538 //   debug_stream << std::left << "[>] Detector " << det.name(names) << " has "
0539 //                << det.volumes().size() << " volumes." << std::endl;
0540 //
0541 //   for (const auto [i, v_desc] : detray::views::enumerate(det.volumes())) {
0542 //     detray::tracking_volume v{det, v_desc};
0543 //
0544 //     debug_stream << "[>>] Volume " << v.name(names) << std::endl;
0545 //     debug_stream << v << std::endl;
0546 //
0547 //     debug_stream << "[>>>] Acceleration Structures:" << std::endl;
0548 //     const auto acc_link = v_desc.accel_link();
0549 //     for (std::size_t j = 0u; j < acc_link.size(); ++j) {
0550 //       // An acceleration data structure link was set, but is invalid
0551 //       if (!acc_link[j].is_invalid_id() && !acc_link[j].is_invalid_index()) {
0552 //         debug_stream << j << ":" << std::endl;
0553 //         det.accelerator_store().template visit<detail::accelerator_printer>(
0554 //             acc_link[j], debug_stream);
0555 //       }
0556 //     }
0557 //
0558 //     debug_stream << "[>>>] Surfaces:" << std::endl;
0559 //     for (const auto sf_desc : v.template surfaces<>()) {
0560 //       detray::geometry::surface sf{det, sf_desc};
0561 //       debug_stream << sf << std::endl;
0562 //
0563 //       // Check the surface material, if present
0564 //       if (sf.has_material()) {
0565 //         debug_stream << "[>>>>] Surface material:" << std::endl;
0566 //         sf.template visit_material<detail::material_printer>(debug_stream);
0567 //       }
0568 //     }
0569 //
0570 //     debug_stream << std::endl;
0571 //   }
0572 //
0573 //   return debug_stream.str();
0574 // }
0575 // }  // namespace
0576 
0577 BOOST_AUTO_TEST_CASE(DetrayTrackingGeometryConversionTests) {
0578   GeometryContext gctx;
0579   auto geoLogger = getDefaultLogger("Geo", Logging::VERBOSE);
0580 
0581   CylindricalTrackingGeometry cGeometry(gctx, true);
0582   auto tGeometry = cGeometry(*geoLogger);
0583 
0584   ObjVisualization3D obj;
0585 
0586   tGeometry->visualize(obj, gctx);
0587 
0588   obj.write("cylindrical.obj");
0589 
0590   vecmem::host_memory_resource mr;
0591 
0592   DetrayPayloadConverter::Config cfg;
0593 
0594   tGeometry->apply([&cfg](const TrackingVolume& volume) {
0595     if (volume.volumeName() == "Beampipe") {
0596       cfg.beampipeVolume = &volume;
0597     }
0598   });
0599 
0600   auto logger = getDefaultLogger("Cnv", Logging::DEBUG);
0601   DetrayPayloadConverter converter(cfg, std::move(logger));
0602   auto payloads = converter.convertTrackingGeometry(gctx, *tGeometry);
0603 
0604   const auto& detector = *payloads.detector;
0605   const auto& homogeneousMaterial = *payloads.homogeneousMaterial;
0606   auto& materialGrids = *payloads.materialGrids;
0607   const auto& surfaceGrids = *payloads.surfaceGrids;
0608 
0609   BOOST_CHECK_EQUAL(detector.volumes.size(), 6);
0610   for (const auto& volume : detector.volumes) {
0611     BOOST_CHECK(volume.type == detray::volume_id::e_cylinder);
0612   }
0613   BOOST_CHECK_EQUAL(detector.volumes.at(0).name, "Beampipe");
0614   BOOST_CHECK_EQUAL(detector.volumes.at(1).name, "World");
0615   BOOST_CHECK_EQUAL(detector.volumes.at(2).name, "L0");
0616   BOOST_CHECK_EQUAL(detector.volumes.at(3).name, "L1");
0617   BOOST_CHECK_EQUAL(detector.volumes.at(4).name, "L2");
0618   BOOST_CHECK_EQUAL(detector.volumes.at(5).name, "L3");
0619 
0620   // @HACK: At this time, the conversion introduces a number of dummy material slabs which
0621   //        should ultimately not be there.
0622 
0623   BOOST_CHECK_EQUAL(homogeneousMaterial.volumes.size(), 6);
0624 
0625   for (const auto& volume : detector.volumes) {
0626     auto it = std::ranges::find_if(
0627         homogeneousMaterial.volumes, [&](const auto& hMat) {
0628           return hMat.volume_link.link == volume.index.link;
0629         });
0630 
0631     if (it == homogeneousMaterial.volumes.end()) {
0632       BOOST_FAIL("No material slab found for volume: " + volume.name);
0633       continue;
0634     }
0635 
0636     auto& hMat = *it;
0637 
0638     // Currently, we expect exactly one slab per surface!
0639     BOOST_CHECK_EQUAL(volume.surfaces.size(), hMat.mat_slabs.size());
0640   }
0641 
0642   BOOST_CHECK_EQUAL(materialGrids.grids.size(), 2);
0643 
0644   BOOST_CHECK_EQUAL(surfaceGrids.grids.size(), 4);
0645 
0646   // Empirical binning config from construction
0647   const std::vector<std::pair<int, int>> kLayerBinning = {
0648       {16, 14}, {32, 14}, {52, 14}, {78, 14}};
0649 
0650   for (const auto& [idx, it] : enumerate(surfaceGrids.grids)) {
0651     const auto [b0, b1] = kLayerBinning.at(idx);
0652 
0653     const auto [key, grids] = it;
0654 
0655     // We're only building one surface grid for each layer
0656     BOOST_CHECK_EQUAL(grids.size(), 1);
0657 
0658     auto& grid = grids.front();
0659 
0660     // All grids in this case are 2D
0661     BOOST_CHECK_EQUAL(grid.axes.size(), 2);
0662 
0663     BOOST_CHECK_EQUAL(b0, grid.axes.front().bins);
0664     BOOST_CHECK_EQUAL(b1, grid.axes.back().bins);
0665 
0666     std::size_t nBinsExp = grid.axes.front().bins * grid.axes.back().bins;
0667     BOOST_CHECK_EQUAL(grid.bins.size(), nBinsExp);
0668   }
0669 
0670   BOOST_CHECK_EQUAL(payloads.names.size(), 7);
0671 
0672   // Write payloads to JSON directly
0673 
0674   {
0675     detray::io::geo_header_payload header_data;
0676     header_data.common = detray::io::detail::basic_converter::to_payload(
0677         payloads.names.at(0), detray::io::geometry_writer::tag);
0678     header_data.sub_header.emplace();
0679     auto& geo_sub_header = header_data.sub_header.value();
0680     geo_sub_header.n_volumes = detector.volumes.size();
0681     geo_sub_header.n_surfaces = 0;
0682     for (const auto& volume : detector.volumes) {
0683       geo_sub_header.n_surfaces += volume.surfaces.size();
0684     }
0685 
0686     nlohmann::ordered_json out_json;
0687     out_json["header"] = header_data;
0688     out_json["data"] = detector;
0689 
0690     std::ofstream ofs{"Detector_geometry_direct.json"};
0691     ofs << out_json.dump(2) << std::endl;
0692   }
0693 
0694   {
0695     detray::io::homogeneous_material_header_payload header_data;
0696     header_data.common = detray::io::detail::basic_converter::to_payload(
0697         payloads.names.at(0), detray::io::homogeneous_material_writer::tag);
0698     header_data.sub_header.emplace();
0699     header_data.sub_header->n_rods = 0;
0700     header_data.sub_header->n_slabs = 0;
0701 
0702     for (const auto& hVol : homogeneousMaterial.volumes) {
0703       if (hVol.mat_rods.has_value()) {
0704         header_data.sub_header->n_rods += hVol.mat_rods->size();
0705       }
0706       header_data.sub_header->n_slabs += hVol.mat_slabs.size();
0707     }
0708 
0709     nlohmann::ordered_json out_json;
0710     out_json["header"] = header_data;
0711     out_json["data"] = homogeneousMaterial;
0712 
0713     std::ofstream ofs{"Detector_homogeneous_material_direct.json"};
0714     ofs << out_json.dump(2) << std::endl;
0715   }
0716 
0717   // Payloads DONE, let's actually build a detray detector from them.
0718 
0719   using detector_t =
0720       detray::detector<detray::default_metadata<detray::array<double>>>;
0721 
0722   // build detector
0723   detray::detector_builder<detector_t::metadata> detectorBuilder{};
0724   // (1) geometry
0725   detray::io::geometry_reader::from_payload<detector_t>(detectorBuilder,
0726                                                         detector);
0727 
0728   detray::io::homogeneous_material_reader::from_payload<detector_t>(
0729       detectorBuilder, homogeneousMaterial);
0730 
0731   detray::io::material_map_reader<std::integral_constant<std::size_t, 2>>::
0732       from_payload<detector_t>(detectorBuilder, std::move(materialGrids));
0733 
0734   detray::io::surface_grid_reader<detector_t::surface_type,
0735                                   std::integral_constant<std::size_t, 0>,
0736                                   std::integral_constant<std::size_t, 2>>::
0737       from_payload<detector_t>(detectorBuilder, surfaceGrids);
0738 
0739   detector_t detrayDetector(detectorBuilder.build(mr));
0740 
0741   // Checks and print
0742   detray::detail::check_consistency(detrayDetector);
0743 
0744   detray::svgtools::illustrator illustrator(detrayDetector, payloads.names);
0745   illustrator.hide_eta_lines(true);
0746   illustrator.show_info(true);
0747 
0748   const auto svg_zr = illustrator.draw_detector(actsvg::views::z_r{});
0749   actsvg::style::stroke stroke_black = actsvg::style::stroke();
0750   auto zr_axis = actsvg::draw::x_y_axes("axes", {-250, 250}, {-250, 250},
0751                                         stroke_black, "z", "r");
0752   detray::svgtools::write_svg("test_svgtools_detector_zr", {
0753                                                                zr_axis,
0754                                                                svg_zr,
0755                                                            });
0756 
0757   const auto svg_xy = illustrator.draw_detector(actsvg::views::x_y{});
0758   auto xy_axis = actsvg::draw::x_y_axes("axes", {-250, 250}, {-250, 250},
0759                                         stroke_black, "x", "y");
0760   detray::svgtools::write_svg("test_svgtools_detector_xy", {
0761                                                                xy_axis,
0762                                                                svg_xy,
0763                                                            });
0764 
0765   auto writer_cfg = detray::io::detector_writer_config{}
0766                         .format(detray::io::format::json)
0767                         .replace_files(true);
0768 
0769   // std::cout << detray::utils::print_detector(detrayDetector, payloads.names)
0770   //           << std::endl;
0771 
0772   detray::io::write_detector(detrayDetector, payloads.names, writer_cfg);
0773 }
0774 
0775 BOOST_AUTO_TEST_SUITE_END()
0776 }  // namespace ActsTests