diff --git a/.gitignore b/.gitignore
index 8877fff55..a374c129a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,2 +1,3 @@
build
test_runs
+/src/MicroBooNE/scriptholder/
diff --git a/data/MicroBooNE/CC0pi_2025/2D/Extracted2DCrossSection_MicroBooNE_CC0PiSelection_BinningScheme1.root b/data/MicroBooNE/CC0pi_2025/2D/Extracted2DCrossSection_MicroBooNE_CC0PiSelection_BinningScheme1.root
new file mode 100644
index 000000000..a6b53a160
Binary files /dev/null and b/data/MicroBooNE/CC0pi_2025/2D/Extracted2DCrossSection_MicroBooNE_CC0PiSelection_BinningScheme1.root differ
diff --git a/data/MicroBooNE/CC0pi_2025/2D/bin_defsCC0pi_2025.txt b/data/MicroBooNE/CC0pi_2025/2D/bin_defsCC0pi_2025.txt
new file mode 100644
index 000000000..af92e66b0
--- /dev/null
+++ b/data/MicroBooNE/CC0pi_2025/2D/bin_defsCC0pi_2025.txt
@@ -0,0 +1,85 @@
+Muon2D
+stv_tree
+44
+0 0 "mc_is_cc0pi_signal && mc_p3_mu.Mag() >= 0.100 && mc_p3_mu.Mag() < 0.240 && mc_p3_mu.CosTheta() >= -1.000 && mc_p3_mu.CosTheta() < -0.550"
+0 0 "mc_is_cc0pi_signal && mc_p3_mu.Mag() >= 0.100 && mc_p3_mu.Mag() < 0.240 && mc_p3_mu.CosTheta() >= -0.550 && mc_p3_mu.CosTheta() < 0.000"
+0 0 "mc_is_cc0pi_signal && mc_p3_mu.Mag() >= 0.100 && mc_p3_mu.Mag() < 0.240 && mc_p3_mu.CosTheta() >= 0.000 && mc_p3_mu.CosTheta() < 0.450"
+0 0 "mc_is_cc0pi_signal && mc_p3_mu.Mag() >= 0.100 && mc_p3_mu.Mag() < 0.240 && mc_p3_mu.CosTheta() >= 0.450 && mc_p3_mu.CosTheta() < 1.000"
+0 0 "mc_is_cc0pi_signal && mc_p3_mu.Mag() >= 0.240 && mc_p3_mu.Mag() < 0.300 && mc_p3_mu.CosTheta() >= -1.000 && mc_p3_mu.CosTheta() < -0.550"
+0 0 "mc_is_cc0pi_signal && mc_p3_mu.Mag() >= 0.240 && mc_p3_mu.Mag() < 0.300 && mc_p3_mu.CosTheta() >= -0.550 && mc_p3_mu.CosTheta() < -0.250"
+0 0 "mc_is_cc0pi_signal && mc_p3_mu.Mag() >= 0.240 && mc_p3_mu.Mag() < 0.300 && mc_p3_mu.CosTheta() >= -0.250 && mc_p3_mu.CosTheta() < 0.000"
+0 0 "mc_is_cc0pi_signal && mc_p3_mu.Mag() >= 0.240 && mc_p3_mu.Mag() < 0.300 && mc_p3_mu.CosTheta() >= 0.000 && mc_p3_mu.CosTheta() < 0.250"
+0 0 "mc_is_cc0pi_signal && mc_p3_mu.Mag() >= 0.240 && mc_p3_mu.Mag() < 0.300 && mc_p3_mu.CosTheta() >= 0.250 && mc_p3_mu.CosTheta() < 0.450"
+0 0 "mc_is_cc0pi_signal && mc_p3_mu.Mag() >= 0.240 && mc_p3_mu.Mag() < 0.300 && mc_p3_mu.CosTheta() >= 0.450 && mc_p3_mu.CosTheta() < 0.700"
+0 0 "mc_is_cc0pi_signal && mc_p3_mu.Mag() >= 0.240 && mc_p3_mu.Mag() < 0.300 && mc_p3_mu.CosTheta() >= 0.700 && mc_p3_mu.CosTheta() < 1.000"
+0 0 "mc_is_cc0pi_signal && mc_p3_mu.Mag() >= 0.300 && mc_p3_mu.Mag() < 0.380 && mc_p3_mu.CosTheta() >= -1.000 && mc_p3_mu.CosTheta() < -0.400"
+0 0 "mc_is_cc0pi_signal && mc_p3_mu.Mag() >= 0.300 && mc_p3_mu.Mag() < 0.380 && mc_p3_mu.CosTheta() >= -0.400 && mc_p3_mu.CosTheta() < -0.100"
+0 0 "mc_is_cc0pi_signal && mc_p3_mu.Mag() >= 0.300 && mc_p3_mu.Mag() < 0.380 && mc_p3_mu.CosTheta() >= -0.100 && mc_p3_mu.CosTheta() < 0.100"
+0 0 "mc_is_cc0pi_signal && mc_p3_mu.Mag() >= 0.300 && mc_p3_mu.Mag() < 0.380 && mc_p3_mu.CosTheta() >= 0.100 && mc_p3_mu.CosTheta() < 0.350"
+0 0 "mc_is_cc0pi_signal && mc_p3_mu.Mag() >= 0.300 && mc_p3_mu.Mag() < 0.380 && mc_p3_mu.CosTheta() >= 0.350 && mc_p3_mu.CosTheta() < 0.500"
+0 0 "mc_is_cc0pi_signal && mc_p3_mu.Mag() >= 0.300 && mc_p3_mu.Mag() < 0.380 && mc_p3_mu.CosTheta() >= 0.500 && mc_p3_mu.CosTheta() < 0.700"
+0 0 "mc_is_cc0pi_signal && mc_p3_mu.Mag() >= 0.300 && mc_p3_mu.Mag() < 0.380 && mc_p3_mu.CosTheta() >= 0.700 && mc_p3_mu.CosTheta() < 0.850"
+0 0 "mc_is_cc0pi_signal && mc_p3_mu.Mag() >= 0.300 && mc_p3_mu.Mag() < 0.380 && mc_p3_mu.CosTheta() >= 0.850 && mc_p3_mu.CosTheta() < 1.000"
+0 0 "mc_is_cc0pi_signal && mc_p3_mu.Mag() >= 0.380 && mc_p3_mu.Mag() < 0.480 && mc_p3_mu.CosTheta() >= -1.000 && mc_p3_mu.CosTheta() < 0.000"
+0 0 "mc_is_cc0pi_signal && mc_p3_mu.Mag() >= 0.380 && mc_p3_mu.Mag() < 0.480 && mc_p3_mu.CosTheta() >= 0.000 && mc_p3_mu.CosTheta() < 0.500"
+0 0 "mc_is_cc0pi_signal && mc_p3_mu.Mag() >= 0.380 && mc_p3_mu.Mag() < 0.480 && mc_p3_mu.CosTheta() >= 0.500 && mc_p3_mu.CosTheta() < 0.650"
+0 0 "mc_is_cc0pi_signal && mc_p3_mu.Mag() >= 0.380 && mc_p3_mu.Mag() < 0.480 && mc_p3_mu.CosTheta() >= 0.650 && mc_p3_mu.CosTheta() < 0.800"
+0 0 "mc_is_cc0pi_signal && mc_p3_mu.Mag() >= 0.380 && mc_p3_mu.Mag() < 0.480 && mc_p3_mu.CosTheta() >= 0.800 && mc_p3_mu.CosTheta() < 0.920"
+0 0 "mc_is_cc0pi_signal && mc_p3_mu.Mag() >= 0.380 && mc_p3_mu.Mag() < 0.480 && mc_p3_mu.CosTheta() >= 0.920 && mc_p3_mu.CosTheta() < 1.000"
+0 0 "mc_is_cc0pi_signal && mc_p3_mu.Mag() >= 0.480 && mc_p3_mu.Mag() < 0.700 && mc_p3_mu.CosTheta() >= -1.000 && mc_p3_mu.CosTheta() < 0.500"
+0 0 "mc_is_cc0pi_signal && mc_p3_mu.Mag() >= 0.480 && mc_p3_mu.Mag() < 0.700 && mc_p3_mu.CosTheta() >= 0.500 && mc_p3_mu.CosTheta() < 0.650"
+0 0 "mc_is_cc0pi_signal && mc_p3_mu.Mag() >= 0.480 && mc_p3_mu.Mag() < 0.700 && mc_p3_mu.CosTheta() >= 0.650 && mc_p3_mu.CosTheta() < 0.800"
+0 0 "mc_is_cc0pi_signal && mc_p3_mu.Mag() >= 0.480 && mc_p3_mu.Mag() < 0.700 && mc_p3_mu.CosTheta() >= 0.800 && mc_p3_mu.CosTheta() < 0.875"
+0 0 "mc_is_cc0pi_signal && mc_p3_mu.Mag() >= 0.480 && mc_p3_mu.Mag() < 0.700 && mc_p3_mu.CosTheta() >= 0.875 && mc_p3_mu.CosTheta() < 0.950"
+0 0 "mc_is_cc0pi_signal && mc_p3_mu.Mag() >= 0.480 && mc_p3_mu.Mag() < 0.700 && mc_p3_mu.CosTheta() >= 0.950 && mc_p3_mu.CosTheta() < 1.000"
+0 0 "mc_is_cc0pi_signal && mc_p3_mu.Mag() >= 0.700 && mc_p3_mu.Mag() < 0.850 && mc_p3_mu.CosTheta() >= -1.000 && mc_p3_mu.CosTheta() < 0.875"
+0 0 "mc_is_cc0pi_signal && mc_p3_mu.Mag() >= 0.700 && mc_p3_mu.Mag() < 0.850 && mc_p3_mu.CosTheta() >= 0.875 && mc_p3_mu.CosTheta() < 0.950"
+0 0 "mc_is_cc0pi_signal && mc_p3_mu.Mag() >= 0.700 && mc_p3_mu.Mag() < 0.850 && mc_p3_mu.CosTheta() >= 0.950 && mc_p3_mu.CosTheta() < 1.000"
+0 0 "mc_is_cc0pi_signal && mc_p3_mu.Mag() >= 0.850 && mc_p3_mu.Mag() < 2.000 && mc_p3_mu.CosTheta() >= -1.000 && mc_p3_mu.CosTheta() < 0.900"
+0 0 "mc_is_cc0pi_signal && mc_p3_mu.Mag() >= 0.850 && mc_p3_mu.Mag() < 2.000 && mc_p3_mu.CosTheta() >= 0.900 && mc_p3_mu.CosTheta() < 0.950"
+0 0 "mc_is_cc0pi_signal && mc_p3_mu.Mag() >= 0.850 && mc_p3_mu.Mag() < 2.000 && mc_p3_mu.CosTheta() >= 0.950 && mc_p3_mu.CosTheta() < 1.000"
+1 -1 "category == 0"
+1 -1 "category == 5"
+1 -1 "category == 7"
+1 -1 "category == 8"
+1 -1 "category == 9"
+1 -1 "category == 10"
+1 -1 "category == 11"
+37
+0 0 "sel_CC0pi && sel_muon_contained && p3_mu.Mag() >= 0.100 && p3_mu.Mag() < 0.240 && p3_mu.CosTheta() >= -1.000 && p3_mu.CosTheta() < -0.550"
+0 0 "sel_CC0pi && sel_muon_contained && p3_mu.Mag() >= 0.100 && p3_mu.Mag() < 0.240 && p3_mu.CosTheta() >= -0.550 && p3_mu.CosTheta() < 0.000"
+0 0 "sel_CC0pi && sel_muon_contained && p3_mu.Mag() >= 0.100 && p3_mu.Mag() < 0.240 && p3_mu.CosTheta() >= 0.000 && p3_mu.CosTheta() < 0.450"
+0 0 "sel_CC0pi && sel_muon_contained && p3_mu.Mag() >= 0.100 && p3_mu.Mag() < 0.240 && p3_mu.CosTheta() >= 0.450 && p3_mu.CosTheta() < 1.000"
+0 0 "sel_CC0pi && sel_muon_contained && p3_mu.Mag() >= 0.240 && p3_mu.Mag() < 0.300 && p3_mu.CosTheta() >= -1.000 && p3_mu.CosTheta() < -0.550"
+0 0 "sel_CC0pi && sel_muon_contained && p3_mu.Mag() >= 0.240 && p3_mu.Mag() < 0.300 && p3_mu.CosTheta() >= -0.550 && p3_mu.CosTheta() < -0.250"
+0 0 "sel_CC0pi && sel_muon_contained && p3_mu.Mag() >= 0.240 && p3_mu.Mag() < 0.300 && p3_mu.CosTheta() >= -0.250 && p3_mu.CosTheta() < 0.000"
+0 0 "sel_CC0pi && sel_muon_contained && p3_mu.Mag() >= 0.240 && p3_mu.Mag() < 0.300 && p3_mu.CosTheta() >= 0.000 && p3_mu.CosTheta() < 0.250"
+0 0 "sel_CC0pi && sel_muon_contained && p3_mu.Mag() >= 0.240 && p3_mu.Mag() < 0.300 && p3_mu.CosTheta() >= 0.250 && p3_mu.CosTheta() < 0.450"
+0 0 "sel_CC0pi && sel_muon_contained && p3_mu.Mag() >= 0.240 && p3_mu.Mag() < 0.300 && p3_mu.CosTheta() >= 0.450 && p3_mu.CosTheta() < 0.700"
+0 0 "sel_CC0pi && sel_muon_contained && p3_mu.Mag() >= 0.240 && p3_mu.Mag() < 0.300 && p3_mu.CosTheta() >= 0.700 && p3_mu.CosTheta() < 1.000"
+0 0 "sel_CC0pi && sel_muon_contained && p3_mu.Mag() >= 0.300 && p3_mu.Mag() < 0.380 && p3_mu.CosTheta() >= -1.000 && p3_mu.CosTheta() < -0.400"
+0 0 "sel_CC0pi && sel_muon_contained && p3_mu.Mag() >= 0.300 && p3_mu.Mag() < 0.380 && p3_mu.CosTheta() >= -0.400 && p3_mu.CosTheta() < -0.100"
+0 0 "sel_CC0pi && sel_muon_contained && p3_mu.Mag() >= 0.300 && p3_mu.Mag() < 0.380 && p3_mu.CosTheta() >= -0.100 && p3_mu.CosTheta() < 0.100"
+0 0 "sel_CC0pi && sel_muon_contained && p3_mu.Mag() >= 0.300 && p3_mu.Mag() < 0.380 && p3_mu.CosTheta() >= 0.100 && p3_mu.CosTheta() < 0.350"
+0 0 "sel_CC0pi && sel_muon_contained && p3_mu.Mag() >= 0.300 && p3_mu.Mag() < 0.380 && p3_mu.CosTheta() >= 0.350 && p3_mu.CosTheta() < 0.500"
+0 0 "sel_CC0pi && sel_muon_contained && p3_mu.Mag() >= 0.300 && p3_mu.Mag() < 0.380 && p3_mu.CosTheta() >= 0.500 && p3_mu.CosTheta() < 0.700"
+0 0 "sel_CC0pi && sel_muon_contained && p3_mu.Mag() >= 0.300 && p3_mu.Mag() < 0.380 && p3_mu.CosTheta() >= 0.700 && p3_mu.CosTheta() < 0.850"
+0 0 "sel_CC0pi && sel_muon_contained && p3_mu.Mag() >= 0.300 && p3_mu.Mag() < 0.380 && p3_mu.CosTheta() >= 0.850 && p3_mu.CosTheta() < 1.000"
+0 0 "sel_CC0pi && sel_muon_contained && p3_mu.Mag() >= 0.380 && p3_mu.Mag() < 0.480 && p3_mu.CosTheta() >= -1.000 && p3_mu.CosTheta() < 0.000"
+0 0 "sel_CC0pi && sel_muon_contained && p3_mu.Mag() >= 0.380 && p3_mu.Mag() < 0.480 && p3_mu.CosTheta() >= 0.000 && p3_mu.CosTheta() < 0.500"
+0 0 "sel_CC0pi && sel_muon_contained && p3_mu.Mag() >= 0.380 && p3_mu.Mag() < 0.480 && p3_mu.CosTheta() >= 0.500 && p3_mu.CosTheta() < 0.650"
+0 0 "sel_CC0pi && sel_muon_contained && p3_mu.Mag() >= 0.380 && p3_mu.Mag() < 0.480 && p3_mu.CosTheta() >= 0.650 && p3_mu.CosTheta() < 0.800"
+0 0 "sel_CC0pi && sel_muon_contained && p3_mu.Mag() >= 0.380 && p3_mu.Mag() < 0.480 && p3_mu.CosTheta() >= 0.800 && p3_mu.CosTheta() < 0.920"
+0 0 "sel_CC0pi && sel_muon_contained && p3_mu.Mag() >= 0.380 && p3_mu.Mag() < 0.480 && p3_mu.CosTheta() >= 0.920 && p3_mu.CosTheta() < 1.000"
+0 0 "sel_CC0pi && sel_muon_contained && p3_mu.Mag() >= 0.480 && p3_mu.Mag() < 0.700 && p3_mu.CosTheta() >= -1.000 && p3_mu.CosTheta() < 0.500"
+0 0 "sel_CC0pi && sel_muon_contained && p3_mu.Mag() >= 0.480 && p3_mu.Mag() < 0.700 && p3_mu.CosTheta() >= 0.500 && p3_mu.CosTheta() < 0.650"
+0 0 "sel_CC0pi && sel_muon_contained && p3_mu.Mag() >= 0.480 && p3_mu.Mag() < 0.700 && p3_mu.CosTheta() >= 0.650 && p3_mu.CosTheta() < 0.800"
+0 0 "sel_CC0pi && sel_muon_contained && p3_mu.Mag() >= 0.480 && p3_mu.Mag() < 0.700 && p3_mu.CosTheta() >= 0.800 && p3_mu.CosTheta() < 0.875"
+0 0 "sel_CC0pi && sel_muon_contained && p3_mu.Mag() >= 0.480 && p3_mu.Mag() < 0.700 && p3_mu.CosTheta() >= 0.875 && p3_mu.CosTheta() < 0.950"
+0 0 "sel_CC0pi && sel_muon_contained && p3_mu.Mag() >= 0.480 && p3_mu.Mag() < 0.700 && p3_mu.CosTheta() >= 0.950 && p3_mu.CosTheta() < 1.000"
+0 0 "sel_CC0pi && sel_muon_contained && p3_mu.Mag() >= 0.700 && p3_mu.Mag() < 0.850 && p3_mu.CosTheta() >= -1.000 && p3_mu.CosTheta() < 0.875"
+0 0 "sel_CC0pi && sel_muon_contained && p3_mu.Mag() >= 0.700 && p3_mu.Mag() < 0.850 && p3_mu.CosTheta() >= 0.875 && p3_mu.CosTheta() < 0.950"
+0 0 "sel_CC0pi && sel_muon_contained && p3_mu.Mag() >= 0.700 && p3_mu.Mag() < 0.850 && p3_mu.CosTheta() >= 0.950 && p3_mu.CosTheta() < 1.000"
+0 0 "sel_CC0pi && sel_muon_contained && p3_mu.Mag() >= 0.850 && p3_mu.Mag() < 2.000 && p3_mu.CosTheta() >= -1.000 && p3_mu.CosTheta() < 0.900"
+0 0 "sel_CC0pi && sel_muon_contained && p3_mu.Mag() >= 0.850 && p3_mu.Mag() < 2.000 && p3_mu.CosTheta() >= 0.900 && p3_mu.CosTheta() < 0.950"
+0 0 "sel_CC0pi && sel_muon_contained && p3_mu.Mag() >= 0.850 && p3_mu.Mag() < 2.000 && p3_mu.CosTheta() >= 0.950 && p3_mu.CosTheta() < 1.000"
diff --git a/src/FCN/SampleList.cxx b/src/FCN/SampleList.cxx
index 0eab78a45..3332912bb 100644
--- a/src/FCN/SampleList.cxx
+++ b/src/FCN/SampleList.cxx
@@ -186,6 +186,8 @@
#include "MicroBooNE_CC1MuNp_XSec_1D_nu.h"
#include "MicroBooNE_CC1ENp_XSec_1D_nu.h"
#include "MicroBooNE_CCInc_XSec_2DPcos_nu.h"
+#include "MicroBooNE_BNB_NumuCC0Pi_2025_XSec_nu_2D.h"
+
#endif
#ifdef MINERvA_ENABLED
@@ -1146,7 +1148,9 @@ MeasurementBase *CreateSample(nuiskey samplekey) {
!name.compare("MicroBooNE_CC1Mu1p_XSec_1DECal_nu") ||
!name.compare("MicroBooNE_CC1Mu1p_XSec_1DEQE_nu")) {
return (new MicroBooNE_CC1Mu1p_XSec_1D_nu(samplekey));
- } else
+ }else if (!name.compare("MicroBooNE_BNB_NumuCC0Pi_2025_XSec_nu_2D")){
+ return (new MicroBooNE_BNB_NumuCC0Pi_2025_XSec_nu_2D(samplekey));
+ }else
#endif
#ifdef MINERvA_ENABLED
diff --git a/src/MicroBooNE/CMakeLists.txt b/src/MicroBooNE/CMakeLists.txt
index 71bc689f8..652b54906 100644
--- a/src/MicroBooNE/CMakeLists.txt
+++ b/src/MicroBooNE/CMakeLists.txt
@@ -22,6 +22,7 @@ set(MicroBooNE_Impl_Files
MicroBooNE_CC1ENp_XSec_1D_nu.cxx
MicroBooNE_CC1Mu2p_XSec_1D_nu.cxx
MicroBooNE_CC1Mu1p_XSec_1D_nu.cxx
+ MicroBooNE_BNB_NumuCC0Pi_2025_XSec_nu_2D.cxx
MicroBooNE_SignalDef.cxx
)
@@ -30,6 +31,7 @@ set(MicroBooNE_Hdr_Files
MicroBooNE_CC1MuNp_XSec_1D_nu.h
MicroBooNE_CC1Mu2p_XSec_1D_nu.h
MicroBooNE_CC1Mu1p_XSec_1D_nu.h
+ MicroBooNE_BNB_NumuCC0Pi_2025_XSec_nu_2D.h
MicroBooNE_SignalDef.h
)
@@ -41,4 +43,4 @@ install(TARGETS MicroBooNE
LIBRARY DESTINATION lib/
PUBLIC_HEADER DESTINATION include)
-add_library(NUIS::MicroBooNE ALIAS MicroBooNE)
\ No newline at end of file
+add_library(NUIS::MicroBooNE ALIAS MicroBooNE)
diff --git a/src/MicroBooNE/MicroBooNE_BNB_NumuCC0Pi_2025_XSec_nu_2D.cxx b/src/MicroBooNE/MicroBooNE_BNB_NumuCC0Pi_2025_XSec_nu_2D.cxx
new file mode 100644
index 000000000..3080d5317
--- /dev/null
+++ b/src/MicroBooNE/MicroBooNE_BNB_NumuCC0Pi_2025_XSec_nu_2D.cxx
@@ -0,0 +1,414 @@
+// Copyright 2016 L. Pickering, P Stowell, R. Terri, C. Wilkinson, C. Wret
+
+/*******************************************************************************
+* This file is part of NUISANCE.
+*
+* NUISANCE is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published by
+* the Free Software Foundation, either version 3 of the License, or
+* (at your option) any later version.
+*
+* NUISANCE is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with NUISANCE. If not, see .
+*******************************************************************************/
+
+#include
+#include
+
+#include "MicroBooNE_BNB_NumuCC0Pi_2025_XSec_nu_2D.h"
+#include "MicroBooNE_SignalDef.h"
+#include "TH2D.h"
+#include "TMatrixD.h"
+
+namespace {
+ constexpr int MASS_NUMBER_40AR = 40;
+ constexpr int PROTON = 2212;
+ constexpr int MU_MINUS = 13;
+
+ constexpr double TARGET_MASS = 37.215526; // 40Ar, GeV
+ constexpr double NEUTRON_MASS = 0.93956541; // GeV
+ constexpr double PROTON_MASS = 0.93827208; // GeV
+ constexpr double MUON_MASS = 0.10565837; // GeV
+ constexpr double BINDING_ENERGY = 0.02478; // 40Ar, GeV
+
+/*
+ void compute_stvs( const TVector3& p3mu, const TVector3& p3p,
+ double& delta_pT, double& delta_phiT, double& delta_alphaT,
+ double& delta_pL, double& pn, double& delta_pTx, double& delta_pTy )
+ {
+ delta_pT = (p3mu + p3p).Perp();
+
+ delta_phiT = std::acos( (-p3mu.X()*p3p.X() - p3mu.Y()*p3p.Y())
+ / (p3mu.XYvector().Mod() * p3p.XYvector().Mod()) );
+
+ TVector2 delta_pT_vec = (p3mu + p3p).XYvector();
+ delta_alphaT = std::acos( (-p3mu.X()*delta_pT_vec.X()
+ - p3mu.Y()*delta_pT_vec.Y())
+ / (p3mu.XYvector().Mod() * delta_pT_vec.Mod()) );
+
+ double Emu = std::sqrt(std::pow(MUON_MASS, 2) + p3mu.Mag2());
+ double Ep = std::sqrt(std::pow(PROTON_MASS, 2) + p3p.Mag2());
+ double R = TARGET_MASS + p3mu.Z() + p3p.Z() - Emu - Ep;
+
+ // Estimated mass of the final remnant nucleus (CCQE assumption)
+ double mf = TARGET_MASS - NEUTRON_MASS + BINDING_ENERGY;
+ delta_pL = 0.5*R - (std::pow(mf, 2) + std::pow(delta_pT, 2)) / (2.*R);
+
+ pn = std::sqrt( std::pow(delta_pL, 2) + std::pow(delta_pT, 2) );
+
+ // Components of the 2D delta_pT vector (see arXiv:1910.08658)
+
+ // We assume that the neutrino travels along the +z direction (also done
+ // in the other expressions above)
+ TVector3 zUnit( 0., 0., 1. );
+
+ // Defines the x direction for the components of the delta_pT vector
+ TVector2 xTUnit = zUnit.Cross( p3mu ).XYvector().Unit();
+
+ delta_pTx = xTUnit.X()*delta_pT_vec.X() + xTUnit.Y()*delta_pT_vec.Y();
+
+ // Defines the y direction for the components of the delta_T vector
+ TVector2 yTUnit = ( -p3mu ).XYvector().Unit();
+
+ delta_pTy = yTUnit.X()*delta_pT_vec.X() + yTUnit.Y()*delta_pT_vec.Y();
+ }
+
+ double compute_stvs( FitEvent* ev, const std::string& var ) {
+ TVector3 p3mu = ev->GetHMFSParticle( MU_MINUS )->fP.Vect();
+ TVector3 p3p = ev->GetHMFSParticle( PROTON )->fP.Vect();
+
+ // Convert to GeV
+ p3mu *= 1e-3;
+ p3p *= 1e-3;
+
+ double delta_pT, delta_phiT, delta_alphaT, delta_pL;
+ double pn, delta_pTx, delta_pTy;
+
+ compute_stvs( p3mu, p3p, delta_pT, delta_phiT, delta_alphaT,
+ delta_pL, pn, delta_pTx, delta_pTy );
+
+ if ( var == "delta_pT" ) return delta_pT;
+ else if ( var == "delta_phiT" ) return delta_phiT;
+ else if ( var == "delta_alphaT" ) return delta_alphaT;
+ else if ( var == "delta_pL" ) return delta_pL;
+ else if ( var == "pn" ) return pn;
+ else if ( var == "delta_pTx" ) return delta_pTx;
+ else if ( var == "delta_pTy" ) return delta_pTy;
+ return 0.;
+ }
+*/
+
+
+}
+
+
+ MicroBooNE_BNB_NumuCC0Pi_2025_XSec_nu_2D::MicroBooNE_BNB_NumuCC0Pi_2025_XSec_nu_2D( nuiskey samplekey )
+{
+ fSettings = LoadSampleSettings( samplekey );
+ std::string name = fSettings.GetS( "name" );
+
+ // The main histograms use the bin number on the x-axis
+ fSettings.SetXTitle( "bin number" );
+
+ if ( !name.compare("MicroBooNE_BNB_NumuCC0Pi_2025_XSec_nu_2D") ) {
+ fSettings.SetYTitle( "#sigma (cm^{2}/^{40}Ar)" );
+ }
+ else {
+ assert( false );
+ }
+
+ // Sample overview ---------------------------------------------------
+ std::string descrip = name + " sample.\n" \
+ "Target: Ar\n" \
+ "Flux: BNB FHC numu\n" \
+ "reference: https://arxiv.org/abs/2507.00921\n" \
+ "Signal: CC0pi (yr 2025)\n";
+
+ fSettings.SetDescription( descrip );
+ fSettings.SetTitle( name );
+ fSettings.SetAllowedTypes( "FULL", "FIX/FULL" );
+ fSettings.SetEnuRange( 0.0, 10 ); /// using range we flux intergered
+ fSettings.DefineAllowedTargets( "Ar" );
+ fSettings.DefineAllowedSpecies( "numu" );
+ FinaliseSampleSettings();
+
+ // Scale factor for the flux-averaged total cross section
+ // (10^{-38} cm^2 / Ar) in each bin
+ fScaleFactor = GetEventHistogram()->Integral( "width" )
+ * MASS_NUMBER_40AR / fNEvents / TotalIntegratedFlux();
+
+ // Get bin definitions
+ this->LoadBinDefinitions();
+
+ std::string data_file_name( FitPar::GetDataBase()
+ + "/MicroBooNE/CC0pi_2025/2D/Extracted2DCrossSection_MicroBooNE_CC0PiSelection_BinningScheme1.root" );
+
+ // Load the additional smearing matrix
+ TMatrixD* A_C = StatUtils::GetMatrixFromRootFile( data_file_name,
+ "AC_Matrix" );
+ fAddSmear.reset( A_C );
+
+ // Load the measured data points
+ fDataHist = PlotUtils::GetTH1DFromRootFile( data_file_name,
+ "h_ExtractedCrossSectionUnfoldedData" );
+
+ fDataHist->SetNameTitle( (fSettings.GetName() + "_data").c_str(),
+ fSettings.GetFullTitles().c_str() );
+
+ // Also retrieve the total covariance matrix and its inverse (pre-computed in
+ // the data release ROOT file for convenience)
+ //this->SetCovarFromRootFile( data_file_name, "cov_total" );
+ fFullCovar = StatUtils::GetCovarFromRootFile( data_file_name, "TotalCov_AfterUnfolding_Norm" );
+ //covar = StatUtils::GetCovarFromRootFile( data_file_name,
+ // "inverse_cov_total" );
+
+ //fDecomp = StatUtils::GetDecomp( fFullCovar );
+ TDecompChol chol( *fFullCovar );
+ chol.Decompose();
+ fDecomp = new TMatrixDSym( fFullCovar->GetNrows(),
+ chol.GetU().GetMatrixArray(), "" );
+
+ // Push the diagonals of fFullCovar onto the data histogram
+ StatUtils::SetDataErrorFromCov( fDataHist, fFullCovar, 1.0, false );
+
+ // Setup fMCHist from data
+ fMCHist = dynamic_cast< TH1D* >( fDataHist->Clone() );
+ fMCHist->SetNameTitle( (fSettings.GetName() + "_MC").c_str(),
+ fSettings.GetFullTitles().c_str() );
+ fMCHist->Reset();
+
+ fMCStat = dynamic_cast< TH1D* >( fMCHist->Clone() );
+ fMCStat->Reset();
+
+ // Since we're using a 1D histogram with bin number along the x-axis, it
+ // doesn't make sense to subdivide the bins. Rather than doing that
+ // automatically, I just copy the original data histogram binning here. Thus,
+ // MCFine ends up using the same bins as regular MC.
+ fMCFine = dynamic_cast< TH1D* >( fMCHist->Clone() );
+ fMCFine->SetNameTitle( (fSettings.GetName() + "_MC_FINE").c_str(),
+ fSettings.GetFullTitles().c_str() );
+ fMCFine->Reset();
+
+ // Set up the MC modes histogram
+ fMCHist_Modes = new TrueModeStack( (fSettings.GetName() + "_MODES").c_str(),
+ "True Channels", fMCHist );
+ fMCHist_Modes->SetTitleX( fDataHist->GetXaxis()->GetTitle() );
+ fMCHist_Modes->SetTitleY( fDataHist->GetYaxis()->GetTitle() );
+ this->SetAutoProcessTH1( fMCHist_Modes, kCMD_Reset, kCMD_Norm, kCMD_Write );
+
+ //this->FinaliseMeasurement();
+}
+
+
+bool MicroBooNE_BNB_NumuCC0Pi_2025_XSec_nu_2D::isSignal( FitEvent* event ) {
+ return SignalDef::MicroBooNE::isCC1Mu0pi_2025(event, EnuMin, EnuMax);
+}
+
+void MicroBooNE_BNB_NumuCC0Pi_2025_XSec_nu_2D::FillEventVariables( FitEvent* event ) {
+
+ // Clear out the vector of passing bins, which may have already been filled
+ // for the previous event
+ fPassingBins.clear();
+
+ if ( event->NumFSParticle(MU_MINUS) == 0 ) return;
+
+ // Loop over each of the bin definitions. Keep track of the bins that
+ // pass all cuts (and should thus be filled in this event)
+ for ( size_t b = 0u; b < fBinDefinitions.size(); ++b ) {
+
+ // Start out by assuming that the event passes all cuts
+ bool passed_cuts = true;
+
+ // Apply all cuts
+ const auto& my_cuts = fBinDefinitions.at( b );
+ for ( const auto& cut : my_cuts ) {
+ passed_cuts &= cut.evaluate( event );
+ }
+
+ // If all cuts were passed, add this bin to the vector of indices for bins
+ // that should be filled
+ if ( passed_cuts ) fPassingBins.push_back( b );
+
+ } // loop over bins
+
+}
+/////////////////////////////////////////////////////////////////////
+//
+/////////////////////////////////////////////////////////////////////
+void MicroBooNE_BNB_NumuCC0Pi_2025_XSec_nu_2D::LoadBinDefinitions() {
+ std::string binning_file_name( FitPar::GetDataBase()
+ + "/MicroBooNE/CC0pi_2025/2D/bin_defsCC0pi_2025.txt" );
+
+ std::ifstream bin_file( binning_file_name );
+ std::string dummy_str;
+ int bin_type, dummy_int;
+ size_t num_true_bins;
+
+ // TODO: add I/O error handling hereg
+ // Skip the output TDirectoryFile name and the input TTree name
+ bin_file >> dummy_str >> dummy_str >> num_true_bins;
+
+ const std::string delimiter( "&&" );
+ for ( size_t tb = 0u; tb < num_true_bins; ++tb ) {
+
+ // Skip the true bin type and block index
+ bin_file >> bin_type >> dummy_int;
+
+ // Use two calls to std::getline using a double quote delimiter
+ // in order to get the contents of the next double-quoted string
+ std::string bin_def;
+ std::getline( bin_file, bin_def, '\"' );
+ std::getline( bin_file, bin_def, '\"' );
+
+ // Skip bins of type == 1 (background true bins)
+ if ( bin_type == 1 ) continue;
+
+ // Skip the text before the first "&&" (here we assume that it is a simple
+ // bool for the signal definition)
+ std::string all_cuts = bin_def.substr(
+ bin_def.find(delimiter) + delimiter.length() );
+
+ // Create an empty vector of MyCut objects to start defining the new bin
+ fBinDefinitions.emplace_back();
+
+ // Loop over the rest of the cuts and check each one
+ size_t pos = 0u;
+ do {
+ // Advance to the next individual cut, removing it from the temporary
+ // copy of the list of cuts
+ pos = all_cuts.find( delimiter );
+ std::string cut = all_cuts.substr( 0, pos );
+ all_cuts.erase( 0, pos + delimiter.length() );
+
+ // Parse the cut
+ std::string var_name;
+ std::string comp_op;
+ double cut_val;
+ std::stringstream cut_ss( cut );
+
+ cut_ss >> var_name >> comp_op >> cut_val;
+
+ // Interpret the variable for this cut and set up a function
+ // object that will calculate it from the input FitEvent
+ std::function< double(FitEvent*) > getter;
+
+ if ( var_name == "mc_p3_lead_p.Mag()" ) {
+ getter = [=]( FitEvent* ev ) -> double {
+ return ev->GetHMFSParticle( PROTON )->fP.Vect().Mag() / 1e3; // GeV
+ };
+ }
+ else if ( var_name == "mc_p3_lead_p.CosTheta()" ) {
+ getter = [=]( FitEvent* ev ) -> double {
+ return ev->GetHMFSParticle( PROTON )->fP.Vect().CosTheta();
+ };
+ }
+ else if ( var_name == "mc_p3_mu.Mag()" ) {
+ getter = [=]( FitEvent* ev ) -> double {
+ return ev->GetHMFSParticle( MU_MINUS )->fP.Vect().Mag() / 1e3; // GeV
+ };
+ }
+ else if ( var_name == "mc_p3_mu.CosTheta()" ) {
+ getter = [=]( FitEvent* ev ) -> double {
+ return ev->GetHMFSParticle( MU_MINUS )->fP.Vect().CosTheta();
+ };
+ }
+ else NUIS_ABORT( "Unrecognized cut variable " + var_name );
+
+ // Test the cut based on the appropriate comparison operator
+ std::function< bool(double) > tester;
+ if ( comp_op == ">=" ) {
+ tester = [cut_val]( double x ) -> bool {
+ return x >= cut_val;
+ };
+ }
+ else if ( comp_op == "<" ) {
+ tester = [cut_val]( double x ) -> bool {
+ return x < cut_val;
+ };
+ }
+ else NUIS_ABORT( "Unrecognized comparison operator " + comp_op );
+
+ // Add the finished cut to the definition for the current bin
+ fBinDefinitions.back().emplace_back( getter, tester );
+
+ } while ( pos != std::string::npos );
+
+ } // loop over true bins
+
+}
+/////////////////////////////////////////////////////////////////////
+//
+/////////////////////////////////////////////////////////////////////
+void MicroBooNE_BNB_NumuCC0Pi_2025_XSec_nu_2D::FillHistograms() {
+
+ if ( !Signal ) return;
+
+ // The histograms have bin number as the x-axis variable, so just fill them
+ // using the bin index of each bin that passed all cuts
+ for ( const auto& bin : fPassingBins ) {
+ NUIS_LOG(DEB, "Fill MCHist: " << bin << ", " << Weight);
+
+ fMCHist->Fill( bin, Weight );
+ fMCStat->Fill( bin, 1.0 );
+ if ( fMCHist_Modes ) fMCHist_Modes->Fill( Mode, bin, Weight );
+
+ fMCFine->Fill( bin, Weight );
+ if ( fMCFine_Modes ) fMCFine_Modes->Fill( Mode, bin, Weight );
+ }
+
+}
+
+
+void MicroBooNE_BNB_NumuCC0Pi_2025_XSec_nu_2D::ConvertEventRates() {
+ // Do the standard conversion
+ Measurement1D::ConvertEventRates();
+
+ // TODO: Restore use of A_C once you make sure you aren't double-counting it
+ //// Build a column vector using the predicted cross sections
+ //int num_bins = fMCHist->GetNbinsX();
+ //TMatrixD pred( num_bins, 1 );
+ //for ( int b = 0; b < num_bins; ++b ) {
+ // pred( b, 0 ) = fMCHist->GetBinContent( b + 1 );
+ //}
+
+ //// Apply the additional smearing matrix to create a new prediction
+ //TMatrixD new_pred( *fAddSmear, TMatrixD::kMult, pred );
+
+ //// Update the MC prediction histogram with the smeared version
+ //for ( int b = 0; b < num_bins; ++b ) {
+ // double xsec = new_pred( b, 0 );
+ // fMCHist->SetBinContent( b + 1, xsec );
+ //}
+}
+
+double MicroBooNE_BNB_NumuCC0Pi_2025_XSec_nu_2D::GetLikelihood() {
+
+ if ( fNoData || !fDataHist ) return 0.;
+
+ // Apply Masking to MC if Required.
+ if ( fIsMask and fMaskHist ) {
+ NUIS_ERR(FTL, "Bin masks not yet supported by"
+ " the MicroBooNE_BNB_NumuCC0Pi_2025_XSec_nu_2D sample" );
+ //PlotUtils::MaskBins(fMCHist, fMaskHist);
+ }
+
+ // Likelihood Calculation
+ double stat = 0.;
+ if ( fIsChi2 ) {
+ if ( fIsNS ) {
+ NUIS_ERR(FTL, "Norm-shape covariance not yet supported by"
+ " the MicroBooNE_BNB_NumuCC0Pi_2025_XSec_nu_2D sample" );
+ }
+ stat = StatUtils::GetChi2FromCov( fDataHist, fMCHist, covar, NULL, 1.0,
+ 1.0, fIsWriting ? fResidualHist : NULL );
+ }
+
+ fLikelihood = stat;
+
+ return stat;
+}
diff --git a/src/MicroBooNE/MicroBooNE_BNB_NumuCC0Pi_2025_XSec_nu_2D.h b/src/MicroBooNE/MicroBooNE_BNB_NumuCC0Pi_2025_XSec_nu_2D.h
new file mode 100644
index 000000000..7a8a22717
--- /dev/null
+++ b/src/MicroBooNE/MicroBooNE_BNB_NumuCC0Pi_2025_XSec_nu_2D.h
@@ -0,0 +1,90 @@
+// Copyright 2016 L. Pickering, P Stowell, R. Terri, C. Wilkinson, C. Wret
+
+/*******************************************************************************
+* This file is part of NUISANCE.
+*
+* NUISANCE is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published by
+* the Free Software Foundation, either version 3 of the License, or
+* (at your option) any later version.
+*
+* NUISANCE is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with NUISANCE. If not, see .
+*******************************************************************************/
+#ifndef MICROBOONE_BNB_NUMUCC0Pi_2025_XSEC_NU_2D_H_SEEN
+#define MICROBOONE_BNB_NUMUCC0Pi_2025_XSEC_NU_2D_H_SEEN
+
+
+
+#include
+#include
+#include
+
+#include "Measurement1D.h"
+
+class TH2D;
+
+struct MyCut {
+ MyCut( std::function< double(FitEvent*) > getter,
+ std::function< bool(double) > tester ) : getter_( getter ),
+ tester_( tester ) {}
+
+ std::function< double(FitEvent*) > getter_;
+ std::function< bool(double) > tester_;
+
+ inline bool evaluate( FitEvent* event ) const {
+ return tester_( getter_(event) );
+ }
+};
+
+class MicroBooNE_BNB_NumuCC0Pi_2025_XSec_nu_2D : public Measurement1D {
+
+public:
+
+ /// Basic Constructor.
+ MicroBooNE_BNB_NumuCC0Pi_2025_XSec_nu_2D( nuiskey samplekey );
+
+ /// Virtual Destructor
+ inline ~MicroBooNE_BNB_NumuCC0Pi_2025_XSec_nu_2D() {
+ fResidualHist = NULL;
+ fChi2LessBinHist = NULL;
+ };
+
+ /// Apply signal definition
+ virtual bool isSignal( FitEvent* nvect );
+
+ /// Fill kinematic distributions
+ void FillEventVariables( FitEvent* customEvent );
+
+ virtual void FillHistograms() override;
+
+ /// Adjust the standard event rate conversion to include
+ /// application of the additional smearing matrix A_C
+ void ConvertEventRates() override;
+
+ /// Work around some hard-coded assumptions in Measurement1D::GetLikelihood()
+ double GetLikelihood() override;
+
+private:
+
+ void LoadBinDefinitions();
+
+ // Each bin is defined as a series of cuts that are applied to a FitEvent to
+ // determine whether it belongs
+ std::vector< std::vector > fBinDefinitions;
+
+ // Temporary storage for the index of each bin that passed all cuts for any
+ // particular event
+ std::vector< size_t > fPassingBins;
+
+ // Additional smearing matrix used to transform the input MC predictions
+ std::unique_ptr< TMatrixD > fAddSmear;
+
+};
+
+#endif
diff --git a/src/MicroBooNE/MicroBooNE_SignalDef.cxx b/src/MicroBooNE/MicroBooNE_SignalDef.cxx
index b21f0c438..e1b63a92e 100644
--- a/src/MicroBooNE/MicroBooNE_SignalDef.cxx
+++ b/src/MicroBooNE/MicroBooNE_SignalDef.cxx
@@ -200,7 +200,40 @@ bool isCC1Mu1p(FitEvent* event, double EnuMin, double EnuMax) {
return true;
}
- //----------------------------------------//
+ //----------------------------------------//
+
+bool isCC1Mu0pi_2025(FitEvent* event, double EnuMin, double EnuMax) {
+ // Check CC inclusive
+ if (!SignalDef::isCCINC(event, 14, EnuMin, EnuMax)) return false;
+
+ // Veto events which don't have exactly 1 FS muon
+ if (event->NumFSMuon() != 1) return false;
+
+ // Muon momentum range
+ auto mu_mom = event->GetHMFSParticle(13)->p();
+ if ((mu_mom < 100) || (mu_mom > 2000)) return false;
+
+ // Reject events with neutral pions of any momenta
+ if (event->NumFSParticle(111) != 0) return false;
+
+ // Reject events with positively charged pions above 70 MeV/c
+ if (event->NumFSParticle(211) != 0) {
+ double ppiplus = event->GetHMFSParticle(211)->p();
+ if (ppiplus > 70) { return false; }
+ }
+
+ // Reject events with negatively charged pions above 70 MeV/c
+ if (event->NumFSParticle(-211) != 0) {
+ double ppiminus = event->GetHMFSParticle(-211)->p();
+ if (ppiminus > 70) { return false; }
+ }
+
+ return true;
+}
+
+
+
+
} // namespace MicroBooNE
diff --git a/src/MicroBooNE/MicroBooNE_SignalDef.h b/src/MicroBooNE/MicroBooNE_SignalDef.h
index 7513cb708..95e727eda 100644
--- a/src/MicroBooNE/MicroBooNE_SignalDef.h
+++ b/src/MicroBooNE/MicroBooNE_SignalDef.h
@@ -48,6 +48,9 @@ bool isCC1Mu2p(FitEvent* event, double EnuMin, double EnuMax);
bool isCC1Mu1p(FitEvent* event, double EnuMin, double EnuMax);
std::vector GetCC1Mu1pProtonsInPS(FitEvent* event);
+
+bool isCC1Mu0pi_2025(FitEvent* event, double EnuMin, double EnuMax);
+
} // namespace MicroBooNE
} // namespace SignalDef