Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2026-05-27 07:24:12

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 #pragma once
0010 
0011 // Project include(s)
0012 #include "detray/builders/cylinder_portal_generator.hpp"
0013 #include "detray/builders/detector_builder.hpp"
0014 #include "detray/builders/grid_builder.hpp"
0015 #include "detray/builders/homogeneous_material_builder.hpp"
0016 #include "detray/builders/homogeneous_material_generator.hpp"
0017 #include "detray/builders/material_map_builder.hpp"
0018 #include "detray/builders/material_map_generator.hpp"
0019 #include "detray/builders/surface_factory.hpp"
0020 #include "detray/builders/volume_builder.hpp"
0021 #include "detray/core/detector.hpp"
0022 #include "detray/definitions/algebra.hpp"
0023 #include "detray/definitions/indexing.hpp"
0024 #include "detray/definitions/units.hpp"
0025 #include "detray/detectors/toy_metadata.hpp"
0026 #include "detray/geometry/tracking_volume.hpp"
0027 #include "detray/material/mixture.hpp"
0028 #include "detray/material/predefined_materials.hpp"
0029 #include "detray/utils/consistency_checker.hpp"
0030 #include "detray/utils/print_detector.hpp"
0031 #include "detray/utils/ranges.hpp"
0032 
0033 // Detray test include(s)
0034 #include "detray/test/common/factories/barrel_generator.hpp"
0035 #include "detray/test/common/factories/endcap_generator.hpp"
0036 
0037 // Vecmem include(s)
0038 #include <vecmem/memory/memory_resource.hpp>
0039 
0040 // System include(s)
0041 #include <limits>
0042 #include <stdexcept>
0043 #include <string>
0044 #include <type_traits>
0045 #include <utility>
0046 
0047 namespace detray {
0048 
0049 /// Configure the toy detector
0050 template <concepts::scalar scalar_t>
0051 struct toy_det_config {
0052   /// Default toy detector configuration
0053   toy_det_config() {
0054     // Barrel module creator
0055     m_barrel_factory_cfg.half_length(500.f * unit<scalar_t>::mm)
0056         .module_bounds({8.4f * unit<scalar_t>::mm, 36.f * unit<scalar_t>::mm})
0057         .tilt_phi(0.14f /*0.145*/)
0058         .radial_stagger(0.5f * unit<scalar_t>::mm /*2.f*/)
0059         .z_overlap(2.f * unit<scalar_t>::mm /*5.f*/);
0060 
0061     // Endcap module creator
0062     m_endcap_factory_cfg.inner_radius(m_beampipe_volume_radius)
0063         .outer_radius(m_outer_radius)
0064         .module_bounds({{3.f * unit<scalar_t>::mm, 9.5f * unit<scalar_t>::mm,
0065                          39.f * unit<scalar_t>::mm},
0066                         {6.f * unit<scalar_t>::mm, 10.f * unit<scalar_t>::mm,
0067                          39.f * unit<scalar_t>::mm}})
0068         .ring_stagger(2.f * unit<scalar_t>::mm)
0069         .phi_stagger({4.f * unit<scalar_t>::mm, 4.f * unit<scalar_t>::mm})
0070         .phi_sub_stagger({0.5f * unit<scalar_t>::mm, 0.5f * unit<scalar_t>::mm})
0071         .module_tilt({0.f, 0.f})
0072         .binning({40u, 68u});
0073 
0074     // Configure the material generation
0075     m_material_config.sensitive_material(silicon_tml<scalar_t>())
0076         .passive_material(beryllium_tml<scalar_t>())  // < beampipe
0077         .portal_material(vacuum<scalar_t>())
0078         .thickness(1.5f * unit<scalar_t>::mm);
0079 
0080     // Configure the material map generation
0081     m_beampipe_map_cfg.n_bins = {20u, 20u};
0082     m_beampipe_map_cfg.axis_index = 1u;
0083     m_beampipe_map_cfg.mapped_material = beryllium_tml<scalar_t>();
0084     m_beampipe_map_cfg.thickness = 0.8f * unit<scalar_t>::mm;
0085     // Don't scale the generation of the material thickness
0086     m_beampipe_map_cfg.scalor = 0.f;
0087     m_beampipe_map_cfg.mat_generator =
0088         detray::detail::generate_cyl_mat<scalar_t>;
0089 
0090     m_disc_map_cfg.n_bins = {5u, 20u};
0091     m_disc_map_cfg.axis_index = 0u;
0092     m_disc_map_cfg.mapped_material =
0093         mixture<scalar_t, silicon_tml<scalar_t, std::ratio<9, 10>>,
0094                 aluminium<scalar_t, std::ratio<1, 10>>>{};
0095     m_disc_map_cfg.thickness = 1.f * unit<scalar_t>::mm;
0096     m_disc_map_cfg.scalor = 1e-4f;
0097     m_disc_map_cfg.mat_generator = detray::detail::generate_disc_mat<scalar_t>;
0098 
0099     m_cyl_map_cfg.n_bins = {20u, 20u};
0100     m_cyl_map_cfg.axis_index = 1u;
0101     m_cyl_map_cfg.mapped_material =
0102         mixture<scalar_t, silicon_tml<scalar_t, std::ratio<9, 10>>,
0103                 aluminium<scalar_t, std::ratio<1, 10>>>{};
0104     m_cyl_map_cfg.thickness = 5.f * unit<scalar_t>::mm;
0105     m_cyl_map_cfg.scalor = 1e-8f;
0106     m_cyl_map_cfg.mat_generator = detray::detail::generate_cyl_mat<scalar_t>;
0107   }
0108 
0109   /// No. of barrel layers the detector should be built with
0110   unsigned int m_n_brl_layers{4u};
0111   /// No. of endcap layers (on either side) the detector should be built with
0112   unsigned int m_n_edc_layers{3u};
0113   /// Total outer radius of the pixel subdetector
0114   scalar_t m_outer_radius{180.f * unit<scalar_t>::mm};
0115   // Radius of the innermost volume that contains the beampipe
0116   scalar_t m_beampipe_volume_radius{25.f * unit<scalar_t>::mm};
0117   // Envelope around the modules used by the cylinder portal generator
0118   scalar_t m_portal_envelope{2.f * unit<scalar_t>::mm};
0119   /// Configuration for the homogeneous material generator
0120   hom_material_config<scalar_t> m_material_config{};
0121   /// Put material maps on portals or use homogeneous material on modules
0122   bool m_use_material_maps{false};
0123   /// Configuration for the material map generator (beampipe)
0124   typename material_map_config<scalar_t>::map_config m_beampipe_map_cfg{};
0125   /// Configuration for the material map generator (disc)
0126   typename material_map_config<scalar_t>::map_config m_disc_map_cfg{};
0127   /// Configuration for the material map generator (cylinder)
0128   typename material_map_config<scalar_t>::map_config m_cyl_map_cfg{};
0129   /// Thickness of the beampipe material
0130   scalar_t m_beampipe_mat_thickness{0.8f * unit<scalar_t>::mm};
0131   /// Thickness of the material slabs in the homogeneous material description
0132   scalar_t m_module_mat_thickness{1.5f * unit<scalar_t>::mm};
0133   /// Radii at which to place the barrel module layers (including beampipe)
0134   std::vector<scalar_t> m_barrel_layer_radii = {
0135       19.f * unit<scalar_t>::mm, 32.f * unit<scalar_t>::mm,
0136       72.f * unit<scalar_t>::mm, 116.f * unit<scalar_t>::mm,
0137       172.f * unit<scalar_t>::mm};
0138   /// Number of modules in phi and z for the barrel
0139   std::vector<std::pair<unsigned int, unsigned int>> m_barrel_binning = {
0140       {0u, 0u}, {16u, 14u}, {32u, 14u}, {52u, 14u}, {78u, 14u}};
0141   /// Positions at which to place the endcap module layers on either side
0142   std::vector<scalar_t> m_endcap_layer_positions = {
0143       600.f * unit<scalar_t>::mm,  700.f * unit<scalar_t>::mm,
0144       820.f * unit<scalar_t>::mm,  960.f * unit<scalar_t>::mm,
0145       1100.f * unit<scalar_t>::mm, 1300.f * unit<scalar_t>::mm,
0146       1500.f * unit<scalar_t>::mm};
0147   /// Config for the module generation (barrel)
0148   barrel_generator_config<scalar_t> m_barrel_factory_cfg{};
0149   /// Config for the module generation (endcaps)
0150   endcap_generator_config<scalar_t> m_endcap_factory_cfg{};
0151   /// Run detector consistency check after reading
0152   bool m_do_check{true};
0153 
0154   /// Setters
0155   /// @{
0156   constexpr toy_det_config &n_brl_layers(const unsigned int n) {
0157     m_n_brl_layers = n;
0158     return *this;
0159   }
0160   constexpr toy_det_config &n_edc_layers(const unsigned int n) {
0161     m_n_edc_layers = n;
0162     return *this;
0163   }
0164   constexpr toy_det_config &envelope(const scalar_t env) {
0165     m_portal_envelope = env;
0166     return *this;
0167   }
0168   constexpr toy_det_config &use_material_maps(const bool b) {
0169     m_use_material_maps = b;
0170     return *this;
0171   }
0172   constexpr toy_det_config &cyl_map_bins(const std::size_t n_phi,
0173                                          const std::size_t n_z) {
0174     m_cyl_map_cfg.n_bins = {n_phi, n_z};
0175     return *this;
0176   }
0177   constexpr toy_det_config &disc_map_bins(const std::size_t n_r,
0178                                           const std::size_t n_phi) {
0179     m_disc_map_cfg.n_bins = {n_r, n_phi};
0180     return *this;
0181   }
0182   constexpr toy_det_config &material_map_min_thickness(const scalar_t t) {
0183     assert(t > 0.f);
0184     m_cyl_map_cfg.thickness = t;
0185     m_disc_map_cfg.thickness = t;
0186     return *this;
0187   }
0188   constexpr toy_det_config &beampipe_mat_thickness(const scalar_t t) {
0189     assert(t > 0.f);
0190     m_beampipe_mat_thickness = t;
0191     return *this;
0192   }
0193   constexpr toy_det_config &module_mat_thickness(const scalar_t t) {
0194     assert(t > 0.f);
0195     m_module_mat_thickness = t;
0196     return *this;
0197   }
0198   constexpr toy_det_config &mapped_material(const material<scalar_t> &mat) {
0199     m_cyl_map_cfg.mapped_material = mat;
0200     m_disc_map_cfg.mapped_material = mat;
0201     return *this;
0202   }
0203   constexpr toy_det_config &do_check(const bool check) {
0204     m_do_check = check;
0205     return *this;
0206   }
0207   /// @}
0208 
0209   /// Getters
0210   /// @{
0211   constexpr unsigned int n_brl_layers() const { return m_n_brl_layers; }
0212   constexpr unsigned int n_edc_layers() const { return m_n_edc_layers; }
0213   constexpr const auto &outer_radius() const { return m_outer_radius; }
0214   constexpr scalar_t envelope() const { return m_portal_envelope; }
0215   constexpr scalar_t beampipe_vol_radius() const {
0216     return m_beampipe_volume_radius;
0217   }
0218   constexpr auto &material_config() { return m_material_config; }
0219   constexpr const auto &material_config() const { return m_material_config; }
0220   constexpr bool use_material_maps() const { return m_use_material_maps; }
0221   constexpr auto &beampipe_material_map() { return m_beampipe_map_cfg; }
0222   constexpr const auto &beampipe_material_map() const {
0223     return m_beampipe_map_cfg;
0224   }
0225   constexpr const auto &cyl_material_map() const { return m_cyl_map_cfg; }
0226   constexpr auto &cyl_material_map() { return m_cyl_map_cfg; }
0227   constexpr const auto &disc_material_map() const { return m_disc_map_cfg; }
0228   constexpr auto &disc_material_map() { return m_disc_map_cfg; }
0229   constexpr const darray<std::size_t, 2> &cyl_map_bins() const {
0230     return m_cyl_map_cfg.n_bins;
0231   }
0232   constexpr const darray<std::size_t, 2> &disc_map_bins() const {
0233     return m_disc_map_cfg.n_bins;
0234   }
0235   constexpr scalar_t material_map_min_thickness() const {
0236     assert(m_cyl_map_cfg.thickness == m_disc_map_cfg.thickness);
0237     return m_cyl_map_cfg.thickness;
0238   }
0239   constexpr scalar_t beampipe_mat_thickness() const {
0240     return m_beampipe_mat_thickness;
0241   }
0242   constexpr scalar_t module_mat_thickness() const {
0243     return m_module_mat_thickness;
0244   }
0245   auto barrel_mat_generator() const { return m_cyl_map_cfg.mat_generator; }
0246   auto edc_mat_generator() const { return m_disc_map_cfg.mat_generator; }
0247   constexpr material<scalar_t> mapped_material() const {
0248     assert(m_cyl_map_cfg.mapped_material == m_disc_map_cfg.mapped_material);
0249     return m_cyl_map_cfg.mapped_material;
0250   }
0251   constexpr const auto &barrel_layer_radii() const {
0252     return m_barrel_layer_radii;
0253   }
0254   constexpr const auto &endcap_layer_positions() const {
0255     return m_endcap_layer_positions;
0256   }
0257   constexpr const auto &barrel_layer_binning() const {
0258     return m_barrel_binning;
0259   }
0260   constexpr barrel_generator_config<scalar_t> &barrel_config() {
0261     return m_barrel_factory_cfg;
0262   }
0263   constexpr endcap_generator_config<scalar_t> &endcap_config() {
0264     return m_endcap_factory_cfg;
0265   }
0266   constexpr bool do_check() const { return m_do_check; }
0267   /// @}
0268 
0269   /// Print the toy detector configuration
0270   friend std::ostream &operator<<(std::ostream &out,
0271                                   const toy_det_config &cfg) {
0272     out << "\nToy Detector\n"
0273         << "----------------------------\n"
0274         << "  No. barrel layers     : " << cfg.n_brl_layers() << "\n"
0275         << "  No. endcap layers     : " << cfg.n_edc_layers() << "\n"
0276         << "  Portal envelope       : " << cfg.envelope() << " [mm]\n";
0277 
0278     if (cfg.use_material_maps()) {
0279       const auto &cyl_map_bins = cfg.cyl_map_bins();
0280       const auto &disc_map_bins = cfg.disc_map_bins();
0281 
0282       out << "  Material maps \n"
0283           << "    -> cyl. map bins    : (phi: " << cyl_map_bins[0]
0284           << ", z: " << cyl_map_bins[1] << ")\n"
0285           << "    -> disc map bins    : (r: " << disc_map_bins[0]
0286           << ", phi: " << disc_map_bins[1] << ")\n"
0287           << "    -> cyl. min. thickness: "
0288           << cfg.cyl_material_map().thickness / detray::unit<scalar_t>::mm
0289           << " [mm]\n"
0290           << "    -> disc min. thickness: "
0291           << cfg.disc_material_map().thickness / detray::unit<scalar_t>::mm
0292           << " [mm]\n"
0293           << "    -> Material         : " << cfg.mapped_material() << "\n";
0294     } else {
0295       out << "  Homogeneous material \n"
0296           << "    -> Thickness        : "
0297           << cfg.module_mat_thickness() / detray::unit<scalar_t>::mm
0298           << " [mm]\n"
0299           << "    -> Material         : " << silicon_tml<scalar_t>() << "\n";
0300     }
0301 
0302     return out;
0303   }
0304 };
0305 
0306 namespace detail {
0307 
0308 // Helper type, used to define the r- or z-extent of detector volumes
0309 template <concepts::scalar scalar_t>
0310 struct extent2D {
0311   scalar_t lower;
0312   scalar_t upper;
0313 };
0314 
0315 /// Helper method to decorate a volume builder with material
0316 ///
0317 /// @param cfg config for the toy detector
0318 /// @param det_builder detector builder the volume belongs to
0319 /// @param v_builder the builder of the volume that should be decorated
0320 ///
0321 /// @returns the decorated volume builder and surface factory
0322 template <typename detector_builder_t, typename detector_t, typename config_t>
0323 volume_builder_interface<detector_t> *decorate_material(
0324     config_t &cfg, detector_builder_t &det_builder,
0325     volume_builder_interface<detector_t> *v_builder) {
0326   static_assert(
0327       std::is_same_v<detector_t, typename detector_builder_t::detector_type>,
0328       "Detector builder and volume builder/surface factory have different "
0329       "detector type");
0330 
0331   volume_builder_interface<detector_t> *vm_builder{v_builder};
0332 
0333   // Decorate the builder with material
0334   if (cfg.use_material_maps()) {
0335     // Build the volume with material maps
0336     vm_builder =
0337         det_builder.template decorate<material_map_builder<detector_t>>(
0338             v_builder);
0339   } else {
0340     // Build the volume with a homogeneous material description
0341     vm_builder =
0342         det_builder.template decorate<homogeneous_material_builder<detector_t>>(
0343             v_builder);
0344   }
0345 
0346   if (!vm_builder) {
0347     throw std::runtime_error("Material decoration failed");
0348   }
0349 
0350   return vm_builder;
0351 }
0352 
0353 /// Helper method to decorate a surface factory with material
0354 ///
0355 /// @param cfg config for the toy detector
0356 /// @param sf_factory surface factory that should be decorated with material
0357 /// @param is_module_factory should homogeneous material be added to surfaces
0358 ///                          of this factory (assumes no material maps are used)
0359 ///
0360 /// @returns the decorated volume builder and surface factory
0361 template <typename detector_t>
0362 std::shared_ptr<surface_factory_interface<detector_t>> decorate_material(
0363     toy_det_config<typename detector_t::scalar_type> &cfg,
0364     std::unique_ptr<surface_factory_interface<detector_t>> sf_factory,
0365     bool is_module_factory = false) {
0366   using scalar_t = dscalar<typename detector_t::algebra_type>;
0367   using mask_id = typename detector_t::masks::id;
0368   using material_id = typename detector_t::material::id;
0369 
0370   // Decorate the surfaces with material
0371   if (cfg.use_material_maps()) {
0372     // How to generate the specific material map for every surface
0373     material_map_config<scalar_t> material_map_config{};
0374 
0375     // Add the complete configuration for the beampipe map generation
0376     cfg.beampipe_material_map().map_id =
0377         static_cast<dindex>(material_id::e_concentric_cylinder2D_map);
0378     material_map_config.set_map_config(mask_id::e_concentric_cylinder2D,
0379                                        surface_id::e_passive,
0380                                        cfg.beampipe_material_map());
0381 
0382     // Add the complete configuration for disc map generation
0383     cfg.disc_material_map().map_id =
0384         static_cast<dindex>(material_id::e_ring2D_map);
0385     material_map_config.set_map_config(mask_id::e_ring2D, surface_id::e_portal,
0386                                        cfg.disc_material_map());
0387 
0388     // Add the complete configuration for cylinder map generation
0389     cfg.cyl_material_map().map_id =
0390         static_cast<dindex>(material_id::e_concentric_cylinder2D_map);
0391     material_map_config.set_map_config(mask_id::e_concentric_cylinder2D,
0392                                        surface_id::e_portal,
0393                                        cfg.cyl_material_map());
0394 
0395     auto mat_generator = std::make_shared<material_map_generator<detector_t>>(
0396         std::move(sf_factory), material_map_config);
0397 
0398     return mat_generator;
0399 
0400   } else if (!cfg.use_material_maps() && is_module_factory) {
0401     // How to generate the specific material for every surface
0402     auto mat_generator =
0403         std::make_shared<homogeneous_material_generator<detector_t>>(
0404             std::move(sf_factory), cfg.material_config());
0405 
0406     return mat_generator;
0407   } else {
0408     // If no material should be added, return the factory as is
0409     return sf_factory;
0410   }
0411 }
0412 
0413 /// Add the cylinder and disc portals for a volume from explicit parameters
0414 ///
0415 /// @param v_builder the volume builder to add the portals to
0416 /// @param cfg config for the toy detector
0417 /// @param vol_z half length of the cylinder
0418 /// @param h_z half length of the cylinder
0419 /// @param inner_r inner volume radius
0420 /// @param outer_r outer volume radius
0421 /// @param link_north portal volume link of the outer cylinder
0422 /// @param link_south portal volume link of the inner cylinder
0423 /// @param link_east portal volume link of the left disc
0424 /// @param link_west portal volume link of the right disc
0425 /// @param add_material decorate material maps to portals
0426 template <typename detector_t>
0427 void add_cylinder_portals(volume_builder_interface<detector_t> *v_builder,
0428                           toy_det_config<typename detector_t::scalar_type> &cfg,
0429                           const typename detector_t::scalar_type lower_z,
0430                           const typename detector_t::scalar_type upper_z,
0431                           const typename detector_t::scalar_type inner_r,
0432                           const typename detector_t::scalar_type outer_r,
0433                           const dindex link_north, const dindex link_south,
0434                           const dindex link_east, const dindex link_west) {
0435   using algebra_t = typename detector_t::algebra_type;
0436   using transform3_t = dtransform3D<algebra_t>;
0437   using scalar_t = dscalar<algebra_t>;
0438   using point3_t = dpoint3D<algebra_t>;
0439   using nav_link_t = typename detector_t::surface_type::navigation_link;
0440 
0441   const transform3_t identity{};
0442   const dindex vol_idx{v_builder->vol_index()};
0443 
0444   scalar_t min_r{math::min(inner_r, outer_r)};
0445   scalar_t max_r{math::max(inner_r, outer_r)};
0446   scalar_t min_z{math::min(lower_z, upper_z)};
0447   scalar_t max_z{math::max(lower_z, upper_z)};
0448 
0449   using factory_interface_t = surface_factory_interface<detector_t>;
0450   using cyl_factory_t = surface_factory<detector_t, concentric_cylinder2D>;
0451   using disc_factory_t = surface_factory<detector_t, ring2D>;
0452 
0453   std::shared_ptr<factory_interface_t> pt_cyl_factory{nullptr};
0454   std::shared_ptr<factory_interface_t> pt_disc_factory{nullptr};
0455 
0456   if (cfg.use_material_maps()) {
0457     pt_cyl_factory =
0458         decorate_material<detector_t>(cfg, std::make_unique<cyl_factory_t>());
0459     pt_disc_factory =
0460         decorate_material<detector_t>(cfg, std::make_unique<disc_factory_t>());
0461   } else {
0462     pt_cyl_factory = std::make_shared<cyl_factory_t>();
0463     pt_disc_factory = std::make_shared<disc_factory_t>();
0464   }
0465 
0466   // Inner cylinder portal
0467   pt_cyl_factory->push_back({surface_id::e_portal, identity,
0468                              static_cast<nav_link_t>(link_south),
0469                              std::vector<scalar_t>{min_r, min_z, max_z}});
0470   // Outer cylinder portal
0471   pt_cyl_factory->push_back({surface_id::e_portal, identity,
0472                              static_cast<nav_link_t>(link_north),
0473                              std::vector<scalar_t>{max_r, min_z, max_z}});
0474 
0475   // Left disc portal
0476   pt_disc_factory->push_back(
0477       {surface_id::e_portal,
0478        transform3_t{
0479            point3_t{static_cast<scalar_t>(0), static_cast<scalar_t>(0), min_z}},
0480        static_cast<nav_link_t>(link_west),
0481        std::vector<scalar_t>{min_r, max_r}});
0482   // Right disc portal
0483   pt_disc_factory->push_back(
0484       {surface_id::e_portal,
0485        transform3_t{
0486            point3_t{static_cast<scalar_t>(0), static_cast<scalar_t>(0), max_z}},
0487        static_cast<nav_link_t>(link_east),
0488        std::vector<scalar_t>{min_r, max_r}});
0489 
0490   v_builder->add_surfaces(pt_cyl_factory);
0491   v_builder->add_surfaces(pt_disc_factory);
0492 
0493   v_builder->set_name("gap_" + std::to_string(vol_idx));
0494 }
0495 
0496 /// Helper method for creating the barrel surface grids.
0497 ///
0498 /// @param det_builder detector builder the barrel section should be added to
0499 /// @param cfg config for the toy detector
0500 /// @param vol_index index of the volume to which the grid should be added
0501 template <typename detector_builder_t>
0502 inline void add_cylinder_grid(
0503     detector_builder_t &det_builder,
0504     toy_det_config<typename detector_builder_t::detector_type::scalar_type>
0505         &cfg,
0506     const dindex vol_index) {
0507   using detector_t = typename detector_builder_t::detector_type;
0508   using scalar_t = dscalar<typename detector_t::algebra_type>;
0509 
0510   constexpr auto grid_id =
0511       detector_t::accel::id::e_surface_concentric_cylinder2D_grid;
0512   using cyl_grid_t = types::get<typename detector_t::accel, grid_id>;
0513 
0514   using grid_builder_t =
0515       grid_builder<detector_t, cyl_grid_t, detray::fill_by_pos>;
0516 
0517   const auto &barrel_cfg{cfg.barrel_config()};
0518   const scalar_t h_z{barrel_cfg.half_length()};
0519 
0520   auto vgr_builder = det_builder.template decorate<grid_builder_t>(vol_index);
0521 
0522   if (!vgr_builder) {
0523     throw std::runtime_error("Grid decoration failed");
0524   }
0525 
0526   vgr_builder->set_type(detector_t::geo_obj_ids::e_sensitive);
0527   vgr_builder->init_grid(
0528       {-constant<scalar_t>::pi, constant<scalar_t>::pi, -h_z, h_z},
0529       {barrel_cfg.binning().first, barrel_cfg.binning().second});
0530 }
0531 
0532 /// Helper method for creating the endcap surface grids.
0533 ///
0534 /// @param det_builder detector builder the barrel section should be added to
0535 /// @param cfg config for the toy detector
0536 /// @param vol_index index of the volume to which the grid should be added
0537 template <typename detector_builder_t>
0538 inline void add_disc_grid(
0539     detector_builder_t &det_builder,
0540     toy_det_config<typename detector_builder_t::detector_type::scalar_type>
0541         &cfg,
0542     const dindex vol_index) {
0543   using detector_t = typename detector_builder_t::detector_type;
0544   using scalar_t = dscalar<typename detector_t::algebra_type>;
0545 
0546   constexpr auto grid_id = detector_t::accel::id::e_surface_ring2D_grid;
0547   using disc_grid_t = types::get<typename detector_t::accel, grid_id>;
0548 
0549   using grid_builder_t =
0550       grid_builder<detector_t, disc_grid_t, detray::fill_by_pos>;
0551 
0552   const auto &endcap_cfg{cfg.endcap_config()};
0553   const scalar_t inner_r{cfg.beampipe_vol_radius()};
0554   const scalar_t outer_r{cfg.outer_radius()};
0555 
0556   auto vgr_builder = det_builder.template decorate<grid_builder_t>(vol_index);
0557 
0558   if (!vgr_builder) {
0559     throw std::runtime_error("Grid decoration failed");
0560   }
0561 
0562   vgr_builder->set_type(detector_t::geo_obj_ids::e_sensitive);
0563   vgr_builder->init_grid(
0564       {inner_r, outer_r, -constant<scalar_t>::pi, constant<scalar_t>::pi},
0565       {endcap_cfg.binning().size(), endcap_cfg.binning().back()});
0566 }
0567 
0568 /// Helper method to retrieve the volume extent from a potentially decorated
0569 /// cylinder portal factory
0570 ///
0571 /// @param[in] cfg config for the toy detector
0572 /// @param[in] sf_factory (material) factory to get the cylinder extent from
0573 /// @param[out] vol_bounds boundary struct
0574 template <typename detector_t>
0575 inline void get_volume_extent(
0576     toy_det_config<typename detector_t::scalar_type> &cfg,
0577     const std::shared_ptr<surface_factory_interface<detector_t>> &sf_factory,
0578     typename cylinder_portal_generator<detector_t>::boundaries &vol_bounds) {
0579   // Get the volume extent from the cylinder factory
0580   const cylinder_portal_generator<detector_t> *cyl_factory{nullptr};
0581   if (cfg.use_material_maps()) {
0582     // Retrieve the underlying portal factory
0583     auto decorator =
0584         std::dynamic_pointer_cast<const factory_decorator<detector_t>>(
0585             sf_factory);
0586     if (!decorator) {
0587       throw std::bad_cast();
0588     }
0589     cyl_factory = dynamic_cast<const cylinder_portal_generator<detector_t> *>(
0590         decorator->get_factory());
0591   } else {
0592     // No material was decorated
0593     cyl_factory = dynamic_cast<const cylinder_portal_generator<detector_t> *>(
0594         sf_factory.get());
0595   }
0596   if (!cyl_factory) {
0597     throw std::bad_cast();
0598   }
0599 
0600   // Set the new current boundaries, to construct the next gap
0601   vol_bounds = cyl_factory->volume_boundaries();
0602 }
0603 
0604 /// Helper method for creating the barrel section.
0605 ///
0606 /// @param det_builder detector builder the barrel section should be added to
0607 /// @param gctx geometry context
0608 /// @param cfg config for the toy detector
0609 /// @param beampipe_idx index of the beampipe outermost volume
0610 ///
0611 /// @returns the radial extents of the barrel module layers and gap volumes
0612 template <typename detector_builder_t>
0613 inline auto add_barrel_detector(
0614     detector_builder_t &det_builder,
0615     typename detector_builder_t::detector_type::geometry_context &gctx,
0616     toy_det_config<typename detector_builder_t::detector_type::scalar_type>
0617         &cfg,
0618     dindex beampipe_idx) {
0619   using detector_t = typename detector_builder_t::detector_type;
0620   using algebra_t = typename detector_t::algebra_type;
0621   using scalar_t = dscalar<algebra_t>;
0622   using transform3_t = dtransform3D<algebra_t>;
0623   using nav_link_t = typename detector_t::surface_type::navigation_link;
0624 
0625   // Register the sizes in z per volume index
0626   std::vector<std::pair<dindex, extent2D<scalar_t>>> volume_sizes{};
0627 
0628   // Mask volume link for portals that exit the detector
0629   constexpr auto end_of_world{detail::invalid_value<nav_link_t>()};
0630   const transform3_t identity{};
0631 
0632   // Links to the connector gaps. Their volume index depends on the
0633   // number of endcap layer that are being constructed (before and after
0634   // the barrel volumes are constructed)
0635   auto link_east{end_of_world};
0636   auto link_west{end_of_world};
0637 
0638   if (cfg.n_edc_layers() > 0) {
0639     link_east = static_cast<nav_link_t>(det_builder.n_volumes() +
0640                                         2u * cfg.n_brl_layers() + 2u);
0641     link_west = static_cast<nav_link_t>(det_builder.n_volumes() -
0642                                         2u * cfg.n_edc_layers() + 1u);
0643   }
0644 
0645   const scalar_t h_z{cfg.barrel_config().half_length()};
0646   // Set the inner radius of the first gap to the radius of the beampipe vol.
0647   scalar_t gap_inner_r{cfg.beampipe_vol_radius()};
0648 
0649   typename cylinder_portal_generator<detector_t>::boundaries vol_bounds{};
0650 
0651   // Alternate barrel module layers and gap volumes
0652   bool is_gap = true;
0653   for (unsigned int i = 0u; i < 2u * cfg.n_brl_layers(); ++i) {
0654     // New volume
0655     auto v_builder = det_builder.new_volume(volume_id::e_cylinder);
0656     auto vm_builder = decorate_material(cfg, det_builder, v_builder);
0657     const dindex vol_idx{vm_builder->vol_index()};
0658 
0659     // The barrel volumes are centered at the origin
0660     vm_builder->add_volume_placement(identity);
0661 
0662     // Every second layer is a gap volume
0663     is_gap = !is_gap;
0664     if (is_gap) {
0665       auto link_north{vol_idx - 1};
0666       auto link_south{vol_idx - 3};
0667 
0668       // The first time a gap is built, it needs to link to the beampipe
0669       link_south = (i == 1u) ? beampipe_idx : link_south;
0670 
0671       detail::add_cylinder_portals(vm_builder, cfg, -h_z, h_z, gap_inner_r,
0672                                    vol_bounds.inner_radius, link_north,
0673                                    link_south, link_east, link_west);
0674       volume_sizes.push_back({vol_idx, {gap_inner_r, vol_bounds.inner_radius}});
0675 
0676       // Set the inner gap radius for the next gap volume
0677       gap_inner_r = vol_bounds.outer_radius;
0678 
0679       vm_builder->set_name("gap_" + std::to_string(vol_idx));
0680 
0681     } else {
0682       // Limit to maximum valid link
0683       auto link_north{std::min(
0684           vol_idx + 3, 2 * cfg.n_edc_layers() + 2 * cfg.n_brl_layers() + 1)};
0685       auto link_south{vol_idx + 1};
0686 
0687       // Configure the module factory for this layer
0688       auto &barrel_cfg = cfg.barrel_config();
0689 
0690       const unsigned int j{(i + 2u) / 2u};
0691       barrel_cfg.binning(cfg.barrel_layer_binning().at(j))
0692           .radius(cfg.barrel_layer_radii().at(j));
0693 
0694       // Configure the portal factory
0695       cylinder_portal_config<scalar_t> portal_cfg{};
0696 
0697       portal_cfg.envelope(cfg.envelope())
0698           .fixed_half_length(h_z)
0699           // Link the volume portals to its neighbors
0700           .link_north(link_north)
0701           .link_south(link_south)
0702           .link_east(link_east)
0703           .link_west(link_west);
0704 
0705       // Configure the material
0706       cfg.material_config().thickness(cfg.module_mat_thickness());
0707 
0708       // Add a layer of module surfaces (may have material)
0709       auto module_mat_factory = decorate_material<detector_t>(
0710           cfg,
0711           std::make_unique<barrel_generator<detector_t, rectangle2D>>(
0712               barrel_cfg),
0713           true);
0714 
0715       // Add cylinder and disc portals (may have material, depending on
0716       // whether material maps are being used or not)
0717       auto portal_mat_factory = decorate_material<detector_t>(
0718           cfg,
0719           std::make_unique<cylinder_portal_generator<detector_t>>(portal_cfg));
0720 
0721       vm_builder->add_surfaces(module_mat_factory, gctx);
0722       vm_builder->add_surfaces(portal_mat_factory);
0723 
0724       // Fill the volume extent into 'vol_bounds'
0725       get_volume_extent(cfg, portal_mat_factory, vol_bounds);
0726 
0727       volume_sizes.push_back(
0728           {vol_idx, {vol_bounds.inner_radius, vol_bounds.outer_radius}});
0729 
0730       vm_builder->set_name("barrel_" + std::to_string(vol_idx));
0731 
0732       // Add a cylinder grid to every barrel module layer
0733       add_cylinder_grid(det_builder, cfg, vol_idx);
0734     }
0735   }
0736 
0737   // Add a final gap volume to get to the full barrel radius
0738   auto v_builder = det_builder.new_volume(volume_id::e_cylinder);
0739   const dindex vol_idx{v_builder->vol_index()};
0740   v_builder->add_volume_placement(identity);
0741 
0742   detail::add_cylinder_portals(
0743       v_builder, cfg, -h_z, h_z, vol_bounds.outer_radius, cfg.outer_radius(),
0744       end_of_world, vol_idx - 2u, link_east, link_west);
0745   volume_sizes.push_back(
0746       {vol_idx, {vol_bounds.outer_radius, cfg.outer_radius()}});
0747 
0748   v_builder->set_name("gap_" + std::to_string(vol_idx));
0749 
0750   return volume_sizes;
0751 }
0752 
0753 /// Helper method for creating one of the two endcaps.
0754 ///
0755 /// @param det_builder detector builder the barrel section should be added to
0756 /// @param gctx geometry context
0757 /// @param cfg config for the toy detector
0758 /// @param beampipe_idx index of the beampipe outermost volume
0759 ///
0760 /// @returns the z extents of the endcap module layers and gap volumes
0761 template <typename detector_builder_t>
0762 inline auto add_endcap_detector(
0763     detector_builder_t &det_builder,
0764     typename detector_builder_t::detector_type::geometry_context &gctx,
0765     toy_det_config<typename detector_builder_t::detector_type::scalar_type>
0766         &cfg,
0767     dindex beampipe_idx) {
0768   using detector_t = typename detector_builder_t::detector_type;
0769   using algebra_t = typename detector_t::algebra_type;
0770   using scalar_t = dscalar<algebra_t>;
0771   using point3_t = dpoint3D<algebra_t>;
0772   using nav_link_t = typename detector_t::surface_type::navigation_link;
0773 
0774   std::vector<std::pair<dindex, extent2D<scalar_t>>> volume_sizes{};
0775 
0776   // Mask volume link for portals that exit the detector
0777   constexpr auto end_of_world{detail::invalid_value<nav_link_t>()};
0778   // Disc portal link to the barrel section (via connector gap volume, which
0779   // is the second volume initialized by this function, but built in a
0780   // later step, when all necessaty information is available)
0781   const dindex connector_link{det_builder.n_volumes() + 1u};
0782 
0783   const auto sign{static_cast<scalar_t>(cfg.endcap_config().side())};
0784 
0785   // Mask volume links
0786   auto link_north{end_of_world};
0787   auto link_south{beampipe_idx};
0788 
0789   // Set the inner radius of the first gap to the radius of the beampipe vol.
0790   const scalar_t inner_radius{cfg.beampipe_vol_radius()};
0791   const scalar_t outer_radius{cfg.outer_radius()};
0792 
0793   scalar_t gap_east_z{sign * cfg.barrel_config().half_length()};
0794 
0795   typename cylinder_portal_generator<detector_t>::boundaries vol_bounds{};
0796 
0797   // Alternate endcap module layers and gap volumes
0798   bool is_gap = true;
0799   for (dindex i = 0u; i < 2u * cfg.n_edc_layers(); ++i) {
0800     // New volume
0801     auto v_builder = det_builder.new_volume(volume_id::e_cylinder);
0802     auto vm_builder = decorate_material(cfg, det_builder, v_builder);
0803     const dindex vol_idx{vm_builder->vol_index()};
0804 
0805     // Don't build the first gap here, as this will be done in a separate
0806     // step once all portals of the barrel are constructed
0807     if (i == 1u) {
0808       const scalar_t gap_west_z{sign * std::min(std::abs(vol_bounds.upper_z),
0809                                                 std::abs(vol_bounds.lower_z))};
0810 
0811       volume_sizes.push_back({vol_idx, {gap_east_z, gap_west_z}});
0812 
0813       // Update the gap extent for the next gap
0814       gap_east_z = sign * std::max(std::abs(vol_bounds.upper_z),
0815                                    std::abs(vol_bounds.lower_z));
0816 
0817       is_gap = !is_gap;
0818       continue;
0819     }
0820 
0821     // Every second layer is a gap volume
0822     is_gap = !is_gap;
0823     if (is_gap) {
0824       auto link_east{static_cast<dindex>(static_cast<int>(vol_idx) +
0825                                          cfg.endcap_config().side() - 2)};
0826       auto link_west{static_cast<dindex>(static_cast<int>(vol_idx) -
0827                                          cfg.endcap_config().side() - 2)};
0828 
0829       const scalar_t gap_west_z{sign * std::min(std::abs(vol_bounds.upper_z),
0830                                                 std::abs(vol_bounds.lower_z))};
0831 
0832       const point3_t gap_center{static_cast<scalar_t>(0),
0833                                 static_cast<scalar_t>(0),
0834                                 0.5f * (gap_east_z + gap_west_z)};
0835       vm_builder->add_volume_placement({gap_center});
0836 
0837       detail::add_cylinder_portals(vm_builder, cfg, gap_west_z, gap_east_z,
0838                                    inner_radius, outer_radius, link_north,
0839                                    link_south, link_east, link_west);
0840 
0841       volume_sizes.push_back({vol_idx, {gap_east_z, gap_west_z}});
0842 
0843       // Set the inner gap radius for the next gap volume
0844       gap_east_z = sign * std::max(std::abs(vol_bounds.upper_z),
0845                                    std::abs(vol_bounds.lower_z));
0846 
0847       vm_builder->set_name("gap_" + std::to_string(vol_idx));
0848 
0849     } else {
0850       const dindex j{i / 2u};
0851 
0852       auto link_east{static_cast<dindex>(static_cast<int>(vol_idx) +
0853                                          cfg.endcap_config().side() + 2)};
0854       auto link_west{static_cast<dindex>(static_cast<int>(vol_idx) -
0855                                          cfg.endcap_config().side() + 2)};
0856 
0857       // The first endacp layer needs to link to the connector gap
0858       // The last endcap layer needs to exit the detector
0859       if (sign < 0) {
0860         link_east = (i == 0u) ? connector_link : link_east;
0861         link_west = (j == cfg.n_edc_layers() - 1u) ? end_of_world : link_west;
0862       } else {
0863         link_west = (i == 0u) ? connector_link : link_west;
0864         link_east = (j == cfg.n_edc_layers() - 1u) ? end_of_world : link_east;
0865       }
0866 
0867       // Position the volume at the respective endcap layer position
0868       const scalar_t center_z{sign * cfg.endcap_layer_positions().at(j)};
0869       const point3_t vol_center{static_cast<scalar_t>(0),
0870                                 static_cast<scalar_t>(0), center_z};
0871       vm_builder->add_volume_placement({vol_center});
0872 
0873       // Configure the module factory for this layer
0874       auto &endcap_cfg = cfg.endcap_config();
0875 
0876       endcap_cfg.center(center_z)
0877           .inner_radius(inner_radius)
0878           .outer_radius(outer_radius - cfg.envelope());
0879 
0880       // Configure the portal factory
0881       cylinder_portal_config<scalar_t> portal_cfg{};
0882 
0883       portal_cfg.envelope(cfg.envelope())
0884           .fixed_inner_radius(inner_radius)
0885           .fixed_outer_radius(outer_radius)
0886           // Link the volume portals to their neighbors
0887           .link_north(link_north)
0888           .link_south(link_south)
0889           .link_east(link_east)
0890           .link_west(link_west);
0891 
0892       // Configure the material
0893       cfg.material_config().thickness(cfg.module_mat_thickness());
0894       // Use different material thickness for cylinder portals in endcaps
0895       const scalar_t t{cfg.cyl_material_map().thickness};
0896       cfg.cyl_material_map().thickness = 0.15f * unit<scalar_t>::mm;
0897 
0898       // Add a layer of module surfaces (mat have material)
0899       auto module_mat_factory = decorate_material<detector_t>(
0900           cfg,
0901           std::make_unique<endcap_generator<detector_t, trapezoid2D>>(
0902               endcap_cfg),
0903           true);
0904 
0905       // Add cylinder and disc portals (may have material, depending on
0906       // whether material maps are being used or not)
0907       auto portal_mat_factory = decorate_material<detector_t>(
0908           cfg,
0909           std::make_unique<cylinder_portal_generator<detector_t>>(portal_cfg));
0910 
0911       // Reset cylinder material thickness
0912       cfg.cyl_material_map().thickness = t;
0913 
0914       vm_builder->add_surfaces(module_mat_factory, gctx);
0915       vm_builder->add_surfaces(portal_mat_factory);
0916 
0917       // Fill the volume extent into 'vol_bounds'
0918       get_volume_extent(cfg, portal_mat_factory, vol_bounds);
0919 
0920       // Set the new current boundaries to construct the next gap
0921       volume_sizes.push_back(
0922           {vol_idx, {vol_bounds.lower_z, vol_bounds.upper_z}});
0923 
0924       vm_builder->set_name("endcap_" + std::to_string(vol_idx));
0925 
0926       // Add a disc grid to every endcap module layer
0927       add_disc_grid(det_builder, cfg, vol_idx);
0928     }
0929   }
0930   return volume_sizes;
0931 }
0932 
0933 /// Helper method for creating the portals of one of endcap sections of the
0934 /// beampipe volume.
0935 ///
0936 /// @param beampipe_builder volume builder for the beampipe volume
0937 /// @param cfg config for the toy detector
0938 /// @param neg_edc_lay_sizes indices and z-extent of the endcap volumes of one
0939 ///                          detector side (positive or negative)
0940 template <typename detector_builder_t, typename vol_extent_data_t>
0941 inline void add_connector_portals(
0942     detector_builder_t &det_builder,
0943     toy_det_config<typename detector_builder_t::detector_type::scalar_type>
0944         &cfg,
0945     const dindex beampipe_idx, const vol_extent_data_t &edc_vol_extents,
0946     const vol_extent_data_t &brl_vol_extents) {
0947   using detector_t = typename detector_builder_t::detector_type;
0948   using algebra_t = typename detector_t::algebra_type;
0949   using transform3_t = dtransform3D<algebra_t>;
0950   using scalar_t = dscalar<algebra_t>;
0951   using point3_t = dpoint3D<algebra_t>;
0952   using nav_link_t = typename detector_t::surface_type::navigation_link;
0953 
0954   using factory_interface_t = surface_factory_interface<detector_t>;
0955   using cyl_factory_t = surface_factory<detector_t, concentric_cylinder2D>;
0956   using disc_factory_t = surface_factory<detector_t, ring2D>;
0957 
0958   std::shared_ptr<factory_interface_t> pt_cyl_factory{nullptr};
0959   std::shared_ptr<factory_interface_t> pt_disc_factory{nullptr};
0960 
0961   if (cfg.use_material_maps()) {
0962     pt_cyl_factory =
0963         decorate_material<detector_t>(cfg, std::make_unique<cyl_factory_t>());
0964     pt_disc_factory =
0965         decorate_material<detector_t>(cfg, std::make_unique<disc_factory_t>());
0966   } else {
0967     pt_cyl_factory = std::make_shared<cyl_factory_t>();
0968     pt_disc_factory = std::make_shared<disc_factory_t>();
0969   }
0970 
0971   // Mask volume link for portals that exit the detector
0972   constexpr auto end_of_world{detail::invalid_value<nav_link_t>()};
0973   const transform3_t identity{};
0974 
0975   // Set the inner radius of the gap to the radius of the beampipe vol.
0976   const scalar_t inner_r{cfg.beampipe_vol_radius()};
0977   const scalar_t outer_r{cfg.outer_radius()};
0978 
0979   // Get the data of the gap volume
0980   // The connector gap is always the second volume constructed in the endcaps
0981   const auto &connector_gap_data = edc_vol_extents[1];
0982   const dindex connector_gap_idx{connector_gap_data.first};
0983 
0984   const scalar_t side{std::copysign(1.f, connector_gap_data.second.lower)};
0985   const scalar_t gap_east_z{connector_gap_data.second.lower};
0986   const scalar_t gap_west_z{connector_gap_data.second.upper};
0987   const scalar_t min_z{math::min(gap_east_z, gap_west_z)};
0988   const scalar_t max_z{math::max(gap_east_z, gap_west_z)};
0989   const point3_t gap_center{static_cast<scalar_t>(0), static_cast<scalar_t>(0),
0990                             0.5f * (gap_east_z + gap_west_z)};
0991 
0992   // Check that the volume under construction is really the connector gap
0993   assert(std::abs(gap_east_z) == cfg.barrel_config().half_length() ||
0994          std::abs(gap_west_z) == cfg.barrel_config().half_length());
0995 
0996   volume_builder_interface<detector_t> *connector_builder =
0997       det_builder[connector_gap_idx];
0998 
0999   connector_builder->set_name("connector_gap_" +
1000                               std::to_string(connector_gap_idx));
1001   connector_builder->add_volume_placement({gap_center});
1002 
1003   // Go over all volume extends and build the corresponding disc portal
1004   std::vector<std::vector<scalar_t>> boundaries{};
1005   std::vector<nav_link_t> vol_links{};
1006   for (const auto &e : brl_vol_extents) {
1007     const scalar_t min_r{math::min(e.second.lower, e.second.upper)};
1008     const scalar_t max_r{math::max(e.second.lower, e.second.upper)};
1009 
1010     boundaries.push_back(std::vector<scalar_t>{min_r, max_r});
1011     vol_links.push_back(static_cast<nav_link_t>(e.first));
1012   }
1013   assert(boundaries.size() == vol_links.size());
1014 
1015   // Barrel-facing disc portal
1016   pt_disc_factory->push_back(
1017       {surface_id::e_portal,
1018        transform3_t{point3_t{static_cast<scalar_t>(0), static_cast<scalar_t>(0),
1019                              (side < 0.f) ? max_z : min_z}},
1020        vol_links, boundaries});
1021 
1022   // Outward-facing disc portal
1023   pt_disc_factory->push_back(
1024       {surface_id::e_portal,
1025        transform3_t{point3_t{static_cast<scalar_t>(0), static_cast<scalar_t>(0),
1026                              (side < 0.f) ? min_z : max_z}},
1027        static_cast<nav_link_t>(connector_gap_idx - 1u),
1028        std::vector<scalar_t>{inner_r, outer_r}});
1029 
1030   // Inner cylinder portal
1031   pt_cyl_factory->push_back({surface_id::e_portal, identity,
1032                              static_cast<nav_link_t>(beampipe_idx),
1033                              std::vector<scalar_t>{inner_r, min_z, max_z}});
1034 
1035   // Outer cylinder portal
1036   pt_cyl_factory->push_back({surface_id::e_portal, identity, end_of_world,
1037                              std::vector<scalar_t>{outer_r, min_z, max_z}});
1038 
1039   // Add all portals to the connector gap volume
1040   connector_builder->add_surfaces(pt_disc_factory);
1041   connector_builder->add_surfaces(pt_cyl_factory);
1042 }
1043 
1044 /// Helper method for creating the portals of the beampipe barrel section.
1045 ///
1046 /// @param beampipe_builder volume builder for the beampipe volume
1047 /// @param cfg config for the toy detector
1048 template <typename detector_t>
1049 inline void add_beampipe_portals(
1050     volume_builder_interface<detector_t> *beampipe_builder,
1051     toy_det_config<typename detector_t::scalar_type> &cfg) {
1052   using algebra_t = typename detector_t::algebra_type;
1053   using transform3_t = dtransform3D<algebra_t>;
1054   using scalar_t = dscalar<algebra_t>;
1055   using point3_t = dpoint3D<algebra_t>;
1056   using nav_link_t = typename detector_t::surface_type::navigation_link;
1057 
1058   using factory_interface_t = surface_factory_interface<detector_t>;
1059   using cyl_factory_t = surface_factory<detector_t, concentric_cylinder2D>;
1060   using disc_factory_t = surface_factory<detector_t, ring2D>;
1061 
1062   std::shared_ptr<factory_interface_t> pt_cyl_factory{nullptr};
1063 
1064   if (cfg.use_material_maps()) {
1065     pt_cyl_factory =
1066         decorate_material<detector_t>(cfg, std::make_unique<cyl_factory_t>());
1067   } else {
1068     pt_cyl_factory = std::make_shared<cyl_factory_t>();
1069   }
1070 
1071   // Mask volume link for portals that exit the detector
1072   constexpr auto end_of_world{detail::invalid_value<nav_link_t>()};
1073 
1074   const scalar_t h_z{cfg.barrel_config().half_length()};
1075   const scalar_t inner_r{0.f};
1076   const scalar_t outer_r{cfg.beampipe_vol_radius()};
1077 
1078   // If there are no endcaps, add the beampipe disc portals at the boundaries
1079   // of the barrel section
1080   if (cfg.n_edc_layers() == 0u) {
1081     std::shared_ptr<factory_interface_t> pt_disc_factory{nullptr};
1082     if (cfg.use_material_maps()) {
1083       pt_disc_factory = decorate_material<detector_t>(
1084           cfg, std::make_unique<disc_factory_t>());
1085     } else {
1086       pt_disc_factory = std::make_shared<disc_factory_t>();
1087     }
1088 
1089     // Lower dics portal
1090     pt_disc_factory->push_back(
1091         {surface_id::e_portal,
1092          transform3_t{point3_t{static_cast<scalar_t>(0),
1093                                static_cast<scalar_t>(0), -h_z}},
1094          end_of_world, std::vector<scalar_t>{inner_r, outer_r}});
1095 
1096     // Outward-facing disc portal
1097     pt_disc_factory->push_back(
1098         {surface_id::e_portal,
1099          transform3_t{
1100              point3_t{static_cast<scalar_t>(0), static_cast<scalar_t>(0), h_z}},
1101          end_of_world, std::vector<scalar_t>{inner_r, outer_r}});
1102 
1103     beampipe_builder->add_surfaces(pt_disc_factory);
1104   }
1105 
1106   // Cylinder portal that leads into the barrel section
1107   dindex first_barrel_idx{
1108       cfg.n_brl_layers() == 0u ? end_of_world : 2u * cfg.n_edc_layers() + 2u};
1109   pt_cyl_factory->push_back(
1110       {surface_id::e_portal, transform3_t{},
1111        static_cast<nav_link_t>(first_barrel_idx),
1112        std::vector<scalar_t>{outer_r,
1113                              -h_z + std::numeric_limits<scalar_t>::epsilon(),
1114                              h_z - std::numeric_limits<scalar_t>::epsilon()}});
1115 
1116   beampipe_builder->add_surfaces(pt_cyl_factory);
1117 }
1118 
1119 /// Helper method for creating the portals of one of the endcap sections of the
1120 /// beampipe volume.
1121 ///
1122 /// @param beampipe_builder volume builder for the beampipe volume
1123 /// @param cfg config for the toy detector
1124 /// @param neg_edc_lay_sizes indices and z-extent of the endcap volumes of one
1125 ///                          detector side (positive or negative)
1126 template <typename detector_t, typename layer_size_cont_t>
1127 inline void add_beampipe_portals(
1128     volume_builder_interface<detector_t> *beampipe_builder,
1129     toy_det_config<typename detector_t::scalar_type> &cfg,
1130     const layer_size_cont_t &edc_lay_sizes) {
1131   using algebra_t = typename detector_t::algebra_type;
1132   using transform3_t = dtransform3D<algebra_t>;
1133   using scalar_t = dscalar<algebra_t>;
1134   using point3_t = dpoint3D<algebra_t>;
1135   using nav_link_t = typename detector_t::surface_type::navigation_link;
1136 
1137   using factory_interface_t = surface_factory_interface<detector_t>;
1138   using cyl_factory_t = surface_factory<detector_t, concentric_cylinder2D>;
1139   using disc_factory_t = surface_factory<detector_t, ring2D>;
1140 
1141   // Mask volume link for portals that exit the detector
1142   constexpr auto end_of_world{detail::invalid_value<nav_link_t>()};
1143   // For which endcap side should the portals be constructed?
1144   const scalar_t side{std::copysign(1.f, edc_lay_sizes.front().second.lower)};
1145 
1146   std::shared_ptr<factory_interface_t> pt_cyl_factory{nullptr};
1147   std::shared_ptr<factory_interface_t> pt_disc_factory{nullptr};
1148 
1149   if (cfg.use_material_maps()) {
1150     pt_cyl_factory =
1151         decorate_material<detector_t>(cfg, std::make_unique<cyl_factory_t>());
1152     pt_disc_factory =
1153         decorate_material<detector_t>(cfg, std::make_unique<disc_factory_t>());
1154   } else {
1155     pt_cyl_factory = std::make_shared<cyl_factory_t>();
1156     pt_disc_factory = std::make_shared<disc_factory_t>();
1157   }
1158 
1159   const scalar_t inner_r{0.f};
1160   const scalar_t outer_r{cfg.beampipe_vol_radius()};
1161   scalar_t disc_pos_z{-side * std::numeric_limits<scalar_t>::max()};
1162 
1163   // Go over all volume extends and build the corresponding cylinder portals
1164   std::vector<std::vector<scalar_t>> boundaries{};
1165   std::vector<nav_link_t> vol_links{};
1166   for (const auto &e : edc_lay_sizes) {
1167     const scalar_t min_z{math::min(e.second.lower, e.second.upper)};
1168     const scalar_t max_z{math::max(e.second.lower, e.second.upper)};
1169 
1170     if (side < 0.f) {
1171       disc_pos_z = (disc_pos_z > min_z) ? min_z : disc_pos_z;
1172     } else {
1173       disc_pos_z = (disc_pos_z < max_z) ? max_z : disc_pos_z;
1174     }
1175 
1176     boundaries.push_back(std::vector<scalar_t>{outer_r, min_z, max_z});
1177     vol_links.push_back(static_cast<nav_link_t>(e.first));
1178   }
1179   assert(boundaries.size() == vol_links.size());
1180 
1181   pt_cyl_factory->push_back(
1182       {surface_id::e_portal, transform3_t{}, vol_links, boundaries});
1183 
1184   // Outward-facing disc portal
1185   pt_disc_factory->push_back(
1186       {surface_id::e_portal,
1187        transform3_t{point3_t{static_cast<scalar_t>(0), static_cast<scalar_t>(0),
1188                              disc_pos_z}},
1189        end_of_world, std::vector<scalar_t>{inner_r, outer_r}});
1190 
1191   // Add all portals to the beampipe volume
1192   beampipe_builder->add_surfaces(pt_disc_factory);
1193   beampipe_builder->add_surfaces(pt_cyl_factory);
1194 }
1195 
1196 }  // namespace detail
1197 
1198 /// Builds a detray geometry that contains the innermost tml layers. The number
1199 /// of barrel and endcap layers can be chosen, but all barrel layers should be
1200 /// present when an endcap detector is built to have the barrel region radius
1201 /// match the endcap diameter.
1202 ///
1203 /// @param resource vecmem memory resource to use for container allocations
1204 /// @param cfg toy detector configuration
1205 ///
1206 /// @returns a complete detector object
1207 template <concepts::algebra algebra_t>
1208 inline auto build_toy_detector(vecmem::memory_resource &resource,
1209                                toy_det_config<dscalar<algebra_t>> cfg = {}) {
1210   using scalar_t = dscalar<algebra_t>;
1211   using transform3_t = dtransform3D<algebra_t>;
1212 
1213   using builder_t = detector_builder<toy_metadata<algebra_t>, volume_builder>;
1214   using detector_t = typename builder_t::detector_type;
1215   using nav_link_t = typename detector_t::surface_type::navigation_link;
1216   using cyl_factory_t = surface_factory<detector_t, concentric_cylinder2D>;
1217   using vol_extent_container_t =
1218       std::vector<std::pair<dindex, detail::extent2D<scalar_t>>>;
1219 
1220   // Check config
1221   if (cfg.n_edc_layers() > cfg.endcap_layer_positions().size()) {
1222     throw std::invalid_argument(
1223         "ERROR: Too many endcap layers requested (max " +
1224         std::to_string(cfg.endcap_layer_positions().size()) + ")!");
1225   }
1226   if (cfg.n_brl_layers() > cfg.barrel_layer_radii().size() - 1u) {
1227     throw std::invalid_argument(
1228         "ERROR: Too many barrel layers requested (max " +
1229         std::to_string(cfg.barrel_layer_radii().size() - 1u) + ")!");
1230   }
1231   if (cfg.n_edc_layers() > 0 && cfg.n_brl_layers() < 4) {
1232     throw std::invalid_argument(
1233         "ERROR: All four barrel layers need to be present in order to add "
1234         "endcap layers");
1235   }
1236 
1237   // Toy detector builder
1238   builder_t det_builder;
1239   det_builder.set_name("toy_detector");
1240 
1241   // Geometry context object
1242   typename detector_t::geometry_context gctx{};
1243 
1244   // Add the volume that contains the beampipe
1245   cfg.material_config().thickness(cfg.beampipe_mat_thickness());
1246   auto beampipe_builder = detail::decorate_material(
1247       cfg, det_builder, det_builder.new_volume(volume_id::e_cylinder));
1248 
1249   const dindex beampipe_idx{beampipe_builder->vol_index()};
1250   beampipe_builder->add_volume_placement(transform3_t{});
1251   beampipe_builder->set_name("beampipe_" + std::to_string(beampipe_idx));
1252 
1253   // Add the beampipe as a passive material surface
1254   auto beampipe_factory = detail::decorate_material<detector_t>(
1255       cfg, std::make_unique<cyl_factory_t>(), true);
1256 
1257   scalar_t max_z{cfg.n_edc_layers() == 0u ? cfg.barrel_config().half_length()
1258                                           : cfg.endcap_layer_positions().at(
1259                                                 cfg.n_edc_layers() - 1u)};
1260   scalar_t min_z{-max_z};
1261 
1262   beampipe_factory->push_back(
1263       {surface_id::e_passive, transform3_t{},
1264        static_cast<nav_link_t>(beampipe_idx),
1265        std::vector<scalar_t>{cfg.barrel_layer_radii().at(0), min_z, max_z}});
1266 
1267   beampipe_builder->add_surfaces(beampipe_factory);
1268 
1269   // Build the negative endcap
1270   vol_extent_container_t neg_edc_vol_extents;
1271   if (cfg.n_edc_layers() > 0u) {
1272     cfg.endcap_config().side(-1);
1273 
1274     neg_edc_vol_extents =
1275         detail::add_endcap_detector(det_builder, gctx, cfg, beampipe_idx);
1276 
1277     // Add the beampipe volume portals for the negative endcap section
1278     detail::add_beampipe_portals(beampipe_builder, cfg, neg_edc_vol_extents);
1279   }
1280   // Build the barrel section
1281   vol_extent_container_t brl_vol_extents;
1282   if (cfg.n_brl_layers() > 0u) {
1283     brl_vol_extents =
1284         detail::add_barrel_detector(det_builder, gctx, cfg, beampipe_idx);
1285   }
1286   // Add the beampipe volume portals for the barrel section
1287   detail::add_beampipe_portals(beampipe_builder, cfg);
1288 
1289   // Build the positive endcap
1290   vol_extent_container_t pos_edc_vol_extents;
1291   if (cfg.n_edc_layers() > 0u) {
1292     cfg.endcap_config().side(1);
1293 
1294     pos_edc_vol_extents =
1295         detail::add_endcap_detector(det_builder, gctx, cfg, beampipe_idx);
1296 
1297     // Add the beampipe volume portals for the positive endcap section
1298     detail::add_beampipe_portals(beampipe_builder, cfg, pos_edc_vol_extents);
1299 
1300     // Add the connection between barrel and both endcaps
1301     // Negative endcap
1302     add_connector_portals(det_builder, cfg, beampipe_idx, neg_edc_vol_extents,
1303                           brl_vol_extents);
1304     // Positive endcap
1305     add_connector_portals(det_builder, cfg, beampipe_idx, pos_edc_vol_extents,
1306                           brl_vol_extents);
1307   }
1308 
1309   // Build and return the detector and fill the name map
1310   typename detector_t::name_map name_map{};
1311   auto det = det_builder.build(resource, name_map);
1312 
1313   if (cfg.do_check()) {
1314     const bool verbose_check{false};
1315     detray::detail::check_consistency(det, verbose_check, name_map);
1316   }
1317 
1318   DETRAY_DEBUG_HOST("\n" << detray::utils::print_detector(det, name_map));
1319 
1320   return std::make_pair(std::move(det), std::move(name_map));
1321 }
1322 
1323 }  // namespace detray