User Guide¶
Installation and first steps: Getting Started.
Model Classes¶
One class per input variable, sharing the same interface (full signatures in the API Reference):
GSFEnergy¶
GSFEnergy —
Input: Total energy per nucleus [GeV]
Output: Differential flux [particles/(m² s sr GeV)]
GSFEnergyPerNucleon¶
GSFEnergyPerNucleon —
Input: Total energy per nucleon [GeV/nucleon] (use
GSFKineticEnergyPerNucleon
for kinetic energy per nucleon)
Output: Nucleon flux [nucleons/(m² s sr GeV)]
GSFRigidity¶
GSFRigidity —
Input: Magnetic rigidity [GV]
Output: Modulated flux [particles/(m² s sr GV)]
Model Versions¶
All model classes accept a version parameter selecting the fitted parameter set:
gsf = GSFEnergy() # default 2026 fit
gsf_uso = GSFEnergy(version="2026-USO") # Usoskin modulation variant
gsf_s23e = GSFEnergy(version="2026.1-SIB23e")
gsf_epos = GSFEnergy(version="2026.1-EPOSLHCR")
gsf_2025 = GSFEnergy(version="2025") # earlier conference update
Physical model versions are named <line>.<revision>[-<physics classifier>]:
2026.1 is the current revision of the 2026 line, 2026.1-USO its
Usoskin-potential variant, 2026.1-SIB23e its SIBYLL-2.3e-only variant. A data or fit patch is published as a new revision; the previous revision (2026.0, ...) stays available so the two can be compared.
An unrevisioned name ("2026", "2026-USO") resolves to the newest
registered revision of that line and variant — use it to follow patches
automatically, or pass the revisioned name to pin one
(resolve_version("2026") shows the resolution). Bare names 2025, 2019,
2017 select the earlier sets.
| Version | Status | Covering | Solar modulation | Description |
|---|---|---|---|---|
"2026.1" |
current -- default | SIBYLL-2.3e / EPOS-LHC-R mixture | self-consistent, Ghelfi--Maurin--Derome (baseline) | Default 2026 fit |
"2026.1-USO" |
current -- alternative | SIBYLL-2.3e / EPOS-LHC-R mixture | self-consistent, Usoskin 2017 | The same fit with the other potential; use it to gauge the solar-modulation systematic |
"2026.1-SIB23e" |
current -- single interpretation | Auger FD-2026 SIBYLL-2.3e only | self-consistent, Ghelfi--Maurin--Derome | For applications needing a definite hadronic model |
"2026.1-EPOSLHCR" |
current -- variant | Auger FD-2026 EPOS-LHC-R only | self-consistent, Ghelfi--Maurin--Derome | The other single-interpretation half of the mixture |
"2025" |
conference update (ICRC 2025 / UHECR 2024) | PDG scale factors | data demodulated with a single effective potential; forward-modulated here with the bundled Usoskin table | GSF 2025 |
"2019" |
conference update | PDG scale factors | as 2025 | GSF 2019 |
"2017" |
original release | PDG scale factors | see the ICRC 2017 proceedings — the treatment is not recorded in this package; forward-modulated here with the bundled Usoskin table | Original GSF (Dembinski et al. 2017) |
get_available_versions(include_historical=False) returns just the current
sets, and version_info(version) reports any version's status, covering and
modulation potential. A loaded model also carries its own provenance:
gsf.version # -> "2026.1", the revision actually loaded
gsf.params.provenance["covering"] # the air-shower interpretation
gsf.params.provenance["solar_modulation_source"] # GMD or USO
How to cite each version¶
| Version | Cite |
|---|---|
"2026.0" |
GSF 2026 |
"2026.0-USO" |
GSF 2026 |
"2026.0-SIB23e" |
GSF 2026 |
"2026.0-EPOSLHCR" |
GSF 2026 |
"2025" |
Fujisue:2025wnp, Dembinski:2025nmp |
"2019" |
Dembinski:2017zsh |
"2017" |
Dembinski:2017zsh |
Full records, with BibTeX to copy:
GSF 2017 / 2019 — Dembinski:2017zsh — InspireHEP
@article{Dembinski:2017zsh,
author = "Dembinski, Hans Peter and Engel, Ralph and Fedynitch, Anatoli and Gaisser, Thomas and Riehn, Felix and Stanev, Todor",
title = "{Data-driven model of the cosmic-ray flux and mass composition from 10 GeV to $10^{11}$ GeV}",
eprint = "1711.11432",
archivePrefix = "arXiv",
primaryClass = "astro-ph.HE",
doi = "10.22323/1.301.0533",
journal = "PoS",
volume = "ICRC2017",
pages = "533",
year = "2018"
}
GSF 2024/2025 — UHECR 2024 — Fujisue:2025wnp — InspireHEP
GSF 2025 — ICRC 2025 — Dembinski:2025nmp — InspireHEP
@article{Dembinski:2025nmp,
author = "Dembinski, Hans and Engel, Ralph Richard and Fedynitch, Anatoli and Fujisue, Kozo",
title = "{Global Spline Fit GSF-2025 - An update of the data-driven model of the cosmic-ray flux and its mass composition}",
doi = "10.22323/1.501.0248",
journal = "PoS",
volume = "ICRC2025",
pages = "248",
year = "2025"
}
GSF 2026
Publication in preparation — citation to follow.
What "covering" means, and why both current sets are a mixture¶
Above ~10^8 GeV the mass composition inferred from air-shower data depends on the
hadronic interaction model used to interpret it. Both current sets use an
equal-weight parameter-level mixture of the Auger FD-2026 SIBYLL-2.3e and
EPOS-LHC-R interpretations: parameters are the mean of the two fits, and the
covariance carries an additional rank-one between-model term. The published band
therefore spans both interpretations where they diverge and collapses to the
ordinary fit covariance where they agree (below ~2x10^8 GeV, where the two
coincide). 2026.1-SIB23e and 2026.1-EPOSLHCR are the two halves of the mixture. Revision 0 of each name (2026.0, ...) remains selectable for comparison.
The 2026 sets are fitted in isotope format, with deuterium carried explicitly as its own spline under the H* group.
Particle Groups¶
All models support these cosmic ray groups:
| Group | Description | Atomic Numbers |
|---|---|---|
"H*" |
Hydrogen group (p and D when available) | Z = 1 |
"He" |
Helium group (written He* in the papers) | Z = 2 |
"O*" or "CNO" |
Light/intermediate group | Z = 3--9 |
"Fe*" or "heavy" |
Heavy group | Z = 10--28 |
The four groups are written H*, He*, O*, Fe* in the papers — the star
marks a group that carries neighbouring elements scaled from its leader. As
target names the model still takes "H*" and "He" ("He*" is not an
accepted key).
A bare element symbol selects that single element instead of its group:
gsf.flux(energy, "O*") # the whole oxygen group, Z = 3-9
gsf.flux(energy, "O") # oxygen alone (Z = 8), the group leader
gsf.flux(energy, 8) # the same, addressed by charge number
"p", "O" and "Fe" are the individual proton, oxygen and iron species;
any element can be addressed by its charge number Z.
Sub-leading Elements and High-Energy Extrapolation¶
Within each mass group only the leading element carries its own B-spline; the remaining sub-leading species are tied to their group leader through a rigidity-dependent flux ratio. Inside the range covered by direct data this ratio follows the fitted splines. Above each species' top knot \(R_{\text{max}}\) (the highest rigidity at which that element has data), the ratio is extrapolated as a power law in rigidity that saturates to a constant:
where \(J_L\) is the group-leader flux. The normalization \(w_j\) and the slope \(s_j\) are anchored to the data — obtained from an error-weighted power-law fit to the measured member-to-leader ratio over the last decade of the element's direct data. The saturation rigidity \(R_{\text{sat}} = 5\) PV sits near the proton knee, motivated by the common galactic origin and the assumed similarity of transport effects above it.
Working with sub-leading elements¶
gsf.z_group # {leader Z: (member Z, ...)} for all four groups
gsf.z_group[8] # -> (3, 4, 5, 6, 7, 8, 9): the O* group members
# flux of one sub-leading species (carbon, Z = 6)
carbon = gsf.flux(energy, 6)
# its uncertainty: error() propagates the LEADER's spline covariance through
# the species' own kinematics, so the relative error differs from the
# leader's at the same energy per nucleus
carbon_err = gsf.error(energy, 6)
# the fitted ratio to the group leader, per species
leader, ratio = gsf.flux_ratio[(6, 12.011)] # -> ((8, 15.999), 1.1487)
The fit varies the four leader splines and holds the sub-leading ones fixed, so a sub-leading species has no fitted uncertainty of its own: whatever error you quote for it is inherited from its group leader. Two recipes are available, and they are not the same below the species' top knot:
from globalsplinefit import GSFRigidity
gsf_r = GSFRigidity()
rigidity = np.logspace(1, 6, 200)
# (a) what error() returns: the leader's spline covariance contracted through
# ratio x d(leader flux), divided by the species' own central flux
sigma_a = gsf_r.error(rigidity, 6)
# (b) the leader's RELATIVE uncertainty carried onto the species' flux at the
# SAME RIGIDITY — "the group's normalisation is uncertain, the frozen
# composition ratio is not"
leader_z = 8
sigma_b = gsf_r.flux(rigidity, 6) * (
gsf_r.error(rigidity, leader_z) / gsf_r.flux(rigidity, leader_z)
)
Above the species' top knot (8.3e4 GV for carbon) its flux is ratio x the
leader's, and the two agree to machine precision. Below it the flux comes
from the species' own frozen spline while the Jacobian still uses
ratio x the leader's, so they part company — 1.28x at 1e3 GV for carbon,
and between 0.1x and 2.5x across the group members. Recipe (b) is the one
that keeps a sub-leading band consistent with its group's; whichever you use,
say so. Note that both must be compared at fixed rigidity: at fixed energy
per nucleus, leader and member sit at different rigidities.
For the current sets these per-element parameters are in
data/<version>/subleading.dat (columns Z A norm slope). Without the file
(2017, 2019, 2025), or with a zero slope, the ratio is constant above
\(R_{\text{max}}\).
Note
The norm and slope are best-fit point estimates, and uncertainty
propagation uses the four group-leader blocks alone, so a sub-leading
flux carries the leader's uncertainty and nothing extra. The 2026 sets
do ship covariance blocks for all 28 charges — each species' own short
spline over its direct-data range, mostly pinned — which the model loads
but never propagates. The saturation constant is exposed as
globalsplinefit.model.SUBLEADING_SAT_LNR.
Uncertainty Quantification¶
Errors and covariances:
flux = gsf.flux(energy, "p")
error = gsf.error(energy, "p")
# Relative uncertainty
rel_error = error / flux
# Covariance matrix
cov_matrix = gsf.covariance("p", "He", energy)
Jacobian Access¶
The flux is linear in the fitted spline amplitudes, so a single Jacobian
carries the whole error propagation:
\(J_{ij} = \partial\,\text{flux}(E_i)\,/\,\partial\,a_j\) for the amplitudes
\(a_j\) of the group that target belongs to. error() is exactly
\(\sqrt{\mathrm{diag}(J\,C\,J^\mathsf{T})}\) with \(C\) the fitted amplitude
covariance — the Jacobian is what you need when you want something else:
a correlated band across energies, a derived quantity, or a covariance
between two groups.
jac = gsf.jacobian(energy, "p") # shape (len(energy), n_amplitudes)
cov = gsf.covariance("p", "p", energy) # flux covariance across energies
# uncertainty of a p + He sum, correlations included
import numpy as np
f_sum = gsf.flux(energy, "p") + gsf.flux(energy, "He")
var = (
np.diag(gsf.covariance("p", "p", energy))
+ np.diag(gsf.covariance("He", "He", energy))
+ 2 * np.diag(gsf.covariance("p", "He", energy))
)
err_sum = np.sqrt(var)
Adding the two errors in quadrature instead would ignore the p--He correlation, which the fit constrains.
Solar Modulation¶
The model is fitted as a local interstellar spectrum (LIS) and modulated to the top of the atmosphere with a force-field potential \(\phi(t)\).
# Local interstellar spectrum (no modulation)
flux_lis = gsf.flux(energy, "p", time_interval="LIS")
# Specific time period (YYYYMM format)
flux_2009 = gsf.flux(energy, "p", time_interval=(200901, 201001)) # end EXCLUSIVE: calendar year 2009
# Default: Solar Cycle 24 average (Dec 2008 - Dec 2019), NOT the LIS
flux_default = gsf.flux(energy, "p")
The potentials, and what "self-consistent" means¶
| Potential | Reference | Used by |
|---|---|---|
| Ghelfi–Maurin–Derome (GMD) φ(t) | Ghelfi:2016pcv |
2026.0, 2026.0-SIB23e, 2026.0-EPOSLHCR |
| Usoskin et al. 2017 φ(t) | Usoskin:2017cli |
2026.0-USO, 2025, 2019, 2017 |
- Ghelfi–Maurin–Derome (GMD) φ(t) — Neutron monitors and muon detectors for solar modulation studies: 2. φ time series. The GSF 2026 default; each current set ships its own monthly table as data/
/solar_modulation.dat. - Usoskin et al. 2017 φ(t) — Heliospheric modulation of cosmic rays during the neutron monitor era. Used by the 2026.0-USO variant and by the historical sets; bundled at the package data root as solar_modulation.dat.
Every current set is self-consistent: the model is modulated with exactly
the same potential model that was used to demodulate the data during the fit.
Each current set therefore ships its own monthly \(\phi(t)\) table
(data/<version>/solar_modulation.dat), while the historical sets use the
bundled Usoskin table at the package data root.
This is why the choice of potential matters less than it appears. Relative to the default, the Usoskin potential yields a 10--14% lower interstellar spectrum below 2 GV, with the two converging above ~10 GV — but because each LIS is paired with the potential it was derived with, the fluxes at Earth are almost identical even where the interstellar spectra differ. All higher-energy results are identical.
See the Solar Modulation tutorial for worked examples.
Geomagnetic Rigidity Cutoff¶
No cutoff is applied by default (rigidity_cutoff=None, i.e. 0 GV).
Pass a cutoff to suppress low-rigidity cosmic rays:
# Cutoff at 20 GV (smooth sigmoid transition by default, cutoff_width=1 GV;
# construct the model with cutoff_width=0.0 for a sharp Heaviside cutoff)
flux_cut = gsf.flux(energy, "p", rigidity_cutoff=20.0)
See the Rigidity Cutoff tutorial for more details.
Practical Notes¶
- Pass arrays, not loops. Every method is vectorized over the energy argument; calling it once with an array of 1000 energies is far cheaper than 1000 scalar calls. That is the only vectorization gain — there is nothing to batch over targets or versions.
- Memory: the Jacobian cache. Jacobians are cached per model instance and per energy grid. One model with a dense grid is fine, but many models (~10 or more) each holding a dense grid can add up to a substantial footprint. If that bites, change the access pattern rather than the grid: reuse one model instance instead of constructing many, and keep the set of distinct energy grids small.
- Energy range. Stay within the fitted range (~1 GeV -- 10^11 GeV per nucleus); the flux is zero below the first knot and extrapolated above the last.
- The default is modulated.
flux(energy, target)returns the Solar-Cycle-24 average at Earth, not the interstellar spectrum — passtime_interval="LIS"when you want the LIS.
Tutorials¶
See the tutorial gallery for detailed, worked examples.