11#include "Acts/Utilities/Helpers.hpp"
12#include "Acts/Definitions/Tolerance.hpp"
13#include "Acts/Definitions/Units.hpp"
14#include "Acts/Geometry/TrackingGeometry.hpp"
16#include "Acts/Surfaces/LineBounds.hpp"
17#include "Acts/Surfaces/PlaneSurface.hpp"
22#include "GaudiKernel/PhysicalConstants.h"
25 using namespace Acts::UnitLiterals;
27 constexpr bool chargeAgree(
double PtimesQ1,
double PtimesQ2) {
28 return PtimesQ1 * PtimesQ2 > 0;
31 inline double momentumDev(
double PtimesQ1,
double PtimesQ2) {
32 const double denom {std::max(std::abs(PtimesQ1) + std::abs(PtimesQ2), Acts::s_epsilon)};
33 return std::abs(PtimesQ1 - PtimesQ2) /
denom;
39 return std::format(
"{:}, nPrecHits: {:}, nPhiHits: {:}",
MuonR4::printID(seg),
64 return StatusCode::FAILURE;
71 return StatusCode::SUCCESS;
74 Acts::Result<Acts::BoundTrackParameters>
78 const Acts::MagneticFieldContext mfContext =
m_extrapolationTool->getMagneticFieldContext(ctx);
90 if (!isNswSegment(*segment) &&
100 if (isNswSegment(*segment) &&
103 ATH_MSG_VERBOSE(__func__<<
"() "<<__LINE__<<
" - NSW is the best what we have apparently....");
111 <<
" - No reference segment passing seeding quality was found.");
112 return Acts::Result<Acts::BoundTrackParameters>::failure(std::make_error_code(std::errc::invalid_argument));
117 <<
", dir: "<<
Amg::toString(seedDir) <<
" eta " << seedDir.eta() <<
" phi "
118 << (seedDir.phi() /Gaudi::Units::degree) );
123 const Acts::GeometryIdentifier volId =
volumeId(firstSurf);
130 <<
" - Failed to find tracking volume for seed measurement "<<volId);
131 return Acts::Result<Acts::BoundTrackParameters>::failure(std::make_error_code(std::errc::invalid_argument));
133 if (volume->motherVolume() && volume->motherVolume()->isAlignable()) {
134 volume = volume->motherVolume();
137 <<
" - Bounding volume "<<volume->volumeName()
138 <<
", trf: "<<
Amg::toString(volume->localToGlobalTransform(tgContext))
139 <<
", bounds: "<<volume->volumeBounds());
143 if (frontSegment != refSeg) {
145 const Amg::Transform3D toFirstTrf = firstSurf.localToGlobalTransform(tgContext).inverse();
146 const Amg::Vector3D locFrontSegPos = toFirstTrf * frontSegPos;
147 if (!volume->inside(tgContext, frontSegPos)) {
149 <<
" not inside mother volume: "<<volume->volumeName()<<
", "
150 <<
Amg::toString(volume->globalToLocalTransform(tgContext)*frontSegPos)
151 <<
", bounds: "<<volume->volumeBounds()<<
", "
158 const Acts::MultiIntersection firstIsect = firstSurf.intersect(tgContext, seedPos, seedDir,
159 Acts::BoundaryTolerance::Infinite());
160 const Amg::Vector3D locAtFirst = toFirstTrf * firstIsect.at(0).position();
161 if (firstSurf.type() == Acts::Surface::SurfaceType::Straw) {
162 const auto& bounds =
static_cast<const Acts::LineBounds&
>(firstSurf.bounds());
163 using enum Acts::LineBounds::BoundValues;
166 const Amg::Vector3D locStartPos{locFrontSegPos.x(), locFrontSegPos.y(),
167 std::clamp(locAtFirst.z(), -bounds.get(eHalfLengthZ), bounds.get(eHalfLengthZ))};
168 ATH_MSG_VERBOSE(__func__<<
"() "<<__LINE__<<
" - The first surface is a straw "
169 <<bounds<<
", track seed @first: "<<
Amg::toString(locAtFirst)<<
" vs. segment @first: "
171 seedPos = firstSurf.localToGlobalTransform(tgContext) * locStartPos;
172 }
else if (firstSurf.type() == Acts::Surface::SurfaceType::Plane) {
173 if (isNswSegment(*frontSegment)) {
174 seedPos = frontSegPos;
176 ATH_MSG_VERBOSE(__func__<<
"() "<<__LINE__<<
" - The first surface is a straw "
179 ATH_MSG_WARNING(__func__<<
"() "<<__LINE__<<
" - Unexpected surface type "<<firstSurf.type());
180 return Acts::Result<Acts::BoundTrackParameters>::failure(std::make_error_code(std::errc::invalid_argument));
188 ATH_MSG_WARNING(__func__<<
"() "<<__LINE__<<
" - Failed to find boundary surface for tracking volume");
189 return Acts::Result<Acts::BoundTrackParameters>::failure(std::make_error_code(std::errc::invalid_argument));
191 std::shared_ptr<const Acts::Surface> targetSurf{};
196 auto propagateToBoundary = [&](
const Acts::Surface& volBoundary) -> Acts::Result<Amg::Vector3D> {
199 using namespace Acts::PlanarHelper;
200 auto pIsect = intersectPlane(seedPos, seedDir, trf.linear().col(
Amg::z), trf.translation());
202 if (pIsect.pathLength() > Acts::s_epsilon || !pIsect.isValid()) {
204 <<
" is forward "<<pIsect.pathLength()<<
" or invalid "<<(!pIsect.isValid())
205 <<
" within volume "<<volume->inside(tgContext, pIsect.position()));
206 return Acts::Result<Amg::Vector3D>::failure(std::make_error_code(std::errc::invalid_argument));
208 Acts::Result<Amg::Vector2D> locPos = volBoundary.globalToLocal(tgContext, pIsect.position(),
209 Amg::Vector3D::Zero());
211 ATH_MSG_VERBOSE(__func__<<
"() "<<__LINE__<<
" - Intersection is not on surface "<<
213 return Acts::Result<Amg::Vector3D>::failure(std::make_error_code(std::errc::invalid_argument));
215 if (!volBoundary.insideBounds(*locPos)) {
216 ATH_MSG_VERBOSE(__func__<<
"() "<<__LINE__<<
" - Intersection is outside the boundaries: "<<
218 return Acts::Result<Amg::Vector3D>::failure(std::make_error_code(std::errc::invalid_argument));
220 targetSurf = volBoundary.getSharedPtr();
221 return Acts::Result<Amg::Vector3D>::success(pIsect.position());
224 auto pIsect = propagateToBoundary(*boundSurf);
228 if (!pIsect.ok() && volume->isAlignable()) {
229 const Acts::VolumePlacementBase* placement = volume->volumePlacement();
230 for (std::size_t portal = 0; !pIsect.ok() && portal< placement->nPortalPlacements(); ++portal) {
231 pIsect = propagateToBoundary(placement->portalPlacement(portal)->surface());
235 ATH_MSG_WARNING(__func__<<
"() "<<__LINE__<<
" Cannot create valid start parameters from seed "<<seed<<
".");
236 return Acts::Result<Acts::BoundTrackParameters>::failure(std::make_error_code(std::errc::invalid_argument));
241 return Acts::BoundTrackParameters::create(tgContext, targetSurf,
242 fourPos, seedDir, qOverP,
243 Acts::BoundMatrix::Identity(),
244 Acts::ParticleHypothesis::muon());
251 for (std::size_t meas = 0; meas < nMeas; ++ meas) {
261 localToGlobalTransform(tgContext).linear().col(
Amg::x);
270 return Acts::PlanarHelper::intersectPlane(segment.
position(), wireDir,
271 planeNormal, Amg::Vector3D::Zero()).position();
287 <<
", direction: "<<
Amg::toString(dir)<<
" sector projector: " << sector
288 <<
" location: " << Acts::toUnderlying(loc));
293 if (Location::Barrel == loc) {
299 Acts::copySign(1.*
m_endcapDiscZ, projPos[1])).value_or(10. * Gaudi::Units::km);
303 return projPos + lambda * projDir;
322 std::span<const PosMomPair_t> circlePoints)
const {
323 const Acts::MagneticFieldContext mfContext =
m_extrapolationTool->getMagneticFieldContext(ctx);
326 if (circlePoints.size() < 2 || circlePoints.size() > 3){
327 ATH_MSG_WARNING(__func__<<
"() "<<__LINE__<<
" - Invalid number of circle points passed: "<<circlePoints.size());
328 constexpr double straightLine = 10._TeV;
331 return circlePoints.size() == 3 ?
estimateQtimesP(planeNorm, circlePoints[0], circlePoints[1], circlePoints[2], magField)
332 :
estimateQtimesP(planeNorm, circlePoints[0], circlePoints[1], magField);
335 const PosMomPair_t& p1,
336 const PosMomPair_t& p2,
337 const PosMomPair_t& p3,
354 std::vector<Estimate> estimates{};
355 const double weightNorm {force12.mag() + force23.mag()};
357 estimates.emplace_back(
getPtimesQ(force12, p2.second - p1.second), force12.mag()/weightNorm, 0.);
358 estimates.emplace_back(
getPtimesQ(force23, p3.second - p2.second), force23.mag()/weightNorm, 0.);
360 const double weight13 {force13.mag()/weightNorm};
361 estimates.emplace_back(
getPtimesQ(force13, p3.second - p1.second), weight13, 0.);
364 estimates.emplace_back(
getPtimesQ(force13, t23 - t12), weight13, 0.);
366 for (std::size_t i {0}; i < estimates.size(); ++i) {
367 Estimate& est1 {estimates[i]};
368 for (std::size_t j {i+1}; j < estimates.size(); ++j) {
369 Estimate& est2 {estimates[j]};
371 double w {std::min(est1.weight, est2.weight)};
372 double chargeScore {chargeAgree(est1.PtimesQ, est2.PtimesQ) ? 1. : -1.};
373 double pDevPenalty {momentumDev(est1.PtimesQ, est2.PtimesQ)};
375 est1.score += w * (chargeScore - pDevPenalty);
376 est2.score += w * (chargeScore - pDevPenalty);
379 if (msgLvl(MSG::VERBOSE)) {
380 std::vector<std::string> names {
"Pair01",
"Pair12",
"Pair02Seg",
"Pair02Pos"};
381 for (
const auto& [i, est] : Acts::enumerate(estimates)) {
382 ATH_MSG_VERBOSE(__func__<<
"() Estimate "<<names[i]<<
": PtimesQ: "<<est.PtimesQ*1e-3
383 <<
", weight: "<<est.weight<<
", score: "<<est.score);
388 const Estimate& bestEstimate {*std::ranges::max_element(estimates,
389 std::ranges::less{}, &Estimate::score)};
390 const double charge {std::copysign(1., bestEstimate.PtimesQ)};
392 double totalSum {0.}, totalWeight {0.};
393 for (
const Estimate& est : estimates) {
394 if (chargeAgree(est.PtimesQ,
charge)) {
395 totalSum += est.PtimesQ * est.weight;
396 totalWeight += est.weight;
399 assert(totalWeight > Acts::s_epsilon);
400 return totalSum / totalWeight;
403 const PosMomPair_t& p1,
404 const PosMomPair_t& p2,
408 p2.second - p1.second);
411 const PosMomPair_t& point2,
414 const auto& [pos1, dir1] = point1;
415 const auto& [pos2, dir2] = point2;
422 const Amg::Vector3D extPos {(1. - fieldStep) * pos1 + fieldStep * pos2};
423 const Amg::Vector3D extDir {((1. - fieldStep) * dir1 + fieldStep * dir2).
unit()};
425 fieldCache.
getField(extPos.data(), locField.data());
426 const Amg::Vector3D locForce {locField.dot(planeNorm) * extDir.cross(planeNorm)};
427 accumForce += locForce;
431 <<
" --> local |B|: "<<locField.mag()*1e3 <<
" [T]"<<
", |Bnorm|: "<<locField.dot(planeNorm)*1e3
432 <<
" [T], local |v x Bnorm|: "<<locForce.mag()*1e3<<
" [T].");
436 return accumForce * dS;
440 const double PtimesQ {0.3 * Gaudi::Units::GeV * forceIntegral.mag2() / deltaDir.dot(forceIntegral)};
442 ATH_MSG_VERBOSE(__func__<<
"() "<<__LINE__<<
" - estimateQtimesP() force integral: "<<forceIntegral.mag()<<
" [T*m], deltaDir: "
443 <<deltaDir.mag()<<
", cos: "<<deltaDir.dot(forceIntegral)/ (deltaDir.mag() * forceIntegral.mag())
444 <<
", PtimesQ: "<<PtimesQ/Gaudi::Units::GeV <<
" [GeV].");
453 double deltaPhiAcc {0.};
454 std::optional<double> centralPhi {};
455 unsigned nSegsWithPhi{0};
457 if (segment->nPhiLayers() > 0) {
458 if (!centralPhi) centralPhi = segment->position().phi();
463 const double circPhi {nSegsWithPhi > 0
465 : seed.sector().phi()};
467 std::array<const xAOD::MuonSegment*, 3> segmentsToUse{};
476 segmentsToUse[0] = segment;
481 segmentsToUse[1] = segment;
486 segmentsToUse[2] = segment;
493 unsigned nSegments = std::ranges::count_if(segmentsToUse,
499 auto missingSeg = std::ranges::find(segmentsToUse,
nullptr);
505 assert(missingSeg != segmentsToUse.end());
506 *missingSeg = segment;
508 if (nSegments == 3) {
511 missingSeg = std::ranges::find(segmentsToUse,
nullptr);
514 if (!seg1 || !seg2) {
515 return seg1 !=
nullptr;
520 const Amg::Vector3D planeNorm {Acts::makeDirectionFromPhiTheta(circPhi + 90._degree, 90._degree)};
525 return nSegments == 3
526 ?
estimateQtimesP(planeNorm, point(segmentsToUse[0]), point(segmentsToUse[1]), point(segmentsToUse[2]), magField)
527 :
estimateQtimesP(planeNorm, point(segmentsToUse[0]), point(segmentsToUse[1]), magField);
534 const unsigned segSector = segment->
sector();
535 for (
const auto proj : {SectorProjector::leftOverlap,
536 SectorProjector::center,
537 SectorProjector::rightOverlap}) {
542 <<
" is not in sector "<<projSector);
550 std::array<double, 3> coords{Acts::filledArray<double, 3>(0.)};
555 coords[Acts::toUnderlying(
eDetSection)] = Acts::copySign(Acts::toUnderlying(loc), refPoint[1]);
557 coords[Acts::toUnderlying(
ePosOnCylinder)] = refPoint[Location::Barrel == loc];
559 <<
" with "<<coords<<
" to the search tree");
560 outContainer.emplace_back(std::move(coords), segment);
566 rawData.reserve(3*segments.
size());
568 appendSegment(tgContext, segment, Location::Barrel, rawData);
569 appendSegment(tgContext, segment, Location::Endcap, rawData);
571 ATH_MSG_VERBOSE(__func__<<
"() "<<__LINE__<<
" - Create a new tree with "<<rawData.size()<<
" entries. ");
575 std::vector<MsTrackSeed>& outputSeeds)
const {
583 for (
const auto& [coords, seedCandidate] : orderedSegs) {
586 if (!
m_segSelector->passSeedingQuality(ctx, *seedCandidate)){
587 ATH_MSG_VERBOSE(__func__<<
"() "<<__LINE__<<
" - Segment "<<
::print(*seedCandidate)<<
" does not pass the seeding quality.");
591 SearchTree_t::range_t selectRange{};
600 selectRange[Acts::toUnderlying(
eSector)].shrink(coords[Acts::toUnderlying(
eSector)] -0.25,
601 coords[Acts::toUnderlying(
eSector)] +0.25);
606 ATH_MSG_VERBOSE(__func__<<
"() "<<__LINE__<<
" - Search for compatible segments to "<<
::print(*seedCandidate)<<
".");
607 orderedSegs.rangeSearchMapDiscard(selectRange, [&](
608 const SearchTree_t::coordinate_t& ,
611 if (!
m_segSelector->compatibleForTrack(ctx, *seedCandidate, *extendWithMe)) {
616 return extendWithMe->chamberIndex() == onSeed->chamberIndex();
618 if (itr == newSeed.
segments().end()){
622 else if (reducedChi2(**itr) > reducedChi2(*extendWithMe) &&
623 (*itr)->nPhiLayers() <= extendWithMe->
nPhiLayers()) {
626 <<
::print(*extendWithMe)<<
" on seed due to better chi2.");
640 ATH_MSG_VERBOSE(__func__<<
"() "<<__LINE__<<
" - Reject seed with segments in the same station.");
653 +
z * Amg::Vector3D::UnitZ();
656 ATH_MSG_VERBOSE(__func__<<
"() "<<__LINE__<<
" - Add new seed "<<newSeed);
657 trackSeeds.emplace_back(std::move(newSeed));
659 ATH_MSG_VERBOSE(__func__<<
"() "<<__LINE__<<
" - Found in total "<<trackSeeds.size()<<
" before overlap removal");
662 outputSeeds.insert(outputSeeds.end(), std::make_move_iterator(trackSeeds.begin()),
663 std::make_move_iterator(trackSeeds.end()));
664 return StatusCode::SUCCESS;
671 return a.segments().size() > b.segments().size();
674 outputSeeds.reserve(unresolved.size());
675 std::ranges::copy_if(std::move(unresolved), std::back_inserter(outputSeeds),
681 const std::size_t sharedSegs = std::ranges::count_if(testMe.
segments(),
683 return Acts::rangeContainsValue(good.segments(), segInTest);
685 if (sharedSegs == testMe.
segments().size()) {
692 ATH_MSG_VERBOSE(__func__<<
"() "<<__LINE__<<
" - Found in total "<<outputSeeds.size()<<
" after overlap removal");
Scalar mag() const
mag method
const PlainObject unit() const
This is a plugin that makes Eigen look like CLHEP & defines some convenience methods.
#define ATH_CHECK
Evaluate an expression and check for errors.
#define ATH_MSG_VERBOSE(x)
#define ATH_MSG_WARNING(x)
double charge(const T &p)
void print(char *figname, TCanvas *c1)
size_type size() const noexcept
Returns the number of elements in the collection.
Local cache for magnetic field (based on MagFieldServices/AtlasFieldSvcTLS.h).
void getField(const double *ATH_RESTRICT xyz, double *ATH_RESTRICT bxyz, double *ATH_RESTRICT deriv=nullptr)
get B field value at given position xyz[3] is in mm, bxyz[3] is in kT if deriv[9] is given,...
A spectrometer sector forms the envelope of all chambers that are placed in the same MS sector & laye...
const Acts::PlaneSurface & surface() const
Returns the associated surface.
Amg::Vector3D normalDir() const
Returns the vector that is normal to the plane spanned by the expanded sector.
std::int8_t sector() const
Returns the expanded sector number.
bool isNeighbour(const ExpandedSector &other) const
Amg::Vector3D radialDir() const
Returns the vector pointing radially along the sector plane.
void addSegment(const xAOD::MuonSegment *seg)
Append a segment to the seed.
Location location() const
Returns the location of the seed.
ExpandedSector sector() const
Returns the seed's sector.
const std::vector< const xAOD::MuonSegment * > & segments() const
Returns the vector of associated segments.
void replaceSegment(const xAOD::MuonSegment *exist, const xAOD::MuonSegment *updated)
Replaces an already added segment in the seed with a better suited one.
void setPosition(Amg::Vector3D &&pos)
set the seed's position
float numberDoF() const
Returns the numberDoF.
Amg::Vector3D direction() const
Returns the direction as Amg::Vector.
std::uint8_t nPrecisionHits() const
Returns the number of precision hits.
::Muon::MuonStationIndex::TechnologyIndex technology() const
Returns the main technology of the segment.
::Muon::MuonStationIndex::ChIndex chamberIndex() const
Returns the chamber index.
Amg::Vector3D position() const
Returns the position as Amg::Vector.
int etaIndex() const
Returns the eta index, which corresponds to stationEta in the offline identifiers (and the ).
std::uint8_t nPhiLayers() const
Returns the number of trigger phi hits.
virtual xAOD::UncalibMeasType type() const =0
Returns the type of the measurement type as a simple enumeration.
constexpr double energyToActs(const double athenaE)
Converts an energy scalar from Athena to Acts units.
Acts::Vector4 convertPosToActs(const Amg::Vector3D &athenaPos, const double athenaTime=0.)
Converts a position vector & time from Athena units into Acts units.
Amg::Vector3D projectDirOntoPlane(const Amg::Vector3D &direction, const Amg::Vector3D &planeNorm)
Project the direction vector onto the plane and renormalize to unity.
std::optional< double > intersect(const AmgVector(N)&posA, const AmgVector(N)&dirA, const AmgVector(N)&posB, const AmgVector(N)&dirB)
Calculates the point B' along the line B that's closest to a second line A.
std::string toString(const Translation3D &translation, int precision=4)
GeoPrimitvesToStringConverter.
Eigen::Affine3d Transform3D
Eigen::Matrix< double, 2, 1 > Vector2D
Eigen::Matrix< double, 3, 1 > Vector3D
const Acts::Surface * bottomBoundary(const Acts::TrackingVolume &volume)
Returns the boundary surface parallel to the x-y plane at negative local z.
Parameters localSegmentPars(const xAOD::MuonSegment &seg)
Returns the localSegPars decoration from a xAODMuon::Segment.
This header ties the generic definitions in this package.
std::vector< MsTrackSeed > MsTrackSeedContainer
std::string printID(const xAOD::MuonSegment &seg)
Print the chamber ID of a segment, e.g.
const xAOD::UncalibratedMeasurement * getMeasurement(const xAOD::MuonSegment &segment, const std::size_t n)
Returns the n-th uncalibrated measurement.
std::size_t nMeasurements(const xAOD::MuonSegment &segment)
Returns the number of associated Uncalibrated measurements.
Acts::GeometryIdentifier volumeId(const Acts::Surface &surface)
Returns the identifier of the volume in which the surface is embedded.
bool isOutlierMeasurement(const xAOD::MuonSegment &segment, const std::size_t n)
Returns whether the n-the uncalibrated measurement is an outlier.
const xAOD::UncalibratedMeasurement * firstMeasurement(const xAOD::MuonSegment &segment, const bool skipOutlier=true)
Retrieves the first measurement associated with the segment.
Amg::Vector3D atFirstSurface(const Acts::GeometryContext &gctx, const xAOD::MuonSegment &segment, const bool skipOutlier=true)
Expresses the segment position on the surface of the first measurement.
std::string print(const cont_t &container)
Print a space point container to string.
MsTrackSeederTool::SearchTree_t SearchTree_t
ChIndex chIndex(const std::string &index)
convert ChIndex name string to enum
StIndex toStationIndex(ChIndex index)
convert ChIndex into StIndex
bool isBarrel(const ChIndex index)
Returns true if the chamber index points to a barrel chamber.
LayerIndex
enum to classify the different layers in the muon spectrometer
LayerIndex toLayerIndex(ChIndex index)
convert ChIndex into LayerIndex
ChIndex
enum to classify the different chamber layers in the muon spectrometer
double deltaPhi(double phiA, double phiB)
delta Phi in range [-pi,pi[
const T * get(const ReadCondHandleKey< T > &key, const EventContext &ctx)
Convenience function to retrieve an object given a ReadCondHandleKey.
bool isNSW(const UncalibMeasType aodType)
Returns whether the measurement is a NSW measurement.
UncalibratedMeasurement_v1 UncalibratedMeasurement
Define the version of the uncalibrated measurement class.
MuonSegmentContainer_v1 MuonSegmentContainer
Definition of the current "MuonSegment container version".
MuonSegment_v1 MuonSegment
Reference the current persistent version:
const Acts::Surface & muonSurface(const UncalibratedMeasurement *meas)
Returns the associated Acts surface to the measurement.