Skip to content

Commit dda2bac

Browse files
committed
Enable track finding
1 parent ae95a2f commit dda2bac

1 file changed

Lines changed: 273 additions & 18 deletions

File tree

  • Detectors/Upgrades/ALICE3/GlobalReconstruction/reconstruction/src

‎Detectors/Upgrades/ALICE3/GlobalReconstruction/reconstruction/src/TrackerACTS.cxx‎

Lines changed: 273 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,21 @@
3434
#include <Acts/Utilities/RangeXD.hpp>
3535
#include <Acts/Definitions/TrackParametrization.hpp>
3636
#include <Acts/Seeding/EstimateTrackParamsFromSeed.hpp>
37+
#include <Acts/EventData/BoundTrackParameters.hpp>
38+
#include <Acts/EventData/ParticleHypothesis.hpp>
39+
#include <Acts/EventData/SourceLink.hpp>
40+
#include <Acts/EventData/TrackContainer.hpp>
41+
#include <Acts/EventData/VectorMultiTrajectory.hpp>
42+
#include <Acts/EventData/VectorTrackContainer.hpp>
43+
#include <Acts/MagneticField/ConstantBField.hpp>
44+
#include <Acts/MagneticField/MagneticFieldContext.hpp>
45+
#include <Acts/Propagator/DirectNavigator.hpp>
46+
#include <Acts/Propagator/EigenStepper.hpp>
47+
#include <Acts/Propagator/Propagator.hpp>
48+
#include <Acts/TrackFitting/GainMatrixSmoother.hpp>
49+
#include <Acts/TrackFitting/GainMatrixUpdater.hpp>
50+
#include <Acts/TrackFitting/KalmanFitter.hpp>
51+
#include <Acts/Utilities/CalibrationContext.hpp>
3752

3853
namespace o2::trk
3954
{
@@ -135,11 +150,11 @@ void TrackerACTS<nLayers>::buildSpacePoints(int rof)
135150
template <int nLayers>
136151
void TrackerACTS<nLayers>::createSeeds()
137152
{
153+
mSeeds.clear();
138154
if (mSpacePoints.empty()) {
139155
LOGF(info, "No space points available for seeding");
140156
return;
141157
}
142-
mSeeds.clear();
143158

144159
// Backend adaptor that exposes mSpacePoints to Acts::SpacePointContainer
145160
struct SpacePointBackend {
@@ -327,31 +342,271 @@ bool TrackerACTS<nLayers>::estimateTrackParams(const SeedACTS& seed, o2::its::Tr
327342
LOG(info) << "Estimated track parameters";
328343
return true;
329344
}
330-
331345
template <int nLayers>
332346
void TrackerACTS<nLayers>::findTracks()
333347
{
334-
return; // For now we only create seeds, track finding and fitting will be implemented in the next iterations
335-
int nTracks = 0;
348+
if (mSeeds.empty()) {
349+
return;
350+
}
351+
352+
auto& actsGeometryManager = o2::acts::TrackingGeometryManager::instance();
353+
const auto& geometryContext = actsGeometryManager.getNominalContext();
354+
355+
Acts::MagneticFieldContext magneticFieldContext;
356+
Acts::CalibrationContext calibrationContext;
357+
358+
// Initial implementation with a constant magnetic field
359+
auto magneticField = std::make_shared<Acts::ConstantBField>(
360+
Acts::Vector3{0., 0., mBz * Acts::UnitConstants::T});
361+
362+
using Propagator = Acts::Propagator<Acts::EigenStepper<>, Acts::DirectNavigator>;
363+
using Trajectory = Acts::VectorMultiTrajectory;
364+
using TrackContainer = Acts::TrackContainer<Acts::VectorTrackContainer, Trajectory>;
365+
366+
Propagator propagator{
367+
Acts::EigenStepper<>{magneticField},
368+
Acts::DirectNavigator{}};
369+
370+
Acts::KalmanFitter<Propagator, Trajectory> fitter{std::move(propagator)};
371+
372+
// Each measurement is defined in its ACTS surface frame
373+
struct HitMeasurement {
374+
const Acts::Surface* surface{nullptr};
375+
Acts::Vector2 local = Acts::Vector2::Zero();
376+
Acts::SquareMatrix<2> covariance = Acts::SquareMatrix<2>::Zero();
377+
};
378+
379+
struct HitSourceLink {
380+
const HitMeasurement* hit{nullptr};
381+
};
382+
383+
struct SurfaceAccessor {
384+
const Acts::Surface* getSurface(const Acts::SourceLink& sourceLink) const
385+
{
386+
return sourceLink.get<HitSourceLink>().hit->surface;
387+
}
388+
};
389+
390+
struct MeasurementCalibrator {
391+
void calibrate(const Acts::GeometryContext&,
392+
const Acts::CalibrationContext&,
393+
const Acts::SourceLink& sourceLink,
394+
Trajectory::TrackStateProxy trackState) const
395+
{
396+
const auto& hit = *sourceLink.get<HitSourceLink>().hit;
397+
398+
trackState.setUncalibratedSourceLink(Acts::SourceLink{sourceLink});
399+
trackState.allocateCalibrated(2);
400+
trackState.calibrated<2>() = hit.local;
401+
trackState.calibratedCovariance<2>() = hit.covariance;
402+
403+
trackState.setProjectorSubspaceIndices(
404+
std::array<std::uint8_t, 2>{
405+
static_cast<std::uint8_t>(Acts::eBoundLoc0),
406+
static_cast<std::uint8_t>(Acts::eBoundLoc1)});
407+
}
408+
};
409+
410+
SurfaceAccessor surfaceAccessor;
411+
MeasurementCalibrator calibrator;
412+
Acts::GainMatrixUpdater updater;
413+
Acts::GainMatrixSmoother smoother;
414+
415+
Acts::KalmanFitterExtensions<Trajectory> extensions;
416+
417+
extensions.surfaceAccessor.connect<&SurfaceAccessor::getSurface>(&surfaceAccessor);
418+
extensions.calibrator.connect<&MeasurementCalibrator::calibrate>(&calibrator);
419+
extensions.updater.connect<&Acts::GainMatrixUpdater::operator()<Trajectory>>(&updater);
420+
extensions.smoother.connect<&Acts::GainMatrixSmoother::operator()<Trajectory>>(&smoother);
421+
422+
Acts::PropagatorPlainOptions propagatorOptions{
423+
geometryContext, magneticFieldContext};
424+
425+
Acts::KalmanFitterOptions<Trajectory> fitterOptions{
426+
geometryContext,
427+
magneticFieldContext,
428+
calibrationContext,
429+
extensions,
430+
propagatorOptions,
431+
nullptr,
432+
false, // Multiple scattering: disable for the first test
433+
false // Energy loss: disable for the first test
434+
};
435+
436+
TrackContainer tracks{
437+
std::make_shared<Acts::VectorTrackContainer>(),
438+
std::make_shared<Trajectory>()};
439+
440+
int nFitted = 0;
441+
int nFailed = 0;
442+
int nInvalid = 0;
336443

337444
for (const auto& seed : mSeeds) {
338-
o2::its::TrackITSExt track;
445+
const std::array<const SpacePoint*, 3> spacePoints{
446+
seed.bottom, seed.middle, seed.top};
339447

340-
LOG(info) << "Estimating track parameters for seed with quality (pT) = " << seed.quality;
341-
if (!estimateTrackParams(seed, track)) {
448+
if (!spacePoints[0] || !spacePoints[1] || !spacePoints[2]) {
449+
++nInvalid;
342450
continue;
343451
}
344452

345-
// Add track to TimeFrame
346-
const int rof = seed.middle->rof;
347-
if (mTimeFrame && rof >= 0 && rof < mTimeFrame->getNrof(0)) {
348-
LOG(info) << "Adding track to ROF " << rof;
349-
auto& tracks = mTimeFrame->getTracks();
350-
// tracks.emplace_back(track);
351-
++nTracks;
453+
if (!spacePoints[0]->surface || !spacePoints[1]->surface ||
454+
!spacePoints[2]->surface) {
455+
++nInvalid;
456+
continue;
352457
}
458+
459+
// The direct fitter expects at most one measurement per surface
460+
if (spacePoints[0]->surface == spacePoints[1]->surface ||
461+
spacePoints[0]->surface == spacePoints[2]->surface ||
462+
spacePoints[1]->surface == spacePoints[2]->surface) {
463+
++nInvalid;
464+
continue;
465+
}
466+
467+
Acts::Vector3 pos0{
468+
spacePoints[0]->x, spacePoints[0]->y, spacePoints[0]->z};
469+
Acts::Vector3 pos1{
470+
spacePoints[1]->x, spacePoints[1]->y, spacePoints[1]->z};
471+
Acts::Vector3 pos2{
472+
spacePoints[2]->x, spacePoints[2]->y, spacePoints[2]->z};
473+
474+
// Estimate initial free parameters from the triplet
475+
Acts::FreeVector freeParams;
476+
477+
try {
478+
freeParams = Acts::estimateTrackParamsFromSeed(
479+
pos0, 0., pos1, pos2,
480+
Acts::Vector3{0., 0., mBz * Acts::UnitConstants::T});
481+
} catch (const std::exception& e) {
482+
LOG(debug) << "ACTS seed parameter estimation failed: " << e.what();
483+
++nInvalid;
484+
continue;
485+
}
486+
487+
const double qOverP = freeParams[Acts::eFreeQOverP];
488+
489+
if (!std::isfinite(qOverP) || std::abs(qOverP) < 1.e-12) {
490+
++nInvalid;
491+
continue;
492+
}
493+
494+
Acts::Vector3 direction{
495+
freeParams[Acts::eFreeDir0],
496+
freeParams[Acts::eFreeDir1],
497+
freeParams[Acts::eFreeDir2]};
498+
499+
if (!direction.allFinite() || direction.norm() < 1.e-12) {
500+
++nInvalid;
501+
continue;
502+
}
503+
504+
direction.normalize();
505+
506+
// Convert the global hit positions into local ACTS measurements
507+
std::array<HitMeasurement, 3> measurements;
508+
std::vector<Acts::SourceLink> sourceLinks;
509+
std::vector<const Acts::Surface*> surfaces;
510+
511+
sourceLinks.reserve(3);
512+
surfaces.reserve(3);
513+
514+
bool validMeasurements = true;
515+
516+
for (int i = 0; i < 3; ++i) {
517+
const auto* sp = spacePoints[i];
518+
const auto* surface = sp->surface;
519+
520+
Acts::Vector3 position{sp->x, sp->y, sp->z};
521+
522+
// Allow small differences between chip positions and ideal surfaces
523+
auto localResult = surface->globalToLocal(
524+
geometryContext, position, direction,
525+
1. * Acts::UnitConstants::mm);
526+
527+
if (!localResult.ok()) {
528+
validMeasurements = false;
529+
break;
530+
}
531+
532+
auto& measurement = measurements[i];
533+
measurement.surface = surface;
534+
measurement.local = localResult.value();
535+
536+
// Temporary isotropic 100 um resolution
537+
const double sigma = 100. * Acts::UnitConstants::um;
538+
539+
measurement.covariance.setZero();
540+
measurement.covariance(0, 0) = sigma * sigma;
541+
measurement.covariance(1, 1) = sigma * sigma;
542+
543+
sourceLinks.emplace_back(HitSourceLink{&measurement});
544+
surfaces.push_back(surface);
545+
}
546+
547+
if (!validMeasurements) {
548+
++nInvalid;
549+
continue;
550+
}
551+
552+
// Start from the first seed space point
553+
Acts::Vector4 position4;
554+
position4 << pos0.x(), pos0.y(), pos0.z(), 0.;
555+
556+
// Initial covariance, deliberately loose
557+
Acts::BoundMatrix covariance = Acts::BoundMatrix::Zero();
558+
559+
covariance(Acts::eBoundLoc0, Acts::eBoundLoc0) =
560+
std::pow(1. * Acts::UnitConstants::mm, 2);
561+
covariance(Acts::eBoundLoc1, Acts::eBoundLoc1) =
562+
std::pow(1. * Acts::UnitConstants::mm, 2);
563+
covariance(Acts::eBoundPhi, Acts::eBoundPhi) = 0.1 * 0.1;
564+
covariance(Acts::eBoundTheta, Acts::eBoundTheta) = 0.1 * 0.1;
565+
covariance(Acts::eBoundQOverP, Acts::eBoundQOverP) =
566+
std::pow(0.5 * qOverP, 2);
567+
covariance(Acts::eBoundTime, Acts::eBoundTime) =
568+
std::pow(1. * Acts::UnitConstants::ns, 2);
569+
570+
auto initialParameters = Acts::BoundTrackParameters::createCurvilinear(
571+
position4,
572+
direction,
573+
qOverP,
574+
covariance,
575+
Acts::ParticleHypothesis::pion());
576+
577+
auto result = fitter.fit(
578+
sourceLinks.begin(),
579+
sourceLinks.end(),
580+
initialParameters,
581+
fitterOptions,
582+
surfaces,
583+
tracks);
584+
585+
if (!result.ok()) {
586+
LOG(debug) << "ACTS Kalman fit failed: " << result.error().message();
587+
++nFailed;
588+
continue;
589+
}
590+
591+
const auto track = result.value();
592+
593+
if (track.nMeasurements() < 3) {
594+
++nFailed;
595+
continue;
596+
}
597+
598+
++nFitted;
599+
600+
LOG(debug) << "ACTS fitted seed: "
601+
<< "nMeasurements=" << track.nMeasurements()
602+
<< " chi2=" << track.chi2()
603+
<< " nDoF=" << track.nDoF();
353604
}
354-
LOG(info) << "Created " << nTracks << " tracks from " << mSeeds.size() << " seeds";
605+
606+
LOG(info) << "ACTS fitting: " << nFitted << " fitted, "
607+
<< nFailed << " failed, "
608+
<< nInvalid << " invalid from "
609+
<< mSeeds.size() << " seeds";
355610
}
356611

357612
template <int nLayers>
@@ -446,9 +701,9 @@ void TrackerACTS<nLayers>::clustersToTracks()
446701
StateNames[mCurState]);
447702

448703
// Find tracks
449-
// mCurState = TrackFinding;
450-
// totalTime += evaluateTask([this]() { findTracks(); },
451-
// StateNames[mCurState]);
704+
mCurState = TrackFinding;
705+
totalTime += evaluateTask([this]() { findTracks(); },
706+
StateNames[mCurState]);
452707
}
453708

454709
// MC labeling

0 commit comments

Comments
 (0)