Example of reading KM3NeT Open Data
This notebook demonstrates read and fill histograms from the KM3NeT/ORCA 433 kt-y oscillations data release using KM3NeT's tools.
Setup and Dependencies
First, we load the necessary packages:
KM3io: For reading KM3NeT data formatsNuFlux: For neutrino flux calculationsKM3OpenOsc: For neutrino oscillation calculations
begin
using KM3io
using NuFlux
using KM3OpenOsc
end
Downloading Open Dataset
ORCA 433 kt-y open dataset can be downloaded from the following dataverse link and later unzipped to have access to the files needed. After unzipping the file into a directory, 5 files will be found including a README among them where the content of each file is carefully explained.
Loading Data Files into the notebook
Here we define paths to two key files:
PATH_UNZIPPED_DATASET: Path to unzipped open datasetBINDEF: JSON file containing binning definitions for the analysisOSCFILE: ROOT file containing data from KM3NeT/ORCA
begin
PATH_UNZIPPED_DATASET = "../../unzipped_ORCA433kt-y_open_dataset/" # to change as a function of the user
BINDEF = PATH_UNZIPPED_DATASET * "bins_433kt-y_v0.5.json"
OSCFILE = PATH_UNZIPPED_DATASET * "ORCA6_433kt-y_opendata_v0.5.root"
end
"../../unzipped_ORCA433kt-y_open_dataset/ORCA6_433kt-y_opendata_v0.5.root"
Reading the Oscillation Data File
To read the file we use KM3io.OSCFile which is a function that directly identifies the type of file and what is inside
We load the oscillation data file and extract three data samples:
nu: Neutrino Monte Carlo eventsdata: Experimental data eventsmuons: Atmospheric muon background events
f = KM3io.OSCFile(OSCFILE)
OSCFile{OscOpenDataTree of Neutrinos (592099 events), OscOpenDataTree of Data (900 events), OscOpenDataTree of Muons (900 events)}
begin
nu = f.osc_opendata_nu
data = f.osc_opendata_data
muons = f.osc_opendata_muons
end
OscOpenDataTree of Muons (900 events)
Creating Histogram Containers
We initialize empty 2D histogram containers using the binning definition file:
hn: For simulated neutrino eventshd: For real experimental datahm: For simulated atmospheric muon events
begin
hn = create_histograms(BINDEF)
hd = create_histograms(BINDEF)
hm = create_histograms(BINDEF); nothing
end
fieldnames(typeof(hn))
(:hists_true, :hists_reco)
keys(hn.hists_reco)
KeySet for a Dict{String, Hist2D} with 4 entries. Keys:
"reco"
"recoShowers"
"recoHighPurityTracks"
"recoLowPurityTracks"
hn.hists_reco["recoShowers"]
- edges: ([2.0, 2.95752, 4.37345, 5.3183, 6.46727, 7.86447, 9.56352, 11.6296, 14.1421, 17.1974, 20.9128, 25.4308, 30.9249, 55.6102, 100.0, 1000.0], [-1.0, -0.9, -0.8, -0.7, -0.6, -0.5, -0.3999999999999999, -0.29999999999999993, -0.19999999999999996, -0.09999999999999998 … 0.1, 0.2, 0.30000000000000004, 0.4, 0.5, 0.6000000000000001, 0.7000000000000001, 0.8, 0.9, 1.0])
- bin counts: [0.0 0.0 … 0.0 0.0; 0.0 0.0 … 0.0 0.0; … ; 0.0 0.0 … 0.0 0.0; 0.0 0.0 … 0.0 0.0]
- maximum count: 0.0
- total count: 0.0
Setting Oscillation Parameters
We define the oscillation parameters using the best fit values from the standard oscillations analysis from ORCA 433 kt-y paper JHEP10(2024)206:
dm_21,dm_31: Mass-squared differences in eV²theta_12,theta_23,theta_13: Mixing anglesdcp: CP-violating phase
These parameters are used to calculate the oscillation matrices (U and H). It uses Neurthino.jl behind the scenes.
begin
BF = Dict("dm_21" => 7.42e-5,
"dm_31" => 2.18e-3,
"theta_12" => deg2rad(33.45),
"theta_23" => deg2rad(45.57299599919429),
"theta_13" => deg2rad(8.62),
"dcp" => deg2rad(230))
U,H = get_oscillation_matrices(BF)
end
(ComplexF64[0.8249422516589652 + 0.0im 0.5449826827686066 + 0.0im -0.09634131252970905 + 0.1148151053225223im; -0.3284406729882627 + 0.06841342277740595im 0.6219810005635141 + 0.04519604930846363im 0.7060759732322109 + 0.0im; 0.449910833254889 + 0.06705856737677285im -0.5586843920183072 + 0.04430098940637267im 0.6920928861894314 + 0.0im], ComplexF64[-0.0007514 + 0.0im, -0.0006772000000000001 + 0.0im, 0.0014286000000000001 + 0.0im])
Loading Neutrino Flux Model
We load the Honda Frejus site flux model, which provides the atmospheric neutrino flux predictions needed for the analysis. Any Honda formatted table can be read using this function. It uses NuFlux.jl behind the scenes.
begin
NUFLUX_PATH = split(Base.pathof(NuFlux), "src")[1]
FLUX_DATA_DIR = joinpath(NUFLUX_PATH, "data")
flux_path = joinpath(FLUX_DATA_DIR, "frj-nu-20-01-000.d") # Get flux of Honda Frejus site from NuFlux
flux_dict = get_flux_dict(flux_path)
end
Dict{Int32, NuFlux.FluxTable} with 4 entries:
-12 => FluxTable([-1.0, -0.9, -0.8, -0.7, -0.6, -0.5, -0.4, -0.3, -0.2, -0.1 … 0.1,…
12 => FluxTable([-1.0, -0.9, -0.8, -0.7, -0.6, -0.5, -0.4, -0.3, -0.2, -0.1 … 0.1,…
14 => FluxTable([-1.0, -0.9, -0.8, -0.7, -0.6, -0.5, -0.4, -0.3, -0.2, -0.1 … 0.1,…
-14 => FluxTable([-1.0, -0.9, -0.8, -0.7, -0.6, -0.5, -0.4, -0.3, -0.2, -0.1 … 0.1,…
Filling Histograms with Events
We populate our histogram containers with events:
Neutrinos with flux weighting and oscillation effects applied
Atmospheric muons
Experimental data
There is an optional argument which is livetime that can be used to scale up or down montecarlo
begin
fill_response!(hn, nu, flux_dict, U, H; oscillations=true) # fill neutrinos ,need flux, oscillation parameters
fill_response!(hm, muons)
fill_response!(hd, data)
end
hn.hists_true["muon_cc_nu"]
- edges: ([1.0, 1.2589254117941673, 1.5848931924611136, 1.9952623149688797, 2.51188643150958, 3.1622776601683795, 3.9810717055349727, 5.011872336272723, 6.309573444801933, 7.943282347242818 … 1258.925411794168, 1584.8931924611143, 1995.2623149688823, 2511.886431509581, 3162.2776601683804, 3981.071705534977, 5011.872336272724, 6309.573444801938, 7943.28234724283, 10000.0], [-1.0, -0.975, -0.95, -0.925, -0.9, -0.875, -0.85, -0.825, -0.8, -0.775 … 0.775, 0.8, 0.8250000000000001, 0.8500000000000001, 0.875, 0.9, 0.925, 0.9500000000000001, 0.9750000000000001, 1.0])
- bin counts: [0.04085472213947171 0.004977305716097258 … 0.0 0.0; 0.12829694773170977 0.008506359166404558 … 0.0 0.0; … ; 0.05740980482210812 0.057535779183707024 … 0.00011768083104523938 0.0; 0.037587732908178864 0.0380479135922043 … 0.0 0.0]
- maximum count: 6.423147039212604
- total count: 2915.5498586995577
hn.hists_reco["recoShowers"]
- edges: ([2.0, 2.95752, 4.37345, 5.3183, 6.46727, 7.86447, 9.56352, 11.6296, 14.1421, 17.1974, 20.9128, 25.4308, 30.9249, 55.6102, 100.0, 1000.0], [-1.0, -0.9, -0.8, -0.7, -0.6, -0.5, -0.3999999999999999, -0.29999999999999993, -0.19999999999999996, -0.09999999999999998 … 0.1, 0.2, 0.30000000000000004, 0.4, 0.5, 0.6000000000000001, 0.7000000000000001, 0.8, 0.9, 1.0])
- bin counts: [3.191829228181372 4.369504546333975 … 0.0 0.0; 11.66977246088045 13.448746078325804 … 0.0 0.0; … ; 19.297699494383636 15.513395665284698 … 0.0 0.0; 12.833286366966924 12.16150406073552 … 0.0 0.0]
- maximum count: 28.520710756581217
- total count: 2032.1392944893719
Exporting results to HDF5
You can easily export the filled histograms to HDF5.
Additionally it is possible to export the full response to a single hdf5 file
begin
export_histograms_hdf5(hn, "neutrino_histograms_from_testdata.h5") # You can easily export the filled histograms to hdf5
h5f = build_HDF5_file("responses_to_file.h5") # Create h5 file with same structure as responses bins
fill_HDF5_file!(h5f, nu, "neutrinos") # Completely export the response as a table in an hdf5 file at a given path
fill_HDF5_file!(h5f, muons, "muons")
fill_HDF5_file!(h5f, data, "data")
close(h5f)
end
Visualizing the Reconstruction Channels
First, we load the necessary packages:
CairoMakie: For plottingFHist: For histogram operations
This plot shows three different reconstruction channels:
High Purity Tracks: Events classified with track topology with low atmospheric muon contamination
Low Purity Tracks: Events classified with track topology with higher atmospheric muon contamination
Showers: Events classified with a shower topology
For each channel, we display:
Stacked histograms of neutrino and muon Monte Carlo events
Data points with error bars
The x-axis represents the cosine of the zenith angle (cos θ), where -1 means upgoing events (through the Earth) and 0 means horizontal events.
using CairoMakie, FHist
with_theme(ATLASTHEME) do
reco_types = ["recoHighPurityTracks", "recoLowPurityTracks", "recoShowers"]
f = Figure(size = (1200, 400))
for (i, reco_type) in enumerate(reco_types)
neutrinos = project(hn.hists_reco[reco_type],:y)
muons = project(hm.hists_reco[reco_type],:y)
data = project(hd.hists_reco[reco_type],:y)
ax = Axis(f[1, i],
xlabel = "Cos theta",
ylabel = i == 1 ? "Event count" : "",
title = reco_type)
p = stackedhist!(ax, [neutrinos, muons]; error_color=Pattern('/'))
xlims!(ax, (-1, 0))
scatter!(ax, bincenters(data), bincounts(data),
color = :black,
markersize = 10)
if i == 3
labels = ["Neutrinos", "Muons", "Data"]
elements = [
[PolyElement(polycolor = p.attributes.color[][j]) for j in 1:2]...,
MarkerElement(color = :black, marker = :circle, markersize = 8)
]
end
end
f
end
Examining True Event Categories
This plot shows the distribution of true Monte Carlo event categories:
muon_cc_nu: Charged-current muon neutrino events
muon_cc_nub: Charged-current muon antineutrino events
with_theme(ATLASTHEME) do
reco_types = ["muon_cc_nu", "muon_cc_nub"]
f = Figure(size = (1200, 400))
# Loop through each reconstruction type
for (i, reco_type) in enumerate(reco_types)
muon_cc_nu = project(hn.hists_true[reco_type],:y)
muon_cc_nub = project(hm.hists_true[reco_type],:y)
ax = Axis(f[1, i],
xlabel = "Cos theta",
ylabel = i == 1 ? "Event count" : "",
title = reco_type)
p = stackedhist!(ax, [muon_cc_nu, muon_cc_nub]; error_color=Pattern('/'))
xlims!(ax, (-1, 0))
if i == 3
labels = ["muon_cc_nu", "muon_cc_nub"]
elements = [
[PolyElement(polycolor = p.attributes.color[][j]) for j in 1:2]...,
MarkerElement(color = :black, marker = :circle, markersize = 8)
]
end
end
f
end
Built with Julia 1.11.2 and
CairoMakie 0.13.10FHist 0.11.11
KM3OpenOsc 0.1.9
KM3io 0.18.7
NuFlux 0.1.6