From ccd2d3e4390c28b50938976f85b24e2acfca1894 Mon Sep 17 00:00:00 2001 From: ssdetlab <113530373+ssdetlab@users.noreply.github.com> Date: Mon, 22 Jan 2024 15:46:18 +0200 Subject: [PATCH] feat: Cuboidal detector builder from blueprint (#2887) Adding the Unit Test for the Cuboidal Container Builder. The Unit Test creates a simple Telescope-style geometry with two arms and four layers from a Blueprint. The Builder chain is tested to reproduce the detector with expected parameters. Changes in BlueprintHelper are made to accommodate the Cuboidal geometry. The old fillGaps method is separated into fillGapsCylindrical, fillGapsCuboidal and the interface method that calls the specific implementations. Modifications are made in the endPoint finding function to accommodate not only Z, but also X and Y directions. It should be noted that without adjustToParent flag being set to true, gaps are not guaranteed to be filled properly. A small fix related to the selection of the Portal volume attachment direction is added to the CuboidalDetectorHelper. Now it is possible to connect containers and create >1D connected structures. It should be noted that the portal merging is not yet consistent between the volume and container connections. --- CI/codespell_ignore.txt | 1 + .../Acts/Detector/detail/BlueprintHelper.hpp | 14 +- Core/src/Detector/detail/BlueprintHelper.cpp | 327 ++++++++++----- .../detail/CuboidalDetectorHelper.cpp | 11 +- .../Core/Detector/BlueprintHelperTests.cpp | 25 +- Tests/UnitTests/Core/Detector/CMakeLists.txt | 1 + .../CuboidalDetectorFromBlueprintTests.cpp | 374 ++++++++++++++++++ 7 files changed, 620 insertions(+), 133 deletions(-) create mode 100644 Tests/UnitTests/Core/Detector/CuboidalDetectorFromBlueprintTests.cpp diff --git a/CI/codespell_ignore.txt b/CI/codespell_ignore.txt index 43a7da5831e..b6550f340ea 100644 --- a/CI/codespell_ignore.txt +++ b/CI/codespell_ignore.txt @@ -21,3 +21,4 @@ ans dthe dthe vart +pixelx \ No newline at end of file diff --git a/Core/include/Acts/Detector/detail/BlueprintHelper.hpp b/Core/include/Acts/Detector/detail/BlueprintHelper.hpp index 076facab8e5..59dc89d75b3 100644 --- a/Core/include/Acts/Detector/detail/BlueprintHelper.hpp +++ b/Core/include/Acts/Detector/detail/BlueprintHelper.hpp @@ -28,10 +28,20 @@ void sort(Blueprint::Node& node, bool recursive = true); /// /// @param node the node for with the gaps should be filled /// @param adjustToParent nodes, if nodes should be adjusted to parent -/// -/// @note currently only cylindrical volumes are supported void fillGaps(Blueprint::Node& node, bool adjustToParent = true); +/// @brief Fill the gaps in the Cylindrical container node +/// +/// @param node the node for with the gaps should be filled +/// @param adjustToParent nodes, if nodes should be adjusted to parent +void fillGapsCylindrical(Blueprint::Node& node, bool adjustToParent = true); + +/// @brief Fill the gaps in the Cuboidal container node +/// +/// @param node the node for with the gaps should be filled +/// @param adjustToParent nodes, if nodes should be adjusted to parent +void fillGapsCuboidal(Blueprint::Node& node, bool adjustToParent = true); + } // namespace BlueprintHelper } // namespace detail } // namespace Experimental diff --git a/Core/src/Detector/detail/BlueprintHelper.cpp b/Core/src/Detector/detail/BlueprintHelper.cpp index dc452d2757b..08984d333f6 100644 --- a/Core/src/Detector/detail/BlueprintHelper.cpp +++ b/Core/src/Detector/detail/BlueprintHelper.cpp @@ -16,13 +16,27 @@ namespace { -std::array cylEndpointsZ( - const Acts::Experimental::Blueprint::Node& node) { - Acts::Vector3 axisZ = node.transform.rotation().col(2); - auto halfZ = node.boundaryValues[2]; +std::array endPointsXYZ( + const Acts::Experimental::Blueprint::Node& node, Acts::BinningValue bVal) { + unsigned int bIdx = 0; + switch (bVal) { + case Acts::binX: + bIdx = 0; + break; + case Acts::binY: + bIdx = 1; + break; + case Acts::binZ: + bIdx = 2; + break; + default: + break; + } + Acts::Vector3 axis = node.transform.rotation().col(bIdx); + auto halfL = node.boundaryValues[bIdx]; Acts::Vector3 center = node.transform.translation(); - Acts::Vector3 p0 = center - halfZ * axisZ; - Acts::Vector3 p1 = center + halfZ * axisZ; + Acts::Vector3 p0 = center - halfL * axis; + Acts::Vector3 p1 = center + halfL * axis; return {p0, p1}; } @@ -69,118 +83,215 @@ void Acts::Experimental::detail::BlueprintHelper::fillGaps( if (node.isLeaf()) { return; } + if (node.boundsType == VolumeBounds::eCylinder && node.binning.size() == 1) { + fillGapsCylindrical(node, adjustToParent); + } else if (node.boundsType == VolumeBounds::eCuboid && + node.binning.size() == 1) { + // Doesn't look like NOT adjusting to parent + // makes sense. The gaps are not going + // to be filled in non-binned directions + fillGapsCuboidal(node, adjustToParent); + } else { + throw std::runtime_error( + "BlueprintHelper: gap filling is not implemented for " + "this boundary type"); + } +} - if (node.boundsType == VolumeBounds::eCylinder) { - // Nodes must be sorted - sort(node, false); - - // Container values - auto cInnerR = node.boundaryValues[0]; - auto cOuterR = node.boundaryValues[1]; - auto cHalfZ = node.boundaryValues[2]; - - std::vector> gaps; - // Only 1D binning implemented for the moment - if (node.binning.size() == 1) { - auto bVal = node.binning.front(); - if (bVal == binZ) { - // adjust inner/outer radius - if (adjustToParent) { - std::for_each(node.children.begin(), node.children.end(), - [&](auto& child) { - child->boundaryValues[0] = cInnerR; - child->boundaryValues[1] = cOuterR; - }); - } - auto [negC, posC] = cylEndpointsZ(node); - // Assume sorted along the local z axis - unsigned int igap = 0; - for (auto& child : node.children) { - auto [neg, pos] = cylEndpointsZ(*child); - ActsScalar gapSpan = (neg - negC).norm(); - if (gapSpan > s_onSurfaceTolerance) { - // Fill a gap node - auto gapName = node.name + "_gap_" + std::to_string(igap); - auto gapTransform = Transform3::Identity(); - gapTransform.rotate(node.transform.rotation()); - gapTransform.translate(0.5 * (neg + negC)); - auto gap = std::make_unique( - gapName, gapTransform, VolumeBounds::eCylinder, - std::vector{cInnerR, cOuterR, 0.5 * gapSpan}); - gaps.push_back(std::move(gap)); - ++igap; - } - // Set to new current negative value - negC = pos; - } - // Check if a last one needs to be filled - ActsScalar gapSpan = (negC - posC).norm(); - if (gapSpan > s_onSurfaceTolerance) { - // Fill a gap node - auto gapName = node.name + "_gap_" + std::to_string(igap); - auto gapTransform = Transform3::Identity(); - gapTransform.rotate(node.transform.rotation()); - gapTransform.translate(0.5 * (negC + posC)); - auto gap = std::make_unique( - gapName, gapTransform, VolumeBounds::eCylinder, - std::vector{cInnerR, cOuterR, 0.5 * gapSpan}); - gaps.push_back(std::move(gap)); - } +void Acts::Experimental::detail::BlueprintHelper::fillGapsCylindrical( + Blueprint::Node& node, bool adjustToParent) { + // Nodes must be sorted + sort(node, false); - } else if (bVal == binR) { - // We have binning in R present - if (adjustToParent) { - std::for_each(node.children.begin(), node.children.end(), - [&](auto& child) { - child->transform = node.transform; - child->boundaryValues[2] = cHalfZ; - }); - } - // Fill the gaps in R - unsigned int igap = 0; - ActsScalar lastR = cInnerR; - for (auto& child : node.children) { - ActsScalar iR = child->boundaryValues[0]; - if (std::abs(iR - lastR) > s_onSurfaceTolerance) { - auto gap = std::make_unique( - node.name + "_gap_" + std::to_string(igap), node.transform, - VolumeBounds::eCylinder, - std::vector{lastR, iR, cHalfZ}); - gaps.push_back(std::move(gap)); - ++igap; - } - // Set to new outer radius - lastR = child->boundaryValues[1]; - } - // Check if a last one needs to be filled - if (std::abs(lastR - cOuterR) > s_onSurfaceTolerance) { - auto gap = std::make_unique( - node.name + "_gap_" + std::to_string(igap), node.transform, - VolumeBounds::eCylinder, - std::vector{lastR, cOuterR, cHalfZ}); - gaps.push_back(std::move(gap)); - } - } else { - throw std::runtime_error( - "BlueprintHelper: gap filling not implemented for " - "cylinder and this binning type."); + // Container values + auto cInnerR = node.boundaryValues[0]; + auto cOuterR = node.boundaryValues[1]; + auto cHalfZ = node.boundaryValues[2]; + + std::vector> gaps; + // Only 1D binning implemented for the moment + auto bVal = node.binning.front(); + if (bVal == binZ) { + // adjust inner/outer radius + if (adjustToParent) { + std::for_each(node.children.begin(), node.children.end(), + [&](auto& child) { + child->boundaryValues[0] = cInnerR; + child->boundaryValues[1] = cOuterR; + }); + } + auto [negC, posC] = endPointsXYZ(node, bVal); + // Assume sorted along the local z axis + unsigned int igap = 0; + for (auto& child : node.children) { + auto [neg, pos] = endPointsXYZ(*child, bVal); + ActsScalar gapSpan = (neg - negC).norm(); + if (gapSpan > s_onSurfaceTolerance) { + // Fill a gap node + auto gapName = node.name + "_gap_" + std::to_string(igap); + auto gapTransform = Transform3::Identity(); + gapTransform.rotate(node.transform.rotation()); + gapTransform.translate(0.5 * (neg + negC)); + auto gap = std::make_unique( + gapName, gapTransform, VolumeBounds::eCylinder, + std::vector{cInnerR, cOuterR, 0.5 * gapSpan}); + gaps.push_back(std::move(gap)); + ++igap; } + // Set to new current negative value + negC = pos; } - // Insert - for (auto& gap : gaps) { - node.add(std::move(gap)); + // Check if a last one needs to be filled + ActsScalar gapSpan = (negC - posC).norm(); + if (gapSpan > s_onSurfaceTolerance) { + // Fill a gap node + auto gapName = node.name + "_gap_" + std::to_string(igap); + auto gapTransform = Transform3::Identity(); + gapTransform.rotate(node.transform.rotation()); + gapTransform.translate(0.5 * (negC + posC)); + auto gap = std::make_unique( + gapName, gapTransform, VolumeBounds::eCylinder, + std::vector{cInnerR, cOuterR, 0.5 * gapSpan}); + gaps.push_back(std::move(gap)); } - // Sort again after inserting - sort(node, false); - // Fill the gaps recursively + } else if (bVal == binR) { + // We have binning in R present + if (adjustToParent) { + std::for_each(node.children.begin(), node.children.end(), + [&](auto& child) { + child->transform = node.transform; + child->boundaryValues[2] = cHalfZ; + }); + } + // Fill the gaps in R + unsigned int igap = 0; + ActsScalar lastR = cInnerR; for (auto& child : node.children) { - fillGaps(*child, adjustToParent); + ActsScalar iR = child->boundaryValues[0]; + if (std::abs(iR - lastR) > s_onSurfaceTolerance) { + auto gap = std::make_unique( + node.name + "_gap_" + std::to_string(igap), node.transform, + VolumeBounds::eCylinder, + std::vector{lastR, iR, cHalfZ}); + gaps.push_back(std::move(gap)); + ++igap; + } + // Set to new outer radius + lastR = child->boundaryValues[1]; + } + // Check if a last one needs to be filled + if (std::abs(lastR - cOuterR) > s_onSurfaceTolerance) { + auto gap = std::make_unique( + node.name + "_gap_" + std::to_string(igap), node.transform, + VolumeBounds::eCylinder, + std::vector{lastR, cOuterR, cHalfZ}); + gaps.push_back(std::move(gap)); } - } else { throw std::runtime_error( "BlueprintHelper: gap filling not implemented for " - "this boundary type"); + "cylinder and this binning type."); + } + + // Insert + for (auto& gap : gaps) { + node.add(std::move(gap)); + } + + // Sort again after inserting + sort(node, false); + // Fill the gaps recursively + for (auto& child : node.children) { + fillGaps(*child, adjustToParent); + } +} + +void Acts::Experimental::detail::BlueprintHelper::fillGapsCuboidal( + Blueprint::Node& node, bool adjustToParent) { + // Nodes must be sorted + sort(node, false); + + // Cuboidal detector binnings + std::array allowedBinVals = {binX, binY, binZ}; + + std::vector> gaps; + auto binVal = node.binning.front(); + + // adjust non-binned directions + if (adjustToParent) { + std::for_each(node.children.begin(), node.children.end(), [&](auto& child) { + for (auto bv : allowedBinVals) { + if (bv != binVal) { + // Both boundary values and translation + // have to be adjusted + child->boundaryValues[bv] = node.boundaryValues[bv]; + child->transform.translation()[bv] = node.transform.translation()[bv]; + } + } + }); + } + auto [negC, posC] = endPointsXYZ(node, binVal); + + // Assume sorted along the local binned axis + unsigned int igap = 0; + for (auto& child : node.children) { + auto [neg, pos] = endPointsXYZ(*child, binVal); + if (neg[binVal] < negC[binVal]) { + throw std::runtime_error("BlueprintHelper: Overlap detected for child '" + + child->name + "' of node '" + node.name + "'"); + } + ActsScalar gapSpan = (neg - negC).norm(); + if (gapSpan > s_onSurfaceTolerance) { + // Fill a gap node + auto gapName = node.name + "_gap_" + std::to_string(igap); + auto gapTransform = Transform3::Identity(); + gapTransform.rotate(node.transform.rotation()); + gapTransform.translate(0.5 * (neg + negC)); + std::vector gapBounds{0, 0, 0}; + gapBounds[binVal] = 0.5 * gapSpan; + for (auto bv : allowedBinVals) { + if (bv != binVal) { + gapBounds[bv] = node.boundaryValues[bv]; + } + } + auto gap = std::make_unique( + gapName, gapTransform, VolumeBounds::eCuboid, gapBounds); + gaps.push_back(std::move(gap)); + ++igap; + } + // Set to new current negative value + negC = pos; + } + // Check if a last one needs to be filled + ActsScalar gapSpan = (negC - posC).norm(); + if (gapSpan > s_onSurfaceTolerance) { + // Fill a gap node + auto gapName = node.name + "_gap_" + std::to_string(igap); + auto gapTransform = Transform3::Identity(); + gapTransform.rotate(node.transform.rotation()); + gapTransform.translate(0.5 * (negC + posC)); + std::vector gapBounds{0, 0, 0}; + gapBounds[binVal] = 0.5 * gapSpan; + for (auto bv : allowedBinVals) { + if (bv != binVal) { + gapBounds[bv] = node.boundaryValues[bv]; + } + } + auto gap = std::make_unique( + gapName, gapTransform, VolumeBounds::eCuboid, gapBounds); + gaps.push_back(std::move(gap)); + } + + // Insert + for (auto& gap : gaps) { + node.add(std::move(gap)); + } + + // Sort again after inserting + sort(node, false); + // Fill the gaps recursively + for (auto& child : node.children) { + fillGaps(*child, adjustToParent); } } diff --git a/Core/src/Detector/detail/CuboidalDetectorHelper.cpp b/Core/src/Detector/detail/CuboidalDetectorHelper.cpp index 4ce2237566b..045dc7075da 100644 --- a/Core/src/Detector/detail/CuboidalDetectorHelper.cpp +++ b/Core/src/Detector/detail/CuboidalDetectorHelper.cpp @@ -202,10 +202,15 @@ Acts::Experimental::detail::CuboidalDetectorHelper::connect( auto portalSurface = Surface::makeShared(portalTransform, portalBounds); auto portal = std::make_shared(portalSurface); + + // Assign the portal direction + // in a consistent way + Acts::Direction dir = + (index % 2 == 0) ? Direction::Forward : Direction::Backward; + // Make the stitch boundaries - pReplacements.push_back( - PortalReplacement(portal, index, Direction::Backward, - stitchBoundaries, (mergedInX ? binX : binY))); + pReplacements.push_back(PortalReplacement( + portal, index, dir, stitchBoundaries, (mergedInX ? binX : binY))); } } diff --git a/Tests/UnitTests/Core/Detector/BlueprintHelperTests.cpp b/Tests/UnitTests/Core/Detector/BlueprintHelperTests.cpp index e85943563ac..2c5da5edf00 100644 --- a/Tests/UnitTests/Core/Detector/BlueprintHelperTests.cpp +++ b/Tests/UnitTests/Core/Detector/BlueprintHelperTests.cpp @@ -269,30 +269,15 @@ BOOST_AUTO_TEST_CASE(BlueprintCylindricalGapException) { std::vector detectorBoundaries = {0., 50., 100.}; std::vector detectorBinning = {Acts::binX}; auto detector = std::make_unique( - "detector", Acts::Transform3::Identity(), Acts::VolumeBounds::eCuboid, + "detector", Acts::Transform3::Identity(), Acts::VolumeBounds::eCylinder, detectorBoundaries, detectorBinning); - std::vector cubeOneBoundaries = {0., 20., 100.}; - auto cubeOne = std::make_unique( - "cubeOne", Acts::Transform3::Identity(), Acts::VolumeBounds::eCuboid, - cubeOneBoundaries, innerBuilder); - detector->add(std::move(cubeOne)); - - // Throw because the detector is not cylindrical (cube not yet implemented) - BOOST_CHECK_THROW( - Acts::Experimental::detail::BlueprintHelper::fillGaps(*detector), - std::runtime_error); - - // Let's change both from a cuboid to a cylinder - detector->boundsType = Acts::VolumeBounds::eCylinder; - detector->children.front()->boundsType = Acts::VolumeBounds::eCylinder; - - // Add a second volume + // Add a volume std::vector volTwoBoundaries = {0., 20., 100.}; - auto volTwo = std::make_unique( - "volTwo", Acts::Transform3::Identity(), Acts::VolumeBounds::eCylinder, + auto vol = std::make_unique( + "vol", Acts::Transform3::Identity(), Acts::VolumeBounds::eCylinder, volTwoBoundaries, innerBuilder); - detector->add(std::move(volTwo)); + detector->add(std::move(vol)); // Throw because cylinders can not be binned in x BOOST_CHECK_THROW( diff --git a/Tests/UnitTests/Core/Detector/CMakeLists.txt b/Tests/UnitTests/Core/Detector/CMakeLists.txt index 686b89fdfcc..6f1fdbb82dc 100644 --- a/Tests/UnitTests/Core/Detector/CMakeLists.txt +++ b/Tests/UnitTests/Core/Detector/CMakeLists.txt @@ -2,6 +2,7 @@ add_unittest(Blueprint BlueprintTests.cpp) add_unittest(BlueprintHelper BlueprintHelperTests.cpp) add_unittest(CylindricalContainerBuilder CylindricalContainerBuilderTests.cpp) add_unittest(CylindricalDetectorFromBlueprint CylindricalDetectorFromBlueprintTests.cpp) +add_unittest(CuboidalDetectorFromBlueprint CuboidalDetectorFromBlueprintTests.cpp) add_unittest(CuboidalDetectorHelper CuboidalDetectorHelperTests.cpp) add_unittest(CuboidalContainerBuilder CuboidalContainerBuilderTests.cpp) add_unittest(CylindricalDetectorHelper CylindricalDetectorHelperTests.cpp) diff --git a/Tests/UnitTests/Core/Detector/CuboidalDetectorFromBlueprintTests.cpp b/Tests/UnitTests/Core/Detector/CuboidalDetectorFromBlueprintTests.cpp new file mode 100644 index 00000000000..2fb8ac15c05 --- /dev/null +++ b/Tests/UnitTests/Core/Detector/CuboidalDetectorFromBlueprintTests.cpp @@ -0,0 +1,374 @@ +// This file is part of the Acts project. +// +// Copyright (C) 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/Detector/Blueprint.hpp" +#include "Acts/Detector/CuboidalContainerBuilder.hpp" +#include "Acts/Detector/DetectorBuilder.hpp" +#include "Acts/Detector/DetectorComponents.hpp" +#include "Acts/Detector/DetectorVolume.hpp" +#include "Acts/Detector/GeometryIdGenerator.hpp" +#include "Acts/Detector/IndexedRootVolumeFinderBuilder.hpp" +#include "Acts/Detector/detail/BlueprintDrawer.hpp" +#include "Acts/Detector/detail/BlueprintHelper.hpp" +#include "Acts/Detector/interface/IInternalStructureBuilder.hpp" +#include "Acts/Geometry/GeometryContext.hpp" +#include "Acts/Navigation/DetectorVolumeFinders.hpp" +#include "Acts/Navigation/SurfaceCandidatesUpdaters.hpp" +#include "Acts/Surfaces/PlaneSurface.hpp" +#include "Acts/Surfaces/RectangleBounds.hpp" +#include "Acts/Surfaces/Surface.hpp" +#include "Acts/Utilities/BinningData.hpp" + +#include + +class SurfaceBuilder : public Acts::Experimental::IInternalStructureBuilder { + public: + SurfaceBuilder(const Acts::Transform3& transform, + const Acts::RectangleBounds& sBounds) + : m_transform(transform), m_surfaceBounds(sBounds){}; + + /// Conrstruct and return the internal structure creation + /// + /// @param gctx the geometry context at the creation of the internal structure + /// + /// @return a consistent set of detector volume internals + Acts::Experimental::InternalStructure construct( + [[maybe_unused]] const Acts::GeometryContext& gctx) const final { + auto surface = Acts::Surface::makeShared( + (m_transform), + std::make_shared(m_surfaceBounds)); + + // Trivialities first: internal volumes + std::vector> + internalVolumes = {}; + Acts::Experimental::DetectorVolumeUpdater internalVolumeUpdater = + Acts::Experimental::tryNoVolumes(); + + // Retrieve the layer surfaces + Acts::Experimental::SurfaceCandidatesUpdater internalCandidatesUpdater = + Acts::Experimental::tryAllPortalsAndSurfaces(); + + // Return the internal structure + return Acts::Experimental::InternalStructure{ + {surface}, + internalVolumes, + std::move(internalCandidatesUpdater), + std::move(internalVolumeUpdater)}; + } + + private: + Acts::Transform3 m_transform; + Acts::RectangleBounds m_surfaceBounds; +}; + +BOOST_AUTO_TEST_SUITE(Detector) + +BOOST_AUTO_TEST_CASE(CuboidalDetectorFromBlueprintTest) { + Acts::GeometryContext tContext; + + // This tests shows how to careate cuboidal detector from a detector + // blueprint. + // + // In general, the blueprint (lines below) is generated through reading in + // or by parsing the geometry model (DD4heo, TGeo, Geant4, etc.). For + // testing purpose, let us create the blueprint manually. + // + + // Blueprint starts here ---------------- + + // Detector dimensions + Acts::ActsScalar detectorX = 100.; + Acts::ActsScalar detectorY = 100.; + Acts::ActsScalar detectorZ = 100.; + + // Pixel system + Acts::ActsScalar pixelX = 20; + Acts::ActsScalar pixelY = 100; + Acts::ActsScalar pixelZ = 10; + + // Create root node + std::vector detectorBins = {Acts::binX}; + std::vector detectorBounds = {detectorX, detectorY, + detectorZ}; + + // The root node - detector + auto detectorBpr = std::make_unique( + "detector", Acts::Transform3::Identity(), Acts::VolumeBounds::eCuboid, + detectorBounds, detectorBins); + + // Left arm + std::vector leftArmBounds = {detectorX * 0.5, detectorY, + detectorZ}; + + std::vector leftArmBins = {Acts::binZ}; + + Acts::Transform3 leftArmTransform = + Acts::Transform3::Identity() * + Acts::Translation3(-detectorX * 0.5, 0., 0); + + auto leftArm = std::make_unique( + "leftArm", leftArmTransform, Acts::VolumeBounds::eCuboid, leftArmBounds, + leftArmBins); + + // Pixel layer L1 + std::vector pixelL1Boundaries = {pixelX, pixelY, pixelZ}; + + Acts::Transform3 pixelL1Transform = + Acts::Transform3::Identity() * + Acts::Translation3(-pixelX - 5, 0., -detectorZ + pixelZ + 5); + + auto pixelL1Structure = std::make_shared( + pixelL1Transform, Acts::RectangleBounds(pixelX * 0.8, pixelY * 0.8)); + + auto pixelL1 = std::make_unique( + "pixelL1", pixelL1Transform, Acts::VolumeBounds::eCuboid, + pixelL1Boundaries, pixelL1Structure); + + // Pixel layer L2 + std::vector pixelL2Boundaries = {pixelX, pixelY, pixelZ}; + + Acts::Transform3 pixelL2Transform = + Acts::Transform3::Identity() * + Acts::Translation3(-pixelX - 5, 0., -detectorZ + 2 * pixelZ + 5 * 5); + + auto pixelL2Structure = std::make_shared( + pixelL2Transform, Acts::RectangleBounds(pixelX * 0.8, pixelY * 0.8)); + + auto pixelL2 = std::make_unique( + "pixelL2", pixelL2Transform, Acts::VolumeBounds::eCuboid, + pixelL2Boundaries, pixelL2Structure); + + // Add pixel layers to left arm + // and left arm to detector + leftArm->add(std::move(pixelL1)); + leftArm->add(std::move(pixelL2)); + detectorBpr->add(std::move(leftArm)); + + // Right arm + std::vector rightArmBounds = {detectorX * 0.5, detectorY, + detectorZ}; + + std::vector rightArmBins = {Acts::binZ}; + + Acts::Transform3 rightArmTransform = + Acts::Transform3::Identity() * Acts::Translation3(detectorX * 0.5, 0., 0); + + auto rightArm = std::make_unique( + "rightArm", rightArmTransform, Acts::VolumeBounds::eCuboid, + rightArmBounds, rightArmBins); + + // Pixel layer R1 + std::vector pixelR1Boundaries = {pixelX, pixelY, pixelZ}; + + Acts::Transform3 pixelR1Transform = + Acts::Transform3::Identity() * + Acts::Translation3(pixelX + 5, 0., -detectorZ + pixelZ + 5); + + auto pixelR1Structure = std::make_shared( + pixelR1Transform, Acts::RectangleBounds(pixelX * 0.8, pixelY * 0.8)); + + auto pixelR1 = std::make_unique( + "pixelR1", pixelR1Transform, Acts::VolumeBounds::eCuboid, + pixelR1Boundaries, pixelR1Structure); + + // Pixel layer R2 + std::vector pixelR2Boundaries = {pixelX, pixelY, pixelZ}; + + Acts::Transform3 pixelR2Transform = + Acts::Transform3::Identity() * + Acts::Translation3(pixelX + 5, 0., -detectorZ + 2 * pixelZ + 5 * 5); + + auto pixelR2Structure = std::make_shared( + pixelR2Transform, Acts::RectangleBounds(pixelX * 0.8, pixelY * 0.8)); + + auto pixelR2 = std::make_unique( + "pixelR2", pixelR2Transform, Acts::VolumeBounds::eCuboid, + pixelR2Boundaries, pixelR2Structure); + + // Add pixel layers to right arm + // and right arm to detector + rightArm->add(std::move(pixelR1)); + rightArm->add(std::move(pixelR2)); + detectorBpr->add(std::move(rightArm)); + + // A geo ID generator + detectorBpr->geoIdGenerator = + std::make_shared( + Acts::Experimental::GeometryIdGenerator::Config{}, + Acts::getDefaultLogger("RecursiveIdGenerator", + Acts::Logging::VERBOSE)); + + std::cout << "Fill gaps ..." << std::endl; + // Complete and fill gaps + Acts::Experimental::detail::BlueprintHelper::fillGaps(*detectorBpr); + std::cout << "Filled gaps ..." << std::endl; + + std::fstream fs("cylindrical_detector_blueprint.dot", std::ios::out); + Acts::Experimental::detail::BlueprintDrawer::dotStream(fs, *detectorBpr); + fs.close(); + + // ----------------------------- end of blueprint + + // Create a Cuboidal detector builder from this blueprint + auto detectorBuilder = + std::make_shared( + *detectorBpr, Acts::Logging::VERBOSE); + + // Detector builder + Acts::Experimental::DetectorBuilder::Config dCfg; + dCfg.auxiliary = "*** Test : auto generated cuboidal detector builder ***"; + dCfg.name = "Cuboidal detector from blueprint"; + dCfg.builder = detectorBuilder; + dCfg.geoIdGenerator = detectorBpr->geoIdGenerator; + + auto detector = Acts::Experimental::DetectorBuilder(dCfg).construct(tContext); + + BOOST_REQUIRE_NE(detector, nullptr); + + // There should be 10 volumes, and they should be built in order + // leftArm_gap_0 + // pixelL1 + // leftArm_gap_1 + // pixelL2 + // leftArm_gap_2 + // rightArm_gap_0 + // pixelR1 + // rightArm_gap_1 + // pixelR2 + // rightArm_gap_2 + BOOST_CHECK_EQUAL(detector->volumes().size(), 10u); + BOOST_CHECK_EQUAL(detector->volumes()[0]->name(), "leftArm_gap_0"); + BOOST_CHECK_EQUAL(detector->volumes()[1]->name(), "pixelL1"); + BOOST_CHECK_EQUAL(detector->volumes()[2]->name(), "leftArm_gap_1"); + BOOST_CHECK_EQUAL(detector->volumes()[3]->name(), "pixelL2"); + BOOST_CHECK_EQUAL(detector->volumes()[4]->name(), "leftArm_gap_2"); + BOOST_CHECK_EQUAL(detector->volumes()[5]->name(), "rightArm_gap_0"); + BOOST_CHECK_EQUAL(detector->volumes()[6]->name(), "pixelR1"); + BOOST_CHECK_EQUAL(detector->volumes()[7]->name(), "rightArm_gap_1"); + BOOST_CHECK_EQUAL(detector->volumes()[8]->name(), "pixelR2"); + BOOST_CHECK_EQUAL(detector->volumes()[9]->name(), "rightArm_gap_2"); + + // Volumes have to be contained within the + // initial detector bounds + double internalStretchLeftZ = + detector->volumes()[0]->volumeBounds().values()[Acts::binZ] + + detector->volumes()[1]->volumeBounds().values()[Acts::binZ] + + detector->volumes()[2]->volumeBounds().values()[Acts::binZ] + + detector->volumes()[3]->volumeBounds().values()[Acts::binZ] + + detector->volumes()[4]->volumeBounds().values()[Acts::binZ]; + + double internalStretchRightZ = + detector->volumes()[5]->volumeBounds().values()[Acts::binZ] + + detector->volumes()[6]->volumeBounds().values()[Acts::binZ] + + detector->volumes()[7]->volumeBounds().values()[Acts::binZ] + + detector->volumes()[8]->volumeBounds().values()[Acts::binZ] + + detector->volumes()[9]->volumeBounds().values()[Acts::binZ]; + + double internalStretchX1 = + detector->volumes()[0]->volumeBounds().values()[Acts::binX] + + detector->volumes()[5]->volumeBounds().values()[Acts::binX]; + + double internalStretchX2 = + detector->volumes()[1]->volumeBounds().values()[Acts::binX] + + detector->volumes()[6]->volumeBounds().values()[Acts::binX]; + + double internalStretchX3 = + detector->volumes()[2]->volumeBounds().values()[Acts::binX] + + detector->volumes()[7]->volumeBounds().values()[Acts::binX]; + + double internalStretchX4 = + detector->volumes()[3]->volumeBounds().values()[Acts::binX] + + detector->volumes()[8]->volumeBounds().values()[Acts::binX]; + + double internalStretchX5 = + detector->volumes()[4]->volumeBounds().values()[Acts::binX] + + detector->volumes()[9]->volumeBounds().values()[Acts::binX]; + + BOOST_CHECK_EQUAL(internalStretchLeftZ, detectorZ); + BOOST_CHECK_EQUAL(internalStretchRightZ, detectorZ); + BOOST_CHECK_EQUAL(internalStretchX1, detectorX); + BOOST_CHECK_EQUAL(internalStretchX2, detectorX); + BOOST_CHECK_EQUAL(internalStretchX3, detectorX); + BOOST_CHECK_EQUAL(internalStretchX4, detectorX); + BOOST_CHECK_EQUAL(internalStretchX5, detectorX); + + for (auto& volume : detector->volumes()) { + BOOST_CHECK_EQUAL(volume->volumeBounds().values()[Acts::binY], detectorY); + } + + // There should be surfaces inside the pixel + // volumes + BOOST_CHECK_EQUAL(detector->volumes()[1]->surfaces().size(), 1u); + BOOST_CHECK_EQUAL(detector->volumes()[3]->surfaces().size(), 1u); + BOOST_CHECK_EQUAL(detector->volumes()[6]->surfaces().size(), 1u); + BOOST_CHECK_EQUAL(detector->volumes()[8]->surfaces().size(), 1u); + + // There should be 8 Z-portals from + // connecting the arms, 4 outside Z-portals, + // 3 X-portals from fusing the containers, and + // 2+2 Y-portals that were replaced when connecting + // in Z, but didn't get renewed when connecting in X + // 8+4+3+2+2 = 19 in total + std::vector portals; + for (auto& volume : detector->volumes()) { + portals.insert(portals.end(), volume->portals().begin(), + volume->portals().end()); + } + std::sort(portals.begin(), portals.end()); + auto last = std::unique(portals.begin(), portals.end()); + portals.erase(last, portals.end()); + BOOST_CHECK_EQUAL(portals.size(), 19u); + + // Volumes should have the same Y-normal-direction portals + // but only within the same arm. CuboidalDetectorHelper + // does not yet connect the containers in a consistent way + bool samePortalY1 = true, samePortalY2 = true; + for (int i = 0; i < 4; i++) { + samePortalY1 = + samePortalY1 && (detector->volumes()[i]->portals().at(4) == + detector->volumes()[i + 1]->portals().at(4)); + samePortalY2 = + samePortalY2 && (detector->volumes()[5 + i]->portals().at(4) == + detector->volumes()[5 + i + 1]->portals().at(4)); + } + BOOST_CHECK_EQUAL(samePortalY1, true); + BOOST_CHECK_EQUAL(samePortalY2, true); + samePortalY1 = true, samePortalY2 = true; + for (int i = 0; i < 4; i++) { + samePortalY1 = + samePortalY1 && (detector->volumes()[i]->portals().at(5) == + detector->volumes()[i + 1]->portals().at(5)); + samePortalY2 = + samePortalY2 && (detector->volumes()[5 + i]->portals().at(5) == + detector->volumes()[5 + i + 1]->portals().at(5)); + } + BOOST_CHECK_EQUAL(samePortalY1, true); + BOOST_CHECK_EQUAL(samePortalY2, true); + + // Volumes should be connected in Z-direction + for (int i = 0; i < 4; i++) { + bool samePortalZ1 = (detector->volumes()[i]->portals().at(1) == + detector->volumes()[i + 1]->portals().at(0)); + bool samePortalZ2 = (detector->volumes()[5 + i]->portals().at(1) == + detector->volumes()[5 + i + 1]->portals().at(0)); + + BOOST_CHECK_EQUAL(samePortalZ1, true); + BOOST_CHECK_EQUAL(samePortalZ2, true); + } + + // Volumes should be connected in X-direction + for (int i = 0; i < 5; i++) { + bool samePortalX = (detector->volumes()[i]->portals().at(3) == + detector->volumes()[5 + i]->portals().at(2)); + BOOST_CHECK_EQUAL(samePortalX, true); + } +} + +BOOST_AUTO_TEST_SUITE_END()