From 868a027bfd29ac5c43b5dc83d22876c4dd4d6c0f Mon Sep 17 00:00:00 2001 From: Maximiliano Puccio Date: Mon, 14 Sep 2026 20:06:49 +0200 Subject: [PATCH 1/2] Implement Maurice's comments --- .../ITSMFTDetectorDefinitions.h | 6 +- .../ITSMFTTracking/TrackingPrimitives.h | 2 +- .../common/tracking/src/TrackerTraits.cxx | 17 +++-- .../test/testCombinedTrackingComposition.cxx | 62 ++++++++++++++++- .../testComputeLayerCellsOrchestration.cxx | 16 ++--- ...testComputeLayerTrackletsOrchestration.cxx | 69 ++++++++++++++++--- .../test/testITSMFTSurfaceSpecProjection.cxx | 19 ++--- .../common/tracking/test/testPropagator.cxx | 5 +- 8 files changed, 152 insertions(+), 44 deletions(-) diff --git a/Detectors/ITSMFT/common/tracking/include/ITSMFTTracking/ITSMFTDetectorDefinitions.h b/Detectors/ITSMFT/common/tracking/include/ITSMFTTracking/ITSMFTDetectorDefinitions.h index e2a4efc9ad356..c1347869c46ca 100644 --- a/Detectors/ITSMFT/common/tracking/include/ITSMFTTracking/ITSMFTDetectorDefinitions.h +++ b/Detectors/ITSMFT/common/tracking/include/ITSMFTTracking/ITSMFTDetectorDefinitions.h @@ -36,10 +36,10 @@ inline constexpr std::array kMFTLookupRMax{ constexpr std::array makeNominalMFTLayerX0() { std::array values{}; - // Each disk's budget is shared by its two sensor planes: the refit applies - // the nominal material once per attached surface. + // The nominal MFT CA prescription assigns 0.042/5 X/X0 to each surface. + // Both sensor planes use this value; do not divide it by two again. for (auto& value : values) { - value = kMFTNominalRadLength / static_cast(MFTNLayers); + value = kMFTNominalRadLength / static_cast(MFTDisks); } return values; } diff --git a/Detectors/ITSMFT/common/tracking/include/ITSMFTTracking/TrackingPrimitives.h b/Detectors/ITSMFT/common/tracking/include/ITSMFTTracking/TrackingPrimitives.h index c578ae778796b..d26de280b146f 100644 --- a/Detectors/ITSMFT/common/tracking/include/ITSMFTTracking/TrackingPrimitives.h +++ b/Detectors/ITSMFT/common/tracking/include/ITSMFTTracking/TrackingPrimitives.h @@ -45,7 +45,7 @@ struct Tracklet { int firstClusterIndex{o2::its::constants::UnusedIndex}; int secondClusterIndex{o2::its::constants::UnusedIndex}; - float tanLambda{o2::its::constants::UnsetValue}; + float tanLambda{o2::its::constants::UnsetValue}; // Directed first-to-second deltaZ / transverse chord. float phi{o2::its::constants::UnsetValue}; o2::its::TimeEstBC mTime; }; diff --git a/Detectors/ITSMFT/common/tracking/src/TrackerTraits.cxx b/Detectors/ITSMFT/common/tracking/src/TrackerTraits.cxx index b84f0d6f24355..5c0b4273b581e 100644 --- a/Detectors/ITSMFT/common/tracking/src/TrackerTraits.cxx +++ b/Detectors/ITSMFT/common/tracking/src/TrackerTraits.cxx @@ -298,9 +298,14 @@ void TrackerTraits::computeLayerTracklets(IterationContext& context, const int i if (chi2 >= o2::its::math_utils::Sq(mKernelParameters.nSigmaCut)) { continue; } - const float deltaR = sourceMeasurement.radius - targetMeasurement.radius; - const float deltaZ = sourceMeasurement.z - targetMeasurement.z; - const float tanL = o2::its::math_utils::Sq(deltaR) > o2::constants::math::Almost0 ? deltaZ / deltaR : std::copysign(o2::constants::math::VeryBig, deltaZ); + // The segment dip follows the directed edge for every surface kind. + // A vanishing transverse chord also leaves its azimuth undefined. + const float transverseChord = std::hypot(targetMeasurement.x - sourceMeasurement.x, + targetMeasurement.y - sourceMeasurement.y); + if (!(transverseChord > 1.e-6f)) { + continue; + } + const float tanL = (targetMeasurement.z - sourceMeasurement.z) / transverseChord; const float phi{o2::gpu::GPUCommonMath::ATan2(sourceMeasurement.y - targetMeasurement.y, sourceMeasurement.x - targetMeasurement.x)}; emit(currentSortedIndex, mFrame->getSortedIndex(targetROF, toLayer, iNext), tanL, phi, ts); @@ -975,12 +980,10 @@ void TrackerTraits::findRoads(IterationContext& context, const int iteration) auto seedFilter = [&](const auto& seed) { const auto hitLayerMask = seed.getHitLayerMask(); - const int effectiveTrackLength = hitLayerMask.empty() - ? 0 - : hitLayerMask.length() - (LayerMask::span(hitLayerMask.first(), hitLayerMask.last()) & nonSeedingLayerMask).count(); const auto effectiveHoleMask = hitLayerMask.holeMask() & ~nonSeedingLayerMask; + // Missing layers may be allowed, but do not count toward MinTrackLength. return effectiveHoleMask.isAllowedHoleMask(trkParam.MaxHoles, holeLayerMask) && - effectiveTrackLength >= trkParam.getMinSeedingClusters() && + hitLayerMask.count() >= trkParam.MinTrackLength && std::abs(seed.getQOverPt()) <= maxAbsQOverPt && seed.getChi2() <= trkParam.MaxChi2NDF * ((startLevel + 2) * 2 - 5); }; diff --git a/Detectors/ITSMFT/common/tracking/test/testCombinedTrackingComposition.cxx b/Detectors/ITSMFT/common/tracking/test/testCombinedTrackingComposition.cxx index 46b414895b702..e5ee44739e9d9 100644 --- a/Detectors/ITSMFT/common/tracking/test/testCombinedTrackingComposition.cxx +++ b/Detectors/ITSMFT/common/tracking/test/testCombinedTrackingComposition.cxx @@ -309,7 +309,7 @@ struct StandaloneRun { StandaloneRun(o2::detectors::DetID::ID det, SurfaceKind kind, const TrackingParameters& singleParams, const std::vector& decoded, - int rofLength = 40) + int rofLength = 40, LayerMask holeLayers = {}) : params{singleParams} { const auto orderedSurfaces = ordered(0, NLayers); @@ -329,7 +329,7 @@ struct StandaloneRun { TrackerInitialization configuration; configuration.catalog = catalogView; configuration.memoryPool = pool; - configuration.layout = makeDetectorLayout(); + configuration.layout = makeDetectorLayout(holeLayers); configuration.plan = o2::itsmft::tracking::test::makeTrackingPlan(singleParams); const auto configured = tracker.initialize(frame, configuration); BOOST_REQUIRE(configured.ok()); @@ -513,8 +513,66 @@ CombinedTrackingComposer makeComposer(const TrackingParameters& itsParams, const return CombinedTrackingComposer{std::vector{itsParams}, std::vector{mftParams}}; } +template +void checkMinimumHitLayers(SurfaceKind kind, TrackingParameters params, std::vector clusters) +{ + ensureTrivialMagneticFieldIsSet(); + BOOST_REQUIRE_EQUAL(clusters.size(), static_cast(NLayers)); + const LayerMask allowedHoles{1u << 3}; + params.MaxHoles = 1; + params.MinTrackLength = NLayers - 1; + + // Exercise both an internal hole (span exceeds hit count) and a missing + // endpoint (no internal hole, but MaxHoles must not lower the minimum). + for (const int missingLayer : {3, NLayers - 1}) { + BOOST_TEST_CONTEXT("missing layer " << missingLayer) { + auto incomplete = clusters; + incomplete.erase(incomplete.begin() + missingLayer); + StandaloneRun accepted{DetId, kind, params, incomplete, 40, allowedHoles}; + BOOST_REQUIRE(accepted.result.outcome == TrackingOutcome::Success); + BOOST_REQUIRE_EQUAL(accepted.frame.getGenericTracks().size(), 1u); + BOOST_CHECK_EQUAL(accepted.frame.getGenericTracks().front().hitLayers.count(), NLayers - 1); + BOOST_CHECK(!accepted.frame.getGenericTracks().front().hitLayers.has(missingLayer)); + + auto stricter = params; + stricter.MinTrackLength = NLayers; + StandaloneRun rejected{DetId, kind, stricter, incomplete, 40, allowedHoles}; + BOOST_REQUIRE(rejected.result.outcome == TrackingOutcome::Success); + BOOST_CHECK(rejected.frame.getGenericTracks().empty()); + } + } + + // A skipped non-seeding surface is not a hole; it still cannot contribute + // a hit toward MinTrackLength. + clusters.erase(clusters.begin() + 3); + params.MaxHoles = 0; + params.SeedingLayers = LayerMask::span(0, NLayers - 1) & ~allowedHoles; + StandaloneRun sparseAccepted{DetId, kind, params, clusters}; + BOOST_REQUIRE(sparseAccepted.result.outcome == TrackingOutcome::Success); + BOOST_REQUIRE_EQUAL(sparseAccepted.frame.getGenericTracks().size(), 1u); + BOOST_CHECK_EQUAL(sparseAccepted.frame.getGenericTracks().front().hitLayers.count(), NLayers - 1); + params.MinTrackLength = NLayers; + StandaloneRun sparseRejected{DetId, kind, params, clusters}; + BOOST_REQUIRE(sparseRejected.result.outcome == TrackingOutcome::Success); + BOOST_CHECK(sparseRejected.frame.getGenericTracks().empty()); +} + } // namespace +BOOST_AUTO_TEST_CASE(CylinderRoadMinimumCountsHitLayers) +{ + const auto params = makeItsParams(); + checkMinimumHitLayers( + SurfaceKind::Cylinder, params, buildItsHelixChainClusters(params.LayerRadii, Bz, 1.f, 0.4f, 0.3f)); +} + +BOOST_AUTO_TEST_CASE(DiskRoadMinimumCountsHitLayers) +{ + const auto params = makeMftParams(); + checkMinimumHitLayers( + SurfaceKind::Disk, params, buildMftChainClusters(params, Bz, MFTNLayers - 1)); +} + BOOST_AUTO_TEST_CASE(CombinedLoadingBackfillsOneGlobalWorkspace) { // TrackerTraits::findRoads() unconditionally touches the global diff --git a/Detectors/ITSMFT/common/tracking/test/testComputeLayerCellsOrchestration.cxx b/Detectors/ITSMFT/common/tracking/test/testComputeLayerCellsOrchestration.cxx index 106ca35f8a94a..01c1116826b3b 100644 --- a/Detectors/ITSMFT/common/tracking/test/testComputeLayerCellsOrchestration.cxx +++ b/Detectors/ITSMFT/common/tracking/test/testComputeLayerCellsOrchestration.cxx @@ -460,11 +460,9 @@ int findCellIndex(const TopologyView& topology, int inner, int middle, int outer Tracklet candidateTracklet(const GlobalMeasurement& first, const GlobalMeasurement& second, const o2::its::TimeEstBC& timestamp) { - const float deltaR = first.radius - second.radius; - const float deltaZ = first.z - second.z; - const float tanLambda = deltaR * deltaR > o2::constants::math::Almost0 - ? deltaZ / deltaR - : std::copysign(o2::constants::math::VeryBig, deltaZ); + const float transverseChord = std::hypot(second.x - first.x, second.y - first.y); + BOOST_REQUIRE_GT(transverseChord, 1.e-6f); + const float tanLambda = (second.z - first.z) / transverseChord; const float phi = std::atan2(first.y - second.y, first.x - second.x); return {0, 0, tanLambda, phi, timestamp}; } @@ -960,15 +958,15 @@ BOOST_AUTO_TEST_CASE(DiskCellRejectsKinkBeyondNominalScatteringTolerance) rig.params[0].TrackletMinPt = 0.3f; rig.establishLayout(); - // This kinked triplet used to be the threading/repeated-call fixture. - // Its dip-angle change exceeds the tolerance with nominal MFT material. + // Keep the dip-angle change beyond the tolerance with 0.0084 X/X0 + // per MFT surface, so this remains an angular-rejection test. const std::array clusters{makeGlobalCluster(1.0f, 0.5f, -0.4f, 0), makeGlobalCluster(1.3f, 0.62f, -0.6f, 0), - makeGlobalCluster(1.7f, 0.78f, -0.9f, 0)}; + makeGlobalCluster(1.7f, 0.78f, -1.0f, 0)}; loadCandidateClusters(rig, clusters, {makeDiskHit(-0.4f, 1.0f, 0.5f), makeDiskHit(-0.6f, 1.3f, 0.62f), - makeDiskHit(-0.9f, 1.7f, 0.78f)}); + makeDiskHit(-1.0f, 1.7f, 0.78f)}); auto view = prepare(rig); const auto topology = topologyView(rig); const int cellIndex = findCellIndex(topology, 0, 1, 2); diff --git a/Detectors/ITSMFT/common/tracking/test/testComputeLayerTrackletsOrchestration.cxx b/Detectors/ITSMFT/common/tracking/test/testComputeLayerTrackletsOrchestration.cxx index 09c07f5e6e4c0..caa4554eaee13 100644 --- a/Detectors/ITSMFT/common/tracking/test/testComputeLayerTrackletsOrchestration.cxx +++ b/Detectors/ITSMFT/common/tracking/test/testComputeLayerTrackletsOrchestration.cxx @@ -485,16 +485,66 @@ BOOST_AUTO_TEST_CASE(DiskOnePassAndTwoPassProduceIdenticalTracklets) SurfaceKind::Disk, clusters, 1); const auto parallel = runFixture(o2::detectors::DetID::MFT, SurfaceKind::Disk, SurfaceKind::Disk, clusters, 4); - const float sourceRadius = o2::gpu::CAMath::Hypot(1.f, 0.5f); - const float targetRadius = o2::gpu::CAMath::Hypot(targetX, targetY); - const float expectedTanLambda = (fromZ - toZ) / (sourceRadius - targetRadius); + const float transverseChord = std::hypot(targetX - 1.f, targetY - 0.5f); + const float expectedTanLambda = (toZ - fromZ) / transverseChord; const float expectedPhi = o2::gpu::CAMath::ATan2(0.5f - targetY, 1.f - targetX); checkExactTracklet(serial, expectedTanLambda, expectedPhi); checkExactTracklet(parallel, expectedTanLambda, expectedPhi); checkSame(serial, parallel); } -BOOST_AUTO_TEST_CASE(DiskSameRadiusClustersProduceInfiniteSlopeTracklet) +BOOST_AUTO_TEST_CASE(CylinderDisplacedChordPreservesBothLongitudinalSigns) +{ + // A line parallel to x, displaced by y=1: its transverse length is exactly + // one, while the difference of beam-axis radii is smaller than one. + for (const float sign : {-1.f, 1.f}) { + std::vector clusters; + for (int layer = 0; layer < 2; ++layer) { + const float x = 3.f + layer; + const float z = sign * 0.25f * (layer + 1); + auto cluster = cylinderCluster(x, z, layer); + cluster.global.y = 1.f; + cluster.cylinderFrame.u = 1.f; + clusters.push_back(cluster); + } + const auto widenSearch = [](ReferenceTrackingParameters& p) { + p.NSigmaCut = 100.f; + p.PVres = 10.f; // Widen the independent azimuthal search gate too. + }; + const auto serial = runFixture(o2::detectors::DetID::ITS, SurfaceKind::Cylinder, + SurfaceKind::Cylinder, clusters, 1, widenSearch); + const auto parallel = runFixture(o2::detectors::DetID::ITS, SurfaceKind::Cylinder, + SurfaceKind::Cylinder, clusters, 4, widenSearch); + const float expectedPhi = o2::gpu::CAMath::ATan2(0.f, -1.f); + checkExactTracklet(serial, sign * 0.25f, expectedPhi); + checkExactTracklet(parallel, sign * 0.25f, expectedPhi); + checkSame(serial, parallel); + } +} + +BOOST_AUTO_TEST_CASE(DiskEqualRadiusDistinctHitsHaveFiniteSignedSlope) +{ + const float fromZ = detail::mftLayerZ(0); + const float toZ = detail::mftLayerZ(1); + // Same radius, different positions, with a transverse chord of exactly one. + const std::vector clusters{ + diskCluster(1.f, 0.5f, fromZ, 0), + diskCluster(1.f, -0.5f, toZ, 1)}; + const auto widenSearch = [](ReferenceTrackingParameters& p) { + p.NSigmaCut = 100.f; + p.PVres = 10.f; + }; + const auto serial = runFixture(o2::detectors::DetID::MFT, SurfaceKind::Disk, + SurfaceKind::Disk, clusters, 1, widenSearch); + const auto parallel = runFixture(o2::detectors::DetID::MFT, SurfaceKind::Disk, + SurfaceKind::Disk, clusters, 4, widenSearch); + const float expectedPhi = o2::gpu::CAMath::ATan2(1.f, 0.f); + checkExactTracklet(serial, toZ - fromZ, expectedPhi); + checkExactTracklet(parallel, toZ - fromZ, expectedPhi); + checkSame(serial, parallel); +} + +BOOST_AUTO_TEST_CASE(DiskZeroTransverseChordRejectsTracklet) { const float fromZ = detail::mftLayerZ(0); const float toZ = detail::mftLayerZ(1); @@ -506,10 +556,8 @@ BOOST_AUTO_TEST_CASE(DiskSameRadiusClustersProduceInfiniteSlopeTracklet) SurfaceKind::Disk, clusters, 1, widenSearch); const auto parallel = runFixture(o2::detectors::DetID::MFT, SurfaceKind::Disk, SurfaceKind::Disk, clusters, 4, widenSearch); - const float expectedTanLambda = std::copysign(o2::constants::math::VeryBig, fromZ - toZ); - const float expectedPhi = o2::gpu::CAMath::ATan2(0.f, 0.f); - checkExactTracklet(serial, expectedTanLambda, expectedPhi); - checkExactTracklet(parallel, expectedTanLambda, expectedPhi); + BOOST_CHECK(serial.tracklets.empty()); + BOOST_CHECK(parallel.tracklets.empty()); checkSame(serial, parallel); } @@ -694,9 +742,8 @@ BOOST_AUTO_TEST_CASE(MftIdentityLayoutTrackletsSpanMultipleAdjacentEdgesInOrder) BOOST_CHECK_EQUAL(tracklet.secondClusterIndex, 0); const auto& source = clusters[from].global; const auto& target = clusters[to].global; - const float sourceRadius = o2::gpu::CAMath::Hypot(source.x, source.y); - const float targetRadius = o2::gpu::CAMath::Hypot(target.x, target.y); - const float expectedTanLambda = (source.z - target.z) / (sourceRadius - targetRadius); + const float transverseChord = std::hypot(target.x - source.x, target.y - source.y); + const float expectedTanLambda = (target.z - source.z) / transverseChord; BOOST_CHECK_EQUAL(tracklet.tanLambda, expectedTanLambda); BOOST_CHECK_EQUAL_COLLECTIONS(snapshot.allLookups[id].begin(), snapshot.allLookups[id].end(), expectedLookup.begin(), expectedLookup.end()); sawEdge01 |= (from == 0 && to == 1); diff --git a/Detectors/ITSMFT/common/tracking/test/testITSMFTSurfaceSpecProjection.cxx b/Detectors/ITSMFT/common/tracking/test/testITSMFTSurfaceSpecProjection.cxx index b34c34f95be92..3f99da8936d7f 100644 --- a/Detectors/ITSMFT/common/tracking/test/testITSMFTSurfaceSpecProjection.cxx +++ b/Detectors/ITSMFT/common/tracking/test/testITSMFTSurfaceSpecProjection.cxx @@ -165,20 +165,21 @@ BOOST_AUTO_TEST_CASE(MFTMaterialMatchesNominalDefaultsAndRadlRhoFormula) } } -BOOST_AUTO_TEST_CASE(MFTSensorPairsShareThePhysicalDiskBudget) +BOOST_AUTO_TEST_CASE(MFTSurfacesUseTheNominalCAPrescription) { + constexpr float expectedSurfaceX0 = 0.0084f; float totalX0 = 0.f; float totalArealDensity = 0.f; - for (int disk = 0; disk < MFTDisks; ++disk) { - const auto& front = kMFTStaticSurfaceCatalog[2 * disk].material; - const auto& back = kMFTStaticSurfaceCatalog[2 * disk + 1].material; - BOOST_CHECK_CLOSE(front.xOverX0 + back.xOverX0, kMFTNominalRadLength / MFTDisks, 1.e-4f); - totalX0 += front.xOverX0 + back.xOverX0; - totalArealDensity += front.arealDensityGPerCm2 + back.arealDensityGPerCm2; + for (const auto& surface : kMFTStaticSurfaceCatalog) { + BOOST_CHECK_CLOSE(surface.material.xOverX0, expectedSurfaceX0, 1.e-4f); + BOOST_CHECK_CLOSE(surface.material.arealDensityGPerCm2, + expectedSurfaceX0 * o2::its::constants::Radl * o2::its::constants::Rho, 1.e-4f); + totalX0 += surface.material.xOverX0; + totalArealDensity += surface.material.arealDensityGPerCm2; } - BOOST_CHECK_CLOSE(totalX0, kMFTNominalRadLength, 1.e-4f); + BOOST_CHECK_CLOSE(totalX0, 0.084f, 1.e-4f); BOOST_CHECK_CLOSE(totalArealDensity, - kMFTNominalRadLength * o2::its::constants::Radl * o2::its::constants::Rho, 1.e-4f); + 0.084f * o2::its::constants::Radl * o2::its::constants::Rho, 1.e-4f); } BOOST_AUTO_TEST_CASE(ITSProjectionPreservesEveryFieldBitExactly) diff --git a/Detectors/ITSMFT/common/tracking/test/testPropagator.cxx b/Detectors/ITSMFT/common/tracking/test/testPropagator.cxx index 8c4de92364ae4..d7d585eb85e2f 100644 --- a/Detectors/ITSMFT/common/tracking/test/testPropagator.cxx +++ b/Detectors/ITSMFT/common/tracking/test/testPropagator.cxx @@ -868,7 +868,7 @@ BOOST_AUTO_TEST_CASE(RefitDriverSkipsHoleSlots) BOOST_CHECK_EQUAL(acceptedHitCount, 1u); } -BOOST_AUTO_TEST_CASE(FullMFTRefitLegUsesOneDetectorMaterialBudget) +BOOST_AUTO_TEST_CASE(FullMFTRefitLegUsesNominalMaterialAtEverySurface) { const SurfaceCatalogView catalog{kMFTStaticSurfaceCatalog.data(), MFTNLayers}; for (const auto direction : {material::MaterialTraversalDirection::AlongMomentum, @@ -887,7 +887,8 @@ BOOST_AUTO_TEST_CASE(FullMFTRefitLegUsesOneDetectorMaterialBudget) const float momentumScale = std::sqrt(1.f + tanl * tanl); float expectedMomentum = momentumScale / std::abs(state.parameters[4]); const float initialMomentum = expectedMomentum; - const float pathX0 = kMFTNominalRadLength / MFTNLayers * momentumScale / std::abs(tanl); + constexpr float expectedSurfaceX0 = 0.0084f; + const float pathX0 = expectedSurfaceX0 * momentumScale / std::abs(tanl); const material::IntegratedMaterialBudget expectedMaterial{ pathX0, pathX0 * o2::its::constants::Radl * o2::its::constants::Rho}; std::array slots{}; From c91b9f18141b5b5ac7ad73f2df9bad62cb799bb9 Mon Sep 17 00:00:00 2001 From: ALICE Action Bot Date: Mon, 14 Sep 2026 18:07:43 +0000 Subject: [PATCH 2/2] Please consider the following formatting changes --- .../common/tracking/test/testCombinedTrackingComposition.cxx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Detectors/ITSMFT/common/tracking/test/testCombinedTrackingComposition.cxx b/Detectors/ITSMFT/common/tracking/test/testCombinedTrackingComposition.cxx index e5ee44739e9d9..ea6b1cb517100 100644 --- a/Detectors/ITSMFT/common/tracking/test/testCombinedTrackingComposition.cxx +++ b/Detectors/ITSMFT/common/tracking/test/testCombinedTrackingComposition.cxx @@ -525,7 +525,8 @@ void checkMinimumHitLayers(SurfaceKind kind, TrackingParameters params, std::vec // Exercise both an internal hole (span exceeds hit count) and a missing // endpoint (no internal hole, but MaxHoles must not lower the minimum). for (const int missingLayer : {3, NLayers - 1}) { - BOOST_TEST_CONTEXT("missing layer " << missingLayer) { + BOOST_TEST_CONTEXT("missing layer " << missingLayer) + { auto incomplete = clusters; incomplete.erase(incomplete.begin() + missingLayer); StandaloneRun accepted{DetId, kind, params, incomplete, 40, allowedHoles};