API reference#
Simulation#
@file: simulator.py @Author: Maxence Larose
@Creation Date: 06/2026 @Last modification: 07/2026
- @Description: This file defines the Simulator class, which orchestrates the full jet analysis workflow.
It generates events, selects particles, reconstructs jets, evaluates observables, and saves the resulting histograms to a single JSON file.
- class jetgo.simulation.simulator.Simulator(event_generator, jet_finder, particle_selector)[source]#
Bases:
objectHigh-level driver for a jet physics analysis workflow.
This class orchestrates the full event-level analysis pipeline: event generation, particle selection, jet reconstruction, and observable evaluation.
For each event, particles are selected according some acceptance criteria, clustered into jets, and passed to the observable for filling the histogram.
- Parameters:
event_generator (EventGenerator)
jet_finder (JetFinder)
particle_selector (ParticleSelector)
- simulate(n_events, observables, output_path, jet_flavor_taggers=None, verbose=False)[source]#
Run the full event loop for one or more observables, optionally split by jet flavor.
Each event is processed sequentially: generation → particle selection → jet finding → observable histogram filling. Every observable in
observablesis filled with all reconstructed jets, regardless of flavor.If
jet_flavor_taggersis given, each tagger additionally produces a quark-tagged and a gluon-tagged copy of every observable (independent deep copies: same class, same construction parameters, empty histograms), filled only with the jets that tagger labels as a quark or a gluon respectively. Jets a tagger leaves untagged are excluded from both of its copies, but still contribute to the inclusive histogram.All results are written to a single JSON file: run metadata (number of events, number of jets, and, for every tagger, its number of quark-tagged/gluon-tagged/untagged jets, plus, for every pair of taggers, how often they agree on flavor among jets both of them tag), plus the inclusive histogram of every observable and the quark/gluon histograms for every (observable, tagger) pair, keyed by
ObservableIdentifierandJetFlavorTaggerIdentifierrespectively.- Parameters:
- n_eventsint
Number of events to generate.
- observablesObservable or Sequence[Observable]
One observable, or a sequence of observables, whose internal histograms are filled event-by-event with all jets. Each observable must have a distinct
identifier, since it is used as that observable’s key in the output file.- output_pathstr or Path
Output JSON file where every result is stored. Created or overwritten.
- jet_flavor_taggersOptional[JetFlavorTagger or Sequence[JetFlavorTagger]]
One tagger, or a sequence of taggers, used to label each jet as a quark or a gluon. Each tagger must have a distinct
identifier. If None (default), only inclusive histograms are produced.- verbosebool
Verbose mode.
- Parameters:
n_events (int)
observables (Observable | Sequence[Observable])
jet_flavor_taggers (JetFlavorTagger | Sequence[JetFlavorTagger] | None)
verbose (bool)
- Return type:
None
@file: event_generator.py @Author: Maxence Larose
@Creation Date: 06/2026 @Last modification: 06/2026
- @Description: This file defines the EventGenerator class, a wrapper around Pythia8 for generating hard QCD
collision events with configurable beams, center-of-mass energy, and partonic phase space cuts.
- class jetgo.simulation.event_generator.EventGenerator(beam_1, beam_2, sqrt_s, pt_min, pt_max, random_seed=0)[source]#
Bases:
objectPythia8 event generator for hard-QCD hadronic collision events.
This class configures Pythia8 for generic beam–beam collisions, restricts the hard-scattering phase space in pT-hat, and provides utilities for event generation and cross-section normalization.
- Parameters:
- property scale_factor: float#
Compute the event normalization factor for cross-section scaling.
In Pythia, events generated with restricted pT-hat ranges represent a biased Monte Carlo sample. Each event must therefore be reweighted to recover the physical cross-section.
The normalization factor is defined as:
scale_factor = σ_gen / Σw
- where:
σ_gen is the Pythia estimate of the process cross-section;
Σw is the sum of event weights in the generated sample.
- Returns:
- scale_factor: float
Normalization factor in nanobarns (nb).
@file: particle_selector.py @Author: Maxence Larose
@Creation Date: 06/2026 @Last modification: 07/2026
- @Description: This file defines the ParticleSelector class, which selects particles from Pythia8 events for
jet reconstruction. It keeps final-state, visible particles, applies a rapidity acceptance cut, and converts the survivors into FastJet PseudoJet objects.
- class jetgo.simulation.particle_selector.ParticleSelector(max_abs_rapidity=None, max_abs_pseudorapidity=None, charged_only=False)[source]#
Bases:
objectAn object that is used to apply particle-level selections to a Pythia event and converts the accepted particles into FastJet PseudoJet objects.
The selector retains only final-state, visible particles within a specified rapidity acceptance.
- Parameters:
- select(event)[source]#
Select particles from a Pythia event and convert them to a list of FastJet PseudoJets.
- The following selections are applied:
Final-state particles only;
Visible particles only (interacting via electromagnetic or strong force);
Charged particles only, if
charged_onlywas set;|y|< max_abs_rapidity.
The Pythia8 index of each selected particle is encoded into the PseudoJet’s
user_index()byjetgo.kinematics.encode_user_index, which defines the convention: its magnitude is the particle’s index inevent, and its sign indicates whether the particle is charged (positive) or neutral (negative). Real final-state particles always have a positive index (index 0 in a Pythia8 event is a reserved placeholder, never a real particle), so this encoding is unambiguous and reversible.- Parameters:
- eventpythia8.Event
A Pythia-generated event.
- Returns:
- particlesList[fj.PseudoJet]
Selected particles as FastJet PseudoJets, ready for jet clustering.
- Parameters:
event (pythia8mc.Event)
- Return type:
List[PseudoJet]
@file: jet_finder.py @Author: Maxence Larose
@Creation Date: 06/2026 @Last modification: 06/2026
- @Description: This file defines the JetFinder class, a wrapper around FastJet for jet clustering and jet-level
selections on reconstructed particles.
- class jetgo.simulation.jet_finder.JetFinder(radius, pt_min=None, pt_max=None, max_abs_rapidity=None, max_abs_pseudorapidity=None, clustering_algorithm=2, recombination_scheme=0, strategy=1)[source]#
Bases:
objectA FastJet-based jet finder for clustering and applying jet-level selections.
- Parameters:
- get_cluster(particles)[source]#
Cluster particles into a FastJet ClusterSequence.
- Parameters:
- particlesList[fj.PseudoJet]
Input particles as FastJet PseudoJets.
- Returns:
- cluster_sequencefj.ClusterSequence
FastJet ClusterSequence.
- Parameters:
particles (List[PseudoJet])
- Return type:
ClusterSequence
- find_jets(cluster_sequence)[source]#
Return jets passing the kinematic selections.
- Parameters:
- cluster_sequencefj.ClusterSequence
FastJet ClusterSequence.
- Returns:
- jetsList[fj.PseudoJet]
Reconstructed jets sorted by transverse momentum (highest first), after applying pT and rapidity selections.
- Parameters:
cluster_sequence (ClusterSequence)
- Return type:
List[PseudoJet]
@file: simulation_summary.py @Author: Maxence Larose
@Creation Date: 07/2026 @Last modification: 07/2026
- @Description: This file defines the SimulationSummary dataclass, which accumulates jet counts and
pairwise flavor agreement over a full
Simulatorrun, and knows how to print itself and serialize itself into the metadata saved alongside the histograms.
- class jetgo.simulation.simulation_summary.SimulationSummary(n_events, n_jets, tagger_counts, pairwise_agreement)[source]#
Bases:
objectJet counts and pairwise flavor agreement accumulated over a full simulation run, saved as metadata alongside the histograms.
- Parameters:
n_events (int)
n_jets (int)
tagger_counts (Dict[JetFlavorTagger, Dict[JetFlavor, int]])
pairwise_agreement (Dict[Tuple[JetFlavorTagger, JetFlavorTagger], Dict[str, int]])
- pairwise_agreement: Dict[Tuple[JetFlavorTagger, JetFlavorTagger], Dict[str, int]]#
- print_statistics()[source]#
Print the run’s jet counts, any tagger-specific diagnostics, and, for every pair of taggers, how often they agree on flavor.
- Return type:
None
- to_dict()[source]#
Build the run metadata entry: number of events, number of jets, and, for every tagger, its counts per flavor plus any tagger-specific diagnostics (see
JetFlavorTagger.get_diagnostics), plus every pair of taggers’ flavor agreement.The pairwise agreement is a sanity check for tagger consistency: two taggers implementing genuinely different strategies should still agree on flavor most of the time for jets they both manage to tag, since the underlying hard-scattering ancestry is the same physical truth regardless of the algorithm used to recover it.
- Returns:
- dict
Metadata entry, saved under the
"metadata"key of the output file.
- Return type:
Observables#
@file: base.py @Author: Maxence Larose
@Creation Date: 06/2026 @Last modification: 07/2026
- @Description: This file defines the Observable abstract base class. It provides the shared interface and
common behavior that all physics observables inherit.
Observables accumulate a histogram during the simulation, on bin edges fixed when the observable is constructed. Those edges are not a property of the physics being simulated – they come from outside it, from whichever measurement or analysis the run will be read against – so every observable takes them as a required argument rather than choosing any itself.
Saving each contribution unbinned instead would leave that choice open until analysis time, which is the more flexible design and was the original one. It does not survive contact with the run sizes this project needs: an EEC stores one entry per constituent pair, which grows quadratically with jet multiplicity, and a million-event window costs gigabytes of RAM and JSON to keep values that are only ever histogrammed onto one known binning anyway. Binning at fill time makes the cost independent of the run length.
What remains downstream (see
finalize) is the normalization, plus whatever re-binning each observable implements on top of the saved edges. Only the cross-section does: it sums whole bins onto coarser ones, and its edges are deliberately filled fine enough to be a refinement of every binning the analysis asks for. The EEC serves only the edges it was filled on.
- class jetgo.observables.base.Observable[source]#
Bases:
ABCAbstract base class for physics observables.
Defines the shared interface and delegates observable-specific logic to abstract methods implemented by each concrete subclass.
- METADATA_KEY = 'metadata'#
- VALUES_KEY = 'values'#
- abstract property identifier: ObservableIdentifier#
Return the identifier of the observable. The identifier should match the observable identifier defined on the HEP data website.
- Returns:
- identifierObservableIdentifier
Identifier of the observable.
- abstractmethod fill(jets, cluster)[source]#
Extract this observable’s contributions from one event and accumulate them into its histogram. Called once per generated event inside the simulation loop. Subclasses decide which quantities to extract and which bin each one lands in.
- Parameters:
- jetsList[fj.PseudoJet]
Reconstructed jets for this event, as returned by the jet finder.
- clusterfj.ClusterSequence
Cluster sequence for this event, as returned by the cluster finder.
- Parameters:
jets (List[PseudoJet])
cluster (ClusterSequence)
- Return type:
None
- abstractmethod scale(scale_factor)[source]#
Record the event-normalization scale factor, applied later, at
finalizetime.
- abstractmethod classmethod finalize(values, bin_edges, metadata)[source]#
Normalize the histogram saved by
to_dictontobin_edges.Called downstream, by whatever plots or analyses the output – never during the simulation itself.
bin_edgescannot reopen the binning decision, which was made when the observable was constructed: it is the caller stating which edges it believes it is reading, and each subclass either serves it from the edges it was filled on or raises. Implementations must never silently return values on edges other than the ones asked for.- Parameters:
- values
The saved histogram, in whatever shape this observable writes it (see each subclass’s
to_dict).- bin_edgesnp.ndarray
The bin edges the result is wanted on.
- metadataDict
This observable’s saved metadata (see
_get_complementary_metadata).
- Returns:
- np.ndarray
Normalized values, one per target bin.
- Parameters:
- Return type:
@file: eec.py @Author: Maxence Larose
@Creation Date: 06/2026 @Last modification: 07/2026
- @Description: This file defines the EEC observable, the Energy-Energy Correlator as a function of the
angular separation ΔR between pairs of jet constituents. Every constituent pair is accumulated into a ΔR histogram on the bin edges the observable is constructed with – necessarily so, since one entry per pair is quadratic in jet multiplicity and storing them individually is what makes a large run unaffordable (see
Observable).Each pair is weighted by (z_i * z_j)^n, where z_i = pT_i / pT_jet is the constituent’s momentum fraction of its own jet – not by the raw (pT_i * pT_j)^n, so that a pair’s weight means the same thing regardless of the absolute pT of the jet it came from. That is what lets a per-jet-normalized EEC remain well defined across jets of different pT within one window. ΔR is computed manually from the rapidity (or, optionally, the pseudorapidity) and the azimuthal angle of each constituent.
- class jetgo.observables.eec.EEC(bin_edges, pt_min=0, energy_weight=1, charged_only=False, use_pseudorapidity=False)[source]#
Bases:
ObservableEnergy-Energy Correlator (EEC) as a function of angular separation ΔR.
Defined as (Eq. 1):
EEC(ΔR) = (1 / W_pairs) * (1 / ΔR_bin) * Σ_{jets} Σ_{i<j ∈ jet} (z_i * z_j)^n
where z_i = pT_i / pT_jet is constituent i’s momentum fraction of its own jet, and W_pairs is the total sum of all pair weights,
W_pairs = Σ_{jets} Σ_{i<j ∈ jet} (z_i * z_j)^n,
such that the observable is normalized to unity after integration over ΔR. Optionally, only charged constituents and/or constituents above a minimum transverse momentum threshold can be included.
- Parameters:
- W_PAIRS_KEY = 'w_pairs'#
- N_JETS_KEY = 'n_jets'#
- BIN_EDGES_KEY = 'bin_edges'#
- ORDERED_PAIR_FACTOR = 2#
- property identifier: ObservableIdentifier#
Identifier of the observable.
- Returns:
- identifierObservableIdentifier
Identifier of the observable.
- fill(jets, cluster)[source]#
Accumulate every constituent pair of every reconstructed jet from one event into the ΔR histogram.
For each jet, constituents are first filtered according to the minimum constituent pT requirement and, optionally, the charged-particle selection. All unique constituent pairs (i < j) are then considered and added to their ΔR bin, along with the total pair weight W_pairs accumulated for later normalization.
- Parameters:
- jetsList[fj.PseudoJet]
Reconstructed jets for this event.
- clusterfj.ClusterSequence
Cluster sequence for this event. Unused by this observable.
- Parameters:
jets (List[PseudoJet])
cluster (ClusterSequence)
- Return type:
None
- scale(scale_factor)[source]#
The EEC is normalized by the total pair weight W_pairs and is therefore independent of the event normalization. Consequently, this method intentionally performs no operation.
- Parameters:
- scale_factorfloat
Multiplicative scale factor. Ignored.
- Parameters:
scale_factor (float)
- Return type:
None
- classmethod finalize(values, bin_edges, metadata)[source]#
Normalize the accumulated ΔR histogram into EEC(ΔR), by dividing by the total pair weight W_pairs and by each bin’s width in ΔR.
- Parameters:
- valuesList[float]
The accumulated pair weight per ΔR bin.
- bin_edgesnp.ndarray
The bin edges the result is wanted on, which must be the ones the observable was filled with.
- metadataDict
This observable’s saved metadata, must contain
W_PAIRS_KEYandBIN_EDGES_KEY.
- Returns:
- np.ndarray
EEC(ΔR), one value per bin.
- Raises:
- ValueError
If the requested edges are not the ones the observable was filled on. Summing whole bins onto a coarser grid would be exact here as it is for the cross-section, but nothing asks for it – the EEC is always read on the measurement’s own edges – so anything else is refused rather than approximated.
- Parameters:
- Return type:
- classmethod normalization_denominator(metadata, per_jet_normalized)[source]#
The quantity this convention divides by to turn raw binned pair weight into an EEC.
This is what a flavor-summed EEC must be recombined with.
finalizedivides by the sample’s total pair weight andto_per_jet_normalizedby its jet count, so for two flavorsEEC_inclusive = (D_q * s_q + D_g * s_g) / (D_q + D_g)
with D the value returned here – and not with the flavor cross sections, which is only the same thing when both flavors carry equal pair weight per jet. They do not: gluon jets carry about 11% more at R = 0.4, so weighting by cross section mis-mixes the two shapes by a coherent tilt of a couple of percent across ΔR. In the per-jet convention D is the jet count, which is proportional to the cross section, so that convention is unaffected.
- Parameters:
- metadataDict
This observable’s saved metadata, must contain
W_PAIRS_KEYandN_JETS_KEY.- per_jet_normalizedbool
Whether the shape is per-jet normalized rather than normalized to unity.
- Returns:
- float
The flavor’s EEC normalization denominator.
- Parameters:
- Return type:
- classmethod to_per_jet_normalized(shape, metadata)[source]#
Convert a unity-normalized EEC shape (as returned by
finalize) into the per-jet normalized Σ_EEC convention that measurements are typically published in – i.e. divided by the number of jets rather than by the total pair weight, and summed over ordered constituent pairs rather than unordered ones (seeORDERED_PAIR_FACTOR).- Parameters:
- shapenp.ndarray
Unity-normalized EEC(ΔR), one value per bin, as returned by
finalize.- metadataDict
This observable’s saved metadata, must contain
W_PAIRS_KEYandN_JETS_KEY.
- Returns:
- np.ndarray
Σ_EEC(ΔR) per jet, one value per bin.
- Parameters:
- Return type:
@file: double_differential_jet_cross_section.py @Author: Maxence Larose
@Creation Date: 06/2026 @Last modification: 07/2026
- @Description: This file defines the DoubleDifferentialJetCrossSection observable, the double
differential inclusive jet cross-section d²σ/dpT dy. Jets are accumulated into a pT histogram on the bin edges the observable is constructed with (see
Observable); unlike the EEC, that grid can still be coarsened downstream, by summing whole bins (seefinalize).
- class jetgo.observables.double_differential_jet_cross_section.DoubleDifferentialJetCrossSection(bin_edges, max_abs_rapidity=None, max_abs_pseudorapidity=None)[source]#
Bases:
ObservableDouble-differential inclusive jet cross-section d²σ/dpT dy (or d²σ/dpT dη, if jets were selected in pseudorapidity instead – see
__init__).- Parameters:
- RAPIDITY_INTERVAL_KEY = 'rapidity_interval'#
- SCALE_FACTOR_KEY = 'scale_factor'#
- BIN_EDGES_KEY = 'bin_edges'#
- property identifier: ObservableIdentifier#
Identifier of the observable.
- Returns:
- identifierObservableIdentifier
Identifier of the observable.
- scale(scale_factor)[source]#
Record the scale factor converting raw jet counts into an absolute cross-section in nb, applied later at
finalizetime.
- fill(jets, cluster)[source]#
Accumulate every jet reconstructed in one event into its transverse momentum bin. Jets falling outside the grid are dropped, exactly as they would be when histogramming stored pT values after the fact.
- Parameters:
- jetsList[fj.PseudoJet]
Reconstructed jets for this event, as returned by the jet finder after kinematic selections.
- clusterfj.ClusterSequence
Cluster sequence for this event. Unused by this observable.
- Parameters:
jets (List[PseudoJet])
cluster (ClusterSequence)
- Return type:
None
- classmethod finalize(values, bin_edges, metadata)[source]#
Sum the accumulated jet counts onto
bin_edgesand normalize into d²σ/dpT dy, by dividing by each bin’s width in pT and by the total rapidity interval Δy.- Parameters:
- valuesList[float]
The jet count per bin of the grid the observable was filled on.
- bin_edgesnp.ndarray
The bin edges the result is wanted on. They must fall on that grid, so that each requested bin is a whole number of grid bins.
- metadataDict
This observable’s saved metadata, must contain
RAPIDITY_INTERVAL_KEY,SCALE_FACTOR_KEYandBIN_EDGES_KEY.
- Returns:
- np.ndarray
d²σ/dpT dy in nb/GeV, one value per target bin.
- Raises:
- ValueError
If the requested edges do not lie on the filled grid, so the requested bins cannot be formed by summing whole grid bins.
- Parameters:
- Return type:
@file: identifier.py @Author: Maxence Larose
@Creation Date: 06/2026 @Last modification: 06/2026
- @Description: This file defines the ObservableIdentifier enumeration. It provides a strongly-typed set of
identifiers used to reference HEPData observables in a consistent and error-safe way across the codebase.
Taggers#
@file: base.py @Author: Maxence Larose
@Creation Date: 06/2026 @Last modification: 07/2026
- @Description: This file defines the JetFlavorTagger abstract base class. It provides the shared interface
and common PDG-id-to-flavor mapping used by all concrete jet flavor tagging strategies.
- class jetgo.taggers.base.JetFlavorTagger[source]#
Bases:
ABCAbstract interface for jet quark/gluon tagging strategies.
Implementations label a reconstructed jet as originating from a quark or a gluon (or leave it untagged) using information from the full Pythia8 event record for the event the jet was reconstructed from.
- HARD_PROCESS_STATUS = 23#
- QUARK_PDG_IDS = frozenset({1, 2, 3, 4, 5, 6})#
- GLUON_PDG_ID = 21#
- abstract property identifier: JetFlavorTaggerIdentifier#
Return the identifier of this tagging strategy. Used as a key to distinguish this tagger’s results when several taggers are saved to the same output file.
- Returns:
- identifierJetFlavorTaggerIdentifier
Identifier of the tagging strategy.
- get_diagnostics()[source]#
Return tagger-specific diagnostic counters accumulated since construction, to be included in the run’s saved metadata and printed summary alongside the quark/gluon/untagged counts. Empty by default; a subclass that tracks something extra (e.g. how often it fell back to a secondary strategy) should override this.
- Returns:
- dict
Extra diagnostic counters, or an empty dict if this tagger doesn’t track any.
- Return type:
- abstractmethod tag(jet, event)[source]#
Label a jet as originating from a quark or a gluon.
- Parameters:
- jetfj.PseudoJet
Reconstructed jet to tag.
- eventpythia8.Event
Full Pythia8 event record for the same event the jet was reconstructed from. Must be the untouched event yielded by the generator, since it needs to still contain the original hard-scattering partons and, for ancestry-based strategies, the full particle history.
- Returns:
- flavorJetFlavor
JetFlavor.QUARKorJetFlavor.GLUONif the jet was successfully tagged,JetFlavor.UNTAGGEDotherwise.
- Parameters:
jet (PseudoJet)
event (pythia8.Event)
- Return type:
@file: descendancy_tracing.py @Author: Maxence Larose
@Creation Date: 07/2026 @Last modification: 07/2026
- @Description: This file defines the DescendancyTracing tagger, which tags a jet via a pT-weighted
average vote cast by each hard-scattering parton, or one of its descendants, that reaches geometrically close to the jet axis. Jets left untagged by that search can optionally fall back to every quark/gluon daughter of the relevant beam-remnant parton (found by tracing the jet’s own constituents backward), letting them all vote the same way, to catch jets that genuinely originate from the beam remnant rather than from the two outgoing hard-scattering partons.
- class jetgo.taggers.descendancy_tracing.FlavorDecisionRule(*values)[source]#
Bases:
StrEnumHow
DescendancyTracingpicks the winning flavor out of a set of pT-weighted votes (seeDescendancyTracing._decide_flavor).- AVERAGE_PT = 'average_pt'#
- CLOSEST_PT_TO_JET = 'closest_pt_to_jet'#
- class jetgo.taggers.descendancy_tracing.DescendancyTracing(delta_r_max, min_generations=5, max_generations=100, use_pseudorapidity=False, handle_untagged_jets=True, decision_rule=FlavorDecisionRule.AVERAGE_PT)[source]#
Bases:
JetFlavorTaggerTags a jet with a pT-weighted average vote cast by each hard-scattering parton, or one of its descendants, that reaches geometrically close to the jet axis.
For each hard-scattering (
|status|== 23) parton, the parton itself and its shower are walked forward generation by generation, starting with the parton itself (generation 0), then its daughters (generation 1), then daughters of daughters, and so on. At each generation, every particle in it casts a vote (weighted by its pT) for its parton’s flavor, but only if it satisfies both:It is within
delta_r_maxof the jet axis;It is one of the jet’s constituents, or an ancestor of one.
Establishing that second condition is a two-step process, and neither step ever uses a mother pointer:
Forward check (
_valid_descendants_of): does the hard parton have any descendant that is a jet constituent at all, viadaughterListRecursive()? If not, this parton contributes nothing, no matter how far the search below expands.Forward ancestor check (
_is_ancestor_of_seed_constituent): every candidate the generation-by-generation search below actually visits – not the hard parton’s entire shower up front, since with the defaultmin_generationsthe search almost always settles well before reaching final-state hadronization, so most of that shower is never visited at all – is checked, one at a time, the same way: does its owndaughterListRecursive()reach one of the constituents confirmed in step 1? If so, it is recognized as “on the path” and may cast a vote, even though it isn’t a jet constituent itself. This is needed because the search usually finds a candidate long before it reaches the constituent itself.Mother pointers (
mother1()/mother2()) are deliberately not used for either step. PYTHIA8’s color reconnection can rearrange which partons end up color-connected into the same hadronizing string, so a hadron’s recorded mother1()/mother2() can point somewhere entirely disconnected from its true shower-emission history, even when a forward path from the hard parton to that hadron genuinely exists. Both steps here rely only ondaughterListRecursive(), which is unaffected by this and remains exhaustive.
At least
min_generationsgenerations are always explored for every hard parton (not just until the first parton to find a vote), so that partons whose shower happens to resolve close to the jet earlier don’t cut the search short for a parton that would only be identified deeper in its own shower. If no parton has collected any vote aftermin_generationsgenerations, the search keeps expanding, for every parton simultaneously, one generation at a time, until at least one vote is found or every parton’s shower is fully exhausted.The jet is tagged according to
decision_rule(see_decide_flavor): by default, whichever flavor has the higher average pT among its own collected votes (not the sum, so a flavor with fewer but harder matching particles can outrank one with more but softer ones). If neither flavor collects any vote, the jet is left untagged by this search.If
handle_untagged_jetsis True (the default), a jet still untagged at that point gets one more chance: some jets genuinely originate from a beam-remnant parton (spawned to conserve color/quantum numbers once the actual hard-scattering parton is removed from the incoming proton) rather than from either of the two outgoing hard-scattering partons, so no|status|== 23 parton was ever going to claim them. For such a jet, every constituent is traced backward throughmother1()to its earliest quark/gluon ancestor (see_trace_to_earliest_ancestor), and that ancestor’s own mother – typically the incoming beam proton itself – is used to enumerate every one of its quark/gluon daughters (see_find_fallback_hard_partons).Rather than picking a single substitute parton, every one of these candidates is run through the exact same generation-by-generation forward search as a real hard parton, so the same pT-weighted-average vote decides the flavor – this mirrors how the primary search above never commits to a single hard parton in advance either; it considers every
|status|== 23 parton in the event and lets them all vote. Committing to one substitute would be unprincipled here anyway: these beam-remnant partons are typically pieces of a single jointly-fragmenting color system, so more than one of them can legitimately claim to have contributed to the same jet. If no candidate collects any vote, or no constituent’s lineage passes through a quark or a gluon at all, the jet remains untagged.- Parameters:
- MAX_ANCESTRY_STEPS = 500#
- get_diagnostics()[source]#
Return how many times the fallback (see
handle_untagged_jets) was invoked, i.e. how many jets the normal hard-parton search alone left untagged.- Returns:
- dict
{"fallback_call_count": <int>}.
- Return type:
- property identifier: JetFlavorTaggerIdentifier#
Identifier of the tagging strategy.
- Returns:
- identifierJetFlavorTaggerIdentifier
Identifier of the tagging strategy.
- tag(jet, event)[source]#
Label a jet as originating from a quark or a gluon.
- Parameters:
- jetfj.PseudoJet
Reconstructed jet to tag.
- eventpythia8.Event
Full Pythia8 event record for the same event the jet was reconstructed from. Must be the untouched event yielded by the generator, since it needs to still contain the original hard-scattering partons and their full shower history.
- Returns:
- flavorJetFlavor
JetFlavor.QUARKorJetFlavor.GLUONif the jet was successfully tagged,JetFlavor.UNTAGGEDotherwise.
- Parameters:
jet (PseudoJet)
event (pythia8.Event)
- Return type:
@file: flavor.py @Author: Maxence Larose
@Creation Date: 06/2026 @Last modification: 07/2026
- @Description: This file defines the JetFlavor enumeration, used to label a reconstructed jet as
originating from a quark, a gluon, or as untagged.
- class jetgo.taggers.flavor.JetFlavor(*values)[source]#
Bases:
StrEnum- GLUON = 'gluon'#
- QUARK = 'quark'#
- UNTAGGED = 'untagged'#
@file: identifier.py @Author: Maxence Larose
@Creation Date: 07/2026 @Last modification: 07/2026
- @Description: This file defines the JetFlavorTaggerIdentifier enumeration. It provides a strongly-typed set
of identifiers used to reference jet flavor tagging strategies in a consistent and error-safe way across the codebase, e.g. as keys when saving results to disk.
Kinematics#
@file: kinematics.py @Author: Maxence Larose
@Creation Date: 07/2026 @Last modification: 09/2026
- @Description: This file defines the shared kinematic helpers used across the package: the
delta_r function, so every part of the codebase that needs a ΔR (the EEC observable, a tagger matching a jet to a parton, …) agrees on the same definition, and the encoding of a particle’s Pythia8 index and charge into a
PseudoJet’s user index.FastJet’s
PseudoJetcarries no particle identity of its own, only a four-momentum and a single integer slot,user_index(). This package uses that slot to carry two facts about the particle aPseudoJetwas built from: its position in the Pythia8 event record, and whether it is electrically charged. The magnitude is the Pythia8 index and the sign is the charge, positive for charged and negative for neutral.That encoding works because index 0 in a Pythia8 event record is a reserved placeholder and never a real particle, so no real particle has an index whose sign is meaningless.
ParticleSelectorwrites the encoding and observables read it back. Anyone writing their ownObservablesubclass should read it through the functions below rather than touchinguser_index()directly, since the encoding is an implementation detail that these three functions define.
- jetgo.kinematics.delta_r(a, b, use_pseudorapidity=False)[source]#
Angular separation ΔR between two four-vectors.
- Parameters:
- afj.PseudoJet
First four-vector.
- bfj.PseudoJet
Second four-vector.
- use_pseudorapiditybool, default=False
If True, ΔR is computed using the pseudorapidity η instead of the (true, mass-dependent) rapidity y, i.e. ΔR = sqrt((Δη)² + (Δφ)²) instead of ΔR = sqrt((Δy)² + (Δφ)²).
- Returns:
- delta_rfloat
ΔR = sqrt((Δy or Δη)² + (Δφ)²), with Δφ correctly wrapped to [0, π].
- Parameters:
a (PseudoJet)
b (PseudoJet)
use_pseudorapidity (bool)
- Return type:
- jetgo.kinematics.encode_user_index(pythia_event_index, charged)[source]#
Encode a particle’s Pythia8 event-record index and charge into a single
PseudoJetuser index.- Parameters:
- pythia_event_indexint
Position of the particle in the Pythia8 event record. Must be strictly positive: index 0 is Pythia8’s reserved placeholder entry and never a real particle.
- chargedbool
Whether the particle is electrically charged.
- Returns:
- user_indexint
The index itself if the particle is charged, its negation if it is neutral.
- Raises:
- ValueError
If
pythia_event_indexis not strictly positive, which would make the sign meaningless.
- Parameters:
- Return type:
- jetgo.kinematics.is_charged(particle)[source]#
Whether a particle is electrically charged, read back from its encoded user index.
- Parameters:
- particlefj.PseudoJet
A particle whose user index was written by
encode_user_index, i.e. one produced byParticleSelector.
- Returns:
- chargedbool
True if the particle is charged.
- Parameters:
particle (PseudoJet)
- Return type:
- jetgo.kinematics.pythia_index(particle)[source]#
The particle’s position in the Pythia8 event record, read back from its encoded user index.
- Parameters:
- particlefj.PseudoJet
A particle whose user index was written by
encode_user_index, i.e. one produced byParticleSelector.
- Returns:
- indexint
Position of the particle in the Pythia8 event record it was selected from.
- Parameters:
particle (PseudoJet)
- Return type: