Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2026-08-20 08:20:05

0001 // This file is part of the ACTS project.
0002 //
0003 // Copyright (C) 2016 CERN for the benefit of the ACTS project
0004 //
0005 // This Source Code Form is subject to the terms of the Mozilla Public
0006 // License, v. 2.0. If a copy of the MPL was not distributed with this
0007 // file, You can obtain one at https://mozilla.org/MPL/2.0/.
0008 
0009 #include <boost/test/unit_test.hpp>
0010 
0011 #include "Acts/Definitions/Algebra.hpp"
0012 #include "Acts/Surfaces/CylinderBounds.hpp"
0013 #include "Acts/Surfaces/CylinderSurface.hpp"
0014 #include "Acts/Surfaces/DiscSurface.hpp"
0015 #include "Acts/Surfaces/PlaneSurface.hpp"
0016 #include "Acts/Surfaces/RadialBounds.hpp"
0017 #include "Acts/Surfaces/RectangleBounds.hpp"
0018 #include "Acts/Surfaces/StrawSurface.hpp"
0019 #include "Acts/Surfaces/TrapezoidBounds.hpp"
0020 #include "Acts/Utilities/AxisDefinitions.hpp"
0021 #include "Acts/Utilities/AxisSpec.hpp"
0022 #include "Acts/Utilities/MultiAxisSpec.hpp"
0023 #include "ActsTests/CommonHelpers/FloatComparisons.hpp"
0024 
0025 #include <array>
0026 #include <numbers>
0027 #include <optional>
0028 #include <stdexcept>
0029 
0030 using namespace Acts;
0031 
0032 namespace ActsTests {
0033 
0034 BOOST_AUTO_TEST_SUITE(UtilitiesSuite)
0035 
0036 BOOST_AUTO_TEST_CASE(MultiAxisSpecBasics) {
0037   using enum Acts::AxisBoundaryType;
0038 
0039   MultiAxisSpec maf({AxisSpec::Equidistant(10, 0., 1., Bound),
0040                      AxisSpec::Variable({0., 1., 3.}, Open)});
0041   BOOST_CHECK_EQUAL(maf.size(), 2);
0042   BOOST_CHECK(!maf.isDeferred());
0043   BOOST_CHECK_EQUAL(maf.axisSpec(0).nBins(), 10);
0044   BOOST_CHECK_EQUAL(maf.axisSpec(1).nBins(), 2);
0045   BOOST_CHECK_THROW(maf.axisSpec(2), std::out_of_range);
0046 
0047   auto multiAxis = maf.buildMultiAxis();
0048   BOOST_CHECK_EQUAL(multiAxis->getNAxes(), 2);
0049   BOOST_CHECK(multiAxis->getAxis(0).isEquidistant());
0050   BOOST_CHECK(multiAxis->getAxis(1).isVariable());
0051   BOOST_CHECK_EQUAL(multiAxis->getAxis(0).getNBins(), 10);
0052   BOOST_CHECK_EQUAL(multiAxis->getAxis(1).getNBins(), 2);
0053 
0054   // Empty construction is invalid
0055   BOOST_CHECK_THROW(MultiAxisSpec({}), std::invalid_argument);
0056 
0057   // Mixed with a deferred axis: full resolution is required for all axes
0058   MultiAxisSpec mixed({AxisSpec::Equidistant(10, 0., 1., Bound),
0059                        AxisSpec::DeferredEquidistant(5)});
0060   BOOST_CHECK(mixed.isDeferred());
0061   BOOST_CHECK_THROW(mixed.buildMultiAxis(), std::domain_error);
0062 }
0063 
0064 BOOST_AUTO_TEST_CASE(MultiAxisSpecDeferredResolution) {
0065   using enum Acts::AxisBoundaryType;
0066   using enum Acts::AxisDirection;
0067 
0068   MultiAxisSpec maf(
0069       {AxisSpec::DeferredEquidistant(4, AxisRPhi),
0070        AxisSpec::DeferredVariable({0., 0.5, 1.}, std::nullopt, AxisZ)});
0071   BOOST_CHECK(maf.isDeferred());
0072   BOOST_CHECK_THROW(maf.buildMultiAxis(), std::domain_error);
0073 
0074   MultiAxisSpec::Options options = {
0075       {.min = -3., .max = 3., .boundaryType = Closed, .direction = AxisRPhi},
0076       {.min = -10., .max = 10., .boundaryType = Bound, .direction = AxisZ}};
0077   auto axes = maf.buildMultiAxis(options);
0078   BOOST_CHECK_EQUAL(axes->getNAxes(), 2);
0079   BOOST_CHECK_EQUAL(axes->getAxis(0).getBoundaryType(), Closed);
0080   BOOST_CHECK_EQUAL(axes->getAxis(0).getNBins(), 4);
0081   BOOST_CHECK(axes->getAxis(0).getDirection() == AxisRPhi);
0082   BOOST_CHECK_EQUAL(axes->getAxis(1).getBoundaryType(), Bound);
0083   CHECK_CLOSE_ABS(axes->getAxis(1).getBinEdges()[1], 0., 1e-15);
0084   BOOST_CHECK(axes->getAxis(1).getDirection() == AxisZ);
0085 
0086   // Directions are validated per axis
0087   BOOST_CHECK_THROW(
0088       maf.buildMultiAxis(
0089           {{.min = -3., .max = 3., .boundaryType = Closed, .direction = AxisZ},
0090            {.min = -10.,
0091             .max = 10.,
0092             .boundaryType = Bound,
0093             .direction = AxisRPhi}}),
0094       std::invalid_argument);
0095 
0096   // Sizes have to match
0097   BOOST_CHECK_THROW(
0098       maf.buildMultiAxis({{.min = 0., .max = 1., .boundaryType = Bound}}),
0099       std::invalid_argument);
0100 }
0101 
0102 BOOST_AUTO_TEST_CASE(MultiAxisSpecXDApi) {
0103   using enum Acts::AxisBoundaryType;
0104 
0105   Acts::MultiAxisSpec1D maf1D({AxisSpec::DeferredEquidistant(8)});
0106   BOOST_CHECK_EQUAL(maf1D.size(), 1);
0107   std::unique_ptr<Acts::IMultiAxis1D> ma1D = maf1D.buildMultiAxis(
0108       {AxisSpec::Options{.min = 0., .max = 4., .boundaryType = Bound}});
0109   BOOST_CHECK_EQUAL(ma1D->getNAxes(), 1);
0110   BOOST_CHECK_EQUAL(ma1D->getAxis(0).getNBins(), 8);
0111 
0112   Acts::MultiAxisSpec2D maf2D({AxisSpec::Equidistant(2, 0., 1., Bound),
0113                                AxisSpec::Equidistant(3, 0., 1., Bound)});
0114   std::unique_ptr<Acts::IMultiAxis2D> ma2D = maf2D.buildMultiAxis();
0115   BOOST_CHECK_EQUAL(ma2D->getNAxes(), 2);
0116   BOOST_CHECK_EQUAL(ma2D->getNTotalBins(), 6);
0117 
0118   // The XD types remain usable through the runtime-dimension base
0119   const MultiAxisSpec& base = maf2D;
0120   BOOST_CHECK_EQUAL(base.buildMultiAxis()->getNAxes(), 2);
0121 }
0122 
0123 BOOST_AUTO_TEST_CASE(MultiAxisSpecEqualityAndStreams) {
0124   using enum Acts::AxisBoundaryType;
0125 
0126   MultiAxisSpec a(
0127       {AxisSpec::DeferredEquidistant(4), AxisSpec::DeferredEquidistant(5)});
0128   MultiAxisSpec b(
0129       {AxisSpec::DeferredEquidistant(4), AxisSpec::DeferredEquidistant(5)});
0130   MultiAxisSpec c({AxisSpec::DeferredEquidistant(4)});
0131   BOOST_CHECK(a == b);
0132   BOOST_CHECK(a != c);
0133 
0134   BOOST_CHECK_EQUAL(
0135       c.toString(),
0136       "MultiAxisSpec: 1 axes [AxisSpec: 4 bins, equidistant within "
0137       "deferred range, deferred boundary type]");
0138 }
0139 
0140 BOOST_AUTO_TEST_CASE(ResolveMultiAxisAgainstSurface) {
0141   using enum AxisDirection;
0142   using enum AxisBoundaryType;
0143 
0144   auto cylinder = Surface::makeShared<CylinderSurface>(
0145       Transform3::Identity(), std::make_shared<CylinderBounds>(30., 100.));
0146 
0147   // Positional matching without directions
0148   MultiAxisSpec2D positional(
0149       {AxisSpec::DeferredEquidistant(10), AxisSpec::DeferredEquidistant(20)});
0150   auto axes = resolveMultiAxis(positional, *cylinder);
0151   BOOST_CHECK_EQUAL(axes->getNAxes(), 2);
0152   BOOST_CHECK(axes->getAxis(0).getDirection() == AxisRPhi);
0153   BOOST_CHECK_EQUAL(axes->getAxis(0).getBoundaryType(), Closed);
0154   BOOST_CHECK_EQUAL(axes->getAxis(0).getNBins(), 10);
0155   BOOST_CHECK(axes->getAxis(1).getDirection() == AxisZ);
0156   BOOST_CHECK_EQUAL(axes->getAxis(1).getBoundaryType(), Bound);
0157   CHECK_CLOSE_ABS(axes->getAxis(1).getMin(), -100., 1e-12);
0158 
0159   // Directed matching in canonical order
0160   MultiAxisSpec2D directed({AxisSpec::DeferredEquidistant(10, AxisRPhi),
0161                             AxisSpec::DeferredEquidistant(20, AxisZ)});
0162   auto directedAxes = resolveMultiAxis(directed, *cylinder);
0163   BOOST_CHECK_EQUAL(directedAxes->getAxis(0).getNBins(), 10);
0164   BOOST_CHECK_EQUAL(directedAxes->getAxis(1).getNBins(), 20);
0165 
0166   // Swapped order is re-ordered to match the surface
0167   MultiAxisSpec2D swapped({AxisSpec::DeferredEquidistant(20, AxisZ),
0168                            AxisSpec::DeferredEquidistant(10, AxisRPhi)});
0169   auto reordered = resolveMultiAxis(swapped, *cylinder);
0170   BOOST_CHECK(reordered->getAxis(0).getDirection() == AxisRPhi);
0171   BOOST_CHECK_EQUAL(reordered->getAxis(0).getNBins(), 10);
0172   BOOST_CHECK(reordered->getAxis(1).getDirection() == AxisZ);
0173   BOOST_CHECK_EQUAL(reordered->getAxis(1).getNBins(), 20);
0174 
0175   // Binning along a single direction is a 2D binning with one bin in the
0176   // other direction
0177   MultiAxisSpec2D oneD({AxisSpec::DeferredEquidistant(1, AxisRPhi),
0178                         AxisSpec::DeferredEquidistant(8, AxisZ)});
0179   auto oneDAxes = resolveMultiAxis(oneD, *cylinder);
0180   BOOST_CHECK_EQUAL(oneDAxes->getNAxes(), 2);
0181   BOOST_CHECK_EQUAL(oneDAxes->getAxis(0).getNBins(), 1);
0182   BOOST_CHECK_EQUAL(oneDAxes->getAxis(1).getNBins(), 8);
0183 
0184   // Deferred variable binning is scaled onto the surface range
0185   MultiAxisSpec2D variable(
0186       {AxisSpec::DeferredEquidistant(1, AxisRPhi),
0187        AxisSpec::DeferredVariable({0., 0.25, 1.}, std::nullopt, AxisZ)});
0188   auto variableAxes = resolveMultiAxis(variable, *cylinder);
0189   auto edges = variableAxes->getAxis(1).getBinEdges();
0190   CHECK_CLOSE_ABS(edges[0], -100., 1e-12);
0191   CHECK_CLOSE_ABS(edges[1], -50., 1e-12);
0192   CHECK_CLOSE_ABS(edges[2], 100., 1e-12);
0193 
0194   // Mismatching directions are rejected
0195   MultiAxisSpec2D wrongDirs({AxisSpec::DeferredEquidistant(10, AxisR),
0196                              AxisSpec::DeferredEquidistant(20, AxisPhi)});
0197   BOOST_CHECK_THROW(resolveMultiAxis(wrongDirs, *cylinder),
0198                     std::invalid_argument);
0199 
0200   // Duplicate directions are rejected
0201   MultiAxisSpec2D duplicate({AxisSpec::DeferredEquidistant(10, AxisZ),
0202                              AxisSpec::DeferredEquidistant(20, AxisZ)});
0203   BOOST_CHECK_THROW(resolveMultiAxis(duplicate, *cylinder),
0204                     std::invalid_argument);
0205 
0206   // Mixed directed and undirected axes are rejected
0207   MultiAxisSpec2D mixed({AxisSpec::DeferredEquidistant(10, AxisRPhi),
0208                          AxisSpec::DeferredEquidistant(20)});
0209   BOOST_CHECK_THROW(resolveMultiAxis(mixed, *cylinder), std::invalid_argument);
0210 
0211   // Unsupported surface
0212   auto straw =
0213       Surface::makeShared<StrawSurface>(Transform3::Identity(), 5., 100.);
0214   MultiAxisSpec2D strawBinning(
0215       {AxisSpec::DeferredEquidistant(4), AxisSpec::DeferredEquidistant(4)});
0216   BOOST_CHECK_THROW(resolveMultiAxis(strawBinning, *straw),
0217                     std::invalid_argument);
0218 
0219   // A spec that fixes what the surface dictates has to agree with it
0220   MultiAxisSpec2D full({AxisSpec::Equidistant(10, 0., 1., Bound),
0221                         AxisSpec::DeferredEquidistant(20)});
0222   BOOST_CHECK_THROW(resolveMultiAxis(full, *cylinder), std::invalid_argument);
0223 }
0224 
0225 BOOST_AUTO_TEST_CASE(ResolveMultiAxisSurfaceRanges) {
0226   using enum AxisBoundaryType;
0227   using enum AxisDirection;
0228 
0229   // Undirected axes bind positionally to the canonical surface directions
0230   MultiAxisSpec2D binning(
0231       {AxisSpec::DeferredEquidistant(1), AxisSpec::DeferredEquidistant(1)});
0232 
0233   // Disc: bound in r, closed in phi over the full azimuth
0234   auto disc = Surface::makeShared<DiscSurface>(
0235       Transform3::Identity(), std::make_shared<RadialBounds>(50., 75.));
0236   std::array<AxisDirection, 2> expectedDisc = {AxisR, AxisPhi};
0237   BOOST_CHECK(static_cast<const Surface&>(*disc).localAxes() == expectedDisc);
0238   auto discAxes = resolveMultiAxis(binning, *disc);
0239   CHECK_CLOSE_ABS(discAxes->getAxis(0).getMin(), 50., 1e-12);
0240   CHECK_CLOSE_ABS(discAxes->getAxis(0).getMax(), 75., 1e-12);
0241   BOOST_CHECK_EQUAL(discAxes->getAxis(0).getBoundaryType(), Bound);
0242   BOOST_CHECK_EQUAL(discAxes->getAxis(1).getBoundaryType(), Closed);
0243 
0244   // Rectangle
0245   auto rectangle = Surface::makeShared<PlaneSurface>(
0246       Transform3::Identity(), std::make_shared<RectangleBounds>(20., 30.));
0247   std::array<AxisDirection, 2> expectedPlane = {AxisX, AxisY};
0248   BOOST_CHECK(static_cast<const Surface&>(*rectangle).localAxes() ==
0249               expectedPlane);
0250   auto rectangleAxes = resolveMultiAxis(binning, *rectangle);
0251   CHECK_CLOSE_ABS(rectangleAxes->getAxis(0).getMin(), -20., 1e-12);
0252   CHECK_CLOSE_ABS(rectangleAxes->getAxis(0).getMax(), 20., 1e-12);
0253   BOOST_CHECK_EQUAL(rectangleAxes->getAxis(0).getBoundaryType(), Bound);
0254 
0255   // Trapezoid: x spans the wider of the two half lengths
0256   auto trapezoid = Surface::makeShared<PlaneSurface>(
0257       Transform3::Identity(), std::make_shared<TrapezoidBounds>(5., 15., 30.));
0258   auto trapezoidAxes = resolveMultiAxis(binning, *trapezoid);
0259   CHECK_CLOSE_ABS(trapezoidAxes->getAxis(0).getMin(), -15., 1e-12);
0260   CHECK_CLOSE_ABS(trapezoidAxes->getAxis(0).getMax(), 15., 1e-12);
0261   CHECK_CLOSE_ABS(trapezoidAxes->getAxis(1).getMax(), 30., 1e-12);
0262 
0263   // Sectoral cylinder: the azimuthal axis is bound, not closed
0264   auto sector = Surface::makeShared<CylinderSurface>(
0265       Transform3::Identity(),
0266       std::make_shared<CylinderBounds>(25., 50., std::numbers::pi / 4.));
0267   auto sectorAxes = resolveMultiAxis(binning, *sector);
0268   CHECK_CLOSE_ABS(sectorAxes->getAxis(0).getMin(), -25. * std::numbers::pi / 4.,
0269                   1e-12);
0270   BOOST_CHECK_EQUAL(sectorAxes->getAxis(0).getBoundaryType(), Bound);
0271 }
0272 
0273 BOOST_AUTO_TEST_SUITE_END()
0274 
0275 }  // namespace ActsTests