-
Notifications
You must be signed in to change notification settings - Fork 441
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
TRD: PID: fixups #11664
Merged
Merged
TRD: PID: fixups #11664
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
# Particle Identification with TRD | ||
## Usage | ||
Activate PID during tracking with the '--with-pid' flag. | ||
|
||
o2-trd-global-tracking --with-pid --policy ML | ||
|
||
Specify a which algorithm (called policy) should be use. | ||
Implemented are the following: | ||
|
||
- LQ1D | ||
- LQ2D | ||
- LQ3D | ||
- ML (every model, which is exported to the ONNX format): | ||
- XGB (XGBoost model) | ||
- NN (Pytorch model) | ||
- Dummy (returns only -1) | ||
- Test (one of the above) | ||
- Default (one of the above, gets picked if '--policy' is unspecified) | ||
|
||
## Implementation details | ||
### Tracking workflow | ||
Every TRDTrack gets a PID value set (mSignal), which then gets propergated to the AO2D writer. | ||
|
||
### Basic Interface | ||
The base interface for PID is defined in [here](include/TRDPID/PIDBase.h). | ||
The 'init' function is such that each policy can specify what if anything it needs from the CCDB. | ||
For the 'process' each policy defines how a TRDTrack gets assigned a PID value. | ||
Additionally, the base class implements how to get the _corrected charges_ from the tracklets. | ||
_Corrected charges_ means z-row merged and calibrated charges. | ||
|
||
### Classical Likelihood | ||
The classical LQND policies ([here](include/TRDPID/LQND.h)) require an array of lookup tables (LUTs) from the ccdb. | ||
$N$ stands for the dimension. | ||
Then the electron likelihood for layer $i$ is defined as this: | ||
|
||
$$L_i(e|Q_i)=\frac{P(Q_i|e)}{P(Q_i|e)+P(Q_i|\pi)}$$ | ||
|
||
From the charge $Q_i$ the LUTs give the corresponding $L_i$. | ||
The _combined electron likelihood_ is obtained by this formula: | ||
|
||
$$L(e|Q)=\frac{\prod_i L_i(e|Q_i)}{\prod_i L_i(e|Q_i) + \prod_i L_i(\pi|Q_i)}$$ | ||
|
||
where $L_i(\pi|Q_i)=1-L_i(e|Q_i)$. | ||
|
||
|
||
Extension to higher dimensions is easy each tracklet has charges $Q_j$ which cover the integral of the pulse height spectrum in different slice ($j\in [0,1,2]$). | ||
In our case $Q0$ covers the pulse height peak, $Q1$ the Transition Radiation peak and $Q2$ the plateau. | ||
For each charge $j$ a LUT is available which gives the likelihood $L^e_j$. | ||
For each layer $i$ the likelihood is then: | ||
|
||
$$L_i(e|Q)=\frac{\prod_j L_{i,j}(e|Q_j)}{\prod_j L_{i,j}(e|Q_j) + \prod_j L_{i,j}(\pi|Q_j)}$$ | ||
|
||
The combined electron likelihood is then: | ||
|
||
$$L(e|Q)=\frac{\prod_{i,j} L_{i,j}(e|Q_j)}{\prod_{i,j} L_{i,j}(e|Q_j) + \prod_{i,j} L_{i,j}(\pi|Q_j)}$$ | ||
|
||
|
||
### Machine Learning | ||
The ML policies ([here](include/TRDPID/ML.h)) are uploaded to the CCDB in the ONNX file format (most python machine learning libraries support this standardized format). | ||
In O2 we leverage the ONNXRuntime to use these formats and calculate a PID value. | ||
The models can thus be trained in python which is very convenient. | ||
The code should take care of most of the annoying stuff. | ||
Policies just have to specify how to get the electron likelihood from the ONNXRuntime output (each python library varies in that somewhat). | ||
The 'prepareModelInput' prepares the TRDTracks as input. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,160 @@ | ||
// Copyright 2019-2020 CERN and copyright holders of ALICE O2. | ||
// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. | ||
// All rights not expressly granted are reserved. | ||
// | ||
// This software is distributed under the terms of the GNU General Public | ||
// License v3 (GPL Version 3), copied verbatim in the file "COPYING". | ||
// | ||
// In applying this license CERN does not waive the privileges and immunities | ||
// granted to it by virtue of its status as an Intergovernmental Organization | ||
// or submit itself to any jurisdiction. | ||
|
||
/// \file LQND.h | ||
/// \brief This file provides the interface for loglikehood policies | ||
/// \author Felix Schlepper | ||
|
||
#ifndef O2_TRD_LQND_H | ||
#define O2_TRD_LQND_H | ||
|
||
#include "TGraph.h" | ||
#include "TRDPID/PIDBase.h" | ||
#include "DataFormatsTRD/PID.h" | ||
#include "DataFormatsTRD/Constants.h" | ||
#include "DataFormatsTRD/HelperMethods.h" | ||
#include "Framework/ProcessingContext.h" | ||
#include "Framework/InputRecord.h" | ||
#include "DataFormatsTRD/CalibratedTracklet.h" | ||
#include "DetectorsBase/Propagator.h" | ||
#include "Framework/Logger.h" | ||
#include "ReconstructionDataFormats/TrackParametrization.h" | ||
|
||
#include <memory> | ||
#include <vector> | ||
#include <array> | ||
#include <string> | ||
#include <numeric> | ||
|
||
namespace o2 | ||
{ | ||
namespace trd | ||
{ | ||
namespace detail | ||
{ | ||
/// Lookup Table class for ccdb upload | ||
template <int nDim> | ||
class LUT | ||
{ | ||
public: | ||
LUT() = default; | ||
LUT(std::vector<float> p, std::vector<TGraph> l) : mIntervalsP(p), mLUTs(l) {} | ||
|
||
// | ||
const TGraph& get(float p, bool isNegative, int iDim = 0) const | ||
{ | ||
auto upper = std::upper_bound(mIntervalsP.begin(), mIntervalsP.end(), p); | ||
if (upper == mIntervalsP.end()) { | ||
// outside of momentum intervals, should not happen | ||
return mLUTs[0]; | ||
} | ||
auto index = std::distance(mIntervalsP.begin(), upper); | ||
index += (isNegative) ? 0 : mIntervalsP.size() * nDim; | ||
return mLUTs[index + iDim]; | ||
} | ||
|
||
private: | ||
std::vector<float> mIntervalsP; ///< half-open interval upper bounds starting at 0, e.g., for {1.0,2.0,...} is (-inf,1.0], (1.0,2.0], (2.0, ...) | ||
std::vector<TGraph> mLUTs; ///< corresponding likelihood lookup tables | ||
|
||
ClassDefNV(LUT, 1); | ||
}; | ||
} // namespace detail | ||
|
||
/// This is the ML Base class which defines the interface all machine learning | ||
/// models. | ||
template <int nDim> | ||
class LQND : public PIDBase | ||
{ | ||
static_assert(nDim == 1 || nDim == 2 || nDim == 3, "Likelihood only for 1/2/3 dimension"); | ||
using PIDBase::PIDBase; | ||
|
||
public: | ||
~LQND() = default; | ||
|
||
void init(o2::framework::ProcessingContext& pc) final | ||
{ | ||
// retrieve lookup tables (LUTs) from ccdb | ||
mLUTs = *(pc.inputs().get<detail::LUT<nDim>*>(Form("lq%ddlut", nDim))); | ||
} | ||
|
||
float process(const TrackTRD& trkIn, const o2::globaltracking::RecoContainer& input, bool isTPCTRD) const final | ||
{ | ||
const auto& trkSeed = isTPCTRD ? input.getTPCTracks()[trkIn.getRefGlobalTrackId()].getParamOut() : input.getTPCITSTracks()[trkIn.getRefGlobalTrackId()].getParamOut(); // seeding track | ||
auto trk = trkSeed; | ||
|
||
const auto isNegative = std::signbit(trkSeed.getSign()); // positive and negative charged particles are treated differently since ExB effects the charge distributions | ||
const auto& trackletsRaw = input.getTRDTracklets(); | ||
float lei0{1.f}, lei1{1.f}, lei2{1.f}, lpi0{1.f}, lpi1{1.f}, lpi2{1.f}; // likelihood per layer | ||
for (int iLayer = 0; iLayer < constants::NLAYER; ++iLayer) { | ||
int trkltId = trkIn.getTrackletIndex(iLayer); | ||
if (trkltId < 0) { // no tracklet attached | ||
continue; | ||
} | ||
const auto xCalib = input.getTRDCalibratedTracklets()[trkIn.getTrackletIndex(iLayer)].getX(); | ||
auto bz = o2::base::Propagator::Instance()->getNominalBz(); | ||
const auto tgl = trk.getTgl(); | ||
const auto snp = trk.getSnpAt(o2::math_utils::sector2Angle(HelperMethods::getSector(input.getTRDTracklets()[trkIn.getTrackletIndex(iLayer)].getDetector())), xCalib, bz); | ||
const auto& trklt = trackletsRaw[trkltId]; | ||
const auto [q0, q1, q2] = getCharges(trklt, iLayer, trkIn, input, snp, tgl); // correct charges | ||
if constexpr (nDim == 1) { | ||
auto lut = mLUTs.get(trk.getP(), isNegative); | ||
auto ll1{1.f}; | ||
ll1 = lut.Eval(q0 + q1 + q2); | ||
lei0 *= ll1; | ||
lpi0 *= (1.f - ll1); | ||
} else if (nDim == 2) { | ||
auto lut1 = mLUTs.get(trk.getP(), isNegative, 0); | ||
auto lut2 = mLUTs.get(trk.getP(), isNegative, 1); | ||
auto ll1{1.f}; | ||
auto ll2{1.f}; | ||
ll1 = lut1.Eval(q0 + q2); | ||
ll2 = lut2.Eval(q1); | ||
lei0 *= ll1; | ||
lei1 *= ll2; | ||
lpi0 *= (1.f - ll1); | ||
lpi1 *= (1.f - ll2); | ||
} else { | ||
auto lut1 = mLUTs.get(trk.getP(), isNegative, 0); | ||
auto lut2 = mLUTs.get(trk.getP(), isNegative, 1); | ||
auto lut3 = mLUTs.get(trk.getP(), isNegative, 2); | ||
auto ll1{1.f}; | ||
auto ll2{1.f}; | ||
auto ll3{1.f}; | ||
ll1 = lut1.Eval(q0); | ||
ll2 = lut2.Eval(q1); | ||
ll3 = lut3.Eval(q2); | ||
lei0 *= ll1; | ||
lei1 *= ll2; | ||
lei2 *= ll3; | ||
lpi0 *= (1.f - ll1); | ||
lpi1 *= (1.f - ll2); | ||
lpi2 *= (1.f - ll3); | ||
} | ||
} | ||
|
||
return (lei0 * lei1 * lei2) / (lei0 * lei1 * lei2 + lpi0 * lpi1 * lpi2); // combined likelihood | ||
} | ||
|
||
private: | ||
detail::LUT<nDim> mLUTs; ///< likelihood lookup tables | ||
|
||
ClassDefNV(LQND, 1); | ||
}; | ||
|
||
using LQ1D = LQND<1>; | ||
using LQ2D = LQND<2>; | ||
using LQ3D = LQND<3>; | ||
|
||
} // namespace trd | ||
} // namespace o2 | ||
|
||
#endif |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like the way to have the LUT as a class with a configurable number of dimensions. Would it be possible to make it accept not only 1 and 3D, but also things like 2 or 7D for example? I think we had such LUTs in the past in Run 2
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
7D seems nonsensical since we only get 3 windows from the FEE anyways, this was different in Run 2 were we would get 7 windows, I think.
How would you combine the windows in the 2D case (Q0+Q2 and Q1, seems the most resonable to me?)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ok 7D is probably indeed not needed, but 2D I could imagine (although no idea which windows would be best at the moment). Could you not get rid of the whole
if constexpr
block of the class? For 1D it will anyhow be called with iDim = 0 only and so you could returnmLUTS[index + iDim]
always, no?