diff --git a/Core/include/Acts/Detector/MultiWireStructureBuilder.hpp b/Core/include/Acts/Detector/MultiWireStructureBuilder.hpp new file mode 100644 index 00000000000..e413d0d019e --- /dev/null +++ b/Core/include/Acts/Detector/MultiWireStructureBuilder.hpp @@ -0,0 +1,76 @@ +// This file is part of the Acts project. +// +// Copyright (C) 2022-2023 CERN for the benefit of the Acts project +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#pragma once + +#include "Acts/Detector/LayerStructureBuilder.hpp" +#include "Acts/Detector/ProtoBinning.hpp" +#include "Acts/Detector/interface/IDetectorComponentBuilder.hpp" +#include "Acts/Detector/interface/IExternalStructureBuilder.hpp" +#include "Acts/Detector/interface/IInternalStructureBuilder.hpp" +#include "Acts/Geometry/GeometryContext.hpp" +#include "Acts/Surfaces/Surface.hpp" +#include "Acts/Utilities/Logger.hpp" + +#include +#include +#include + +namespace Acts { +namespace Experimental { + +class MultiWireStructureBuilder { + public: + /// @brief Configuration struct for the MultiWireStructure Builder + + struct Config { + /// The name of the detector volume component + std::string name = ""; + + /// The surfaces of the Multi Wire + std::vector> mlSurfaces = {}; + + /// The bounds of the multi-wire volume + std::vector mlBounds = {}; + + // The binning of the multi wire structure + std::vector mlBinning = {}; + + /// A tolerance config + float toleranceOverlap = 10.; + }; + + /// Constructor + /// @param config The configure of the MultiWireStructureBuilder + /// @param logger logging instance for screen output + + MultiWireStructureBuilder( + const Config& config, + std::unique_ptr logger = Acts::getDefaultLogger( + "MultiWireStructureBuilder", Acts::Logging::VERBOSE)); + + ~MultiWireStructureBuilder() = default; + + /// Construct the detector component + + /// @param gctx The Geometry Context of the current geometry + /// @return a detector component object with the detector volume of the multilayer + + Acts::Experimental::DetectorComponent construct( + const Acts::GeometryContext& gctx); + + private: + Config mCfg; + + const Acts::Logger& logger() const { return *mLogger; } + + std::unique_ptr mLogger; +}; + +} // namespace Experimental +} // namespace Acts diff --git a/Core/include/Acts/Detector/detail/IndexedSurfacesGenerator.hpp b/Core/include/Acts/Detector/detail/IndexedSurfacesGenerator.hpp index 9a7a38a86b2..eb2581c1176 100644 --- a/Core/include/Acts/Detector/detail/IndexedSurfacesGenerator.hpp +++ b/Core/include/Acts/Detector/detail/IndexedSurfacesGenerator.hpp @@ -75,12 +75,8 @@ struct IndexedSurfacesGenerator { bvArray[ibv] = bv; } - // The indexed surfaces delegate - // IndexedSurfacesImpl indexedSurfaces(std::move(grid), bvArray, - // transform); indexed_updator indexedSurfaces(std::move(grid), bvArray, transform); - // Fill the bin indices IndexedGridFiller filler{binExpansion}; filler.oLogger = oLogger->cloneWithSuffix("_filler"); diff --git a/Core/include/Acts/Navigation/MultiLayerSurfacesUpdator.hpp b/Core/include/Acts/Navigation/MultiLayerSurfacesUpdator.hpp new file mode 100644 index 00000000000..b71ef8cba41 --- /dev/null +++ b/Core/include/Acts/Navigation/MultiLayerSurfacesUpdator.hpp @@ -0,0 +1,143 @@ +// This file is part of the Acts project. +// +// Copyright (C) 2022 CERN for the benefit of the Acts project +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#pragma once + +#include "Acts/Detector/DetectorVolume.hpp" +#include "Acts/Navigation/NavigationDelegates.hpp" +#include "Acts/Navigation/NavigationStateFillers.hpp" +#include "Acts/Navigation/NavigationStateUpdators.hpp" +#include "Acts/Utilities/VectorHelpers.hpp" + +#include +#include + +namespace Acts { +namespace Experimental { + +template +class MultiLayerSurfacesUpdatorImpl : public INavigationDelegate { + public: + /// Broadcast the grid type + using grid_type = grid_t; + + /// The grid where the indices are stored + grid_type grid; + + /// These are the cast parameters - copied from constructor + std::array casts{}; + + /// A transform to be applied to the position + Transform3 transform = Transform3::Identity(); + + /// The path generator + path_generator pgenerator; + + /// @brief Constructor for a grid based surface attacher + ///@param igrid the grid that is moved into this attacher + /// @param icasts is the cast values array + /// @param itr a transform applied to the global position + MultiLayerSurfacesUpdatorImpl( + grid_type&& igrid, const std::array& icasts, + const Transform3& itr = Transform3::Identity()) + : grid(std::move(igrid)), casts(icasts), transform(itr) {} + + MultiLayerSurfacesUpdatorImpl() = delete; + + void update(const GeometryContext& gctx, NavigationState& nState) const { + auto step = std::sqrt(std::pow(grid.binWidth()[0], 2) + + std::pow(grid.binWidth()[1], 2)); + auto path = pgenerator(nState.position, nState.direction, step, + grid.numLocalBins()[1]); + + std::vector surfCandidates = {}; + + for (const auto& p : path) { + const auto& entry = grid.atPosition(castPosition(p)); + const auto extracted = + IndexedSurfacesExtractor::extract(gctx, nState, entry); + surfCandidates.insert(surfCandidates.end(), extracted.begin(), + extracted.end()); + } + + resolveDuplicates(gctx, surfCandidates); + SurfacesFiller::fill(nState, surfCandidates); + } + + /// Cast into a lookup position + /// + /// @param position is the position of the update call + std::array castPosition( + const Vector3& position) const { + // Transform into local 3D frame + Vector3 tposition = transform * position; + + std::array casted{}; + fillCasts(tposition, casted, + std::make_integer_sequence{}); + return casted; + } + + /// Resolve duplicate on surface candidates + /// + /// @param gctx The geometry context of the current geometry + /// @param surfaces is the surface candidates to check and resolve for duplicates + void resolveDuplicates(const GeometryContext& gctx, + std::vector& surfaces) const { + // sorting the surfaces according to their radial distance + std::sort(surfaces.begin(), surfaces.end(), + [&gctx](const auto& surf1, const auto& surf2) { + if (surf1->center(gctx).x() != surf2->center(gctx).x()) { + return surf1->center(gctx).x() < surf2->center(gctx).x(); + } + if (surf1->center(gctx).y() != surf2->center(gctx).y()) { + return surf1->center(gctx).y() < surf2->center(gctx).y(); + } + return surf1->center(gctx).z() < surf2->center(gctx).z(); + }); + + // Remove the duplicates + surfaces.erase(std::unique(surfaces.begin(), surfaces.end()), + surfaces.end()); + } + + private: + /// Unroll the cast loop + /// @param position is the position of the update call + /// @param a is the array to be filled + template + void fillCasts(const Vector3& position, Array& a, + std::index_sequence /*indices*/) const { + ((a[idx] = VectorHelpers::cast(position, casts[idx])), ...); + } +}; + +struct PathGridSurfacesGenerator { + std::vector operator()(Vector3 startPosition, + const Vector3& direction, ActsScalar stepSize, + std::size_t numberOfSteps) const { + std::vector pathCoordinates = {}; + pathCoordinates.reserve(numberOfSteps); + + auto tposition = std::move(startPosition); + auto stepSizeY = stepSize * sin(Acts::VectorHelpers::phi(direction)); + auto stepSizeX = stepSize * cos(Acts::VectorHelpers::phi(direction)); + + for (std::size_t i = 0; i < numberOfSteps; i++) { + pathCoordinates.push_back(tposition); + tposition.y() = tposition.y() + stepSizeY; + tposition.x() = tposition.x() + stepSizeX; + } + + return pathCoordinates; + } +}; + +} // namespace Experimental + +} // namespace Acts diff --git a/Core/include/Acts/Navigation/SurfaceCandidatesUpdators.hpp b/Core/include/Acts/Navigation/SurfaceCandidatesUpdators.hpp index 53379492170..48edba8d9c1 100644 --- a/Core/include/Acts/Navigation/SurfaceCandidatesUpdators.hpp +++ b/Core/include/Acts/Navigation/SurfaceCandidatesUpdators.hpp @@ -13,6 +13,7 @@ #include "Acts/Detector/DetectorVolume.hpp" #include "Acts/Detector/Portal.hpp" #include "Acts/Geometry/GeometryContext.hpp" +#include "Acts/Navigation/MultiLayerSurfacesUpdator.hpp" #include "Acts/Navigation/NavigationState.hpp" #include "Acts/Navigation/NavigationStateFillers.hpp" #include "Acts/Navigation/NavigationStateUpdators.hpp" @@ -45,9 +46,8 @@ inline static void updateCandidates(const GeometryContext& gctx, (c.surface != nullptr) ? (*c.surface) : (c.portal->surface()); // Get the intersection @todo make a templated intersector - // TODO surface tolerance - auto sIntersection = sRep.intersect(gctx, position, direction, - c.boundaryCheck, s_onSurfaceTolerance); + auto sIntersection = + sRep.intersect(gctx, position, direction, c.boundaryCheck); // Re-order and swap if necessary if (sIntersection.intersection.pathLength + s_onSurfaceTolerance < nState.overstepTolerance and @@ -192,6 +192,13 @@ template using IndexedSurfacesImpl = IndexedUpdatorImpl; +/// @brief An indexed multi layer surface implementation access +/// +/// @tparam grid_type is the grid type used for this indexed lookup +template +using MultiLayerSurfacesImpl = + MultiLayerSurfacesUpdatorImpl; + /// @brief An indexed surface implementation with portal access /// ///@tparam inexed_updator is the updator for the indexed surfaces diff --git a/Core/src/Detector/CMakeLists.txt b/Core/src/Detector/CMakeLists.txt index a6dca8c4fc0..9a3d716da8e 100644 --- a/Core/src/Detector/CMakeLists.txt +++ b/Core/src/Detector/CMakeLists.txt @@ -15,4 +15,7 @@ target_sources( PortalGenerators.cpp ProtoDetector.cpp VolumeStructureBuilder.cpp + MultiWireStructureBuilder.cpp + + ) diff --git a/Core/src/Detector/MultiWireStructureBuilder.cpp b/Core/src/Detector/MultiWireStructureBuilder.cpp new file mode 100644 index 00000000000..19e656191b3 --- /dev/null +++ b/Core/src/Detector/MultiWireStructureBuilder.cpp @@ -0,0 +1,161 @@ +// This file is part of the Acts project. +// +// Copyright (C) 2022-2023 CERN for the benefit of the Acts project +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include "Acts/Detector/MultiWireStructureBuilder.hpp" + +#include "Acts/Detector/DetectorComponents.hpp" +#include "Acts/Detector/DetectorVolumeBuilder.hpp" +#include "Acts/Detector/LayerStructureBuilder.hpp" +#include "Acts/Detector/ProtoBinning.hpp" +#include "Acts/Detector/VolumeStructureBuilder.hpp" +#include "Acts/Detector/detail/GridAxisGenerators.hpp" +#include "Acts/Detector/detail/IndexedSurfacesGenerator.hpp" +#include "Acts/Detector/detail/ReferenceGenerators.hpp" +#include "Acts/Detector/interface/IExternalStructureBuilder.hpp" +#include "Acts/Detector/interface/IInternalStructureBuilder.hpp" +#include "Acts/Geometry/GeometryContext.hpp" +#include "Acts/Geometry/VolumeBounds.hpp" +#include "Acts/Navigation/DetectorVolumeFinders.hpp" +#include "Acts/Navigation/SurfaceCandidatesUpdators.hpp" +#include "Acts/Utilities/Logger.hpp" + +#include +#include +#include +#include +#include + +class MultiWireInternalStructureBuilder + : public Acts::Experimental::IInternalStructureBuilder { + public: + struct Config { + /// The internal surfaces + std::vector> iSurfaces; + + /// Definition of Binning + std::vector binning; + + /// Extra information, mainly for screen output + std::string auxiliary = ""; + + /// The transform into the local binning schema + Acts::Transform3 transform = Acts::Transform3::Identity(); + }; + + // Constructor + + MultiWireInternalStructureBuilder( + const Config& cfg, + std::unique_ptr mlogger = Acts::getDefaultLogger( + "MUltiWireInternalBuilder", Acts::Logging::INFO)) + : Acts::Experimental::IInternalStructureBuilder(), + m_cfg(cfg), + m_logger(std::move(mlogger)) {} + + Acts::Experimental::InternalStructure construct( + const Acts::GeometryContext& gctx) const final { + if (not m_cfg.auxiliary.empty()) { + ACTS_DEBUG(m_cfg.auxiliary); + } + + Acts::Experimental::DetectorVolumeUpdator internalVolumeUpdator = + Acts::Experimental::tryNoVolumes(); + // Create the indexed surfaces + auto internalSurfaces = m_cfg.iSurfaces; + Acts::Experimental::detail::IndexedSurfacesGenerator< + decltype(internalSurfaces), Acts::Experimental::MultiLayerSurfacesImpl> + isg{internalSurfaces, + {}, + {m_cfg.binning[0u].binValue, m_cfg.binning[1u].binValue}, + {m_cfg.binning[0u].expansion, m_cfg.binning[1u].expansion}}; + Acts::Experimental::detail::CenterReferenceGenerator rGenerator; + Acts::Experimental::detail::GridAxisGenerators::EqBoundEqBound aGenerator{ + {m_cfg.binning[0u].edges.front(), m_cfg.binning[0u].edges.back()}, + m_cfg.binning[0u].edges.size() - 1, + {m_cfg.binning[1u].edges.front(), m_cfg.binning[1u].edges.back()}, + m_cfg.binning[1u].edges.size() - 1}; + + auto sfCandidatesUpdator = isg(gctx, aGenerator, rGenerator); + + return {internalSurfaces, + {}, + std::move(sfCandidatesUpdator), + std::move(internalVolumeUpdator)}; + } + + private: + /// Configuration object + Config m_cfg; + + /// Private access method to the logger + const Acts::Logger& logger() const { return *m_logger; } + + /// logging instance + std::unique_ptr m_logger; +}; + +Acts::Experimental::MultiWireStructureBuilder::MultiWireStructureBuilder( + const Acts::Experimental::MultiWireStructureBuilder::Config& config, + std::unique_ptr logger) + : mCfg(config), mLogger(std::move(logger)) { + // check if the surfaces are set + if (mCfg.mlSurfaces.empty()) { + throw std::invalid_argument( + "MultiWireStructureBuilder: No surfaces are given"); + } else if (mCfg.mlBinning.size() != 2u) { + throw ::std::invalid_argument( + "MultiWireStructureBuilder: Invalid binning provided"); + } +} + +Acts::Experimental::DetectorComponent +Acts::Experimental::MultiWireStructureBuilder::construct( + const Acts::GeometryContext& gctx) { + if (mCfg.mlBounds.size() != 3u) { + throw std::invalid_argument( + "MultiWireStructureBuilder: Invalid dimension for bounds."); + } + + // Configure the external structure builder for the internal structure + Acts::Experimental::VolumeStructureBuilder::Config vsConfig; + vsConfig.boundsType = Acts::VolumeBounds::eCuboid; + + Acts::Extent cuboidExtent; + cuboidExtent.set(Acts::binX, -mCfg.mlBounds[0], mCfg.mlBounds[0]); + cuboidExtent.set(Acts::binY, -mCfg.mlBounds[1], mCfg.mlBounds[1]); + cuboidExtent.set(Acts::binZ, -mCfg.mlBounds[2], mCfg.mlBounds[2]); + + vsConfig.extent = cuboidExtent; + vsConfig.auxiliary = "Construct External Structure"; + + // Configure the internal structure builder for the internal structure + MultiWireInternalStructureBuilder::Config iConfig; + iConfig.iSurfaces = mCfg.mlSurfaces; + iConfig.binning = mCfg.mlBinning; + iConfig.auxiliary = "Construct Internal Structure"; + + Acts::Experimental::DetectorVolumeBuilder::Config dvConfig; + dvConfig.auxiliary = "Construct Detector Volume"; + dvConfig.name = mCfg.name; + dvConfig.internalsBuilder = + std::make_shared( + iConfig, + Acts::getDefaultLogger("MultiWire Internal Structure Builder", + Acts::Logging::VERBOSE)); + dvConfig.externalsBuilder = + std::make_shared( + vsConfig, Acts::getDefaultLogger("VolumeStructureBuilder", + Acts::Logging::VERBOSE)); + auto dvBuilder = std::make_shared( + dvConfig, + Acts::getDefaultLogger("DetectorVolumeBuilder", Acts::Logging::VERBOSE)); + + auto dvComponent = dvBuilder->construct(gctx); + + return dvComponent; +} diff --git a/Tests/UnitTests/Core/Detector/CMakeLists.txt b/Tests/UnitTests/Core/Detector/CMakeLists.txt index 2c912b97175..a492624ef18 100644 --- a/Tests/UnitTests/Core/Detector/CMakeLists.txt +++ b/Tests/UnitTests/Core/Detector/CMakeLists.txt @@ -16,5 +16,6 @@ add_unittest(ProtoBinning ProtoBinningTests.cpp) add_unittest(Portal PortalTests.cpp) add_unittest(PortalGenerators PortalGeneratorsTests.cpp) add_unittest(VolumeStructureBuilder VolumeStructureBuilderTests.cpp) +add_unittest(MultiWireStructureBuilder MultiWireStructureBuilderTests.cpp) diff --git a/Tests/UnitTests/Core/Detector/MultiWireStructureBuilderTests.cpp b/Tests/UnitTests/Core/Detector/MultiWireStructureBuilderTests.cpp new file mode 100644 index 00000000000..e7d202e8b19 --- /dev/null +++ b/Tests/UnitTests/Core/Detector/MultiWireStructureBuilderTests.cpp @@ -0,0 +1,87 @@ +// This file is part of the Acts project. +// +// Copyright (C) 2022-2023 CERN for the benefit of the Acts project +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include + +#include "Acts/Definitions/Algebra.hpp" +#include "Acts/Detector/MultiWireStructureBuilder.hpp" +#include "Acts/Geometry/GeometryContext.hpp" +#include "Acts/Surfaces/LineBounds.hpp" +#include "Acts/Surfaces/RectangleBounds.hpp" +#include "Acts/Surfaces/StrawSurface.hpp" +#include "Acts/Surfaces/Surface.hpp" +#include "Acts/Visualization/GeometryView3D.hpp" +#include "Acts/Visualization/IVisualization3D.hpp" +#include "Acts/Visualization/ObjVisualization3D.hpp" + +#include +#include +#include +#include +#include + +using namespace Acts; +using namespace Acts::Experimental; + +GeometryContext tContext; + +BOOST_AUTO_TEST_SUITE(Detector) + +BOOST_AUTO_TEST_CASE(Multi_Wire_Structure_Builder_StrawSurfacesCreation) { + // Create the surfaces of the multiwire structure-straw surfaces + std::vector> strawSurfaces = {}; + + // Set the number of surfaces along each dimension of the multi wire structure + // aligned along z axis + std::size_t nSurfacesY = 3; + std::size_t nSurfacesX = 15; + + double radius = 15.; + double halfZ = 250.; + + // The transform of the 1st surface + Vector3 ipos = {-0.5 * nSurfacesX * 2 * radius + radius, + -0.5 * nSurfacesY * 2 * radius + radius, 0.}; + AngleAxis3 rotation(M_PI / 2, Acts::Vector3(0., 1., 0.)); + + Vector3 pos = ipos; + + // Generate the surfaces + for (std::size_t i = 0; i < nSurfacesY; i++) { + for (std::size_t j = 0; j < nSurfacesX; j++) { + auto surface = Surface::makeShared( + Transform3(Translation3(pos)), radius, halfZ); + strawSurfaces.push_back(surface); + pos.x() = ipos.x() + 2 * j * radius; + pos.y() = ipos.y() + 2 * i * radius; + } + } + + std::vector vBounds = {0.5 * nSurfacesX * 2 * radius, + 0.5 * nSurfacesY * 2 * radius, halfZ}; + + MultiWireStructureBuilder::Config mlCfg; + mlCfg.name = "Multi_Layer_With_Wires"; + mlCfg.mlSurfaces = strawSurfaces; + mlCfg.mlBounds = vBounds; + mlCfg.mlBinning = { + ProtoBinning(Acts::binX, Acts::detail::AxisBoundaryType::Bound, + -vBounds[0], vBounds[0], nSurfacesX, 1u), + ProtoBinning(Acts::binY, Acts::detail::AxisBoundaryType::Bound, + -vBounds[1], vBounds[1], nSurfacesY, 0u)}; + + MultiWireStructureBuilder mlBuilder(mlCfg); + auto [volumes, portals, roots] = mlBuilder.construct(tContext); + + BOOST_CHECK(volumes.size() == 1u); + BOOST_CHECK(volumes.front()->surfaces().size() == nSurfacesX * nSurfacesY); + BOOST_CHECK(volumes.front()->volumes().empty()); + BOOST_CHECK(volumes.front()->surfaceCandidatesUpdator().connected()); +} + +BOOST_AUTO_TEST_SUITE_END() diff --git a/Tests/UnitTests/Core/Navigation/CMakeLists.txt b/Tests/UnitTests/Core/Navigation/CMakeLists.txt index 1cd210e7752..c75f1b40298 100644 --- a/Tests/UnitTests/Core/Navigation/CMakeLists.txt +++ b/Tests/UnitTests/Core/Navigation/CMakeLists.txt @@ -3,3 +3,5 @@ add_unittest(DetectorVolumeFinders DetectorVolumeFindersTests.cpp) add_unittest(NavigationState NavigationStateTests.cpp) add_unittest(NavigationStateUpdators NavigationStateUpdatorsTests.cpp) add_unittest(DetectorNavigator DetectorNavigatorTests.cpp) +add_unittest(MultiWireNavigation MultiWireNavigationTests.cpp) + diff --git a/Tests/UnitTests/Core/Navigation/MultiWireNavigationTests.cpp b/Tests/UnitTests/Core/Navigation/MultiWireNavigationTests.cpp new file mode 100644 index 00000000000..04941f5169e --- /dev/null +++ b/Tests/UnitTests/Core/Navigation/MultiWireNavigationTests.cpp @@ -0,0 +1,101 @@ +// This file is part of the Acts project. +// +// Copyright (C) 2022-2023 CERN for the benefit of the Acts project +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include + +#include "Acts/Definitions/Algebra.hpp" +#include "Acts/Detector/Detector.hpp" +#include "Acts/Detector/DetectorVolume.hpp" +#include "Acts/Detector/MultiWireStructureBuilder.hpp" +#include "Acts/Navigation/DetectorVolumeFinders.hpp" +#include "Acts/Navigation/NavigationState.hpp" +#include "Acts/Navigation/NavigationStateFillers.hpp" +#include "Acts/Navigation/NavigationStateUpdators.hpp" +#include "Acts/Navigation/SurfaceCandidatesUpdators.hpp" +#include "Acts/Surfaces/RectangleBounds.hpp" +#include "Acts/Surfaces/StrawSurface.hpp" +#include "Acts/Surfaces/Surface.hpp" +#include "Acts/Utilities/VectorHelpers.hpp" +#include "Acts/Utilities/detail/Grid.hpp" + +#include +#include +#include +#include + +using namespace Acts; +using namespace Acts::Experimental; +using namespace Acts::detail; + +GeometryContext tContext; + +BOOST_AUTO_TEST_SUITE(Experimental) + +BOOST_AUTO_TEST_CASE(Navigation_in_Indexed_Surfaces) { + using GlobalBin = size_t; + using LocalBin = std::array; + + std::vector> strawSurfaces = {}; + + // Set the number of surfaces along each dimension of the multi wire structure + // aligned along z axis + std::size_t nSurfacesY = 4; + std::size_t nSurfacesX = 15; + + double radius = 15.; + double halfZ = 250.; + + // The transform of the 1st surface + Vector3 ipos = {-0.5 * nSurfacesX * 2 * radius + radius, + -0.5 * nSurfacesY * 2 * radius + radius, 0.}; + + Vector3 pos = ipos; + + // Generate the surfaces + for (std::size_t i = 0; i < nSurfacesY; i++) { + for (std::size_t j = 0; j < nSurfacesX; j++) { + pos.x() = ipos.x() + 2 * j * radius; + + auto surface = Surface::makeShared( + Transform3(Translation3(pos)), radius, halfZ); + + strawSurfaces.push_back(surface); + } + + pos.y() = ipos.y() + 2 * (i + 1) * radius; + } + + std::vector vBounds = {0.5 * nSurfacesX * 2 * radius, + 0.5 * nSurfacesY * 2 * radius, halfZ}; + + MultiWireStructureBuilder::Config mlCfg; + mlCfg.name = "Multi_Layer_With_Wires"; + mlCfg.mlSurfaces = strawSurfaces; + + mlCfg.mlBinning = { + ProtoBinning(Acts::binX, Acts::detail::AxisBoundaryType::Bound, + -vBounds[0], vBounds[0], nSurfacesX, 1u), + ProtoBinning(Acts::binY, Acts::detail::AxisBoundaryType::Bound, + -vBounds[1], vBounds[1], nSurfacesY, 0u)}; + mlCfg.mlBounds = vBounds; + + MultiWireStructureBuilder mlBuilder(mlCfg); + auto [volumes, portals, roots] = mlBuilder.construct(tContext); + + Acts::Experimental::NavigationState nState; + nState.position = Acts::Vector3(0., -60., 0.); + nState.direction = Acts::Vector3(-1., 1., 0.); + + nState.currentVolume = volumes.front().get(); + nState.currentVolume->updateNavigationState(tContext, nState); + + // check the surface candidates after update (12 surfaces + 6 portals) + BOOST_CHECK(nState.surfaceCandidates.size() == 18u); +} + +BOOST_AUTO_TEST_SUITE_END()