2#ifndef __FASTJET_CLUSTERQUENCE_N2_ICC__
3#define __FASTJET_CLUSTERQUENCE_N2_ICC__
4#include "fastjet/ClusterSequence.hh"
7// $Id: ClusterSequence_N2.cc 1351 2009-01-09 18:03:03Z salam $
9// Copyright (c) 2005-2025, Matteo Cacciari, Gavin P. Salam and Gregory Soyez
11//----------------------------------------------------------------------
12// This file is part of FastJet.
14// FastJet is free software; you can redistribute it and/or modify
15// it under the terms of the GNU General Public License as published by
16// the Free Software Foundation; either version 2 of the License, or
17// (at your option) any later version.
19// The algorithms that underlie FastJet have required considerable
20// development. They are described in the original FastJet paper,
21// hep-ph/0512210 and in the manual, arXiv:1111.6097. If you use
22// FastJet as part of work towards a scientific publication, please
23// quote the version you use and include a citation to the manual and
24// optionally also to hep-ph/0512210.
26// FastJet is distributed in the hope that it will be useful,
27// but WITHOUT ANY WARRANTY; without even the implied warranty of
28// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
29// GNU General Public License for more details.
31// You should have received a copy of the GNU General Public License
32// along with FastJet. If not, see <http://www.gnu.org/licenses/>.
33//----------------------------------------------------------------------
36//----------------------------------------------------------------------
37/// Order(N^2) clustering
39/// Works for any class BJ that satisfies certain minimal
40/// requirements (which are ...?)
42/// - need to have _bj_set_jetinfo
43/// - need to have _bj_dist
44/// - should contain members kt2 (=energy^2), NN, NN_dist, _jets_index
46FASTJET_BEGIN_NAMESPACE // defined in fastjet/internal/base.hh
48// this should not normally appear in an include file, but we'll make an
49// exception seeing as this is
53template<class BJ> void ClusterSequence::_simple_N2_cluster() {
55 BJ * briefjets = new BJ[n];
56 BJ * jetA = briefjets, * jetB;
58 // initialise the basic jet info
59 for (int i = 0; i< n; i++) {
60 _bj_set_jetinfo(jetA, i);
61 jetA++; // move on to next entry of briefjets
63 BJ * tail = jetA; // a semaphore for the end of briefjets
64 BJ * head = briefjets; // a nicer way of naming start
66 // now initialise the NN distances: jetA will run from 1..n-1; and
67 // jetB from 0..jetA-1
68 for (jetA = head + 1; jetA != tail; jetA++) {
69 // set NN info for jetA based on jets running from head..jetA-1,
70 // checking in the process whether jetA itself is an undiscovered
71 // NN of one of those jets.
72 _bj_set_NN_crosscheck(jetA, head, jetA);
76 // now create the diJ (where J is i's NN) table -- remember that
77 // we differ from standard normalisation here by a factor of R2
78 double * diJ = new double[n];
80 for (int i = 0; i < n; i++) {
81 diJ[i] = _bj_diJ(jetA);
82 jetA++; // have jetA follow i
85 // now run the recombination loop
86 while (tail != head) {
88 // find the minimum of the diJ on this round
89 double diJ_min = diJ[0];
91 for (int i = 1; i < n; i++) {
92 if (diJ[i] < diJ_min) {diJ_min_jet = i; diJ_min = diJ[i];}
95 // do the recombination between A and B
96 jetA = & briefjets[diJ_min_jet];
99 jetB = static_cast<BJ *>(jetA->NN);
100 // put the normalisation back in
103 // jet-jet recombination
104 // If necessary relabel A & B to ensure jetB < jetA, that way if
105 // the larger of them == newtail then that ends up being jetA and
106 // the new jet that is added as jetB is inserted in a position that
108 if (jetA < jetB) {std::swap(jetA,jetB);}
110 int nn; // new jet index
111 _do_ij_recombination_step(jetA->_jets_index, jetB->_jets_index, diJ_min, nn);
113 // what was jetB will now become the new jet
114 _bj_set_jetinfo(jetB, nn);
117 // jet-beam recombination
118 _do_iB_recombination_step(jetA->_jets_index, diJ_min);
121 // now update our nearest neighbour info and diJ table
122 // first reduce size of table
124 // Copy last jet contents and diJ info into position of jetA
126 diJ[jetA - head] = diJ[tail-head];
128 // Initialise jetB's NN distance as well as updating it for
131 // There are several potential variants of the algorithm below for
132 // updating the NN info and diJ table. Differences affect cases
133 // where jetB is null. Numbers below are for antikt, R=0.4,
134 // Nparticles=50, cf. top-level/development-notes/2025-03-speed and
135 // the percent number indicates the change in runtime relative to
136 // "original" (more negative is better)
138 // 0: original, i.e. up to FJ version 3.4.3, good compromise in normal conditions, pathological for very small R (cf. 2503.08146)
139 // 1: add extra non-null jetB test in loop intel/linux:+1%, M2Pro/mac:+2%
140 // 2: two independent loops intel/linux:-3%, M2Pro/mac:+2%
141 // 3: proxy jetB_or_A to avoid null test intel/linux:-1%, M2Pro/mac:-1%
143 // Note that on intel/linux/gcc(14.2) -O2 v. -O3 tends to be a small difference
144 // while on M2Pro/mac/clang(17) -O3 is about 10% faster than -O2, while gcc is 7% faster and less sensitive to -O2 v. -O3
146 // We settle on strategy 2 for the 3.5.X series, starting 2025-05, on the
147 // assumption that intel/linux is the dominant platform for most
148 // very long runs with FJ.
149#define FJ_N2_UPDATE_STRATEGY 2
151#if FJ_N2_UPDATE_STRATEGY==0
152 for (BJ * jetI = head; jetI != tail; jetI++) {
153 // see if jetI had jetA or jetB as a NN -- if so recalculate the NN
154 if (jetI->NN == jetA || jetI->NN == jetB) {
155 _bj_set_NN_nocross(jetI, head, tail);
156 diJ[jetI-head] = _bj_diJ(jetI); // update diJ
158 // check whether new jetB is closer than jetI's current NN and
159 // if need be update things
161 double dist = _bj_dist(jetI,jetB);
162 if (dist < jetI->NN_dist) {
164 jetI->NN_dist = dist;
166 diJ[jetI-head] = _bj_diJ(jetI); // update diJ...
169 if (dist < jetB->NN_dist) {
171 jetB->NN_dist = dist;
175 // if jetI's NN is the new tail then relabel it so that it becomes jetA
176 if (jetI->NN == tail) {jetI->NN = jetA;}
178 if (jetB != NULL) {diJ[jetB-head] = _bj_diJ(jetB);}
179#elif FJ_N2_UPDATE_STRATEGY==1
180 for (BJ * jetI = head; jetI != tail; jetI++) {
181 // see if jetI had jetA or jetB as a NN -- if so recalculate the NN
182 if (jetI->NN == jetA || (jetB && jetI->NN == jetB)) {
183 _bj_set_NN_nocross(jetI, head, tail);
184 diJ[jetI-head] = _bj_diJ(jetI); // update diJ
186 // check whether new jetB is closer than jetI's current NN and
187 // if need be update things
189 double dist = _bj_dist(jetI,jetB);
190 if (dist < jetI->NN_dist) {
192 jetI->NN_dist = dist;
194 diJ[jetI-head] = _bj_diJ(jetI); // update diJ...
197 if (dist < jetB->NN_dist) {
199 jetB->NN_dist = dist;
203 // if jetI's NN is the new tail then relabel it so that it becomes jetA
204 if (jetI->NN == tail) {jetI->NN = jetA;}
206 if (jetB != NULL) {diJ[jetB-head] = _bj_diJ(jetB);}
207#elif FJ_N2_UPDATE_STRATEGY==2
209 for (BJ * jetI = head; jetI != tail; jetI++) {
210 // see if jetI had jetA or jetB as a NN -- if so recalculate the NN
211 if (jetI->NN == jetA || jetI->NN == jetB) {
212 _bj_set_NN_nocross(jetI, head, tail);
213 diJ[jetI-head] = _bj_diJ(jetI); // update diJ
215 // check whether new jetB is closer than jetI's current NN and
216 // if need be update things
217 double dist = _bj_dist(jetI,jetB);
218 if (dist < jetI->NN_dist) {
220 jetI->NN_dist = dist;
222 diJ[jetI-head] = _bj_diJ(jetI); // update diJ...
225 // and check whether jetI is potentially jetB's NN
226 if (dist < jetB->NN_dist) {
228 jetB->NN_dist = dist;
231 // if jetI's NN is the new tail then relabel it so that it becomes jetA
232 if (jetI->NN == tail) {jetI->NN = jetA;}
234 diJ[jetB-head] = _bj_diJ(jetB);
236 // case where jetB == NULL
237 for (BJ * jetI = head; jetI != tail; jetI++) {
238 // see if jetI had jetA as a NN -- if so recalculate the NN
239 if (jetI->NN == jetA) {
240 _bj_set_NN_nocross(jetI, head, tail);
241 diJ[jetI-head] = _bj_diJ(jetI); // update diJ
243 // if jetI's NN is the new tail then relabel it so that it becomes jetA
244 if (jetI->NN == tail) {jetI->NN = jetA;}
247#elif FJ_N2_UPDATE_STRATEGY==3
248 auto jetB_or_A = jetB ? jetB : jetA;
249 for (BJ * jetI = head; jetI != tail; jetI++) {
250 // see if jetI had jetA or jetB as a NN -- if so recalculate the NN
251 if (jetI->NN == jetA || jetI->NN == jetB_or_A) {
252 _bj_set_NN_nocross(jetI, head, tail);
253 diJ[jetI-head] = _bj_diJ(jetI); // update diJ
255 // check whether new jetB is closer than jetI's current NN and
256 // if need be update things
258 double dist = _bj_dist(jetI,jetB);
259 if (dist < jetI->NN_dist) {
261 jetI->NN_dist = dist;
263 diJ[jetI-head] = _bj_diJ(jetI); // update diJ...
266 if (dist < jetB->NN_dist) {
268 jetB->NN_dist = dist;
272 // if jetI's NN is the new tail then relabel it so that it becomes jetA
273 if (jetI->NN == tail) {jetI->NN = jetA;}
275 if (jetB != NULL) {diJ[jetB-head] = _bj_diJ(jetB);}
277#error "unknown N2 update strategy"
279#undef FJ_N2_UPDATE_STRATEGY
283 // final cleaning up;
290// //----------------------------------------------------------------------
291// // initialises a GenBriefJet
292// template<> inline void ClusterSequence::_bj_set_jetinfo(
293// GenBriefJet * const jetA, const int _jets_index) const {
295// jetA->init(_jets[_jets_index]);
296// jetA->_jets_index = _jets_index;
301// //----------------------------------------------------------------------
302// // returns the distance between two GenBriefJets
303// template<> double ClusterSequence::_bj_dist(
304// const GenBriefJet * const jeta,
305// const GenBriefJet * const jetb) const {
306// return jeta->geom_ij(jetb);
311#endif // __FASTJET_CLUSTERQUENCE_N2_ICC__