// $Id: RecursiveSymmetryCutBase.cc 1080 2017-09-28 07:51:37Z gsoyez $
//
// Copyright (c) 2014-, Gavin P. Salam, Gregory Soyez, Jesse Thaler
//
//----------------------------------------------------------------------
// 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 "RecursiveSymmetryCutBase.hh"
#include "fastjet/JetDefinition.hh"
#include "fastjet/ClusterSequenceAreaBase.hh"
#include
#include
#include
using namespace std;
FASTJET_BEGIN_NAMESPACE // defined in fastjet/internal/base.hh
namespace contrib{
LimitedWarning RecursiveSymmetryCutBase::_negative_mass_warning;
LimitedWarning RecursiveSymmetryCutBase::_mu2_gt1_warning;
//LimitedWarning RecursiveSymmetryCutBase::_nonca_warning;
LimitedWarning RecursiveSymmetryCutBase::_explicit_ghost_warning;
bool RecursiveSymmetryCutBase::_verbose = false;
//----------------------------------------------------------------------
PseudoJet RecursiveSymmetryCutBase::result(const PseudoJet & jet) const {
// construct the input jet (by default, recluster with C/A)
if (! jet.has_constituents()){
throw Error("RecursiveSymmetryCutBase can only be applied to jets with constituents");
}
PseudoJet j = _recluster_if_needed(jet);
// sanity check: the jet must have a valid CS
if (! j.has_valid_cluster_sequence()){
throw Error("RecursiveSymmetryCutBase can only be applied to jets associated to a (valid) cluster sequence");
}
// check that area information is there in case we have a subtractor
// GS: do we really need this since subtraction may not require areas?
if (_subtractor) {
const ClusterSequenceAreaBase * csab =
dynamic_cast(j.associated_cs());
if (csab == 0 || (!csab->has_explicit_ghosts()))
_explicit_ghost_warning.warn("RecursiveSymmetryCutBase: there is no clustering sequence, or it lacks explicit ghosts: subtraction is not guaranteed to function properly");
}
// establish the first subjet and optionally subtract it
PseudoJet subjet = j;
if (_subtractor && (!_input_jet_is_subtracted)) {
subjet = (*_subtractor)(subjet);
}
// variables for tracking what will happen
PseudoJet piece1, piece2;
// vectors for storing optional verbose structure
// these hold the deltaR, symmetry, and mu values of dropped branches
std::vector dropped_delta_R;
std::vector dropped_symmetry;
std::vector dropped_mu;
double sym, mu2;
// now recurse into the jet's structure
RecursionStatus status;
while ((status=recurse_one_step(subjet, piece1, piece2, sym, mu2)) != recursion_success) {
// start with sanity checks:
if ((status == recursion_issue) || (status == recursion_no_parents)) {
// we should return piece1 by our convention for recurse_one_step
PseudoJet result;
if (status == recursion_issue){
result = piece1;
if (_verbose) cout << "reached end; returning null jet " << endl;
} else {
result = _result_no_substructure(piece1);
if (_verbose) cout << "no parents found; returning last PJ or empty jet" << endl;
}
if (result != 0) {
// if in grooming mode, add dummy structure information
StructureType * structure = new StructureType(result);
// structure->_symmetry = 0.0;
// structure->_mu = 0.0;
// structure->_delta_R = 0.0;
if (_verbose_structure) { // still want to store verbose information about dropped branches
structure->_has_verbose = true;
structure->_dropped_symmetry = dropped_symmetry;
structure->_dropped_mu = dropped_mu;
structure->_dropped_delta_R = dropped_delta_R;
}
result.set_structure_shared_ptr(SharedPtr(structure));
}
return result;
}
assert(status == recursion_dropped);
// if desired, store information about dropped branches before recursing
if (_verbose_structure) {
dropped_delta_R.push_back(piece1.delta_R(piece2));
dropped_symmetry.push_back(sym);
dropped_mu.push_back((mu2 >= 0) ? sqrt(mu2) : -sqrt(-mu2));
}
subjet = piece1;
}
// we've tagged the splitting, return the jet with its substructure
StructureType * structure = new StructureType(subjet);
structure->_symmetry = sym;
structure->_mu = (mu2 >= 0) ? sqrt(mu2) : -sqrt(-mu2);
structure->_delta_R = sqrt(squared_geometric_distance(piece1, piece2));
if (_verbose_structure) {
structure->_has_verbose = true;
structure->_dropped_symmetry = dropped_symmetry;
structure->_dropped_mu = dropped_mu;
structure->_dropped_delta_R = dropped_delta_R;
}
subjet.set_structure_shared_ptr(SharedPtr(structure));
return subjet;
}
//----------------------------------------------------------------------
// the method below is the one actually performing one step of the
// recursion.
//
// It returns a status code (defined above)
//
// In case of success, all the information is filled
// In case of "no parents", piee1 is the same subjet
// In case of trouble, piece2 will be a 0 PJ and piece1 is the PJ we
// should return (either 0 itself if the issue was critical, or
// non-wero in case of a minor issue just causing the recursion to
// stop)
RecursiveSymmetryCutBase::RecursionStatus
RecursiveSymmetryCutBase::recurse_one_step(const PseudoJet & subjet,
PseudoJet &piece1, PseudoJet &piece2,
double &sym, double &mu2,
void *extra_parameters) const {
if (!subjet.has_parents(piece1, piece2)){
piece1 = subjet;
piece2 = PseudoJet();
return recursion_no_parents;
}
// first sanity check:
// - zero or negative pts are not allowed for the input subjet
// - zero or negative masses are not allowed for configurations
// in which the mass will effectively appear in a denominator
// (The masses will be checked later)
if (subjet.pt2() <= 0){ // this is a critical problem, return an empty PJ
piece1 = piece2 = PseudoJet();
return recursion_issue;
}
if (_subtractor) {
piece1 = (*_subtractor)(piece1);
piece2 = (*_subtractor)(piece2);
}
// determine the symmetry parameter
if (_symmetry_measure == y) {
// the original d_{ij}/m^2 choice from MDT
// first make sure the mass is sensible
if (subjet.m2() <= 0) {
_negative_mass_warning.warn("RecursiveSymmetryCutBase: cannot calculate y, because (sub)jet mass is negative; bailing out");
// since rounding errors can give -ve masses, be a it more
// tolerant and consider that no substructure has been found
piece1 = _result_no_substructure(subjet);
piece2 = PseudoJet();
return recursion_issue;
}
sym = piece1.kt_distance(piece2) / subjet.m2();
} else if (_symmetry_measure == vector_z) {
// min(pt1, pt2)/(pt), where the denominator is a vector sum
// of the two subjets
sym = min(piece1.pt(), piece2.pt()) / subjet.pt();
} else if (_symmetry_measure == scalar_z) {
// min(pt1, pt2)/(pt1+pt2), where the denominator is a scalar sum
// of the two subjets
double pt1 = piece1.pt();
double pt2 = piece2.pt();
// make sure denominator is non-zero
sym = pt1 + pt2;
if (sym == 0){ // this is a critical problem, return an empty PJ
piece1 = piece2 = PseudoJet();
return recursion_issue;
}
sym = min(pt1, pt2) / sym;
} else if ((_symmetry_measure == theta_E) || (_symmetry_measure == cos_theta_E)){
// min(E1, E2)/(E1+E2)
double E1 = piece1.E();
double E2 = piece2.E();
// make sure denominator is non-zero
sym = E1 + E2;
if (sym == 0){ // this is a critical problem, return an empty PJ
piece1 = piece2 = PseudoJet();
return recursion_issue;
}
sym = min(E1, E2) / sym;
} else {
throw Error ("Unrecognized choice of symmetry_measure");
}
// determine the symmetry cut
// (This function is specified in the derived classes)
double this_symmetry_cut = symmetry_cut_fn(piece1, piece2, extra_parameters);
// and make a first tagging decision based on symmetry cut
bool tagged = (sym > this_symmetry_cut);
// if tagged based on symmetry cut, then check the mu cut (if relevant)
// and update the tagging decision. Calculate mu^2 regardless, for cases
// of users not cutting on mu2, but still interested in its value.
bool use_mu_cut = (_mu_cut != numeric_limits::infinity());
mu2 = max(piece1.m2(), piece2.m2())/subjet.m2();
if (tagged && use_mu_cut) {
// first a sanity check -- mu2 won't be sensible if the subjet mass
// is negative, so we can't then trust the mu cut - bail out
if (subjet.m2() <= 0) {
_negative_mass_warning.warn("RecursiveSymmetryCutBase: cannot trust mu, because (sub)jet mass is negative; bailing out");
piece1 = piece2 = PseudoJet();
return recursion_issue;
}
if (mu2 > 1) _mu2_gt1_warning.warn("RecursiveSymmetryCutBase encountered mu^2 value > 1");
if (mu2 > pow(_mu_cut,2)) tagged = false;
}
// we'll continue unclustering, allowing for the different
// ways of choosing which parent to look into
if (_recursion_choice == larger_pt) {
if (piece1.pt2() < piece2.pt2()) std::swap(piece1, piece2);
} else if (_recursion_choice == larger_mt) {
if (piece1.mt2() < piece2.mt2()) std::swap(piece1, piece2);
} else if (_recursion_choice == larger_m) {
if (piece1.m2() < piece2.m2()) std::swap(piece1, piece2);
} else if (_recursion_choice == larger_E) {
if (piece1.E() < piece2.E()) std::swap(piece1, piece2);
} else {
throw Error ("Unrecognized value for recursion_choice");
}
return tagged ? recursion_success : recursion_dropped;
}
//----------------------------------------------------------------------
string RecursiveSymmetryCutBase::description() const {
ostringstream ostr;
ostr << "Recursive " << (_grooming_mode ? "Groomer" : "Tagger") << " with a symmetry cut ";
switch(_symmetry_measure) {
case y:
ostr << "y"; break;
case scalar_z:
ostr << "scalar_z"; break;
case vector_z:
ostr << "vector_z"; break;
case theta_E:
ostr << "theta_E"; break;
case cos_theta_E:
ostr << "cos_theta_E"; break;
default:
cerr << "failed to interpret symmetry_measure" << endl; exit(-1);
}
ostr << " > " << symmetry_cut_description();
if (_mu_cut != numeric_limits::infinity()) {
ostr << ", mass-drop cut mu=max(m1,m2)/m < " << _mu_cut;
} else {
ostr << ", no mass-drop requirement";
}
ostr << ", recursion into the subjet with larger ";
switch(_recursion_choice) {
case larger_pt:
ostr << "pt"; break;
case larger_mt:
ostr << "mt(=sqrt(m^2+pt^2))"; break;
case larger_m:
ostr << "mass"; break;
case larger_E:
ostr << "energy"; break;
default:
cerr << "failed to interpret recursion_choice" << endl; exit(-1);
}
if (_subtractor) {
ostr << ", subtractor: " << _subtractor->description();
if (_input_jet_is_subtracted) {ostr << " (input jet is assumed already subtracted)";}
}
if (_recluster) {
ostr << " and reclustering using " << _recluster->description();
}
return ostr.str();
}
//----------------------------------------------------------------------
// helper for handling the reclustering
PseudoJet RecursiveSymmetryCutBase::_recluster_if_needed(const PseudoJet &jet) const{
if (! _do_reclustering) return jet;
if (_recluster) return (*_recluster)(jet);
if (is_ee()){
#if FASTJET_VERSION_NUMBER >= 30100
return Recluster(JetDefinition(ee_genkt_algorithm, JetDefinition::max_allowable_R, 0.0), true)(jet);
#else
return Recluster(JetDefinition(ee_genkt_algorithm, JetDefinition::max_allowable_R, 0.0))(jet);
#endif
}
return Recluster(cambridge_algorithm, JetDefinition::max_allowable_R)(jet);
}
//----------------------------------------------------------------------
// decide what to return when no substructure has been found
double RecursiveSymmetryCutBase::squared_geometric_distance(const PseudoJet &j1,
const PseudoJet &j2) const{
if (_symmetry_measure == theta_E){
double dot_3d = j1.px()*j2.px() + j1.py()*j2.py() + j1.pz()*j2.pz();
double cos_theta = max(-1.0,min(1.0, dot_3d/sqrt(j1.modp2()*j2.modp2())));
double theta = acos(cos_theta);
return theta*theta;
} else if (_symmetry_measure == cos_theta_E){
double dot_3d = j1.px()*j2.px() + j1.py()*j2.py() + j1.pz()*j2.pz();
return max(0.0, 2*(1-dot_3d/sqrt(j1.modp2()*j2.modp2())));
}
return j1.squared_distance(j2);
}
//----------------------------------------------------------------------
PseudoJet RecursiveSymmetryCutBase::_result_no_substructure(const PseudoJet &last_parent) const{
if (_grooming_mode){
// in grooming mode, return the last parent
return last_parent;
} else {
// in tagging mode, return an empty PseudoJet
return PseudoJet();
}
}
//========================================================================
// implementation of the details of the structure
// the number of dropped subjets
int RecursiveSymmetryCutBase::StructureType::dropped_count(bool global) const {
check_verbose("dropped_count()");
// if this jet has no substructure, just return an empty vector
if (!has_substructure()) return _dropped_delta_R.size();
// deal with the non-global case
if (!global) return _dropped_delta_R.size();
// for the global case, we've unfolded the recursion (likely more
// efficient as it requires less copying)
unsigned int count = 0;
vector to_parse;
to_parse.push_back(this);
unsigned int i_parse = 0;
while (i_parse_dropped_delta_R.size();
// check if we need to recurse deeper in the substructure
//
// we can have 2 situations here for the underlying structure (the
// one we've wrapped around):
// - it's of the clustering type
// - it's a composite jet
// only in the 2nd case do we have to recurse deeper
const CompositeJetStructure *css = dynamic_cast(current->_structure.get());
if (css == 0){ ++i_parse; continue; }
vector prongs = css->pieces(PseudoJet()); // argument irrelevant
assert(prongs.size() == 2);
for (unsigned int i_prong=0; i_prong<2; ++i_prong){
if (prongs[i_prong].has_structure_of()){
RecursiveSymmetryCutBase::StructureType* prong_structure
= (RecursiveSymmetryCutBase::StructureType*) prongs[i_prong].structure_ptr();
if (prong_structure->has_substructure())
to_parse.push_back(prong_structure);
}
}
++i_parse;
}
return count;
}
// the delta_R of all the dropped subjets
vector RecursiveSymmetryCutBase::StructureType::dropped_delta_R(bool global) const {
check_verbose("dropped_delta_R()");
// if this jet has no substructure, just return an empty vector
if (!has_substructure()) return vector();
// deal with the non-global case
if (!global) return _dropped_delta_R;
// for the global case, we've unfolded the recursion (likely more
// efficient as it requires less copying)
vector all_dropped;
vector to_parse;
to_parse.push_back(this);
unsigned int i_parse = 0;
while (i_parse_dropped_delta_R.begin(), current->_dropped_delta_R.end());
// check if we need to recurse deeper in the substructure
//
// we can have 2 situations here for the underlying structure (the
// one we've wrapped around):
// - it's of the clustering type
// - it's a composite jet
// only in the 2nd case do we have to recurse deeper
const CompositeJetStructure *css = dynamic_cast(current->_structure.get());
if (css == 0){ ++i_parse; continue; }
vector prongs = css->pieces(PseudoJet()); // argument irrelevant
assert(prongs.size() == 2);
for (unsigned int i_prong=0; i_prong<2; ++i_prong){
if (prongs[i_prong].has_structure_of()){
RecursiveSymmetryCutBase::StructureType* prong_structure
= (RecursiveSymmetryCutBase::StructureType*) prongs[i_prong].structure_ptr();
if (prong_structure->has_substructure())
to_parse.push_back(prong_structure);
}
}
++i_parse;
}
return all_dropped;
}
// the symmetry of all the dropped subjets
vector RecursiveSymmetryCutBase::StructureType::dropped_symmetry(bool global) const {
check_verbose("dropped_symmetry()");
// if this jet has no substructure, just return an empty vector
if (!has_substructure()) return vector();
// deal with the non-global case
if (!global) return _dropped_symmetry;
// for the global case, we've unfolded the recursion (likely more
// efficient as it requires less copying)
vector all_dropped;
vector to_parse;
to_parse.push_back(this);
unsigned int i_parse = 0;
while (i_parse_dropped_symmetry.begin(), current->_dropped_symmetry.end());
// check if we need to recurse deeper in the substructure
//
// we can have 2 situations here for the underlying structure (the
// one we've wrapped around):
// - it's of the clustering type
// - it's a composite jet
// only in the 2nd case do we have to recurse deeper
const CompositeJetStructure *css = dynamic_cast(current->_structure.get());
if (css == 0){ ++i_parse; continue; }
vector prongs = css->pieces(PseudoJet()); // argument irrelevant
assert(prongs.size() == 2);
for (unsigned int i_prong=0; i_prong<2; ++i_prong){
if (prongs[i_prong].has_structure_of()){
RecursiveSymmetryCutBase::StructureType* prong_structure
= (RecursiveSymmetryCutBase::StructureType*) prongs[i_prong].structure_ptr();
if (prong_structure->has_substructure())
to_parse.push_back(prong_structure);
}
}
++i_parse;
}
return all_dropped;
}
// the mu of all the dropped subjets
vector RecursiveSymmetryCutBase::StructureType::dropped_mu(bool global) const {
check_verbose("dropped_mu()");
// if this jet has no substructure, just return an empty vector
if (!has_substructure()) return vector();
// deal with the non-global case
if (!global) return _dropped_mu;
// for the global case, we've unfolded the recursion (likely more
// efficient as it requires less copying)
vector all_dropped;
vector to_parse;
to_parse.push_back(this);
unsigned int i_parse = 0;
while (i_parse_dropped_mu.begin(), current->_dropped_mu.end());
// check if we need to recurse deeper in the substructure
//
// we can have 2 situations here for the underlying structure (the
// one we've wrapped around):
// - it's of the clustering type
// - it's a composite jet
// only in the 2nd case do we have to recurse deeper
const CompositeJetStructure *css = dynamic_cast(current->_structure.get());
if (css == 0){ ++i_parse; continue; }
vector prongs = css->pieces(PseudoJet()); // argument irrelevant
assert(prongs.size() == 2);
for (unsigned int i_prong=0; i_prong<2; ++i_prong){
if (prongs[i_prong].has_structure_of()){
RecursiveSymmetryCutBase::StructureType* prong_structure
= (RecursiveSymmetryCutBase::StructureType*) prongs[i_prong].structure_ptr();
if (prong_structure->has_substructure())
to_parse.push_back(prong_structure);
}
}
++i_parse;
}
return all_dropped;
}
// the maximum of the symmetry over the dropped subjets
double RecursiveSymmetryCutBase::StructureType::max_dropped_symmetry(bool global) const {
check_verbose("max_dropped_symmetry()");
// if there is no substructure, just exit
if (!has_substructure()){ return 0.0; }
// local value of the max_dropped_symmetry
double local_max = (_dropped_symmetry.size() == 0)
? 0.0 : *max_element(_dropped_symmetry.begin(),_dropped_symmetry.end());
// recurse down the structure if instructed to do so
if (global){
// we can have 2 situations here for the underlying structure (the
// one we've wrapped around):
// - it's of the clustering type
// - it's a composite jet
// only in the 2nd case do we have to recurse deeper
const CompositeJetStructure *css = dynamic_cast(_structure.get());
if (css == 0) return local_max;
vector prongs = css->pieces(PseudoJet()); // argument irrelevant
assert(prongs.size() == 2);
for (unsigned int i_prong=0; i_prong<2; ++i_prong){
// check if the prong has further substructure
if (prongs[i_prong].has_structure_of()){
RecursiveSymmetryCutBase::StructureType* prong_structure
= (RecursiveSymmetryCutBase::StructureType*) prongs[i_prong].structure_ptr();
local_max = max(local_max, prong_structure->max_dropped_symmetry(true));
}
}
}
return local_max;
}
//------------------------------------------------------------------------
// helper class to sort by decreasing thetag
class SortRecursiveSoftDropStructureZgThetagPair{
public:
bool operator()(const pair &p1, const pair &p2) const{
return p1.second > p2.second;
}
};
//------------------------------------------------------------------------
// the (zg,thetag) pairs of all the splitting that were found and passed the SD condition
vector > RecursiveSymmetryCutBase::StructureType::sorted_zg_and_thetag() const {
//check_verbose("sorted_zg_and_thetag()");
// if this jet has no substructure, just return an empty vector
if (!has_substructure()) return vector >();
// otherwise fill a vector with all the prongs (no specific ordering)
vector > all;
vector to_parse;
to_parse.push_back(this);
unsigned int i_parse = 0;
while (i_parse(current->_symmetry, current->_delta_R));
vector prongs = current->pieces(PseudoJet());
assert(prongs.size() == 2);
for (unsigned int i_prong=0; i_prong<2; ++i_prong){
if (prongs[i_prong].has_structure_of()){
RecursiveSymmetryCutBase::StructureType* prong_structure
= (RecursiveSymmetryCutBase::StructureType*) prongs[i_prong].structure_ptr();
if (prong_structure->has_substructure())
to_parse.push_back(prong_structure);
}
}
++i_parse;
}
sort(all.begin(), all.end(), SortRecursiveSoftDropStructureZgThetagPair());
return all;
}
} // namespace contrib
FASTJET_END_NAMESPACE