// $Id: Recluster.cc 3629 2014-08-14 17:21:15Z salam $
//
// Copyright (c) 2014, Matteo Cacciari, Gavin P. Salam and Gregory Soyez
//
//----------------------------------------------------------------------
// 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 "fastjet/tools/Recluster.hh"
#include "fastjet/CompositeJetStructure.hh"
#include
#include
#include
using namespace std;
// Comments:
//
// - If the jet comes from a C/A clustering (or is a composite jet
// made of C/A clusterings) and we recluster it with a C/A
// algorithm, we just use exclusive jets instead of doing the
// clustering explicitly. In this specific case, we need to make
// sure that all the pieces share the same cluster sequence.
//
// - If the recombiner has to be taken from the original jet and that
// jet is composite, we need to check that all the pieces were
// obtained with the same recombiner.
//
// - Note that a preliminary version of this code has been
// distributed in the RecursiveTools fastjet-contrib. The present
// version has a few minor fixed
FASTJET_BEGIN_NAMESPACE // defined in fastjet/internal/base.hh
LimitedWarning Recluster::_explicit_ghost_warning;
// ctor
Recluster::Recluster(JetAlgorithm new_jet_alg, double new_jet_radius, Keep keep_in)
: _new_jet_def(JetDefinition(new_jet_alg, new_jet_radius)),
_acquire_recombiner(true), _keep(keep_in), _cambridge_optimisation_enabled(true){}
Recluster::Recluster(JetAlgorithm new_jet_alg, Keep keep_in)
: _acquire_recombiner(true), _keep(keep_in), _cambridge_optimisation_enabled(true){
switch (JetDefinition::n_parameters_for_algorithm(new_jet_alg)){
case 0: _new_jet_def = JetDefinition(new_jet_alg); break;
case 1: _new_jet_def = JetDefinition(new_jet_alg, JetDefinition::max_allowable_R); break;
default:
throw Error("Recluster(): tried to construct specifying only a jet algorithm ("+JetDefinition::algorithm_description(new_jet_alg)+") which takes more than 1 parameter");
};
}
// class description
string Recluster::description() const {
ostringstream ostr;
ostr << "Recluster with new_jet_def = ";
if (_acquire_recombiner){
ostr << _new_jet_def.description_no_recombiner();
ostr << ", using a recombiner obtained from the jet being reclustered";
} else {
ostr << _new_jet_def.description();
}
if (_keep == keep_only_hardest)
ostr << " and keeping the hardest inclusive jet";
else
ostr << " and joining all inclusive jets into a composite jet";
return ostr.str();
}
// the main piece of code that performs the reclustering
PseudoJet Recluster::result(const PseudoJet &jet) const {
// get the incljets and the exact jet definition that has been used
// to get them
vector incljets;
bool ca_optimised = get_new_jets_and_def(jet, incljets);
return generate_output_jet(incljets, ca_optimised);
}
// a lower-level method that does the actual work of reclustering the
// input jet. The resulting incljets are stored in output_jets and the
// jet definition that has been used can be deduced from their
// associated ClusterSequence
//
// - input_jet the (input) jet that one wants to recluster
// - output_jets incljets resulting from the new clustering
//
// returns true if the C/A optimisation has been used (this means
// that generate_output_jet will watch out for non-explicit-ghost
// areas that might be leftover)
bool Recluster::get_new_jets_and_def(const PseudoJet & input_jet,
vector & output_jets) const{
// generic sanity checks
//-------------------------------------------------------------------
// make sure that the jet has constituents
if (! input_jet.has_constituents())
throw Error("Recluster can only be applied on jets having constituents");
// tests particular to certain configurations
//-------------------------------------------------------------------
// for a whole variety of tests, we shall need the "recursive"
// pieces of the jet (the jet itself or recursing down to its most
// fundamental pieces). So we do compute these once and for all.
//
// Note that the pieces are always needed (either for C/A or for the
// area checks)
vector all_pieces;
if ((!_get_all_pieces(input_jet, all_pieces)) || (all_pieces.size()==0)){
throw Error("Recluster: failed to retrieve all the pieces composing the jet.");
}
// decide which jet definition to use
//-------------------------------------------------------------------
JetDefinition new_jet_def = _new_jet_def;
if (_acquire_recombiner){
_acquire_recombiner_from_pieces(all_pieces, new_jet_def);
}
// the vector that will ultimately hold the incljets
output_jets.clear();
// check if we can apply the simplification for C/A jets reclustered
// with C/A
//
// we apply C/A clustering iff
// - the requested new_jet_def is C/A
// - the jet is either directly coming from C/A or if it is a
// superposition of C/A jets from the same cluster sequence
// - the pieces agree with the recombination scheme of new_jet_def
//
// In this case area support will be automatically inherited so we
// can only worry about this later
// -------------------------------------------------------------------
if (_check_ca(all_pieces, new_jet_def)){
_recluster_ca(all_pieces, output_jets, new_jet_def.R());
output_jets = sorted_by_pt(output_jets);
return true;
}
// decide if area support has to be kept
//-------------------------------------------------------------------
bool include_area_support = input_jet.has_area();
if ((include_area_support) && (!_check_explicit_ghosts(all_pieces))){
_explicit_ghost_warning.warn("Recluster: the original cluster sequence is lacking explicit ghosts; area support will no longer be available after re-clustering");
include_area_support = false;
}
// extract the incljets
//-------------------------------------------------------------------
_recluster_generic(input_jet, output_jets, new_jet_def, include_area_support);
output_jets = sorted_by_pt(output_jets);
return false;
}
// given a set of incljets and a jet definition used, create the
// resulting PseudoJet
PseudoJet Recluster::generate_output_jet(std::vector & incljets,
bool ca_optimisation_used) const{
// first handle the case where we only need to keep the hardest incljet
if (_keep == keep_only_hardest) {
if (incljets.size() > 0) {
return incljets[0];
} else {
return PseudoJet();
}
}
// now the case where all incljets have to be joined
// safekeeper
if (incljets.size()==0) return join(incljets);
PseudoJet reclustered = join(incljets,
*(incljets[0].associated_cluster_sequence()->jet_def().recombiner()));
// if we've used C/A optimisation, we need to get rid of the area
// information if it comes from a non-explicit-ghost clustering.
// (because in that case it can be erroneous due to the lack of
// information about empty areas)
if (ca_optimisation_used){
if (reclustered.has_area() &&
(incljets.size() > 0) &&
(! incljets[0].validated_csab()->has_explicit_ghosts())){
CompositeJetStructure *css = dynamic_cast(reclustered.structure_non_const_ptr());
assert(css);
css->discard_area();
}
}
return reclustered;
}
//----------------------------------------------------------------------
// the parts that really do the reclustering
//----------------------------------------------------------------------
// get the subjets in the simple case of C/A+C/A
void Recluster::_recluster_ca(const vector & all_pieces,
vector & subjets,
double Rfilt) const{
subjets.clear();
// each individual piece should have a C/A cluster sequence
// associated to it. So a simple loop would do the job
for (vector::const_iterator piece_it = all_pieces.begin();
piece_it!=all_pieces.end(); piece_it++){
// just extract the exclusive subjets of 'jet'
const ClusterSequence *cs = piece_it->associated_cluster_sequence();
vector local_subjets;
double dcut = Rfilt / cs->jet_def().R();
if (dcut>=1.0){
// remember that in this case all the pairwise interpiece
// distances are supposed to be larger than Rfilt (this was
// tested in _check_ca), which means that they can never
// recombine with each other.
local_subjets.push_back(*piece_it);
} else {
local_subjets = piece_it->exclusive_subjets(dcut*dcut);
}
copy(local_subjets.begin(), local_subjets.end(), back_inserter(subjets));
}
}
// perform the reclustering itself for all cases where the "C/A trick"
// does not apply
void Recluster::_recluster_generic(const PseudoJet & jet,
vector & incljets,
const JetDefinition & new_jet_def,
bool do_areas) const{
// create a new, internal, ClusterSequence from the jet constituents
// get the incljets directly from there
//
// If the jet has area support then we separate the ghosts from the
// "regular" particles so the incljets will also have area
// support. Note that we do this regardless of whether rho is zero
// or not.
//
// Note that to be able to separate the ghosts, one needs explicit
// ghosts!!
// ---------------------------------------------------------------
if (do_areas){
//vector all_constituents = jet.constituents();
vector regular_constituents, ghosts;
SelectorIsPureGhost().sift(jet.constituents(), ghosts, regular_constituents);
// figure out the ghost area from the 1st ghost (if none, any value
// would probably do as the area will be 0 and subtraction will have
// no effect!)
double ghost_area = (ghosts.size()) ? ghosts[0].area() : 0.01;
ClusterSequenceActiveAreaExplicitGhosts * csa
= new ClusterSequenceActiveAreaExplicitGhosts(regular_constituents,
new_jet_def,
ghosts, ghost_area);
incljets = csa->inclusive_jets();
// allow the cs to be deleted when it's no longer used
//
// Note that there is at least one constituent in the jet so there
// is in principle at least one incljet. But one may have used a
// nasty recombiner or jet def that left an empty set of incljets,
// so we'd rather play it safe (e.g. GridJetPlugin, with the
// constituents outside the range of the grid)
if (incljets.size())
csa->delete_self_when_unused();
else
delete csa;
} else {
ClusterSequence * cs = new ClusterSequence(jet.constituents(), new_jet_def);
incljets = cs->inclusive_jets();
// allow the cs to be deleted when it's no longer used (again, we
// add an extra safety check)
if (incljets.size())
cs->delete_self_when_unused();
else
delete cs;
}
}
//----------------------------------------------------------------------
// various checks and internal constructs
//----------------------------------------------------------------------
// fundamental info for CompositeJets
//----------------------------------------------------------------------
// get the pieces down to the fundamental pieces
//
// Note that this just checks that there is an associated CS to the
// fundamental pieces, not that it is still valid
bool Recluster::_get_all_pieces(const PseudoJet &jet, vector &all_pieces) const{
if (jet.has_associated_cluster_sequence()){
all_pieces.push_back(jet);
return true;
}
if (jet.has_pieces()){
const vector pieces = jet.pieces();
for (vector::const_iterator it=pieces.begin(); it!=pieces.end(); it++)
if (!_get_all_pieces(*it, all_pieces)) return false;
return true;
}
return false;
}
// construct the re-clustering jet definition using the recombiner
// from whatever definition has been used to obtain the original jet
//----------------------------------------------------------------------
void Recluster::_acquire_recombiner_from_pieces(const vector &all_pieces,
JetDefinition &new_jet_def) const{
// a quick safety check
assert(_acquire_recombiner);
// check that all the pieces have the same recombiner
//
// Note that if the jet has an associated cluster sequence that is no
// longer valid, an error will be thrown (needed since it could be the
// 1st check called after the enumeration of the pieces)
const JetDefinition & jd_ref = all_pieces[0].validated_cs()->jet_def();
for (unsigned int i=1; ijet_def().has_same_recombiner(jd_ref)){
throw Error("Recluster instance is configured to determine the recombination scheme (or recombiner) from the original jet, but different pieces of the jet were found to have non-equivalent recombiners.");
}
}
// get the recombiner from the original jet_def
new_jet_def.set_recombiner(jd_ref);
}
// area support
//----------------------------------------------------------------------
// check if the jet (or all its pieces) have explicit ghosts
// (assuming the jet has area support).
//
// Note that if the jet has an associated cluster sequence that is no
// longer valid, an error will be thrown (needed since it could be the
// 1st check called after the enumeration of the pieces)
bool Recluster::_check_explicit_ghosts(const vector &all_pieces) const{
for (vector::const_iterator it=all_pieces.begin(); it!=all_pieces.end(); it++)
if (! it->validated_csab()->has_explicit_ghosts()) return false;
return true;
}
// C/A specific tests
//----------------------------------------------------------------------
// check if one can apply the simplification for C/A incljets
//
// This includes:
// - the incljet definition asks for C/A incljets
// - all the pieces share the same CS
// - that CS is C/A with the same recombiner as the incljet def
// - the re-clustering radius is not larger than any of the pairwise
// distance between the pieces
//
// Note that if the jet has an associated cluster sequence that is no
// longer valid, an error will be thrown (needed since it could be the
// 1st check called after the enumeration of the pieces)
bool Recluster::_check_ca(const vector &all_pieces,
const JetDefinition &new_jet_def) const{
// check that optimisation is enabled
if (!_cambridge_optimisation_enabled) return false;
// check that we're reclustering with C/A
if (new_jet_def.jet_algorithm() != cambridge_algorithm) return false;
// check that the 1st of all the pieces (we're sure there is at
// least one) is coming from a C/A clustering. Then check that all
// the following pieces share the same ClusterSequence
const ClusterSequence * cs_ref = all_pieces[0].validated_cs();
if (cs_ref->jet_def().jet_algorithm() != cambridge_algorithm) return false;
for (unsigned int i=1; ijet_def().has_same_recombiner(new_jet_def)) return false;
// we also have to make sure that the reclustering radius is not larger
// than any of the inter-piece distances
double Rnew2 = new_jet_def.R();
Rnew2 *= Rnew2;
for (unsigned int i=0; i