--- /dev/null
+// MLMhooks.h is a part of the PYTHIA event generator.
+// Copyright (C) 2012 Torbjorn Sjostrand.
+// PYTHIA is licenced under the GNU GPL version 2, see COPYING for details.
+// Please respect the MCnet Guidelines, see GUIDELINES for details.
+
+// Author: Richard Corke (richard.corke@thep.lu.se)
+// This file provides the MLMhooks class to perform MLM merging.
+// Example usage is shown in main32.cc, and further details
+// can be found in the 'Alpgen and MLM Merging' manual page.
+
+#ifndef MLMHOOKS_H
+#define MLMHOOKS_H
+
+// Includes
+#include "Pythia.h"
+using namespace Pythia8;
+
+//==========================================================================
+
+// Preprocessor settings
+
+// Debug flag to give verbose information at all stages of the merging.
+//#define MLMDEBUG
+// Debug flag to enable some extra checks in the code
+//#define MLMCHECK
+
+//==========================================================================
+
+// Declaration of main MLMhooks class to perform MLM matching.
+// Note that it is defined with virtual inheritance, so that it can
+// be combined with other UserHooks classes, see e.g. main32.cc.
+
+class MLMhooks : virtual public UserHooks {
+
+public:
+
+ // Constructor and destructor
+ MLMhooks() : cellJet(NULL), slowJet(NULL) {}
+ ~MLMhooks() {
+ if (cellJet) delete cellJet;
+ if (slowJet) delete slowJet;
+ }
+
+ // Initialisation
+ virtual bool initAfterBeams();
+
+ // Process level vetos
+ virtual bool canVetoProcessLevel();
+ virtual bool doVetoProcessLevel(Event &);
+
+ // Parton level vetos (before beam remnants and resonance decays)
+ virtual bool canVetoPartonLevelEarly();
+ virtual bool doVetoPartonLevelEarly(const Event &);
+
+private:
+
+ // Different steps of the MLM matching algorithm
+ void sortIncomingProcess(const Event &);
+ void jetAlgorithmInput(const Event &, int);
+ void runJetAlgorithm();
+ bool matchPartonsToJets(int);
+ int matchPartonsToJetsLight();
+ int matchPartonsToJetsHeavy();
+
+ // DeltaR between two 4-vectors (eta and y variants)
+ inline double Vec4eta(const Vec4 &pIn) {
+ return -log(tan(pIn.theta() / 2.));
+ }
+ inline double Vec4y(const Vec4 &pIn) {
+ return 0.5 * log((pIn.e() + pIn.pz()) / (pIn.e() - pIn.pz()));
+ }
+ inline double deltaReta(const Vec4 &p1, const Vec4 &p2) {
+ double dEta = abs(Vec4eta(p1) - Vec4eta(p2));
+ double dPhi = abs(p1.phi() - p2.phi());
+ if (dPhi > M_PI) dPhi = 2. * M_PI - dPhi;
+ return sqrt(dEta*dEta + dPhi*dPhi);
+ }
+ inline double deltaRy(const Vec4 &p1, const Vec4 &p2) {
+ double dy = abs(Vec4y(p1) - Vec4y(p2));
+ double dPhi = abs(p1.phi() - p2.phi());
+ if (dPhi > M_PI) dPhi = 2. * M_PI - dPhi;
+ return sqrt(dy*dy + dPhi*dPhi);
+ }
+
+ // Function to sort typeIdx vectors into descending eT/pT order.
+ // Uses a selection sort, as number of partons generally small
+ // and so efficiency not a worry.
+ void sortTypeIdx(vector < int > &vecIn) {
+ for (size_t i = 0; i < vecIn.size(); i++) {
+ size_t jMax = i;
+ double vMax = (jetAlgorithm == 1) ?
+ eventProcess[vecIn[i]].eT() :
+ eventProcess[vecIn[i]].pT();
+ for (size_t j = i + 1; j < vecIn.size(); j++) {
+ double vNow = (jetAlgorithm == 1) ?
+ eventProcess[vecIn[j]].eT() :
+ eventProcess[vecIn[j]].pT();
+ if (vNow > vMax) {
+ vMax = vNow;
+ jMax = j;
+ }
+ }
+ if (jMax != i) swap(vecIn[i], vecIn[jMax]);
+ }
+ }
+
+ // Master switch for merging
+ bool doMerge;
+
+ // Maximum and current number of jets
+ int nJetMax, nJet;
+
+ // Jet algorithm parameters
+ int jetAlgorithm;
+ double eTjetMin, coneRadius, etaJetMax, etaJetMaxAlgo;
+
+ // CellJet specific
+ int nEta, nPhi;
+ double eTseed, eTthreshold;
+
+ // SlowJet specific
+ int slowJetPower;
+
+ // Merging procedure parameters
+ int jetAllow, jetMatch, exclusiveMode;
+ double coneMatchLight, coneRadiusHeavy, coneMatchHeavy;
+ bool exclusive;
+
+ // Event records to store original incoming process, final-state of the
+ // incoming process and what will be passed to the jet algorithm.
+ // Not completely necessary to store all steps, but makes tracking the
+ // steps of the algorithm a lot easier.
+ Event eventProcessOrig, eventProcess, workEventJet;
+
+ // Internal jet algorithms
+ CellJet *cellJet;
+ SlowJet *slowJet;
+
+ // Sort final-state of incoming process into light/heavy jets and 'other'
+ vector < int > typeIdx[3];
+ set < int > typeSet[3];
+
+ // Momenta output of jet algorithm (to provide same output regardless of
+ // the selected jet algorithm)
+ vector < Vec4 > jetMomenta;
+
+ // Store the minimum eT/pT of matched light jets
+ double eTpTlightMin;
+
+ // Constants
+ static const double GHOSTENERGY, ZEROTHRESHOLD;
+};
+
+//--------------------------------------------------------------------------
+
+// Main implementation of MLMhooks class. This may be split out to a
+// separate C++ file if desired, but currently included here for ease
+// of use.
+
+// Constants: could be changed here if desired, but normally should not.
+// These are of technical nature, as described for each.
+
+// The energy of ghost particles. For technical reasons, this cannot be
+// set arbitrarily low, see 'Particle::TINY' in 'Event.cc' for details.
+const double MLMhooks::GHOSTENERGY = 1e-15;
+
+// A zero threshold value for double comparisons.
+const double MLMhooks::ZEROTHRESHOLD = 1e-10;
+
+//--------------------------------------------------------------------------
+
+// Initialisation routine automatically called from Pythia::init().
+// Setup all parts needed for the merging.
+
+bool MLMhooks::initAfterBeams() {
+
+ // Read in parameters
+ doMerge = settingsPtr->flag("MLM:merge");
+ nJet = settingsPtr->mode("MLM:nJet");
+ nJetMax = settingsPtr->mode("MLM:nJetMax");
+ jetAlgorithm = settingsPtr->mode("MLM:jetAlgorithm");
+ eTjetMin = settingsPtr->parm("MLM:eTjetMin");
+ coneRadius = settingsPtr->parm("MLM:coneRadius");
+ etaJetMax = settingsPtr->parm("MLM:etaJetMax");
+ // Use etaJetMax + coneRadius in input to jet algorithms
+ etaJetMaxAlgo = etaJetMax + coneRadius;
+ // CellJet specific
+ nEta = settingsPtr->mode("MLM:nEta");
+ nPhi = settingsPtr->mode("MLM:nPhi");
+ eTseed = settingsPtr->parm("MLM:eTseed");
+ eTthreshold = settingsPtr->parm("MLM:eTthreshold");
+ // SlowJet specific
+ slowJetPower = settingsPtr->mode("MLM:slowJetPower");
+ // Matching procedure
+ jetAllow = settingsPtr->mode("MLM:jetAllow");
+ jetMatch = settingsPtr->mode("MLM:jetMatch");
+ coneMatchLight = settingsPtr->parm("MLM:coneMatchLight");
+ coneRadiusHeavy = settingsPtr->parm("MLM:coneRadiusHeavy");
+ if (coneRadiusHeavy < 0.) coneRadiusHeavy = coneRadius;
+ coneMatchHeavy = settingsPtr->parm("MLM:coneMatchHeavy");
+ exclusiveMode = settingsPtr->mode("MLM:exclusive");
+
+ // If not merging, then done
+ if (!doMerge) return true;
+
+ // Exclusive mode; if set to 2, then set based on nJet/nJetMax
+ if (exclusiveMode == 2) {
+
+ // No nJet or nJetMax, so default to exclusive mode
+ if (nJet < 0 || nJetMax < 0) {
+ infoPtr->errorMsg("Warning in MLMhooks:init: "
+ "missing jet multiplicity information; running in exclusive mode");
+ exclusive = true;
+
+ // Inclusive if nJet == nJetMax, exclusive otherwise
+ } else {
+ exclusive = (nJet == nJetMax) ? false : true;
+ }
+
+ // Otherwise, just set as given
+ } else {
+ exclusive = (exclusiveMode == 0) ? false : true;
+ }
+
+ // Initialise chosen jet algorithm. CellJet.
+ if (jetAlgorithm == 1) {
+
+ // Extra options for CellJet. nSel = 1 means that all final-state
+ // particles are taken and we retain control of what to select.
+ // smear/resolution/upperCut are not used and are set to default values.
+ int nSel = 2, smear = 0;
+ double resolution = 0.5, upperCut = 2.;
+ cellJet = new CellJet(etaJetMaxAlgo, nEta, nPhi, nSel,
+ smear, resolution, upperCut, eTthreshold);
+
+ // SlowJet
+ } else if (jetAlgorithm == 2) {
+ slowJet = new SlowJet(slowJetPower, coneRadius, eTjetMin, etaJetMaxAlgo);
+ }
+
+ // Check the jetMatch parameter; option 2 only works with SlowJet
+ if (jetAlgorithm == 1 && jetMatch == 2) {
+ infoPtr->errorMsg("Warning in MLMhooks:init: "
+ "jetMatch = 2 only valid with SlowJet algorithm. "
+ "Reverting to jetMatch = 1.");
+ jetMatch = 1;
+ }
+
+ // Setup local event records
+ eventProcessOrig.init("(eventProcessOrig)", particleDataPtr);
+ eventProcess.init("(eventProcess)", particleDataPtr);
+ workEventJet.init("(workEventJet)", particleDataPtr);
+
+ // Print information
+ string jetStr = (jetAlgorithm == 1) ? "CellJet" :
+ (slowJetPower == -1) ? "anti-kT" :
+ (slowJetPower == 0) ? "C/A" :
+ (slowJetPower == 1) ? "kT" : "unknown";
+ string modeStr = (exclusive) ? "exclusive" : "inclusive";
+ stringstream nJetStr, nJetMaxStr;
+ if (nJet >= 0) nJetStr << nJet; else nJetStr << "unknown";
+ if (nJetMax >= 0) nJetMaxStr << nJetMax; else nJetMaxStr << "unknown";
+ cout << endl
+ << " *------- MLM matching parameters -------*" << endl
+ << " | nJet | " << setw(14)
+ << nJetStr.str() << " |" << endl
+ << " | nJetMax | " << setw(14)
+ << nJetMaxStr.str() << " |" << endl
+ << " | Jet algorithm | " << setw(14)
+ << jetStr << " |" << endl
+ << " | eTjetMin | " << setw(14)
+ << eTjetMin << " |" << endl
+ << " | coneRadius | " << setw(14)
+ << coneRadius << " |" << endl
+ << " | etaJetMax | " << setw(14)
+ << etaJetMax << " |" << endl
+ << " | jetAllow | " << setw(14)
+ << jetAllow << " |" << endl
+ << " | jetMatch | " << setw(14)
+ << jetMatch << " |" << endl
+ << " | coneMatchLight | " << setw(14)
+ << coneMatchLight << " |" << endl
+ << " | coneRadiusHeavy | " << setw(14)
+ << coneRadiusHeavy << " |" << endl
+ << " | coneMatchHeavy | " << setw(14)
+ << coneMatchHeavy << " |" << endl
+ << " | Mode | " << setw(14)
+ << modeStr << " |" << endl
+ << " *-----------------------------------------*" << endl;
+
+ return true;
+}
+
+//--------------------------------------------------------------------------
+
+// Process level veto. Stores incoming event for later.
+
+bool MLMhooks::canVetoProcessLevel() { return doMerge; }
+
+bool MLMhooks::doVetoProcessLevel(Event& process) {
+
+ // Copy incoming process
+ eventProcessOrig = process;
+ return false;
+}
+
+//--------------------------------------------------------------------------
+
+// Early parton level veto (before beam remnants and resonance showers)
+
+bool MLMhooks::canVetoPartonLevelEarly() { return doMerge; }
+
+bool MLMhooks::doVetoPartonLevelEarly(const Event& event) {
+
+ // 1) Sort the original incoming process. After this step is performed,
+ // the following assignments have been made:
+ // eventProcessOrig - the original incoming process
+ // eventProcess - the final-state of the incoming process with
+ // resonance decays removed (and resonances
+ // themselves now with positive status code)
+ // typeIdx[0/1/2] - indices into 'eventProcess' of
+ // light jets/heavy jets/other
+ // typeSet[0/1/2] - indices into 'event' of
+ // light jets/heavy jets/other
+ // workEvent - partons from the hardest subsystem
+ // + ISR + FSR only
+ sortIncomingProcess(event);
+
+ // Debug
+#ifdef MLMDEBUG
+ // Begin
+ cout << endl << "---------- Begin MLM Debug ----------" << endl;
+
+ // Original incoming process
+ cout << endl << "Original incoming process:";
+ eventProcessOrig.list();
+
+ // Final-state of original incoming process
+ cout << endl << "Final-state incoming process:";
+ eventProcess.list();
+
+ // List categories of sorted particles
+ for (size_t i = 0; i < typeIdx[0].size(); i++)
+ cout << ((i == 0) ? "Light jets: " : ", ")
+ << setw(3) << typeIdx[0][i];
+ for (size_t i = 0; i < typeIdx[1].size(); i++)
+ cout << ((i == 0) ? "\nHeavy jets: " : ", ")
+ << setw(3) << typeIdx[1][i];
+ for (size_t i = 0; i < typeIdx[2].size(); i++)
+ cout << ((i == 0) ? "\nOther: " : ", ")
+ << setw(3) << typeIdx[2][i];
+
+ // Full event at this stage
+ cout << endl << endl << "Event:";
+ event.list();
+
+ // Work event (partons from hardest subsystem + ISR + FSR)
+ cout << endl << "Work event:";
+ workEvent.list();
+#endif
+
+ // 2) Light/heavy jets: iType = 0 (light jets), 1 (heavy jets)
+ int iTypeEnd = (typeIdx[1].empty()) ? 1 : 2;
+ for (int iType = 0; iType < iTypeEnd; iType++) {
+
+ // 2a) Find particles which will be passed from the jet algorithm.
+ // Input from 'workEvent' and output in 'workEventJet'.
+ jetAlgorithmInput(event, iType);
+
+ // Debug
+#ifdef MLMDEBUG
+ // Jet algorithm event
+ cout << endl << "Jet algorithm event (iType = " << iType << "):";
+ workEventJet.list();
+#endif
+
+ // 2b) Run jet algorithm on 'workEventJet'.
+ // Output is stored in jetMomenta.
+ runJetAlgorithm();
+
+ // 2c) Match partons to jets and decide if veto is necessary
+ if (matchPartonsToJets(iType) == true) {
+ // Debug
+#ifdef MLMDEBUG
+ cout << endl << "Event vetoed" << endl
+ << "---------- End MLM Debug ----------" << endl;
+#endif
+ return true;
+ }
+ }
+
+ // Debug
+#ifdef MLMDEBUG
+ cout << endl << "Event accepted" << endl
+ << "---------- End MLM Debug ----------" << endl;
+#endif
+
+ // If we reached here, then no veto
+ return false;
+}
+
+//--------------------------------------------------------------------------
+
+// Step (1): sort the incoming particles
+
+void MLMhooks::sortIncomingProcess(const Event &event) {
+
+ // Remove resonance decays from original process and keep only final
+ // state. Resonances will have positive status code after this step.
+ omitResonanceDecays(eventProcessOrig, true);
+ eventProcess = workEvent;
+
+ // Sort original process final state into light/heavy jets and 'other'.
+ // Criteria:
+ // 1 <= ID <= 5 and massless, or ID == 21 --> light jet (typeIdx[0])
+ // 4 <= ID <= 6 and massive --> heavy jet (typeIdx[1])
+ // All else --> other (typeIdx[2])
+ // Note that 'typeIdx' stores indices into 'eventProcess' (after resonance
+ // decays are omitted), while 'typeSet' stores indices into the original
+ // process record, 'eventProcessOrig', but these indices are also valid
+ // in 'event'.
+ for (int i = 0; i < 3; i++) {
+ typeIdx[i].clear();
+ typeSet[i].clear();
+ }
+ for (int i = 0; i < eventProcess.size(); i++) {
+ // Ignore nonfinal and default to 'other'
+ if (!eventProcess[i].isFinal()) continue;
+ int idx = 2;
+
+ // Light jets
+ if (eventProcess[i].id() == 21 || (eventProcess[i].idAbs() <= 5 &&
+ abs(eventProcess[i].m()) < ZEROTHRESHOLD))
+ idx = 0;
+
+ // Heavy jets
+ else if (eventProcess[i].idAbs() >= 4 && eventProcess[i].idAbs() <= 6)
+ idx = 1;
+
+ // Store
+ typeIdx[idx].push_back(i);
+ typeSet[idx].insert(eventProcess[i].daughter1());
+ }
+
+ // Extract partons from hardest subsystem + ISR + FSR only into
+ // workEvent. Note no resonance showers or MPIs.
+ subEvent(event);
+}
+
+//--------------------------------------------------------------------------
+
+// Step (2a): pick which particles to pass to the jet algorithm
+
+void MLMhooks::jetAlgorithmInput(const Event &event, int iType) {
+
+ // Take input from 'workEvent' and put output in 'workEventJet'
+ workEventJet = workEvent;
+
+ // Loop over particles and decide what to pass to the jet algorithm
+ for (int i = 0; i < workEventJet.size(); ++i) {
+ if (!workEventJet[i].isFinal()) continue;
+
+ // jetAllow option to disallow certain particle types
+ if (jetAllow == 1) {
+
+ // Original AG+Py6 algorithm explicitly excludes tops,
+ // leptons and photons.
+ int id = workEventJet[i].idAbs();
+ if ((id >= 11 && id <= 16) || id == 6 || id == 22) {
+ workEventJet[i].statusNeg();
+ continue;
+ }
+ }
+
+ // Get the index of this particle in original event
+ int idx = workEventJet[i].daughter1();
+
+ // Start with particle idx, and afterwards track mothers
+ while (true) {
+
+ // Light jets
+ if (iType == 0) {
+
+ // Do not include if originates from heavy jet or 'other'
+ if (typeSet[1].find(idx) != typeSet[1].end() ||
+ typeSet[2].find(idx) != typeSet[2].end()) {
+ workEventJet[i].statusNeg();
+ break;
+ }
+
+ // Made it to start of event record so done
+ if (idx == 0) break;
+ // Otherwise next mother and continue
+ idx = event[idx].mother1();
+
+ // Heavy jets
+ } else if (iType == 1) {
+
+ // Only include if originates from heavy jet
+ if (typeSet[1].find(idx) != typeSet[1].end()) break;
+
+ // Made it to start of event record with no heavy jet mother,
+ // so DO NOT include particle
+ if (idx == 0) {
+ workEventJet[i].statusNeg();
+ break;
+ }
+
+ // Otherwise next mother and continue
+ idx = event[idx].mother1();
+
+ } // if (iType)
+ } // while (true)
+ } // for (i)
+
+ // For jetMatch = 2, insert ghost particles corresponding to
+ // each hard parton in the original process
+ if (jetMatch == 2) {
+ for (int i = 0; i < int(typeIdx[iType].size()); i++) {
+ // Get y/phi of the parton
+ Vec4 pIn = eventProcess[typeIdx[iType][i]].p();
+ double y = Vec4y(pIn);
+ double phi = pIn.phi();
+
+ // Create a ghost particle and add to the workEventJet
+ double e = GHOSTENERGY;
+ double e2y = exp(2. * y);
+ double pz = e * (e2y - 1.) / (e2y + 1.);
+ double pt = sqrt(e*e - pz*pz);
+ double px = pt * cos(phi);
+ double py = pt * sin(phi);
+ workEventJet.append(Particle(21, 99, 0, 0, 0, 0, 0, 0,
+ px, py, pz, e, 0., 0, 9.));
+
+ // Extra check on reconstructed y/phi values. If many warnings
+ // of this type, GHOSTENERGY may be set too low.
+#ifdef MLMCHECK
+ int lastIdx = workEventJet.size() - 1;
+ if (abs(y - workEventJet[lastIdx].y()) > ZEROTHRESHOLD ||
+ abs(phi - workEventJet[lastIdx].phi()) > ZEROTHRESHOLD)
+ infoPtr->errorMsg("Warning in MLMhooks:jetAlgorithmInput: "
+ "ghost particle y/phi mismatch");
+#endif
+
+ } // for (i)
+ } // if (jetMatch == 2)
+}
+
+//--------------------------------------------------------------------------
+
+// Step (2b): run jet algorithm and provide common output
+
+void MLMhooks::runJetAlgorithm() {
+
+ // Run the jet clustering algorithm
+ if (jetAlgorithm == 1)
+ cellJet->analyze(workEventJet, eTjetMin, coneRadius, eTseed);
+ else
+ slowJet->analyze(workEventJet);
+
+ // Extract four-momenta of jets with |eta| < etaJetMax and
+ // put into jetMomenta. Note that this is done backwards as
+ // jets are removed with SlowJet.
+ jetMomenta.clear();
+ int iJet = (jetAlgorithm == 1) ? cellJet->size() - 1:
+ slowJet->sizeJet() - 1;
+ for (int i = iJet; i > -1; i--) {
+ Vec4 jetMom = (jetAlgorithm == 1) ? cellJet->pMassive(i) :
+ slowJet->p(i);
+ double eta = Vec4eta(jetMom);
+
+ if (abs(eta) > etaJetMax) {
+ if (jetAlgorithm == 2) slowJet->removeJet(i);
+ continue;
+ }
+ jetMomenta.push_back(jetMom);
+ }
+
+ // Reverse jetMomenta to restore eT/pT ordering
+ reverse(jetMomenta.begin(), jetMomenta.end());
+}
+
+//--------------------------------------------------------------------------
+
+// Step (2c): veto decision (returning true vetoes the event)
+
+bool MLMhooks::matchPartonsToJets(int iType) {
+
+ // Use two different routines for light/heavy jets as
+ // different veto conditions and for clarity
+ if (iType == 0) return (matchPartonsToJetsLight() > 0);
+ else return (matchPartonsToJetsHeavy() > 0);
+}
+
+//--------------------------------------------------------------------------
+
+// Step(2c): light jets
+// Return codes are given indicating the reason for a veto.
+// Although not currently used, they are a useful debugging tool:
+// 0 = no veto
+// 1 = veto as number of jets less than number of partons
+// 2 = veto as exclusive mode and number of jets greater than
+// number of partons
+// 3 = veto as inclusive mode and there would be an extra jet
+// that is harder than any matched soft jet
+// 4 = veto as there is a parton which does not match a jet
+
+int MLMhooks::matchPartonsToJetsLight() {
+
+ // Always veto if number of jets is less than original number of jets
+ if (jetMomenta.size() < typeIdx[0].size()) return 1;
+ // Veto if in exclusive mode and number of jets bigger than original
+ if (exclusive && jetMomenta.size() > typeIdx[0].size()) return 2;
+
+ // Sort partons by eT/pT
+ sortTypeIdx(typeIdx[0]);
+
+ // Number of hard partons
+ int nParton = typeIdx[0].size();
+
+ // Keep track of which jets have been assigned a hard parton
+ vector < bool > jetAssigned;
+ jetAssigned.assign(jetMomenta.size(), false);
+
+ // Jet matching procedure: (1) deltaR between partons and jets
+ if (jetMatch == 1) {
+
+ // Loop over light hard partons and get 4-momentum
+ for (int i = 0; i < nParton; i++) {
+ Vec4 p1 = eventProcess[typeIdx[0][i]].p();
+
+ // Track which jet has the minimal dR measure with this parton
+ int jMin = -1;
+ double dRmin = 0.;
+
+ // Loop over all jets (skipping those already assigned).
+ for (int j = 0; j < int(jetMomenta.size()); j++) {
+ if (jetAssigned[j]) continue;
+
+ // DeltaR between parton/jet and store if minimum
+ double dR = (jetAlgorithm == 1) ?
+ deltaReta(p1, jetMomenta[j]) : deltaRy(p1, jetMomenta[j]);
+ if (jMin < 0 || dR < dRmin) {
+ dRmin = dR;
+ jMin = j;
+ }
+ } // for (j)
+
+ // Check for jet-parton match
+ if (jMin >= 0 && dRmin < coneRadius * coneMatchLight) {
+
+ // If the matched jet is not one of the nParton hardest jets,
+ // the extra left over jet would be harder than some of the
+ // matched jets. This is disallowed, so veto.
+ if (jMin >= nParton) return 3;
+
+ // Mark jet as assigned.
+ jetAssigned[jMin] = true;
+
+ // If no match, then event will be vetoed in all cases
+ } else return 4;
+
+ } // for (i)
+
+ // Jet matching procedure: (2) ghost particles in SlowJet
+ } else {
+
+ // Loop over added 'ghost' particles and find if assigned to a jet
+ for (int i = workEventJet.size() - nParton;
+ i < workEventJet.size(); i++) {
+ int jMin = slowJet->jetAssignment(i);
+
+ // Veto if:
+ // 1) not one of nParton hardest jets
+ // 2) not assigned to a jet
+ // 3) jet has already been assigned
+ if (jMin >= nParton) return 3;
+ if (jMin < 0 || jetAssigned[jMin]) return 4;
+
+ // Mark jet as assigned
+ jetAssigned[jMin] = true;
+
+ } // for (i)
+ } // if (jetMatch)
+
+ // Minimal eT/pT (CellJet/SlowJet) of matched light jets. Needed
+ // later for heavy jet vetos in inclusive mode.
+ if (nParton > 0)
+ eTpTlightMin = (jetAlgorithm == 1) ? jetMomenta[nParton - 1].eT()
+ : jetMomenta[nParton - 1].pT();
+ else
+ eTpTlightMin = -1.;
+
+ // No veto
+ return 0;
+}
+
+//--------------------------------------------------------------------------
+
+// Step(2c): heavy jets
+// Return codes are given indicating the reason for a veto.
+// Although not currently used, they are a useful debugging tool:
+// 0 = no veto as there are no extra jets present
+// 1 = veto as in exclusive mode and extra jets present
+// 2 = veto as in inclusive mode and extra jets were harder
+// than any matched light jet
+
+int MLMhooks::matchPartonsToJetsHeavy() {
+
+ // If there are no extra jets, then accept
+ if (jetMomenta.empty()) return 0;
+
+ // Number of hard partons
+ int nParton = typeIdx[1].size();
+
+ // Remove jets that are close to heavy quarks
+ set < int > removeJets;
+
+ // Jet matching procedure: (1) deltaR between partons and jets
+ if (jetMatch == 1) {
+
+ // Loop over heavy hard partons and get 4-momentum
+ for (int i = 0; i < nParton; i++) {
+ Vec4 p1 = eventProcess[typeIdx[1][i]].p();
+
+ // Loop over all jets, find dR and mark for removal if match
+ for (int j = 0; j < int(jetMomenta.size()); j++) {
+ double dR = (jetAlgorithm == 1) ?
+ deltaReta(p1, jetMomenta[j]) : deltaRy(p1, jetMomenta[j]);
+ if (dR < coneRadiusHeavy * coneMatchHeavy)
+ removeJets.insert(j);
+
+ } // for (j)
+ } // for (i)
+
+ // Jet matching procedure: (2) ghost particles in SlowJet
+ } else {
+
+ // Loop over added 'ghost' particles and if assigned to a jet
+ // then mark this jet for removal
+ for (int i = workEventJet.size() - nParton;
+ i < workEventJet.size(); i++) {
+ int jMin = slowJet->jetAssignment(i);
+ if (jMin >= 0) removeJets.insert(jMin);
+ }
+
+ }
+
+ // Remove jets (backwards order to not disturb indices)
+ for (set < int >::reverse_iterator it = removeJets.rbegin();
+ it != removeJets.rend(); it++)
+ jetMomenta.erase(jetMomenta.begin() + *it);
+
+ // Handle case if there are still extra jets
+ if (!jetMomenta.empty()) {
+
+ // Exclusive mode, so immediate veto
+ if (exclusive) return 1;
+
+ // Inclusive mode; extra jets must be softer than any matched light jet
+ else if (eTpTlightMin >= 0.)
+ for (size_t j = 0; j < jetMomenta.size(); j++) {
+ // CellJet uses eT, SlowJet uses pT
+ if ( (jetAlgorithm == 1 && jetMomenta[j].eT() > eTpTlightMin) ||
+ (jetAlgorithm == 2 && jetMomenta[j].pT() > eTpTlightMin) )
+ return 2;
+ }
+
+ } // if (!jetMomenta.empty())
+
+ // No extra jets were present so no veto
+ return 0;
+}
+
+//==========================================================================
+
+#endif // MLMHOOKS_H