// $Id: GenericSubtractor.cc 2997 2013-01-28 20:52:04Z soyez $ // // Copyright (c) 2012-, Matteo Cacciari, Jihun Kim, 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/ClusterSequenceAreaBase.hh" #include "fastjet/tools/JetMedianBackgroundEstimator.hh" #include "GenericSubtractor.hh" #include "SimpleGhostRescaler.hh" #include "ShapeWithPartition.hh" using namespace std; FASTJET_BEGIN_NAMESPACE // defined in fastjet/internal/base.hh namespace contrib{ //------------------------------------------------------------------------ // implementation of Genericsubtractor //------------------------------------------------------------------------ const double GenericSubtractor::_invalid_rho = -numeric_limits::infinity(); // Constructor that takes an externally supplied value for rho and, // optionally, for rho_m. The latter defaults to zero. GenericSubtractor:: GenericSubtractor(double rho, double rhom) : _bge_rho(0), _bge_rhom(0),_jet_pt_fraction(0.01), _common_bge(false), _rho(rho), _rhom(rhom), _externally_supplied_rho_rhom(true) { assert(_rho >= 0); assert(_rhom >= 0); } //---------------------------------------------------------------------- // the action on a given jet for a given shape double GenericSubtractor::operator()(const FunctionOfPseudoJet &shape, const PseudoJet &jet) const{ GenericSubtractorInfo dummy_info; return (*this)(shape, jet, dummy_info); } //---------------------------------------------------------------------- // the action on a given jet for a given shape double GenericSubtractor::operator()(const FunctionOfPseudoJet &shape, const PseudoJet &jet, GenericSubtractorInfo &info) const{ // make sure we have a BGE or a rho value if (!_bge_rho && !_externally_supplied_rho_rhom) throw Error("GenericSubtractor::operator(): generic subtraction needs a JetMedianBackgroundEstimator or a value for rho"); // if the shape is of the "ShapeWithPartition" type, first compute // the partition and work with that const ShapeWithPartition * shape_with_partition_ptr = dynamic_cast(&shape); PseudoJet working_jet = (shape_with_partition_ptr != 0) ? shape_with_partition_ptr->partition(jet) : jet; // for a shape with components, we will recursively use a separation // function that recursively calls the subtraction on the different // components and then assembles them. const ShapeWithComponents * shape_ptr = dynamic_cast(&shape); if (shape_ptr != 0) { return _component_subtraction(shape_ptr, working_jet, info); } //---------------------------------------------------------------------- // compute the average ghost scale (as a reference) vector ghosts = SelectorIsPureGhost()(working_jet.constituents()); if (ghosts.size() == 0){ // Note: no need to worry about any potential partitioning at this stage // we just do it for efficiency (in case it has been computed already info._unsubtracted = (shape_with_partition_ptr != 0) ? shape_with_partition_ptr->result_from_partition(working_jet) : shape(jet); info._first_order_subtracted = info._unsubtracted; info._second_order_subtracted = info._unsubtracted; info._third_order_subtracted = info._unsubtracted; info._first_derivative = info._second_derivative = info._third_derivative = 0.0; info._ghost_scale_used = 0; return info._unsubtracted; } double ghost_scale=0.0; for (vector::iterator git=ghosts.begin(); git!=ghosts.end();git++) { ghost_scale += git->perp(); } ghost_scale /= ghosts.size(); //---------------------------------------------------------------------- // compute once and for all the "unsubtracted" shape double f0 = _shape_with_rescaled_ghosts(shape, working_jet, ghost_scale, ghost_scale, 0); info._unsubtracted = f0; //---------------------------------------------------------------------- // estimate the background (both pt and dt=mt-pt) densities double ghost_area = ghosts[0].area(); double rho, rhom; if ( _externally_supplied_rho_rhom ) { rho = _rho; rhom = _rhom; } else { rho = _bge_rho->rho(jet); // rho_m may be evaluated from a dedicated background estimator or // from a modification of the jet density class of the "rho" background estimator; // if neither of those options is chosen, we set it to zero. if (_bge_rhom){ rhom = _bge_rhom->rho(jet); } else if (_common_bge){ BackgroundJetPtMDensity _m_density; JetMedianBackgroundEstimator *jmbge = dynamic_cast(_bge_rho); const FunctionOfPseudoJet * orig_density = jmbge->jet_density_class(); jmbge->set_jet_density_class(&_m_density); rhom = jmbge->rho(jet); jmbge->set_jet_density_class(orig_density); } else { rhom = 0.0; } } info._rho = rho; info._rhom = rhom; double rho_sum = rho + rhom; double rho_pt_fraction = (rho_sum == 0) ? 0.0 : rho/rho_sum; //---------------------------------------------------------------------- // compute the average ghost scale (as a reference) _compute_derivatives(shape, working_jet, ghost_scale, ghost_area, f0, rho_pt_fraction, info); info._first_order_subtracted = f0 - rho_sum * info._first_derivative; info._second_order_subtracted = info._first_order_subtracted + 0.5 * pow(rho_sum,2) * info._second_derivative; info._third_order_subtracted = info._second_order_subtracted - pow(rho_sum,3)/6.0 * info._third_derivative; return info._second_order_subtracted; } //---------------------------------------------------------------------- void GenericSubtractor::use_common_bge_for_rho_and_rhom(bool value){ if (value){ // make sure we only have one bge if (_bge_rhom) throw Error("GenericSubtractor::use_common_bge_for_rho_and_rhom() is not allowed in the presence of an existing background estimator for rho_m."); // make sure we are not using externally supplied values for tho and rhom if (_externally_supplied_rho_rhom) throw Error("GenericSubtractor::use_common_bge_for_rho_and_rhom() is not allowed when supplying externally the values for rho and rho_m."); // check the background estimator type JetMedianBackgroundEstimator *jmbge = dynamic_cast(_bge_rho); if (!jmbge) throw Error("GenericSubtractor::use_common_bge_for_rho_and_rhom() is currently only allowed for background estimators of JetMedianBackgroundEstimator type."); } _common_bge=value; } //---------------------------------------------------------------------- // a description of what this class does string GenericSubtractor::description() const{ ostringstream descr; if ( _externally_supplied_rho_rhom){ descr << "GenericSubtractor using externally supplied rho = " << _rho << " and rho_m = " << _rhom << " to describe the background"; } else { if (_bge_rhom) { descr << "GenericSubtractor using [" << _bge_rho->description() << "] and [" << _bge_rhom->description() << "] to estimate the background"; } else { descr << "GenericSubtractor using [" << _bge_rho->description() << "] to estimate the background"; } } return descr.str(); } // tools to help compute the derivatives //---------------------------------------------------------------------- // do the computation of the various derivatives // // rho_pt_fraction is rho/(rho + rho_m) is used to know along // which trajectory in the (rho, rho_m) plane one must estimate // the derivative. void GenericSubtractor::_compute_derivatives( const FunctionOfPseudoJet &shape, const PseudoJet &jet, const double original_ghost_scale, const double ghost_area, const double f0, const double rho_pt_fraction, GenericSubtractorInfo &info) const{ // here's how we proceed: // // 1. compute the 1st and 2nd derivatives in (rho+rho_m) at various step sizes // 2. search for the optimal step size // 3. recompute the 1st, 2nd and 3rd derivatives and store them in info //---------------------------------------------------------------------- // compute the optimal step sizes double cached_functions[4]; // the maximum step is chosen such that the sum of the ghosts will have a // pt equal to the jet's pt. double step_max = jet.pt() / (jet.area()/ghost_area); // go and compute the optimal step-size in pt double h = _optimize_step(shape, jet, original_ghost_scale, ghost_area, rho_pt_fraction, f0, cached_functions, step_max); double f1 = cached_functions[0]; double f2 = cached_functions[1]; double f3 = cached_functions[2]; double f4 = cached_functions[3]; info._ghost_scale_used = h; //---------------------------------------------------------------------- // compute all the derivatives these points double d1 = (f1-f0)*8; double d2 = (f2-f0)*4; double d3 = (f3-f0)*2; double d4 = (f4-f0); info._first_derivative = (64.0/21.0*d1 - 8.0/3.0*d2 + 2.0/3.0*d3 - 1.0/21.0*d4)/h * ghost_area; double s1 = 8*(d2/h-d1/h); double s2 = 4*(d3/h-d2/h); double s3 = 2*(d4/h-d3/h); info._second_derivative = (8.0/3.0*s1-2.0*s2+1/3.0*s3)/(h/2) * ghost_area * ghost_area; double t1 = (s2-s1)/h; double t2 = (s3-s2)/h; info._third_derivative = (4*t1-t2)/(h/8) * ghost_area * ghost_area * ghost_area; } //---------------------------------------------------------------------- // make a sweep in stepsize to determine which step is the most precise // // Note that this returns the values of the function at the points // needed to compute the derivatives double GenericSubtractor::_optimize_step( const FunctionOfPseudoJet &shape, const PseudoJet &jet, const double original_ghost_scale, const double ghost_area, const double x_fraction, const double f0, double cached_functions[4], const double max_step=1.0) const{ // do the scale sweep for the 1st derivative in x // // we need to compute the derivative using steps // h_i = 2^{-i} h0 i=0, ..., nh // // Even with a very good numerical precision, a scale of // h=1e-3..1e-4 gave very good results, so we shall use h0=pt, // nh=28 (to have a margin of security and go down to 1e-6 for pt=1000) // // For each of these stepsizes, we need to compute the derivative at // x, x+h_i/8, x+h_i/4, x+h_i/2, x+h_i // // We start by computing the derivatives at each scale // // Note that one may perhaps want to use a forward rule i.e. not use // the shape at x to estimate the derivatives. const int nh=28; const double h0 = max_step; double ref_pt = _jet_pt_fraction * jet.perp(); double d1[nh+1], d2[nh+1], stab[nh+1], fcts[nh+4]; double f1, f2, f3, f4; double h = h0*pow(2.0,-nh); fcts[0] = f1 = _shape_with_rescaled_ghosts(shape, jet, original_ghost_scale, x_fraction*h/8, (1-x_fraction)*h/8); fcts[1] = f2 = _shape_with_rescaled_ghosts(shape, jet, original_ghost_scale, x_fraction*h/4, (1-x_fraction)*h/4); fcts[2] = f3 = _shape_with_rescaled_ghosts(shape, jet, original_ghost_scale, x_fraction*h/2, (1-x_fraction)*h/2); for (int i=0;i<=nh;i++){ fcts[i+3] = f4 = _shape_with_rescaled_ghosts(shape,jet,original_ghost_scale, x_fraction*h, (1-x_fraction)*h); // apply a forward 5-point rule (including f0) double D1 = (f1-f0)/(h/8); double D2 = (f2-f0)/(h/4); double D3 = (f3-f0)/(h/2); double D4 = (f4-f0)/h; d1[nh-i] = (64.0/21.0*D1 - 8.0/ 3.0*D2 + 2.0/ 3.0*D3 - 1.0/21.0*D4) * ghost_area; double S1 = (D2-D1)/(h/8); double S2 = (D3-D2)/(h/4); double S3 = (D4-D3)/(h/2); d2[nh-i] = (2*(8.0/3.0*S1-2.0*S2+1/3.0*S3)) * ghost_area * ghost_area; stab[nh-i] = ref_pt * (abs(d1[nh-i]) + ref_pt*abs(d2[nh-i])); // some of the points can be reused for the next scale h = h0*pow(2.0,-nh+i+1); f1 = f2; f2 = f3; f3 = f4; } int n_plateau = 4; double mindiff = numeric_limits::max(); int n_mindiff = 0; for (int iscale = n_plateau/2; iscale <= ((int) nh)-n_plateau/2+1; iscale++){ // this was mostly Gavin and Matteo's original code. I've replaced // it by a test discarding when the diff is 0. That gives better // results for the 2nd derivative as all its building blocks can // be 0 (in which case we'd better increase the step size). // // // ignore cases where one of the derivatives is zero (could this // // get us into trouble if the derivative is genuinely zero?) // if (stab[iscale-1] == 0 || stab[iscale] == 0 || stab[iscale+1] == 0) break; double diff=0; for (int i = -n_plateau/2+1; i <= n_plateau/2-1; i++) diff += abs(stab[iscale+i] - stab[iscale+i-1]); if ((diff>0) && (diff < mindiff)){ mindiff = diff; n_mindiff = iscale; } } for (unsigned int i=0;i<4;i++) cached_functions[i] = fcts[nh-n_mindiff+i]; return h0*pow(2.0,-n_mindiff); } //---------------------------------------------------------------------- // the function that does the rescaling double GenericSubtractor::_shape_with_rescaled_ghosts( const FunctionOfPseudoJet &shape, const PseudoJet &jet, double original_ghost_scale, double new_ghost_scale, double new_dmass_scale) const{ const ShapeWithPartition * shape_with_partition_ptr = dynamic_cast(&shape); if (shape_with_partition_ptr != 0) return shape_with_partition_ptr->result_from_partition(SimpleGhostRescaler(new_ghost_scale, new_dmass_scale, original_ghost_scale)(jet)); return shape(SimpleGhostRescaler(new_ghost_scale, new_dmass_scale, original_ghost_scale)(jet)); } //---------------------------------------------------------------------- // perform subtractor on individual components of a shape; currently // not necessarily the most efficient of all possible implementations (e.g. // scaling is repeated for each of the individual components, etc.) double GenericSubtractor::_component_subtraction( const ShapeWithComponents * shape_ptr, const PseudoJet & jet, GenericSubtractorInfo &info) const { unsigned n = shape_ptr->n_components(); // prepare vectors to contain (un)subtracted results of individual components vector subtracted1_components(n); vector subtracted2_components(n); vector subtracted3_components(n); vector unsubtracted_components(n); // then perform subtraction of each of the components GenericSubtractorInfo component_info; for (unsigned i = 0; i < n; i++) { // make a subsiduary shape that returns just the i^{th} component // (an auto_ptr makes sure memory usage is safe) auto_ptr > shape(shape_ptr->component_shape(i)); subtracted2_components[i] = (*this)(*shape, jet, component_info); subtracted1_components[i] = component_info.first_order_subtracted(); subtracted3_components[i] = component_info.third_order_subtracted(); unsubtracted_components[i] = component_info.unsubtracted(); } // obtain (un)subtracted results by combining components. info._unsubtracted = shape_ptr->result_from_components(unsubtracted_components); info._first_order_subtracted = shape_ptr->result_from_components(subtracted1_components); info._second_order_subtracted = shape_ptr->result_from_components(subtracted2_components); info._third_order_subtracted = shape_ptr->result_from_components(subtracted3_components); // final result (without any care for safety estimates) double result_subtracted = info._second_order_subtracted; // there is no straightforward way of evaluating individual derivatives for // the combination of the components, so these are set to zero. info._first_derivative = info._second_derivative = info._third_derivative = 0.0; return result_subtracted; } } // namespace contrib FASTJET_END_NAMESPACE