Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2026-04-17 07:46:20

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 #include "Acts/Definitions/Algebra.hpp"
0012 #include "Acts/Geometry/BlueprintNode.hpp"
0013 #include "Acts/Geometry/ContainerBlueprintNode.hpp"
0014 #include "Acts/Geometry/Extent.hpp"
0015 #include "Acts/Geometry/LayerBlueprintNode.hpp"
0016 #include "Acts/Geometry/NavigationPolicyFactory.hpp"
0017 #include "Acts/Geometry/VolumeAttachmentStrategy.hpp"
0018 #include "Acts/Navigation/CylinderNavigationPolicy.hpp"
0019 #include "Acts/Surfaces/Surface.hpp"
0020 #include "Acts/Utilities/Logger.hpp"
0021 
0022 #include <concepts>
0023 #include <functional>
0024 #include <memory>
0025 #include <optional>
0026 #include <regex>
0027 #include <span>
0028 #include <string>
0029 #include <string_view>
0030 #include <type_traits>
0031 #include <utility>
0032 #include <variant>
0033 #include <vector>
0034 
0035 namespace Acts::Experimental {
0036 
0037 namespace detail {
0038 
0039 /// @brief Concept requiring @p BackendT to expose a nested `LayerSpec` type.
0040 ///
0041 /// A backend satisfying this concept must define a `LayerSpec` struct that
0042 /// carries the configuration needed to construct a single detector layer.
0043 template <typename BackendT>
0044 concept HasLayerSpec = requires { typename BackendT::LayerSpec; };
0045 
0046 /// @brief Concept requiring a backend to expose axis-definition support in its
0047 /// `LayerSpec`.
0048 ///
0049 /// In addition to satisfying @ref HasLayerSpec, the backend must define an
0050 /// `AxisDefinition` type and the corresponding `LayerSpec` must have
0051 /// - `axes` : optional axes used to orient sensitive surfaces, and
0052 /// - `layerAxes` : optional axes used to determine the layer transform from the
0053 ///   parent detector element shape.
0054 template <typename BackendT>
0055 concept HasAxisDefinition =
0056     HasLayerSpec<BackendT> && requires { typename BackendT::AxisDefinition; } &&
0057     requires(typename BackendT::LayerSpec layerSpec,
0058              typename BackendT::AxisDefinition axes,
0059              std::optional<typename BackendT::AxisDefinition> layerAxes) {
0060       layerSpec.axes = std::move(axes);
0061       { layerSpec.axes.has_value() } -> std::convertible_to<bool>;
0062       layerSpec.layerAxes = std::move(axes);
0063       layerSpec.layerAxes = std::move(layerAxes);
0064     };
0065 
0066 using LayerNodePtr = std::shared_ptr<LayerBlueprintNode>;
0067 using ContainerNodePtr = std::shared_ptr<ContainerBlueprintNode>;
0068 using SurfacePtr = std::shared_ptr<Acts::Surface>;
0069 using SurfaceVector = std::vector<SurfacePtr>;
0070 
0071 /// @brief Callback type that can replace or wrap a @ref LayerBlueprintNode.
0072 ///
0073 /// Receives the source layer element (or @c std::nullopt when no element
0074 /// context exists) and the newly created layer node, and returns the
0075 /// (possibly replaced) node to be added to the container.
0076 template <typename ElementT>
0077 using LayerCustomizer = std::function<std::shared_ptr<LayerBlueprintNode>(
0078     const std::optional<ElementT>&, std::shared_ptr<LayerBlueprintNode>)>;
0079 
0080 /// @brief Callback type that can replace or wrap a
0081 /// @ref CylinderContainerBlueprintNode.
0082 template <typename ElementT>
0083 using ContainerCustomizer =
0084     std::function<std::shared_ptr<ContainerBlueprintNode>(
0085         const ElementT&, std::shared_ptr<ContainerBlueprintNode>)>;
0086 
0087 /// @brief Concept satisfied when @p CallableT can be called with an optional
0088 /// element and a @ref LayerBlueprintNode shared pointer and returns a (possibly
0089 /// different) @ref LayerBlueprintNode shared pointer.
0090 ///
0091 /// Used to constrain the returning form of the `onLayer` callback accepted by
0092 /// the assembler builders.
0093 template <typename ElementT, typename CallableT>
0094 concept LayerNodeReturningCallable =
0095     std::invocable<CallableT&, const std::optional<ElementT>&, LayerNodePtr> &&
0096     std::same_as<std::invoke_result_t<
0097                      CallableT&, const std::optional<ElementT>&, LayerNodePtr>,
0098                  LayerNodePtr>;
0099 
0100 /// @brief Concept satisfied when @p CallableT can be called with an optional
0101 /// element and a mutable @ref LayerBlueprintNode reference and returns `void`.
0102 ///
0103 /// Used to constrain the in-place (mutating) form of the `onLayer` callback.
0104 template <typename ElementT, typename CallableT>
0105 concept LayerNodeReplacingCallable =
0106     std::invocable<CallableT&, const std::optional<ElementT>&,
0107                    LayerBlueprintNode&> &&
0108     std::same_as<
0109         std::invoke_result_t<CallableT&, const std::optional<ElementT>&,
0110                              LayerBlueprintNode&>,
0111         void>;
0112 
0113 /// @brief Concept satisfied when @p CallableT can be called with an element and
0114 /// a @ref ContainerBlueprintNode shared pointer and returns a (possibly
0115 /// different) @ref ContainerBlueprintNode shared pointer.
0116 ///
0117 /// Used to constrain the returning form of the `onContainer` callback.
0118 template <typename ElementT, typename CallableT>
0119 concept ContainerNodeReturningCallable =
0120     std::invocable<CallableT&, const ElementT&, ContainerNodePtr> &&
0121     std::same_as<
0122         std::invoke_result_t<CallableT&, const ElementT&, ContainerNodePtr>,
0123         ContainerNodePtr>;
0124 
0125 /// @brief Concept satisfied when @p CallableT can be called with an element and
0126 /// a mutable @ref ContainerBlueprintNode reference and returns `void`.
0127 ///
0128 /// Used to constrain the in-place (mutating) form of the `onContainer`
0129 /// callback.
0130 template <typename ElementT, typename CallableT>
0131 concept ContainerNodeReplacingCallable =
0132     std::invocable<CallableT&, const ElementT&, ContainerBlueprintNode&> &&
0133     std::same_as<std::invoke_result_t<CallableT&, const ElementT&,
0134                                       ContainerBlueprintNode&>,
0135                  void>;
0136 
0137 /// @brief Concept requiring a backend to provide a surface-construction method.
0138 ///
0139 /// The method must accept a span of sensitive child elements plus a `LayerSpec`
0140 /// and return the corresponding ACTS surfaces.
0141 template <typename BackendT>
0142 concept HasSurfaceFactory =
0143     HasLayerSpec<BackendT> &&
0144     requires(const BackendT& backend,
0145              std::span<const typename BackendT::Element> sensitives,
0146              const typename BackendT::LayerSpec& layerSpec) {
0147       {
0148         backend.makeSurfaces(sensitives, layerSpec)
0149       } -> std::same_as<SurfaceVector>;
0150     };
0151 
0152 /// @brief Optional backend capability to extract a layer transform from one
0153 /// context element and a layer specification.
0154 ///
0155 /// Backends that satisfy this concept may provide automatic layer-transform
0156 /// extraction (for example from detector-element geometry). The interface layer
0157 /// applies this only when a context element is available.
0158 template <typename BackendT>
0159 concept HasLayerTransformLookup =
0160     HasLayerSpec<BackendT> &&
0161     requires(const BackendT& backend, const typename BackendT::Element& elem,
0162              const typename BackendT::LayerSpec& layerSpec) {
0163       {
0164         backend.lookupLayerTransform(elem, layerSpec)
0165       } -> std::same_as<std::optional<Acts::Transform3>>;
0166     };
0167 
0168 /// @brief Concept requiring `LayerSpec` to carry an optional `layerName` field.
0169 ///
0170 /// The `layerName` member, when set, overrides the name derived from the
0171 /// detector element hierarchy when constructing a layer node.
0172 template <typename BackendT>
0173 concept HasLayerNameMember =
0174     HasLayerSpec<BackendT> && requires(typename BackendT::LayerSpec layerSpec,
0175                                        std::optional<std::string> layerName) {
0176       layerSpec.layerName = std::move(layerName);
0177       { layerSpec.layerName.has_value() } -> std::convertible_to<bool>;
0178     };
0179 
0180 /// @brief Optional backend capability for barrel/endcap assembly discovery.
0181 template <typename BackendT>
0182 concept HasBarrelEndcapClassifier = requires(
0183     const BackendT& backend, const typename BackendT::Element& element) {
0184   { backend.isBarrel(element) } -> std::same_as<bool>;
0185   { backend.isEndcap(element) } -> std::same_as<bool>;
0186   { backend.isTracker(element) } -> std::same_as<bool>;
0187 };
0188 
0189 /// @brief Concept that fully constrains a geometry backend usable with
0190 /// @ref BlueprintBuilder.
0191 ///
0192 /// A conforming backend must:
0193 /// - satisfy @ref HasSurfaceFactory and @ref HasLayerNameMember,
0194 /// - be constructible from a `Config` object and an `Acts::Logger` reference,
0195 /// - expose the element-hierarchy query interface (`world`, `nameOf`,
0196 ///   `children`, `parent`),
0197 /// - expose `isSensitive()`, and
0198 /// - support equality comparison between `Element` instances, and
0199 /// - define `static constexpr std::string_view kIdentifier`.
0200 template <typename BackendT>
0201 concept BlueprintBackend =
0202     HasSurfaceFactory<BackendT> && HasLayerNameMember<BackendT> &&
0203     requires(const typename BackendT::Config& cfg, const Acts::Logger& logger,
0204              const BackendT& backend,
0205              const typename BackendT::Element& element) {
0206       BackendT{cfg, logger};
0207       { BackendT::kIdentifier } -> std::convertible_to<std::string_view>;
0208       { backend.world() } -> std::same_as<typename BackendT::Element>;
0209       { backend.nameOf(element) } -> std::same_as<std::string>;
0210       {
0211         backend.children(element)
0212       } -> std::same_as<std::vector<typename BackendT::Element>>;
0213       { backend.parent(element) } -> std::same_as<typename BackendT::Element>;
0214       { backend.isSensitive(element) } -> std::same_as<bool>;
0215       requires requires(const typename BackendT::Element& a,
0216                         const typename BackendT::Element& b) {
0217         { a == b } -> std::convertible_to<bool>;
0218       };
0219     };
0220 
0221 }  // namespace detail
0222 
0223 template <detail::BlueprintBackend BackendT>
0224 class BlueprintBuilder;
0225 
0226 template <detail::BlueprintBackend BackendT>
0227 class SensorLayerAssembler;
0228 
0229 template <detail::BlueprintBackend BackendT>
0230 class SensorLayer;
0231 
0232 /// @brief Fluent builder that assembles a flat collection of cylindrical or
0233 /// disc-like detector layers from layer-representative detector elements into a
0234 /// @ref CylinderContainerBlueprintNode.
0235 ///
0236 /// Each supplied element represents one layer: its hierarchy path is used as
0237 /// the layer name and (optionally) its geometry drives the layer transform.
0238 /// Obtained from @ref BlueprintBuilder::layers().
0239 ///
0240 /// ```cpp
0241 /// builder.layers()
0242 ///   .barrel()
0243 ///   .setSensorAxes(myAxes)
0244 ///   .setLayerFilter(layerPattern)
0245 ///   .setContainer(containerElement)
0246 ///   .addTo(parentNode);
0247 /// ```
0248 ///
0249 /// @tparam BackendT Geometry backend that provides detector elements, layer
0250 ///         specifications, hierarchy traversal, sensitive-element
0251 ///         classification, and surface construction.
0252 template <detail::BlueprintBackend BackendT>
0253 class ElementLayerAssembler {
0254  public:
0255   /// The associated @ref BlueprintBuilder type.
0256   using Builder = BlueprintBuilder<BackendT>;
0257   /// Distinguishes barrel (Cylinder) from endcap (Disc) layer geometry.
0258   using LayerType = Acts::Experimental::LayerBlueprintNode::LayerType;
0259   /// Backend detector element handle type.
0260   using Element = typename BackendT::Element;
0261   /// Backend layer-specification type.
0262   using LayerSpec = typename BackendT::LayerSpec;
0263   /// Axis definition type, or `std::monostate` when the backend does not
0264   /// support axis definitions.
0265   using AxisDefinition =
0266       std::conditional_t<detail::HasAxisDefinition<BackendT>,
0267                          typename BackendT::AxisDefinition, std::monostate>;
0268   /// Callback type that can replace or wrap a @ref LayerBlueprintNode.
0269   using LayerCustomizer = detail::LayerCustomizer<Element>;
0270 
0271   /// @brief Set the layer geometry type explicitly.
0272   /// @param layerType `LayerType::Cylinder` for barrel, `LayerType::Disc` for
0273   ///                  endcap.
0274   /// @return `*this` (rvalue).
0275   [[nodiscard]] ElementLayerAssembler&& setLayerType(LayerType layerType) &&;
0276 
0277   /// @brief Shorthand for `setLayerType(LayerType::Disc)`.
0278   /// @return `*this` (rvalue).
0279   [[nodiscard]] ElementLayerAssembler&& endcap() &&;
0280 
0281   /// @brief Shorthand for `setLayerType(LayerType::Cylinder)`.
0282   /// @return `*this` (rvalue).
0283   [[nodiscard]] ElementLayerAssembler&& barrel() &&;
0284 
0285   /// @brief Shorthand for `setLayerType(LayerType::Plane)`.
0286   /// @return `*this` (rvalue).
0287   [[nodiscard]] ElementLayerAssembler&& planar() &&;
0288 
0289   /// @brief Set the axis definition used to orient sensitive surfaces.
0290   ///
0291   /// Only available when the backend defines an @ref AxisDefinition type and
0292   /// stores optional surface-axis information in `LayerSpec`.
0293   /// @param axes Axis definition forwarded to `LayerSpec::axes`.
0294   /// @return `*this` (rvalue).
0295   template <typename B = BackendT>
0296       [[nodiscard]] ElementLayerAssembler&& setSensorAxes(
0297           typename B::AxisDefinition axes) &&
0298       requires(detail::HasAxisDefinition<B>) {
0299         m_layerSpec.axes = std::move(axes);
0300         return std::move(*this);
0301       }
0302 
0303       /// @brief Set the axis definition used to derive the layer transform from the
0304       /// parent element shape.
0305       ///
0306       /// Only available when the backend defines an @ref AxisDefinition type and
0307       /// stores optional layer-axis information in `LayerSpec`.
0308       /// When set, the layer transform is extracted automatically from the
0309       /// geometry of the enclosing detector element.
0310       /// @param layerAxes Axis definition forwarded to `LayerSpec::layerAxes`.
0311       /// @return `*this` (rvalue).
0312       template <typename B = BackendT>
0313       [[nodiscard]] ElementLayerAssembler&& setLayerAxes(
0314           typename B::AxisDefinition layerAxes) &&
0315       requires(detail::HasAxisDefinition<B>) {
0316         m_layerSpec.layerAxes = std::move(layerAxes);
0317         return std::move(*this);
0318       }
0319 
0320       /// @brief Set the regex filter used to select layer elements inside the
0321       /// container by name string.
0322       /// @param pattern Regular-expression string; converted to `std::regex`
0323       ///                internally.
0324       /// @return `*this` (rvalue).
0325       [[nodiscard]] ElementLayerAssembler&& setLayerFilter(
0326           const std::string& pattern) &&;
0327 
0328   /// @brief Set the regex filter used to select layer elements inside the
0329   /// container.
0330   /// @param pattern Pre-compiled regular expression matched against each
0331   ///                child element name.
0332   /// @return `*this` (rvalue).
0333   [[nodiscard]] ElementLayerAssembler&& setLayerFilter(
0334       const std::regex& pattern) &&;
0335 
0336   /// @brief Set the detector element that acts as the containing volume for the
0337   /// layer search.
0338   /// @param container Element whose subtree is searched for layers matching the
0339   ///                  filter.
0340   /// @return `*this` (rvalue).
0341   [[nodiscard]] ElementLayerAssembler&& setContainer(
0342       const Element& container) &&;
0343 
0344   /// @brief Override the output container node name.
0345   ///
0346   /// If set, this name is used verbatim for the produced
0347   /// @ref CylinderContainerBlueprintNode. Otherwise, the name is taken from the
0348   /// configured container element (when @ref setContainer is used).
0349   /// @param containerName Explicit container-node name to use.
0350   /// @return `*this` (rvalue).
0351   [[nodiscard]] ElementLayerAssembler&& setContainerName(
0352       std::string containerName) &&;
0353 
0354   /// @brief Set an explicit suffix for produced layer node names.
0355   ///
0356   /// The final node name is `"<anchor path>|<suffix>"`. Use `std::nullopt` to
0357   /// clear a previously configured suffix.
0358   /// @param layerNameSuffix Optional suffix appended to each produced layer
0359   ///                        name.
0360   /// @return `*this` (rvalue).
0361   [[nodiscard]] ElementLayerAssembler&& setLayerNameSuffix(
0362       const std::optional<std::string>& layerNameSuffix) &&;
0363 
0364   /// @brief Set an explicit list of layer-representative elements.
0365   ///
0366   /// When set, the assembler skips subtree discovery via `setContainer()` and
0367   /// uses these elements directly as layer representatives. `setLayerFilter()`
0368   /// still applies as an optional post-filter on this list. Set either
0369   /// @ref setContainerName or @ref setContainer to define the output container
0370   /// name.
0371   /// @param layerElements Layer-representative elements; one layer per element.
0372   /// @return `*this` (rvalue).
0373   [[nodiscard]] ElementLayerAssembler&& setLayerElements(
0374       std::vector<Element> layerElements) &&;
0375 
0376   /// @brief Set the container element by name, searching from the world root.
0377   ///
0378   /// @throws std::runtime_error if no element with @p name is found.
0379   /// @param name Name of the detector element to use as the container.
0380   /// @return `*this` (rvalue).
0381   [[nodiscard]] ElementLayerAssembler&& setContainer(
0382       const std::string& name) &&;
0383 
0384   /// @brief Set an envelope to be applied to every layer node produced.
0385   /// @param envelope Envelope margins added around each layer's extent.
0386   /// @return `*this` (rvalue).
0387   [[nodiscard]] ElementLayerAssembler&& setEnvelope(
0388       const Acts::ExtentEnvelope& envelope) &&;
0389 
0390   /// @brief Control whether an empty layer collection is an error.
0391   ///
0392   /// When @p emptyOk is `false` (the default) and no layers are found in the
0393   /// container, @ref build() throws. Setting it to `true` downgrades the
0394   /// failure to an informational log message.
0395   /// @param emptyOk If `true`, silently accept an empty result.
0396   /// @return `*this` (rvalue).
0397   [[nodiscard]] ElementLayerAssembler&& setEmptyOk(bool emptyOk) &&;
0398 
0399   /// @brief Register a callback invoked for each created layer node.
0400   ///
0401   /// The callback may either:
0402   /// - return a (possibly replaced/wrapped) @ref LayerBlueprintNode, or
0403   /// - mutate a @ref LayerBlueprintNode in-place and return `void`.
0404   ///
0405   /// In both cases, the first argument is the source layer element.
0406   /// @param customizer Callback applied to each created layer node.
0407   /// @return `*this` (rvalue).
0408   template <typename CustomizerT>
0409   [[nodiscard]] ElementLayerAssembler&& onLayer(CustomizerT customizer) &&
0410     requires(
0411         detail::LayerNodeReturningCallable<Element,
0412                                            std::decay_t<CustomizerT>> ||
0413         detail::LayerNodeReplacingCallable<Element, std::decay_t<CustomizerT>>)
0414   {
0415     if constexpr (detail::LayerNodeReturningCallable<
0416                       Element, std::decay_t<CustomizerT>>) {
0417       m_onLayer = std::move(customizer);
0418     } else {
0419       m_onLayer = [customizer = std::move(customizer)](
0420                       const std::optional<Element>& layerElement,
0421                       std::shared_ptr<LayerBlueprintNode> layer) mutable {
0422         customizer(layerElement, *layer);
0423         return layer;
0424       };
0425     }
0426     return std::move(*this);
0427   }
0428 
0429   /// @brief Override the attachment strategy for the container node.
0430   ///
0431   /// When unset the backend's default strategy is used.
0432   /// @param strategy Optional attachment strategy; pass `std::nullopt` to
0433   ///                 reset to the default.
0434   /// @return `*this` (rvalue).
0435   [[nodiscard]] ElementLayerAssembler&& setAttachmentStrategy(
0436       std::optional<Acts::VolumeAttachmentStrategy> strategy) &&;
0437 
0438   /// @brief Build and return the assembled container node.
0439   ///
0440   /// Each resolved layer element becomes exactly one @ref LayerBlueprintNode.
0441   /// The layer name is derived from the element's full path in the hierarchy
0442   /// (plus an optional suffix). The layer transform is deduced from the element
0443   /// when `setLayerAxes()` is configured.
0444   ///
0445   /// @throws std::runtime_error if the layer type has not been set, if the
0446   ///         backend requires axes and none were provided, if neither a layer
0447   ///         filter nor explicit layer elements have been provided, if no
0448   ///         container name is resolvable, or if the container yields no
0449   ///         matching elements and @p emptyOk is `false`.
0450   /// @return Shared pointer to the fully assembled container node.
0451   [[nodiscard]] std::shared_ptr<Acts::Experimental::ContainerBlueprintNode>
0452   build() const;
0453 
0454   /// @brief Build the container node and attach it as a child of @p node.
0455   ///
0456   /// Equivalent to `node.addChild(build())`.
0457   /// @param node Blueprint node that will receive the built container as a
0458   ///             child.
0459   void addTo(Acts::Experimental::BlueprintNode& node) const&&;
0460 
0461  private:
0462   friend class BlueprintBuilder<BackendT>;
0463 
0464   /// @brief Construct an @ref ElementLayerAssembler bound to @p builder.
0465   /// @param builder The owning @ref BlueprintBuilder; must outlive this object.
0466   explicit ElementLayerAssembler(const Builder& builder);
0467 
0468   const Builder* m_builder = nullptr;
0469   std::optional<LayerType> m_layerType;
0470   LayerSpec m_layerSpec{};
0471   std::optional<std::regex> m_filter;
0472   std::optional<Element> m_container;
0473   std::optional<std::string> m_containerName;
0474   std::optional<std::vector<Element>> m_layerElements;
0475   std::optional<Acts::ExtentEnvelope> m_envelope;
0476   std::optional<Acts::VolumeAttachmentStrategy> m_attachmentStrategy;
0477   bool m_emptyOk = false;
0478   LayerCustomizer m_onLayer;
0479 };
0480 
0481 /// @brief Fluent builder that assembles multiple cylindrical or disc-like
0482 /// detector layers directly from sensor elements into a
0483 /// @ref CylinderContainerBlueprintNode.
0484 ///
0485 /// Unlike @ref ElementLayerAssembler, no layer-representative element is
0486 /// assumed to exist. Names and transforms are never deduced from the element
0487 /// hierarchy; they come solely from @ref groupBy keys.
0488 /// Obtained from @ref BlueprintBuilder::layersFromSensors().
0489 ///
0490 /// A `groupBy` function is required: sensors mapped to the same key are merged
0491 /// into one layer, and the key becomes the layer name.
0492 ///
0493 /// For a single layer (no grouping), use @ref SensorLayer via
0494 /// @ref BlueprintBuilder::layerFromSensors() instead.
0495 ///
0496 /// ```cpp
0497 /// builder.layersFromSensors()
0498 ///   .barrel()
0499 ///   .setSensorAxes(myAxes)
0500 ///   .setSensors(sensorElements)
0501 ///   .groupBy(keyExtractor)
0502 ///   .setContainerName("MyBarrel")
0503 ///   .addTo(parentNode);
0504 /// ```
0505 ///
0506 /// @tparam BackendT Geometry backend that provides detector elements, layer
0507 ///         specifications, hierarchy traversal, sensitive-element
0508 ///         classification, and surface construction.
0509 template <detail::BlueprintBackend BackendT>
0510 class SensorLayerAssembler {
0511  public:
0512   /// The associated @ref BlueprintBuilder type.
0513   using Builder = BlueprintBuilder<BackendT>;
0514   /// Distinguishes barrel (Cylinder) from endcap (Disc) layer geometry.
0515   using LayerType = LayerBlueprintNode::LayerType;
0516   /// Backend detector element handle type.
0517   using Element = typename BackendT::Element;
0518   /// Backend layer-specification type.
0519   using LayerSpec = typename BackendT::LayerSpec;
0520   /// Axis definition type, or `std::monostate` when the backend does not
0521   /// support axis definitions.
0522   using AxisDefinition =
0523       std::conditional_t<detail::HasAxisDefinition<BackendT>,
0524                          typename BackendT::AxisDefinition, std::monostate>;
0525   /// Callback type that can replace or wrap a @ref LayerBlueprintNode.
0526   using LayerCustomizer = detail::LayerCustomizer<Element>;
0527   /// Callable that maps a sensor element to a string group key.
0528   using LayerGrouper = std::function<std::string(const Element&)>;
0529 
0530   /// @brief Set the layer geometry type explicitly.
0531   /// @param layerType `LayerType::Cylinder` for barrel, `LayerType::Disc` for
0532   ///                  endcap.
0533   /// @return `*this` (rvalue).
0534   [[nodiscard]] SensorLayerAssembler&& setLayerType(LayerType layerType) &&;
0535 
0536   /// @brief Shorthand for `setLayerType(LayerType::Disc)`.
0537   /// @return `*this` (rvalue).
0538   [[nodiscard]] SensorLayerAssembler&& endcap() &&;
0539 
0540   /// @brief Shorthand for `setLayerType(LayerType::Cylinder)`.
0541   /// @return `*this` (rvalue).
0542   [[nodiscard]] SensorLayerAssembler&& barrel() &&;
0543 
0544   /// @brief Shorthand for `setLayerType(LayerType::Plane)`.
0545   /// @return `*this` (rvalue).
0546   [[nodiscard]] SensorLayerAssembler&& planar() &&;
0547 
0548   /// @brief Set the axis definition used to orient sensitive surfaces.
0549   ///
0550   /// Only available when the backend defines an @ref AxisDefinition type and
0551   /// stores optional surface-axis information in `LayerSpec`.
0552   /// @param axes Axis definition forwarded to `LayerSpec::axes`.
0553   /// @return `*this` (rvalue).
0554   template <typename B = BackendT>
0555       [[nodiscard]] SensorLayerAssembler&& setSensorAxes(
0556           typename B::AxisDefinition axes) &&
0557       requires(detail::HasAxisDefinition<B>) {
0558         m_layerSpec.axes = std::move(axes);
0559         return std::move(*this);
0560       }
0561 
0562       /// @brief Set the sensor elements to assemble into layers.
0563       /// @param sensors Sensor elements (leaf-level sensitives).
0564       /// @return `*this` (rvalue).
0565       [[nodiscard]] SensorLayerAssembler&& setSensors(
0566           std::vector<Element> sensors) &&;
0567 
0568   /// @brief Group sensors into layers by key (required).
0569   ///
0570   /// Sensors mapped to the same key are merged into one layer. The key becomes
0571   /// the layer name.
0572   /// @param grouper Callable `std::string(const Element& sensor)`.
0573   /// @return `*this` (rvalue).
0574   [[nodiscard]] SensorLayerAssembler&& groupBy(LayerGrouper grouper) &&;
0575 
0576   /// @brief Set the output container node name (required).
0577   /// @param containerName Name of the produced
0578   ///                      @ref CylinderContainerBlueprintNode.
0579   /// @return `*this` (rvalue).
0580   [[nodiscard]] SensorLayerAssembler&& setContainerName(
0581       std::string containerName) &&;
0582 
0583   /// @brief Set an envelope applied to every produced layer node.
0584   /// @param envelope Envelope margins added around each layer's extent.
0585   /// @return `*this` (rvalue).
0586   [[nodiscard]] SensorLayerAssembler&& setEnvelope(
0587       const Acts::ExtentEnvelope& envelope) &&;
0588 
0589   /// @brief Override the attachment strategy for the container node.
0590   ///
0591   /// When unset the backend's default strategy is used.
0592   /// @param strategy Optional attachment strategy; pass `std::nullopt` to
0593   ///                 reset to the default.
0594   /// @return `*this` (rvalue).
0595   [[nodiscard]] SensorLayerAssembler&& setAttachmentStrategy(
0596       std::optional<Acts::VolumeAttachmentStrategy> strategy) &&;
0597 
0598   /// @brief Register a callback invoked for each created layer node.
0599   ///
0600   /// The callback may either return a (possibly replaced/wrapped) layer node,
0601   /// or mutate a layer node in-place and return `void`.
0602   /// @param customizer Callback applied to each created layer node.
0603   /// @return `*this` (rvalue).
0604   template <typename CustomizerT>
0605   [[nodiscard]] SensorLayerAssembler&& onLayer(CustomizerT customizer) &&
0606     requires(
0607         detail::LayerNodeReturningCallable<Element,
0608                                            std::decay_t<CustomizerT>> ||
0609         detail::LayerNodeReplacingCallable<Element, std::decay_t<CustomizerT>>)
0610   {
0611     if constexpr (detail::LayerNodeReturningCallable<
0612                       Element, std::decay_t<CustomizerT>>) {
0613       m_onLayer = std::move(customizer);
0614     } else {
0615       m_onLayer = [customizer = std::move(customizer)](
0616                       const std::optional<Element>& elem,
0617                       std::shared_ptr<LayerBlueprintNode> layer) mutable {
0618         customizer(elem, *layer);
0619         return layer;
0620       };
0621     }
0622     return std::move(*this);
0623   }
0624 
0625   /// @brief Build and return the assembled container node.
0626   ///
0627   /// @throws std::runtime_error if the layer type is not set, if the backend
0628   ///         requires axes and none were provided, if sensors are not set,
0629   ///         if
0630   ///         the container name is not set, or if @ref groupBy has not been
0631   ///         configured.
0632   /// @return Shared pointer to the assembled container node.
0633   [[nodiscard]] std::shared_ptr<ContainerBlueprintNode> build() const;
0634 
0635   /// @brief Build the container node and attach it as a child of @p node.
0636   ///
0637   /// Equivalent to `node.addChild(build())`.
0638   /// @param node Blueprint node that will receive the built container as a
0639   ///             child.
0640   void addTo(BlueprintNode& node) const&&;
0641 
0642  private:
0643   friend class BlueprintBuilder<BackendT>;
0644 
0645   /// @brief Construct a @ref SensorLayerAssembler bound to @p builder.
0646   /// @param builder The owning @ref BlueprintBuilder; must outlive this object.
0647   explicit SensorLayerAssembler(const Builder& builder);
0648 
0649   const Builder* m_builder = nullptr;
0650   std::optional<LayerType> m_layerType;
0651   LayerSpec m_layerSpec{};
0652   std::optional<std::vector<Element>> m_sensors;
0653   LayerGrouper m_groupBy;
0654   std::optional<std::string> m_containerName;
0655   std::optional<Acts::ExtentEnvelope> m_envelope;
0656   std::optional<Acts::VolumeAttachmentStrategy> m_attachmentStrategy;
0657   LayerCustomizer m_onLayer;
0658 };
0659 
0660 /// @brief Fluent builder that assembles a single cylindrical or disc-like
0661 /// detector layer directly from sensor elements, returning a
0662 /// @ref LayerBlueprintNode (no container wrapper).
0663 ///
0664 /// Unlike @ref SensorLayerAssembler, this builder produces exactly one layer.
0665 /// The layer name must be provided explicitly via @ref setLayerName. No
0666 /// grouping function is required or supported.
0667 /// Obtained from @ref BlueprintBuilder::layerFromSensors().
0668 ///
0669 /// ```cpp
0670 /// builder.layerFromSensors()
0671 ///   .barrel()
0672 ///   .setSensorAxes(myAxes)
0673 ///   .setSensors(sensorElements)
0674 ///   .setLayerName("MyLayer")
0675 ///   .addTo(parentNode);
0676 /// ```
0677 ///
0678 /// @tparam BackendT Geometry backend that provides detector elements, layer
0679 ///         specifications, hierarchy traversal, sensitive-element
0680 ///         classification, and surface construction.
0681 template <detail::BlueprintBackend BackendT>
0682 class SensorLayer {
0683  public:
0684   /// The associated @ref BlueprintBuilder type.
0685   using Builder = BlueprintBuilder<BackendT>;
0686   /// Distinguishes barrel (Cylinder) from endcap (Disc) layer geometry.
0687   using LayerType = LayerBlueprintNode::LayerType;
0688   /// Backend detector element handle type.
0689   using Element = typename BackendT::Element;
0690   /// Backend layer-specification type.
0691   using LayerSpec = typename BackendT::LayerSpec;
0692   /// Axis definition type, or `std::monostate` when the backend does not
0693   /// support axis definitions.
0694   using AxisDefinition =
0695       std::conditional_t<detail::HasAxisDefinition<BackendT>,
0696                          typename BackendT::AxisDefinition, std::monostate>;
0697   /// Callback type that can replace or wrap a @ref LayerBlueprintNode.
0698   using LayerCustomizer = detail::LayerCustomizer<Element>;
0699 
0700   /// @brief Set the layer geometry type explicitly.
0701   /// @param layerType `LayerType::Cylinder` for barrel, `LayerType::Disc` for
0702   ///                  endcap.
0703   /// @return `*this` (rvalue).
0704   [[nodiscard]] SensorLayer&& setLayerType(LayerType layerType) &&;
0705 
0706   /// @brief Shorthand for `setLayerType(LayerType::Disc)`.
0707   /// @return `*this` (rvalue).
0708   [[nodiscard]] SensorLayer&& endcap() &&;
0709 
0710   /// @brief Shorthand for `setLayerType(LayerType::Cylinder)`.
0711   /// @return `*this` (rvalue).
0712   [[nodiscard]] SensorLayer&& barrel() &&;
0713 
0714   /// @brief Shorthand for `setLayerType(LayerType::Plane)`.
0715   /// @return `*this` (rvalue).
0716   [[nodiscard]] SensorLayer&& planar() &&;
0717 
0718   /// @brief Set the axis definition used to orient sensitive surfaces.
0719   ///
0720   /// Only available when the backend defines an @ref AxisDefinition type and
0721   /// stores optional surface-axis information in `LayerSpec`.
0722   /// @param axes Axis definition forwarded to `LayerSpec::axes`.
0723   /// @return `*this` (rvalue).
0724   template <typename B = BackendT>
0725       [[nodiscard]] SensorLayer&& setSensorAxes(
0726           typename B::AxisDefinition axes) &&
0727       requires(detail::HasAxisDefinition<B>) {
0728         m_layerSpec.axes = std::move(axes);
0729         return std::move(*this);
0730       }
0731 
0732       /// @brief Set the sensor elements to assemble into the layer.
0733       /// @param sensors Sensor elements (leaf-level sensitives).
0734       /// @return `*this` (rvalue).
0735       [[nodiscard]] SensorLayer&& setSensors(std::vector<Element> sensors) &&;
0736 
0737   /// @brief Set the name for the produced layer node (required).
0738   /// @param name Layer node name.
0739   /// @return `*this` (rvalue).
0740   [[nodiscard]] SensorLayer&& setLayerName(std::string name) &&;
0741 
0742   /// @brief Set an envelope applied to the produced layer node.
0743   /// @param envelope Envelope margins added around the layer's extent.
0744   /// @return `*this` (rvalue).
0745   [[nodiscard]] SensorLayer&& setEnvelope(
0746       const Acts::ExtentEnvelope& envelope) &&;
0747 
0748   /// @brief Register a callback invoked for the created layer node.
0749   ///
0750   /// The callback may either return a (possibly replaced/wrapped) layer node,
0751   /// or mutate a layer node in-place and return `void`.
0752   /// @param customizer Callback applied to the created layer node.
0753   /// @return `*this` (rvalue).
0754   template <typename CustomizerT>
0755   [[nodiscard]] SensorLayer&& onLayer(CustomizerT customizer) &&
0756     requires(
0757         detail::LayerNodeReturningCallable<Element,
0758                                            std::decay_t<CustomizerT>> ||
0759         detail::LayerNodeReplacingCallable<Element, std::decay_t<CustomizerT>>)
0760   {
0761     if constexpr (detail::LayerNodeReturningCallable<
0762                       Element, std::decay_t<CustomizerT>>) {
0763       m_onLayer = std::move(customizer);
0764     } else {
0765       m_onLayer = [customizer = std::move(customizer)](
0766                       const std::optional<Element>& elem,
0767                       std::shared_ptr<LayerBlueprintNode> layer) mutable {
0768         customizer(elem, *layer);
0769         return layer;
0770       };
0771     }
0772     return std::move(*this);
0773   }
0774 
0775   /// @brief Build and return the assembled layer node.
0776   ///
0777   /// @throws std::runtime_error if the layer type is not set, if the backend
0778   ///         requires axes and none were provided, if sensors are not set,
0779   ///         or
0780   ///         if @ref setLayerName has not been called.
0781   /// @return Shared pointer to the assembled @ref LayerBlueprintNode.
0782   [[nodiscard]] std::shared_ptr<LayerBlueprintNode> build() const;
0783 
0784   /// @brief Build the layer node and attach it as a child of @p node.
0785   ///
0786   /// Equivalent to `node.addChild(build())`.
0787   /// @param node Blueprint node that will receive the built layer as a child.
0788   void addTo(BlueprintNode& node) const&&;
0789 
0790  private:
0791   friend class BlueprintBuilder<BackendT>;
0792 
0793   /// @brief Construct a @ref SensorLayer bound to @p builder.
0794   /// @param builder The owning @ref BlueprintBuilder; must outlive this object.
0795   explicit SensorLayer(const Builder& builder);
0796 
0797   const Builder* m_builder = nullptr;
0798   std::optional<LayerType> m_layerType;
0799   LayerSpec m_layerSpec{};
0800   std::optional<std::vector<Element>> m_sensors;
0801   std::optional<std::string> m_layerName;
0802   std::optional<Acts::ExtentEnvelope> m_envelope;
0803   LayerCustomizer m_onLayer;
0804 };
0805 
0806 /// @brief Fluent builder that assembles a combined barrel + endcap subdetector
0807 /// into a @ref CylinderContainerBlueprintNode arranged along the Z axis.
0808 ///
0809 /// Instances are obtained from @ref BlueprintBuilder::barrelEndcap(). The
0810 /// builder inspects the subtree of the provided assembly element for barrel and
0811 /// endcap children (using the backend's `isBarrel` / `isEndcap` / `isTracker`
0812 /// predicates, when available) and delegates individual layer assembly to
0813 /// @ref ElementLayerAssembler internally.
0814 ///
0815 /// Typical usage:
0816 /// @code
0817 /// builder.barrelEndcap()
0818 ///   .setAssembly(innerTrackerElement)
0819 ///   .setSensorAxes(barrelAxes, endcapAxes)
0820 ///   .setLayerFilter(layerPattern)
0821 ///   .addTo(rootNode);
0822 /// @endcode
0823 ///
0824 /// This builder requires backend predicates that classify elements as barrel,
0825 /// endcap, and tracker components.
0826 /// @tparam BackendT Geometry backend that provides detector elements, layer
0827 ///         specifications, hierarchy traversal, sensitive-element
0828 ///         classification, and surface construction.
0829 template <detail::BlueprintBackend BackendT>
0830 class BarrelEndcapAssembler {
0831  public:
0832   /// The associated @ref BlueprintBuilder type.
0833   using Builder = BlueprintBuilder<BackendT>;
0834   /// Backend detector element handle type.
0835   using Element = typename BackendT::Element;
0836   /// Axis definition type, or `std::monostate` when the backend does not
0837   /// support axis definitions.
0838   using AxisDefinition =
0839       std::conditional_t<detail::HasAxisDefinition<BackendT>,
0840                          typename BackendT::AxisDefinition, std::monostate>;
0841   /// The @ref ElementLayerAssembler specialisation for this backend.
0842   using ElementLayerAssembler =
0843       ::Acts::Experimental::ElementLayerAssembler<BackendT>;
0844   /// Callback type that can replace or wrap a
0845   /// @ref CylinderContainerBlueprintNode.
0846   using ContainerCustomizer = detail::ContainerCustomizer<Element>;
0847 
0848   /// @brief Construct a @ref BarrelEndcapAssembler bound to @p builder.
0849   /// @param builder The owning @ref BlueprintBuilder; must outlive this object.
0850   explicit BarrelEndcapAssembler(const Builder& builder);
0851 
0852   /// @brief Build and return the assembled barrel+endcap container node.
0853   ///
0854   /// Locates barrel and endcap sub-elements inside the assembly, creates one
0855   /// @ref ElementLayerAssembler -based barrel container and one or more endcap
0856   /// containers, then returns a Z-axis @ref CylinderContainerBlueprintNode
0857   /// holding them all.
0858   ///
0859   /// @throws std::runtime_error if the assembly element has not been set, if
0860   ///         axes are required by the backend but not provided, if the layer
0861   ///         filter has not been set, or if more than one barrel element is
0862   ///         found inside the assembly.
0863   /// @return Shared pointer to the assembled Z-axis container node.
0864   [[nodiscard]] std::shared_ptr<CylinderContainerBlueprintNode> build() const
0865     requires(detail::HasBarrelEndcapClassifier<BackendT>);
0866 
0867   /// @brief Build the container node and attach it as a child of @p node.
0868   ///
0869   /// Equivalent to `node.addChild(build())`.
0870   /// @param node Blueprint node that will receive the built container as a
0871   ///             child.
0872   void addTo(BlueprintNode& node) const&&
0873     requires(detail::HasBarrelEndcapClassifier<BackendT>);
0874 
0875   /// @brief Register a layer callback forwarded to each inner
0876   /// @ref ElementLayerAssembler.
0877   ///
0878   /// The callback may either return a (possibly replaced/wrapped) layer node,
0879   /// or mutate a layer node in-place and return `void`.
0880   /// @param customizer Callback applied to each created layer node.
0881   /// @return `*this` (rvalue).
0882   template <typename CustomizerT>
0883   [[nodiscard]] BarrelEndcapAssembler&& onLayer(CustomizerT customizer) &&
0884     requires(
0885         detail::LayerNodeReturningCallable<Element,
0886                                            std::decay_t<CustomizerT>> ||
0887         detail::LayerNodeReplacingCallable<Element, std::decay_t<CustomizerT>>)
0888   {
0889     if constexpr (detail::LayerNodeReturningCallable<
0890                       Element, std::decay_t<CustomizerT>>) {
0891       m_onLayer = std::move(customizer);
0892     } else {
0893       m_onLayer = [customizer = std::move(customizer)](
0894                       const std::optional<Element>& elem,
0895                       std::shared_ptr<LayerBlueprintNode> layer) mutable {
0896         customizer(elem, *layer);
0897         return layer;
0898       };
0899     }
0900     return std::move(*this);
0901   }
0902 
0903   /// @brief Register a callback invoked for each barrel or endcap container
0904   /// node.
0905   ///
0906   /// The callback may either return a (possibly replaced/wrapped) container
0907   /// node, or mutate a container node in-place and return `void`.
0908   /// @param customizer Callback applied to each created barrel or endcap
0909   ///        container node.
0910   /// @return `*this` (rvalue).
0911   template <typename CustomizerT>
0912   [[nodiscard]] BarrelEndcapAssembler&& onContainer(CustomizerT customizer) &&
0913     requires(detail::ContainerNodeReturningCallable<
0914                  Element, std::decay_t<CustomizerT>> ||
0915              detail::ContainerNodeReplacingCallable<Element,
0916                                                     std::decay_t<CustomizerT>>)
0917   {
0918     if constexpr (detail::ContainerNodeReturningCallable<
0919                       Element, std::decay_t<CustomizerT>>) {
0920       m_onContainer = std::move(customizer);
0921     } else {
0922       m_onContainer =
0923           [customizer = std::move(customizer)](
0924               const Element& elem,
0925               std::shared_ptr<ContainerBlueprintNode> node) mutable {
0926             customizer(elem, *node);
0927             return node;
0928           };
0929     }
0930     return std::move(*this);
0931   }
0932 
0933   /// @brief Set the top-level detector element whose subtree is searched for
0934   /// barrel and endcap elements.
0935   /// @param assembly Root element of the barrel+endcap sub-detector.
0936   /// @return `*this` (rvalue).
0937   [[nodiscard]] BarrelEndcapAssembler&& setAssembly(const Element& assembly) &&;
0938 
0939   /// @brief Set the axis definitions for both barrel and endcap layers at once.
0940   ///
0941   /// Only available when the backend defines an @ref AxisDefinition type and
0942   /// stores optional surface-axis information in `LayerSpec`.
0943   /// @param barrel Axis definition forwarded to barrel
0944   ///               @ref ElementLayerAssembler s.
0945   /// @param endcap Axis definition forwarded to endcap
0946   ///               @ref ElementLayerAssembler s.
0947   /// @return `*this` (rvalue).
0948   [[nodiscard]] BarrelEndcapAssembler&& setSensorAxes(AxisDefinition barrel,
0949                                                       AxisDefinition endcap) &&
0950     requires(detail::HasAxisDefinition<BackendT>);
0951 
0952   /// @brief Set the axis definition used for endcap layers only.
0953   ///
0954   /// Only available when the backend defines an @ref AxisDefinition type and
0955   /// stores optional surface-axis information in `LayerSpec`.
0956   /// @param axes Axis definition forwarded to endcap
0957   ///             @ref ElementLayerAssembler s.
0958   /// @return `*this` (rvalue).
0959   [[nodiscard]] BarrelEndcapAssembler&& setEndcapAxes(AxisDefinition axes) &&
0960     requires(detail::HasAxisDefinition<BackendT>);
0961 
0962   /// @brief Set the regex filter used to select individual layer elements
0963   /// within each barrel or endcap container.
0964   /// @param pattern Regular expression matched against child element names.
0965   /// @return `*this` (rvalue).
0966   [[nodiscard]] BarrelEndcapAssembler&& setLayerFilter(
0967       const std::regex& pattern) &&;
0968 
0969  private:
0970   typename ElementLayerAssembler::LayerCustomizer m_onLayer;
0971   ContainerCustomizer m_onContainer =
0972       [](const Element&, std::shared_ptr<ContainerBlueprintNode> node) {
0973         return node;
0974       };
0975 
0976   std::optional<Element> m_assembly;
0977   std::optional<AxisDefinition> m_barrelAxes;
0978   std::optional<AxisDefinition> m_endcapAxes;
0979   std::optional<std::regex> m_layerFilter;
0980   const Builder* m_builder = nullptr;
0981 };
0982 
0983 /// @brief High-level builder that converts a backend detector element hierarchy
0984 /// into a blueprint node tree.
0985 ///
0986 /// @ref BlueprintBuilder provides the entry points for blueprint construction:
0987 /// - @ref BlueprintBuilder::layers() returns an @ref ElementLayerAssembler
0988 ///   for building a layer stack from layer-representative detector elements,
0989 /// - @ref BlueprintBuilder::layersFromSensors() returns a
0990 ///   @ref SensorLayerAssembler for building multiple layers directly from
0991 ///   sensor elements (`groupBy` required),
0992 /// - @ref BlueprintBuilder::layerFromSensors() returns a @ref SensorLayer for
0993 ///   building a single layer directly from sensor elements,
0994 /// - @ref BlueprintBuilder::barrelEndcap() returns a
0995 ///   @ref BarrelEndcapAssembler for combined barrel+endcap sub-detectors.
0996 ///
0997 /// It also exposes helpers for traversing and querying the detector element
0998 /// hierarchy, which are used internally by the assembler classes.
0999 ///
1000 /// The builder is parameterised on a backend type @p BackendT. The backend
1001 /// must be constructible from its `Config` plus an `Acts::Logger`, expose
1002 /// `Element` and `LayerSpec` types, traverse the detector hierarchy via
1003 /// `world()` / `children()` / `parent()` / `nameOf()`, classify sensitive
1004 /// elements with `isSensitive()`, and build ACTS surfaces from sensitive
1005 /// elements and a layer specification. It encapsulates all
1006 /// geometry-framework-specific knowledge (e.g. DD4hep `DetElement`
1007 /// navigation, type-flag interpretation, surface conversion).
1008 ///
1009 /// @tparam BackendT Geometry backend that provides detector elements, layer
1010 ///         specifications, hierarchy traversal, sensitive-element
1011 ///         classification, and surface construction.
1012 template <detail::BlueprintBackend BackendT>
1013 class BlueprintBuilder {
1014  public:
1015   /// The backend type.
1016   using Backend = BackendT;
1017   /// Backend detector element handle type.
1018   using Element = typename Backend::Element;
1019   /// Backend layer-specification type.
1020   using LayerSpec = typename Backend::LayerSpec;
1021   /// Axis definition type, or `std::monostate` when the backend does not
1022   /// support axis definitions.
1023   using AxisDefinition =
1024       std::conditional_t<detail::HasAxisDefinition<Backend>,
1025                          typename Backend::AxisDefinition, std::monostate>;
1026   /// The @ref ElementLayerAssembler specialisation for this backend.
1027   using ElementLayerAssembler =
1028       ::Acts::Experimental::ElementLayerAssembler<Backend>;
1029   /// The @ref SensorLayerAssembler specialisation for this backend.
1030   using SensorLayerAssembler =
1031       ::Acts::Experimental::SensorLayerAssembler<Backend>;
1032   /// The @ref SensorLayer specialisation for this backend.
1033   using SensorLayer = ::Acts::Experimental::SensorLayer<Backend>;
1034   /// The @ref BarrelEndcapAssembler specialisation for this backend.
1035   using BarrelEndcapAssembler =
1036       ::Acts::Experimental::BarrelEndcapAssembler<Backend>;
1037 
1038   /// @brief Construct a `BlueprintBuilder` from a backend configuration.
1039   ///
1040   /// @param cfg  Backend-specific configuration object passed directly to the
1041   ///             backend constructor.
1042   /// @param logger_ Optional logger; defaults to an `INFO`-level logger named
1043   ///                `"BlueprintBuilder"`.
1044   explicit BlueprintBuilder(const typename Backend::Config& cfg,
1045                             std::unique_ptr<const Acts::Logger> logger_ =
1046                                 Acts::getDefaultLogger("BlueprintBuilder",
1047                                                        Acts::Logging::INFO));
1048 
1049   /// @brief Create a @ref LayerBlueprintNode from a single detector element.
1050   ///
1051   /// Recursively collects all sensitive descendants of @p layerElement and
1052   /// delegates to the two-argument overload.
1053   /// @param layerElement Layer element whose subtree is scanned for
1054   ///                   sensitive volumes.
1055   /// @param layerSpec  Specification controlling surface axes and optional
1056   ///                   name/transform overrides.
1057   /// @return Shared pointer to the constructed @ref LayerBlueprintNode.
1058   std::shared_ptr<LayerBlueprintNode> makeLayer(
1059       const Element& layerElement, const LayerSpec& layerSpec) const;
1060 
1061   /// @brief Create a @ref LayerBlueprintNode from an explicit list of sensitive
1062   /// elements.
1063   ///
1064   /// This is a low-level forwarding API: @p layerSpec is passed to the backend
1065   /// as-is.
1066   /// @param parent     Detector element that contextualises the layer
1067   ///                   (used for naming and transform extraction).
1068   /// @param sensitives Span of sensitive detector elements to be converted to
1069   ///                   surfaces and assigned to the node.
1070   /// @param layerSpec  Specification controlling surface axes and optional
1071   ///                   name/transform overrides.
1072   /// @return Shared pointer to the constructed @ref LayerBlueprintNode.
1073   std::shared_ptr<LayerBlueprintNode> makeLayer(
1074       const Element& parent, std::span<const Element> sensitives,
1075       const LayerSpec& layerSpec) const;
1076 
1077   /// @brief Create a @ref LayerBlueprintNode from sensitive elements without
1078   /// a parent/context element.
1079   ///
1080   /// This variant cannot perform parent-based transform extraction.
1081   /// `layerSpec.layerName` must be set and is used verbatim.
1082   /// @param sensitives Span of sensitive detector elements to be converted to
1083   ///                   surfaces and assigned to the node.
1084   /// @param layerSpec  Specification controlling surface axes and name.
1085   /// @throws std::runtime_error if `layerSpec.layerName` is not set.
1086   /// @return Shared pointer to the constructed @ref LayerBlueprintNode.
1087   std::shared_ptr<LayerBlueprintNode> makeLayer(
1088       std::span<const Element> sensitives, const LayerSpec& layerSpec) const;
1089 
1090   /// @brief Create an @ref ElementLayerAssembler bound to this builder.
1091   ///
1092   /// Use when layer-representative detector elements exist in the hierarchy.
1093   /// Each element becomes one layer; name and transform are deduced from it.
1094   /// @return A new @ref ElementLayerAssembler instance.
1095   [[nodiscard]] ElementLayerAssembler layers() const;
1096 
1097   /// @brief Create a @ref SensorLayerAssembler bound to this builder.
1098   ///
1099   /// Use when sensor elements are supplied directly and no layer-representative
1100   /// element exists. A @ref SensorLayerAssembler::groupBy function is required;
1101   /// sensors sharing the same key are merged into one layer per key. For a
1102   /// single layer without grouping, use @ref layerFromSensors() instead.
1103   /// @return A new @ref SensorLayerAssembler instance.
1104   [[nodiscard]] SensorLayerAssembler layersFromSensors() const;
1105 
1106   /// @brief Create a @ref SensorLayer bound to this builder.
1107   ///
1108   /// Use when all sensor elements belong to exactly one layer and no grouping
1109   /// is needed. The layer name must be set explicitly via
1110   /// @ref SensorLayer::setLayerName. The result is a single
1111   /// @ref LayerBlueprintNode (no container wrapper).
1112   /// @return A new @ref SensorLayer instance.
1113   [[nodiscard]] SensorLayer layerFromSensors() const;
1114 
1115   /// @brief Create a @ref BarrelEndcapAssembler bound to this builder.
1116   ///
1117   /// The returned assembler must be configured (assembly element, axes, layer
1118   /// filter) and then finalised via @ref BarrelEndcapAssembler::build() or
1119   /// @ref BarrelEndcapAssembler::addTo().
1120   /// @return A new @ref BarrelEndcapAssembler instance.
1121   [[nodiscard]] BarrelEndcapAssembler barrelEndcap() const
1122     requires(detail::HasBarrelEndcapClassifier<Backend>);
1123 
1124   /// @brief Search for a detector element by exact name within a subtree.
1125   ///
1126   /// Performs a depth-first search starting at @p parent.
1127   /// @param parent Starting element for the search.
1128   /// @param name   Exact element name to match.
1129   /// @return The first matching element, or `std::nullopt` if not found.
1130   std::optional<Element> findDetElementByName(const Element& parent,
1131                                               const std::string& name) const;
1132 
1133   /// @brief Search for a detector element by exact name starting from the world
1134   /// root.
1135   /// @param name Exact element name to match.
1136   /// @return The first matching element, or `std::nullopt` if not found.
1137   std::optional<Element> findDetElementByName(const std::string& name) const;
1138 
1139   /// @brief Build a separator-joined path string from the world root down to
1140   /// @p elem.
1141   ///
1142   /// The path consists of element names at each level of the hierarchy, from
1143   /// the immediate child of the world element down to @p elem, joined by
1144   /// @p separator.
1145   /// @param elem      Target element.
1146   /// @param separator String inserted between successive name components
1147   ///                  (default: `"|"`).
1148   /// @return The assembled path string.
1149   std::string getPathToElementName(const Element& elem,
1150                                    std::string_view separator = "|") const;
1151 
1152   /// @brief Collect all elements in a subtree whose names match a regex.
1153   ///
1154   /// Performs a depth-first traversal of the subtree rooted at @p parent and
1155   /// returns every element whose name fully matches @p pattern
1156   /// (`std::regex_match`).
1157   /// @param parent  Root of the subtree to search.
1158   /// @param pattern Regular expression matched against each element name.
1159   /// @return Vector of matching elements in depth-first order.
1160   std::vector<Element> findDetElementByNamePattern(
1161       const Element& parent, const std::regex& pattern) const;
1162 
1163   /// @brief Collect all barrel tracker elements within an assembly subtree.
1164   ///
1165   /// An element is included when the backend reports it as both a tracker
1166   /// element and a barrel element.
1167   /// @param assembly Root element of the assembly subtree to search.
1168   /// @return Vector of barrel elements in depth-first order.
1169   std::vector<Element> findBarrelElements(const Element& assembly) const
1170     requires(detail::HasBarrelEndcapClassifier<Backend>);
1171 
1172   /// @brief Collect all endcap tracker elements within an assembly subtree.
1173   ///
1174   /// An element is included when the backend reports it as both a tracker
1175   /// element and an endcap element.
1176   /// @param assembly Root element of the assembly subtree to search.
1177   /// @return Vector of endcap elements in depth-first order.
1178   std::vector<Element> findEndcapElements(const Element& assembly) const
1179     requires(detail::HasBarrelEndcapClassifier<Backend>);
1180 
1181   /// @brief Return the logger associated with this builder.
1182   /// @return Reference to the logger instance.
1183   const Acts::Logger& logger() const;
1184 
1185   /// @brief Return the backend associated with this builder.
1186   /// @return Const reference to the backend instance.
1187   const Backend& backend() const;
1188 
1189   /// @brief Recursively collect all sensitive descendant elements.
1190   ///
1191   /// Traverses the subtree rooted at @p detElement and returns every element
1192   /// for which the backend's `isSensitive()` predicate returns `true`.
1193   /// @param detElement Root of the subtree to scan.
1194   /// @return Vector of sensitive elements in depth-first order.
1195   std::vector<Element> resolveSensitives(const Element& detElement) const;
1196 
1197  private:
1198   std::unique_ptr<const Acts::Logger> m_logger;
1199   Backend m_backend;
1200 };
1201 
1202 }  // namespace Acts::Experimental