From 75592270f2a15555df5b552571cbbeecd080413a Mon Sep 17 00:00:00 2001 From: ssdetlab <113530373+ssdetlab@users.noreply.github.com> Date: Tue, 22 Oct 2024 01:13:39 +0300 Subject: [PATCH] refactor: Generalizing telescope seeding interface (#3725) Cleaning up and generalizing the interface of the `PathSeeder`. Adding a concept to constrain the grid type during the instantiation. --- Core/include/Acts/Seeding/PathSeeder.hpp | 248 +++++++----------- .../Acts/Seeding/detail/UtilityFunctions.hpp | 6 + .../UnitTests/Core/Seeding/PathSeederTest.cpp | 232 ++++++++-------- 3 files changed, 215 insertions(+), 271 deletions(-) diff --git a/Core/include/Acts/Seeding/PathSeeder.hpp b/Core/include/Acts/Seeding/PathSeeder.hpp index 1d3fd49f66b..d4475c022df 100644 --- a/Core/include/Acts/Seeding/PathSeeder.hpp +++ b/Core/include/Acts/Seeding/PathSeeder.hpp @@ -9,10 +9,13 @@ #pragma once #include "Acts/EventData/SourceLink.hpp" +#include "Acts/EventData/TrackParameters.hpp" +#include "Acts/Seeding/detail/UtilityFunctions.hpp" #include "Acts/Surfaces/Surface.hpp" #include "Acts/Utilities/Delegate.hpp" +#include "Acts/Utilities/GridIterator.hpp" -namespace Acts::Experimental { +namespace Acts { /// @brief Seeding algorigthm that extracts /// the IP parameters and sorts the source links @@ -23,7 +26,7 @@ namespace Acts::Experimental { /// source links -- as follows: First the source links /// are sorted into a user-defined grid. Then, iteration over the source links /// is performed. If a source link is attached to a surface that is -/// in the first tracking layer, as defined by the user, the IP parameters +/// in the reference tracking layer, as defined by the user, the IP parameters /// are estimated and the tracking layers are intersected to construct the /// core of the "Path". The source links in the subsequent layers are then /// added to the seed if they lie within the path width of the core. @@ -39,84 +42,34 @@ namespace Acts::Experimental { /// /// @note Handling of the rotated surfaces has to happen /// in the user-defined delegate functions. - -template class PathSeeder { public: - using GridType = grid_t; - - /// @brief The seed struct - /// - /// The seed struct contains the IP parameters - /// and the source links that are associated with - /// the seed. - struct Seed { - /// The IP momentum magnitude - ActsScalar ipP; - - /// The IP momentum direction - Vector3 ipDir; - - /// The IP vertex position - Vector3 ipVertex; - - /// The source links associated with the seed - std::vector sourceLinks; - - Seed() = delete; - Seed(ActsScalar ipPmag, Vector3 ipPdir, Vector3 ipPos, - std::vector sls) - : ipP(ipPmag), - ipDir(std::move(ipPdir)), - ipVertex(std::move(ipPos)), - sourceLinks(std::move(sls)) {}; - }; - - /// @brief Delegate to provide the relevant grid - /// filled with source links for the given geometry - /// member - /// - /// @arg The geometry identifier to use - /// - /// @return The grid filled with source links - using SourceLinkGridLookup = Delegate; + using PathSeed = + std::pair>; /// @brief Delegate to estimate the IP parameters - /// and the momentum direction at the first tracking layer - /// - /// @arg The geometry context to use - /// @arg The global position of the pivot source link - /// - /// @return Particle charge, the IP momentum magnitude, the IP vertex position, - /// the IP momentum direction, the momentum direction at the - /// first tracking layer - using TrackEstimator = - Delegate( - const GeometryContext&, const Vector3&)>; - - /// @brief Delegate to transform the source link to the - /// appropriate global frame. + /// and the momentum direction at the reference tracking layer /// - /// @arg The geometry context to use - /// @arg The source link to calibrate + /// @arg Geometry context to use + /// @arg Pivot source link /// - /// @return The global position of the source link measurement - using SourceLinkCalibrator = - Delegate; + /// @return Pair of the track parameters at the IP and + /// the reference tracking layer + using TrackEstimator = Delegate< + std::pair( + const GeometryContext&, const SourceLink&)>; /// @brief Delegate to find the intersections for the given pivot /// source link /// /// @arg The geometry context to use - /// @arg The global position of the pivot source link - /// @arg The momentum direction of the pivot source link - /// at the first tracking layer - /// @arg The IP momentum magnitude - /// @arg The particle charge + /// @arg Track parameters at the reference tracking layer + /// + /// @return Vector of pairs of the geometry identifier + /// and the local intersection point using IntersectionLookup = - Delegate>( - const GeometryContext&, const Vector3&, const Vector3&, - const ActsScalar&, const ActsScalar&)>; + Delegate>( + const GeometryContext&, const CurvilinearTrackParameters&)>; /// @brief Delegate to provide the path width around /// the intersection point to pull the source links @@ -133,24 +86,18 @@ class PathSeeder { /// @brief The nested configuration struct struct Config { - /// Binned SourceLink provider - SourceLinkGridLookup sourceLinkGridLookup; /// Parameters estimator TrackEstimator trackEstimator; - /// SourceLink calibrator - SourceLinkCalibrator sourceLinkCalibrator; /// Intersection finder IntersectionLookup intersectionFinder; /// Path width provider PathWidthLookup pathWidthProvider; - /// First layer extent - Extent firstLayerExtent; - /// Direction of the telescope extent - BinningValue orientation = BinningValue::binX; + /// Reference layer IDs + std::vector refLayerIds; }; /// @brief Constructor - PathSeeder(const Config& config) : m_cfg(std::move(config)) {}; + PathSeeder(const Config& config) : m_cfg(config) {}; /// @brief Destructor ~PathSeeder() = default; @@ -159,101 +106,86 @@ class PathSeeder { /// sort the source links into the seeds /// /// @param gctx The geometry context - /// @param sourceLinks The source links to seed + /// @param sourceLinkGridLookup The lookup table for the source links + /// @param seedCollection The collection of seeds to fill /// /// @return The vector of seeds - std::vector getSeeds(const GeometryContext& gctx, - const std::vector& sourceLinks) const { - // Get plane of the telescope - // sensitive surfaces - int bin0 = static_cast(BinningValue::binX); - int bin1 = static_cast(BinningValue::binY); - if (m_cfg.orientation == BinningValue::binX) { - bin0 = static_cast(BinningValue::binY); - bin1 = static_cast(BinningValue::binZ); - } else if (m_cfg.orientation == BinningValue::binY) { - bin0 = static_cast(BinningValue::binX); - bin1 = static_cast(BinningValue::binZ); - } - + template + void findSeeds(const GeometryContext& gctx, + const std::unordered_map& + sourceLinkGridLookup, + container_t& seedCollection) const { // Create the seeds - std::vector seeds; - for (const auto& sl : sourceLinks) { - Vector3 globalPos = m_cfg.sourceLinkCalibrator(gctx, sl); - - // Check if the hit is in the - // first tracking layer - if (!m_cfg.firstLayerExtent.contains(globalPos)) { - continue; - } - - // Get the IP parameters - auto [q, ipP, ipVertex, ipDir, flDir] = - m_cfg.trackEstimator(gctx, globalPos); - - // Intersect with the surfaces - std::vector> intersections = - m_cfg.intersectionFinder(gctx, globalPos, flDir, ipP, q); - - // Continue if no intersections - if (intersections.empty()) { - continue; - } - // Vector to store the source links - std::vector seedSourceLinks; - - // Store the pivot source link - seedSourceLinks.push_back(sl); + for (auto& refGeoId : m_cfg.refLayerIds) { + auto refGrid = sourceLinkGridLookup.at(refGeoId); - // Iterate over the intersections - // and get the source links - // in the subsequent layers - for (auto& [geoId, refPoint] : intersections) { - // Get the path width - auto [pathWidth0, pathWidth1] = m_cfg.pathWidthProvider(gctx, geoId); + for (auto it = refGrid.begin(); it != refGrid.end(); it++) { + std::vector pivotSourceLinks = *it; - // Get the bounds of the path - ActsScalar top0 = refPoint[bin0] + pathWidth0; - ActsScalar bot0 = refPoint[bin0] - pathWidth0; - ActsScalar top1 = refPoint[bin1] + pathWidth1; - ActsScalar bot1 = refPoint[bin1] - pathWidth1; + for (const auto& pivot : pivotSourceLinks) { + // Get the IP parameters + auto [ipParameters, refLayerParameters] = + m_cfg.trackEstimator(gctx, pivot); - // Get the lookup table for the source links - auto grid = m_cfg.sourceLinkGridLookup(geoId); + // Intersect with the surfaces + std::vector> intersections = + m_cfg.intersectionFinder(gctx, refLayerParameters); - // Get the range of bins to search for source links - auto botLeftBin = grid.localBinsFromPosition(Vector2(bot0, bot1)); - auto topRightBin = grid.localBinsFromPosition(Vector2(top0, top1)); - - // Get the source links from the lookup table - // by iterating over the bin ranges - auto currentBin = botLeftBin; - while (currentBin.at(1) <= topRightBin.at(1)) { - while (currentBin.at(0) <= topRightBin.at(0)) { - auto sourceLinksToAdd = grid.atLocalBins(currentBin); + // Continue if no intersections + if (intersections.empty()) { + continue; + } - seedSourceLinks.insert(seedSourceLinks.end(), - sourceLinksToAdd.begin(), - sourceLinksToAdd.end()); - currentBin.at(0)++; + // Iterate over the intersections + // and get the source links + // in the subsequent layers + std::vector seedSourceLinks; + for (auto& [geoId, refPoint] : intersections) { + // Get the path width + auto [pathWidth0, pathWidth1] = + m_cfg.pathWidthProvider(gctx, geoId); + + // Get the bounds of the path + ActsScalar top0 = refPoint[0] + pathWidth0; + ActsScalar bot0 = refPoint[0] - pathWidth0; + ActsScalar top1 = refPoint[1] + pathWidth1; + ActsScalar bot1 = refPoint[1] - pathWidth1; + + // Get the lookup table for the source links + auto grid = sourceLinkGridLookup.at(geoId); + + // Get the range of bins to search for source links + auto botLeftBin = grid.localBinsFromPosition(Vector2(bot0, bot1)); + auto topRightBin = grid.localBinsFromPosition(Vector2(top0, top1)); + + // Get the source links from the lookup table + // by iterating over the bin ranges + auto currentBin = botLeftBin; + while (currentBin.at(1) <= topRightBin.at(1)) { + while (currentBin.at(0) <= topRightBin.at(0)) { + auto sourceLinksToAdd = grid.atLocalBins(currentBin); + + seedSourceLinks.insert(seedSourceLinks.end(), + sourceLinksToAdd.begin(), + sourceLinksToAdd.end()); + + currentBin.at(0)++; + } + currentBin.at(1)++; + currentBin.at(0) = botLeftBin.at(0); + } } - currentBin.at(1)++; - currentBin.at(0) = botLeftBin.at(0); + PathSeed seed = {ipParameters, seedSourceLinks}; + + // Add the seed to the collection + Acts::detail::pushBackOrInsertAtEnd(seedCollection, seed); } } - - // Store the IP parameters and - // add the source links to the seed - Seed seed{ipP, ipDir, ipVertex, seedSourceLinks}; - - // Add the seed to the list - seeds.push_back(seed); } - return seeds; - }; + } private: Config m_cfg; }; -} // namespace Acts::Experimental +} // namespace Acts diff --git a/Core/include/Acts/Seeding/detail/UtilityFunctions.hpp b/Core/include/Acts/Seeding/detail/UtilityFunctions.hpp index 9cc61c4fe3f..b4fa190fa37 100644 --- a/Core/include/Acts/Seeding/detail/UtilityFunctions.hpp +++ b/Core/include/Acts/Seeding/detail/UtilityFunctions.hpp @@ -8,6 +8,8 @@ #pragma once +#include "Acts/EventData/SourceLink.hpp" + #include namespace Acts::detail { @@ -29,6 +31,10 @@ concept isCollectionThatSupportsInsert = coll.insert(std::ranges::end(coll), val); }; +template +concept SourceLinkGrid = + std::same_as>; + // Define some functions template diff --git a/Tests/UnitTests/Core/Seeding/PathSeederTest.cpp b/Tests/UnitTests/Core/Seeding/PathSeederTest.cpp index 70a8f9ebaa0..e8338925747 100644 --- a/Tests/UnitTests/Core/Seeding/PathSeederTest.cpp +++ b/Tests/UnitTests/Core/Seeding/PathSeederTest.cpp @@ -38,6 +38,8 @@ using namespace Acts::UnitLiterals; using Axis = Acts::Axis; using Grid = Acts::Grid, Axis, Axis>; +using TrackParameters = CurvilinearTrackParameters; + GeometryContext gctx; // Parameters for the geometry @@ -46,6 +48,12 @@ const ActsScalar halfZ = 10.; const ActsScalar deltaX = 10.; const ActsScalar deltaYZ = 1.; +const Vector4 trueVertex(-5., 0., 0., 0); +const std::vector truePhis = {-0.15, -0.1, -0.05, 0, + 0.05, 0.1, 0.15}; +const ActsScalar trueTheta = M_PI_2; +const ActsScalar trueQOverP = 1. / 1._GeV; + // Intersection finding to get the // region of interest for seeding class NoFieldIntersectionFinder { @@ -57,11 +65,13 @@ class NoFieldIntersectionFinder { // Find the intersections along the path // and return them in the order of the path // length - std::vector> operator()( - const GeometryContext& geoCtx, const Vector3& position, - const Vector3& direction, [[maybe_unused]] const ActsScalar& Pmag = 0, - [[maybe_unused]] const ActsScalar& Charge = 0) const { - std::vector> sIntersections; + std::vector> operator()( + const GeometryContext& geoCtx, + const TrackParameters& trackParameters) const { + Vector3 position = trackParameters.position(); + Vector3 direction = trackParameters.direction(); + + std::vector> sIntersections; // Intersect the surfaces for (auto& surface : m_surfaces) { // Get the intersection @@ -76,7 +86,11 @@ class NoFieldIntersectionFinder { if (closestForward.status() == IntersectionStatus::reachable && closestForward.pathLength() > 0.0) { sIntersections.push_back( - {closestForward.object()->geometryId(), closestForward.position()}); + {closestForward.object()->geometryId(), + surface + ->globalToLocal(geoCtx, closestForward.position(), + Vector3{0, 1, 0}) + .value()}); continue; } } @@ -84,92 +98,85 @@ class NoFieldIntersectionFinder { } }; -// Grid to store the source links for -// the seeding fast lookup -class SourceLinkGrid { +// A simple path width provider to set +// the grid lookup boundaries around the +// intersection point +class PathWidthProvider { + public: + std::pair width; + + std::pair operator()( + const GeometryContext& /*gctx*/, + const GeometryIdentifier& /*geoId*/) const { + return width; + } +}; + +// Estimator of the particle's energy, +// vertex, momentum direction at the IP +// and the direction at the first hit +class TrackEstimator { public: - using GridType = Grid; + Vector3 m_ip; + SourceLinkSurfaceAccessor m_surfaceAccessor; + + std::pair operator()( + const GeometryContext& geoCtx, const SourceLink& pivot) const { + auto testSourceLink = pivot.get(); + Vector3 pivot3 = m_surfaceAccessor(pivot)->localToGlobal( + geoCtx, testSourceLink.parameters, Vector3{0, 1, 0}); + + Vector3 direction = (pivot3 - m_ip).normalized(); - /// Lookup table collection - std::unordered_map m_lookupTables; + Vector4 ip = {m_ip.x(), m_ip.y(), m_ip.z(), 0}; + ActsScalar qOverP = 1_e / 1._GeV; + ActsScalar phi = Acts::VectorHelpers::phi(direction); + ActsScalar theta = Acts::VectorHelpers::theta(direction); + ParticleHypothesis particle = ParticleHypothesis::electron(); + + TrackParameters ipParams(ip, phi, theta, qOverP, std::nullopt, particle); + TrackParameters firstLayerParams(ip, phi, theta, qOverP, std::nullopt, + particle); + + return {ipParams, firstLayerParams}; + } +}; - /// Surface accessor +// Construct grid with the source links +struct ConstructSourceLinkGrid { SourceLinkSurfaceAccessor m_surfaceAccessor; - void initialize(const GeometryContext& geoCtx, - std::vector sourceLinks) { + std::unordered_map construct( + std::vector sourceLinks) { // Lookup table for each layer - std::unordered_map lookupTable; + std::unordered_map lookupTable; // Construct a binned grid for each layer for (int i : {14, 15, 16, 17}) { - Axis xAxis(-halfY, halfY, 50); - Axis yAxis(-halfZ, halfZ, 50); + Axis xAxis(-halfY, halfY, 100); + Axis yAxis(-halfZ, halfZ, 100); + + Grid grid(std::make_tuple(xAxis, yAxis)); + + GeometryIdentifier geoId; + + geoId.setSensitive(i); - GridType grid(std::make_tuple(xAxis, yAxis)); - lookupTable.insert({i, grid}); + lookupTable.insert({geoId, grid}); } // Fill the grid with source links for (auto& sl : sourceLinks) { - auto ssl = sl.get(); - auto id = ssl.m_geometryId; + auto tsl = sl.get(); + auto id = tsl.m_geometryId; - // Grid works with global positions - Vector3 globalPos = m_surfaceAccessor(sl)->localToGlobal( - geoCtx, ssl.parameters, Vector3{0, 1, 0}); - - auto bin = - lookupTable.at(id.sensitive()) - .localBinsFromPosition(Vector2(globalPos.y(), globalPos.z())); - lookupTable.at(id.sensitive()).atLocalBins(bin).push_back(sl); + auto bin = lookupTable.at(id).localBinsFromPosition(tsl.parameters); + lookupTable.at(id).atLocalBins(bin).push_back(sl); } - m_lookupTables = lookupTable; - }; - - // Get the source link grid for a given geometry id - GridType operator()(const GeometryIdentifier& geoId) const { - return m_lookupTables.at(geoId.sensitive()); - } -}; - -// A simple path width provider to set -// the grid lookup boundaries around the -// intersection point -std::pair getPathWidth( - const GeometryContext& /*gctx*/, const GeometryIdentifier& /*geoId*/) { - return {0.1, 0.1}; -} - -// Calibrator to transform the source links -// to global coordinates -class SourceLinkCalibrator { - public: - SourceLinkSurfaceAccessor m_surfaceAccessor; - - Vector3 operator()(const GeometryContext& geoCtx, - const SourceLink& sourceLink) const { - auto ssl = sourceLink.get(); - auto res = m_surfaceAccessor(sourceLink) - ->localToGlobal(geoCtx, ssl.parameters, Vector3{0, 1, 0}); - return res; + return lookupTable; } }; -// Estimator of the particle's energy, -// vertex, momentum direction at the IP -// and the direction at the first hit -class TrackEstimator { - public: - Vector3 ip; - - std::tuple operator()( - const GeometryContext& /*geoCtx*/, const Vector3& pivot) const { - Vector3 direction = (pivot - ip).normalized(); - return {1_e, 1._GeV, ip, direction, direction}; - }; -}; - // Construct a simple telescope detector std::shared_ptr constructTelescopeDetector() { RotationMatrix3 rotation; @@ -257,7 +264,7 @@ std::shared_ptr constructTelescopeDetector() { // Connect the volumes auto portalContainer = Experimental::detail::CuboidalDetectorHelper::connect( - gctx, volumes, BinningValue::binX, {}, Logging::VERBOSE); + gctx, volumes, BinningValue::binX, {}, Logging::INFO); // Make sure that the geometry ids are // independent of the potential Id generation @@ -302,23 +309,19 @@ std::vector createSourceLinks( } } - Vector3 vertex(-5., 0., 0.); - std::vector phis = {-0.15, -0.1, -0.05, 0, 0.05, 0.1, 0.15}; - std::vector sourceLinks; - for (ActsScalar phi : phis) { - Vector3 direction(cos(phi), sin(phi), 0.); + for (ActsScalar phi : truePhis) { + TrackParameters trackParameters(trueVertex, phi, trueTheta, trueQOverP, + std::nullopt, + ParticleHypothesis::electron()); - auto intersections = intersectionFinder(geoCtx, vertex, direction); + auto intersections = intersectionFinder(geoCtx, trackParameters); SquareMatrix2 cov = SquareMatrix2::Identity(); for (auto& [id, refPoint] : intersections) { - auto surf = *detector.sensitiveHierarchyMap().find(id); - Vector2 val = surf->globalToLocal(geoCtx, refPoint, direction).value(); - - detail::Test::TestSourceLink sourceLink(eBoundLoc0, eBoundLoc1, val, cov, - id, id.value()); + detail::Test::TestSourceLink sourceLink(eBoundLoc0, eBoundLoc1, refPoint, + cov, id, id.value()); SourceLink sl{sourceLink}; sourceLinks.push_back(sl); @@ -339,30 +342,27 @@ BOOST_AUTO_TEST_CASE(PathSeederZeroField) { auto sourceLinks = createSourceLinks(gctx, *detector); // Prepare the PathSeeder - auto pathSeederCfg = Acts::Experimental::PathSeeder::Config(); + auto pathSeederCfg = Acts::PathSeeder::Config(); // Grid to bin the source links SurfaceAccessor surfaceAccessor{*detector}; - SourceLinkGrid sourceLinkGrid; - sourceLinkGrid.m_surfaceAccessor.connect<&SurfaceAccessor::operator()>( - &surfaceAccessor); - pathSeederCfg.sourceLinkGridLookup.connect<&SourceLinkGrid::operator()>( - &sourceLinkGrid); + auto sourceLinkGridConstructor = ConstructSourceLinkGrid(); + sourceLinkGridConstructor.m_surfaceAccessor + .connect<&SurfaceAccessor::operator()>(&surfaceAccessor); + + // Create the grid + std::unordered_map sourceLinkGrid = + sourceLinkGridConstructor.construct(sourceLinks); // Estimator of the IP and first hit // parameters of the track TrackEstimator trackEstimator; - trackEstimator.ip = Vector3(-5., 0., 0.); + trackEstimator.m_ip = Vector3(-5., 0., 0.); + trackEstimator.m_surfaceAccessor.connect<&SurfaceAccessor::operator()>( + &surfaceAccessor); pathSeederCfg.trackEstimator.connect<&TrackEstimator::operator()>( &trackEstimator); - // Transforms the source links to global coordinates - SourceLinkCalibrator sourceLinkCalibrator; - sourceLinkCalibrator.m_surfaceAccessor.connect<&SurfaceAccessor::operator()>( - &surfaceAccessor); - pathSeederCfg.sourceLinkCalibrator.connect<&SourceLinkCalibrator::operator()>( - &sourceLinkCalibrator); - // Intersection finder NoFieldIntersectionFinder intersectionFinder; for (auto volume : detector->volumes()) { @@ -374,29 +374,35 @@ BOOST_AUTO_TEST_CASE(PathSeederZeroField) { .connect<&NoFieldIntersectionFinder::operator()>(&intersectionFinder); // Path width provider - pathSeederCfg.pathWidthProvider.connect<&getPathWidth>(); + PathWidthProvider pathWidthProvider; - // First tracking layer - Extent firstLayerExtent; - firstLayerExtent.set(BinningValue::binX, -0.1, 0.1); - firstLayerExtent.set(BinningValue::binY, -halfY - deltaYZ, halfY + deltaYZ); - firstLayerExtent.set(BinningValue::binZ, -halfZ - deltaYZ, halfZ + deltaYZ); + pathSeederCfg.pathWidthProvider.connect<&PathWidthProvider::operator()>( + &pathWidthProvider); - pathSeederCfg.firstLayerExtent = firstLayerExtent; + GeometryIdentifier geoId; + geoId.setSensitive(14); + pathSeederCfg.refLayerIds.push_back(geoId); // Create the PathSeeder - Acts::Experimental::PathSeeder pathSeeder(pathSeederCfg); + Acts::PathSeeder pathSeeder(pathSeederCfg); // Get the seeds - sourceLinkGrid.initialize(gctx, sourceLinks); - auto seeds = pathSeeder.getSeeds(gctx, sourceLinks); + pathWidthProvider.width = {1e-3, 1e-3}; + + std::vector seeds; + + // SeedTreeContainer seeds; + pathSeeder.findSeeds(gctx, sourceLinkGrid, seeds); // Check the seeds BOOST_CHECK_EQUAL(seeds.size(), 7); - for (auto& seed : seeds) { - BOOST_CHECK_EQUAL(seed.sourceLinks.size(), 4); - BOOST_CHECK_EQUAL(seed.ipVertex, Vector3(-5., 0., 0.)); - BOOST_CHECK_EQUAL(seed.ipP, 1._GeV); + + for (std::size_t i = 0; i < seeds.size(); i++) { + auto seed = seeds.at(i); + BOOST_CHECK_EQUAL(seed.second.size(), 4); + BOOST_CHECK_EQUAL(seed.first.phi(), truePhis.at(i)); + BOOST_CHECK_EQUAL(seed.first.theta(), trueTheta); + BOOST_CHECK_EQUAL(seed.first.qOverP(), trueQOverP); } }