nano.rust agrees with ROOT — on ROOT's own NanoAOD tutorial file
2026-06-21 — a screencast + an interop check: read the file from ROOT's df102
dimuon tutorial with pure-Rust nano.rust, reproduce the dimuon spectrum (the Z
peak shows up), and confirm the values match ROOT itself, bit for bit.
ROOT ships a canonical NanoAOD tutorial, df102_NanoAODDimuonAnalysis, whose
input is Run2012BC_DoubleMuParked_Muons.root — the famous CMS dimuon-spectrum
file (J/ψ, Υ, Z). It's a great interop target: if nano.rust reads that file and
agrees with ROOT, the pure-Rust I/O has nothing to hide.
(No player? Raw cast: demo-root-interop.cast.)
1. Inspect ROOT's file over HTTPS
nano inspect now takes a URL — no download, just byte-range metadata reads:
$ nano inspect "https://eospublic.cern.ch//eos/opendata/cms/derived-data/AOD2NanoAODOutreachTool/Run2012BC_DoubleMuParked_Muons.root" --insecure
TTree Events entries=61540413
nMuon u32
Muon_pt f32
Muon_eta f32
Muon_phi f32
Muon_mass f32
Muon_charge i32
61.5 M events; the muon branches ROOT's df102 uses.
2. Reproduce the df102 dimuon spectrum
dimuon_opendata streams events, pairs opposite-charge muons, and computes the
invariant mass — the same analysis as the tutorial:
$ dimuon_opendata "<that url>" 20000 --insecure
opposite_charge_pairs: 13743
z_window_60_120_gev: 2465
mass_histogram_gev:
0-20 6861 ################################
20-40 3155 ###############
40-60 1154 ######
60-80 445 ###
80-100 1867 ######### <-- the Z
100-120 153 #
bytes_fetched: 768178 / 2244449133
The Z peak stands out in the 80–100 GeV bin, and reading 20 000 events fetched 768 KB of the 2.2 GB file (0.034 %) — on-demand, nothing stored.

The dimuon spectrum, plotted in-process with kuva
via --plot (--features plot).
3. The interop check: nano.rust vs ROOT, same file
The point of the exercise. nano.rust reads over HTTPS (pure Rust, no ROOT); the locally-installed ROOT reads the same file over xrootd. First five events:
| entry | nano.rust (HTTPS) | ROOT (xrootd) |
|---|---|---|
| 0 | nMuon=2 [10.7637, 15.7365] |
nMuon=2 [10.7637, 15.7365] |
| 1 | nMuon=2 [10.5385, 16.3271] |
nMuon=2 [10.5385, 16.3271] |
| 2 | nMuon=1 [3.2753] |
nMuon=1 [3.27533] |
| 3 | nMuon=4 [11.4292, 17.6340, 9.6247, 3.5022] |
nMuon=4 [11.4292, 17.634, 9.62473, 3.50223] |
| 4 | nMuon=4 [3.2834, 3.6440, 32.9112, 23.7218] |
nMuon=4 [3.28344, 3.64401, 32.9112, 23.7218] |
Identical (modulo display rounding). A pure-Rust reader over an HTTPS byte-range
and ROOT over xrootd return the same bytes decoded the same way, on ROOT's own
canonical example file. (Reproduce locally with scripts/root_crosscheck.sh.)
Why it matters
This is the I/O layer earning trust the only way that counts: against the
reference implementation, on its own data. The same nano-io reader powers the
spec → kernel → workflow pipeline, so everything above it inherits a read path
that ROOT itself agrees with — while staying pure Rust, remote-on-demand, and
ROOT-free at runtime.