Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Plane surface merging #4037

Merged
merged 13 commits into from
Jan 21, 2025
14 changes: 14 additions & 0 deletions Core/include/Acts/Surfaces/PlaneSurface.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,20 @@ class PlaneSurface : public RegularSurface {
ActsMatrix<2, 3> localCartesianToBoundLocalDerivative(
const GeometryContext& gctx, const Vector3& position) const final;

/// Merge two plane surfaces into a single one.
/// @note The surfaces need to be *compatible*, i.e. have bounds
/// that align along merging direction, and have the same bound size
/// along the non-merging direction
/// @param other The other plane surface to merge with
/// @param direction The binning direction: either @c binX or @c binY
ssdetlab marked this conversation as resolved.
Show resolved Hide resolved
/// @param logger The logger to use
/// @return The merged plane surface and a boolean indicating if surfaces are reversed
/// @note The returned boolean is `false` if `this` is *left* or
ssdetlab marked this conversation as resolved.
Show resolved Hide resolved
/// *counter-clockwise* of @p other, and `true` if not.
std::pair<std::shared_ptr<PlaneSurface>, bool> mergedWith(
const PlaneSurface& other, BinningValue direction,
const Logger& logger = getDummyLogger()) const;
ssdetlab marked this conversation as resolved.
Show resolved Hide resolved
ssdetlab marked this conversation as resolved.
Show resolved Hide resolved

protected:
/// the bounds of this surface
std::shared_ptr<const PlanarBounds> m_bounds;
Expand Down
117 changes: 117 additions & 0 deletions Core/src/Surfaces/PlaneSurface.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,20 @@

#include "Acts/Surfaces/PlaneSurface.hpp"

#include "Acts/Definitions/Algebra.hpp"
#include "Acts/Geometry/GeometryObject.hpp"
#include "Acts/Surfaces/BoundaryTolerance.hpp"
#include "Acts/Surfaces/CurvilinearSurface.hpp"
#include "Acts/Surfaces/EllipseBounds.hpp"
#include "Acts/Surfaces/InfiniteBounds.hpp"
#include "Acts/Surfaces/PlanarBounds.hpp"
#include "Acts/Surfaces/RectangleBounds.hpp"
#include "Acts/Surfaces/SurfaceBounds.hpp"
#include "Acts/Surfaces/SurfaceError.hpp"
#include "Acts/Surfaces/SurfaceMergingException.hpp"
#include "Acts/Surfaces/detail/FacesHelper.hpp"
#include "Acts/Surfaces/detail/PlanarHelper.hpp"
#include "Acts/Utilities/BinningType.hpp"
#include "Acts/Utilities/Intersection.hpp"
#include "Acts/Utilities/ThrowAssert.hpp"

Expand Down Expand Up @@ -190,4 +194,117 @@ ActsMatrix<2, 3> PlaneSurface::localCartesianToBoundLocalDerivative(
return loc3DToLocBound;
}

std::pair<std::shared_ptr<PlaneSurface>, bool> PlaneSurface::mergedWith(
const PlaneSurface& other, BinningValue direction,
const Logger& logger) const {
ACTS_VERBOSE("Merging plane surfaces in " << binningValueName(direction)
<< " direction");

if (m_associatedDetElement != nullptr ||
other.m_associatedDetElement != nullptr) {
throw SurfaceMergingException(getSharedPtr(), other.getSharedPtr(),
"PlaneSurface::merge: surfaces are "
"associated with a detector element");
}

assert(m_transform != nullptr && other.m_transform != nullptr);

Transform3 otherLocal = m_transform->inverse() * *other.m_transform;
constexpr auto tolerance = s_onSurfaceTolerance;

// Surface cannot have any relative rotation
if (std::abs((otherLocal.rotation().matrix() - RotationMatrix3::Identity())
.norm())) {
ssdetlab marked this conversation as resolved.
Show resolved Hide resolved
ACTS_ERROR("PlaneSurface::merge: surfaces have relative rotation");
throw SurfaceMergingException(
getSharedPtr(), other.getSharedPtr(),
"PlaneSurface::merge: surfaces have relative rotation");
}

const RectangleBounds* thisBounds =
dynamic_cast<const RectangleBounds*>(&bounds());
const RectangleBounds* otherBounds =
dynamic_cast<const RectangleBounds*>(&other.bounds());

if (thisBounds == nullptr || otherBounds == nullptr) {
throw SurfaceMergingException(
getSharedPtr(), other.getSharedPtr(),
"PlaneSurface::merge: only Rectangle Bounds are supported");
}

if (direction != BinningValue::binX && direction != BinningValue::binY) {
throw SurfaceMergingException(getSharedPtr(), other.getSharedPtr(),
"PlaneSurface::merge: invalid direction " +
binningValueName(direction));
}

bool mergeX = direction == BinningValue::binX;

double thisHalfMerge =
mergeX ? thisBounds->halfLengthX() : thisBounds->halfLengthY();
double otherHalfMerge =
mergeX ? otherBounds->halfLengthX() : otherBounds->halfLengthY();

double thisHalfNonMerge =
mergeX ? thisBounds->halfLengthY() : thisBounds->halfLengthX();
double otherHalfNonMerge =
mergeX ? otherBounds->halfLengthY() : otherBounds->halfLengthX();

if (std::abs(thisHalfNonMerge - otherHalfNonMerge) > tolerance) {
ACTS_ERROR(
"PlaneSurface::merge: surfaces have different non-merging lengths");
throw SurfaceMergingException(
getSharedPtr(), other.getSharedPtr(),
"PlaneSurface::merge: surfaces have different non-merging lengths");
}
Vector3 otherTranslation = otherLocal.translation();

// No translation in non-merging direction/z is allowed
double nonMergeShift = mergeX ? otherTranslation.y() : otherTranslation.x();

if (std::abs(nonMergeShift) > tolerance ||
std::abs(otherTranslation.z()) > tolerance) {
ACTS_ERROR(
"PlaneSurface::merge: surfaces have relative translation in y/z");
throw SurfaceMergingException(
getSharedPtr(), other.getSharedPtr(),
"PlaneSurface::merge: surfaces have relative translation in y/z");
}

double mergeShift = mergeX ? otherTranslation.x() : otherTranslation.y();

double thisMinMerge = -thisHalfMerge;
double thisMaxMerge = thisHalfMerge;

double otherMinMerge = mergeShift - otherHalfMerge;
double otherMaxMerge = mergeShift + otherHalfMerge;

// Surfaces have to "touch" along merging direction
if (std::abs(thisMaxMerge - otherMinMerge) > tolerance &&
std::abs(thisMinMerge - otherMaxMerge) > tolerance) {
ACTS_ERROR(
"PlaneSurface::merge: surfaces have incompatible merge bound location");
throw SurfaceMergingException(
getSharedPtr(), other.getSharedPtr(),
"PlaneSurface::merge: surfaces have incompatible merge bound location");
}

double newMaxMerge = std::max(thisMaxMerge, otherMaxMerge);
double newMinMerge = std::min(thisMinMerge, otherMinMerge);

double newHalfMerge = (newMaxMerge - newMinMerge) / 2;

double newMidMerge = (newMaxMerge + newMinMerge) / 2;

auto newBounds =
mergeX
? std::make_shared<RectangleBounds>(newHalfMerge, thisHalfNonMerge)
: std::make_shared<RectangleBounds>(thisHalfNonMerge, newHalfMerge);

Vector3 unitDir = mergeX ? Vector3::UnitX() : Vector3::UnitY();
Transform3 newTransform = *m_transform * Translation3{unitDir * newMidMerge};
return {Surface::makeShared<PlaneSurface>(newTransform, newBounds),
mergeShift < 0};
}

} // namespace Acts
172 changes: 172 additions & 0 deletions Tests/UnitTests/Core/Surfaces/PlaneSurfaceTests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.

#include <boost/test/tools/old/interface.hpp>
ssdetlab marked this conversation as resolved.
Show resolved Hide resolved
#include <boost/test/tools/output_test_stream.hpp>
#include <boost/test/unit_test.hpp>

Expand All @@ -21,6 +22,7 @@
#include "Acts/Surfaces/RectangleBounds.hpp"
#include "Acts/Surfaces/Surface.hpp"
#include "Acts/Surfaces/SurfaceBounds.hpp"
#include "Acts/Surfaces/SurfaceMergingException.hpp"
#include "Acts/Surfaces/TrapezoidBounds.hpp"
#include "Acts/Tests/CommonHelpers/DetectorElementStub.hpp"
#include "Acts/Tests/CommonHelpers/FloatComparisons.hpp"
Expand Down Expand Up @@ -378,6 +380,176 @@ BOOST_AUTO_TEST_CASE(PlaneSurfaceAlignment) {
CHECK_CLOSE_ABS(alignToloc1, expAlignToloc1, 1e-10);
}

BOOST_AUTO_TEST_SUITE(PlaneSurfaceMerging)

auto logger = Acts::getDefaultLogger("UnitTests", Acts::Logging::VERBOSE);

// Create a test context
GeometryContext gctx = GeometryContext();

auto rBounds = std::make_shared<const RectangleBounds>(1., 2.);

BOOST_AUTO_TEST_CASE(MisalignedMergingException) {
// Correct orientation, not aligned along merging
Translation3 offsetX{4., 0., 0.};
Translation3 offsetY{0., 2., 0.};

Transform3 base(Translation3::Identity());
Transform3 otherX = base * offsetX;
Transform3 otherY = base * offsetY;

auto plane = Surface::makeShared<PlaneSurface>(base, rBounds);
auto planeX = Surface::makeShared<PlaneSurface>(otherX, rBounds);
auto planeY = Surface::makeShared<PlaneSurface>(otherY, rBounds);

BOOST_CHECK_THROW(
plane->mergedWith(*planeX, Acts::BinningValue::binX, *logger),
SurfaceMergingException);
BOOST_CHECK_THROW(
plane->mergedWith(*planeY, Acts::BinningValue::binY, *logger),
SurfaceMergingException);

BOOST_CHECK_THROW(
planeX->mergedWith(*plane, Acts::BinningValue::binX, *logger),
SurfaceMergingException);
BOOST_CHECK_THROW(
planeY->mergedWith(*plane, Acts::BinningValue::binY, *logger),
SurfaceMergingException);
}

BOOST_AUTO_TEST_CASE(MisalignedOrthogonalException) {
// Correct orientation, not aligned along merging
Translation3 offsetX{2., 1., 0.};
Translation3 offsetY{-1., 4., 0.};
Translation3 offsetZ{0., 4., 1.};

Transform3 base(Translation3::Identity());
Transform3 otherX = base * offsetX;
Transform3 otherY = base * offsetY;
Transform3 otherZ = base * offsetZ;

auto plane = Surface::makeShared<PlaneSurface>(base, rBounds);
auto planeX = Surface::makeShared<PlaneSurface>(otherX, rBounds);
auto planeY = Surface::makeShared<PlaneSurface>(otherY, rBounds);
auto planeZ = Surface::makeShared<PlaneSurface>(otherZ, rBounds);

BOOST_CHECK_THROW(
plane->mergedWith(*planeX, Acts::BinningValue::binX, *logger),
SurfaceMergingException);
BOOST_CHECK_THROW(
plane->mergedWith(*planeY, Acts::BinningValue::binY, *logger),
SurfaceMergingException);
BOOST_CHECK_THROW(
plane->mergedWith(*planeZ, Acts::BinningValue::binX, *logger),
SurfaceMergingException);

BOOST_CHECK_THROW(
planeX->mergedWith(*plane, Acts::BinningValue::binX, *logger),
SurfaceMergingException);
BOOST_CHECK_THROW(
planeY->mergedWith(*plane, Acts::BinningValue::binY, *logger),
SurfaceMergingException);
BOOST_CHECK_THROW(
planeZ->mergedWith(*plane, Acts::BinningValue::binX, *logger),
SurfaceMergingException);
}

BOOST_AUTO_TEST_CASE(MisalignedAngleException) {
// Correct orientation, not aligned along merging
Translation3 offsetX{2., 0., 0.};
Translation3 offsetY{0., 4., 0.};

double angle = std::numbers::pi / 12;
Transform3 base(Translation3::Identity());
Transform3 otherX = base * offsetX * AngleAxis3(angle, Vector3::UnitZ());
Transform3 otherY = base * offsetY * AngleAxis3(angle, Vector3::UnitY());
Transform3 otherZ = base * offsetY * AngleAxis3(angle, Vector3::UnitZ());

auto plane = Surface::makeShared<PlaneSurface>(base, rBounds);
auto planeX = Surface::makeShared<PlaneSurface>(otherX, rBounds);
auto planeY = Surface::makeShared<PlaneSurface>(otherY, rBounds);
auto planeZ = Surface::makeShared<PlaneSurface>(otherZ, rBounds);

BOOST_CHECK_THROW(
plane->mergedWith(*planeX, Acts::BinningValue::binX, *logger),
SurfaceMergingException);
BOOST_CHECK_THROW(
plane->mergedWith(*planeY, Acts::BinningValue::binY, *logger),
SurfaceMergingException);
BOOST_CHECK_THROW(
plane->mergedWith(*planeZ, Acts::BinningValue::binY, *logger),
SurfaceMergingException);

BOOST_CHECK_THROW(
planeX->mergedWith(*plane, Acts::BinningValue::binX, *logger),
SurfaceMergingException);
BOOST_CHECK_THROW(
planeY->mergedWith(*plane, Acts::BinningValue::binY, *logger),
SurfaceMergingException);
BOOST_CHECK_THROW(
planeZ->mergedWith(*plane, Acts::BinningValue::binY, *logger),
SurfaceMergingException);
}

BOOST_AUTO_TEST_CASE(DifferentBoundsException) {
// Correct orientation, not aligned along merging
Translation3 offset{2., 0., 0.};

Transform3 base(Translation3::Identity());
Transform3 other = base * offset;

auto plane = Surface::makeShared<PlaneSurface>(base, rBounds);

auto rBoundsOther = std::make_shared<const RectangleBounds>(2., 4.);
auto planeOther = Surface::makeShared<PlaneSurface>(other, rBoundsOther);

BOOST_CHECK_THROW(
plane->mergedWith(*planeOther, Acts::BinningValue::binX, *logger),
SurfaceMergingException);
}

BOOST_AUTO_TEST_CASE(XYDirection) {
double angle = std::numbers::pi / 12;
Translation3 offsetX{2., 0., 0.};
Translation3 offsetY{0., 4., 0.};

Transform3 base =
AngleAxis3(angle, Vector3::UnitX()) * Translation3::Identity();
Transform3 otherX = base * offsetX;
Transform3 otherY = base * offsetY;

auto plane = Surface::makeShared<PlaneSurface>(base, rBounds);
auto planeX = Surface::makeShared<PlaneSurface>(otherX, rBounds);
auto planeY = Surface::makeShared<PlaneSurface>(otherY, rBounds);

BOOST_CHECK_THROW(
plane->mergedWith(*planeX, Acts::BinningValue::binZ, *logger),
SurfaceMergingException);

auto [planeXMerged, reversedX] =
plane->mergedWith(*planeX, Acts::BinningValue::binX, *logger);
BOOST_REQUIRE_NE(planeXMerged, nullptr);
BOOST_CHECK(!reversedX);

auto [planeYMerged, reversedY] =
plane->mergedWith(*planeY, Acts::BinningValue::binY, *logger);
BOOST_REQUIRE_NE(planeYMerged, nullptr);
BOOST_CHECK(!reversedY);

auto [planeXMerged2, reversedX2] =
planeX->mergedWith(*plane, Acts::BinningValue::binX, *logger);
BOOST_REQUIRE_NE(planeXMerged2, nullptr);
BOOST_CHECK(planeXMerged->bounds() == planeXMerged2->bounds());
BOOST_CHECK(reversedX2);

auto [planeYMerged2, reversedY2] =
planeY->mergedWith(*plane, Acts::BinningValue::binY, *logger);
BOOST_REQUIRE_NE(planeYMerged2, nullptr);
BOOST_CHECK(planeYMerged->bounds() == planeYMerged2->bounds());
BOOST_CHECK(reversedY2);
}

BOOST_AUTO_TEST_SUITE_END()
BOOST_AUTO_TEST_SUITE_END()

} // namespace Acts::Test
Loading