Skip to content

Commit fb7fe7f

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 fb7fe7f

2 files changed

Lines changed: 168 additions & 47 deletions

File tree

PWGEM/PhotonMeson/TableProducer/photonconversionbuilder.cxx

Lines changed: 151 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@
7272
#include <map>
7373
#include <set>
7474
#include <string>
75+
#include <tuple>
7576
#include <unordered_map>
7677
#include <utility>
7778
#include <vector>
@@ -103,6 +104,44 @@ enum TrackPropMode {
103104
kBoth = 2
104105
};
105106

107+
enum V0DeduplicationMode {
108+
Pairwise = 0, // old algorithm
109+
GreedyMatching = 1 // new algorithm
110+
};
111+
112+
// Struct needed for deduplication mode 1. Used to keep track of v0 properties and a score based on pca and cosPA
113+
struct V0CandidateHelper {
114+
int v0ID = -1;
115+
int colID = -1;
116+
int posID = -1;
117+
int eleID = -1;
118+
float cosPA = -1.f;
119+
float pca = -1.f;
120+
float score = -1.f;
121+
122+
V0CandidateHelper() = default;
123+
124+
V0CandidateHelper(int v0, int col, int pos, int ele, float cpa, float p, float s)
125+
: v0ID(v0), colID(col), posID(pos), eleID(ele), cosPA(cpa), pca(p), score(s)
126+
{
127+
}
128+
129+
bool operator==(const V0CandidateHelper& other) const
130+
{
131+
return score == other.score;
132+
}
133+
134+
bool operator<(const V0CandidateHelper& other) const
135+
{
136+
return score < other.score;
137+
}
138+
139+
bool operator>(const V0CandidateHelper& other) const
140+
{
141+
return score > other.score;
142+
}
143+
};
144+
106145
struct PhotonConversionBuilder {
107146
Produces<aod::V0PhotonsKF> v0photonskf;
108147
Produces<aod::V0Legs> v0legs;
@@ -124,6 +163,8 @@ struct PhotonConversionBuilder {
124163
Configurable<double> d_bz_input{"d_bz", -999, "bz field, -999 is automatic"};
125164
Configurable<int> useMatCorrType{"useMatCorrType", 0, "0: none, 1: TGeo, 2: LUT"};
126165
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)"};
166+
Configurable<int> deduplicationMode{"deduplicationMode", 0, "0: Pairwise deduplication, 1: Based on Greedy matching (best score wins)"};
167+
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"};
127168

128169
// single track cuts
129170
Configurable<int> min_ncluster_tpc{"min_ncluster_tpc", 0, "min ncluster tpc"};
@@ -343,6 +384,14 @@ struct PhotonConversionBuilder {
343384
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}}});
344385
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}}});
345386
}
387+
388+
// Make sure the deduplicationScoreWeight is between 0 and 1
389+
if (deduplicationScoreWeight > 1.f) { // o2-linter: disable=magic-number (score has to be below unity)
390+
LOG(warning) << "deduplicationScoreWeight is larger than unity which is not allowed";
391+
}
392+
if (deduplicationScoreWeight < 0.f) { // o2-linter: disable=magic-number (score has to be above zero)
393+
LOG(warning) << "deduplicationScoreWeight is smaller than zero which is not allowed";
394+
}
346395
}
347396

348397
void initCCDB(aod::BCsWithTimestamps::iterator const& bc)
@@ -585,7 +634,7 @@ struct PhotonConversionBuilder {
585634
std::array<float, 2> dcaInfoFast = dcaInfo;
586635
if (modeTrackPropagation != TrackPropMode::kFast) {
587636
o2::base::Propagator::Instance()->propagateToDCABxByBz({collision.posX(), collision.posY(), collision.posZ()}, pTrackC, 2.f, matCorr, &dcaInfo);
588-
if (modeTrackPropagation == TrackPropMode::kBoth) {
637+
if (filltable && modeTrackPropagation == TrackPropMode::kBoth) {
589638
registry.fill(HIST("V0Leg/hDCAxyPropagationCompare"), dcaInfo[0], dcaInfoFast[0]);
590639
registry.fill(HIST("V0Leg/hDCAzPropagationCompare"), dcaInfo[1], dcaInfoFast[1]);
591640
}
@@ -607,7 +656,7 @@ struct PhotonConversionBuilder {
607656
dcaInfoFast = dcaInfo;
608657
if (modeTrackPropagation != TrackPropMode::kFast) {
609658
o2::base::Propagator::Instance()->propagateToDCABxByBz({collision.posX(), collision.posY(), collision.posZ()}, nTrackC, 2.f, matCorr, &dcaInfo);
610-
if (modeTrackPropagation == TrackPropMode::kBoth) {
659+
if (filltable && modeTrackPropagation == TrackPropMode::kBoth) {
611660
registry.fill(HIST("V0Leg/hDCAxyPropagationCompare"), dcaInfo[0], dcaInfoFast[0]);
612661
registry.fill(HIST("V0Leg/hDCAzPropagationCompare"), dcaInfo[1], dcaInfoFast[1]);
613662
}
@@ -856,6 +905,10 @@ struct PhotonConversionBuilder {
856905
pca_map[std::make_tuple(v0.globalIndex(), collision.globalIndex(), pos.globalIndex(), ele.globalIndex())] = v0photoncandidate.getPCA();
857906
cospa_map[std::make_tuple(v0.globalIndex(), collision.globalIndex(), pos.globalIndex(), ele.globalIndex())] = v0photoncandidate.getCosPA();
858907

908+
const auto score = getScoreV0(v0photoncandidate.getCosPA(), v0photoncandidate.getPCA(), deduplicationScoreWeight);
909+
V0CandidateHelper v0Helper(v0.globalIndex(), collision.globalIndex(), pos.globalIndex(), ele.globalIndex(), v0photoncandidate.getCosPA(), v0photoncandidate.getPCA(), score);
910+
vecV0Dedup.emplace_back(v0Helper);
911+
859912
if (applyPCMMl) {
860913
bool isSelectedML = false;
861914
std::vector<float> mlInputFeatures = emMlResponse.getInputFeatures(v0photoncandidate, pos, ele);
@@ -953,6 +1006,7 @@ struct PhotonConversionBuilder {
9531006
Preslice<aod::V0s> perCollision = o2::aod::v0::collisionId;
9541007
std::map<std::tuple<int64_t, int64_t, int64_t, int64_t>, float> pca_map; // (v0.globalIndex(), collision.globalIndex(), pos.globalIndex(), ele.globalIndex()) -> pca
9551008
std::map<std::tuple<int64_t, int64_t, int64_t, int64_t>, float> cospa_map; // (v0.globalIndex(), collision.globalIndex(), pos.globalIndex(), ele.globalIndex()) -> cospa
1009+
std::vector<V0CandidateHelper> vecV0Dedup; // vector with all V0Candidates that is used to sort them by score (see struct V0CandidateHelper for more details of content)
9561010
std::vector<std::pair<int64_t, int64_t>> stored_v0Ids; // (pos.globalIndex(), ele.globalIndex())
9571011
std::vector<std::tuple<int64_t, int64_t, int64_t, int64_t>> stored_fullv0Ids; // (v0.globalIndex(), collision.globalIndex(), pos.globalIndex(), ele.globalIndex())
9581012
std::unordered_map<int64_t, int> nv0_map; // map collisionId -> nv0
@@ -985,6 +1039,7 @@ struct PhotonConversionBuilder {
9851039

9861040
updateCCDB(bc); // delay update until is needed
9871041

1042+
vecV0Dedup.reserve(30000); // rough estimate for number of V0s per DF
9881043
const auto& v0s_per_coll = v0s.sliceBy(perCollision, collision.globalIndex());
9891044
// LOGF(info, "n v0 = %d", v0s_per_coll.size());
9901045
for (const auto& v0 : v0s_per_coll) {
@@ -997,56 +1052,104 @@ struct PhotonConversionBuilder {
9971052
stored_fullv0Ids.reserve(pca_map.size()); // number of photon candidates per DF
9981053

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