Skip to content

Commit 51621b5

Browse files
feat: expose contrib::Njettiness (#336)
* expose contrib::Njettiness * get shapes right and add test * lint * address first review comments * improve docstring * make njettiness work on input jet constituents instead * njettiness test better demonstrates usage * typo * add in dask dispatch * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * update tests with a real jet and expected taus * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
1 parent b2b472f commit 51621b5

File tree

6 files changed

+877
-0
lines changed

6 files changed

+877
-0
lines changed

src/_ext.cpp

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
#include <fastjet/PseudoJet.hh>
1818
#include <fastjet/contrib/EnergyCorrelator.hh>
1919
#include <fastjet/contrib/LundGenerator.hh>
20+
#include <fastjet/contrib/Njettiness.hh>
2021
#include <fastjet/contrib/SoftDrop.hh>
2122

2223
#include <pybind11/numpy.h>
@@ -28,6 +29,58 @@ namespace fj = fastjet;
2829
namespace py = pybind11;
2930
using namespace pybind11::literals;
3031

32+
// adapted from
33+
// https://github.com/cms-svj/SVJProduction/blob/Run3/interface/NjettinessHelper.h
34+
namespace njettiness {
35+
enum MeasureDefinition_t {
36+
NormalizedMeasure = 0, // (beta,R0)
37+
UnnormalizedMeasure, // (beta)
38+
OriginalGeometricMeasure, // (beta)
39+
NormalizedCutoffMeasure, // (beta,R0,Rcutoff)
40+
UnnormalizedCutoffMeasure, // (beta,Rcutoff)
41+
GeometricCutoffMeasure, // (beta,Rcutoff)
42+
N_MEASURE_DEFINITIONS
43+
};
44+
enum AxesDefinition_t {
45+
KT_Axes = 0,
46+
CA_Axes,
47+
AntiKT_Axes, // (axAxesR0)
48+
WTA_KT_Axes,
49+
WTA_CA_Axes,
50+
Manual_Axes,
51+
OnePass_KT_Axes,
52+
OnePass_CA_Axes,
53+
OnePass_AntiKT_Axes, // (axAxesR0)
54+
OnePass_WTA_KT_Axes,
55+
OnePass_WTA_CA_Axes,
56+
OnePass_Manual_Axes,
57+
MultiPass_Axes,
58+
N_AXES_DEFINITIONS
59+
};
60+
const std::unordered_map<std::string, MeasureDefinition_t>
61+
measure_def_names_to_enum = {
62+
{"NormalizedMeasure", NormalizedMeasure},
63+
{"UnnormalizedMeasure", UnnormalizedMeasure},
64+
{"OriginalGeometricMeasure", OriginalGeometricMeasure},
65+
{"NormalizedCutoffMeasure", NormalizedCutoffMeasure},
66+
{"UnnormalizedCutoffMeasure", UnnormalizedCutoffMeasure},
67+
{"GeometricCutoffMeasure", GeometricCutoffMeasure}};
68+
const std::unordered_map<std::string, AxesDefinition_t> axis_def_names_to_enum =
69+
{{"KT_Axes", KT_Axes},
70+
{"CA_Axes", CA_Axes},
71+
{"AntiKT_Axes", AntiKT_Axes},
72+
{"WTA_KT_Axes", WTA_KT_Axes},
73+
{"WTA_CA_Axes", WTA_CA_Axes},
74+
{"Manual_Axes", Manual_Axes},
75+
{"OnePass_KT_Axes", OnePass_KT_Axes},
76+
{"OnePass_CA_Axes", OnePass_CA_Axes},
77+
{"OnePass_AntiKT_Axes", OnePass_AntiKT_Axes},
78+
{"OnePass_WTA_KT_Axes", OnePass_WTA_KT_Axes},
79+
{"OnePass_WTA_CA_Axes", OnePass_WTA_CA_Axes},
80+
{"OnePass_Manual_Axes", OnePass_Manual_Axes},
81+
{"MultiPass_Axes", MultiPass_Axes}};
82+
} // namespace njettiness
83+
3184
typedef struct {
3285
PyObject_HEAD void *ptr;
3386
void *ty;
@@ -2315,6 +2368,93 @@ PYBIND11_MODULE(_ext, m) {
23152368
None.
23162369
Returns:
23172370
pt, eta, phi, m of inclusive jets.
2371+
)pbdoc")
2372+
.def("to_numpy_njettiness",
2373+
[](
2374+
const output_wrapper ow,
2375+
const std::string& measure_definition,
2376+
const std::string& axes_definition,
2377+
const std::vector<unsigned int>& njets,
2378+
const double beta,
2379+
const double R0,
2380+
const double Rcutoff,
2381+
const int nPass,
2382+
const double akAxesR0
2383+
) {
2384+
auto maybe_measdef = njettiness::measure_def_names_to_enum.find(measure_definition);
2385+
const auto measdefenum = maybe_measdef == njettiness::measure_def_names_to_enum.end() ? njettiness::NormalizedMeasure : maybe_measdef->second;
2386+
2387+
auto maybe_axesdef = njettiness::axis_def_names_to_enum.find(axes_definition);
2388+
const auto axesdefenum = maybe_axesdef == njettiness::axis_def_names_to_enum.end() ? njettiness::KT_Axes : maybe_axesdef->second;
2389+
2390+
// Get the measure definition
2391+
fastjet::contrib::NormalizedMeasure normalizedMeasure (beta, R0);
2392+
fastjet::contrib::UnnormalizedMeasure unnormalizedMeasure (beta);
2393+
fastjet::contrib::OriginalGeometricMeasure geometricMeasure (beta);
2394+
fastjet::contrib::NormalizedCutoffMeasure normalizedCutoffMeasure (beta, R0, Rcutoff);
2395+
fastjet::contrib::UnnormalizedCutoffMeasure unnormalizedCutoffMeasure(beta, Rcutoff);
2396+
2397+
fastjet::contrib::MeasureDefinition const * measureDef = 0;
2398+
switch ( measdefenum ) {
2399+
case njettiness::UnnormalizedMeasure : measureDef = &unnormalizedMeasure; break;
2400+
case njettiness::OriginalGeometricMeasure : measureDef = &geometricMeasure; break;
2401+
case njettiness::NormalizedCutoffMeasure : measureDef = &normalizedCutoffMeasure; break;
2402+
case njettiness::UnnormalizedCutoffMeasure : measureDef = &unnormalizedCutoffMeasure; break;
2403+
case njettiness::NormalizedMeasure : default : measureDef = &normalizedMeasure; break;
2404+
}
2405+
2406+
// Get the axes definition
2407+
fastjet::contrib::KT_Axes kt_axes;
2408+
fastjet::contrib::CA_Axes ca_axes;
2409+
fastjet::contrib::AntiKT_Axes antikt_axes (akAxesR0);
2410+
fastjet::contrib::WTA_KT_Axes wta_kt_axes;
2411+
fastjet::contrib::WTA_CA_Axes wta_ca_axes;
2412+
fastjet::contrib::OnePass_KT_Axes onepass_kt_axes;
2413+
fastjet::contrib::OnePass_CA_Axes onepass_ca_axes;
2414+
fastjet::contrib::OnePass_AntiKT_Axes onepass_antikt_axes(akAxesR0);
2415+
fastjet::contrib::OnePass_WTA_KT_Axes onepass_wta_kt_axes;
2416+
fastjet::contrib::OnePass_WTA_CA_Axes onepass_wta_ca_axes;
2417+
fastjet::contrib::MultiPass_Axes multipass_axes (nPass);
2418+
2419+
fastjet::contrib::AxesDefinition const * axesDef = 0;
2420+
switch ( axesdefenum ) {
2421+
case njettiness::KT_Axes : default : axesDef = &kt_axes; break;
2422+
case njettiness::CA_Axes : axesDef = &ca_axes; break;
2423+
case njettiness::AntiKT_Axes : axesDef = &antikt_axes; break;
2424+
case njettiness::WTA_KT_Axes : axesDef = &wta_kt_axes; break;
2425+
case njettiness::WTA_CA_Axes : axesDef = &wta_ca_axes; break;
2426+
case njettiness::OnePass_KT_Axes : axesDef = &onepass_kt_axes; break;
2427+
case njettiness::OnePass_CA_Axes : axesDef = &onepass_ca_axes; break;
2428+
case njettiness::OnePass_AntiKT_Axes : axesDef = &onepass_antikt_axes; break;
2429+
case njettiness::OnePass_WTA_KT_Axes : axesDef = &onepass_wta_kt_axes; break;
2430+
case njettiness::OnePass_WTA_CA_Axes : axesDef = &onepass_wta_ca_axes; break;
2431+
case njettiness::MultiPass_Axes : axesDef = &multipass_axes; break;
2432+
}
2433+
2434+
auto routine = std::make_shared<fastjet::contrib::Njettiness>(*axesDef, *measureDef);
2435+
2436+
const auto& constituents = ow.parts;
2437+
std::vector<double> taus;
2438+
2439+
for (size_t i = 0; i < constituents.size(); ++i) {
2440+
for(size_t k = 0; k < njets.size(); ++k) {
2441+
auto tau = routine->getTau(njets[k], *constituents[i]);
2442+
taus.push_back(tau);
2443+
}
2444+
}
2445+
2446+
auto taus_out = py::array(taus.size(), taus.data());
2447+
taus_out.resize({taus.size()/njets.size(), njets.size()});
2448+
2449+
return std::make_tuple(
2450+
taus_out
2451+
);
2452+
}, R"pbdoc(
2453+
Calculates njettiness values from inputs and converts them to numpy arrays.
2454+
Args:
2455+
None.
2456+
Returns:
2457+
the <njets>-tuple of njettiness values for all found jets, and their offsets
23182458
)pbdoc");
23192459
py::class_<ClusterSequence>(m, "ClusterSequence")
23202460
.def(py::init<const std::vector<PseudoJet> &, const JetDefinition &,

src/fastjet/_generalevent.py

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55

66
import fastjet._ext # noqa: F401, E402
77

8+
_default_taus_njettiness = [1, 2, 3, 4]
9+
810

911
class _classgeneralevent:
1012
def __init__(self, data, jetdef):
@@ -708,6 +710,54 @@ def exclusive_jets_softdrop_grooming(
708710
)
709711
return res
710712

713+
def njettiness(
714+
self,
715+
measure_definition="NormalizedMeasure",
716+
axes_definition="OnePass_KT_Axes",
717+
njets=_default_taus_njettiness,
718+
beta=1.0,
719+
R0=0.8,
720+
Rcutoff=None,
721+
nPass=None,
722+
akAxesR0=None,
723+
):
724+
if isinstance(njets, (int, float)):
725+
njets = [njets]
726+
if len(njets) == 0:
727+
raise ValueError("Must provide at least one njets!")
728+
if any(njet <= 0 for njet in njets):
729+
raise ValueError("Requested njets must be > 0!")
730+
731+
double_max = 999.0
732+
int_max = 999
733+
734+
self._out = []
735+
self._input_flag = 0
736+
for i in range(len(self._clusterable_level)):
737+
np_results = self._results[i].to_numpy_njettiness(
738+
measure_definition,
739+
axes_definition,
740+
njets,
741+
beta,
742+
R0,
743+
Rcutoff or double_max,
744+
nPass or int_max,
745+
akAxesR0 or double_max,
746+
)
747+
self._out.append(
748+
ak.Array(
749+
np_results[0],
750+
behavior=self.data.behavior,
751+
attrs=self.data.attrs,
752+
),
753+
)
754+
res = ak.Array(
755+
self._replace_multi(),
756+
behavior=self.data.behavior,
757+
attrs=self.data.attrs,
758+
)
759+
return res
760+
711761
def exclusive_jets_energy_correlator(
712762
self,
713763
njets=1,

src/fastjet/_multievent.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55

66
import fastjet._ext # noqa: F401, E402
77

8+
_default_taus_njettiness = [1, 2, 3, 4]
9+
810

911
class _classmultievent:
1012
def __init__(self, data, jetdef):
@@ -296,6 +298,42 @@ def exclusive_jets_softdrop_grooming(
296298
)
297299
return out
298300

301+
def njettiness(
302+
self,
303+
measure_definition="NormalizedMeasure",
304+
axes_definition="OnePass_KT_Axes",
305+
njets=_default_taus_njettiness,
306+
beta=1.0,
307+
R0=0.8,
308+
Rcutoff=None,
309+
nPass=None,
310+
akAxesR0=None,
311+
):
312+
if isinstance(njets, (int, float)):
313+
njets = [njets]
314+
if len(njets) == 0:
315+
raise ValueError("Must provide at least one njets!")
316+
if any(njet <= 0 for njet in njets):
317+
raise ValueError("Requested njets must be > 0!")
318+
319+
double_max = 999.0
320+
int_max = 999
321+
322+
np_results = self._results.to_numpy_njettiness(
323+
measure_definition,
324+
axes_definition,
325+
njets,
326+
beta,
327+
R0,
328+
Rcutoff or double_max,
329+
nPass or int_max,
330+
akAxesR0 or double_max,
331+
)
332+
out = ak.Array(
333+
ak.contents.NumpyArray(np_results[0]),
334+
)
335+
return out
336+
299337
def exclusive_jets_energy_correlator(
300338
self,
301339
njets=1,

src/fastjet/_pyjet.py

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@
1010
__all__ = ("__version__",)
1111

1212

13+
_default_taus_njettiness = [1, 2, 3, 4]
14+
15+
1316
class AwkwardClusterSequence(ClusterSequence):
1417
def __init__(self, data, jetdef):
1518
if not isinstance(data, ak.Array):
@@ -167,6 +170,28 @@ def exclusive_jets_softdrop_grooming(
167170
mu_cut,
168171
)
169172

173+
def njettiness(
174+
self,
175+
measure_definition="NormalizedMeasure",
176+
axes_definition="OnePass_KT_Axes",
177+
njets=_default_taus_njettiness,
178+
beta=1.0,
179+
R0=0.8,
180+
Rcutoff=None,
181+
nPass=None,
182+
akAxesR0=None,
183+
):
184+
return self._internalrep.njettiness(
185+
measure_definition=measure_definition,
186+
axes_definition=axes_definition,
187+
njets=njets,
188+
beta=beta,
189+
R0=R0,
190+
Rcutoff=Rcutoff,
191+
nPass=nPass,
192+
akAxesR0=akAxesR0,
193+
)
194+
170195
def exclusive_jets_energy_correlator(
171196
self,
172197
njets=1,
@@ -492,6 +517,30 @@ def exclusive_jets_softdrop_grooming(
492517
mu_cut=mu_cut,
493518
)
494519

520+
def njettiness(
521+
self,
522+
measure_definition="NormalizedMeasure",
523+
axes_definition="OnePass_KT_Axes",
524+
njets=_default_taus_njettiness,
525+
beta=1.0,
526+
R0=0.8,
527+
Rcutoff=None,
528+
nPass=None,
529+
akAxesR0=None,
530+
):
531+
return _dak_dispatch(
532+
self,
533+
"njettiness",
534+
measure_definition=measure_definition,
535+
axes_definition=axes_definition,
536+
njets=njets,
537+
beta=beta,
538+
R0=R0,
539+
Rcutoff=Rcutoff,
540+
nPass=nPass,
541+
akAxesR0=akAxesR0,
542+
)
543+
495544
def exclusive_jets_energy_correlator(
496545
self,
497546
njets=1,

src/fastjet/_singleevent.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55

66
import fastjet._ext # noqa: F401, E402
77

8+
_default_taus_njettiness = [1, 2, 3, 4]
9+
810

911
class _classsingleevent:
1012
def __init__(self, data, jetdef):
@@ -272,6 +274,42 @@ def exclusive_jets_softdrop_grooming(
272274
)
273275
return out[0]
274276

277+
def njettiness(
278+
self,
279+
measure_definition="NormalizedMeasure",
280+
axes_definition="OnePass_KT_Axes",
281+
njets=_default_taus_njettiness,
282+
beta=1.0,
283+
R0=0.8,
284+
Rcutoff=None,
285+
nPass=None,
286+
akAxesR0=None,
287+
):
288+
if isinstance(njets, (int, float)):
289+
njets = [njets]
290+
if len(njets) == 0:
291+
raise ValueError("Must provide at least one njets!")
292+
if any(njet <= 0 for njet in njets):
293+
raise ValueError("Requested njets must be > 0!")
294+
295+
double_max = 999.0
296+
int_max = 999
297+
298+
np_results = self._results.to_numpy_njettiness(
299+
measure_definition,
300+
axes_definition,
301+
njets,
302+
beta,
303+
R0,
304+
Rcutoff or double_max,
305+
nPass or int_max,
306+
akAxesR0 or double_max,
307+
)
308+
out = ak.Array(
309+
ak.contents.NumpyArray(np_results[0]),
310+
)
311+
return out
312+
275313
def exclusive_jets_energy_correlator(
276314
self,
277315
njets=1,

0 commit comments

Comments
 (0)