Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Max chi2 for outliers in Core MeasurementSelector #3475

Merged
Merged
Show file tree
Hide file tree
Changes from 34 commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
08f7eb4
feat: Max chi2 for outlier for Core `MeasurementSelector`
andiwand Aug 2, 2024
6c8e69d
pr feedback
andiwand Aug 5, 2024
fb318ac
Merge branch 'main' into feat-measurement-selector-maxchi2outlier
andiwand Aug 7, 2024
fd7c9b8
Merge branch 'main' into feat-measurement-selector-maxchi2outlier
andiwand Aug 8, 2024
30f9c80
Merge branch 'main' of github.com:acts-project/acts into feat-measure…
andiwand Aug 14, 2024
785c051
format
andiwand Aug 14, 2024
821c6e7
switch to new args; add default args to physmon
andiwand Aug 14, 2024
4ee5e31
fix: Add missing newline for `Sequencer` `timing.csv` in Examples
andiwand Aug 14, 2024
7f1ec0f
`maxHolesAndOutliers`
andiwand Aug 14, 2024
6c0d7fa
Merge branch 'feat-measurement-selector-maxchi2outlier' of github.com…
andiwand Aug 14, 2024
5125cce
update hashes
andiwand Aug 14, 2024
f4681c0
update refs
andiwand Aug 14, 2024
975bc52
revert sequencer
andiwand Aug 14, 2024
b077fca
pr feedback
andiwand Aug 14, 2024
a4e195f
Merge branch 'main' into feat-measurement-selector-maxchi2outlier
kodiakhq[bot] Aug 19, 2024
a4911d4
Merge branch 'main' into feat-measurement-selector-maxchi2outlier
andiwand Aug 20, 2024
601af98
update hashes
andiwand Aug 20, 2024
0d59f76
Merge branch 'main' into feat-measurement-selector-maxchi2outlier
kodiakhq[bot] Aug 20, 2024
87a95b5
fix
andiwand Aug 20, 2024
bd86ee0
pr feedback
andiwand Aug 20, 2024
bf509bf
format
andiwand Aug 20, 2024
1c80332
pr feedback
andiwand Aug 20, 2024
b637c4b
Update Core/src/TrackFinding/MeasurementSelector.cpp
andiwand Aug 20, 2024
5a1a727
pr feedback
andiwand Aug 20, 2024
d3116f0
Merge branch 'main' of github.com:acts-project/acts into feat-measure…
andiwand Aug 21, 2024
d9b0b13
fix warning
andiwand Aug 21, 2024
ddcd232
Merge branch 'main' into feat-measurement-selector-maxchi2outlier
andiwand Aug 21, 2024
c0634ec
update hashes
andiwand Aug 21, 2024
2e58270
pr feedback; revert ODD change
andiwand Aug 21, 2024
8c640ae
Update Core/include/Acts/TrackFinding/MeasurementSelector.ipp
andiwand Aug 21, 2024
789adf0
internal cuts; use abs theta
andiwand Aug 21, 2024
b997e9b
Merge branch 'feat-measurement-selector-maxchi2outlier' of github.com…
andiwand Aug 21, 2024
9ea5dcf
pr feedback; fix theta comparison
andiwand Aug 22, 2024
0c4249e
pr feedback
andiwand Aug 22, 2024
a612743
Update Core/src/TrackFinding/MeasurementSelector.cpp
andiwand Aug 22, 2024
e07a683
Merge branch 'main' into feat-measurement-selector-maxchi2outlier
andiwand Aug 22, 2024
4a3477c
Merge branch 'main' into feat-measurement-selector-maxchi2outlier
kodiakhq[bot] Aug 22, 2024
2379082
Merge branch 'main' into feat-measurement-selector-maxchi2outlier
paulgessinger Aug 23, 2024
41bca04
Merge branch 'main' into feat-measurement-selector-maxchi2outlier
paulgessinger Aug 23, 2024
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
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file modified CI/physmon/reference/trackfinding_ttbar_pu200/performance_ckf.root
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
3 changes: 3 additions & 0 deletions CI/physmon/workflows/physmon_trackfinding_1muon.py
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,9 @@ def run_ckf_tracking(label, seeding):
maxOutliers=2,
),
CkfConfig(
chi2CutOffMeasurement=15.0,
chi2CutOffOutlier=25.0,
numMeasurementsCutOff=10,
seedDeduplication=(
True if seeding != SeedingAlgorithm.TruthSmeared else False
),
Expand Down
3 changes: 3 additions & 0 deletions CI/physmon/workflows/physmon_trackfinding_4muon_50vertices.py
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,9 @@
maxOutliers=2,
),
CkfConfig(
chi2CutOffMeasurement=15.0,
chi2CutOffOutlier=25.0,
numMeasurementsCutOff=10,
seedDeduplication=True,
stayOnSeed=True,
),
Expand Down
3 changes: 3 additions & 0 deletions CI/physmon/workflows/physmon_trackfinding_ttbar_pu200.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,9 @@
maxOutliers=2,
),
CkfConfig(
chi2CutOffMeasurement=15.0,
chi2CutOffOutlier=25.0,
numMeasurementsCutOff=10,
seedDeduplication=True,
stayOnSeed=True,
),
Expand Down
32 changes: 24 additions & 8 deletions Core/include/Acts/TrackFinding/MeasurementSelector.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,12 @@ namespace Acts {
struct MeasurementSelectorCuts {
/// bins in |eta| to specify variable selections
std::vector<double> etaBins{};
/// Maximum local chi2 contribution.
/// Maximum local chi2 contribution to classify as measurement.
std::vector<double> chi2CutOff{15};
andiwand marked this conversation as resolved.
Show resolved Hide resolved
/// Maximum number of associated measurements on a single surface.
std::vector<std::size_t> numMeasurementsCutOff{1};
/// Maximum local chi2 contribution to classify as outlier.
std::vector<double> chi2CutOffOutlier{};
};

/// @brief Measurement selection struct selecting those measurements compatible
Expand Down Expand Up @@ -73,7 +75,7 @@ class MeasurementSelector {
/// @brief Constructor with config
///
/// @param config a config instance
explicit MeasurementSelector(Config config);
explicit MeasurementSelector(const Config& config);

/// @brief Function that select the measurements compatible with
/// the given track parameter on a surface
Expand All @@ -93,11 +95,25 @@ class MeasurementSelector {
bool& isOutlier, const Logger& logger) const;

private:
template <typename traj_t, typename cut_value_t>
static cut_value_t getCut(
const typename traj_t::TrackStateProxy& trackState,
const Acts::MeasurementSelector::Config::Iterator selector,
const std::vector<cut_value_t>& cuts, const Logger& logger);
struct InternalCutBin {
double maxTheta{};
std::size_t maxNumMeasurements{};
double maxChi2Measurement{};
double maxChi2Outlier{};
};
using InternalCutBins = std::vector<InternalCutBin>;
using InternalConfig = Acts::GeometryHierarchyMap<InternalCutBins>;

struct Cuts {
std::size_t numMeasurements{};
double chi2Measurement{};
double chi2Outlier{};
};

static InternalCutBins convertCutBins(const MeasurementSelectorCuts& config);

static Cuts getCutsByTheta(const InternalCutBins& config, double theta);
Result<Cuts> getCuts(const GeometryIdentifier& geoID, double theta) const;

double calculateChi2(
const double* fullCalibrated, const double* fullCalibratedCovariance,
Expand All @@ -109,7 +125,7 @@ class MeasurementSelector {
false>::Projector projector,
unsigned int calibratedSize) const;

Config m_config;
InternalConfig m_config;
};

} // namespace Acts
Expand Down
85 changes: 27 additions & 58 deletions Core/include/Acts/TrackFinding/MeasurementSelector.ipp
Original file line number Diff line number Diff line change
Expand Up @@ -27,25 +27,17 @@ MeasurementSelector::select(
}

// Get geoID of this surface
auto geoID = candidates.front().referenceSurface().geometryId();
GeometryIdentifier geoID = candidates.front().referenceSurface().geometryId();
// Get the theta of the first track state
const double theta = candidates.front().predicted()[eBoundTheta];
// Find the appropriate cuts
auto cuts = m_config.find(geoID);
if (cuts == m_config.end()) {
// for now we consider missing cuts an unrecoverable error
// TODO consider other options e.g. do not add measurements at all (not
// even as outliers)
return CombinatorialKalmanFilterError::MeasurementSelectionFailed;
const auto cutsResult = getCuts(geoID, theta);
if (!cutsResult.ok()) {
return cutsResult.error();
}
const Cuts& cuts = *cutsResult;

assert(!cuts->chi2CutOff.empty());
const std::vector<double>& chi2CutOff = cuts->chi2CutOff;
const double maxChi2Cut =
std::min(*std::max_element(chi2CutOff.begin(), chi2CutOff.end()),
getCut<traj_t>(candidates.front(), cuts, chi2CutOff, logger));
const std::size_t numMeasurementsCut = getCut<traj_t>(
candidates.front(), cuts, cuts->numMeasurementsCutOff, logger);

if (numMeasurementsCut == 0ul) {
if (cuts.numMeasurements == 0ul) {
return CombinatorialKalmanFilterError::MeasurementSelectionFailed;
}

Expand Down Expand Up @@ -80,7 +72,7 @@ MeasurementSelector::select(
}

// only consider track states which pass the chi2 cut
if (chi2 >= maxChi2Cut) {
if (chi2 >= cuts.chi2Measurement) {
continue;
}

Expand All @@ -93,17 +85,25 @@ MeasurementSelector::select(
++passedCandidates;
}

// If there are no measurements below the chi2 cut off, return the
// measurement with the best chi2 and tag it as an outlier
// Handle if there are no measurements below the chi2 cut off
if (passedCandidates == 0ul) {
ACTS_VERBOSE("No measurement candidate. Return an outlier measurement chi2="
<< minChi2);
isOutlier = true;
if (minChi2 < cuts.chi2Outlier) {
ACTS_VERBOSE(
"No measurement candidate. Return an outlier measurement chi2="
<< minChi2);
isOutlier = true;
return Result::success(std::make_pair(candidates.begin() + minIndex,
candidates.begin() + minIndex + 1));
} else {
ACTS_VERBOSE("No measurement candidate. Return empty chi2=" << minChi2);
return Result::success(
std::make_pair(candidates.begin(), candidates.begin()));
}
}

if (passedCandidates <= 1ul || numMeasurementsCut == 1ul) {
if (passedCandidates <= 1ul) {
// return single item range, no sorting necessary
ACTS_VERBOSE("Returning only 1 element");
ACTS_VERBOSE("Returning only 1 element chi2=" << minChi2);
return Result::success(std::make_pair(candidates.begin() + minIndex,
candidates.begin() + minIndex + 1));
}
Expand All @@ -112,43 +112,12 @@ MeasurementSelector::select(
candidates.begin(), candidates.begin() + passedCandidates,
[](const auto& tsa, const auto& tsb) { return tsa.chi2() < tsb.chi2(); });

if (passedCandidates <= numMeasurementsCut) {
ACTS_VERBOSE("Number of selected measurements: "
<< passedCandidates << ", max: " << numMeasurementsCut);
return Result::success(std::make_pair(
candidates.begin(), candidates.begin() + passedCandidates));
}

ACTS_VERBOSE("Number of selected measurements: "
<< numMeasurementsCut << ", max: " << numMeasurementsCut);
<< passedCandidates << ", max: " << cuts.numMeasurements);

return Result::success(std::make_pair(
candidates.begin(), candidates.begin() + numMeasurementsCut));
}

template <typename traj_t, typename cut_value_t>
cut_value_t MeasurementSelector::getCut(
const typename traj_t::TrackStateProxy& trackState,
const Acts::MeasurementSelector::Config::Iterator selector,
const std::vector<cut_value_t>& cuts, const Logger& logger) {
const auto& etaBins = selector->etaBins;
if (etaBins.empty()) {
return cuts[0]; // shortcut if no etaBins
}
const auto eta = std::atanh(std::cos(trackState.predicted()[eBoundTheta]));
const auto abseta = std::abs(eta);
std::size_t bin = 0;
for (auto etaBin : etaBins) {
if (etaBin >= abseta) {
break;
}
bin++;
}
if (bin >= cuts.size()) {
bin = cuts.size() - 1;
}
ACTS_VERBOSE("Get cut for eta=" << eta << ": " << cuts[bin]);
return cuts[bin];
candidates.begin(),
candidates.begin() + std::min(cuts.numMeasurements, passedCandidates)));
}

} // namespace Acts
4 changes: 4 additions & 0 deletions Core/include/Acts/TrackFinding/TrackSelector.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ class TrackSelector {
std::size_t minMeasurements = 0;
std::size_t maxHoles = std::numeric_limits<std::size_t>::max();
std::size_t maxOutliers = std::numeric_limits<std::size_t>::max();
std::size_t maxHolesAndOutliers = std::numeric_limits<std::size_t>::max();
std::size_t maxSharedHits = std::numeric_limits<std::size_t>::max();
double maxChi2 = inf;

Expand Down Expand Up @@ -311,6 +312,7 @@ inline std::ostream& operator<<(std::ostream& os,
print("pt", cuts.ptMin, cuts.ptMax);
print("nHoles", 0, cuts.maxHoles);
print("nOutliers", 0, cuts.maxOutliers);
print("nHoles + nOutliers", 0, cuts.maxHolesAndOutliers);
print("nSharedHits", 0, cuts.maxSharedHits);
print("chi2", 0.0, cuts.maxChi2);
os << " - " << cuts.minMeasurements << " <= nMeasurements\n";
Expand Down Expand Up @@ -434,6 +436,8 @@ bool TrackSelector::isValidTrack(const track_proxy_t& track) const {
checkMin(track.nMeasurements(), cuts.minMeasurements) &&
checkMax(track.nHoles(), cuts.maxHoles) &&
checkMax(track.nOutliers(), cuts.maxOutliers) &&
checkMax(track.nHoles() + track.nOutliers(),
cuts.maxHolesAndOutliers) &&
checkMax(track.nSharedHits(), cuts.maxSharedHits) &&
checkMax(track.chi2(), cuts.maxChi2) &&
cuts.measurementCounter.isValidTrack(track);
Expand Down
83 changes: 79 additions & 4 deletions Core/src/TrackFinding/MeasurementSelector.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,19 +10,64 @@

#include "Acts/Definitions/Algebra.hpp"
#include "Acts/EventData/MeasurementHelpers.hpp"
#include "Acts/Geometry/GeometryIdentifier.hpp"

#include <algorithm>
#include <cstddef>
#include <limits>

namespace Acts {

MeasurementSelector::MeasurementSelector()
: m_config{{GeometryIdentifier(), MeasurementSelectorCuts{}}} {}
: MeasurementSelector({{GeometryIdentifier(), MeasurementSelectorCuts{}}}) {
}

MeasurementSelector::MeasurementSelector(const MeasurementSelectorCuts& cuts)
: m_config{{GeometryIdentifier(), cuts}} {}
: MeasurementSelector({{GeometryIdentifier(), cuts}}) {}

MeasurementSelector::MeasurementSelector(const Config& config) {
std::vector<InternalConfig::InputElement> tmp;
tmp.reserve(config.size());
for (std::size_t i = 0; i < config.size(); ++i) {
GeometryIdentifier geoID = config.idAt(i);
MeasurementSelectorCuts cuts = config.valueAt(i);

InternalCutBins internalCuts = convertCutBins(cuts);
tmp.emplace_back(geoID, std::move(internalCuts));
}
m_config = InternalConfig(std::move(tmp));
}

MeasurementSelector::MeasurementSelector(Config config)
: m_config(std::move(config)) {}
MeasurementSelector::InternalCutBins MeasurementSelector::convertCutBins(
const MeasurementSelectorCuts& config) {
InternalCutBins cutBins;

auto getEtaOrInf = [](const auto& vec, std::size_t bin) {
if (bin >= vec.size()) {
return std::numeric_limits<double>::infinity();
}
assert(vec[bin] >= 0 && "Eta bins must be positive");
return vec[bin];
};

auto getBinOrBackOrMax = [](const auto& vec, std::size_t bin) {
using Value = std::remove_reference_t<decltype(vec[0])>;
static constexpr Value max = std::numeric_limits<Value>::max();
return vec.empty() ? max : (bin < vec.size() ? vec[bin] : vec.back());
};

for (std::size_t bin = 0; bin < config.etaBins.size() + 1; ++bin) {
InternalCutBin cuts;
cuts.maxTheta = getEtaOrInf(config.etaBins, bin);
cuts.maxNumMeasurements =
getBinOrBackOrMax(config.numMeasurementsCutOff, bin);
cuts.maxChi2Measurement = getBinOrBackOrMax(config.chi2CutOff, bin);
cuts.maxChi2Outlier = getBinOrBackOrMax(config.chi2CutOffOutlier, bin);
cutBins.push_back(cuts);
}

return cutBins;
}

double MeasurementSelector::calculateChi2(
const double* fullCalibrated, const double* fullCalibratedCovariance,
Expand Down Expand Up @@ -64,4 +109,34 @@ double MeasurementSelector::calculateChi2(
});
}

MeasurementSelector::Cuts MeasurementSelector::getCutsByTheta(
const InternalCutBins& config, double theta) {
// since theta is in [0, pi] and we have a symmetric cut in eta, we can just
// look at the positive half of the Z axis
const double constrainedTheta = std::min(theta, M_PI - theta);

auto it = std::find_if(config.begin(), config.end(),
[constrainedTheta](const InternalCutBin& cuts) {
return constrainedTheta < cuts.maxTheta;
});
assert(it != config.end());
std::size_t bin = std::distance(config.begin(), it);
andiwand marked this conversation as resolved.
Show resolved Hide resolved

return {config[bin].maxNumMeasurements, config[bin].maxChi2Measurement,
config[bin].maxChi2Outlier};
andiwand marked this conversation as resolved.
Show resolved Hide resolved
}

Result<MeasurementSelector::Cuts> MeasurementSelector::getCuts(
const GeometryIdentifier& geoID, double theta) const {
// Find the appropriate cuts
auto cuts = m_config.find(geoID);
if (cuts == m_config.end()) {
// for now we consider missing cuts an unrecoverable error
// TODO consider other options e.g. do not add measurements at all (not
// even as outliers)
return CombinatorialKalmanFilterError::MeasurementSelectionFailed;
}
return getCutsByTheta(*cuts, theta);
}

} // namespace Acts
Original file line number Diff line number Diff line change
Expand Up @@ -241,8 +241,10 @@ class BranchStopper {
bool tooManyHoles =
track.nHoles() > singleConfig->maxHoles || tooManyHolesPS;
bool tooManyOutliers = track.nOutliers() > singleConfig->maxOutliers;
bool tooManyHolesAndOutliers = (track.nHoles() + track.nOutliers()) >
singleConfig->maxHolesAndOutliers;

if (tooManyHoles || tooManyOutliers) {
if (tooManyHoles || tooManyOutliers || tooManyHolesAndOutliers) {
++m_nStoppedBranches;
return enoughMeasurements ? BranchStopperResult::StopAndKeep
: BranchStopperResult::StopAndDrop;
Expand Down
Loading
Loading