Back to home page

EIC code displayed by LXR

 
 

    


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

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/Utilities/AxisDefinitions.hpp"
0012 #include "Acts/Utilities/AxisSpec.hpp"
0013 #include "Acts/Utilities/IAxis.hpp"
0014 #include "ActsTests/CommonHelpers/FloatComparisons.hpp"
0015 
0016 #include <stdexcept>
0017 #include <variant>
0018 
0019 using Acts::AxisSpec;
0020 
0021 namespace ActsTests {
0022 
0023 BOOST_AUTO_TEST_SUITE(UtilitiesSuite)
0024 
0025 BOOST_AUTO_TEST_CASE(AxisSpecEquidistant) {
0026   using enum Acts::AxisBoundaryType;
0027 
0028   AxisSpec af = AxisSpec::Equidistant(5, 0., 10., Bound);
0029   BOOST_CHECK(!af.isDeferred());
0030   BOOST_CHECK(af.isEquidistant());
0031   BOOST_CHECK(!af.isVariable());
0032   BOOST_CHECK(!af.direction().has_value());
0033   BOOST_CHECK(af.boundaryType() == Bound);
0034   BOOST_CHECK_EQUAL(af.nBins(), 5);
0035   BOOST_CHECK(af.asEquidistant().min == 0.);
0036   BOOST_CHECK(af.asEquidistant().max == 10.);
0037   BOOST_CHECK_THROW(af.asVariable(), std::bad_variant_access);
0038 
0039   auto axis = af.buildAxis({});
0040   BOOST_CHECK(axis->isEquidistant());
0041   BOOST_CHECK_EQUAL(axis->getBoundaryType(), Bound);
0042   BOOST_CHECK_EQUAL(axis->getNBins(), 5);
0043   CHECK_CLOSE_ABS(axis->getMin(), 0., 1e-15);
0044   CHECK_CLOSE_ABS(axis->getMax(), 10., 1e-15);
0045   BOOST_CHECK(!axis->getDirection().has_value());
0046 
0047   // Supplying a specified property validates it instead of overriding it
0048   BOOST_CHECK_NO_THROW(af.buildAxis({.min = 0., .max = 10.}));
0049   BOOST_CHECK_THROW(af.buildAxis({.min = 0., .max = 1.}),
0050                     std::invalid_argument);
0051   BOOST_CHECK_THROW(af.buildAxis({.boundaryType = Closed}),
0052                     std::invalid_argument);
0053 
0054   // Invalid construction
0055   BOOST_CHECK_THROW(AxisSpec::Equidistant(5, 1., 0., Bound),
0056                     std::invalid_argument);
0057   BOOST_CHECK_THROW(AxisSpec::Equidistant(0, 0., 1., Bound),
0058                     std::invalid_argument);
0059 }
0060 
0061 BOOST_AUTO_TEST_CASE(AxisSpecVariable) {
0062   using enum Acts::AxisBoundaryType;
0063 
0064   AxisSpec af = AxisSpec::Variable({0., 1., 4., 10.}, Open);
0065   BOOST_CHECK(!af.isDeferred());
0066   BOOST_CHECK(!af.isEquidistant());
0067   BOOST_CHECK(af.isVariable());
0068   BOOST_CHECK(af.boundaryType() == Open);
0069   BOOST_CHECK_EQUAL(af.nBins(), 3);
0070 
0071   auto axis = af.buildAxis({});
0072   BOOST_CHECK(axis->isVariable());
0073   BOOST_CHECK_EQUAL(axis->getBoundaryType(), Open);
0074   std::vector<double> expectedEdges = {0., 1., 4., 10.};
0075   BOOST_CHECK(axis->getBinEdges() == expectedEdges);
0076 
0077   // The edges are the range, so supplying a different one is a mismatch
0078   BOOST_CHECK_THROW(af.buildAxis({.min = 0., .max = 1.}),
0079                     std::invalid_argument);
0080 
0081   // A variable axis without a boundary type takes it from the consumer
0082   AxisSpec afOpen = AxisSpec::Variable({0., 1., 4., 10.});
0083   BOOST_CHECK(afOpen.isDeferred());
0084   BOOST_CHECK_THROW(afOpen.buildAxis({}), std::domain_error);
0085   BOOST_CHECK_EQUAL(
0086       afOpen.buildAxis({.boundaryType = Bound})->getBoundaryType(), Bound);
0087 
0088   // Invalid construction
0089   BOOST_CHECK_THROW(AxisSpec::Variable({0.}, Bound), std::invalid_argument);
0090   BOOST_CHECK_THROW(AxisSpec::Variable({0., 1., 1.}, Bound),
0091                     std::invalid_argument);
0092   BOOST_CHECK_THROW(AxisSpec::Variable({0., 2., 1.}, Bound),
0093                     std::invalid_argument);
0094 }
0095 
0096 BOOST_AUTO_TEST_CASE(AxisSpecDeferredEquidistant) {
0097   using enum Acts::AxisBoundaryType;
0098 
0099   AxisSpec af = AxisSpec::DeferredEquidistant(20);
0100   BOOST_CHECK(af.isDeferred());
0101   BOOST_CHECK(af.isEquidistant());
0102   BOOST_CHECK(!af.boundaryType().has_value());
0103   BOOST_CHECK_EQUAL(af.nBins(), 20);
0104   BOOST_CHECK_EQUAL(af.asEquidistant().nBins, 20);
0105 
0106   // A deferred spec cannot be built without the missing properties
0107   BOOST_CHECK_THROW(af.buildAxis({}), std::domain_error);
0108   BOOST_CHECK_THROW(af.buildAxis({.min = -2., .max = 2.}), std::domain_error);
0109 
0110   auto axis = af.buildAxis({.min = -2., .max = 2., .boundaryType = Closed});
0111   BOOST_CHECK(axis->isEquidistant());
0112   BOOST_CHECK_EQUAL(axis->getBoundaryType(), Closed);
0113   BOOST_CHECK_EQUAL(axis->getNBins(), 20);
0114   CHECK_CLOSE_ABS(axis->getMin(), -2., 1e-15);
0115   CHECK_CLOSE_ABS(axis->getMax(), 2., 1e-15);
0116 
0117   // Invalid supplied range
0118   BOOST_CHECK_THROW(
0119       af.buildAxis({.min = 2., .max = -2., .boundaryType = Bound}),
0120       std::invalid_argument);
0121 
0122   // A range fixed at configuration time, the boundary type left open
0123   AxisSpec afRanged = AxisSpec::Equidistant(4, 0., 8.);
0124   BOOST_CHECK(afRanged.isDeferred());
0125   auto ranged = afRanged.buildAxis({.boundaryType = Bound});
0126   CHECK_CLOSE_ABS(ranged->getMin(), 0., 1e-15);
0127   CHECK_CLOSE_ABS(ranged->getMax(), 8., 1e-15);
0128 
0129   // Invalid construction
0130   BOOST_CHECK_THROW(AxisSpec::DeferredEquidistant(0), std::invalid_argument);
0131 }
0132 
0133 BOOST_AUTO_TEST_CASE(AxisSpecDeferredVariable) {
0134   using enum Acts::AxisBoundaryType;
0135 
0136   AxisSpec af = AxisSpec::DeferredVariable({0., 0.1, 0.5, 1.});
0137   BOOST_CHECK(af.isDeferred());
0138   BOOST_CHECK(af.isVariable());
0139   BOOST_CHECK_EQUAL(af.nBins(), 3);
0140 
0141   BOOST_CHECK(af.isDeferredVariable());
0142   BOOST_CHECK_THROW(af.buildAxis({}), std::domain_error);
0143 
0144   // Edges are scaled affinely onto the supplied range
0145   auto axis = af.buildAxis({.min = 10., .max = 30., .boundaryType = Bound});
0146   BOOST_CHECK(axis->isVariable());
0147   auto edges = axis->getBinEdges();
0148   BOOST_CHECK_EQUAL(edges.size(), 4);
0149   CHECK_CLOSE_ABS(edges[0], 10., 1e-15);
0150   CHECK_CLOSE_ABS(edges[1], 12., 1e-15);
0151   CHECK_CLOSE_ABS(edges[2], 20., 1e-15);
0152   CHECK_CLOSE_ABS(edges[3], 30., 1e-15);
0153 
0154   // Invalid construction: not normalized to [0, 1] or not strictly increasing
0155   BOOST_CHECK_THROW(AxisSpec::DeferredVariable({0., 0.5}),
0156                     std::invalid_argument);
0157   BOOST_CHECK_THROW(AxisSpec::DeferredVariable({0.1, 1.}),
0158                     std::invalid_argument);
0159   BOOST_CHECK_THROW(AxisSpec::DeferredVariable({0., 0.5, 0.4, 1.}),
0160                     std::invalid_argument);
0161   BOOST_CHECK_THROW(AxisSpec::DeferredVariable({1.}), std::invalid_argument);
0162 }
0163 
0164 BOOST_AUTO_TEST_CASE(AxisSpecFromAxis) {
0165   using enum Acts::AxisBoundaryType;
0166 
0167   auto eqAxis = Acts::IAxis::createEquidistant(Bound, -1., 1., 8,
0168                                                Acts::AxisDirection::AxisZ);
0169   AxisSpec af = AxisSpec::FromAxis(*eqAxis);
0170   BOOST_CHECK(!af.isDeferred());
0171   BOOST_CHECK(af.isEquidistant());
0172   BOOST_CHECK(af.direction() == Acts::AxisDirection::AxisZ);
0173   // Round trip
0174   BOOST_CHECK(*af.buildAxis({}) == *eqAxis);
0175 
0176   auto varAxis = Acts::IAxis::createVariable(Closed, {0., 2., 3.});
0177   AxisSpec afVar = AxisSpec::FromAxis(*varAxis);
0178   BOOST_CHECK(afVar.isVariable());
0179   BOOST_CHECK(!afVar.direction().has_value());
0180   BOOST_CHECK(*afVar.buildAxis({}) == *varAxis);
0181 }
0182 
0183 BOOST_AUTO_TEST_CASE(AxisSpecDirectionHandling) {
0184   using enum Acts::AxisBoundaryType;
0185   using enum Acts::AxisDirection;
0186 
0187   AxisSpec af = AxisSpec::DeferredEquidistant(10, AxisPhi);
0188   BOOST_CHECK(af.direction() == AxisPhi);
0189 
0190   // Matching supplied direction is fine
0191   auto axis = af.buildAxis(
0192       {.min = 0., .max = 1., .boundaryType = Bound, .direction = AxisPhi});
0193   BOOST_CHECK(axis->getDirection() == AxisPhi);
0194 
0195   // Mismatching supplied direction throws
0196   BOOST_CHECK_THROW(
0197       af.buildAxis(
0198           {.min = 0., .max = 1., .boundaryType = Bound, .direction = AxisZ}),
0199       std::invalid_argument);
0200 
0201   // Without a specified direction the supplied one is adopted
0202   AxisSpec afFree = AxisSpec::DeferredEquidistant(10);
0203   auto axisFree = afFree.buildAxis(
0204       {.min = 0., .max = 1., .boundaryType = Bound, .direction = AxisZ});
0205   BOOST_CHECK(axisFree->getDirection() == AxisZ);
0206 
0207   // withDirection attaches the direction
0208   AxisSpec afDir = afFree.withDirection(AxisRPhi);
0209   BOOST_CHECK(afDir.direction() == AxisRPhi);
0210   BOOST_CHECK(afFree != afDir);
0211 
0212   // Same rule for a spec that leaves nothing else open
0213   AxisSpec afFull = AxisSpec::Equidistant(4, 0., 1., Bound, AxisX);
0214   BOOST_CHECK_THROW(afFull.buildAxis({.direction = AxisY}),
0215                     std::invalid_argument);
0216   BOOST_CHECK(afFull.buildAxis({.direction = AxisX})->getDirection() == AxisX);
0217   BOOST_CHECK(afFull.buildAxis({})->getDirection() == AxisX);
0218 }
0219 
0220 BOOST_AUTO_TEST_CASE(AxisSpecToDeferred) {
0221   using enum Acts::AxisBoundaryType;
0222 
0223   // Equidistant keeps only the bin count
0224   AxisSpec af =
0225       AxisSpec::Equidistant(12, -3., 3., Closed, Acts::AxisDirection::AxisPhi);
0226   AxisSpec deferred = af.toDeferred();
0227   BOOST_CHECK(deferred.isDeferred());
0228   BOOST_CHECK(deferred.isEquidistant());
0229   BOOST_CHECK_EQUAL(deferred.nBins(), 12);
0230   BOOST_CHECK(deferred.direction() == Acts::AxisDirection::AxisPhi);
0231 
0232   // Variable edges are normalized to [0, 1] with exact endpoints
0233   AxisSpec afVar = AxisSpec::Variable({10., 12., 20., 30.}, Bound);
0234   AxisSpec deferredVar = afVar.toDeferred();
0235   BOOST_CHECK(deferredVar.isDeferred());
0236   const auto& normalizedEdges =
0237       deferredVar.asDeferredVariable().normalizedEdges;
0238   BOOST_CHECK_EQUAL(normalizedEdges.size(), 4);
0239   BOOST_CHECK_EQUAL(normalizedEdges.front(), 0.);
0240   BOOST_CHECK_EQUAL(normalizedEdges.back(), 1.);
0241   CHECK_CLOSE_ABS(normalizedEdges[1], 0.1, 1e-15);
0242   CHECK_CLOSE_ABS(normalizedEdges[2], 0.5, 1e-15);
0243 
0244   // Deferred specs are returned unchanged
0245   AxisSpec afDef = AxisSpec::DeferredEquidistant(7);
0246   BOOST_CHECK(afDef.toDeferred() == afDef);
0247 }
0248 
0249 BOOST_AUTO_TEST_CASE(AxisSpecEqualityAndStreams) {
0250   using enum Acts::AxisBoundaryType;
0251 
0252   AxisSpec a = AxisSpec::Equidistant(10, 0., 1., Bound);
0253   AxisSpec b = AxisSpec::Equidistant(10, 0., 1., Bound);
0254   AxisSpec c = AxisSpec::Equidistant(10, 0., 2., Bound);
0255   BOOST_CHECK(a == b);
0256   BOOST_CHECK(a != c);
0257   BOOST_CHECK(a != AxisSpec::DeferredEquidistant(10));
0258   BOOST_CHECK(a != a.withDirection(Acts::AxisDirection::AxisX));
0259 
0260   BOOST_CHECK_EQUAL(a.toString(),
0261                     "AxisSpec: 10 bins, equidistant within [0, 1], Bound");
0262   BOOST_CHECK_EQUAL(
0263       AxisSpec::DeferredEquidistant(5, Acts::AxisDirection::AxisZ).toString(),
0264       "AxisSpec: 5 bins in AxisZ, equidistant within deferred range, "
0265       "deferred boundary type");
0266 }
0267 
0268 BOOST_AUTO_TEST_SUITE_END()
0269 
0270 }  // namespace ActsTests