|
34 | 34 | #include <Acts/Utilities/RangeXD.hpp> |
35 | 35 | #include <Acts/Definitions/TrackParametrization.hpp> |
36 | 36 | #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> |
37 | 52 |
|
38 | 53 | namespace o2::trk |
39 | 54 | { |
@@ -135,11 +150,11 @@ void TrackerACTS<nLayers>::buildSpacePoints(int rof) |
135 | 150 | template <int nLayers> |
136 | 151 | void TrackerACTS<nLayers>::createSeeds() |
137 | 152 | { |
| 153 | + mSeeds.clear(); |
138 | 154 | if (mSpacePoints.empty()) { |
139 | 155 | LOGF(info, "No space points available for seeding"); |
140 | 156 | return; |
141 | 157 | } |
142 | | - mSeeds.clear(); |
143 | 158 |
|
144 | 159 | // Backend adaptor that exposes mSpacePoints to Acts::SpacePointContainer |
145 | 160 | struct SpacePointBackend { |
@@ -327,31 +342,271 @@ bool TrackerACTS<nLayers>::estimateTrackParams(const SeedACTS& seed, o2::its::Tr |
327 | 342 | LOG(info) << "Estimated track parameters"; |
328 | 343 | return true; |
329 | 344 | } |
330 | | - |
331 | 345 | template <int nLayers> |
332 | 346 | void TrackerACTS<nLayers>::findTracks() |
333 | 347 | { |
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; |
336 | 443 |
|
337 | 444 | 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}; |
339 | 447 |
|
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; |
342 | 450 | continue; |
343 | 451 | } |
344 | 452 |
|
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; |
352 | 457 | } |
| 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(); |
353 | 604 | } |
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"; |
355 | 610 | } |
356 | 611 |
|
357 | 612 | template <int nLayers> |
@@ -446,9 +701,9 @@ void TrackerACTS<nLayers>::clustersToTracks() |
446 | 701 | StateNames[mCurState]); |
447 | 702 |
|
448 | 703 | // Find tracks |
449 | | - // mCurState = TrackFinding; |
450 | | - // totalTime += evaluateTask([this]() { findTracks(); }, |
451 | | - // StateNames[mCurState]); |
| 704 | + mCurState = TrackFinding; |
| 705 | + totalTime += evaluateTask([this]() { findTracks(); }, |
| 706 | + StateNames[mCurState]); |
452 | 707 | } |
453 | 708 |
|
454 | 709 | // MC labeling |
|
0 commit comments