Skip to content

Commit 8113850

Browse files
author
jokonig
committed
[PWGEM] PhotonMeson: Add faster V0 deduplication mode
- current deduplication relies on nested for-loop of a map with 30000 elements. This makes computation slow - New mode ranks all V0 candidates with a score, based on their cosPA and PCA - V0s are accepted starting from V0s with the best score. A new V0 is only added if its daughter tracks have not yet been included in another V0 that has been accepted already
1 parent 9ffa18c commit 8113850

2 files changed

Lines changed: 169 additions & 48 deletions

File tree

PWGEM/PhotonMeson/TableProducer/photonconversionbuilder.cxx

Lines changed: 152 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,9 @@
7272
#include <map>
7373
#include <set>
7474
#include <string>
75+
#include <tuple>
7576
#include <unordered_map>
77+
#include <unordered_set>
7678
#include <utility>
7779
#include <vector>
7880

@@ -103,6 +105,44 @@ enum TrackPropMode {
103105
kBoth = 2
104106
};
105107

108+
enum V0DeduplicationMode {
109+
Pairwise = 0, // old algorithm
110+
GreedyMatching = 1 // new algorithm
111+
};
112+
113+
// Struct needed for deduplication mode 1. Used to keep track of v0 properties and a score based on pca and cosPA
114+
struct V0CandidateHelper {
115+
int v0ID = -1;
116+
int colID = -1;
117+
int posID = -1;
118+
int eleID = -1;
119+
float cosPA = -1.f;
120+
float pca = -1.f;
121+
float score = -1.f;
122+
123+
V0CandidateHelper() = default;
124+
125+
V0CandidateHelper(int v0, int col, int pos, int ele, float cpa, float p, float s)
126+
: v0ID(v0), colID(col), posID(pos), eleID(ele), cosPA(cpa), pca(p), score(s)
127+
{
128+
}
129+
130+
bool operator==(const V0CandidateHelper& other) const
131+
{
132+
return score == other.score;
133+
}
134+
135+
bool operator<(const V0CandidateHelper& other) const
136+
{
137+
return score < other.score;
138+
}
139+
140+
bool operator>(const V0CandidateHelper& other) const
141+
{
142+
return score > other.score;
143+
}
144+
};
145+
106146
struct PhotonConversionBuilder {
107147
Produces<aod::V0PhotonsKF> v0photonskf;
108148
Produces<aod::V0Legs> v0legs;
@@ -124,6 +164,8 @@ struct PhotonConversionBuilder {
124164
Configurable<double> d_bz_input{"d_bz", -999, "bz field, -999 is automatic"};
125165
Configurable<int> useMatCorrType{"useMatCorrType", 0, "0: none, 1: TGeo, 2: LUT"};
126166
Configurable<int> modeTrackPropagation{"modeTrackPropagation", 0, "0: use real track propagation, including material, 1: use fast approximation using only geometry, 2: Use real track propagation and make comparison to fast propagation (only for debugging and testing)"};
167+
Configurable<int> deduplicationMode{"deduplicationMode", 0, "0: Pairwise deduplication, 1: Based on Greedy matching (best score wins)"};
168+
Configurable<float> deduplicationScoreWeight{"deduplicationScoreWeight", 0.5f, "0.:only pca goes into the score, 1: only cosPA goes itno score, any number in between is a mix of pca and cosPA"};
127169

128170
// single track cuts
129171
Configurable<int> min_ncluster_tpc{"min_ncluster_tpc", 0, "min ncluster tpc"};
@@ -343,6 +385,14 @@ struct PhotonConversionBuilder {
343385
registry.add("V0/hPhivPropagationCompare", "Comparison of #phi_{v};#phi_{v} proper propagation; #phi_{v} geom. propagation", {HistType::kTH2F, {{100, 0., 1.6}, {100, 0., 1.6}}});
344386
registry.add("V0/hPsiPairPropagationCompare", "Comparison of #Psi_{pair};#Psi_{pair} proper propagation; #Psi_{pair} geom. propagation", {HistType::kTH2F, {{100, 0., 1.6}, {100, 0., 1.6}}});
345387
}
388+
389+
// Make sure the deduplicationScoreWeight is between 0 and 1
390+
if (deduplicationScoreWeight > 1.f) { // o2-linter: disable=magic-number (score has to be below unity)
391+
LOG(warning) << "deduplicationScoreWeight is larger than unity which is not allowed";
392+
}
393+
if (deduplicationScoreWeight < 0.f) { // o2-linter: disable=magic-number (score has to be above zero)
394+
LOG(warning) << "deduplicationScoreWeight is smaller than zero which is not allowed";
395+
}
346396
}
347397

348398
void initCCDB(aod::BCsWithTimestamps::iterator const& bc)
@@ -585,7 +635,7 @@ struct PhotonConversionBuilder {
585635
std::array<float, 2> dcaInfoFast = dcaInfo;
586636
if (modeTrackPropagation != TrackPropMode::kFast) {
587637
o2::base::Propagator::Instance()->propagateToDCABxByBz({collision.posX(), collision.posY(), collision.posZ()}, pTrackC, 2.f, matCorr, &dcaInfo);
588-
if (modeTrackPropagation == TrackPropMode::kBoth) {
638+
if (filltable && modeTrackPropagation == TrackPropMode::kBoth) {
589639
registry.fill(HIST("V0Leg/hDCAxyPropagationCompare"), dcaInfo[0], dcaInfoFast[0]);
590640
registry.fill(HIST("V0Leg/hDCAzPropagationCompare"), dcaInfo[1], dcaInfoFast[1]);
591641
}
@@ -607,7 +657,7 @@ struct PhotonConversionBuilder {
607657
dcaInfoFast = dcaInfo;
608658
if (modeTrackPropagation != TrackPropMode::kFast) {
609659
o2::base::Propagator::Instance()->propagateToDCABxByBz({collision.posX(), collision.posY(), collision.posZ()}, nTrackC, 2.f, matCorr, &dcaInfo);
610-
if (modeTrackPropagation == TrackPropMode::kBoth) {
660+
if (filltable && modeTrackPropagation == TrackPropMode::kBoth) {
611661
registry.fill(HIST("V0Leg/hDCAxyPropagationCompare"), dcaInfo[0], dcaInfoFast[0]);
612662
registry.fill(HIST("V0Leg/hDCAzPropagationCompare"), dcaInfo[1], dcaInfoFast[1]);
613663
}
@@ -687,7 +737,7 @@ struct PhotonConversionBuilder {
687737
}
688738
LOG(debug) << "Propagation to offset" << offsetR << " cm failed for " << (pPropagatedSuccess ? "negative" : "positive") << " track. Trying smaller offset.";
689739
}
690-
if (modeTrackPropagation == TrackPropMode::kBoth) {
740+
if (filltable && modeTrackPropagation == TrackPropMode::kBoth) {
691741
registry.fill(HIST("V0/hPhivPropagationCompare"), phiv, phivFast);
692742
registry.fill(HIST("V0/hPsiPairPropagationCompare"), psipair, psipairFast);
693743
}
@@ -856,6 +906,10 @@ struct PhotonConversionBuilder {
856906
pca_map[std::make_tuple(v0.globalIndex(), collision.globalIndex(), pos.globalIndex(), ele.globalIndex())] = v0photoncandidate.getPCA();
857907
cospa_map[std::make_tuple(v0.globalIndex(), collision.globalIndex(), pos.globalIndex(), ele.globalIndex())] = v0photoncandidate.getCosPA();
858908

909+
const auto score = getScoreV0(v0photoncandidate.getCosPA(), v0photoncandidate.getPCA(), deduplicationScoreWeight);
910+
V0CandidateHelper v0Helper(v0.globalIndex(), collision.globalIndex(), pos.globalIndex(), ele.globalIndex(), v0photoncandidate.getCosPA(), v0photoncandidate.getPCA(), score);
911+
vecV0Dedup.emplace_back(v0Helper);
912+
859913
if (applyPCMMl) {
860914
bool isSelectedML = false;
861915
std::vector<float> mlInputFeatures = emMlResponse.getInputFeatures(v0photoncandidate, pos, ele);
@@ -953,6 +1007,7 @@ struct PhotonConversionBuilder {
9531007
Preslice<aod::V0s> perCollision = o2::aod::v0::collisionId;
9541008
std::map<std::tuple<int64_t, int64_t, int64_t, int64_t>, float> pca_map; // (v0.globalIndex(), collision.globalIndex(), pos.globalIndex(), ele.globalIndex()) -> pca
9551009
std::map<std::tuple<int64_t, int64_t, int64_t, int64_t>, float> cospa_map; // (v0.globalIndex(), collision.globalIndex(), pos.globalIndex(), ele.globalIndex()) -> cospa
1010+
std::vector<V0CandidateHelper> vecV0Dedup; // vector with all V0Candidates that is used to sort them by score (see struct V0CandidateHelper for more details of content)
9561011
std::vector<std::pair<int64_t, int64_t>> stored_v0Ids; // (pos.globalIndex(), ele.globalIndex())
9571012
std::vector<std::tuple<int64_t, int64_t, int64_t, int64_t>> stored_fullv0Ids; // (v0.globalIndex(), collision.globalIndex(), pos.globalIndex(), ele.globalIndex())
9581013
std::unordered_map<int64_t, int> nv0_map; // map collisionId -> nv0
@@ -985,6 +1040,7 @@ struct PhotonConversionBuilder {
9851040

9861041
updateCCDB(bc); // delay update until is needed
9871042

1043+
vecV0Dedup.reserve(30000); // rough estimate for number of V0s per DF
9881044
const auto& v0s_per_coll = v0s.sliceBy(perCollision, collision.globalIndex());
9891045
// LOGF(info, "n v0 = %d", v0s_per_coll.size());
9901046
for (const auto& v0 : v0s_per_coll) {
@@ -997,56 +1053,103 @@ struct PhotonConversionBuilder {
9971053
stored_fullv0Ids.reserve(pca_map.size()); // number of photon candidates per DF
9981054

9991055
// find minimal pca
1000-
for (const auto& [key, value] : pca_map) {
1001-
auto v0Id = std::get<0>(key);
1002-
auto collisionId = std::get<1>(key);
1003-
auto posId = std::get<2>(key);
1004-
auto eleId = std::get<3>(key);
1005-
float v0pca = value;
1006-
float cospa = cospa_map[key];
1007-
bool is_closest_v0 = true;
1008-
bool is_most_aligned_v0 = true;
1009-
1010-
for (const auto& [key_tmp, value_tmp] : pca_map) {
1011-
auto v0Id_tmp = std::get<0>(key_tmp);
1012-
auto collisionId_tmp = std::get<1>(key_tmp);
1013-
auto posId_tmp = std::get<2>(key_tmp);
1014-
auto eleId_tmp = std::get<3>(key_tmp);
1015-
float v0pca_tmp = value_tmp;
1016-
float cospa_tmp = cospa_map[key_tmp];
1017-
1018-
if (v0Id == v0Id_tmp) { // skip exactly the same v0
1056+
if (deduplicationMode == V0DeduplicationMode::Pairwise) {
1057+
for (const auto& [key, value] : pca_map) {
1058+
auto v0Id = std::get<0>(key);
1059+
auto collisionId = std::get<1>(key);
1060+
auto posId = std::get<2>(key);
1061+
auto eleId = std::get<3>(key);
1062+
float v0pca = value;
1063+
float cospa = cospa_map[key];
1064+
bool is_closest_v0 = true;
1065+
bool is_most_aligned_v0 = true;
1066+
1067+
for (const auto& [key_tmp, value_tmp] : pca_map) {
1068+
auto v0Id_tmp = std::get<0>(key_tmp);
1069+
auto collisionId_tmp = std::get<1>(key_tmp);
1070+
auto posId_tmp = std::get<2>(key_tmp);
1071+
auto eleId_tmp = std::get<3>(key_tmp);
1072+
float v0pca_tmp = value_tmp;
1073+
float cospa_tmp = cospa_map[key_tmp];
1074+
1075+
if (v0Id == v0Id_tmp) { // skip exactly the same v0
1076+
continue;
1077+
}
1078+
1079+
if (collisionId != collisionId_tmp && eleId == eleId_tmp && posId == posId_tmp && cospa < cospa_tmp) { // same ele and pos, but attached to different collision
1080+
// LOGF(info, "!reject! | collision id = %d | posid1 = %d , eleid1 = %d , posid2 = %d , eleid2 = %d , cospa1 = %f , cospa2 = %f", collisionId, posId, eleId, posId_tmp, eleId_tmp, cospa, cospa_tmp);
1081+
is_most_aligned_v0 = false;
1082+
break;
1083+
}
1084+
1085+
if ((eleId == eleId_tmp || posId == posId_tmp) && v0pca > v0pca_tmp) {
1086+
// LOGF(info, "!reject! | collision id = %d | posid1 = %d , eleid1 = %d , posid2 = %d , eleid2 = %d , pca1 = %f , pca2 = %f", collisionId, posId, eleId, posId_tmp, eleId_tmp, v0pca, v0pca_tmp);
1087+
is_closest_v0 = false;
1088+
break;
1089+
}
1090+
} // end of pca_map tmp loop
1091+
1092+
bool is_stored = std::find(stored_v0Ids.begin(), stored_v0Ids.end(), std::make_pair(posId, eleId)) != stored_v0Ids.end();
1093+
if (is_closest_v0 && is_most_aligned_v0 && !is_stored) {
1094+
// auto v0 = v0s.rawIteratorAt(v0Id);
1095+
// auto collision = collisions.rawIteratorAt(collisionId);
1096+
// auto pos = tracks.rawIteratorAt(posId);
1097+
// auto ele = tracks.rawIteratorAt(eleId);
1098+
// LOGF(info, "!accept! | collision id = %d | v0id1 = %d , posid1 = %d , eleid1 = %d , pca1 = %f , cospa = %f", collisionId, v0Id, posId, eleId, v0pca, cospa);
1099+
1100+
// fillV0Table<isMC, TCollisions, TTracks>(v0, true);
1101+
stored_v0Ids.emplace_back(std::make_pair(posId, eleId));
1102+
stored_fullv0Ids.emplace_back(std::make_tuple(v0Id, collisionId, posId, eleId));
1103+
nv0_map[collisionId]++;
1104+
}
1105+
} // end of pca_map loop
1106+
// LOGF(info, "pca_map.size() = %d", pca_map.size());
1107+
} else {
1108+
// Sort best candidates first, depending on score
1109+
std::sort(vecV0Dedup.begin(), vecV0Dedup.end());
1110+
// containers to keep track of which electron and positron tracks have already been used
1111+
std::unordered_set<int> usedPositrons;
1112+
std::unordered_set<int> usedElectrons;
1113+
1114+
// clear output containers
1115+
stored_v0Ids.clear();
1116+
stored_fullv0Ids.clear();
1117+
nv0_map.clear();
1118+
1119+
// Loop over all v0s, starting with the one with the best score
1120+
// If a v0 contains a track that is already in another (better) V0 candidate, skip V0
1121+
for (const auto& v0Cand : vecV0Dedup) {
1122+
// Skip if one of the tracks is already used
1123+
if (usedPositrons.contains(v0Cand.posID)) {
10191124
continue;
10201125
}
1021-
1022-
if (collisionId != collisionId_tmp && eleId == eleId_tmp && posId == posId_tmp && cospa < cospa_tmp) { // same ele and pos, but attached to different collision
1023-
// LOGF(info, "!reject! | collision id = %d | posid1 = %d , eleid1 = %d , posid2 = %d , eleid2 = %d , cospa1 = %f , cospa2 = %f", collisionId, posId, eleId, posId_tmp, eleId_tmp, cospa, cospa_tmp);
1024-
is_most_aligned_v0 = false;
1025-
break;
1126+
if (usedElectrons.contains(v0Cand.eleID)) {
1127+
continue;
10261128
}
10271129

1028-
if ((eleId == eleId_tmp || posId == posId_tmp) && v0pca > v0pca_tmp) {
1029-
// LOGF(info, "!reject! | collision id = %d | posid1 = %d , eleid1 = %d , posid2 = %d , eleid2 = %d , pca1 = %f , pca2 = %f", collisionId, posId, eleId, posId_tmp, eleId_tmp, v0pca, v0pca_tmp);
1030-
is_closest_v0 = false;
1031-
break;
1032-
}
1033-
} // end of pca_map tmp loop
1034-
1035-
bool is_stored = std::find(stored_v0Ids.begin(), stored_v0Ids.end(), std::make_pair(posId, eleId)) != stored_v0Ids.end();
1036-
if (is_closest_v0 && is_most_aligned_v0 && !is_stored) {
1037-
// auto v0 = v0s.rawIteratorAt(v0Id);
1038-
// auto collision = collisions.rawIteratorAt(collisionId);
1039-
// auto pos = tracks.rawIteratorAt(posId);
1040-
// auto ele = tracks.rawIteratorAt(eleId);
1041-
// LOGF(info, "!accept! | collision id = %d | v0id1 = %d , posid1 = %d , eleid1 = %d , pca1 = %f , cospa = %f", collisionId, v0Id, posId, eleId, v0pca, cospa);
1042-
1043-
// fillV0Table<isMC, TCollisions, TTracks>(v0, true);
1044-
stored_v0Ids.emplace_back(std::make_pair(posId, eleId));
1045-
stored_fullv0Ids.emplace_back(std::make_tuple(v0Id, collisionId, posId, eleId));
1046-
nv0_map[collisionId]++;
1130+
// Accept candidate
1131+
usedPositrons.insert(v0Cand.posID);
1132+
usedElectrons.insert(v0Cand.eleID);
1133+
1134+
stored_v0Ids.emplace_back(v0Cand.posID, v0Cand.eleID);
1135+
1136+
stored_fullv0Ids.emplace_back(
1137+
v0Cand.v0ID,
1138+
v0Cand.colID,
1139+
v0Cand.posID,
1140+
v0Cand.eleID);
1141+
1142+
nv0_map[v0Cand.colID]++;
10471143
}
1048-
} // end of pca_map loop
1049-
// LOGF(info, "pca_map.size() = %d", pca_map.size());
1144+
1145+
// sort accepted candidates by collision ID to be compatible with table
1146+
std::sort(stored_fullv0Ids.begin(), stored_fullv0Ids.end(),
1147+
[](const auto& a, const auto& b) {
1148+
return std::get<1>(a) < std::get<1>(b);
1149+
});
1150+
1151+
// End of deduplication
1152+
}
10501153

10511154
for (const auto& fullv0Id : stored_fullv0Ids) {
10521155
auto v0Id = std::get<0>(fullv0Id);
@@ -1083,6 +1186,7 @@ struct PhotonConversionBuilder {
10831186
stored_v0Ids.shrink_to_fit();
10841187
stored_fullv0Ids.clear();
10851188
stored_fullv0Ids.shrink_to_fit();
1189+
vecV0Dedup.clear();
10861190
} // end of build
10871191

10881192
//! type of V0. 0: built solely for cascades (does not pass standard V0 cuts), 1: standard 2, 3: photon-like with TPC-only use. Regular analysis should always use type 1 or 3.

PWGEM/PhotonMeson/Utils/PCMUtilities.h

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,4 +196,21 @@ inline void Vtx_recalculation(o2::base::Propagator* prop, T1 lTrackPos, T2 lTrac
196196

197197
Vtx_recalculationParCov<TrackPrecision>(prop, trackPosInformation, trackNegInformation, xyz, matCorr);
198198
}
199+
200+
//_______________________________________________________________________
201+
/// \brief Function to calculate a score based on cosPA and PCA. The smaller the score the better the V0 Candidate
202+
/// \param cosPA cosine of pointing angle
203+
/// \param pca point of closest approach in cm
204+
/// \param weight how much is cosPA weighted (for pca its 1-weight). Weight has to be between 0 and 1
205+
/// \return final score
206+
inline float getScoreV0(float cosPA, float pca, float weight)
207+
{
208+
float cosScore = 60 * std::acos(cosPA); // pointing angle in degrees, the smaller the better
209+
float pcaScore = pca / 3.f; // assume pca is between 0 and 3
210+
float wCos = weight;
211+
float wPca = 1.f - wCos; // random values for now
212+
float score = wCos * cosScore + wPca * pcaScore;
213+
return score;
214+
}
215+
199216
#endif // PWGEM_PHOTONMESON_UTILS_PCMUTILITIES_H_

0 commit comments

Comments
 (0)