Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2025-11-04 09:24:23

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/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   // Transform is correctly translated
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   // Rotation is correctly translated
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   // Test AnnulusBounds conversion
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);  // min_r
0110     CHECK_CLOSE_ABS(payload.boundaries[e_max_r], 20., 1e-10);  // max_r
0111     CHECK_CLOSE_ABS(payload.boundaries[e_min_phi_rel], 0.5,
0112                     1e-10);  // min_phi_rel
0113     CHECK_CLOSE_ABS(payload.boundaries[e_max_phi_rel], 1.5,
0114                     1e-10);  // max_phi_rel
0115     CHECK_CLOSE_ABS(payload.boundaries[e_average_phi], 1.,
0116                     1e-10);                                     // average_phi
0117     CHECK_CLOSE_ABS(payload.boundaries[e_shift_x], 1., 1e-10);  // shift_x
0118     CHECK_CLOSE_ABS(payload.boundaries[e_shift_y], 2., 1e-10);  // shift_y
0119   }
0120 
0121   // Test RectangleBounds conversion
0122   {
0123     Acts::RectangleBounds rectangle(5., 10.);  // symmetric around origin
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);   // half_x
0128     CHECK_CLOSE_ABS(payload.boundaries[e_half_y], 10., 1e-10);  // half_y
0129   }
0130 
0131   // Test CylinderBounds conversion (normal and portal)
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);          // r
0138     CHECK_CLOSE_ABS(payload.boundaries[e_lower_z], -100., 1e-10);  // lower_z
0139     CHECK_CLOSE_ABS(payload.boundaries[e_upper_z], 100., 1e-10);   // upper_z
0140 
0141     // Test portal case
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);  // r
0145     CHECK_CLOSE_ABS(portalPayload.boundaries[e_lower_z], -100.,
0146                     1e-10);  // lower_z
0147     CHECK_CLOSE_ABS(portalPayload.boundaries[e_upper_z], 100.,
0148                     1e-10);  // upper_z
0149   }
0150 
0151   // Test TrapezoidBounds conversion
0152   {
0153     Acts::TrapezoidBounds trapezoid(4., 8., 10.,
0154                                     0.);  // minHalfX, maxHalfX, halfY
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);  // half_length_0
0160     CHECK_CLOSE_ABS(payload.boundaries[e_half_length_1], 8.,
0161                     1e-10);  // half_length_1
0162     CHECK_CLOSE_ABS(payload.boundaries[e_half_length_2], 10.,
0163                     1e-10);  // half_length_2
0164     CHECK_CLOSE_ABS(payload.boundaries[e_divisor], 1. / (2. * 10.),
0165                     1e-10);  // divisor
0166   }
0167 
0168   // Test RadialBounds conversion
0169   {
0170     Acts::RadialBounds radial(5.,
0171                               15);  // minR, maxR, minPhi, maxPhi (full azimuth)
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);   // inner_r
0176     CHECK_CLOSE_ABS(payload.boundaries[e_outer_r], 15., 1e-10);  // outer_r
0177   }
0178 }
0179 
0180 BOOST_AUTO_TEST_CASE(DetrayMaskConversionErrors) {
0181   // Test non-symmetric RectangleBounds throws
0182   // The regular rectangle bounds constructor does not allow this, but let's
0183   // test it anyway.
0184   {
0185     std::array<double, Acts::RectangleBounds::eSize> values = {
0186         -2., -3., 5., 4.};  // minX, minY, maxX, maxY
0187     Acts::RectangleBounds asymmetricRect(values);
0188     BOOST_CHECK_THROW(
0189         DetrayPayloadConverter::convertMask(asymmetricRect, false),
0190         std::runtime_error);
0191   }
0192 
0193   // Test partial azimuth RadialBounds throws
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);  // full azimuth, but internal rotation
0204     BOOST_CHECK_THROW(DetrayPayloadConverter::convertMask(partialRadial, false),
0205                       std::runtime_error);
0206   }
0207 
0208   // Test unsupported disc bounds type throws
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& /*lposition*/) const final {
0216         return SquareMatrix2::Identity();
0217       }
0218       SquareMatrix2 boundToCartesianMetric(
0219           const Vector2& /*lposition*/) 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& /*lposition*/) const final { return false; }
0225       Vector2 closestPoint(const Vector2& lposition,
0226                            const SquareMatrix2& /*metric*/) const final {
0227         return lposition;
0228       }
0229       bool inside(const Acts::Vector2& /*loc*/,
0230                   const Acts::BoundaryTolerance& /*bcheck*/) 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   // Test unknown bounds type returns unknown shape
0241   {
0242     class MockUnknownBounds final : public Acts::SurfaceBounds {
0243      public:
0244       BoundsType type() const final {
0245         return static_cast<BoundsType>(999);
0246       }  // Invalid type
0247       bool isCartesian() const final { return true; }
0248       SquareMatrix2 boundToCartesianJacobian(
0249           const Vector2& /*lposition*/) const final {
0250         return SquareMatrix2::Identity();
0251       }
0252       SquareMatrix2 boundToCartesianMetric(
0253           const Vector2& /*lposition*/) 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& /*lposition*/) const final { return false; }
0259       Vector2 closestPoint(const Vector2& lposition,
0260                            const SquareMatrix2& /*metric*/) const final {
0261         return lposition;
0262       }
0263       bool inside(const Acts::Vector2& /*loc*/,
0264                   const Acts::BoundaryTolerance& /*bcheck*/) 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   // Create a transform with translation and rotation
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   // Create rectangle bounds
0284   auto bounds = std::make_shared<RectangleBounds>(5., 10.);
0285 
0286   // Create a plane surface
0287   auto surface = Surface::makeShared<PlaneSurface>(transform, bounds);
0288 
0289   // Test surface conversion with Identifier strategy
0290   {
0291     DetrayPayloadConverter::Config cfg;
0292     cfg.sensitiveStrategy =
0293         DetrayPayloadConverter::Config::SensitiveStrategy::Identifier;
0294 
0295     DetrayPayloadConverter converter(cfg);
0296 
0297     // Test passive surface (default)
0298     {
0299       auto payload = converter.convertSurface(gctx, *surface);
0300 
0301       // Check type
0302       BOOST_CHECK(payload.type == detray::surface_id::e_passive);
0303 
0304       // Check transform
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       // Check mask
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     // Test sensitive surface
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   // Test surface conversion with DetectorElement strategy
0330   {
0331     DetrayPayloadConverter::Config cfg;
0332     cfg.sensitiveStrategy =
0333         DetrayPayloadConverter::Config::SensitiveStrategy::DetectorElement;
0334 
0335     DetrayPayloadConverter converter(cfg);
0336 
0337     // Test passive surface (no detector element)
0338     {
0339       auto payload = converter.convertSurface(gctx, *surface);
0340       BOOST_CHECK(payload.type == detray::surface_id::e_passive);
0341     }
0342 
0343     // Test sensitive surface with detector element
0344     {
0345       // Create detector element first
0346       auto detElement =
0347           std::make_shared<DetectorElementStub>(transform, bounds, 1.0);
0348 
0349       // Create surface using the detector element
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   // Create a transform with translation and rotation
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   // Create rectangle bounds
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   // Create a plane surface
0374   auto surface = Surface::makeShared<PlaneSurface>(transform, bounds);
0375 
0376   // Create a portal with the surface
0377   auto portal = std::make_shared<Portal>(Direction::AlongNormal(), surface, tv);
0378 
0379   // Test portal conversion
0380   {
0381     DetrayPayloadConverter::Config cfg;
0382     DetrayPayloadConverter converter(cfg);
0383     auto payload = converter.convertSurface(gctx, portal->surface(), true);
0384 
0385     // Portal should always be passive
0386     BOOST_CHECK(payload.type == detray::surface_id::e_portal);
0387 
0388     // Check transform is preserved
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     // Check mask - should be same as surface since rectangle doesn't have
0394     // special portal handling
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   // Create a transform with translation and rotation
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   // Test cylinder volume
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     // Check type
0420     BOOST_CHECK(payload.type == detray::volume_id::e_cylinder);
0421 
0422     // Check name
0423     BOOST_CHECK_EQUAL(payload.name, "TestCylinder");
0424 
0425     // Check transform
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   // Test cuboid volume
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   // Test trapezoid volume
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   // Test unknown volume type
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& /*pos*/, double /*tol*/) 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& /*trf*/) const final {
0468         return {};
0469       }
0470 
0471       Volume::BoundingBox boundingBox(const Transform3* /*trf*/,
0472                                       const Vector3& /*envelope*/,
0473                                       const Volume* /*entity*/) 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 // namespace {
0492 //
0493 // namespace detail {
0494 //
0495 // /// A functor that retrieves an acceleration struct and prints it
0496 // struct accelerator_printer {
0497 //   /// Print an acceleration structure
0498 //   ///
0499 //   /// @param accel_coll collection of acceleration structs
0500 //   /// @param idx the specific grid to be checked
0501 //   /// @param id type id of the material grid collection
0502 //   template <typename accel_coll_t, typename index_t>
0503 //   DETRAY_HOST void operator()(const accel_coll_t& accel_coll, const index_t
0504 //   idx,
0505 //                               std::stringstream& os) const {
0506 //     // os << accel_coll[idx];
0507 //   }
0508 // };
0509 //
0510 // /// A functor that retrieves material and prints it
0511 // struct material_printer {
0512 //   /// Print material
0513 //   ///
0514 //   /// @param material_coll collection of material
0515 //   /// @param idx the specific grid to be checked
0516 //   /// @param id type id of the material grid collection
0517 //   template <typename material_coll_t, typename index_t>
0518 //   DETRAY_HOST void operator()(const material_coll_t& material_coll,
0519 //                               const index_t idx, std::stringstream& os) const
0520 //                               {
0521 //     if constexpr (!detray::concepts::grid<
0522 //                       typename material_coll_t::value_type>) {
0523 //       os << material_coll[idx];
0524 //     }
0525 //   }
0526 // };
0527 //
0528 // }  // namespace detail
0529 //
0530 // template <typename detector_t>
0531 // inline std::string print_detector(
0532 //     const detector_t& det, const typename detector_t::name_map& names = {}) {
0533 //   // Gathers navigation information across navigator update calls
0534 //   std::stringstream debug_stream{};
0535 //
0536 //   debug_stream << std::left << "[>] Detector " << det.name(names) << " has "
0537 //                << det.volumes().size() << " volumes." << std::endl;
0538 //
0539 //   for (const auto [i, v_desc] : detray::views::enumerate(det.volumes())) {
0540 //     detray::tracking_volume v{det, v_desc};
0541 //
0542 //     debug_stream << "[>>] Volume " << v.name(names) << std::endl;
0543 //     debug_stream << v << std::endl;
0544 //
0545 //     debug_stream << "[>>>] Acceleration Structures:" << std::endl;
0546 //     const auto acc_link = v_desc.accel_link();
0547 //     for (std::size_t j = 0u; j < acc_link.size(); ++j) {
0548 //       // An acceleration data structure link was set, but is invalid
0549 //       if (!acc_link[j].is_invalid_id() && !acc_link[j].is_invalid_index()) {
0550 //         debug_stream << j << ":" << std::endl;
0551 //         det.accelerator_store().template visit<detail::accelerator_printer>(
0552 //             acc_link[j], debug_stream);
0553 //       }
0554 //     }
0555 //
0556 //     debug_stream << "[>>>] Surfaces:" << std::endl;
0557 //     for (const auto sf_desc : v.template surfaces<>()) {
0558 //       detray::geometry::surface sf{det, sf_desc};
0559 //       debug_stream << sf << std::endl;
0560 //
0561 //       // Check the surface material, if present
0562 //       if (sf.has_material()) {
0563 //         debug_stream << "[>>>>] Surface material:" << std::endl;
0564 //         sf.template visit_material<detail::material_printer>(debug_stream);
0565 //       }
0566 //     }
0567 //
0568 //     debug_stream << std::endl;
0569 //   }
0570 //
0571 //   return debug_stream.str();
0572 // }
0573 // }  // namespace
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   // @HACK: At this time, the conversion introduces a number of dummy material slabs which
0619   //        should ultimately not be there.
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     // Currently, we expect exactly one slab per surface!
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   // Empirical binning config from construction
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     // We're only building one surface grid for each layer
0654     BOOST_CHECK_EQUAL(grids.size(), 1);
0655 
0656     auto& grid = grids.front();
0657 
0658     // All grids in this case are 2D
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   // Write payloads to JSON directly
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   // Payloads DONE, let's actually build a detray detector from them.
0716 
0717   using detector_t =
0718       detray::detector<detray::default_metadata<detray::array<double>>>;
0719 
0720   // build detector
0721   detray::detector_builder<detector_t::metadata> detectorBuilder{};
0722   // (1) geometry
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   // Checks and print
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   // std::cout << detray::utils::print_detector(detrayDetector, payloads.names)
0768   //           << std::endl;
0769 
0770   detray::io::write_detector(detrayDetector, payloads.names, writer_cfg);
0771 }
0772 
0773 BOOST_AUTO_TEST_SUITE_END()
0774 }  // namespace ActsTests