// JetsWithoutJets Package
// Questions/Comments? danbert@mit.edu jthaler@mit.edu
//
// Copyright (c) 2013
// Daniele Bertolini and Jesse Thaler
//
// $Id: $
//----------------------------------------------------------------------
// This file is part of FastJet contrib.
//
// It is free software; you can redistribute it and/or modify it under
// the terms of the GNU General Public License as published by the
// Free Software Foundation; either version 2 of the License, or (at
// your option) any later version.
//
// It is distributed in the hope that it will be useful, but WITHOUT
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
// License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this code. If not, see .
//----------------------------------------------------------------------
#include "JetsWithoutJets.hh"
#include
using namespace std;
FASTJET_BEGIN_NAMESPACE // defined in fastjet/internal/base.hh
namespace contrib {
//////
//
// Storage Array
//
//////
// Creates storage array
void JWJStorageArray::establishStorage(const vector & my_jets, double Rjet, double ptcut) {
// set radius and ptcut
_Rjet = Rjet;
_ptcut = ptcut;
FunctorJetScalarPt sumPt = FunctorJetScalarPt();
// Dividing up Phase Space to bins of (approximate) size 2R by 2R
// Actually makes bins of size bigger than 2R by 2R such that /
// we have equal spacing of regions.
// (This index scheme need to be synced with getRapIndex/getPhiIndex)
_maxRapIndex = (int) floor((_rapmax)/_Rjet);// 2 rapmax / 2 Rjet
_rapSpread = 2.0*_rapmax/(floor((_rapmax)/_Rjet));
_maxPhiIndex = (int) floor((M_PI)/_Rjet); // 2 pi / 2 Rjet
_phiSpread = 2.0*M_PI/(floor((M_PI)/_Rjet));
// Initializing storage
_regionStorage.resize(_maxRapIndex);
_aboveCutBool.resize(_maxRapIndex);
for (int rapIndex = 0 ; rapIndex < _maxRapIndex ; rapIndex++) {
_regionStorage[rapIndex].resize(_maxPhiIndex);
_aboveCutBool[rapIndex].resize(_maxPhiIndex);
for (int phiIndex = 0; phiIndex < _maxPhiIndex; phiIndex++) {
_regionStorage[rapIndex][phiIndex].clear();
}
}
// looping through particles and assigning to bins
for (unsigned int i = 0; i < my_jets.size(); i++) {
PseudoJet currentJet = my_jets[i];
double rap = currentJet.rap();
double phi = currentJet.phi();
// find index for particle
int lowRap = (int) floor((rap + _rapmax)/_rapSpread);
int highRap = (int) ceil((rap + _rapmax)/_rapSpread);
int lowPhi = (int) floor(phi/_phiSpread);
int highPhi = (int) ceil(phi/_phiSpread);
if (highPhi >= _maxPhiIndex) highPhi = highPhi - _maxPhiIndex; // loop around
// if out of bounds, set to be in bounds.
if (lowRap < 0) lowRap = 0;
if (lowRap >= _maxRapIndex) lowRap = _maxRapIndex-1;
if (highRap < 0) highRap = 0;
if (highRap >= _maxRapIndex) highRap = _maxRapIndex-1;
// Fill in storage. For particles at periphery in rapidity, only fill two bins instead of four. If phi overlaps, do the same thing.
_regionStorage[lowRap][lowPhi].push_back(currentJet);
if (lowPhi != highPhi) _regionStorage[lowRap][highPhi].push_back(currentJet);
if (lowRap != highRap) _regionStorage[highRap][lowPhi].push_back(currentJet);
if (lowRap != highRap && lowPhi != highPhi) _regionStorage[highRap][highPhi].push_back(currentJet);
}
// store whether above ptCut values
for (int rapIndex = 0; rapIndex < _maxRapIndex; rapIndex++) {
for (int phiIndex = 0; phiIndex < _maxPhiIndex; phiIndex++) {
_aboveCutBool[rapIndex][phiIndex] = (sumPt(_regionStorage[rapIndex][phiIndex]) >= ptcut);
}
}
}
// What is the rapidity index? (Sync with JWJStorageArray::establishStorage)
int JWJStorageArray::getRapIndex(const fastjet::PseudoJet & currentJet) const {
double rap = currentJet.rap();
int rapIndex = round((rap + _rapmax)/_rapSpread);
if (rapIndex < 0) rapIndex = 0;
if (rapIndex >= _maxRapIndex) rapIndex = _maxRapIndex - 1;
return rapIndex;
}
// what is the phi index? (Sync with JWJStorageArray::establishStorage)
int JWJStorageArray::getPhiIndex(const fastjet::PseudoJet & currentJet) const {
double phi = currentJet.phi();
int phiIndex = round(phi/_phiSpread);
if (phiIndex >= _maxPhiIndex) phiIndex = phiIndex - _maxPhiIndex;
return phiIndex;
}
vector & JWJStorageArray::getStorageFor(const fastjet::PseudoJet & currentJet) {
return _regionStorage[getRapIndex(currentJet)][getPhiIndex(currentJet)];
}
bool JWJStorageArray::aboveCutFor(const fastjet::PseudoJet & currentJet) {
return _aboveCutBool[getRapIndex(currentJet)][getPhiIndex(currentJet)];
}
//////
//
// Extendable Jet-like Event Shape
//
//////
// If called, this helper function establishes the storage array for speed up
void JetLikeEventShape::establishStorage(const std::vector & particles) const {
if (_useStorageArray) {
_myStorageArray.establishStorage(particles,_Rjet, _ptcut);
} else {
// do nothing
}
}
// This helper function calculates the weights assigned to each particle.
// In most cases, the user does not need to access the information set by this function
void JetLikeEventShape::establishWeights(const PseudoJet myParticle, const std::vector & particles) const {
FunctorJetScalarPt sumPt = FunctorJetScalarPt();
_includeParticle = false;
_weightParticle = 0.0;
_pt_in_Rjet = 0.0;
_pt_in_Rsub = 0.0;
// Find particles in my _Rjet neighborhood
fastjet::Selector sel = fastjet::SelectorCircle(_Rjet);
sel.set_reference(myParticle);
if (_useStorageArray) { // start from rapidity/phi blocks
if (!_myStorageArray.aboveCutFor(myParticle)) {
return; //don't do any further analysis if not above jet pTcut
} else {
_nearbyParticles = sel(_myStorageArray.getStorageFor(myParticle));
}
} else { // use all particles
_nearbyParticles = sel(particles);
}
// See if there is enough pt in the neighborhood to pass _ptcut
_pt_in_Rjet = sumPt(_nearbyParticles);
if (_pt_in_Rjet < _ptcut) return;
// If trimming is on, do check of _fcut as well
if (_trim) {
assert(_Rsub <= _Rjet);
fastjet::Selector sel_sub = fastjet::SelectorCircle(_Rsub);
sel_sub.set_reference(myParticle);
std::vector near_particles_sub = sel_sub(_nearbyParticles); // Save time and only look at near particles
_pt_in_Rsub = sumPt(near_particles_sub);
if (_pt_in_Rsub/_pt_in_Rjet < _fcut) return;
}
// If enough pt, find weight and apply measurement
_includeParticle = true;
_weightParticle = myParticle.pt()/_pt_in_Rjet;
}
// For a generic event shape, the result is obtained by simply multiplying the weight
// of a given particle by the _measurement acting on the _nearbyParticles
double JetLikeEventShape::result(const std::vector & particles) const {
double answer = 0.0;
establishStorage(particles);
for (unsigned int i = 0; i < particles.size(); i++) {
establishWeights(particles[i],particles);
if (_includeParticle) {
double measurement = _measurement->result(_nearbyParticles);
answer += measurement*_weightParticle;
}
}
return(answer);
}
//////
//
// MissingTransverseMomentum
//
//////
// To calculate missing pT, we cannot use a standard _measurement, so we code this by hand.
double ShapeMissingPt::result(const std::vector & particles) const {
PseudoJet p_tot(0,0,0,0);
establishStorage(particles);
for (unsigned int i = 0; i < particles.size(); i++) {
establishWeights(particles[i],particles);
if (_includeParticle) {
p_tot += particles[i];
}
}
return(p_tot.pt());
}
string ShapeMissingPt::description() const {
return "Missing Transverse Momentum as event shape, " + jetParameterString();
}
//////
//
// TrimmedSubjetMultiplicity
//
//////
// While it would be possible to calculate trimmed subjet multiplicity using a
// _measurement functor, it is computationally more efficient to use the information
// already captured by establishWeights().
double ShapeTrimmedSubjetMultiplicity::result(const vector & particles) const {
double answer = 0.0;
establishStorage(particles);
for (unsigned int i = 0; i < particles.size(); i++) {
establishWeights(particles[i],particles);
if (_includeParticle && _pt_in_Rsub > _ptsubcut) {
answer += particles[i].pt()/_pt_in_Rsub;
}
}
return(answer);
}
string ShapeTrimmedSubjetMultiplicity::description() const {
ostringstream oss;
oss << "Trimmed Subjet multiplicity with R_jet=" << _Rjet << ", pT_cut=" << _ptcut << ", R_sub=" << _Rsub << ", and fcut=" << _fcut;
return oss.str();
}
//////
//
// Shape Trimming
//
//////
//----------------------------------------------------------------------
// Helper for SelectorShapeTrimming.
// Class derived from SelectorWorker: pass, terminator, applies_jet_by_jet, and description are overloaded.
// It can't be applied to an individual jet since it requires information about the neighbourhood of the jet.
class SW_ShapeTrimming: public SelectorWorker {
private:
double _Rjet, _ptcut, _Rsub, _fcut;
bool _useTempStorage;
public:
SW_ShapeTrimming(double Rjet, double ptcut, double Rsub, double fcut, bool useTempStorage = true) : _Rjet(Rjet), _ptcut(ptcut), _Rsub(Rsub), _fcut(fcut), _useTempStorage(useTempStorage) {};
virtual bool pass(const PseudoJet &) const {
if (!applies_jet_by_jet())
throw Error("Cannot apply this selector worker to an individual jet");
return false;
}
virtual void terminator(vector & jets) const {
// Copy pointers content to a vector of PseudoJets. Check if the pointer has been already nullified.
vector my_jets;
vector indices;
for (unsigned int i=0; i < jets.size(); i++){
if (jets[i]){
indices.push_back(i);
my_jets.push_back(*jets[i]);
}
}
FunctorJetScalarPt sumPt = FunctorJetScalarPt();
JWJStorageArray myStorageArray = JWJStorageArray();
// to increase speed in very high multiplicity events, make a grid of rapidity/phi blocks
if (_useTempStorage) {
myStorageArray.establishStorage(my_jets,_Rjet,_ptcut);
}
for (unsigned int i=0; i < my_jets.size(); i++){
// Select particles in radius _Rjet
fastjet::Selector sel = fastjet::SelectorCircle(_Rjet);
sel.set_reference(my_jets[i]);
vector near_particles;
if (_useTempStorage) { // start from rapidity/phi blocks
if (!myStorageArray.aboveCutFor(my_jets[i])) {
jets[indices[i]] = NULL;
continue; // no need to consider that particle further
} else {
near_particles = sel(myStorageArray.getStorageFor(my_jets[i]));
}
} else { // use all particles
near_particles = sel(my_jets);
}
// Calculate enclosed pT
double pt_Rjet = sumPt(near_particles);
// Check if pT is enough
// Need to have < instead of <= in order to have consistent description with shape variables
if (pt_Rjet < _ptcut) {
jets[indices[i]] = NULL;
continue;
}
// If so, select particles in radius
fastjet::Selector sel_sub = fastjet::SelectorCircle(_Rsub);
sel_sub.set_reference(my_jets[i]);
vector near_particles_sub = sel_sub(near_particles);
// Calculate enclosed pT
double pt_Rsub = sumPt(near_particles_sub);
// Need to have < instead of <= in order to have consistent description with shape variables
if (pt_Rsub/pt_Rjet < _fcut) {
jets[indices[i]] = NULL;
continue;
}
}
}
virtual bool applies_jet_by_jet() const {return false;}
std::string jetParameterString() const {
std::stringstream stream;
stream << "R_jet=" << _Rjet << ", pT_cut=" << _ptcut << ", R_sub=" << _Rsub <<", fcut=" << _fcut;
return stream.str();
}
virtual string description() const {
return "Shape trimmer, " + jetParameterString();
}
};
//----------------------------------------------------------------------
// Helper for SelectorJetShapeTrimming.
// Class derived from SelectorWorker: pass, terminator, applies_jet_by_jet, and description are overloaded.
// This cannot be applied to an individual jet.
class SW_JetShapeTrimming: public SelectorWorker {
private:
double _Rsub, _fcut;
public:
SW_JetShapeTrimming(double Rsub, double fcut) : _Rsub(Rsub), _fcut(fcut) {};
virtual bool pass(const PseudoJet &) const {
if (!applies_jet_by_jet())
throw Error("Cannot apply this selector worker to an individual jet");
return false;
}
virtual void terminator(vector & jets) const {
// Copy pointers content to a vector of PseudoJets. Check if the pointer has been already nullified.
vector my_jets;
vector indices;
for (unsigned int i=0; i < jets.size(); i++){
if (jets[i]){
indices.push_back(i);
my_jets.push_back(*jets[i]);
}
}
FunctorJetScalarPt sumPt = FunctorJetScalarPt();
// take sum pt of given jet
double pt_Rjet = sumPt(my_jets);
for (unsigned int i=0; i < my_jets.size(); i++){
// Select particles in sub radius
fastjet::Selector sel_sub = fastjet::SelectorCircle(_Rsub);
sel_sub.set_reference(my_jets[i]);
vector near_particles_sub = sel_sub(my_jets);
// Calculate enclosed pT
double pt_Rsub = sumPt(near_particles_sub);
// Need to have < instead of <= in order to have consistent description with shape variables
if (pt_Rsub/pt_Rjet < _fcut) {
jets[indices[i]] = NULL;
continue;
}
}
}
virtual bool applies_jet_by_jet() const {return false;}
std::string jetParameterString() const {
std::stringstream stream;
stream << "R_sub=" << _Rsub <<", fcut=" << _fcut;
return stream.str();
}
virtual string description() const {
return "Jet shape trimmer, " + jetParameterString();
}
};
Selector SelectorShapeTrimming(double Rjet, double ptcut, double Rsub, double fcut){
return Selector(new SW_ShapeTrimming(Rjet,ptcut,Rsub,fcut));
}
Selector SelectorJetShapeTrimming(double Rsub, double fcut){
return Selector(new SW_JetShapeTrimming(Rsub,fcut));
}
//////
//
// Variable pT_cut
//
//////
// helper to sort a vector of vector by comparing just the first entry.
bool _mySortFunction (std::vector v_0, std::vector v_1) { return (v_0[0] > v_1[0]); }
// As described in the appendix of the physics paper, one can construct the inverse
// of an event shape with respect to pT by calculating all of the possible pTi,R values and sorting them.
// This helper function accomplishes that task.
void JetLikeEventShape_VariablePtCut::_buildStepFunction(const std::vector particles) const {
FunctorJetScalarPt sumPt = FunctorJetScalarPt();
fastjet::Selector sel = fastjet::SelectorCircle(_Rjet);
_pt_in_Rjet = 0.0;
_pt_in_Rsub = 0.0;
std::vector< std::vector > myValues;
myValues.resize(0);
// this acts much like establishWeights did for JetLikeEventShape,
// except each value of _pt_in_Rjet is associated with the corresponding
// weighted measurement.
for (unsigned int i = 0; i < particles.size(); i++){
sel.set_reference(particles[i]);
_nearbyParticles = sel(particles);
_pt_in_Rjet = sumPt(_nearbyParticles);
if(_trim) {
assert(_Rsub <= _Rjet);
fastjet::Selector sel_sub = fastjet::SelectorCircle(_Rsub);
sel_sub.set_reference(particles[i]);
std::vector near_particles_sub = sel_sub(_nearbyParticles);
_pt_in_Rsub = sumPt(near_particles_sub);
if (_pt_in_Rsub / _pt_in_Rjet >= _fcut) {
double measurement = _measurement->result(_nearbyParticles);
std::vector point(2);
point[0] = _pt_in_Rjet;
point[1] = measurement*particles[i].pt()/_pt_in_Rjet;
myValues.push_back(point);
}
}
else {
double measurement = _measurement->result(_nearbyParticles);
std::vector point(2);
point[0] = _pt_in_Rjet;
point[1] = measurement*particles[i].pt()/_pt_in_Rjet;
myValues.push_back(point);
}
}
std::sort (myValues.begin(), myValues.end(), _mySortFunction);
// Calculating the cumulative sum of the measurements up to a given pt value
_ptR_values.resize(0);
_eventShape_values.resize(0);
if (!myValues.empty()){
_ptR_values.push_back(myValues[0][0]);
_eventShape_values.push_back(myValues[0][1]);
for (unsigned int i = 1; i < myValues.size(); i++){
_ptR_values.push_back(myValues[i][0]);
_eventShape_values.push_back(_eventShape_values[i-1] + myValues[i][1]);
}
}
myValues.clear();
}
double JetLikeEventShape_VariablePtCut::eventShapeFor(const double ptcut_0) const {
// TODO: find a more efficient data structure to do this searching
double eventShape = 0.0;
if ( ptcut_0 <= _ptR_values.back() ) eventShape = _eventShape_values.back();
else if (ptcut_0 > _ptR_values.back() && ptcut_0 <= _ptR_values.front()) {
for (unsigned int i = 0; i < _ptR_values.size()-1; i++){
if (ptcut_0 <= _ptR_values[i] && ptcut_0 > _ptR_values[i+1]){
eventShape = _eventShape_values[i];
break;
}
}
}
return (eventShape);
}
double JetLikeEventShape_VariablePtCut::ptCutFor(const double eventShape_0) const {
// TODO: find a more efficient data structure to do this searching
double ptcut = 0.0;
double new_eventShape_0 = eventShape_0 - _offset;
if ( new_eventShape_0 < 0 || new_eventShape_0 > _eventShape_values.back() ) {
cout << ">>> ERROR: "<< _measurement->description() <<" min allowed value is 0, max allowed value is " << _eventShape_values.back() << endl;
} else if ( new_eventShape_0 <= _eventShape_values.front() ) ptcut = _ptR_values.front();
else {
for (unsigned int i = 0; i < _eventShape_values.size()-1; i++){
if (new_eventShape_0 > _eventShape_values[i] && new_eventShape_0 <= _eventShape_values[i+1]){
ptcut = _ptR_values[i+1];
break;
}
}
}
return(ptcut);
}
//////
//
// Njet with variable R_jet
//
//////
// Similar to the same named function in JetLikeEventShape_VariablePtCut, except now we have
// to store a lot more information.
void ShapeJetMultiplicity_VariableR::_buildStepFunction(const std::vector particles) const {
unsigned int N = particles.size();
FunctorJetScalarPt sumPt = FunctorJetScalarPt();
std::vector< std::vector > myValues;
myValues.resize(0);
for(unsigned int i = 0; i < N; i++) {
unsigned int j = i+1;
while(j < N) {
vector myPair(3);
myPair[0] = particles[i].delta_R(particles[j]);
myPair[1] = i;
myPair[2] = j;
myValues.push_back(myPair);
j++;
}
}
std::sort (myValues.begin(), myValues.end(), _mySortFunction);
_eventShape_values.resize(0);
_dR_values.resize(0);
// Initialize
double _tot_pt = sumPt(particles);
if(_tot_pt >= _ptcut) _eventShape_values.push_back(1.0);
else _eventShape_values.push_back(0.0);
std::vector pTR(N);
std::fill(pTR.begin(), pTR.end(), _tot_pt);
if (_trim) {
std::vector pTRsub;
fastjet::Selector sel_sub = fastjet::SelectorCircle(_Rsub);
for (unsigned int i = 0; i < N; i++) {
sel_sub.set_reference(particles[i]);
std::vector near_particles_sub = sel_sub(particles);
pTRsub.push_back(sumPt(near_particles_sub));
}
for (unsigned int i = 0; i < myValues.size(); i++) {
_dR_values.push_back(myValues[i][0]);
int id1 = (int)myValues[i][1];
int id2 = (int)myValues[i][2];
pTR[id1] -= particles[id2].pt();
pTR[id2] -= particles[id1].pt();
double myEventShape = 0;
for(unsigned int j = 0; j < N; j++) {
if(pTR[j] >= _ptcut && pTRsub[j] / pTR[j] >= _fcut) myEventShape += particles[j].pt() / pTR[j];
}
_eventShape_values.push_back(myEventShape);
}
}
else {
for (unsigned int i = 0; i < myValues.size(); i++) {
_dR_values.push_back(myValues[i][0]);
int id1 = (int)myValues[i][1];
int id2 = (int)myValues[i][2];
pTR[id1] -= particles[id2].pt();
pTR[id2] -= particles[id1].pt();
double myEventShape = 0;
for(unsigned int j = 0; j < N; j++) {
if(pTR[j] >= _ptcut) myEventShape += particles[j].pt() / pTR[j];
}
_eventShape_values.push_back(myEventShape);
}
}
_dR_values.push_back(0.0);
myValues.clear();
}
// returns event shape for a given Rjet value
double ShapeJetMultiplicity_VariableR::eventShapeFor(const double Rjet_0) const {
double eventShape = 0.0;
if( Rjet_0 < _Rsub ) cout <<">>> WARNING: R_jet < R_sub= " << _Rsub << endl;
if( Rjet_0 >= _dR_values.front() ) eventShape = _eventShape_values.front();
else if ( Rjet_0 < _dR_values.front() && Rjet_0 >= _dR_values.back() ){
//-GPS very dangerous to do -1 on a size: if size=0 then, because it's unsigned,
//- you end up with a value that's equal to to 2^nbits-1 where nbits=32 or 64.
for(unsigned int i = 0; i < _dR_values.size()-1; i++) {
if( Rjet_0 < _dR_values[i] && Rjet_0 >= _dR_values[i+1]) {
eventShape = _eventShape_values[i+1];
break;
}
}
}
else cout <<">>> ERROR: R_jet should be >= 0" << endl;
return(eventShape);
}
//////
//
// Finding Jet Axes with the Event Shape Density
//
//////
// Find the local axes in a region of size R around each particle
void EventShapeDensity_JetAxes::_find_local_axes(const std::vector & particles) const {
_myParticles = particles;
_N = particles.size();
_axes.resize(0);
_Njet_weights.resize(0);
_pt_weights.resize(0);
// Indexing particles (note that this is happening internally, so shouldn't modify user input)
for (unsigned int i=0; i<_N; i++) _myParticles[i].set_user_index(i);
// For speed up
if (_useStorageArray) {
_myStorageArray.establishStorage(_myParticles,_Rjet, _ptcut);
}
// Find axis associated with each particle.
for (unsigned int i=0; i<_N; i++) {
PseudoJet myParticle = _myParticles[i];
FunctorJetScalarPt sumPt = FunctorJetScalarPt();
//Recombiner has to carry user_index information.
FunctorJetAxis axis(_jetDef);
fastjet::Selector sel = SelectorCircle(_Rjet);
sel.set_reference(myParticle);
vector nearbyParticles;
if (_useStorageArray) { // start from phi/rapidity strips
if (!_myStorageArray.aboveCutFor(myParticle)) {
nearbyParticles.resize(0); //don't do any further analysis if not above jet pTcut
} else {
nearbyParticles = sel(_myStorageArray.getStorageFor(myParticle));
}
} else { // use all particles
nearbyParticles = sel(_myParticles);
}
int myAxis = -1;
double pt_w = 0;
double Njet_w = 0;
double pt_in_Rjet = sumPt(nearbyParticles);
if(pt_in_Rjet >= _ptcut) {
myAxis = axis(nearbyParticles).user_index();
pt_w = _myParticles[i].pt();
Njet_w = pt_w / pt_in_Rjet;
}
_axes.push_back(myAxis);
_pt_weights.push_back(pt_w);
_Njet_weights.push_back(Njet_w);
}
}
// Take the axes calculated by _find_local_axes and figure out what the final
// global axes will be. With the global consistency condition, this decreases
// the number of axes under consideration.
void EventShapeDensity_JetAxes::find_axes_and_weights() const {
//If requested establish global consistency.
//Loop over axes and reassign unstable axes until no unstable axis is left.
if(_applyGlobalConsistency) {
int n_unstable_axes;
do {
n_unstable_axes = 0;
for (unsigned int i=0; i<_N; i++) {
if(_axes[i]!= -1 && !_isStable(_axes[i])){
n_unstable_axes++;
_axes[i] = _axes[_axes[i]];
}
}
} while(n_unstable_axes>0);
}
//Find distinct axes.
//Output a vector of Pseudojets (sorted by pT), with pT of each axis corresponding to the pT weight.
vector tot_Njet_weights(_N,0), tot_pt_weights(_N,0);
for (unsigned int i=0; i<_N; i++) {
if (_axes[i] != -1) {
tot_pt_weights[_axes[i]] += _pt_weights[i];
tot_Njet_weights[_axes[i]] += _Njet_weights[i];
}
}
_distinctAxes.resize(0);
_tot_Njet_weights.resize(0);
for (unsigned int i=0; i<_N; i++){
if(tot_pt_weights[i] > 0) {
PseudoJet myAxis = _lightLikeVersion(_myParticles[i]) * tot_pt_weights[i];
_distinctAxes.push_back(myAxis);
}
}
_distinctAxes = sorted_by_pt(_distinctAxes);
//Find the corresponding N_jet weights.
for(unsigned int i=0; i<_distinctAxes.size(); i++) _tot_Njet_weights.push_back(tot_Njet_weights[_distinctAxes[i].user_index()]);
}
// Check stability: a particle defines a stable wta axis if it is its own wta or if it is pointing to a particle that does not pass pTcut.
bool EventShapeDensity_JetAxes::_isStable(const int thisAxis) const {
bool answer = false;
if(_axes[thisAxis] == thisAxis || _axes[thisAxis] == -1) answer = true;
return(answer);
}
} // namespace contrib
FASTJET_END_NAMESPACE