// $Id$
//
// Copyright (c) 2016,
// Gavin 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 .
//----------------------------------------------------------------------
#ifndef __FASTJET_CONTRIB_CARTESIANJET_HH__
#define __FASTJET_CONTRIB_CARTESIANJET_HH__
#include
#include
#include
#include
#include
FASTJET_BEGIN_NAMESPACE // defined in fastjet/internal/base.hh
namespace contrib {
//------------------------------------------------------------------------
/// \class CartesianCoordinate
/// This class stores the x, y coordinates and optionally, the intensity of an entry
/// TODO: Allow the inclusion of arbitrary user information (templated? allow inheritance?)
class CartesianCoordinate : public PseudoJet::UserInfoBase {
public:
// empty constructor for zero entry
CartesianCoordinate()
: _x(0), _y(0), _intensity(0), _index(0)
{}
// default constructor
CartesianCoordinate(double x, double y, double intensity = 1.0, int index = 0)
: _x(x), _y(y), _intensity(intensity), _index(index)
{}
// accessing the coordinates, intensity, and optional user index
double x() const {return _x;}
double y() const {return _y;}
double intensity() const {return _intensity;}
int user_index() const {return _index;}
private:
double _x; // x coordinate
double _y; // y coordinate
double _intensity; // intensity
int _index; // user index
};
//------------------------------------------------------------------------
/// \class CartesianJet
/// Inherits from PseudoJet and contains a pointer to a CartesianCoordinate.
/// Pointer is needed to use existing user_info structure of PseudoJet
class CartesianJet : public PseudoJet {
public:
// empty constructor
CartesianJet()
: PseudoJet(0,0,0,0) {
set_user_info(new CartesianCoordinate()); // create dummy coordinate
}
// copy constructor has to be non-trivial to make new copy of CartesianCoordinate
CartesianJet(const CartesianJet & cj)
: PseudoJet(cj) {
set_user_info(new CartesianCoordinate(cj.coordinate()));
}
// Mapping a PseudoJet into a CartesianJet, assuming is has CartesianCoordinate
// as user_info
CartesianJet(const PseudoJet & pj)
: PseudoJet(pj) {
if ( !has_user_info() ) {
throw Error("CertesianJet: Constructor expects PseudoJet to have CartesianCoordinate as its user_info().");
}
set_user_info(new CartesianCoordinate(pj.user_info()));
}
// getting access to underlying coordinate (returns const reference)
const CartesianCoordinate & coordinate() const { return user_info();}
// accessing coordinates and intensities (off-loaded to CartesianCoordinate)
double x() const {return coordinate().x();}
double y() const {return coordinate().y();}
double intensity() const {return coordinate().intensity();}
// GPS: as an interim alternative to user info, it might be useful to
// add a user_index?
// JDT: I'm trying to hijack user index of Pseudojet, not sure if this is best
int user_index() const {return coordinate().user_index();}
std::vector cartesian_constituents() const {
std::vector my_constituents = constituents(); // from PseudoJet base class
return std::vector(my_constituents.begin(), my_constituents.end());
}
protected:
//CartesianRange is responsible for setting the proper pseudojet
friend class CartesianRange;
// Turning a CartesianCoordinate into a CartesianJet.
// The underlying Pseudojet starts off as zero and
// has to be set, e.g., by CartesianRange
CartesianJet(const CartesianCoordinate & coord)
: PseudoJet(0,0,0,0) {
set_user_info(new CartesianCoordinate(coord));
}
private:
// making private so user cannot change (it comes from CartesianCoordinate
void set_user_index(const int index);
};
//------------------------------------------------------------------------
/// \class CartesianRange
/// This class converts CartesianCoordinates into PseudoJets, assuming a given
/// x and y range
class CartesianRange {
public:
static const double max_azimuthal_value; //= M_PI/2.0;
/// default constructor
CartesianRange(double xmin, double xmax, double ymin, double ymax)
: _xmin(xmin), _xmax(xmax), _ymin(ymin), _ymax(ymax)
{
//rescale factor to put angles between 0 and max_azimuthal_value
_scale_factor = (_ymax - _ymin)/ max_azimuthal_value;
}
// convert from CartesianCoordinate to CaretsianJet
CartesianJet create_cartesian_jet(const CartesianCoordinate& coord) const {
// create CartesianJet with zero underlying PseudoJet, to be set later
CartesianJet cj(coord);
// get CartesianCoordinate information
double x = coord.x();
double y = coord.y();
double intensity = coord.intensity();
// GPS: I would perhaps make these "assert", because something that fails here
// is a sign of an internal inconsistency
// JDT: Not necessarily, if the user makes their own CartesianRange
// issue warnings if range is wrong (might still work)
if (x < _xmin) _out_of_range_warning.warn("CartesianRange warning: CartesianCoordinate has x < xmin.");
if (x > _xmax) _out_of_range_warning.warn("CartesianRange warning: CartesianCoordinate has x > xmax.");
if (y < _ymin) _out_of_range_warning.warn("CartesianRange warning: CartesianCoordinate has y < ymin.");
if (y > _ymax) _out_of_range_warning.warn("CartesianRange warning: CartesianCoordinate has y > ymax.");
// rescale to rapidity and azimuthal values
double rap = (x - _xmin) / _scale_factor;
double phi = (y - _ymin) / _scale_factor;
// Define underlying PseudoJet
// Note that you have to call reset_momentum_PtYPhiM, not reset_PtYPhiM
// to avoid reseting user information
cj.reset_momentum_PtYPhiM(intensity,rap,phi);
return cj;
}
// convert vector to vector
std::vector create_cartesian_jets(const std::vector & coords) const {
std::vector cjs;
for (int i = 0; i < coords.size(); i++) {
cjs.push_back(create_cartesian_jet(coords[i]));
}
return cjs;
}
// get scale factor
double scale_factor() const {return _scale_factor;}
// get rescaled jet radius value
double pseudojet_radius(double cj_radius) const {
double radius = cj_radius/_scale_factor;
if (radius > max_azimuthal_value) {
// GPS: again this should be assert (possibly with tolerance?) because
// if this happens, it's a sign of an internal inconsistency
_too_large_radius.warn("CartesianRange warning: resulting radius is beyond max_azimuthal_value; wrapping may occur");
}
// TODO: Should we rescale to make it possible to use bigger radius values?
return radius;
}
private:
// minimum and maximum cartesian ranges
double _xmin;
double _xmax;
double _ymin;
double _ymax;
// calcuated scale factors
double _scale_factor;
// various warnings
mutable LimitedWarning _out_of_range_warning;
mutable LimitedWarning _no_cartesianjet;
mutable LimitedWarning _too_large_radius;
};
//------------------------------------------------------------------------
/// \class CartesianRecombiner
/// This is a custom PseudoJet recombiner the not only adds the Cartesian
/// jet information but also uses pt-scheme recombination
class CartesianRecombiner : public JetDefinition::Recombiner {
public:
// Constructor with delta (effectively pt^delta-scheme)
CartesianRecombiner(double delta = 1.0)
: _delta(delta)
{}
private:
double _delta;
virtual std::string description() const {
std::ostringstream ostr;
ostr << "CartesianRecombiner (delta = " << _delta << ")";
return ostr.str();
}
// This combiner information
virtual void recombine(const fastjet::PseudoJet & pa,
const fastjet::PseudoJet & pb,
fastjet::PseudoJet & pab) const {
double perp_a = pa.perp();
double perp_b = pb.perp();
double perp_ab = perp_a + perp_b;
// setting initial values (used also if equal pTs)
double ratio = 1.0;
double weight_a = 0.5;
double weight_b = 0.5;
if (perp_a > perp_b){
ratio = pow(perp_b/perp_a,_delta);
weight_a = 1.0 /(1.0 + ratio);
weight_b = ratio/(1.0 + ratio);
} else if (perp_b > perp_a){
ratio = pow(perp_a/perp_b,_delta);
weight_b = 1.0 /(1.0 + ratio);
weight_a = ratio/(1.0 + ratio);
}
if (perp_ab != 0.0) {
double y_ab = (weight_a * pa.rap() + weight_b * pb.rap());
double phi_a = pa.phi(), phi_b = pb.phi();
if (phi_a - phi_b > pi) phi_b += twopi;
if (phi_a - phi_b < -pi) phi_b -= twopi;
double phi_ab = (weight_a * phi_a + weight_b * phi_b);
pab.reset_PtYPhiM(perp_ab, y_ab, phi_ab);
// Do the same for underlying CartesianJet
if (pa.has_user_info() && pb.has_user_info()) {
CartesianCoordinate cja = pa.user_info();
CartesianCoordinate cjb = pb.user_info();
double x = weight_a * cja.x() + weight_b * cjb.x();
double y = weight_a * cja.y() + weight_b * cjb.y();
double intensity = cja.intensity() + cjb.intensity();
CartesianCoordinate * new_cj = new CartesianCoordinate(x,y,intensity);
pab.set_user_info(new_cj);
}
}
else {
pab.reset(0.0,0.0,0.0,0.0);
pab.set_user_info(new CartesianCoordinate());
}
}
};
//------------------------------------------------------------------------
/// \class CartesianJetDefinition
/// This wraps the gen-kt algorithm, allowing for pt^delta scheme, but no other options
class CartesianJetDefinition {
public:
// Constructor, default is anti-kT with pT-scheme recombination
CartesianJetDefinition(double cartesian_radius, double p = -1.0, double delta = 1.0);
// running the jet algorithm
std::vector operator() (const std::vector & coord_input) const {
// GPS: 0-size vector is a special case that should be treated smoothly;
if (coord_input.size() == 0) return std::vector();
// First figure out size of image
double xmin = std::numeric_limits().max();
double xmax = std::numeric_limits().min();
double ymin = std::numeric_limits().max();
double ymax = std::numeric_limits().min();
for (int i = 0; i < coord_input.size(); i++) {
// GPS: replace these with std min/max, which is more idiomatic
xmin = std::min(xmin,coord_input[i].x());
xmax = std::max(xmax,coord_input[i].x());
ymin = std::min(ymin,coord_input[i].y());
ymax = std::max(ymax,coord_input[i].y());
}
// GPS: this is necessary to avoid problems that can occur with
// a limited yrange, namely the fact that the cartesian
// radius can end up translating to a large FJ radius,
// which then causes problems with the periodicity
ymax = std::max(ymax, ymin + _cartesian_radius);
// Next figure out rescaling of jet radius.
CartesianRange range(xmin,xmax,ymin,ymax);
double radius = range.pseudojet_radius(_cartesian_radius);
// Define jet algorithm, crucially with recombiner
JetDefinition jet_def(genkt_algorithm,radius, _p);
jet_def.set_recombiner(&_recombiner);
// Convert CartesianCoordinates to CartesianJets
std::vector cjs_input = range.create_cartesian_jets(coord_input);
// Run clustering
ClusterSequence * clust_seq = new ClusterSequence(cjs_input,jet_def);
// Get inclusive jet output
std::vector pjs_output = sorted_by_pt(clust_seq->inclusive_jets(0.0));
// Convert to CartesianJets
std::vector cjs_output(pjs_output.begin(),pjs_output.end());
//Tell cluster sequence to delete self
clust_seq->delete_self_when_unused();
return cjs_output;
}
private:
double _cartesian_radius;
double _p;
CartesianRecombiner _recombiner;
};
} // namespace contrib
FASTJET_END_NAMESPACE
#endif // __FASTJET_CONTRIB_CartesianJet_HH__