jetgo 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- jetgo-0.1.0/.gitignore +13 -0
- jetgo-0.1.0/LICENSE +29 -0
- jetgo-0.1.0/PKG-INFO +153 -0
- jetgo-0.1.0/README.md +116 -0
- jetgo-0.1.0/examples/README.md +16 -0
- jetgo-0.1.0/examples/ex01_minimal_eec.py +63 -0
- jetgo-0.1.0/examples/ex02_flavor_tagged_run.py +69 -0
- jetgo-0.1.0/examples/ex03_custom_observable.py +102 -0
- jetgo-0.1.0/jetgo/__init__.py +20 -0
- jetgo-0.1.0/jetgo/_pythia.py +63 -0
- jetgo-0.1.0/jetgo/kinematics.py +133 -0
- jetgo-0.1.0/jetgo/observables/__init__.py +12 -0
- jetgo-0.1.0/jetgo/observables/base.py +156 -0
- jetgo-0.1.0/jetgo/observables/classes.py +18 -0
- jetgo-0.1.0/jetgo/observables/double_differential_jet_cross_section.py +232 -0
- jetgo-0.1.0/jetgo/observables/eec.py +310 -0
- jetgo-0.1.0/jetgo/observables/identifier.py +22 -0
- jetgo-0.1.0/jetgo/simulation/__init__.py +14 -0
- jetgo-0.1.0/jetgo/simulation/event_generator.py +123 -0
- jetgo-0.1.0/jetgo/simulation/jet_finder.py +110 -0
- jetgo-0.1.0/jetgo/simulation/particle_selector.py +115 -0
- jetgo-0.1.0/jetgo/simulation/simulation_summary.py +103 -0
- jetgo-0.1.0/jetgo/simulation/simulator.py +377 -0
- jetgo-0.1.0/jetgo/taggers/__init__.py +11 -0
- jetgo-0.1.0/jetgo/taggers/base.py +114 -0
- jetgo-0.1.0/jetgo/taggers/classes.py +17 -0
- jetgo-0.1.0/jetgo/taggers/descendancy_tracing.py +593 -0
- jetgo-0.1.0/jetgo/taggers/flavor.py +18 -0
- jetgo-0.1.0/jetgo/taggers/identifier.py +21 -0
- jetgo-0.1.0/pyproject.toml +76 -0
- jetgo-0.1.0/tests/conftest.py +137 -0
- jetgo-0.1.0/tests/data/generate_golden_run.py +25 -0
- jetgo-0.1.0/tests/data/golden_run.json +399 -0
- jetgo-0.1.0/tests/test_jet_finder.py +93 -0
- jetgo-0.1.0/tests/test_kinematics.py +100 -0
- jetgo-0.1.0/tests/test_observables.py +217 -0
- jetgo-0.1.0/tests/test_pythia_integration.py +166 -0
- jetgo-0.1.0/tests/test_simulator.py +209 -0
jetgo-0.1.0/.gitignore
ADDED
jetgo-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
BSD 3-Clause License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026, Maxence Larose
|
|
4
|
+
All rights reserved.
|
|
5
|
+
|
|
6
|
+
Redistribution and use in source and binary forms, with or without
|
|
7
|
+
modification, are permitted provided that the following conditions are met:
|
|
8
|
+
|
|
9
|
+
1. Redistributions of source code must retain the above copyright notice, this
|
|
10
|
+
list of conditions and the following disclaimer.
|
|
11
|
+
|
|
12
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
13
|
+
this list of conditions and the following disclaimer in the documentation
|
|
14
|
+
and/or other materials provided with the distribution.
|
|
15
|
+
|
|
16
|
+
3. Neither the name of the copyright holder nor the names of its
|
|
17
|
+
contributors may be used to endorse or promote products derived from
|
|
18
|
+
this software without specific prior written permission.
|
|
19
|
+
|
|
20
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
21
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
22
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
23
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
24
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
25
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
26
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
27
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
28
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
29
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
jetgo-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: jetgo
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Jet Event Toolkit for Generating Observables: generate collider events, find jets, tag their generator-level flavor, and save the observable data.
|
|
5
|
+
Project-URL: Homepage, https://github.com/MaxenceLarose/jetgo
|
|
6
|
+
Project-URL: Documentation, https://maxencelarose.github.io/jetgo
|
|
7
|
+
Project-URL: Repository, https://github.com/MaxenceLarose/jetgo
|
|
8
|
+
Project-URL: Issues, https://github.com/MaxenceLarose/jetgo/issues
|
|
9
|
+
Author-email: Maxence Larose <maxence.larose@stonybrook.edu>
|
|
10
|
+
Maintainer-email: Maxence Larose <maxence.larose@stonybrook.edu>
|
|
11
|
+
License-Expression: BSD-3-Clause
|
|
12
|
+
License-File: LICENSE
|
|
13
|
+
Keywords: energy-energy-correlator,fastjet,high-energy-physics,jet-flavor,jets,monte-carlo,pythia8,qcd
|
|
14
|
+
Classifier: Development Status :: 4 - Beta
|
|
15
|
+
Classifier: Intended Audience :: Science/Research
|
|
16
|
+
Classifier: License :: OSI Approved :: BSD License
|
|
17
|
+
Classifier: Operating System :: MacOS
|
|
18
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
19
|
+
Classifier: Programming Language :: Python :: 3
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
23
|
+
Classifier: Topic :: Scientific/Engineering :: Physics
|
|
24
|
+
Requires-Python: >=3.11
|
|
25
|
+
Requires-Dist: fastjet>=3.4
|
|
26
|
+
Requires-Dist: numpy>=1.24
|
|
27
|
+
Provides-Extra: dev
|
|
28
|
+
Requires-Dist: pytest-cov>=4.1; extra == 'dev'
|
|
29
|
+
Requires-Dist: pytest>=7.4; extra == 'dev'
|
|
30
|
+
Provides-Extra: docs
|
|
31
|
+
Requires-Dist: numpydoc>=1.6; extra == 'docs'
|
|
32
|
+
Requires-Dist: pydata-sphinx-theme>=0.15; extra == 'docs'
|
|
33
|
+
Requires-Dist: sphinx>=7.2; extra == 'docs'
|
|
34
|
+
Provides-Extra: pythia
|
|
35
|
+
Requires-Dist: pythia8mc>=8.3; extra == 'pythia'
|
|
36
|
+
Description-Content-Type: text/markdown
|
|
37
|
+
|
|
38
|
+
<p align="center">
|
|
39
|
+
<img src="https://raw.githubusercontent.com/MaxenceLarose/jetgo/main/images/jetgo_banner.png" alt="jetgo" width="820">
|
|
40
|
+
</p>
|
|
41
|
+
|
|
42
|
+
<p align="center">
|
|
43
|
+
<i>The simplest way to generate collider events and measure their jet observables.</i>
|
|
44
|
+
</p>
|
|
45
|
+
|
|
46
|
+
<p align="center">
|
|
47
|
+
<img src="https://img.shields.io/badge/python-3.11+-blue.svg?logo=python&logoColor=white" alt="Python 3.11+">
|
|
48
|
+
<img src="https://img.shields.io/badge/PYTHIA-8.3+-orange.svg" alt="PYTHIA 8.3+">
|
|
49
|
+
<img src="https://img.shields.io/badge/FastJet-3.5+-9cf.svg" alt="FastJet 3.5+">
|
|
50
|
+
<a href="https://github.com/MaxenceLarose/jetgo/blob/main/LICENSE"><img src="https://img.shields.io/badge/license-BSD--3--Clause-green.svg" alt="BSD-3-Clause license"></a>
|
|
51
|
+
<a href="https://pypi.org/project/jetgo/"><img src="https://img.shields.io/pypi/dm/jetgo?label=downloads&color=blue" alt="Downloads"></a>
|
|
52
|
+
</p>
|
|
53
|
+
|
|
54
|
+
`jetgo` generates collider events, clusters the particles into jets, tags each jet with the color
|
|
55
|
+
charge of the parton that initiated it, and writes the observables out.
|
|
56
|
+
|
|
57
|
+
You define the simulator, the observables and the tagger. `jetgo` does the rest.
|
|
58
|
+
|
|
59
|
+
# Installation
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
pip install jetgo[pythia]
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
PYTHIA8 is an optional dependency, because its wheels are Linux-only. Plain `pip install jetgo`
|
|
66
|
+
works everywhere and gives you every observable and tagger; only event generation needs the extra.
|
|
67
|
+
|
|
68
|
+
# Quick usage preview
|
|
69
|
+
|
|
70
|
+
```python
|
|
71
|
+
import numpy as np
|
|
72
|
+
|
|
73
|
+
from jetgo.observables import EEC
|
|
74
|
+
from jetgo.simulation import EventGenerator, JetFinder, ParticleSelector, Simulator
|
|
75
|
+
from jetgo.taggers import DescendancyTracing
|
|
76
|
+
|
|
77
|
+
event_generator = EventGenerator(
|
|
78
|
+
beam_1=2212,
|
|
79
|
+
beam_2=2212,
|
|
80
|
+
sqrt_s=5020,
|
|
81
|
+
pt_min=80,
|
|
82
|
+
pt_max=200,
|
|
83
|
+
random_seed=42
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
particle_selector = ParticleSelector(
|
|
87
|
+
max_abs_pseudorapidity=2.4
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
jet_finder = JetFinder(
|
|
91
|
+
radius=0.4,
|
|
92
|
+
pt_min=120,
|
|
93
|
+
pt_max=140,
|
|
94
|
+
max_abs_pseudorapidity=1.6
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
observable = EEC(
|
|
98
|
+
bin_edges=np.geomspace(0.005, 0.8, 31),
|
|
99
|
+
charged_only=True,
|
|
100
|
+
use_pseudorapidity=True
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
jet_flavor_tagger = DescendancyTracing(
|
|
104
|
+
delta_r_max=0.4,
|
|
105
|
+
use_pseudorapidity=True
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
simulator = Simulator(
|
|
109
|
+
event_generator=event_generator,
|
|
110
|
+
particle_selector=particle_selector,
|
|
111
|
+
jet_finder=jet_finder
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
simulator.simulate(
|
|
115
|
+
n_events=100_000,
|
|
116
|
+
observables=observable,
|
|
117
|
+
jet_flavor_taggers=jet_flavor_tagger,
|
|
118
|
+
output_path="jet_eec.json"
|
|
119
|
+
)
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
That run writes one file holding the inclusive energy-energy correlator, its quark-tagged and
|
|
123
|
+
gluon-tagged counterparts, and the run metadata.
|
|
124
|
+
|
|
125
|
+
# Documentation
|
|
126
|
+
|
|
127
|
+
Full documentation lives at [maxencelarose.github.io/jetgo](https://maxencelarose.github.io/jetgo),
|
|
128
|
+
including how to write your own observable or your own flavor tagger. Runnable scripts are in
|
|
129
|
+
[`examples/`](examples).
|
|
130
|
+
|
|
131
|
+
# License
|
|
132
|
+
|
|
133
|
+
This project is licensed under the terms of the [BSD 3-Clause License](LICENSE).
|
|
134
|
+
|
|
135
|
+
# Citation
|
|
136
|
+
|
|
137
|
+
If you use `jetgo` in your work, please cite the paper it was written for:
|
|
138
|
+
|
|
139
|
+
```bibtex
|
|
140
|
+
@article{Bossi:2026colors,
|
|
141
|
+
author = {Bossi, Hannah and Larose, Maxence and Mehtar-Tani, Yacine},
|
|
142
|
+
title = {The Colors of Jet Quenching},
|
|
143
|
+
year = {2026},
|
|
144
|
+
eprint = {2609.05609},
|
|
145
|
+
archivePrefix = {arXiv},
|
|
146
|
+
primaryClass = {hep-ph},
|
|
147
|
+
url = {https://arxiv.org/abs/2609.05609}
|
|
148
|
+
}
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
# Contact
|
|
152
|
+
|
|
153
|
+
Maxence Larose — [maxence.larose@stonybrook.edu](mailto:maxence.larose@stonybrook.edu)
|
jetgo-0.1.0/README.md
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="https://raw.githubusercontent.com/MaxenceLarose/jetgo/main/images/jetgo_banner.png" alt="jetgo" width="820">
|
|
3
|
+
</p>
|
|
4
|
+
|
|
5
|
+
<p align="center">
|
|
6
|
+
<i>The simplest way to generate collider events and measure their jet observables.</i>
|
|
7
|
+
</p>
|
|
8
|
+
|
|
9
|
+
<p align="center">
|
|
10
|
+
<img src="https://img.shields.io/badge/python-3.11+-blue.svg?logo=python&logoColor=white" alt="Python 3.11+">
|
|
11
|
+
<img src="https://img.shields.io/badge/PYTHIA-8.3+-orange.svg" alt="PYTHIA 8.3+">
|
|
12
|
+
<img src="https://img.shields.io/badge/FastJet-3.5+-9cf.svg" alt="FastJet 3.5+">
|
|
13
|
+
<a href="https://github.com/MaxenceLarose/jetgo/blob/main/LICENSE"><img src="https://img.shields.io/badge/license-BSD--3--Clause-green.svg" alt="BSD-3-Clause license"></a>
|
|
14
|
+
<a href="https://pypi.org/project/jetgo/"><img src="https://img.shields.io/pypi/dm/jetgo?label=downloads&color=blue" alt="Downloads"></a>
|
|
15
|
+
</p>
|
|
16
|
+
|
|
17
|
+
`jetgo` generates collider events, clusters the particles into jets, tags each jet with the color
|
|
18
|
+
charge of the parton that initiated it, and writes the observables out.
|
|
19
|
+
|
|
20
|
+
You define the simulator, the observables and the tagger. `jetgo` does the rest.
|
|
21
|
+
|
|
22
|
+
# Installation
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
pip install jetgo[pythia]
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
PYTHIA8 is an optional dependency, because its wheels are Linux-only. Plain `pip install jetgo`
|
|
29
|
+
works everywhere and gives you every observable and tagger; only event generation needs the extra.
|
|
30
|
+
|
|
31
|
+
# Quick usage preview
|
|
32
|
+
|
|
33
|
+
```python
|
|
34
|
+
import numpy as np
|
|
35
|
+
|
|
36
|
+
from jetgo.observables import EEC
|
|
37
|
+
from jetgo.simulation import EventGenerator, JetFinder, ParticleSelector, Simulator
|
|
38
|
+
from jetgo.taggers import DescendancyTracing
|
|
39
|
+
|
|
40
|
+
event_generator = EventGenerator(
|
|
41
|
+
beam_1=2212,
|
|
42
|
+
beam_2=2212,
|
|
43
|
+
sqrt_s=5020,
|
|
44
|
+
pt_min=80,
|
|
45
|
+
pt_max=200,
|
|
46
|
+
random_seed=42
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
particle_selector = ParticleSelector(
|
|
50
|
+
max_abs_pseudorapidity=2.4
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
jet_finder = JetFinder(
|
|
54
|
+
radius=0.4,
|
|
55
|
+
pt_min=120,
|
|
56
|
+
pt_max=140,
|
|
57
|
+
max_abs_pseudorapidity=1.6
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
observable = EEC(
|
|
61
|
+
bin_edges=np.geomspace(0.005, 0.8, 31),
|
|
62
|
+
charged_only=True,
|
|
63
|
+
use_pseudorapidity=True
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
jet_flavor_tagger = DescendancyTracing(
|
|
67
|
+
delta_r_max=0.4,
|
|
68
|
+
use_pseudorapidity=True
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
simulator = Simulator(
|
|
72
|
+
event_generator=event_generator,
|
|
73
|
+
particle_selector=particle_selector,
|
|
74
|
+
jet_finder=jet_finder
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
simulator.simulate(
|
|
78
|
+
n_events=100_000,
|
|
79
|
+
observables=observable,
|
|
80
|
+
jet_flavor_taggers=jet_flavor_tagger,
|
|
81
|
+
output_path="jet_eec.json"
|
|
82
|
+
)
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
That run writes one file holding the inclusive energy-energy correlator, its quark-tagged and
|
|
86
|
+
gluon-tagged counterparts, and the run metadata.
|
|
87
|
+
|
|
88
|
+
# Documentation
|
|
89
|
+
|
|
90
|
+
Full documentation lives at [maxencelarose.github.io/jetgo](https://maxencelarose.github.io/jetgo),
|
|
91
|
+
including how to write your own observable or your own flavor tagger. Runnable scripts are in
|
|
92
|
+
[`examples/`](examples).
|
|
93
|
+
|
|
94
|
+
# License
|
|
95
|
+
|
|
96
|
+
This project is licensed under the terms of the [BSD 3-Clause License](LICENSE).
|
|
97
|
+
|
|
98
|
+
# Citation
|
|
99
|
+
|
|
100
|
+
If you use `jetgo` in your work, please cite the paper it was written for:
|
|
101
|
+
|
|
102
|
+
```bibtex
|
|
103
|
+
@article{Bossi:2026colors,
|
|
104
|
+
author = {Bossi, Hannah and Larose, Maxence and Mehtar-Tani, Yacine},
|
|
105
|
+
title = {The Colors of Jet Quenching},
|
|
106
|
+
year = {2026},
|
|
107
|
+
eprint = {2609.05609},
|
|
108
|
+
archivePrefix = {arXiv},
|
|
109
|
+
primaryClass = {hep-ph},
|
|
110
|
+
url = {https://arxiv.org/abs/2609.05609}
|
|
111
|
+
}
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
# Contact
|
|
115
|
+
|
|
116
|
+
Maxence Larose — [maxence.larose@stonybrook.edu](mailto:maxence.larose@stonybrook.edu)
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# Examples
|
|
2
|
+
|
|
3
|
+
Each script is standalone and runnable. Install the package with the Pythia8 extra first:
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
pip install jetgo[pythia]
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
| script | what it shows |
|
|
10
|
+
|---|---|
|
|
11
|
+
| `ex01_minimal_eec.py` | the smallest useful run: generate events, cluster jets, fill one observable, save |
|
|
12
|
+
| `ex02_flavor_tagged_run.py` | the same run split into quark-tagged and gluon-tagged jets, and how to read the result back |
|
|
13
|
+
| `ex03_custom_observable.py` | writing your own `Observable` subclass and running it through the simulator |
|
|
14
|
+
|
|
15
|
+
They are deliberately small, a few thousand events each, so they finish in under a minute. Real
|
|
16
|
+
runs are the same code with a larger `n_events`.
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"""
|
|
2
|
+
@Title: Minimal energy-energy correlator run.
|
|
3
|
+
|
|
4
|
+
@Description: The smallest useful jetgo run. Generate hard-QCD proton-proton events, keep the
|
|
5
|
+
charged final-state particles inside a tracker-like acceptance, cluster them into
|
|
6
|
+
anti-kT jets, accumulate the energy-energy correlator, and write everything to one
|
|
7
|
+
JSON file.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import numpy as np
|
|
11
|
+
|
|
12
|
+
from jetgo.observables import EEC
|
|
13
|
+
from jetgo.simulation import EventGenerator, JetFinder, ParticleSelector, Simulator
|
|
14
|
+
|
|
15
|
+
if __name__ == "__main__":
|
|
16
|
+
# ---------------------------------------------------------------------------------------------------- #
|
|
17
|
+
# The three pieces #
|
|
18
|
+
# ---------------------------------------------------------------------------------------------------- #
|
|
19
|
+
event_generator = EventGenerator(
|
|
20
|
+
beam_1=2212, # proton
|
|
21
|
+
beam_2=2212, # proton
|
|
22
|
+
sqrt_s=5020, # GeV
|
|
23
|
+
pt_min=80, # partonic p̂T window, in GeV
|
|
24
|
+
pt_max=200,
|
|
25
|
+
random_seed=42, # anything non-zero: 0 lets Pythia8 pick its own seed, and the run stops
|
|
26
|
+
) # being reproducible
|
|
27
|
+
|
|
28
|
+
jet_finder = JetFinder(
|
|
29
|
+
radius=0.4,
|
|
30
|
+
pt_min=120,
|
|
31
|
+
pt_max=140,
|
|
32
|
+
max_abs_pseudorapidity=1.6,
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
particle_selector = ParticleSelector(
|
|
36
|
+
max_abs_pseudorapidity=2.4,
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
# ---------------------------------------------------------------------------------------------------- #
|
|
40
|
+
# The run #
|
|
41
|
+
# ---------------------------------------------------------------------------------------------------- #
|
|
42
|
+
simulator = Simulator(
|
|
43
|
+
event_generator=event_generator,
|
|
44
|
+
jet_finder=jet_finder,
|
|
45
|
+
particle_selector=particle_selector,
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
# The bin edges are required. Observables histogram at fill time, so the file size does not grow with
|
|
49
|
+
# the run length, and the edges have to be the ones the result will be read on.
|
|
50
|
+
observable = EEC(
|
|
51
|
+
bin_edges=np.geomspace(0.005, 0.8, 31),
|
|
52
|
+
pt_min=1,
|
|
53
|
+
energy_weight=1,
|
|
54
|
+
charged_only=True,
|
|
55
|
+
use_pseudorapidity=True,
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
simulator.simulate(
|
|
59
|
+
n_events=5_000,
|
|
60
|
+
observables=observable,
|
|
61
|
+
output_path="jet_eec.json",
|
|
62
|
+
verbose=True,
|
|
63
|
+
)
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"""
|
|
2
|
+
@Title: Splitting a run by generator-level jet flavor.
|
|
3
|
+
|
|
4
|
+
@Description: The same run as ex01, with a flavor tagger attached. Every observable is filled three
|
|
5
|
+
times over: once with all jets, once with the jets the tagger calls quark-initiated,
|
|
6
|
+
and once with the ones it calls gluon-initiated. The second half of the script reads
|
|
7
|
+
the file back and turns the saved histograms into normalized observables.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import json
|
|
11
|
+
|
|
12
|
+
import numpy as np
|
|
13
|
+
|
|
14
|
+
from jetgo.observables import DoubleDifferentialJetCrossSection, EEC
|
|
15
|
+
from jetgo.simulation import EventGenerator, JetFinder, ParticleSelector, Simulator
|
|
16
|
+
from jetgo.taggers import DescendancyTracing
|
|
17
|
+
|
|
18
|
+
OUTPUT_PATH = "flavor_tagged_run.json"
|
|
19
|
+
EEC_BIN_EDGES = np.geomspace(0.005, 0.8, 31)
|
|
20
|
+
CROSS_SECTION_BIN_EDGES = np.linspace(120.0, 140.0, 21)
|
|
21
|
+
|
|
22
|
+
if __name__ == "__main__":
|
|
23
|
+
# ---------------------------------------------------------------------------------------------------- #
|
|
24
|
+
# The run #
|
|
25
|
+
# ---------------------------------------------------------------------------------------------------- #
|
|
26
|
+
simulator = Simulator(
|
|
27
|
+
event_generator=EventGenerator(
|
|
28
|
+
beam_1=2212, beam_2=2212, sqrt_s=5020, pt_min=80, pt_max=200, random_seed=42
|
|
29
|
+
),
|
|
30
|
+
jet_finder=JetFinder(radius=0.4, pt_min=120, pt_max=140, max_abs_pseudorapidity=1.6),
|
|
31
|
+
particle_selector=ParticleSelector(max_abs_pseudorapidity=2.4),
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
simulator.simulate(
|
|
35
|
+
n_events=5_000,
|
|
36
|
+
observables=[
|
|
37
|
+
EEC(bin_edges=EEC_BIN_EDGES, pt_min=1, charged_only=True, use_pseudorapidity=True),
|
|
38
|
+
DoubleDifferentialJetCrossSection(
|
|
39
|
+
bin_edges=CROSS_SECTION_BIN_EDGES, max_abs_pseudorapidity=1.6
|
|
40
|
+
),
|
|
41
|
+
],
|
|
42
|
+
# Pass a list here to run several taggers at once. The output metadata then also reports, for every
|
|
43
|
+
# pair, how often they agreed on the jets both of them tagged.
|
|
44
|
+
jet_flavor_taggers=DescendancyTracing(delta_r_max=0.4, use_pseudorapidity=True),
|
|
45
|
+
output_path=OUTPUT_PATH,
|
|
46
|
+
verbose=True,
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
# ---------------------------------------------------------------------------------------------------- #
|
|
50
|
+
# Reading it back #
|
|
51
|
+
# ---------------------------------------------------------------------------------------------------- #
|
|
52
|
+
result = json.loads(open(OUTPUT_PATH).read())
|
|
53
|
+
|
|
54
|
+
counts = result["metadata"]["flavor_taggers"]["descendancy_tracing"]
|
|
55
|
+
print(f"quark-tagged jets: {counts['n_quark']}")
|
|
56
|
+
print(f"gluon-tagged jets: {counts['n_gluon']}")
|
|
57
|
+
print(f"untagged jets: {counts['n_untagged']}")
|
|
58
|
+
|
|
59
|
+
# finalize turns a saved histogram into the normalized observable. It is a classmethod, and it runs here,
|
|
60
|
+
# never during the simulation itself.
|
|
61
|
+
histograms = result["Energy-energy correlator"]["histograms"]
|
|
62
|
+
for label, saved in (
|
|
63
|
+
("inclusive", histograms["inclusive"]),
|
|
64
|
+
("quark", histograms["descendancy_tracing"]["quark"]),
|
|
65
|
+
("gluon", histograms["descendancy_tracing"]["gluon"]),
|
|
66
|
+
):
|
|
67
|
+
shape = EEC.finalize(saved["values"], EEC_BIN_EDGES, saved["metadata"])
|
|
68
|
+
peak = EEC_BIN_EDGES[:-1][int(np.argmax(shape))]
|
|
69
|
+
print(f"{label:>10} EEC peaks near ΔR = {peak:.3f}")
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
"""
|
|
2
|
+
@Title: Writing your own observable.
|
|
3
|
+
|
|
4
|
+
@Description: Observables are the main extension point of jetgo. This example implements the jet
|
|
5
|
+
constituent multiplicity, a quantity that separates quark and gluon jets, and runs it
|
|
6
|
+
through the simulator exactly like a shipped observable.
|
|
7
|
+
|
|
8
|
+
The contract is six methods. ``fill`` receives one event's jets and adds them to a
|
|
9
|
+
histogram whose edges were fixed at construction; ``finalize`` is the classmethod that
|
|
10
|
+
turns the saved histogram into the normalized quantity, downstream.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
import json
|
|
14
|
+
from typing import Dict, List
|
|
15
|
+
|
|
16
|
+
import fastjet as fj
|
|
17
|
+
import numpy as np
|
|
18
|
+
|
|
19
|
+
from jetgo.observables.base import Observable
|
|
20
|
+
from jetgo.simulation import EventGenerator, JetFinder, ParticleSelector, Simulator
|
|
21
|
+
from jetgo.taggers import DescendancyTracing
|
|
22
|
+
|
|
23
|
+
BIN_EDGES = np.arange(0.5, 60.5, 2.0)
|
|
24
|
+
OUTPUT_PATH = "constituent_multiplicity.json"
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class ConstituentMultiplicity(Observable):
|
|
28
|
+
"""
|
|
29
|
+
Distribution of the number of constituents per jet, normalized to unit area.
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
N_JETS_KEY = "n_jets"
|
|
33
|
+
BIN_EDGES_KEY = "bin_edges"
|
|
34
|
+
|
|
35
|
+
def __init__(self, bin_edges: np.ndarray, pt_min: float = 1.0) -> None:
|
|
36
|
+
self._bin_edges = np.asarray(bin_edges, dtype=float)
|
|
37
|
+
self._counts = np.zeros(len(self._bin_edges) - 1, dtype=float)
|
|
38
|
+
self._pt_min = pt_min
|
|
39
|
+
self._n_jets = 0
|
|
40
|
+
|
|
41
|
+
@property
|
|
42
|
+
def identifier(self) -> str:
|
|
43
|
+
# Shipped observables use an ObservableIdentifier member, whose value is the HEPData keyword. Any
|
|
44
|
+
# hashable, unique value works: the simulator only uses it as this observable's key in the output.
|
|
45
|
+
return "constituent_multiplicity"
|
|
46
|
+
|
|
47
|
+
def fill(self, jets: List[fj.PseudoJet], cluster: fj.ClusterSequence) -> None:
|
|
48
|
+
for jet in jets:
|
|
49
|
+
self._n_jets += 1
|
|
50
|
+
multiplicity = sum(1 for c in jet.constituents() if c.pt() > self._pt_min)
|
|
51
|
+
|
|
52
|
+
index = int(np.searchsorted(self._bin_edges, multiplicity, side="right")) - 1
|
|
53
|
+
if 0 <= index < len(self._counts):
|
|
54
|
+
self._counts[index] += 1.0
|
|
55
|
+
|
|
56
|
+
def scale(self, scale_factor: float) -> None:
|
|
57
|
+
# Self-normalized, like the EEC: the generator's cross-section factor cancels in the shape.
|
|
58
|
+
pass
|
|
59
|
+
|
|
60
|
+
@classmethod
|
|
61
|
+
def finalize(cls, values, bin_edges: np.ndarray, metadata: Dict) -> np.ndarray:
|
|
62
|
+
counts = np.asarray(values, dtype=float)
|
|
63
|
+
return counts / counts.sum() / np.diff(np.asarray(bin_edges, dtype=float))
|
|
64
|
+
|
|
65
|
+
def _get_complementary_metadata(self) -> Dict:
|
|
66
|
+
return {self.N_JETS_KEY: self._n_jets, self.BIN_EDGES_KEY: self._bin_edges.tolist()}
|
|
67
|
+
|
|
68
|
+
def _get_binned_values(self) -> List[float]:
|
|
69
|
+
return self._counts.tolist()
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
if __name__ == "__main__":
|
|
73
|
+
simulator = Simulator(
|
|
74
|
+
event_generator=EventGenerator(
|
|
75
|
+
beam_1=2212, beam_2=2212, sqrt_s=5020, pt_min=80, pt_max=200, random_seed=42
|
|
76
|
+
),
|
|
77
|
+
jet_finder=JetFinder(radius=0.4, pt_min=120, pt_max=140, max_abs_pseudorapidity=1.6),
|
|
78
|
+
particle_selector=ParticleSelector(max_abs_pseudorapidity=2.4),
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
simulator.simulate(
|
|
82
|
+
n_events=5_000,
|
|
83
|
+
observables=ConstituentMultiplicity(bin_edges=BIN_EDGES),
|
|
84
|
+
jet_flavor_taggers=DescendancyTracing(delta_r_max=0.4, use_pseudorapidity=True),
|
|
85
|
+
output_path=OUTPUT_PATH,
|
|
86
|
+
verbose=True,
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
histograms = json.loads(open(OUTPUT_PATH).read())["constituent_multiplicity"]["histograms"]
|
|
90
|
+
centers = 0.5 * (BIN_EDGES[:-1] + BIN_EDGES[1:])
|
|
91
|
+
|
|
92
|
+
for label, saved in (
|
|
93
|
+
("quark", histograms["descendancy_tracing"]["quark"]),
|
|
94
|
+
("gluon", histograms["descendancy_tracing"]["gluon"]),
|
|
95
|
+
):
|
|
96
|
+
shape = ConstituentMultiplicity.finalize(saved["values"], BIN_EDGES, saved["metadata"])
|
|
97
|
+
mean = float(np.sum(centers * shape * np.diff(BIN_EDGES)))
|
|
98
|
+
print(f"{label:>6} jets: mean constituent multiplicity = {mean:.1f}")
|
|
99
|
+
|
|
100
|
+
print("Gluon jets carry more constituents than quark jets: they radiate more, in the ratio of the")
|
|
101
|
+
print("color charges C_A/C_F. The measured ratio is well below that naive 9/4, since hadronization")
|
|
102
|
+
print("and the finite jet energy both dilute it.")
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"""
|
|
2
|
+
@file: __init__.py
|
|
3
|
+
@Author: Maxence Larose
|
|
4
|
+
|
|
5
|
+
@Creation Date: 09/2026
|
|
6
|
+
@Last modification: 09/2026
|
|
7
|
+
|
|
8
|
+
@Description: This file defines package-level metadata (version, author, license, maintainer) for the
|
|
9
|
+
``jetgo`` package. It deliberately exposes no classes: the public API lives in the
|
|
10
|
+
subpackages, so that importing ``jetgo`` never pulls in Pythia8 or FastJet.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
__version__ = "0.1.0"
|
|
14
|
+
|
|
15
|
+
__author__ = "Maxence Larose"
|
|
16
|
+
__credits__ = ["Maxence Larose"]
|
|
17
|
+
__license__ = "BSD-3-Clause"
|
|
18
|
+
__maintainer__ = "Maxence Larose"
|
|
19
|
+
__email__ = "maxence.larose@stonybrook.edu"
|
|
20
|
+
__status__ = "Production"
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"""
|
|
2
|
+
@file: _pythia.py
|
|
3
|
+
@Author: Maxence Larose
|
|
4
|
+
|
|
5
|
+
@Creation Date: 06/2026
|
|
6
|
+
@Last modification: 09/2026
|
|
7
|
+
|
|
8
|
+
@Description: This file resolves and re-exports the Pythia8 module, accounting for the different module
|
|
9
|
+
name used by the pip-installed ``pythia8mc`` package versus a self-compiled ``pythia8``
|
|
10
|
+
build.
|
|
11
|
+
|
|
12
|
+
Pythia8 is an optional dependency of this package: only event generation and particle
|
|
13
|
+
selection need it at runtime, so observables and taggers import without it. Importing
|
|
14
|
+
this module on a machine without either build raises with an actionable message rather
|
|
15
|
+
than a bare ModuleNotFoundError from somewhere deep in the import graph.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
import importlib
|
|
19
|
+
|
|
20
|
+
# The module is named pythia8 when users compile it themselves, and pythia8mc when installed via pip.
|
|
21
|
+
_CANDIDATE_MODULE_NAMES = ("pythia8mc", "pythia8")
|
|
22
|
+
|
|
23
|
+
_NOT_INSTALLED_MESSAGE = (
|
|
24
|
+
"Pythia8 is required for event generation but is not installed. Install it with "
|
|
25
|
+
"`pip install jetgo[pythia]`, which pulls in the `pythia8mc` wheel, or compile Pythia8 yourself with its "
|
|
26
|
+
"Python interface enabled so that `import pythia8` works. Note that the `pythia8mc` wheels are Linux-only; "
|
|
27
|
+
"on another platform, a self-compiled build is the only option. The rest of jetgo, including every "
|
|
28
|
+
"observable and tagger, imports and runs without Pythia8."
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class _Pythia8NotInstalled:
|
|
33
|
+
"""
|
|
34
|
+
Stands in for the Pythia8 module when neither build is installed.
|
|
35
|
+
|
|
36
|
+
Importing a module must not fail merely because an optional dependency is absent, or nothing that
|
|
37
|
+
merely *mentions* Pythia8 could be imported either, including the modules whose only use of it is a
|
|
38
|
+
type annotation. Touching anything on this stand-in raises instead, so the error arrives when event
|
|
39
|
+
generation is actually attempted and names the way to fix it.
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
def __getattr__(self, name: str):
|
|
43
|
+
raise ModuleNotFoundError(_NOT_INSTALLED_MESSAGE)
|
|
44
|
+
|
|
45
|
+
def __bool__(self) -> bool:
|
|
46
|
+
return False
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def _resolve():
|
|
50
|
+
for name in _CANDIDATE_MODULE_NAMES:
|
|
51
|
+
try:
|
|
52
|
+
return importlib.import_module(name)
|
|
53
|
+
except ModuleNotFoundError:
|
|
54
|
+
continue
|
|
55
|
+
|
|
56
|
+
return _Pythia8NotInstalled()
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
pythia8 = _resolve()
|
|
60
|
+
|
|
61
|
+
#: Whether a working Pythia8 build was found. Lets callers branch on availability without having to
|
|
62
|
+
#: import Pythia8 themselves, and lets the test suite skip what genuinely needs a generator.
|
|
63
|
+
PYTHIA8_AVAILABLE = not isinstance(pythia8, _Pythia8NotInstalled)
|