ThresholdFinder
The distribution ships two packages:
threshold_finder(CLIsthreshold-finder,qn-options) finds hadronic thresholds; see below.gimodel(CLIgi-spectrum) computes Godfrey–Isgur quark-model meson spectra; see Quark-model spectrum.
Finds n-body hadronic thresholds compatible with given J^P quantum numbers. Given a mass range and a target J^P, it scans all combinations of PDG hadrons whose combined mass falls in that range and checks whether they can couple — via some total orbital angular momentum L — to produce the desired quantum numbers. The default is two-body; use --n-body (or n_body= in the API) to search three-body or higher final states.
Requirements
- Python >= 3.11
particle>= 0.24numpyandscipy(for the quark model,gimodel)pdg>= 2026.0, the official PDG Python API (forgi-spectrum --match)
pip install thresholds
Usage
Command line
threshold-finder mass_min mass_max [J P] [options]
Positional arguments:
| Argument | Description |
|---|---|
mass_min |
Lower bound of the threshold search range (MeV) |
mass_max |
Upper bound of the threshold search range (MeV) |
J |
Target total angular momentum (integer or half-integer, e.g. 1, 0.5). Optional if --particles is given. |
P |
Target parity: +1 or -1. Optional if --particles is given. |
J and P must either both be given or both be omitted. When omitted, --particles is required and the 3 lowest J^P combinations the pair can produce are used automatically.
Optional arguments:
| Flag | Default | Description |
|---|---|---|
--max-L L |
auto | Maximum orbital angular momentum to consider. Without this flag, L is capped automatically at J + J₁ + J₂ + 4 for each pair. |
--charge CHARGE |
0 |
Required total electric charge of the two-particle system. |
--status S [S ...] |
0 |
PDG status codes to include: 0 = well-established, 1 = evidence but unconfirmed, 2 = omitted from summary tables. |
--unique-pairs |
off | Show each particle combination only once (keeping the lowest L), instead of one entry per valid L. |
--n-body N |
2 |
Number of particles in the final state (must be >= 2). Default is 2 (two-body). Use 3 for three-body, etc. |
Flavor conservation flags (all optional, independent):
| Flag | Description |
|---|---|
--u N |
Required net u-quark number of the pair (#u − #ū) |
--d N |
Required net d-quark number of the pair (#d − #d̄) |
--s N |
Required net s-quark number of the pair (#s − #s̄) |
--c N |
Required net charm of the pair (#c − #c̄) |
--b N |
Required net bottomness of the pair (#b − #b̄) |
--particles P1 P2 |
Derive all flavor numbers automatically from two PDG particle names |
Only the flags you provide are enforced. Omit a flag to leave that flavor unconstrained. Pairs involving particles with undefined quark content (e.g. η, ω — mixed states like uū+dd̄) are excluded when any flavor flag is set.
--particles does two things:
- Derives flavor conservation automatically — no need to compute net quark numbers by hand. Explicit
--u/--d/...flags override the derived values. - Checks feasibility — if J and P are given, the tool first verifies that the specified pair can actually produce that J^P at some L. If not, a warning is printed and no results are shown for that J^P.
- Auto-detects J^P — if J and P are omitted, the 3 lowest J^P combinations the pair can produce (ordered by minimum L) are determined and each is searched separately.
Examples
Find all 1⁻ channels with threshold between 250 and 300 MeV (the ρ region):
$ python3 -m threshold_finder.cli 250 300 1 -1
Thresholds for J^P = 1^- in [250.0, 300.0] MeV (max L = ∞)
Found 1 combination(s):
pi+ + pi- threshold=279.1 MeV L=1 J^P=1^-
Find 1⁻ channels near 1 GeV with full flavor conservation (all net quark numbers = 0):
$ python3 -m threshold_finder.cli 900 1100 1 -1 --u 0 --d 0 --s 0 --c 0 --b 0 --unique-pairs
Thresholds for J^P = 1^- in [900.0, 1100.0] MeV (max L = ∞) flavor: u=+0, d=+0, s=+0, c=+0, b=+0
Found 4 combination(s):
pi+ + rho(770)- threshold=914.7 MeV L=1 J^P=1^-
pi- + rho(770)+ threshold=914.7 MeV L=1 J^P=1^-
K+ + K- threshold=987.4 MeV L=1 J^P=1^-
K0 + K~0 threshold=995.2 MeV L=1 J^P=1^-
Constrain only strangeness (leave u/d free) to find kaonic channels:
$ python3 -m threshold_finder.cli 600 700 0 -1 --s -1 --unique-pairs
Thresholds for J^P = 0^- in [600.0, 700.0] MeV (max L = ∞) flavor: s=-1
Found 4 combination(s):
pi0 + K(L)0 threshold=632.6 MeV L=0 J^P=0^-
...
Find open-charm 1⁻ thresholds near ψ(3770) with net zero flavor:
$ python3 -m threshold_finder.cli 3700 3900 1 -1 --u 0 --d 0 --s 0 --c 0 --b 0 --unique-pairs
...
D0 + D~0 threshold=3729.7 MeV L=1 J^P=1^-
D+ + D- threshold=3739.3 MeV L=1 J^P=1^-
...
D0 + D*(2007)~0 threshold=3871.7 MeV L=1 J^P=1^-
...
Omit J and P to auto-detect the 3 lowest J^P combinations D0 + Lambda can produce:
$ threshold-finder 2800 3000 --particles 'D0' 'Lambda' --unique-pairs
Flavor conservation derived from 'D0' + 'Lambda':
u = +0
d = +1
s = +1
c = +1
No J^P given — using the 3 lowest combinations 'D0' + 'Lambda' can produce:
J^P = 1/2^- (lowest at L=0)
J^P = 1/2^+ (lowest at L=1)
J^P = 3/2^+ (lowest at L=1)
Thresholds for J^P = 1/2^- in [2800.0, 3000.0] MeV ...
...
Thresholds for J^P = 1/2^+ in [2800.0, 3000.0] MeV ...
...
Thresholds for J^P = 3/2^+ in [2800.0, 3000.0] MeV ...
...
If the requested J^P cannot be produced by the given pair at any L, a warning is shown and that J^P is skipped:
$ threshold-finder 2800 3000 0.5 +1 --particles 'pi+' 'pi-'
WARNING: 'pi+' + 'pi-' cannot produce J^P = 1/2^+ at any L
Derive flavor numbers from reference particles (D0 + Lambda defines the channel):
$ threshold-finder 2800 3000 0.5 -1 --particles 'D0' 'Lambda' --unique-pairs
Flavor conservation derived from 'D0' + 'Lambda':
u = +0
d = +1
s = +1
c = +1
Thresholds for J^P = 1/2^- in [2800.0, 3000.0] MeV (max L = ∞) flavor: u=+0, d=+1, s=+1, c=+1
Found 5 combination(s):
pi- + Xi(c)(2790)+ threshold=2931.5 MeV L=1 J^P=1/2^-
K- + Sigma(c)(2455)+ threshold=2946.3 MeV L=0 J^P=1/2^-
...
If the reference threshold is outside the search range, a warning is printed but the search still runs:
$ threshold-finder 2500 2800 0.5 -1 --particles 'D0' 'Lambda' --unique-pairs
WARNING: threshold of D0 + Lambda = 2980.5 MeV is above mass_max = 2800.0 MeV
Flavor conservation derived from 'D0' + 'Lambda':
...
If a particle has ambiguous quark content (mixed state), the tool reports what could be determined and prints a ready-to-edit command with ??? placeholders for the unknown flavors:
$ threshold-finder 2800 3000 0.5 -1 --particles 'eta' 'Lambda'
ERROR: Quark content is ambiguous (mixed/superposition state) for:
eta (PDG quarks string: 'x(uU+dD)+y(sS)')
Determined from ['Lambda']: d=+1, s=+1, u=+1
Set the remaining flavor flags manually. Example command:
threshold-finder 2800.0 3000.0 0.5 -1 --u 1 --d 1 --s 1 --c ??? --b ???
If a particle name is not found, 5 suggestions are printed as ready-to-run commands:
$ threshold-finder 2800 3000 0.5 -1 --particles 'D0' 'Lmabda'
ERROR: Unknown particle 'Lmabda'
Did you mean one of these?
threshold-finder 2800.0 3000.0 0.5 -1 --particles 'D0' 'Lambda'
threshold-finder 2800.0 3000.0 0.5 -1 --particles 'D0' 'Lambda~'
threshold-finder 2800.0 3000.0 0.5 -1 --particles 'D0' 'Lambda(c)+'
threshold-finder 2800.0 3000.0 0.5 -1 --particles 'D0' 'Lambda(b)0'
threshold-finder 2800.0 3000.0 0.5 -1 --particles 'D0' 'Lambda(1520)'
Find three-body 0⁻ thresholds near 420 MeV (the 3π region):
$ threshold-finder 400 450 0 -1 --n-body 3 --max-L 0 --unique-pairs
Thresholds for J^P = 0^- in [400.0, 450.0] MeV (max L = 0) (3-body)
Found 2 combination(s):
pi- + pi0 + pi+ threshold=414.1 MeV L=0 J^P=0^-
pi0 + pi0 + pi0 threshold=404.9 MeV L=0 J^P=0^-
Find 2⁺ channels with threshold 500–700 MeV, restricting to L ≤ 2:
$ python3 -m threshold_finder.cli 500 700 2 +1 --max-L 2 --unique-pairs
Thresholds for J^P = 2^+ in [500.0, 700.0] MeV (max L = 2)
Found 7 combination(s):
pi0 + K(L)0 threshold=632.6 MeV L=2 J^P=2^+
...
Python API
from threshold_finder import ThresholdFinder, FlavorFilter
finder = ThresholdFinder(
mass_min=900,
mass_max=1100,
J_target=1,
P_target=-1,
n_body=2, # 2 = two-body (default); set 3 for three-body, etc.
max_L=None, # None = automatic
total_charge=0.0,
flavor_filter=FlavorFilter(u=0, d=0, s=0, c=0, b=0), # all net quark numbers = 0
status_filter=(0,), # established particles only
)
result = finder.run()
print(result) # formatted summary
for c in result.combinations:
print(" + ".join(c.particles), " L =", c.L, " threshold =", c.threshold, "MeV")
Three-body search:
finder = ThresholdFinder(
mass_min=400,
mass_max=450,
J_target=0,
P_target=-1,
n_body=3,
max_L=0,
)
result = finder.run()
for c in result.combinations:
print(" + ".join(c.particles), " threshold =", c.threshold, "MeV")
Constrain only specific flavors by omitting the rest:
# Only require net charm = 0; u, d, s, b are unconstrained
flavor_filter=FlavorFilter(c=0)
# Only require net strangeness = -1
flavor_filter=FlavorFilter(s=-1)
ThresholdResult has the fields J_target, P_target, mass_min, mass_max, max_L, n_body, flavor_filter, and combinations (a list of CombinationResult).
Each CombinationResult contains:
| Field | Type | Description |
|---|---|---|
particles |
tuple[str, ...] |
PDG names of all particles in the final state |
masses |
tuple[float, ...] |
Masses of all particles (MeV) |
charges |
tuple[float, ...] |
Charges of all particles |
spins |
tuple[float, ...] |
J values of all particles |
parities |
tuple[int, ...] |
Parities of all particles |
threshold |
float |
Sum of all particle masses (MeV) |
L |
int |
Total orbital angular momentum |
J_total |
float |
Total angular momentum (= J_target) |
P_total |
int |
Total parity (= P_target) |
For two-body results the legacy per-particle properties (particle1, particle2, mass1, mass2, charge1, charge2, J1, J2, P1, P2, identical) are still available as convenience aliases.
Physics
The tool checks whether a set of n particles (each with Jᵢ^Pᵢ) in a state of total orbital angular momentum L can produce the target J^P.
Parity:
P_total = P₁ · P₂ · … · Pₙ · (-1)^L
Angular momentum: J_total must be reachable by sequentially coupling all particle spins J₁ ⊗ J₂ ⊗ … ⊗ Jₙ via the triangle rule, then adding L.
Identical bosons (two-body only): For two identical bosons (e.g. π⁰π⁰), the spatial wave function must be symmetric under exchange, which requires L to be even. For n > 2 this constraint is not enforced (exact symmetrisation of n-body wave functions depends on the full Bose/Fermi statistics of all permutations).
Flavor conservation: Net quark numbers are computed as #quark − #antiquark for each flavor (u, d, s, c, b). They are additive over all n particles in the final state. Setting a flavor to 0 requires the combination to have no net quark content in that flavor. Particles with mixed or superposition quark content (η, ω, φ, π⁰, …) have undefined quark numbers and are excluded from any result when a flavor constraint is active.
Particle data (masses, J, P, charge, quark content) are read from the PDG via the particle package. Only hadrons with known mass, J, and P are considered.
Quark-model spectrum (gimodel, gi-spectrum)
A Python port of the core of GIModel.jl by M. Mikhasenko: the Godfrey–Isgur relativized quark model for meson spectra (S. Godfrey and N. Isgur, Phys. Rev. D 32, 189 (1985)).
The port covers the spectrum core: the Table II / Appendix A parameters (bundled
TOML), the running coupling, the closed-form smeared potentials, the
finite-difference relativized Hamiltonian, the contact hyperfine, spin-orbit
(vector and Thomas) and tensor operators, fixed-(L,S,J) channel solves,
intra-meson mixing (antisymmetric spin-orbit and tensor), and radial-wave
utilities. It uses the same numerics, conventions and defaults as the Julia
package. Both radial solvers are ported: the default finite-difference grid
(FiniteDifferenceSolver) and the paper's harmonic-oscillator basis
(OscillatorSolver). Masses agree with GIModel.jl to about 1e-12 GeV on the
grid and about 1e-14 GeV in the oscillator basis.
It does not include flavour annihilation mixing or the transition operators.
Python API
from gimodel import (Meson, compute_spectrum, load_parameters_and_quark_masses,
physical_components, radial_wave, spectrum_levels, spectrum_state,
wave_mean_squares)
params, mq = load_parameters_and_quark_masses() # bundled GI parameter set
spec = compute_spectrum(params, Meson.from_table(mq, "c", "c"), levels=spectrum_levels(2))
print(spec) # table of contributions (GeV)
chi_c1 = spectrum_state(spec, "1^3P_1")
chi_c1.mass_GeV, chi_c1.central_GeV, chi_c1.spin_orbit_shift_GeV, chi_c1.tensor_shift_GeV
[(c.basis.label, c.coefficient) for c in physical_components(spec, "1^3S_1")] # J/psi
wave_mean_squares(radial_wave(spec, "1^1S_0"), 0) # <r^2> [GeV^-2], <p^2> [GeV^2]
To vary parameters, use dataclasses.replace, for example
replace(params, potential=replace(params.potential, b=0.19)). To change the
grid, use FiniteDifferenceSolver(ngrid=900, rmax=24.0). To switch terms off,
use SpinTerms(tensor=False).
Units are GeV and GeV⁻¹ throughout.
Command line: gi-spectrum
gi-spectrum lists the model's meson states in a mass window. It follows the
conventions of threshold-finder from the same repository: masses are in MeV,
J P are optional positional filters, and it has the same flavour flags,
--particles handling and unknown-name suggestions.
gi-spectrum mass_min mass_max [J P] (--quarks Q QBAR | --u/--d/--s/--c/--b N | --particles P [P ...]) [options]
Flavour content. This is required, and you give it in exactly one way:
| Input | Meaning |
|---|---|
--quarks Q QBAR |
Quark and antiquark, from u d q s c b, e.g. --quarks c u (c ū) or --quarks c c (charmonium). This is the only way to request hidden flavour. |
--u/--d/--s/--c/--b N |
Net quark numbers, as in threshold-finder. They must add up to one quark (+1) and one antiquark (−1) of different flavours, e.g. --c 1 --s -1. A net-zero input is rejected with a --quarks suggestion. |
--particles P [P ...] |
The summed PDG quark content: D0 pi+ gives c d̄ and D(s)+ gives c s̄. A single self-conjugate particle uses its own content: J/psi(1S) gives c c̄ and Upsilon(1S) gives b b̄. Light superpositions (pi0, rho(770)0) give the light q q̄. Ambiguous content (eta, eta'(958), phi(1020), K(S)0), net-zero sums such as D0 D~0, and anything that isn't one quark plus one antiquark are errors that suggest --quarks. Flavour-neutral superpositions in a sum carry no net flavour. As in threshold-finder, explicit --u/--d/... flags override the derived values. |
u and d both map to the model's isospin-averaged light quark q. The quark
is flavour 1 and the antiquark flavour 2. This order only affects the sign of
mixing angles. For light hidden flavour (q q̄, s s̄) the output notes that
isoscalar annihilation mixing (η/η′, ω/φ) is not included.
Options.
| Flag | Default | Description |
|---|---|---|
J P |
– | Filter by J^P. J is an integer (half-integer J is rejected), P is +1 or -1. They must be given together. |
--C {+1,-1} |
– | Filter by C-parity. Only valid for self-conjugate systems. |
--solver {fd,ho} |
fd |
Finite-difference or harmonic-oscillator solver. If the solver is unavailable, the CLI prints a clean error. |
--nmax N |
3 |
Radial levels per (L, S, J) sector |
--max-L L |
3 |
Highest orbital angular momentum (S P D F; at most 4) |
--params PATH |
bundled | Parameter TOML file |
--details |
off | Mass contributions (central, contact, spin-orbit, tensor, mixing) and mixing components |
--json |
off | Machine-readable output |
--match |
off | Pair each predicted state with PDG mesons (see PDG matching) |
--match-mass-tol MEV |
50 |
Mass tolerance in MeV |
--match-width-frac F |
0.5 |
Widen the tolerance to F·Γ_PDG for broad states |
--match-status S [S ...] |
0 |
PDG status codes to include, with the same codes and default as threshold-finder's --status. See PDG matching for how they map onto the PDG API. |
--match-include-uncertain |
off | Also use J/P values the PDG gives in parentheses or as alternatives, and flag those candidates |
P = (−1)^(L+1). C = (−1)^(L+S) is shown only for equal-flavour systems.
Unequal-flavour ¹L_L/³L_L states are labelled by their dominant component and
marked as mixtures. If the highest computed level of a relevant sector is still
below mass_max, a warning on stderr suggests a larger --nmax. Errors go to
stderr with a non-zero exit status.
Examples
$ gi-spectrum 2900 3200 --quarks c c
GI spectrum of c c̄ in [2900.0, 3200.0] MeV (solver = fd, nmax = 3, L = SPDF)
Parameters: .../gimodel/data/parameters.provisional.toml
Found 2 state(s):
1^1S_0 J^PC=0^-+ mass=2966.7 MeV
1^3S_1 J^PC=1^-- mass=3091.0 MeV
$ gi-spectrum 2400 2600 1 +1 --particles 'D(s)+' --details
Reference particles: 'D(s)+' -> c s̄
GI spectrum of c s̄ for J^P = 1^+ in [2400.0, 2600.0] MeV (solver = fd, nmax = 3, L = SPDF)
Parameters: .../gimodel/data/parameters.provisional.toml
Found 2 state(s):
1^1P_1 J^P=1^+ mass=2547.1 MeV (mixture: 59% 1^1P_1 + 41% 1^3P_1)
contributions [MeV]: central=2565.0 contact=-14.7 spin-orbit=+0.0 tensor=+0.0 mixing=-3.2
components: +0.7708 1^1P_1, -0.6366 1^3P_1, -0.0200 2^3P_1, +0.0166 2^1P_1
1^3P_1 J^P=1^+ mass=2554.1 MeV (mixture: 59% 1^3P_1 + 41% 1^1P_1)
contributions [MeV]: central=2565.5 contact=+5.0 spin-orbit=-30.7 tensor=+11.7 mixing=+2.5
components: +0.7708 1^3P_1, +0.6366 1^1P_1, -0.0197 2^1P_1, -0.0162 2^3P_1, +0.0015 3^1P_1, +0.0011 3^3P_1
$ gi-spectrum 3000 3700 --particles 'J/psi(1S)' --C -1
Reference particles: 'J/psi(1S)' -> c c̄
GI spectrum of c c̄ with C = - in [3000.0, 3700.0] MeV (solver = fd, nmax = 3, L = SPDF)
Parameters: .../gimodel/data/parameters.provisional.toml
Found 3 state(s):
1^3S_1 J^PC=1^-- mass=3091.0 MeV
1^1P_1 J^PC=1^+- mass=3515.1 MeV
2^3S_1 J^PC=1^-- mass=3678.8 MeV
$ gi-spectrum 1800 2100 --particles D0 pi+ --max-L 1
Reference particles: 'D0' + 'pi+' -> c d̄
GI spectrum of c d̄ in [1800.0, 2100.0] MeV (solver = fd, nmax = 3, L = SP)
Parameters: .../gimodel/data/parameters.provisional.toml
Note: u and d map to the isospin-averaged light quark: model system c q̄
Found 2 state(s):
1^1S_0 J^P=0^- mass=1873.4 MeV
1^3S_1 J^P=1^- mass=2038.0 MeV
$ gi-spectrum 2900 3200 --c 0
ERROR: Net flavour from the flavour flags is zero (hidden flavour), which the --u/--d/--s/--c/--b numbers cannot specify.
Give the quark and antiquark explicitly with --quarks, e.g.:
gi-spectrum 2900.0 3200.0 --quarks c c
--json prints the system, solver, parameter file, filters, notes, warnings
and, for each state, its label, n, L, S, J, P, C, mass in MeV, mixing
components and contributions.
PDG matching (--match)
--match pairs every predicted state with existing PDG mesons. The data comes from
the official PDG Python API (pdg package). It
ships the full PDG database as SQLite and works offline. Unlike the particle
package, it includes states without Monte-Carlo IDs, such as D_s1*(2700),
D_s1*(2860), D_s3*(2860), χc1(3872), ψ(4230) and the X states. The header names
the package version and the edition, e.g.
PDG data: PDG Python API (pdg 2026.0), 2026 edition, and JSON gives them
under match_settings.source. threshold-finder and --particles keep using
particle. The --match-* options require --match. Without --match the output
is unchanged.
Data used. The matcher reads every meson listing from api.get_particles()
(listings whose data_flags contain M) and every charge state in it
(PdgParticle). For each state it takes:
- the best mass from
best_summary(). A mass given as a range, e.g. f₀(500) at "400 to 800", uses the midpoint and is flagged. Limits count as unknown. - the best width, if the listing has a width entry. Upper limits, e.g. D*(2007)⁰ at
"< 2.1", count as unknown. The API's
widthproperty returns 0 for listings without a width entry, so the matcher doesn't use it. - the strings
quantum_J,quantum_P,quantum_Candquantum_I.
Antiparticles (negative MC IDs or charges, and Dbar…/Kbar…/Bbar… names) are
skipped.
Status. The PDG API has no per-listing status. It only marks values that appear
in the Summary Tables (in_summary_table). --match-status uses threshold-finder's
codes as follows:
| Code | threshold-finder (particle) |
PDG API |
|---|---|---|
0 |
established | the listing's best mass is in the Summary Tables |
1 |
evidence, not confirmed | no counterpart: selects nothing |
2 |
omitted from the summary tables | the listing's best mass appears only in the Listings |
In the 2026 edition every meson listing with a usable mass has status 0, so the default already includes D_s1*(2700) and ψ(4230). The light-meson "Further States" (listing M300, e.g. X(1575), ρ(2000)) exist only as text descriptions without mass, J or P data, so they can't be matched.
Criterion. A PDG state is a candidate for a prediction if all of these hold:
-
Mass. |m_pred − m_PDG| ≤ max(
--match-mass-tol,--match-width-frac· Γ_PDG). The model predicts no widths, so the PDG width only widens the window for broad states. If Γ_PDG is unknown, the mass tolerance is used alone. The output shows the effective tolerance and whether it came from the width (tol = 0.5·Γ = 74.1). -
J and P equal. C must also be equal when both are defined, i.e. the system is self-conjugate and the PDG state has C. States whose J or P is
?are never matched, e.g. X(3940), K(1630) and D*(2640). Values in parentheses, and alternatives such as f_J(2220)'sJ = 2++ or 4, count as unknown by default. With--match-include-uncertainthey are used, with one candidate per alternative, and flaggeduncertain J^P in PDG. J and P are never inferred from MC-ID digits, because those are a numbering convention, not measured quantum numbers. -
Flavour compatible. The PDG API has no quark content, so the flavour family comes from the PDG meson naming scheme:
PDG name Family pi rho omega phi eta f a b h(eta^',f_2^', …)light unflavoured K…s q̄ D…/D_s…c q̄ / c s̄ B…/B_s…/B_c…b q̄ / b s̄ / b c̄ eta_c J/psi psi chi_c h_cc c̄ eta_b Upsilon chi_b h_bb b̄ X(…)the PDG section it is listed in (the nearest preceding listing), flagged T…(four-quark states, e.g. T_cc̄1(3900)⁺ = Z_c)never a q q̄ partner This is matched against the system, where u and d are the model's averaged light quark q:
System Matches open flavour, e.g. c s̄, c ū the same pair, up to charge conjugation and isospin partners: c q̄ matches D⁰, D⁺, D⁰ and D⁺ c c̄, b b̄ the c c̄ / b b̄ family, including χc1(3872) and ψ(4230) q q̄, u ū, d d̄ light unflavoured I = 1 and I = 0 states u d̄, d ū light unflavoured I = 1 only s s̄ light unflavoured I = 0 states Light isoscalars (I = 0, or I unknown) fit both q q̄ and s s̄. They are flagged flavour-ambiguous because isoscalar annihilation mixing isn't modelled.
Isospin and charge multiplets. Most charge states share one PDG listing, e.g.
ρ(770)⁰/⁺, D_0*(2300)⁰/⁺ and K*(892)⁰/⁺. Some don't: D⁰/D⁺, D*(2007)⁰/D*(2010)⁺,
K⁺/K⁰ and B⁰/B⁺ are separate listings. These are merged when their MC IDs agree up
to the u/d quark digits (423/413). Members must also share isospin, J and P, which
keeps π⁰ (I = 1) and η (I = 0) apart. The representative is the neutral member, or
the lowest MC ID if there is no neutral member. For a multiplet it is shown with the
charge suffix dropped (D, D^*(2007), rho(770)), and singletons keep their full
PDG API name (omega(782)0, D_s1^*(2700)+). The mass is the average over the
charge states, matching the model's isospin-averaged light quark, so D is 1867.3 MeV
from D⁰ and D⁺. The width is the average of the known widths. C comes from the
neutral member. --details and JSON list the members.
Assignment. Every candidate within tolerance is listed for each prediction,
ranked by |Δm| (Δ = m_pred − m_PDG). Each J^P(C) group also gets a one-to-one
assignment from scipy.optimize.linear_sum_assignment that minimises the total
|Δm|. Pairs outside tolerance get a large cost and are dropped afterwards, so the
assignment first maximises the number of matched pairs. The assignment only uses
the predictions shown in the window. The table shows the assigned partner
(→ chi_c1(1P) 3510.7 MeV (Δ = -2.5)) or → no match. --details lists all
candidates with Δm, effective tolerance, assignment and flags.
Unassigned PDG states. After the table, the section "PDG states in window
without assigned prediction" lists the compatible states that no prediction took.
These states pass the flavour rules and the J P / --C filter, and their mass lies
in [mass_min, mass_max] widened by their own tolerance. This helps to spot missing
levels and exotics. A J^PC that no q q̄ state can have (0⁻⁻, 0⁺⁻, 1⁻⁺, 2⁺⁻, …) is
flagged exotic J^PC, e.g. π₁(1600).
JSON. Each state gains a match object with assigned (or null) and
candidates. Each entry has the name, members, pdg_listing (e.g. M182), status,
mass, width, J/P/C, isospin, delta_MeV, tolerance_MeV, tolerance_source
(mass_tol or width), flags and assigned_to. The top level gains
match_settings, which holds the tolerances, status, include_uncertain and the
data source, and unassigned_pdg. The existing keys are unchanged.
Data quirks (2026 edition): φ(2170) lists a charged row with I = 0, which is merged into the single φ(2170) entry. Some narrow states have only an upper limit on the width (D_s*, D_s0*(2317)) or no width entry (B*), so they use the mass tolerance alone.
$ gi-spectrum 2900 3600 --quarks c c --nmax 2 --max-L 2 --match
GI spectrum of c c̄ in [2900.0, 3600.0] MeV (solver = fd, nmax = 2, L = SPD)
Parameters: .../gimodel/data/parameters.provisional.toml
PDG match: |m_pred - m_PDG| <= max(50 MeV, 0.5·Γ_PDG), PDG status 0; Δ = m_pred - m_PDG; one-to-one per J^P(C), minimal total |Δ|
PDG data: PDG Python API (pdg 2026.0), 2026 edition; only listings with known mass, J and P are matched (uncertain J/P count as unknown)
Found 6 state(s):
1^1S_0 J^PC=0^-+ mass=2966.7 MeV → eta_c(1S) 2984.1 MeV (Δ = -17.4)
1^3S_1 J^PC=1^-- mass=3091.0 MeV → J/psi(1S) 3096.9 MeV (Δ = -5.9)
1^3P_0 J^PC=0^++ mass=3442.8 MeV → chi_c0(1P) 3415.5 MeV (Δ = +27.3)
1^3P_1 J^PC=1^++ mass=3508.2 MeV → chi_c1(1P) 3510.7 MeV (Δ = -2.5)
1^1P_1 J^PC=1^+- mass=3515.1 MeV → h_c(1P) 3525.4 MeV (Δ = -10.3)
1^3P_2 J^PC=2^++ mass=3548.1 MeV → chi_c2(1P) 3556.2 MeV (Δ = -8.0)
PDG states in window without assigned prediction ([2900.0, 3600.0] MeV widened by each state's tolerance):
eta_c(2S) J^PC=0^-+ mass=3637.8 MeV Γ=11.6 MeV tol = 50.0
η_c(2S) is listed because its prediction (2^1S_0 at 3625.4 MeV) lies above the
window. With 2900 3700 it is assigned, and so is ψ(2S).
$ gi-spectrum 2650 3000 --particles 'D~0' 'K-' --match --match-mass-tol 80
Reference particles: 'D~0' + 'K-' -> s c̄
GI spectrum of s c̄ in [2650.0, 3000.0] MeV (solver = fd, nmax = 3, L = SPDF)
Parameters: .../gimodel/data/parameters.provisional.toml
PDG match: |m_pred - m_PDG| <= max(80 MeV, 0.5·Γ_PDG), PDG status 0; Δ = m_pred - m_PDG; one-to-one per J^P(C), minimal total |Δ|
PDG data: PDG Python API (pdg 2026.0), 2026 edition; only listings with known mass, J and P are matched (uncertain J/P count as unknown)
Found 6 state(s):
2^1S_0 J^P=0^- mass=2674.6 MeV → no match
2^3S_1 J^P=1^- mass=2734.3 MeV → D_s1^*(2700)+ 2714.0 MeV (Δ = +20.3)
1^3D_1 J^P=1^- mass=2898.5 MeV → D_s1^*(2860)+ 2859.0 MeV (Δ = +39.5)
1^1D_2 J^P=2^- mass=2899.9 MeV (mixture: 61% 1^1D_2 + 39% 1^3D_2) → no match
1^3D_3 J^P=3^- mass=2916.1 MeV → D_s3^*(2860)+ 2860.5 MeV (Δ = +55.6)
1^3D_2 J^P=2^- mass=2925.4 MeV (mixture: 61% 1^3D_2 + 39% 1^1D_2) → no match
PDG states in window without assigned prediction ([2650.0, 3000.0] MeV widened by each state's tolerance):
D_s0(2590)+ J^P=0^- mass=2591.0 MeV Γ=89.0 MeV tol = 80.0
$ gi-spectrum 700 850 1 -1 --quarks u u --nmax 1 --max-L 1 --match --details
...
Found 1 state(s):
1^3S_1 J^PC=1^-- mass=771.2 MeV → rho(770) 775.2 MeV (Δ = -4.0, tol = 0.5·Γ = 74.1)
contributions [MeV]: central=667.1 contact=+104.1 spin-orbit=+0.0 tensor=+0.0 mixing=+0.0
components: +1.0000 1^3S_1
PDG candidates (by |Δm|):
rho(770) mass=775.2 MeV Γ=148.2 MeV Δ=-4.0 tol = 0.5·Γ = 74.1 assigned (members: rho(770)0, rho(770)+)
omega(782)0 mass=782.7 MeV Γ=8.7 MeV Δ=-11.5 tol = 50.0 unassigned [flavour-ambiguous]
PDG states in window without assigned prediction ([700.0, 850.0] MeV widened by each state's tolerance):
omega(782)0 J^PC=1^-- mass=782.7 MeV Γ=8.7 MeV tol = 50.0 [flavour-ambiguous]
With --quarks u d (I = 1) ω(782) is not a candidate.
Tests and reference data
tests/gimodel/ compares the port layer by layer against JSON reference data generated from GIModel.jl (tests/gimodel/reference/). tools/julia_reference/ holds the Julia generator and explains how to regenerate the data.
python -m pytest # everything
python -m pytest -m "not slow" # skip the long Julia cross-checks
Metadata
Release files for thresholds 0.5.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| thresholds-0.5.0.tar.gz | 121.7 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| thresholds-0.5.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 228.0 kB
Release files / thresholds-0.5.0.tar.gz
| Download URL | thresholds-0.5.0.tar.gz |
|---|---|
| Size | 121.7 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
0b992c947e06c53f09705a1b0e7ac83a8458d484da2f48831cc4c99ada076064
|
|
BLAKE2b-256 checksum How to use checksums |
95d1132c9067d279d57a3d28d5f719e0feef56e30f32859f13dc6a4b302a0b4d
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 6, 2026.
Transparency logRelease files / thresholds-0.5.0-py3-none-any.whl
| Download URL | thresholds-0.5.0-py3-none-any.whl |
|---|---|
| Size | 106.3 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
cd4a367ddabafad000736a196995bb5abab0b7354d7ba6d2dbd36c6c090fb32d
|
|
BLAKE2b-256 checksum How to use checksums |
7c0c0ee7863e1fa0c4c00447e1eb2e69b9a594509613d68e996fc5900261dbef
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 6, 2026.
Transparency log