diff --git a/Core/include/Acts/Detector/detail/CuboidalDetectorHelper.hpp b/Core/include/Acts/Detector/detail/CuboidalDetectorHelper.hpp new file mode 100644 index 00000000000..c264d645d7e --- /dev/null +++ b/Core/include/Acts/Detector/detail/CuboidalDetectorHelper.hpp @@ -0,0 +1,87 @@ +// 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/. + +#pragma once + +#include "Acts/Definitions/Algebra.hpp" +#include "Acts/Definitions/Common.hpp" +#include "Acts/Detector/DetectorComponents.hpp" +#include "Acts/Detector/Portal.hpp" +#include "Acts/Geometry/GeometryContext.hpp" +#include "Acts/Utilities/BinningData.hpp" +#include "Acts/Utilities/Logger.hpp" + +#include +#include +#include +#include +#include + +namespace Acts { + +namespace Experimental { + +class DetectorVolume; +class Portal; + +namespace detail { +namespace CuboidalDetectorHelper { + +/// @brief Connect detector volumes given a binning value +/// +/// @param gctx The geometry context +/// @param volumes the volumes +/// @param bValue the binning value (allowed are binX, binY, binZ) +/// @param selectedOnly switch only selected boundaries +/// @param logLevel is the screen logging level +/// +/// @note a fair amount of consistency checking is done, +/// and exceptions are thrown if any of the tests fail +/// +/// @return a proto container with the outside portals +DetectorComponent::PortalContainer connect( + const GeometryContext& gctx, + std::vector>& volumes, BinningValue bValue, + const std::vector& selectedOnly = {}, + Acts::Logging::Level logLevel = Acts::Logging::INFO); + +/// @brief Connect containers given a binning value +/// +/// @param gctx The geometry context +/// @param containers the containers +/// @param bValue the binning value (allowed are binX, binY, binZ) +/// @param selectedOnly switch only selected boundaries +/// @param logLevel is the screen logging level +/// +/// @note not much checking is done anymore, as the DetectorComponent::PortalContainer +/// are assumed to come properly formed from the prior methods +/// +/// @return a proto container with the outside portals +DetectorComponent::PortalContainer connect( + const GeometryContext& gctx, + const std::vector& containers, + BinningValue bValue, const std::vector& selectedOnly = {}, + Acts::Logging::Level logLevel = Acts::Logging::INFO); + +/// @brief Helper method to extract r,z,phi boundaries for +/// eventual grid volume search +/// +/// @param gctx the geometry context of the call +/// @param volumes the volumes at input +/// @param logLevel is the screen logging level +/// +/// @return extracted boundary values +std::array, 3u> xyzBoundaries( + const GeometryContext& gctx, + const std::vector& volumes, + Acts::Logging::Level logLevel = Acts::Logging::INFO); + +} // namespace CuboidalDetectorHelper +} // namespace detail +} // namespace Experimental +} // namespace Acts diff --git a/Core/include/Acts/Detector/detail/DetectorVolumeConsistency.hpp b/Core/include/Acts/Detector/detail/DetectorVolumeConsistency.hpp new file mode 100644 index 00000000000..5d3c2d2d2ea --- /dev/null +++ b/Core/include/Acts/Detector/detail/DetectorVolumeConsistency.hpp @@ -0,0 +1,58 @@ +// 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/. + +#pragma once + +#include "Acts/Definitions/Common.hpp" +#include "Acts/Geometry/GeometryContext.hpp" +#include "Acts/Utilities/BinningData.hpp" + +#include +#include + +namespace Acts { +namespace Experimental { + +class DetectorVolume; + +namespace detail { +namespace DetectorVolumeConsistency { + +/// @brief Helper method to check alignment of the volumes, this method checks +/// if the rotational part of the transform is identical +/// +/// @param gctx the geometry context +/// @param volumes the input volumes to be checked +/// +/// @note this is a strict matching that requires the rotation to be identical +/// +/// @note throws exception if any of checks fails +void checkRotationAlignment( + const GeometryContext& gctx, + const std::vector>& volumes); + +/// @brief Helper method to check whether a set of volumes is lined up on +/// a given common axis definition +/// +/// @param gctx the geometry context +/// @param volumes the input volumes to be checked +/// @param axisValue the alignment axist +/// +/// @note this will call checkRotationAlignment first +/// @note throws exception if the volumes are not ordered +/// +/// @return a vector with position differences (ordered) +std::vector checkCenterAlignment( + const GeometryContext& gctx, + const std::vector>& volumes, + BinningValue axisValue); + +} // namespace DetectorVolumeConsistency +} // namespace detail +} // namespace Experimental +} // namespace Acts diff --git a/Core/include/Acts/Detector/detail/PortalHelper.hpp b/Core/include/Acts/Detector/detail/PortalHelper.hpp index 45a9c2a4013..cf178c84968 100644 --- a/Core/include/Acts/Detector/detail/PortalHelper.hpp +++ b/Core/include/Acts/Detector/detail/PortalHelper.hpp @@ -15,6 +15,7 @@ #include "Acts/Geometry/GeometryContext.hpp" #include "Acts/Utilities/BinningType.hpp" +#include #include #include #include @@ -89,6 +90,24 @@ void attachDetectorVolumeUpdaters( std::vector> attachedDetectorVolumes( Portal& portal) noexcept(false); +/// @brief Method that strips out attached volumes from portals and +/// provides them back to the caller. +/// +/// @param pContainers the portal containers to be resolved +/// @param sides the sides to be handled +/// @param selectedOnly the selected only volumes, e.g. for complex containers +/// to chose only outside skins, +/// @param logLevel the logging level +/// +std::map>> +stripSideVolumes( + const std::vector>>& + pContainers, + const std::vector& sides, + const std::vector& selectedOnly = {}, + Acts::Logging::Level logLevel = Acts::Logging::INFO); + } // namespace PortalHelper } // namespace detail } // namespace Experimental diff --git a/Core/src/Detector/CMakeLists.txt b/Core/src/Detector/CMakeLists.txt index f5632c37589..3bffdea0ad0 100644 --- a/Core/src/Detector/CMakeLists.txt +++ b/Core/src/Detector/CMakeLists.txt @@ -3,7 +3,9 @@ target_sources( PRIVATE detail/BlueprintHelper.cpp detail/BlueprintDrawer.cpp + detail/CuboidalDetectorHelper.cpp detail/CylindricalDetectorHelper.cpp + detail/DetectorVolumeConsistency.cpp detail/PortalHelper.cpp detail/ProtoMaterialHelper.cpp detail/SupportHelper.cpp diff --git a/Core/src/Detector/Portal.cpp b/Core/src/Detector/Portal.cpp index 2e1123d03e6..4e556d7aaa8 100644 --- a/Core/src/Detector/Portal.cpp +++ b/Core/src/Detector/Portal.cpp @@ -69,47 +69,53 @@ std::shared_ptr Portal::fuse(std::shared_ptr& aPortal, if (noneConnected(*aPortal) || noneConnected(*bPortal)) { throw std::invalid_argument( - "Portal: trying to fuse two portals where at least on has no links."); + "Portal: trying to fuse two portals where at least one has no links."); } - // We checked they're not both empty, so one of them must be connected - Direction aDir = (aPortal->m_volumeUpdaters[0].connected()) + // @TODO: There's no safety against fusing portals with different surfaces + // We model the fused portal after the aPortal + std::shared_ptr fused = std::make_shared(aPortal->m_surface); + + // Get the connection directions + Direction getA = (aPortal->m_volumeUpdaters[0].connected()) + ? Direction::fromIndex(0) + : Direction::fromIndex(1); + Direction getB = (bPortal->m_volumeUpdaters[0].connected()) ? Direction::fromIndex(0) : Direction::fromIndex(1); - Direction bDir = aDir.invert(); - // And now check other direction - if (!bPortal->m_volumeUpdaters[bDir.index()].connected()) { - throw std::runtime_error( - "Portal: trying to fuse portal (discard) with no links."); - } + // Modelling the fused portal after the aPortal, leaves B as inverted + Direction setA = getA; + Direction setB = setA.invert(); + // Check if material is associated const auto& aSurface = aPortal->surface(); const auto& bSurface = bPortal->surface(); - // @TODO: There's no safety against fusing portals with different surfaces - std::shared_ptr fused = std::make_shared(aPortal->m_surface); - if (aSurface.surfaceMaterial() != nullptr && bSurface.surfaceMaterial() != nullptr) { throw std::runtime_error( "Portal: both surfaces have surface material, fusing will lead to " "information loss."); } else if (aSurface.surfaceMaterial() != nullptr) { + // We keep the aPortal modelling fused->m_surface = aPortal->m_surface; } else if (bSurface.surfaceMaterial() != nullptr) { fused->m_surface = bPortal->m_surface; + // Remodel after the bPortal + setB = getB; + setA = setB.invert(); } - fused->m_volumeUpdaters[aDir.index()] = - std::move(aPortal->m_volumeUpdaters[aDir.index()]); - fused->m_attachedVolumes[aDir.index()] = - std::move(aPortal->m_attachedVolumes[aDir.index()]); + fused->m_volumeUpdaters[setA.index()] = + std::move(aPortal->m_volumeUpdaters[getA.index()]); + fused->m_attachedVolumes[setA.index()] = + std::move(aPortal->m_attachedVolumes[getA.index()]); - fused->m_volumeUpdaters[bDir.index()] = - std::move(bPortal->m_volumeUpdaters[bDir.index()]); - fused->m_attachedVolumes[bDir.index()] = - std::move(bPortal->m_attachedVolumes[bDir.index()]); + fused->m_volumeUpdaters[setB.index()] = + std::move(bPortal->m_volumeUpdaters[getB.index()]); + fused->m_attachedVolumes[setB.index()] = + std::move(bPortal->m_attachedVolumes[getB.index()]); return fused; } diff --git a/Core/src/Detector/detail/CuboidalDetectorHelper.cpp b/Core/src/Detector/detail/CuboidalDetectorHelper.cpp new file mode 100644 index 00000000000..4ce2237566b --- /dev/null +++ b/Core/src/Detector/detail/CuboidalDetectorHelper.cpp @@ -0,0 +1,379 @@ +// 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 "Acts/Detector/detail/CuboidalDetectorHelper.hpp" + +#include "Acts/Definitions/Common.hpp" +#include "Acts/Detector/DetectorVolume.hpp" +#include "Acts/Detector/Portal.hpp" +#include "Acts/Detector/detail/DetectorVolumeConsistency.hpp" +#include "Acts/Detector/detail/PortalHelper.hpp" +#include "Acts/Geometry/CuboidVolumeBounds.hpp" +#include "Acts/Surfaces/PlaneSurface.hpp" +#include "Acts/Surfaces/RectangleBounds.hpp" +#include "Acts/Surfaces/Surface.hpp" +#include "Acts/Utilities/BinningData.hpp" +#include "Acts/Utilities/Enumerate.hpp" +#include "Acts/Utilities/StringHelpers.hpp" + +#include + +Acts::Experimental::DetectorComponent::PortalContainer +Acts::Experimental::detail::CuboidalDetectorHelper::connect( + const GeometryContext& gctx, + std::vector>& volumes, + BinningValue bValue, const std::vector& selectedOnly, + Acts::Logging::Level logLevel) { + ACTS_LOCAL_LOGGER(getDefaultLogger("CuboidalDetectorHelper", logLevel)); + + ACTS_DEBUG("Connect " << volumes.size() << " detector volumes in " + << binningValueNames()[bValue] << "."); + + // Check transform for consistency + auto centerDistances = + DetectorVolumeConsistency::checkCenterAlignment(gctx, volumes, bValue); + + // Assign the portal indices according to the volume bounds definition + std::array possibleValues = {binX, binY, binZ}; + // 1 -> [ 2,3 ] for binX connection (cylclic one step) + // 2 -> [ 4,5 ] for binY connection (cylclic two steps) + // 0 -> [ 0,1 ] for binZ connection (to be in line with cylinder covnention) + using PortalSet = std::array; + std::vector portalSets = { + {PortalSet{2, 3}, PortalSet{4, 5}, PortalSet{0, 1}}}; + + // This is the picked set for fusing + auto [sIndex, fIndex] = portalSets[bValue]; + + // Log the merge splits, i.e. the boundaries of the volumes + std::array, 3u> mergeSplits; + std::array mergeHalfLengths = { + 0., + 0., + 0., + }; + + // Pick the counter part value + auto counterPart = [&](BinningValue mValue) -> BinningValue { + for (auto cValue : possibleValues) { + if (cValue != mValue && cValue != bValue) { + return cValue; + } + } + return mValue; + }; + + // Things that can be done without a loop be first/last check + // Estimate the merge parameters: the scalar and the transform + using MergeParameters = std::tuple; + std::map mergeParameters; + auto& firstVolume = volumes.front(); + auto& lastVolume = volumes.back(); + // Values + const auto firstBoundValues = firstVolume->volumeBounds().values(); + const auto lastBoundValues = lastVolume->volumeBounds().values(); + Vector3 stepDirection = firstVolume->transform(gctx).rotation().col(bValue); + + for (auto [im, mergeValue] : enumerate(possibleValues)) { + // Skip the bin value itself, fusing will took care of that + if (mergeValue == bValue) { + continue; + } + for (auto [is, index] : enumerate(portalSets[mergeValue])) { + // Take rotation from first volume + auto rotation = firstVolume->portalPtrs()[index] + ->surface() + .transform(gctx) + .rotation(); + ActsScalar stepDown = firstBoundValues[bValue]; + ActsScalar stepUp = lastBoundValues[bValue]; + // Take translation from first and last volume + auto translationF = firstVolume->portalPtrs()[index] + ->surface() + .transform(gctx) + .translation(); + + auto translationL = lastVolume->portalPtrs()[index] + ->surface() + .transform(gctx) + .translation(); + + Vector3 translation = 0.5 * (translationF - stepDown * stepDirection + + translationL + stepUp * stepDirection); + + Transform3 portalTransform = Transform3::Identity(); + portalTransform.prerotate(rotation); + portalTransform.pretranslate(translation); + // The half length to be kept + ActsScalar keepHalfLength = firstBoundValues[counterPart(mergeValue)]; + mergeParameters[index] = MergeParameters(keepHalfLength, portalTransform); + } + } + + // Loop over the volumes and fuse the portals, collect the merge information + for (auto [iv, v] : enumerate(volumes)) { + // So far works only in a cubioid setup + if (v->volumeBounds().type() != VolumeBounds::BoundsType::eCuboid) { + throw std::invalid_argument( + "CuboidalDetectorHelper: volume bounds are not cuboid"); + } + + // Loop to fuse the portals along the connection direction (bValue) + if (iv > 0u) { + ACTS_VERBOSE("- fuse portals of volume '" + << volumes[iv - 1]->name() << "' with volume '" << v->name() + << "'."); + ACTS_VERBOSE("-- indices " << fIndex << " of first, " << sIndex + << " of second volume."); + // Fusing the portals of the current volume with the previous one + auto fPortal = volumes[iv - 1]->portalPtrs()[fIndex]; + auto sPortal = v->portalPtrs()[sIndex]; + auto fusedPortal = Portal::fuse(fPortal, sPortal); + volumes[iv - 1]->updatePortal(fusedPortal, fIndex); + v->updatePortal(fusedPortal, sIndex); + } + + // Get the bound values + auto boundValues = v->volumeBounds().values(); + // Loop to determine the merge bounds, the new transform + for (auto [im, mergeValue] : enumerate(possibleValues)) { + // Skip the bin value itself, fusing will took care of that + if (mergeValue == bValue) { + continue; + } + // Record the merge splits + mergeSplits[im].push_back(2 * boundValues[bValue]); + mergeHalfLengths[im] += boundValues[bValue]; + } + } + + // Loop to create the new portals as portal replacements + std::vector pReplacements; + for (auto [im, mergeValue] : enumerate(possibleValues)) { + // Skip the bin value itself, fusing took care of that + if (mergeValue == bValue) { + continue; + } + + // Create the new RectangleBounds + // - there are conventions involved, regarding the bounds orientation + // - this is an anticyclic swap + bool mergedInX = true; + switch (bValue) { + case binZ: { + mergedInX = (mergeValue == binY); + } break; + case binY: { + mergedInX = (mergeValue == binX); + } break; + case binX: { + mergedInX = (mergeValue == binZ); + } break; + default: + break; + } + + // The stitch boundaries for portal pointing + std::vector stitchBoundaries; + stitchBoundaries.push_back(-mergeHalfLengths[im]); + for (auto step : mergeSplits[im]) { + stitchBoundaries.push_back(stitchBoundaries.back() + step); + } + + for (auto [is, index] : enumerate(portalSets[mergeValue])) { + // Check if you need to skip due to selections + if (!selectedOnly.empty() && + std::find(selectedOnly.begin(), selectedOnly.end(), index) == + selectedOnly.end()) { + continue; + } + + auto [keepHalfLength, portalTransform] = mergeParameters[index]; + std::shared_ptr portalBounds = + mergedInX ? std::make_shared(mergeHalfLengths[im], + keepHalfLength) + : std::make_shared(keepHalfLength, + mergeHalfLengths[im]); + auto portalSurface = + Surface::makeShared(portalTransform, portalBounds); + auto portal = std::make_shared(portalSurface); + // Make the stitch boundaries + pReplacements.push_back( + PortalReplacement(portal, index, Direction::Backward, + stitchBoundaries, (mergedInX ? binX : binY))); + } + } + + // Attach the new volume updaters + PortalHelper::attachDetectorVolumeUpdaters(gctx, volumes, pReplacements); + + // Return proto container + DetectorComponent::PortalContainer dShell; + + // Update the portals of all volumes + // Exchange the portals of the volumes + for (auto& iv : volumes) { + ACTS_VERBOSE("- update portals of volume '" << iv->name() << "'."); + for (auto& [p, i, dir, boundaries, binning] : pReplacements) { + // Fill the map + dShell[i] = p; + ACTS_VERBOSE("-- update portal with index " << i); + iv->updatePortal(p, i); + } + } + // Done. + + return dShell; +} + +Acts::Experimental::DetectorComponent::PortalContainer +Acts::Experimental::detail::CuboidalDetectorHelper::connect( + const GeometryContext& /*gctx*/, + const std::vector& containers, + BinningValue bValue, const std::vector& /*unused */, + Acts::Logging::Level logLevel) noexcept(false) { + // The local logger + ACTS_LOCAL_LOGGER(getDefaultLogger("CuboidalDetectorHelper", logLevel)); + + ACTS_DEBUG("Connect " << containers.size() << " containers in " + << binningValueNames()[bValue] << "."); + + // Return the new container + DetectorComponent::PortalContainer dShell; + + // The possible bin values + std::array possibleValues = {binX, binY, binZ}; + // And their associated portal sets, see above + using PortalSet = std::array; + std::vector portalSets = { + {PortalSet{2, 3}, PortalSet{4, 5}, PortalSet{0, 1}}}; + + // This is the picked set for refubishing + auto [endIndex, startIndex] = portalSets[bValue]; + + // Fusing along the connection direction (bValue) + for (std::size_t ic = 1; ic < containers.size(); ++ic) { + auto& formerContainer = containers[ic - 1]; + auto& currentContainer = containers[ic]; + // Check and throw exception + if (formerContainer.find(startIndex) == formerContainer.end()) { + throw std::invalid_argument( + "CuboidalDetectorHelper: proto container has no fuse portal at index " + "of former container."); + } + if (currentContainer.find(endIndex) == currentContainer.end()) { + throw std::invalid_argument( + "CuboidalDetectorHelper: proto container has no fuse portal at index " + "of current container."); + } + + std::shared_ptr sPortal = formerContainer.find(startIndex)->second; + auto sAttachedVolumes = + sPortal + ->attachedDetectorVolumes()[Direction(Direction::Backward).index()]; + + std::shared_ptr ePortal = currentContainer.find(endIndex)->second; + auto eAttachedVolumes = + ePortal + ->attachedDetectorVolumes()[Direction(Direction::Forward).index()]; + + auto fusedPortal = Portal::fuse(sPortal, ePortal); + + for (auto& av : sAttachedVolumes) { + ACTS_VERBOSE("Update portal of detector volume '" << av->name() << "'."); + av->updatePortal(fusedPortal, startIndex); + } + + for (auto& av : eAttachedVolumes) { + ACTS_VERBOSE("Update portal of detector volume '" << av->name() << "'."); + av->updatePortal(fusedPortal, endIndex); + } + } + // Proto container refurbishment - outside + dShell[startIndex] = containers.front().find(startIndex)->second; + dShell[endIndex] = containers.back().find(endIndex)->second; + + // Create remaining outside shells now + std::vector sidePortals = {}; + for (auto sVals : possibleValues) { + if (sVals != bValue) { + sidePortals.push_back(portalSets[sVals][0]); + sidePortals.push_back(portalSets[sVals][1]); + } + } + + // Done. + return dShell; +} + +std::array, 3u> +Acts::Experimental::detail::CuboidalDetectorHelper::xyzBoundaries( + [[maybe_unused]] const GeometryContext& gctx, + [[maybe_unused]] const std::vector< + const Acts::Experimental::DetectorVolume*>& volumes, + Acts::Logging::Level logLevel) { + // The local logger + ACTS_LOCAL_LOGGER(getDefaultLogger("CuboidalDetectorHelper", logLevel)); + + // The return boundaries + std::array, 3u> boundaries; + + // The map for collecting + std::array, 3u> valueMaps; + auto& xMap = valueMaps[0u]; + auto& yMap = valueMaps[1u]; + auto& zMap = valueMaps[2u]; + + auto fillMap = [&](std::map& map, + const std::array& values) { + for (auto v : values) { + if (map.find(v) != map.end()) { + ++map[v]; + } else { + map[v] = 1u; + } + } + }; + + // Loop over the volumes and collect boundaries + for (const auto& v : volumes) { + if (v->volumeBounds().type() == Acts::VolumeBounds::BoundsType::eCuboid) { + auto bValues = v->volumeBounds().values(); + // The min/max values + ActsScalar halfX = bValues[CuboidVolumeBounds::BoundValues::eHalfLengthX]; + ActsScalar halfY = bValues[CuboidVolumeBounds::BoundValues::eHalfLengthY]; + ActsScalar halfZ = bValues[CuboidVolumeBounds::BoundValues::eHalfLengthZ]; + // Get the transform @todo use a center of gravity of the detector + auto translation = v->transform(gctx).translation(); + // The min/max values + ActsScalar xMin = translation.x() - halfX; + ActsScalar xMax = translation.x() + halfX; + ActsScalar yMin = translation.y() - halfY; + ActsScalar yMax = translation.y() + halfY; + ActsScalar zMin = translation.z() - halfZ; + ActsScalar zMax = translation.z() + halfZ; + // Fill the maps + fillMap(xMap, {xMin, xMax}); + fillMap(yMap, {yMin, yMax}); + fillMap(zMap, {zMin, zMax}); + } + } + + for (auto [im, map] : enumerate(valueMaps)) { + for (auto [key, value] : map) { + boundaries[im].push_back(key); + } + std::sort(boundaries[im].begin(), boundaries[im].end()); + } + + ACTS_VERBOSE("- did yield " << boundaries[0u].size() << " boundaries in X."); + ACTS_VERBOSE("- did yield " << boundaries[1u].size() << " boundaries in Y."); + ACTS_VERBOSE("- did yield " << boundaries[2u].size() << " boundaries in Z."); + + return boundaries; +} diff --git a/Core/src/Detector/detail/CylindricalDetectorHelper.cpp b/Core/src/Detector/detail/CylindricalDetectorHelper.cpp index ce7973b3889..01330ad8a0c 100644 --- a/Core/src/Detector/detail/CylindricalDetectorHelper.cpp +++ b/Core/src/Detector/detail/CylindricalDetectorHelper.cpp @@ -12,6 +12,7 @@ #include "Acts/Definitions/Tolerance.hpp" #include "Acts/Detector/DetectorVolume.hpp" #include "Acts/Detector/Portal.hpp" +#include "Acts/Detector/detail/DetectorVolumeConsistency.hpp" #include "Acts/Detector/detail/PortalHelper.hpp" #include "Acts/Geometry/CutoutCylinderVolumeBounds.hpp" #include "Acts/Surfaces/CylinderBounds.hpp" @@ -205,87 +206,6 @@ Acts::Experimental::PortalReplacement createSectorReplacement( return pRep; } -/// @brief Helper method to strip side volumes from containers -/// -/// @param containers the list of container -/// @param sides the sides -/// @param selectedOnly the selection restriction -/// -/// @return a map of stripped out container -std::map>> -stripSideVolumes( - const std::vector& - containers, - const std::vector& sides, - const std::vector& selectedOnly = {}, - Acts::Logging::Level logLevel = Acts::Logging::INFO) { - ACTS_LOCAL_LOGGER(Acts::getDefaultLogger("::stripSideVolumes", logLevel)); - - // These are the stripped off outside volumes - std::map>> - sideVolumes; - - // Principle sides and selected sides, make an intersection - std::vector selectedSides; - if (!selectedOnly.empty()) { - std::set_intersection(sides.begin(), sides.end(), selectedOnly.begin(), - selectedOnly.end(), - std::back_inserter(selectedSides)); - } else { - selectedSides = sides; - } - - // Loop through the containers - for (const auto& pc : containers) { - // Loop through the selected sides and check if they are contained - for (const auto& s : selectedSides) { - auto cSide = pc.find(s); - if (cSide != pc.end()) { - auto p = cSide->second; - auto& sVolumes = sideVolumes[s]; - auto aVolumes = - Acts::Experimental::detail::PortalHelper::attachedDetectorVolumes( - *p); - sVolumes.insert(sVolumes.end(), aVolumes.begin(), aVolumes.end()); - } - } - } - // return them - return sideVolumes; -} - -/// @brief Helper method to check alignment of the volumes, this method checks -/// if the z-axes are aligned and throws an exception if not, it assumes that -/// the detector volume content has been checked already -/// -/// @param gctx the geometry context -/// @param volumes the input volumes to be checked -/// -/// @note this is a strict matching that requires the rotation to be identical -/// -/// @note throws exception if any of checks fails -void checkAlignment( - const Acts::GeometryContext& gctx, - const std::vector>& - volumes) { - // Take first transform as reference transform - auto refRotation = volumes[0u]->transform(gctx).rotation(); - // Loop over rest and recursively test - for (auto [iv, v] : Acts::enumerate(volumes)) { - if (iv > 0) { - auto curRotation = v->transform(gctx).rotation(); - if (!curRotation.isApprox(refRotation)) { - std::string message = "CylindricalDetectorHelper: rotation of volume "; - message += std::to_string(iv); - message += std::string(" is not aligned with previous volume"); - throw std::invalid_argument(message.c_str()); - } - } - } -} - /// @brief Helper method to check the volumes in general and throw and exception if fails /// /// @param gctx the geometry context @@ -322,7 +242,8 @@ void checkVolumes( } } // Check the alignment of the volumes - checkAlignment(gctx, volumes); + Acts::Experimental::detail::DetectorVolumeConsistency::checkRotationAlignment( + gctx, volumes); } /// @brief Helper method to check the volume bounds @@ -986,8 +907,8 @@ Acts::Experimental::detail::CylindricalDetectorHelper::connectInR( } dShell[2u] = containers[containers.size() - 1u].find(2u)->second; - auto sideVolumes = - stripSideVolumes(containers, {0u, 1u, 4u, 5u}, selectedOnly, logLevel); + auto sideVolumes = PortalHelper::stripSideVolumes( + containers, {0u, 1u, 4u, 5u}, selectedOnly, logLevel); for (auto [s, volumes] : sideVolumes) { auto pR = connectInR(gctx, volumes, {s}); @@ -1061,8 +982,8 @@ Acts::Experimental::detail::CylindricalDetectorHelper::connectInZ( } // Strip the side volumes - auto sideVolumes = - stripSideVolumes(containers, nominalSides, selectedOnly, logLevel); + auto sideVolumes = PortalHelper::stripSideVolumes(containers, nominalSides, + selectedOnly, logLevel); ACTS_VERBOSE("There remain " << sideVolumes.size() << " side volume packs to be connected"); @@ -1207,7 +1128,8 @@ Acts::Experimental::detail::CylindricalDetectorHelper::wrapInZR( ActsScalar pHalfLengthZ = pValues[CylinderBounds::BoundValues::eHalfLengthZ]; - auto sideVolumes = stripSideVolumes({innerContainer}, {3u}, {3u}, logLevel); + auto sideVolumes = + PortalHelper::stripSideVolumes({innerContainer}, {3u}, {3u}, logLevel); // First the left volume sector std::vector> innerVolumes = { diff --git a/Core/src/Detector/detail/DetectorVolumeConsistency.cpp b/Core/src/Detector/detail/DetectorVolumeConsistency.cpp new file mode 100644 index 00000000000..3e1c54aaa87 --- /dev/null +++ b/Core/src/Detector/detail/DetectorVolumeConsistency.cpp @@ -0,0 +1,81 @@ +// 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 "Acts/Detector/detail/DetectorVolumeConsistency.hpp" + +#include "Acts/Definitions/Algebra.hpp" +#include "Acts/Detector/DetectorVolume.hpp" +#include "Acts/Utilities/StringHelpers.hpp" + +#include + +void Acts::Experimental::detail::DetectorVolumeConsistency:: + checkRotationAlignment( + const GeometryContext& gctx, + const std::vector>& + volumes) { + // Take first transform as reference transform + auto refRotation = volumes[0u]->transform(gctx).rotation(); + // Loop over rest and recursively test + for (auto [iv, v] : Acts::enumerate(volumes)) { + if (iv > 0) { + auto curRotation = v->transform(gctx).rotation(); + if (!curRotation.isApprox(refRotation)) { + std::string message = "ConsitencyChecker: rotation of volume "; + message += std::to_string(iv); + message += std::string(" is not aligned with previous volume"); + throw std::invalid_argument(message.c_str()); + } + } + } +} + +std::vector +Acts::Experimental::detail::DetectorVolumeConsistency::checkCenterAlignment( + const GeometryContext& gctx, + const std::vector>& volumes, + BinningValue axisValue) { + std::vector distances = {}; + // First it needs to surfive the rotation check + checkRotationAlignment(gctx, volumes); + + // Get the reference axis + Vector3 refAxis = volumes[0u]->transform(gctx).rotation().col(axisValue); + + for (auto [iv, v] : enumerate(volumes)) { + if (iv > 0) { + Vector3 lastCenter = volumes[iv - 1]->transform(gctx).translation(); + Vector3 curCenter = v->transform(gctx).translation(); + Vector3 diff(curCenter - lastCenter); + // Check if the difference is aligned with the reference axis + if (!diff.normalized().isApprox(refAxis)) { + std::string message = "ConsitencyChecker: center "; + message += toString(curCenter); + message += " of volume "; + message += std::to_string(iv); + message += " is not aligned with center "; + message += toString(lastCenter); + message += " of previous volume."; + message += " Axis mismatch: "; + message += toString(refAxis); + message += " vs. "; + message += toString(diff.normalized()); + throw std::invalid_argument(message.c_str()); + } + // Check if the projection is positive + if (diff.dot(refAxis) < 0.) { + std::string message = "ConsitencyChecker: center of volume "; + message += std::to_string(iv); + message += std::string(" is not ordered with previous volume"); + throw std::invalid_argument(message.c_str()); + } + distances.push_back(diff.norm()); + } + } + return distances; +} diff --git a/Core/src/Detector/detail/PortalHelper.cpp b/Core/src/Detector/detail/PortalHelper.cpp index d6e4c951b03..4db3b278189 100644 --- a/Core/src/Detector/detail/PortalHelper.cpp +++ b/Core/src/Detector/detail/PortalHelper.cpp @@ -83,3 +83,47 @@ Acts::Experimental::detail::PortalHelper::attachedDetectorVolumes( unsigned int iu = attachedVolumes[0u].empty() ? 1u : 0u; return attachedVolumes[iu]; } + +std::map>> +Acts::Experimental::detail::PortalHelper::stripSideVolumes( + const std::vector>>& + pContainers, + const std::vector& sides, + const std::vector& selectedOnly, + Acts::Logging::Level logLevel) { + ACTS_LOCAL_LOGGER(Acts::getDefaultLogger("::stripSideVolumes", logLevel)); + + // These are the stripped off outside volumes + std::map>> + sideVolumes; + + // Principle sides and selected sides, make an intersection + std::vector selectedSides; + if (!selectedOnly.empty()) { + std::set_intersection(sides.begin(), sides.end(), selectedOnly.begin(), + selectedOnly.end(), + std::back_inserter(selectedSides)); + } else { + selectedSides = sides; + } + + // Loop through the containers + for (const auto& pc : pContainers) { + // Loop through the selected sides and check if they are contained + for (const auto& s : selectedSides) { + auto cSide = pc.find(s); + if (cSide != pc.end()) { + auto p = cSide->second; + auto& sVolumes = sideVolumes[s]; + auto aVolumes = + Acts::Experimental::detail::PortalHelper::attachedDetectorVolumes( + *p); + sVolumes.insert(sVolumes.end(), aVolumes.begin(), aVolumes.end()); + } + } + } + // return them + return sideVolumes; +} diff --git a/Tests/UnitTests/Core/Detector/CMakeLists.txt b/Tests/UnitTests/Core/Detector/CMakeLists.txt index e46960af862..8b621b9d6f1 100644 --- a/Tests/UnitTests/Core/Detector/CMakeLists.txt +++ b/Tests/UnitTests/Core/Detector/CMakeLists.txt @@ -2,10 +2,12 @@ add_unittest(Blueprint BlueprintTests.cpp) add_unittest(BlueprintHelper BlueprintHelperTests.cpp) add_unittest(CylindricalContainerBuilder CylindricalContainerBuilderTests.cpp) add_unittest(CylindricalDetectorFromBlueprint CylindricalDetectorFromBlueprintTests.cpp) +add_unittest(CuboidalDetectorHelper CuboidalDetectorHelperTests.cpp) add_unittest(CylindricalDetectorHelper CylindricalDetectorHelperTests.cpp) add_unittest(Detector DetectorTests.cpp) add_unittest(DetectorBuilder DetectorBuilderTests.cpp) add_unittest(DetectorVolume DetectorVolumeTests.cpp) +add_unittest(DetectorVolumeConsistency DetectorVolumeConsistencyTests.cpp) add_unittest(DetectorVolumeBuilder DetectorVolumeBuilderTests.cpp) add_unittest(GeometryIdGenerator GeometryIdGeneratorTests.cpp) add_unittest(IndexedRootVolumeFinderBuilder IndexedRootVolumeFinderBuilderTests.cpp) diff --git a/Tests/UnitTests/Core/Detector/CuboidalDetectorHelperTests.cpp b/Tests/UnitTests/Core/Detector/CuboidalDetectorHelperTests.cpp new file mode 100644 index 00000000000..ad71c77d015 --- /dev/null +++ b/Tests/UnitTests/Core/Detector/CuboidalDetectorHelperTests.cpp @@ -0,0 +1,248 @@ +// 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/Definitions/Algebra.hpp" +#include "Acts/Detector/DetectorVolume.hpp" +#include "Acts/Detector/PortalGenerators.hpp" +#include "Acts/Detector/detail/CuboidalDetectorHelper.hpp" +#include "Acts/Geometry/CuboidVolumeBounds.hpp" +#include "Acts/Geometry/GeometryContext.hpp" +#include "Acts/Navigation/SurfaceCandidatesUpdaters.hpp" +#include "Acts/Utilities/BinningData.hpp" +#include "Acts/Utilities/StringHelpers.hpp" +#include "Acts/Visualization/GeometryView3D.hpp" +#include "Acts/Visualization/ObjVisualization3D.hpp" + +#include +#include +#include + +auto portalGenerator = Acts::Experimental::defaultPortalGenerator(); +auto tContext = Acts::GeometryContext(); + +BOOST_AUTO_TEST_SUITE(Experimental) + +BOOST_AUTO_TEST_CASE(CubicVolumeExceptions) { + // A perfect box shape + auto box = std::make_shared(10, 10, 10); + + // Create volume A + auto volumeA = Acts::Experimental::DetectorVolumeFactory::construct( + portalGenerator, tContext, "VolumeA", Acts::Transform3::Identity(), box, + Acts::Experimental::tryAllPortals()); + + // Create volume B + auto transformB = Acts::Transform3::Identity(); + transformB.prerotate(Acts::AngleAxis3(0.234, Acts::Vector3::UnitZ())); + + auto volumeB = Acts::Experimental::DetectorVolumeFactory::construct( + portalGenerator, tContext, "volumeB", transformB, box, + Acts::Experimental::tryAllPortals()); + // Build the container + std::vector> volumes = { + volumeA, volumeB}; + + BOOST_CHECK_THROW( + Acts::Experimental::detail::CuboidalDetectorHelper::connect( + tContext, volumes, Acts::binX, {}, Acts::Logging::VERBOSE), + std::invalid_argument); +} + +BOOST_AUTO_TEST_CASE(SimpleBoxConnection) { + std::array binningValues = {Acts::binX, Acts::binY, + Acts::binZ}; + for (auto bVal : binningValues) { + // A perfect box shape + auto box = std::make_shared(10, 10, 10); + + // Create volume A + auto volumeA = Acts::Experimental::DetectorVolumeFactory::construct( + portalGenerator, tContext, "VolumeA", Acts::Transform3::Identity(), box, + Acts::Experimental::tryAllPortals()); + + // Move it into the bval direction + auto transformB = Acts::Transform3::Identity(); + + Acts::Vector3 translation = Acts::Vector3::Zero(); + translation[bVal] = 20; + transformB.pretranslate(translation); + // Create volume B + auto volumeB = Acts::Experimental::DetectorVolumeFactory::construct( + portalGenerator, tContext, "VolumeB", transformB, box, + Acts::Experimental::tryAllPortals()); + // Build the container + std::vector> volumes = { + volumeA, volumeB}; + auto container = + Acts::Experimental::detail::CuboidalDetectorHelper::connect( + tContext, volumes, bVal, {}, Acts::Logging::VERBOSE); + + // Check the container is constructed + BOOST_CHECK(!container.empty()); + + Acts::ObjVisualization3D obj; + Acts::GeometryView3D::drawDetectorVolume(obj, *volumeA, tContext); + Acts::GeometryView3D::drawDetectorVolume(obj, *volumeB, tContext); + obj.write("ConnectectBoxesRegular_" + Acts::binningValueNames()[bVal] + + ".obj"); + } +} + +BOOST_AUTO_TEST_CASE(IrregularBoxConnectionInZ) { + std::vector binningValues = {Acts::binX, Acts::binY, + Acts::binZ}; + + using HlPos = std::array; + using VolHlPos = std::array; + using VolSetup = std::array; + + VolHlPos cPA = {{{10., 0.}, {10., 0.}, {10., 0.}}}; + VolHlPos cPB = {{{20., 0.}, {20., 0.}, {20., 0.}}}; + VolHlPos sP = {{{10., -30.}, {30., 10.}, {90., 130.}}}; + + std::array volSetups = { + {{sP, cPA, cPB}, {cPB, sP, cPA}, {cPA, cPB, sP}}}; + + std::array transforms = { + Acts::Transform3::Identity(), + Acts::Transform3(Acts::Transform3::Identity()) + .prerotate( + Acts::AngleAxis3(0.34, Acts::Vector3(1., 1., 1.).normalized()))}; + + // Try with arbitrary rotations + for (auto [it, t] : Acts::enumerate(transforms)) { + std::string trstr = it == 0 ? "" : "_rotated"; + auto rotation = t.rotation(); + // Try for all binning values + for (auto bVal : binningValues) { + auto [vsA, vsB, vsC] = volSetups[bVal]; + + // Three box shares with different length in Z + auto boxA = std::make_shared( + vsA[0][0], vsB[0][0], vsC[0][0]); + auto boxB = std::make_shared( + vsA[1][0], vsB[1][0], vsC[1][0]); + auto boxC = std::make_shared( + vsA[2][0], vsB[2][0], vsC[2][0]); + + auto transformA = Acts::Transform3::Identity(); + auto transformB = Acts::Transform3::Identity(); + auto transformC = Acts::Transform3::Identity(); + + transformA.pretranslate(Acts::Vector3(vsA[0][1], vsB[0][1], vsC[0][1])); + transformB.pretranslate(Acts::Vector3(vsA[1][1], vsB[1][1], vsC[1][1])); + transformC.pretranslate(Acts::Vector3(vsA[2][1], vsB[2][1], vsC[2][1])); + + transformA.prerotate(rotation); + transformB.prerotate(rotation); + transformC.prerotate(rotation); + + // Create volume A, B, C + auto volumeA = Acts::Experimental::DetectorVolumeFactory::construct( + portalGenerator, tContext, "VolumeA", transformA, boxA, + Acts::Experimental::tryAllPortals()); + auto volumeB = Acts::Experimental::DetectorVolumeFactory::construct( + portalGenerator, tContext, "VolumeB", transformB, boxB, + Acts::Experimental::tryAllPortals()); + auto volumeC = Acts::Experimental::DetectorVolumeFactory::construct( + portalGenerator, tContext, "VolumeC", transformC, boxC, + Acts::Experimental::tryAllPortals()); + + // Build the container + std::vector> volumes = + {volumeA, volumeB, volumeC}; + auto container = + Acts::Experimental::detail::CuboidalDetectorHelper::connect( + tContext, volumes, bVal, {}, Acts::Logging::VERBOSE); + + // Check the container is constructed + BOOST_CHECK(!container.empty()); + + Acts::ObjVisualization3D obj; + Acts::GeometryView3D::drawDetectorVolume(obj, *volumeA, tContext); + Acts::GeometryView3D::drawDetectorVolume(obj, *volumeB, tContext); + Acts::GeometryView3D::drawDetectorVolume(obj, *volumeC, tContext); + obj.write("ConnectectBoxesIrregular_" + Acts::binningValueNames()[bVal] + + trstr + ".obj"); + } + } +} + +BOOST_AUTO_TEST_CASE(ContainerConnection) { + // A perfect box shape + auto box = std::make_shared(10, 10, 10); + + // Create an AB container + + // Create volume A + auto volumeA = Acts::Experimental::DetectorVolumeFactory::construct( + portalGenerator, tContext, "VolumeA", Acts::Transform3::Identity(), box, + Acts::Experimental::tryAllPortals()); + + // Move it into the bval direction + auto transformB = Acts::Transform3::Identity(); + Acts::Vector3 translationB = Acts::Vector3::Zero(); + translationB[Acts::binX] = 20; + transformB.pretranslate(translationB); + // Create volume B + auto volumeB = Acts::Experimental::DetectorVolumeFactory::construct( + portalGenerator, tContext, "VolumeB", transformB, box, + Acts::Experimental::tryAllPortals()); + // Build the container + std::vector> volumes = { + volumeA, volumeB}; + auto containerAB = + Acts::Experimental::detail::CuboidalDetectorHelper::connect( + tContext, volumes, Acts::binX, {}, Acts::Logging::VERBOSE); + + // Create a CD container + + auto transformC = Acts::Transform3::Identity(); + Acts::Vector3 translationC = Acts::Vector3::Zero(); + translationC[Acts::binY] = 20; + transformC.pretranslate(translationC); + + auto volumeC = Acts::Experimental::DetectorVolumeFactory::construct( + portalGenerator, tContext, "VolumeC", transformC, box, + Acts::Experimental::tryAllPortals()); + + auto transformD = Acts::Transform3::Identity(); + Acts::Vector3 translationD = Acts::Vector3::Zero(); + translationD[Acts::binX] = 20; + translationD[Acts::binY] = 20; + transformD.pretranslate(translationD); + + auto volumeD = Acts::Experimental::DetectorVolumeFactory::construct( + portalGenerator, tContext, "VolumeD", transformD, box, + Acts::Experimental::tryAllPortals()); + + volumes = {volumeC, volumeD}; + auto containerCD = + Acts::Experimental::detail::CuboidalDetectorHelper::connect( + tContext, volumes, Acts::binX, {}, Acts::Logging::VERBOSE); + + auto containerABCD = + Acts::Experimental::detail::CuboidalDetectorHelper::connect( + tContext, {containerAB, containerCD}, Acts::binY, {}, + Acts::Logging::VERBOSE); + + // Check the container is constructed + BOOST_CHECK(!containerABCD.empty()); + + Acts::ObjVisualization3D obj; + Acts::GeometryView3D::drawDetectorVolume(obj, *volumeA, tContext); + Acts::GeometryView3D::drawDetectorVolume(obj, *volumeB, tContext); + Acts::GeometryView3D::drawDetectorVolume(obj, *volumeC, tContext); + Acts::GeometryView3D::drawDetectorVolume(obj, *volumeD, tContext); + + obj.write("ConnectContainers_binX.obj"); +} + +BOOST_AUTO_TEST_SUITE_END() diff --git a/Tests/UnitTests/Core/Detector/DetectorVolumeConsistencyTests.cpp b/Tests/UnitTests/Core/Detector/DetectorVolumeConsistencyTests.cpp new file mode 100644 index 00000000000..1619b4891e8 --- /dev/null +++ b/Tests/UnitTests/Core/Detector/DetectorVolumeConsistencyTests.cpp @@ -0,0 +1,80 @@ +// 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/Definitions/Algebra.hpp" +#include "Acts/Detector/DetectorVolume.hpp" +#include "Acts/Detector/PortalGenerators.hpp" +#include "Acts/Detector/detail/DetectorVolumeConsistency.hpp" +#include "Acts/Geometry/CuboidVolumeBounds.hpp" +#include "Acts/Geometry/GeometryContext.hpp" +#include "Acts/Navigation/SurfaceCandidatesUpdaters.hpp" + +auto portalGenerator = Acts::Experimental::defaultPortalGenerator(); +auto tContext = Acts::GeometryContext(); + +BOOST_AUTO_TEST_SUITE(Detector) + +BOOST_AUTO_TEST_CASE(DetectorVolumeConsistencyFail) { + // A perfect box shape + auto box = std::make_shared(10, 10, 10); + + // Create volume A + auto volumeA = Acts::Experimental::DetectorVolumeFactory::construct( + portalGenerator, tContext, "VolumeA", Acts::Transform3::Identity(), box, + Acts::Experimental::tryAllPortals()); + + // Move it into the bval direction + auto transformB = Acts::Transform3::Identity(); + Acts::Vector3 translationB = Acts::Vector3::Zero(); + translationB[Acts::binX] = 20; + translationB[Acts::binY] = 5; + transformB.pretranslate(translationB); + // Create volume B + auto volumeB = Acts::Experimental::DetectorVolumeFactory::construct( + portalGenerator, tContext, "VolumeB", transformB, box, + Acts::Experimental::tryAllPortals()); + // Build the container + std::vector> volumes = { + volumeA, volumeB}; + + BOOST_CHECK_THROW( + Acts::Experimental::detail::DetectorVolumeConsistency:: + checkCenterAlignment(tContext, {volumeA, volumeB}, Acts::binX), + std::invalid_argument); +} + +BOOST_AUTO_TEST_CASE(DetectorVolumeConsistencyPass) { + // A perfect box shape + auto box = std::make_shared(10, 10, 10); + + // Create volume A + auto volumeA = Acts::Experimental::DetectorVolumeFactory::construct( + portalGenerator, tContext, "VolumeA", Acts::Transform3::Identity(), box, + Acts::Experimental::tryAllPortals()); + + // Move it into the bval direction + auto transformB = Acts::Transform3::Identity(); + Acts::Vector3 translationB = Acts::Vector3::Zero(); + translationB[Acts::binX] = 20; + transformB.pretranslate(translationB); + // Create volume B + auto volumeB = Acts::Experimental::DetectorVolumeFactory::construct( + portalGenerator, tContext, "VolumeB", transformB, box, + Acts::Experimental::tryAllPortals()); + // Build the container + std::vector> volumes = { + volumeA, volumeB}; + + BOOST_CHECK_NO_THROW( + Acts::Experimental::detail::DetectorVolumeConsistency:: + checkCenterAlignment(tContext, {volumeA, volumeB}, Acts::binX)); +} + +BOOST_AUTO_TEST_SUITE_END() diff --git a/Tests/UnitTests/Core/Detector/PortalTests.cpp b/Tests/UnitTests/Core/Detector/PortalTests.cpp index c2e89b7dacf..232d3aa980b 100644 --- a/Tests/UnitTests/Core/Detector/PortalTests.cpp +++ b/Tests/UnitTests/Core/Detector/PortalTests.cpp @@ -144,25 +144,6 @@ BOOST_AUTO_TEST_CASE(PortalTest) { // Portal A retains identical position to B BOOST_CHECK_EQUAL(portalA->surface().center(gctx), portalB->surface().center(gctx)); - - // An invalid fusing setup - auto linkToAIImpl = std::make_unique(volumeA); - auto linkToBIImpl = std::make_unique(volumeB); - - auto portalAI = std::make_shared(surface); - DetectorVolumeUpdater linkToAI; - linkToAI.connect<&LinkToVolumeImpl::link>(std::move(linkToAIImpl)); - portalAI->assignDetectorVolumeUpdater(Acts::Direction::Positive, - std::move(linkToAI), {volumeA}); - - auto portalBI = std::make_shared(surface); - DetectorVolumeUpdater linkToBI; - linkToBI.connect<&LinkToVolumeImpl::link>(std::move(linkToBIImpl)); - portalBI->assignDetectorVolumeUpdater(Acts::Direction::Positive, - std::move(linkToBI), {volumeB}); - - BOOST_CHECK_THROW(Portal::fuse(portalAI, portalBI), std::runtime_error); - BOOST_CHECK_THROW(Portal::fuse(portalBI, portalAI), std::runtime_error); } BOOST_AUTO_TEST_CASE(PortalMaterialTest) {