1 //FJSTARTHEADER
2 // $Id: JetDefinition.cc 4442 2020-05-05 07:50:11Z soyez $
3 //
4 // Copyright (c) 2005-2020, Matteo Cacciari, Gavin P. Salam and Gregory Soyez
5 //
6 //----------------------------------------------------------------------
7 // This file is part of FastJet.
8 //
9 //  FastJet is free software; you can redistribute it and/or modify
10 //  it under the terms of the GNU General Public License as published by
11 //  the Free Software Foundation; either version 2 of the License, or
12 //  (at your option) any later version.
13 //
14 //  The algorithms that underlie FastJet have required considerable
15 //  development. They are described in the original FastJet paper,
16 //  hep-ph/0512210 and in the manual, arXiv:1111.6097. If you use
17 //  FastJet as part of work towards a scientific publication, please
18 //  quote the version you use and include a citation to the manual and
19 //  optionally also to hep-ph/0512210.
20 //
21 //  FastJet is distributed in the hope that it will be useful,
22 //  but WITHOUT ANY WARRANTY; without even the implied warranty of
23 //  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
24 //  GNU General Public License for more details.
25 //
26 //  You should have received a copy of the GNU General Public License
27 //  along with FastJet. If not, see <http://www.gnu.org/licenses/>.
28 //----------------------------------------------------------------------
29 //FJENDHEADER
30 
31 #include "fastjet/JetDefinition.hh"
32 #include "fastjet/Error.hh"
33 #include "fastjet/CompositeJetStructure.hh"
34 #include<sstream>
35 
36 FASTJET_BEGIN_NAMESPACE      // defined in fastjet/internal/base.hh
37 
38 using namespace std;
39 
40 const double JetDefinition::max_allowable_R = 1000.0;
41 
42 //----------------------------------------------------------------------
43 // [NB: implementation was getting complex, so in 2.4-devel moved it
44 //  from .hh to .cc]
JetDefinition(JetAlgorithm jet_algorithm_in,double R_in,RecombinationScheme recomb_scheme_in,Strategy strategy_in,int nparameters)45 JetDefinition::JetDefinition(JetAlgorithm jet_algorithm_in,
46 			     double R_in,
47 			     RecombinationScheme recomb_scheme_in,
48 			     Strategy strategy_in,
49                              int nparameters) :
50   _jet_algorithm(jet_algorithm_in), _Rparam(R_in), _strategy(strategy_in) {
51 
52   // set R parameter or ensure its sensibleness, as appropriate
53   if (_jet_algorithm == ee_kt_algorithm) {
54     _Rparam = 4.0; // introduce a fictional R that ensures that
55                    // our clustering sequence will not produce
56                    // "beam" jets except when only a single particle remains.
57                    // Any value > 2 would have done here
58   } else {
59     // We maintain some limit on R because particles with pt=0, m=0
60     // can have rapidities O(100000) and one doesn't want the
61     // clustering to start including them as if their rapidities were
62     // physical.
63     if (R_in > max_allowable_R) {
64       ostringstream oss;
65       oss << "Requested R = " << R_in << " for jet definition is larger than max_allowable_R = " << max_allowable_R;
66       throw Error(oss.str());
67     }
68   }
69 
70   // cross-check the number of parameters that were declared in setting up the
71   // algorithm (passed internally from the public constructors)
72   unsigned int nparameters_expected = n_parameters_for_algorithm(jet_algorithm_in);
73   if (nparameters != (int) nparameters_expected){
74     ostringstream oss;
75     oss << "The jet algorithm you requested ("
76         << jet_algorithm_in << ") should be constructed with " << nparameters_expected
77         << " parameter(s) but was called with " << nparameters << " parameter(s)\n";
78     throw Error(oss.str());
79   }
80 
81   // make sure the strategy requested is sensible
82   assert (_strategy  != plugin_strategy);
83 
84   _plugin = NULL;
85   set_recombination_scheme(recomb_scheme_in);
86   set_extra_param(0.0); // make sure it's defined
87 }
88 
89 
90 //----------------------------------------------------------------------
91 // returns true if the jet definition involves an algorithm
92 // intended for use on a spherical geometry (e.g. e+e- algorithms,
93 // as opposed to most pp algorithms, which use a cylindrical,
94 // rapidity-phi geometry).
is_spherical() const95 bool JetDefinition::is_spherical() const {
96   if (jet_algorithm() == plugin_algorithm) {
97     return plugin()->is_spherical();
98   } else {
99     return (jet_algorithm() == ee_kt_algorithm ||  // as of 2013-02-14, the two
100             jet_algorithm() == ee_genkt_algorithm  // native spherical algorithms
101             );
102   }
103 }
104 
105 //----------------------------------------------------------------------
description() const106 string JetDefinition::description() const {
107   ostringstream name;
108 
109   name << description_no_recombiner();
110 
111   if ((jet_algorithm() == plugin_algorithm) || (jet_algorithm() == undefined_jet_algorithm)){
112     return name.str();
113   }
114 
115   if (n_parameters_for_algorithm(jet_algorithm()) == 0)
116     name << " with ";
117   else
118     name << " and ";
119   name << recombiner()->description();
120 
121   return name.str();
122 }
123 
124 //----------------------------------------------------------------------
description_no_recombiner() const125 string JetDefinition::description_no_recombiner() const {
126 
127   ostringstream name;
128   if (jet_algorithm() == plugin_algorithm) {
129     return plugin()->description();
130   } else if (jet_algorithm() == undefined_jet_algorithm) {
131     return "uninitialised JetDefinition (jet_algorithm=undefined_jet_algorithm)" ;
132   }
133 
134   name << algorithm_description(jet_algorithm());
135   switch (n_parameters_for_algorithm(jet_algorithm())){
136   case 0: name << " (NB: no R)"; break;
137   case 1: name << " with R = " << R(); break; // the parameter is always R
138   case 2:
139     // the 1st parameter is always R
140     name << " with R = " << R();
141     // the 2nd depends on the algorithm
142     if (jet_algorithm() == cambridge_for_passive_algorithm){
143       name << "and a special hack whereby particles with kt < "
144            << extra_param() << "are treated as passive ghosts";
145     } else {
146       name << ", p = " << extra_param();
147     }
148   };
149 
150   return name.str();
151 }
152 
153 //----------------------------------------------------------------------
algorithm_description(const JetAlgorithm jet_alg)154 string JetDefinition::algorithm_description(const JetAlgorithm jet_alg){
155   ostringstream name;
156   switch (jet_alg){
157   case plugin_algorithm:                return "plugin algorithm";
158   case kt_algorithm:                    return "Longitudinally invariant kt algorithm";
159   case cambridge_algorithm:             return "Longitudinally invariant Cambridge/Aachen algorithm";
160   case antikt_algorithm:                return "Longitudinally invariant anti-kt algorithm";
161   case genkt_algorithm:                 return "Longitudinally invariant generalised kt algorithm";
162   case cambridge_for_passive_algorithm: return "Longitudinally invariant Cambridge/Aachen algorithm";
163   case ee_kt_algorithm:                 return "e+e- kt (Durham) algorithm (NB: no R)";
164   case ee_genkt_algorithm:              return "e+e- generalised kt algorithm";
165   case undefined_jet_algorithm:         return "undefined jet algorithm";
166   default:
167     throw Error("JetDefinition::algorithm_description(): unrecognized jet_algorithm");
168   };
169 }
170 
171 //----------------------------------------------------------------------
n_parameters_for_algorithm(const JetAlgorithm jet_alg)172 unsigned int JetDefinition::n_parameters_for_algorithm(const JetAlgorithm jet_alg){
173   switch (jet_alg) {
174   case ee_kt_algorithm:    return 0;
175   case genkt_algorithm:
176   case ee_genkt_algorithm: return 2;
177   default:                 return 1;
178   };
179 }
180 
181 //----------------------------------------------------------------------
set_recombination_scheme(RecombinationScheme recomb_scheme)182 void JetDefinition::set_recombination_scheme(
183                                RecombinationScheme recomb_scheme) {
184   _default_recombiner = JetDefinition::DefaultRecombiner(recomb_scheme);
185 
186   // do not forget to delete the existing recombiner if needed
187   if (_shared_recombiner) _shared_recombiner.reset();
188 
189   _recombiner = 0;
190 }
191 
set_recombiner(const JetDefinition & other_jet_def)192 void JetDefinition::set_recombiner(const JetDefinition &other_jet_def){
193   // make sure the "invariants" of the other jet def are sensible
194   assert(other_jet_def._recombiner ||
195          other_jet_def.recombination_scheme() != external_scheme);
196 
197   // first treat the situation where we're using the default recombiner
198   if (other_jet_def._recombiner == 0){
199     set_recombination_scheme(other_jet_def.recombination_scheme());
200     return;
201   }
202 
203   // in other cases, copy the pointer to the recombiner
204   _recombiner = other_jet_def._recombiner;
205   // set the default recombiner appropriately
206   _default_recombiner = DefaultRecombiner(external_scheme);
207   // and set the _shared_recombiner to the same state
208   // as in the other_jet_def, whatever that was
209   _shared_recombiner.reset(other_jet_def._shared_recombiner);
210 
211   // NB: it is tempting to go via set_recombiner and then to sort
212   // out the shared part, but this would be dangerous in the
213   // specific (rare?) case where other_jet_def is the same as this
214   // it deletes_recombiner_when_unused. In that case the shared
215   // pointer reset would delete the recombiner.
216 }
217 
218 
219 // returns true if the current jet definitions shares the same
220 // recombiner as teh one passed as an argument
has_same_recombiner(const JetDefinition & other_jd) const221 bool JetDefinition::has_same_recombiner(const JetDefinition &other_jd) const{
222   // first make sure that they have the same recombination scheme
223   const RecombinationScheme & scheme = recombination_scheme();
224   if (other_jd.recombination_scheme() != scheme) return false;
225 
226   // if the scheme is "external", also check that they have the same
227   // recombiner
228   return (scheme != external_scheme)
229     || (recombiner() == other_jd.recombiner());
230 }
231 
232 /// causes the JetDefinition to handle the deletion of the
233 /// recombiner when it is no longer used
delete_recombiner_when_unused()234 void JetDefinition::delete_recombiner_when_unused(){
235   if (_recombiner == 0){
236     throw Error("tried to call JetDefinition::delete_recombiner_when_unused() for a JetDefinition without a user-defined recombination scheme");
237   } else if (_shared_recombiner.get()) {
238     throw Error("Error in JetDefinition::delete_recombiner_when_unused: the recombiner is already scheduled for deletion when unused (or was already set as shared)");
239   }
240 
241   _shared_recombiner.reset(_recombiner);
242 }
243 
244 /// allows to let the JetDefinition handle the deletion of the
245 /// plugin when it is no longer used
delete_plugin_when_unused()246 void JetDefinition::delete_plugin_when_unused(){
247   if (_plugin == 0){
248     throw Error("tried to call JetDefinition::delete_plugin_when_unused() for a JetDefinition without a plugin");
249   }
250 
251   _plugin_shared.reset(_plugin);
252 }
253 
254 
255 
description() const256 string JetDefinition::DefaultRecombiner::description() const {
257   switch(_recomb_scheme) {
258   case E_scheme:
259     return "E scheme recombination";
260   case pt_scheme:
261     return "pt scheme recombination";
262   case pt2_scheme:
263     return "pt2 scheme recombination";
264   case Et_scheme:
265     return "Et scheme recombination";
266   case Et2_scheme:
267     return "Et2 scheme recombination";
268   case BIpt_scheme:
269     return "boost-invariant pt scheme recombination";
270   case BIpt2_scheme:
271     return "boost-invariant pt2 scheme recombination";
272   case WTA_pt_scheme:
273     return "pt-ordered Winner-Takes-All recombination";
274   // Energy-ordering can lead to dangerous situations with particles at
275   // rest. We instead implement the WTA_modp_scheme
276   //
277   //   case WTA_E_scheme:
278   //     return "energy-ordered Winner-Takes-All recombination";
279   case WTA_modp_scheme:
280     return "|3-momentum|-ordered Winner-Takes-All recombination";
281   default:
282     ostringstream err;
283     err << "DefaultRecombiner: unrecognized recombination scheme "
284         << _recomb_scheme;
285     throw Error(err.str());
286   }
287 }
288 
289 
recombine(const PseudoJet & pa,const PseudoJet & pb,PseudoJet & pab) const290 void JetDefinition::DefaultRecombiner::recombine(
291            const PseudoJet & pa, const PseudoJet & pb,
292            PseudoJet & pab) const {
293 
294   double weighta, weightb;
295 
296   switch(_recomb_scheme) {
297   case E_scheme:
298     // a call to reset turns out to be somewhat more efficient
299     // than a sum and assignment
300     //pab = pa + pb;
301     pab.reset(pa.px()+pb.px(),
302     	      pa.py()+pb.py(),
303     	      pa.pz()+pb.pz(),
304     	      pa.E ()+pb.E ());
305     return;
306   // all remaining schemes are massless recombinations and locally
307   // we just set weights, while the hard work is done below...
308   case pt_scheme:
309   case Et_scheme:
310   case BIpt_scheme:
311     weighta = pa.perp();
312     weightb = pb.perp();
313     break;
314   case pt2_scheme:
315   case Et2_scheme:
316   case BIpt2_scheme:
317     weighta = pa.perp2();
318     weightb = pb.perp2();
319     break;
320   case WTA_pt_scheme:{
321     const PseudoJet & phard = (pa.pt2() >= pb.pt2()) ? pa : pb;
322     /// keep y,phi and m from the hardest, sum pt
323     pab.reset_PtYPhiM(pa.pt()+pb.pt(),
324                       phard.rap(), phard.phi(), phard.m());
325     return;}
326   // Energy-ordering can lead to dangerous situations with particles at
327   // rest. We instead implement the WTA_modp_scheme
328   //
329   //   case WTA_E_scheme:{
330   //     const PseudoJet & phard = (pa.E() >= pb.E()) ? pa : pb;
331   //     /// keep 3-momentum direction and mass from the hardest, sum energies
332   //     ///
333   //     /// If the particle with the largest energy is at rest, the sum
334   //     /// remains at rest, implying that the mass of the sum is larger
335   //     /// than the mass of pa.
336   //     double Eab = pa.E() + pb.E();
337   //     double scale = (phard.modp2()==0.0)
338   //       ? 0.0
339   //       : sqrt((Eab*Eab - phard.m2())/phard.modp2());
340   //     pab.reset(phard.px()*scale, phard.py()*scale, phard.pz()*scale, Eab);
341   //     return;}
342   case WTA_modp_scheme:{
343     // Note: we need to compute both a and b modp. And we need pthard
344     // and its modp. If we want to avoid repeating the test and do
345     // only 2 modp calculations, we'd have to duplicate the code (or
346     // use a pair<const PJ&>). An alternative is to write modp_soft as
347     // modp_ab-modp_hard but this could suffer from larger rounding
348     // errors
349     bool a_hardest = (pa.modp2() >= pb.modp2());
350     const PseudoJet & phard = a_hardest ? pa : pb;
351     const PseudoJet & psoft = a_hardest ? pb : pa;
352     /// keep 3-momentum direction and mass from the hardest, sum modp
353     ///
354     /// If the hardest particle is at rest, the sum remains at rest
355     /// (the energy of the sum is therefore the mass of pa)
356     double modp_hard = phard.modp();
357     double modp_ab = modp_hard + psoft.modp();
358     if (phard.modp2()==0.0){
359       pab.reset(0.0, 0.0, 0.0, phard.m());
360     } else {
361       double scale = modp_ab/modp_hard;
362       pab.reset(phard.px()*scale, phard.py()*scale, phard.pz()*scale,
363                 sqrt(modp_ab*modp_ab + phard.m2()));
364     }
365     return;}
366   default:
367     ostringstream err;
368     err << "DefaultRecombiner: unrecognized recombination scheme "
369         << _recomb_scheme;
370     throw Error(err.str());
371   }
372 
373   double perp_ab = pa.perp() + pb.perp();
374   if (perp_ab != 0.0) { // weights also non-zero...
375     double y_ab    = (weighta * pa.rap() + weightb * pb.rap())/(weighta+weightb);
376 
377     // take care with periodicity in phi...
378     double phi_a = pa.phi(), phi_b = pb.phi();
379     if (phi_a - phi_b > pi)  phi_b += twopi;
380     if (phi_a - phi_b < -pi) phi_b -= twopi;
381     double phi_ab = (weighta * phi_a + weightb * phi_b)/(weighta+weightb);
382 
383     // this is much more efficient...
384     pab.reset_PtYPhiM(perp_ab,y_ab,phi_ab);
385     // pab = PseudoJet(perp_ab*cos(phi_ab),
386     // 		    perp_ab*sin(phi_ab),
387     // 		    perp_ab*sinh(y_ab),
388     // 		    perp_ab*cosh(y_ab));
389   } else { // weights are zero
390     //pab = PseudoJet(0.0,0.0,0.0,0.0);
391     pab.reset(0.0, 0.0, 0.0, 0.0);
392   }
393 }
394 
395 
preprocess(PseudoJet & p) const396 void JetDefinition::DefaultRecombiner::preprocess(PseudoJet & p) const {
397   switch(_recomb_scheme) {
398   case E_scheme:
399   case BIpt_scheme:
400   case BIpt2_scheme:
401   case WTA_pt_scheme:
402   //case WTA_E_scheme:
403   case WTA_modp_scheme:
404     break;
405   case pt_scheme:
406   case pt2_scheme:
407     {
408       // these schemes (as in the ktjet implementation) need massless
409       // initial 4-vectors with essentially E=|p|.
410       double newE = sqrt(p.perp2()+p.pz()*p.pz());
411       p.reset_momentum(p.px(), p.py(), p.pz(), newE);
412       // FJ2.x version
413       // int    user_index = p.user_index();
414       // p = PseudoJet(p.px(), p.py(), p.pz(), newE);
415       // p.set_user_index(user_index);
416     }
417     break;
418   case Et_scheme:
419   case Et2_scheme:
420     {
421       // these schemes (as in the ktjet implementation) need massless
422       // initial 4-vectors with essentially E=|p|.
423       double rescale = p.E()/sqrt(p.perp2()+p.pz()*p.pz());
424       p.reset_momentum(rescale*p.px(), rescale*p.py(), rescale*p.pz(), p.E());
425       // FJ2.x version
426       // int    user_index = p.user_index();
427       // p = PseudoJet(rescale*p.px(), rescale*p.py(), rescale*p.pz(), p.E());
428       // p.set_user_index(user_index);
429     }
430     break;
431   default:
432     ostringstream err;
433     err << "DefaultRecombiner: unrecognized recombination scheme "
434         << _recomb_scheme;
435     throw Error(err.str());
436   }
437 }
438 
set_ghost_separation_scale(double) const439 void JetDefinition::Plugin::set_ghost_separation_scale(double /*scale*/) const {
440   throw Error("set_ghost_separation_scale not supported");
441 }
442 
443 
444 
445 //-------------------------------------------------------------------------------
446 // helper functions to build a jet made of pieces
447 //
448 // This is the extended version with support for a user-defined
449 // recombination-scheme
450 // -------------------------------------------------------------------------------
451 
452 // build a "CompositeJet" from the vector of its pieces
453 //
454 // the user passes the reciombination scheme used to "sum" the pieces.
join(const vector<PseudoJet> & pieces,const JetDefinition::Recombiner & recombiner)455 PseudoJet join(const vector<PseudoJet> & pieces, const JetDefinition::Recombiner & recombiner){
456   // compute the total momentum
457   //--------------------------------------------------
458   PseudoJet result;  // automatically initialised to 0
459   if (pieces.size()>0){
460     result = pieces[0];
461     for (unsigned int i=1; i<pieces.size(); i++)
462       recombiner.plus_equal(result, pieces[i]);
463   }
464 
465   // attach a CompositeJetStructure to the result
466   //--------------------------------------------------
467   CompositeJetStructure *cj_struct = new CompositeJetStructure(pieces, &recombiner);
468 
469   result.set_structure_shared_ptr(SharedPtr<PseudoJetStructureBase>(cj_struct));
470 
471   return result;
472 }
473 
474 // build a "CompositeJet" from a single PseudoJet
join(const PseudoJet & j1,const JetDefinition::Recombiner & recombiner)475 PseudoJet join(const PseudoJet & j1,
476 	       const JetDefinition::Recombiner & recombiner){
477   return join(vector<PseudoJet>(1,j1), recombiner);
478 }
479 
480 // build a "CompositeJet" from two PseudoJet
join(const PseudoJet & j1,const PseudoJet & j2,const JetDefinition::Recombiner & recombiner)481 PseudoJet join(const PseudoJet & j1, const PseudoJet & j2,
482 	       const JetDefinition::Recombiner & recombiner){
483   vector<PseudoJet> pieces;
484   pieces.push_back(j1);
485   pieces.push_back(j2);
486   return join(pieces, recombiner);
487 }
488 
489 // build a "CompositeJet" from 3 PseudoJet
join(const PseudoJet & j1,const PseudoJet & j2,const PseudoJet & j3,const JetDefinition::Recombiner & recombiner)490 PseudoJet join(const PseudoJet & j1, const PseudoJet & j2, const PseudoJet & j3,
491 	       const JetDefinition::Recombiner & recombiner){
492   vector<PseudoJet> pieces;
493   pieces.push_back(j1);
494   pieces.push_back(j2);
495   pieces.push_back(j3);
496   return join(pieces, recombiner);
497 }
498 
499 // build a "CompositeJet" from 4 PseudoJet
join(const PseudoJet & j1,const PseudoJet & j2,const PseudoJet & j3,const PseudoJet & j4,const JetDefinition::Recombiner & recombiner)500 PseudoJet join(const PseudoJet & j1, const PseudoJet & j2, const PseudoJet & j3, const PseudoJet & j4,
501 	       const JetDefinition::Recombiner & recombiner){
502   vector<PseudoJet> pieces;
503   pieces.push_back(j1);
504   pieces.push_back(j2);
505   pieces.push_back(j3);
506   pieces.push_back(j4);
507   return join(pieces, recombiner);
508 }
509 
510 
511 
512 
513 FASTJET_END_NAMESPACE
514