Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,10 @@ inline constexpr std::array<float, MFTNLayers> kMFTLookupRMax{
constexpr std::array<float, MFTNLayers> makeNominalMFTLayerX0()
{
std::array<float, MFTNLayers> 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<float>(MFTNLayers);
value = kMFTNominalRadLength / static_cast<float>(MFTDisks);
}
return values;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
};
Expand Down
17 changes: 10 additions & 7 deletions Detectors/ITSMFT/common/tracking/src/TrackerTraits.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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);
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -309,7 +309,7 @@ struct StandaloneRun {

StandaloneRun(o2::detectors::DetID::ID det, SurfaceKind kind,
const TrackingParameters& singleParams, const std::vector<DecodedCluster>& decoded,
int rofLength = 40)
int rofLength = 40, LayerMask holeLayers = {})
: params{singleParams}
{
const auto orderedSurfaces = ordered(0, NLayers);
Expand All @@ -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());
Expand Down Expand Up @@ -513,8 +513,67 @@ CombinedTrackingComposer makeComposer(const TrackingParameters& itsParams, const
return CombinedTrackingComposer{std::vector<TrackingParameters>{itsParams}, std::vector<TrackingParameters>{mftParams}};
}

template <o2::detectors::DetID::ID DetId, int NLayers>
void checkMinimumHitLayers(SurfaceKind kind, TrackingParameters params, std::vector<DecodedCluster> clusters)
{
ensureTrivialMagneticFieldIsSet();
BOOST_REQUIRE_EQUAL(clusters.size(), static_cast<size_t>(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<DetId, NLayers> 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<DetId, NLayers> 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<DetId, NLayers> 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<DetId, NLayers> 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<o2::detectors::DetID::ITS, ITSNLayers>(
SurfaceKind::Cylinder, params, buildItsHelixChainClusters(params.LayerRadii, Bz, 1.f, 0.4f, 0.3f));
}

BOOST_AUTO_TEST_CASE(DiskRoadMinimumCountsHitLayers)
{
const auto params = makeMftParams();
checkMinimumHitLayers<o2::detectors::DetID::MFT, MFTNLayers>(
SurfaceKind::Disk, params, buildMftChainClusters(params, Bz, MFTNLayers - 1));
}

BOOST_AUTO_TEST_CASE(CombinedLoadingBackfillsOneGlobalWorkspace)
{
// TrackerTraits::findRoads() unconditionally touches the global
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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};
}
Expand Down Expand Up @@ -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<GlobalMeasurement, 3> 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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -485,16 +485,66 @@ BOOST_AUTO_TEST_CASE(DiskOnePassAndTwoPassProduceIdenticalTracklets)
SurfaceKind::Disk, clusters, 1);
const auto parallel = runFixture<MFTNLayers>(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<DecodedCluster> 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<ITSNLayers>(o2::detectors::DetID::ITS, SurfaceKind::Cylinder,
SurfaceKind::Cylinder, clusters, 1, widenSearch);
const auto parallel = runFixture<ITSNLayers>(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<DecodedCluster> 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<MFTNLayers>(o2::detectors::DetID::MFT, SurfaceKind::Disk,
SurfaceKind::Disk, clusters, 1, widenSearch);
const auto parallel = runFixture<MFTNLayers>(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);
Expand All @@ -506,10 +556,8 @@ BOOST_AUTO_TEST_CASE(DiskSameRadiusClustersProduceInfiniteSlopeTracklet)
SurfaceKind::Disk, clusters, 1, widenSearch);
const auto parallel = runFixture<MFTNLayers>(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);
}

Expand Down Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
5 changes: 3 additions & 2 deletions Detectors/ITSMFT/common/tracking/test/testPropagator.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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<detail::RefitMeasurementSlot, MFTNLayers> slots{};
Expand Down
Loading