Skip to content

Commit e86650b

Browse files
committed
Implement Maurice's comments
1 parent 1afbfdc commit e86650b

8 files changed

Lines changed: 153 additions & 44 deletions

Detectors/ITSMFT/common/tracking/include/ITSMFTTracking/ITSMFTDetectorDefinitions.h

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,10 +36,10 @@ inline constexpr std::array<float, MFTNLayers> kMFTLookupRMax{
3636
constexpr std::array<float, MFTNLayers> makeNominalMFTLayerX0()
3737
{
3838
std::array<float, MFTNLayers> values{};
39-
// Each disk's budget is shared by its two sensor planes: the refit applies
40-
// the nominal material once per attached surface.
39+
// The nominal MFT CA prescription assigns 0.042/5 X/X0 to each surface.
40+
// Both sensor planes use this value; do not divide it by two again.
4141
for (auto& value : values) {
42-
value = kMFTNominalRadLength / static_cast<float>(MFTNLayers);
42+
value = kMFTNominalRadLength / static_cast<float>(MFTDisks);
4343
}
4444
return values;
4545
}

Detectors/ITSMFT/common/tracking/include/ITSMFTTracking/TrackingPrimitives.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ struct Tracklet {
4545

4646
int firstClusterIndex{o2::its::constants::UnusedIndex};
4747
int secondClusterIndex{o2::its::constants::UnusedIndex};
48-
float tanLambda{o2::its::constants::UnsetValue};
48+
float tanLambda{o2::its::constants::UnsetValue}; // Directed first-to-second deltaZ / transverse chord.
4949
float phi{o2::its::constants::UnsetValue};
5050
o2::its::TimeEstBC mTime;
5151
};

Detectors/ITSMFT/common/tracking/src/TrackerTraits.cxx

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -298,9 +298,14 @@ void TrackerTraits::computeLayerTracklets(IterationContext& context, const int i
298298
if (chi2 >= o2::its::math_utils::Sq(mKernelParameters.nSigmaCut)) {
299299
continue;
300300
}
301-
const float deltaR = sourceMeasurement.radius - targetMeasurement.radius;
302-
const float deltaZ = sourceMeasurement.z - targetMeasurement.z;
303-
const float tanL = o2::its::math_utils::Sq(deltaR) > o2::constants::math::Almost0 ? deltaZ / deltaR : std::copysign(o2::constants::math::VeryBig, deltaZ);
301+
// The segment dip follows the directed edge for every surface kind.
302+
// A vanishing transverse chord also leaves its azimuth undefined.
303+
const float transverseChord = std::hypot(targetMeasurement.x - sourceMeasurement.x,
304+
targetMeasurement.y - sourceMeasurement.y);
305+
if (!(transverseChord > 1.e-6f)) {
306+
continue;
307+
}
308+
const float tanL = (targetMeasurement.z - sourceMeasurement.z) / transverseChord;
304309
const float phi{o2::gpu::GPUCommonMath::ATan2(sourceMeasurement.y - targetMeasurement.y,
305310
sourceMeasurement.x - targetMeasurement.x)};
306311
emit(currentSortedIndex, mFrame->getSortedIndex(targetROF, toLayer, iNext), tanL, phi, ts);
@@ -975,12 +980,10 @@ void TrackerTraits::findRoads(IterationContext& context, const int iteration)
975980

976981
auto seedFilter = [&](const auto& seed) {
977982
const auto hitLayerMask = seed.getHitLayerMask();
978-
const int effectiveTrackLength = hitLayerMask.empty()
979-
? 0
980-
: hitLayerMask.length() - (LayerMask::span(hitLayerMask.first(), hitLayerMask.last()) & nonSeedingLayerMask).count();
981983
const auto effectiveHoleMask = hitLayerMask.holeMask() & ~nonSeedingLayerMask;
984+
// Missing layers may be allowed, but do not count toward MinTrackLength.
982985
return effectiveHoleMask.isAllowedHoleMask(trkParam.MaxHoles, holeLayerMask) &&
983-
effectiveTrackLength >= trkParam.getMinSeedingClusters() &&
986+
hitLayerMask.count() >= trkParam.MinTrackLength &&
984987
std::abs(seed.getQOverPt()) <= maxAbsQOverPt && seed.getChi2() <= trkParam.MaxChi2NDF * ((startLevel + 2) * 2 - 5);
985988
};
986989

Detectors/ITSMFT/common/tracking/test/testCombinedTrackingComposition.cxx

Lines changed: 61 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -309,7 +309,7 @@ struct StandaloneRun {
309309

310310
StandaloneRun(o2::detectors::DetID::ID det, SurfaceKind kind,
311311
const TrackingParameters& singleParams, const std::vector<DecodedCluster>& decoded,
312-
int rofLength = 40)
312+
int rofLength = 40, LayerMask holeLayers = {})
313313
: params{singleParams}
314314
{
315315
const auto orderedSurfaces = ordered(0, NLayers);
@@ -329,7 +329,7 @@ struct StandaloneRun {
329329
TrackerInitialization configuration;
330330
configuration.catalog = catalogView;
331331
configuration.memoryPool = pool;
332-
configuration.layout = makeDetectorLayout();
332+
configuration.layout = makeDetectorLayout(holeLayers);
333333
configuration.plan = o2::itsmft::tracking::test::makeTrackingPlan(singleParams);
334334
const auto configured = tracker.initialize(frame, configuration);
335335
BOOST_REQUIRE(configured.ok());
@@ -513,8 +513,67 @@ CombinedTrackingComposer makeComposer(const TrackingParameters& itsParams, const
513513
return CombinedTrackingComposer{std::vector<TrackingParameters>{itsParams}, std::vector<TrackingParameters>{mftParams}};
514514
}
515515

516+
template <o2::detectors::DetID::ID DetId, int NLayers>
517+
void checkMinimumHitLayers(SurfaceKind kind, TrackingParameters params, std::vector<DecodedCluster> clusters)
518+
{
519+
ensureTrivialMagneticFieldIsSet();
520+
BOOST_REQUIRE_EQUAL(clusters.size(), static_cast<size_t>(NLayers));
521+
const LayerMask allowedHoles{1u << 3};
522+
params.MaxHoles = 1;
523+
params.MinTrackLength = NLayers - 1;
524+
525+
// Exercise both an internal hole (span exceeds hit count) and a missing
526+
// endpoint (no internal hole, but MaxHoles must not lower the minimum).
527+
for (const int missingLayer : {3, NLayers - 1}) {
528+
BOOST_TEST_CONTEXT("missing layer " << missingLayer)
529+
{
530+
auto incomplete = clusters;
531+
incomplete.erase(incomplete.begin() + missingLayer);
532+
StandaloneRun<DetId, NLayers> accepted{DetId, kind, params, incomplete, 40, allowedHoles};
533+
BOOST_REQUIRE(accepted.result.outcome == TrackingOutcome::Success);
534+
BOOST_REQUIRE_EQUAL(accepted.frame.getGenericTracks().size(), 1u);
535+
BOOST_CHECK_EQUAL(accepted.frame.getGenericTracks().front().hitLayers.count(), NLayers - 1);
536+
BOOST_CHECK(!accepted.frame.getGenericTracks().front().hitLayers.has(missingLayer));
537+
538+
auto stricter = params;
539+
stricter.MinTrackLength = NLayers;
540+
StandaloneRun<DetId, NLayers> rejected{DetId, kind, stricter, incomplete, 40, allowedHoles};
541+
BOOST_REQUIRE(rejected.result.outcome == TrackingOutcome::Success);
542+
BOOST_CHECK(rejected.frame.getGenericTracks().empty());
543+
}
544+
}
545+
546+
// A skipped non-seeding surface is not a hole; it still cannot contribute
547+
// a hit toward MinTrackLength.
548+
clusters.erase(clusters.begin() + 3);
549+
params.MaxHoles = 0;
550+
params.SeedingLayers = LayerMask::span(0, NLayers - 1) & ~allowedHoles;
551+
StandaloneRun<DetId, NLayers> sparseAccepted{DetId, kind, params, clusters};
552+
BOOST_REQUIRE(sparseAccepted.result.outcome == TrackingOutcome::Success);
553+
BOOST_REQUIRE_EQUAL(sparseAccepted.frame.getGenericTracks().size(), 1u);
554+
BOOST_CHECK_EQUAL(sparseAccepted.frame.getGenericTracks().front().hitLayers.count(), NLayers - 1);
555+
params.MinTrackLength = NLayers;
556+
StandaloneRun<DetId, NLayers> sparseRejected{DetId, kind, params, clusters};
557+
BOOST_REQUIRE(sparseRejected.result.outcome == TrackingOutcome::Success);
558+
BOOST_CHECK(sparseRejected.frame.getGenericTracks().empty());
559+
}
560+
516561
} // namespace
517562

563+
BOOST_AUTO_TEST_CASE(CylinderRoadMinimumCountsHitLayers)
564+
{
565+
const auto params = makeItsParams();
566+
checkMinimumHitLayers<o2::detectors::DetID::ITS, ITSNLayers>(
567+
SurfaceKind::Cylinder, params, buildItsHelixChainClusters(params.LayerRadii, Bz, 1.f, 0.4f, 0.3f));
568+
}
569+
570+
BOOST_AUTO_TEST_CASE(DiskRoadMinimumCountsHitLayers)
571+
{
572+
const auto params = makeMftParams();
573+
checkMinimumHitLayers<o2::detectors::DetID::MFT, MFTNLayers>(
574+
SurfaceKind::Disk, params, buildMftChainClusters(params, Bz, MFTNLayers - 1));
575+
}
576+
518577
BOOST_AUTO_TEST_CASE(CombinedLoadingBackfillsOneGlobalWorkspace)
519578
{
520579
// TrackerTraits::findRoads() unconditionally touches the global

Detectors/ITSMFT/common/tracking/test/testComputeLayerCellsOrchestration.cxx

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -460,11 +460,9 @@ int findCellIndex(const TopologyView& topology, int inner, int middle, int outer
460460
Tracklet candidateTracklet(const GlobalMeasurement& first, const GlobalMeasurement& second,
461461
const o2::its::TimeEstBC& timestamp)
462462
{
463-
const float deltaR = first.radius - second.radius;
464-
const float deltaZ = first.z - second.z;
465-
const float tanLambda = deltaR * deltaR > o2::constants::math::Almost0
466-
? deltaZ / deltaR
467-
: std::copysign(o2::constants::math::VeryBig, deltaZ);
463+
const float transverseChord = std::hypot(second.x - first.x, second.y - first.y);
464+
BOOST_REQUIRE_GT(transverseChord, 1.e-6f);
465+
const float tanLambda = (second.z - first.z) / transverseChord;
468466
const float phi = std::atan2(first.y - second.y, first.x - second.x);
469467
return {0, 0, tanLambda, phi, timestamp};
470468
}
@@ -960,15 +958,15 @@ BOOST_AUTO_TEST_CASE(DiskCellRejectsKinkBeyondNominalScatteringTolerance)
960958
rig.params[0].TrackletMinPt = 0.3f;
961959
rig.establishLayout();
962960

963-
// This kinked triplet used to be the threading/repeated-call fixture.
964-
// Its dip-angle change exceeds the tolerance with nominal MFT material.
961+
// Keep the dip-angle change beyond the tolerance with 0.0084 X/X0
962+
// per MFT surface, so this remains an angular-rejection test.
965963
const std::array<GlobalMeasurement, 3> clusters{makeGlobalCluster(1.0f, 0.5f, -0.4f, 0),
966964
makeGlobalCluster(1.3f, 0.62f, -0.6f, 0),
967-
makeGlobalCluster(1.7f, 0.78f, -0.9f, 0)};
965+
makeGlobalCluster(1.7f, 0.78f, -1.0f, 0)};
968966
loadCandidateClusters(rig, clusters,
969967
{makeDiskHit(-0.4f, 1.0f, 0.5f),
970968
makeDiskHit(-0.6f, 1.3f, 0.62f),
971-
makeDiskHit(-0.9f, 1.7f, 0.78f)});
969+
makeDiskHit(-1.0f, 1.7f, 0.78f)});
972970
auto view = prepare(rig);
973971
const auto topology = topologyView(rig);
974972
const int cellIndex = findCellIndex(topology, 0, 1, 2);

Detectors/ITSMFT/common/tracking/test/testComputeLayerTrackletsOrchestration.cxx

Lines changed: 58 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -485,16 +485,66 @@ BOOST_AUTO_TEST_CASE(DiskOnePassAndTwoPassProduceIdenticalTracklets)
485485
SurfaceKind::Disk, clusters, 1);
486486
const auto parallel = runFixture<MFTNLayers>(o2::detectors::DetID::MFT, SurfaceKind::Disk,
487487
SurfaceKind::Disk, clusters, 4);
488-
const float sourceRadius = o2::gpu::CAMath::Hypot(1.f, 0.5f);
489-
const float targetRadius = o2::gpu::CAMath::Hypot(targetX, targetY);
490-
const float expectedTanLambda = (fromZ - toZ) / (sourceRadius - targetRadius);
488+
const float transverseChord = std::hypot(targetX - 1.f, targetY - 0.5f);
489+
const float expectedTanLambda = (toZ - fromZ) / transverseChord;
491490
const float expectedPhi = o2::gpu::CAMath::ATan2(0.5f - targetY, 1.f - targetX);
492491
checkExactTracklet(serial, expectedTanLambda, expectedPhi);
493492
checkExactTracklet(parallel, expectedTanLambda, expectedPhi);
494493
checkSame(serial, parallel);
495494
}
496495

497-
BOOST_AUTO_TEST_CASE(DiskSameRadiusClustersProduceInfiniteSlopeTracklet)
496+
BOOST_AUTO_TEST_CASE(CylinderDisplacedChordPreservesBothLongitudinalSigns)
497+
{
498+
// A line parallel to x, displaced by y=1: its transverse length is exactly
499+
// one, while the difference of beam-axis radii is smaller than one.
500+
for (const float sign : {-1.f, 1.f}) {
501+
std::vector<DecodedCluster> clusters;
502+
for (int layer = 0; layer < 2; ++layer) {
503+
const float x = 3.f + layer;
504+
const float z = sign * 0.25f * (layer + 1);
505+
auto cluster = cylinderCluster(x, z, layer);
506+
cluster.global.y = 1.f;
507+
cluster.cylinderFrame.u = 1.f;
508+
clusters.push_back(cluster);
509+
}
510+
const auto widenSearch = [](ReferenceTrackingParameters& p) {
511+
p.NSigmaCut = 100.f;
512+
p.PVres = 10.f; // Widen the independent azimuthal search gate too.
513+
};
514+
const auto serial = runFixture<ITSNLayers>(o2::detectors::DetID::ITS, SurfaceKind::Cylinder,
515+
SurfaceKind::Cylinder, clusters, 1, widenSearch);
516+
const auto parallel = runFixture<ITSNLayers>(o2::detectors::DetID::ITS, SurfaceKind::Cylinder,
517+
SurfaceKind::Cylinder, clusters, 4, widenSearch);
518+
const float expectedPhi = o2::gpu::CAMath::ATan2(0.f, -1.f);
519+
checkExactTracklet(serial, sign * 0.25f, expectedPhi);
520+
checkExactTracklet(parallel, sign * 0.25f, expectedPhi);
521+
checkSame(serial, parallel);
522+
}
523+
}
524+
525+
BOOST_AUTO_TEST_CASE(DiskEqualRadiusDistinctHitsHaveFiniteSignedSlope)
526+
{
527+
const float fromZ = detail::mftLayerZ(0);
528+
const float toZ = detail::mftLayerZ(1);
529+
// Same radius, different positions, with a transverse chord of exactly one.
530+
const std::vector<DecodedCluster> clusters{
531+
diskCluster(1.f, 0.5f, fromZ, 0),
532+
diskCluster(1.f, -0.5f, toZ, 1)};
533+
const auto widenSearch = [](ReferenceTrackingParameters& p) {
534+
p.NSigmaCut = 100.f;
535+
p.PVres = 10.f;
536+
};
537+
const auto serial = runFixture<MFTNLayers>(o2::detectors::DetID::MFT, SurfaceKind::Disk,
538+
SurfaceKind::Disk, clusters, 1, widenSearch);
539+
const auto parallel = runFixture<MFTNLayers>(o2::detectors::DetID::MFT, SurfaceKind::Disk,
540+
SurfaceKind::Disk, clusters, 4, widenSearch);
541+
const float expectedPhi = o2::gpu::CAMath::ATan2(1.f, 0.f);
542+
checkExactTracklet(serial, toZ - fromZ, expectedPhi);
543+
checkExactTracklet(parallel, toZ - fromZ, expectedPhi);
544+
checkSame(serial, parallel);
545+
}
546+
547+
BOOST_AUTO_TEST_CASE(DiskZeroTransverseChordRejectsTracklet)
498548
{
499549
const float fromZ = detail::mftLayerZ(0);
500550
const float toZ = detail::mftLayerZ(1);
@@ -506,10 +556,8 @@ BOOST_AUTO_TEST_CASE(DiskSameRadiusClustersProduceInfiniteSlopeTracklet)
506556
SurfaceKind::Disk, clusters, 1, widenSearch);
507557
const auto parallel = runFixture<MFTNLayers>(o2::detectors::DetID::MFT, SurfaceKind::Disk,
508558
SurfaceKind::Disk, clusters, 4, widenSearch);
509-
const float expectedTanLambda = std::copysign(o2::constants::math::VeryBig, fromZ - toZ);
510-
const float expectedPhi = o2::gpu::CAMath::ATan2(0.f, 0.f);
511-
checkExactTracklet(serial, expectedTanLambda, expectedPhi);
512-
checkExactTracklet(parallel, expectedTanLambda, expectedPhi);
559+
BOOST_CHECK(serial.tracklets.empty());
560+
BOOST_CHECK(parallel.tracklets.empty());
513561
checkSame(serial, parallel);
514562
}
515563

@@ -694,9 +742,8 @@ BOOST_AUTO_TEST_CASE(MftIdentityLayoutTrackletsSpanMultipleAdjacentEdgesInOrder)
694742
BOOST_CHECK_EQUAL(tracklet.secondClusterIndex, 0);
695743
const auto& source = clusters[from].global;
696744
const auto& target = clusters[to].global;
697-
const float sourceRadius = o2::gpu::CAMath::Hypot(source.x, source.y);
698-
const float targetRadius = o2::gpu::CAMath::Hypot(target.x, target.y);
699-
const float expectedTanLambda = (source.z - target.z) / (sourceRadius - targetRadius);
745+
const float transverseChord = std::hypot(target.x - source.x, target.y - source.y);
746+
const float expectedTanLambda = (target.z - source.z) / transverseChord;
700747
BOOST_CHECK_EQUAL(tracklet.tanLambda, expectedTanLambda);
701748
BOOST_CHECK_EQUAL_COLLECTIONS(snapshot.allLookups[id].begin(), snapshot.allLookups[id].end(), expectedLookup.begin(), expectedLookup.end());
702749
sawEdge01 |= (from == 0 && to == 1);

Detectors/ITSMFT/common/tracking/test/testITSMFTSurfaceSpecProjection.cxx

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -165,20 +165,21 @@ BOOST_AUTO_TEST_CASE(MFTMaterialMatchesNominalDefaultsAndRadlRhoFormula)
165165
}
166166
}
167167

168-
BOOST_AUTO_TEST_CASE(MFTSensorPairsShareThePhysicalDiskBudget)
168+
BOOST_AUTO_TEST_CASE(MFTSurfacesUseTheNominalCAPrescription)
169169
{
170+
constexpr float expectedSurfaceX0 = 0.0084f;
170171
float totalX0 = 0.f;
171172
float totalArealDensity = 0.f;
172-
for (int disk = 0; disk < MFTDisks; ++disk) {
173-
const auto& front = kMFTStaticSurfaceCatalog[2 * disk].material;
174-
const auto& back = kMFTStaticSurfaceCatalog[2 * disk + 1].material;
175-
BOOST_CHECK_CLOSE(front.xOverX0 + back.xOverX0, kMFTNominalRadLength / MFTDisks, 1.e-4f);
176-
totalX0 += front.xOverX0 + back.xOverX0;
177-
totalArealDensity += front.arealDensityGPerCm2 + back.arealDensityGPerCm2;
173+
for (const auto& surface : kMFTStaticSurfaceCatalog) {
174+
BOOST_CHECK_CLOSE(surface.material.xOverX0, expectedSurfaceX0, 1.e-4f);
175+
BOOST_CHECK_CLOSE(surface.material.arealDensityGPerCm2,
176+
expectedSurfaceX0 * o2::its::constants::Radl * o2::its::constants::Rho, 1.e-4f);
177+
totalX0 += surface.material.xOverX0;
178+
totalArealDensity += surface.material.arealDensityGPerCm2;
178179
}
179-
BOOST_CHECK_CLOSE(totalX0, kMFTNominalRadLength, 1.e-4f);
180+
BOOST_CHECK_CLOSE(totalX0, 0.084f, 1.e-4f);
180181
BOOST_CHECK_CLOSE(totalArealDensity,
181-
kMFTNominalRadLength * o2::its::constants::Radl * o2::its::constants::Rho, 1.e-4f);
182+
0.084f * o2::its::constants::Radl * o2::its::constants::Rho, 1.e-4f);
182183
}
183184

184185
BOOST_AUTO_TEST_CASE(ITSProjectionPreservesEveryFieldBitExactly)

Detectors/ITSMFT/common/tracking/test/testPropagator.cxx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -868,7 +868,7 @@ BOOST_AUTO_TEST_CASE(RefitDriverSkipsHoleSlots)
868868
BOOST_CHECK_EQUAL(acceptedHitCount, 1u);
869869
}
870870

871-
BOOST_AUTO_TEST_CASE(FullMFTRefitLegUsesOneDetectorMaterialBudget)
871+
BOOST_AUTO_TEST_CASE(FullMFTRefitLegUsesNominalMaterialAtEverySurface)
872872
{
873873
const SurfaceCatalogView catalog{kMFTStaticSurfaceCatalog.data(), MFTNLayers};
874874
for (const auto direction : {material::MaterialTraversalDirection::AlongMomentum,
@@ -887,7 +887,8 @@ BOOST_AUTO_TEST_CASE(FullMFTRefitLegUsesOneDetectorMaterialBudget)
887887
const float momentumScale = std::sqrt(1.f + tanl * tanl);
888888
float expectedMomentum = momentumScale / std::abs(state.parameters[4]);
889889
const float initialMomentum = expectedMomentum;
890-
const float pathX0 = kMFTNominalRadLength / MFTNLayers * momentumScale / std::abs(tanl);
890+
constexpr float expectedSurfaceX0 = 0.0084f;
891+
const float pathX0 = expectedSurfaceX0 * momentumScale / std::abs(tanl);
891892
const material::IntegratedMaterialBudget expectedMaterial{
892893
pathX0, pathX0 * o2::its::constants::Radl * o2::its::constants::Rho};
893894
std::array<detail::RefitMeasurementSlot, MFTNLayers> slots{};

0 commit comments

Comments
 (0)