4"""Generate GENIE neutrino interaction events from flux histograms.
6This "launcher" wraps `genie_utils` (gmkspl/gevgen/gntpc helpers) and adds:
7- Argument parsing with sensible defaults and validation
8- Automatic neutrino/antineutrino scaling based on flux hist sums
9- Structured logging and robust error reporting
13$ python $FAIRSHIP/macro/makeGenieEvents.py sim \
16 --filedir /eos/experiment/ship/data/Mbias/background-prod-2018 \
20 --emin 0.5 --emax 350 \
21 --xsec-file gxspl-FNALsmall.xml \
22 --flux-file pythia8_Geant4_1.0_withCharm_nu.root \
23 --event-generator-list CC
27- This tool *does not* modify your parent shell environment.
28- GXMLPATH
is automatically set to $FAIRSHIP_ROOT/shipgen/genie_config by default.
29- Use `--gxmlpath` to override the default GXMLPATH
if needed.
33from __future__ import annotations
38from collections.abc import Mapping, Sequence
39from pathlib import Path
43from genie_interface
import (
45 generate_genie_events,
53DEFAULT_XSEC_FILE =
"gxspl-FNALsmall.xml"
54DEFAULT_FLUX_FILE =
"pythia8_Geant4_1.0_withCharm_nu.root"
56DEFAULT_SPLINE_DIR = Path(
57 "/eos/experiment/ship/user/edursov/genie/genie_xsec/v3_02_00/NULL/G1802a00000-k250-e1000/data"
59DEFAULT_FILE_DIR = Path(
"/eos/experiment/ship/data/Mbias/background-prod-2018")
63 "lead":
"1000822040[0.014],1000822060[0.241],1000822070[0.221],1000822080[0.524]",
64 "tungsten":
"1000741840",
67NUPDGLIST = [16, -16, 14, -14, 12, -12]
73 """Compute ν/ν̄ = sum(nu)/sum(nubar) from 1D flux histograms."""
74 f = ROOT.TFile(str(neutrino_flux),
"READ")
75 if not f
or f.IsZombie():
76 raise FileNotFoundError(f
"Cannot open flux file: {neutrino_flux}")
78 ratios: dict[int, float] = {}
81 h_nu = f.Get(get_1D_flux_name(fam))
82 h_nubar = f.Get(get_1D_flux_name(-fam))
83 if not h_nu
or not h_nubar:
84 raise FileNotFoundError(
85 f
"Missing hists {get_1D_flux_name(fam)} / {get_1D_flux_name(-fam)} in {neutrino_flux}"
87 s_nu = float(h_nu.GetSumOfWeights())
88 s_nubar = float(h_nubar.GetSumOfWeights())
90 raise FileNotFoundError(f
"ν̄ sum of weights is zero for family {fam} in {neutrino_flux}")
91 ratios[fam] = s_nu / s_nubar
92 log_output = f
"ν/ν̄ ratio for |PDG|={fam}: {ratios[fam]:.4f} (nu={s_nu}, nubar={s_nubar})"
93 logging.info(log_output)
100 path.mkdir(parents=
True, exist_ok=
True)
103def _build_env(gxmlpath: Path |
None) -> Mapping[str, str |
None] |
None:
104 """Build per-call env overrides (sets GXMLPATH)."""
105 fairship_root_path = os.getenv(
"FAIRSHIP_ROOT")
or os.getenv(
"FAIRSHIP")
106 if not fairship_root_path:
108 "FAIRSHIP_ROOT (or FAIRSHIP) environment variable is not set. "
109 "Please run inside the FairShip alienv environment."
111 val = str(gxmlpath)
if gxmlpath
else fairship_root_path +
"/shipgen/genie_config"
112 return {
"GXMLPATH": val}
116 code = TARGET_CODE.get(name.lower())
118 raise ValueError(f
"Unknown target '{name}'. Choices: {', '.join(TARGET_CODE)}")
122def _pdg_list(particles: Sequence[int]) -> Sequence[int]:
123 return [int(p)
for p
in particles]
127 """Build xsec splines with gmkspl and write `xsec_splines.xml` to work_dir."""
129 output = work_dir /
"xsec_splines.xml"
137 logging.info(f
"Spline file written: {output}")
144 particles: Sequence[int],
152 env_vars: Mapping[str, str |
None] |
None,
154 nu_over_nubar: Mapping[int, float],
156 """Generate events for PDGs, convert to GST, and attach 2D flux."""
158 pdg_db = ROOT.TDatabasePDG()
160 for pdg
in particles:
162 part = pdg_db.GetParticle(int(pdg))
163 pdg_name = part.GetName()
if part
else str(pdg)
166 N = int(nevents)
if pdg > 0
else int(nevents / nu_over_nubar[fam])
168 out_dir = work_dir / f
"genie-{pdg_name}_{N}_events"
171 filename = f
"run_{run}_{pdg_name}_{N}_events_{targetcode}_{emin}_{emax}_GeV_{process or 'ALL'}.ghep.root"
172 ghep_path = out_dir / filename
173 gst_path = out_dir / f
"genie-{filename.replace('ghep.root', 'gst.root')}"
175 logging.info(f
"Generating {N} events for PDG {pdg_name} (run={run}) -> {ghep_path}")
177 generate_genie_events(
182 targetcode=targetcode,
183 inputflux=str(neutrino_flux),
185 outputfile=str(ghep_path),
191 make_ntuples(str(ghep_path), str(gst_path), env_vars=env_vars)
192 add_hists(str(neutrino_flux), str(gst_path), int(pdg))
194 logging.info(f
"Done PDG {pdg_name}: {ghep_path} -> {gst_path}")
201 p = argparse.ArgumentParser(description=
"Run GENIE neutrino simulation")
202 sub = p.add_subparsers(dest=
"cmd", required=
True)
205 ap = sub.add_parser(
"sim", help=
"Make GENIE simulation file(s)")
206 ap.add_argument(
"-s",
"--seed", type=int, default=65539, help=
"RNG seed (default: GENIE 65539)")
212 default=Path(
"./work"),
213 help=
"Output directory",
220 default=DEFAULT_FILE_DIR,
228 default=DEFAULT_SPLINE_DIR,
234 default=DEFAULT_XSEC_FILE,
235 help=f
"Spline XML (default: {DEFAULT_XSEC_FILE})",
240 default=DEFAULT_FLUX_FILE,
241 help=f
"Flux ROOT (default: {DEFAULT_FLUX_FILE})",
248 choices=sorted(TARGET_CODE),
249 help=
"Target material",
251 ap.add_argument(
"-n",
"--nevents", type=int, default=100, help=
"Events per neutrino species")
252 ap.add_argument(
"--emin", type=float, default=0.5, help=
"Min Eν [GeV]")
253 ap.add_argument(
"--emax", type=float, default=350.0, help=
"Max Eν [GeV]")
256 "--event-generator-list",
260 help=
"GENIE generator list (e.g. CC, CCDIS, CCQE, CharmCCDIS, RES, CCRES, ...)",
262 ap.add_argument(
"--gxmlpath", type=Path, default=
None, help=
"Override GXMLPATH")
269 help=
"PDGs (e.g. 16 -16 14 -14 12 -12). Default: all flavors",
271 ap.add_argument(
"-r",
"--run", type=int, default=1, help=
"GENIE run number")
277 help=
"Increase verbosity (-v or -vv)",
281 ap1 = sub.add_parser(
"spline", help=
"Make a new cross-section spline (xsec_splines.xml)")
282 ap1.add_argument(
"-t",
"--target", type=str, default=
"iron", choices=sorted(TARGET_CODE))
283 ap1.add_argument(
"-o",
"--output", dest=
"work_dir", type=Path, default=Path(
"./work"))
284 ap1.add_argument(
"-n",
"--nknots", type=int, default=500)
285 ap1.add_argument(
"--emax", type=float, default=400.0)
290def main(argv: Sequence[str] |
None =
None) -> int:
291 """Launch GENIE event generation or spline creation from CLI args."""
294 args = parser.parse_args(argv)
297 level = logging.WARNING
298 if args.verbose == 1:
300 elif args.verbose >= 2:
301 level = logging.DEBUG
302 logging.basicConfig(level=level, format=
"%(levelname)s %(name)s: %(message)s")
307 if args.cmd ==
"spline":
310 work_dir=args.work_dir,
320 splines = (args.splinedir / args.xsec_file).resolve()
321 flux = (args.filedir / args.flux_file).resolve()
322 if not splines.is_file():
323 parser.error(f
"Spline file not found: {splines}")
324 if not flux.is_file():
325 parser.error(f
"Flux ROOT file not found: {flux}")
330 logging.info(f
"Seed: {args.seed} | Target: {args.target} ({targetcode}) | Process: {args.evtype or 'ALL'}")
333 nevents=int(args.nevents),
335 targetcode=targetcode,
337 emin=float(args.emin),
338 emax=float(args.emax),
343 work_dir=args.work_dir.resolve(),
344 nu_over_nubar=nu_over_nubar,
346 logging.info(
"Event generation completed successfully.")
350if __name__ ==
"__main__":
str _target_code(str name)
Mapping[str, str|None]|None _build_env(Path|None gxmlpath)
None make_events(*int run, int nevents, Sequence[int] particles, str targetcode, str|None process, float emin, float emax, Path neutrino_flux, Path splines, int seed, Mapping[str, str|None]|None env_vars, Path work_dir, Mapping[int, float] nu_over_nubar)
None _ensure_dir(Path path)
int main(Sequence[str]|None argv=None)
Sequence[int] _pdg_list(Sequence[int] particles)
dict[int, float] extract_nu_over_nubar(Path neutrino_flux, Sequence[int] particles)
argparse.ArgumentParser _build_parser()
None make_splines_cli(str target, Path work_dir, int nknots, float emax)
None configure(darkphoton=None)