Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion JobConfig/ensemble/fcl/prolog.fcl
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ Ensemble : {
InputCommands : [ "keep *",
"keep mu2e::CosmicLivetime_*_*_*",
"drop *_genCounter_*_*",
"drop *_protonBunchIntensity_*_*"]
"drop *_protonBunchIntensity_*_*",
"drop mu2e::SpectrumConfig_*_*_*"]
OutputCommandsMC : [ "keep *_*_*_*"]
OutputCommandsData : [ "drop *_*_*_*",
"keep mu2e::KalSeeds_*_*_*",
Expand Down
27 changes: 27 additions & 0 deletions JobConfig/ensemble/python/calculateEvents.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#! /usr/bin/env python
import argparse
from normalizations import *

def main(args):
Expand Down Expand Up @@ -43,6 +44,30 @@ def main(args):
if(args.prc == "RMC" and int(args.internal) == 0):
Yield = rmc_normalization(float(args.livetime), str(args.internal), float(args.rmcemin))
print("ExternalRMC_yield=",Yield)
if(args.prc == "RMCN0External"):
Yield = rmc_0n_normalization(float(args.livetime), float(args.rmcn0emin), internal=0, run_mode=str(args.BB))
print("ExternalRMCN0_yield=",Yield)
if(args.prc == "RMCN0Internal"):
Yield = rmc_0n_normalization(float(args.livetime), float(args.rmcn0emin), internal=1, run_mode=str(args.BB))
print("InternalRMCN0_yield=",Yield)
if(args.prc == "RMCPhaseSpace0NExternal"):
Comment thread
sophiemiddleton marked this conversation as resolved.
Yield = rmc_0n_normalization(float(args.livetime), float(args.rmcn0emin), internal=0, run_mode=str(args.BB))
print("ExternalRMCPhaseSpace0N_yield=",Yield)
if(args.prc == "RMCPhaseSpace0NInternal"):
Yield = rmc_0n_normalization(float(args.livetime), float(args.rmcn0emin), internal=1, run_mode=str(args.BB))
print("InternalRMCPhaseSpace0N_yield=",Yield)
if(args.prc == "RMCN1External"):
Yield = rmc_1n_normalization(float(args.livetime), float(args.rmcn1emin), internal=0, run_mode=str(args.BB))
print("ExternalRMCN1_yield=",Yield)
if(args.prc == "RMCN1Internal"):
Yield = rmc_1n_normalization(float(args.livetime), float(args.rmcn1emin), internal=1, run_mode=str(args.BB))
print("InternalRMCN1_yield=",Yield)
if(args.prc == "RMCPhaseSpace1NExternal"):
Yield = rmc_1n_normalization(float(args.livetime), float(args.rmcn1emin), internal=0, run_mode=str(args.BB))
print("ExternalRMCPhaseSpace1N_yield=",Yield)
if(args.prc == "RMCPhaseSpace1NInternal"):
Yield = rmc_1n_normalization(float(args.livetime), float(args.rmcn1emin), internal=1, run_mode=str(args.BB))
print("InternalRMCPhaseSpace1N_yield=",Yield)
if(args.prc == "IPAMichel"):
Yield = ipaMichel_normalization(float(args.livetime), float(args.ipaemin), str(args.BB))
print("IPAMichel_yield=",Yield)
Expand All @@ -58,6 +83,8 @@ def main(args):
parser.add_argument("--ipaemin", help="min energy cut dio ipa")
parser.add_argument("--rpcemin", help="rpcemin", default=0)
parser.add_argument("--rmcemin", help="min energy cut rmc")
parser.add_argument("--rmcn0emin", help="min energy cut rmc 0N")
Comment thread
michaelmackenzie marked this conversation as resolved.
parser.add_argument("--rmcn1emin", help="min energy cut rmc 1N")
parser.add_argument("--prc", help="process")
parser.add_argument("--printpot", help="print pot", default="no")
parser.add_argument("--tmin", help="tmin", default=0)
Expand Down
55 changes: 44 additions & 11 deletions JobConfig/ensemble/python/make_template_fcl.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,17 +32,48 @@ def main(args):

ROOT.gRandom.SetSeed(0)

# extract normalization of each background/signal process:
norms = {
"CRYCosmic": cry_onspill_normalization(livetime, args.BB),
"CORSIKACosmic": corsika_onspill_normalization(livetime, args.BB),
"DIO": dio_normalization(livetime, dioemin, args.BB),
"RPCInternal": rpc_normalization(livetime, args.tmin, 1, args.rpcemin, args.BB),
"RPCExternal": rpc_normalization(livetime, args.tmin, 0, args.rpcemin, args.BB),
"RMCInternal": rmc_normalization(livetime, 1, args.rmcemin, args.rmckmax, args.BB),
"RMCExternal": rmc_normalization(livetime, 0, args.rmcemin, args.rmckmax, args.BB),
"IPAMichel": ipaMichel_normalization(livetime, args.ipaemin, args.BB)
}
# Convert args.prc into a set for fast lookup
requested_processes = set(args.prc)

# Initialize an empty dictionary
norms = {}

# Only call functions and define keys if they are in the requested processes
if "CRYCosmic" in requested_processes:
norms["CRYCosmic"] = cry_onspill_normalization(livetime, args.BB)

if "CORSIKACosmic" in requested_processes:
norms["CORSIKACosmic"] = corsika_onspill_normalization(livetime, args.BB)

if "DIO" in requested_processes:
norms["DIO"] = dio_normalization(livetime, dioemin, args.BB)

if "RPCInternal" in requested_processes:
norms["RPCInternal"] = rpc_normalization(livetime, tmin, 1, args.rpcemin, args.BB)

if "RPCExternal" in requested_processes:
norms["RPCExternal"] = rpc_normalization(livetime, tmin, 0, args.rpcemin, args.BB)

if "RMCInternal" in requested_processes:
norms["RMCInternal"] = rmc_normalization(livetime, 1, args.rmcemin, args.rmckmax, args.BB)

if "RMCExternal" in requested_processes:
norms["RMCExternal"] = rmc_normalization(livetime, 0, args.rmcemin, args.rmckmax, args.BB)

if "RMCN0External" in requested_processes or "RMCPhaseSpace0NExternal" in requested_processes:
norms["RMCN0External"] = norms["RMCPhaseSpace0NExternal"] = rmc_0n_normalization(livetime, args.rmcn0emin, internal=0, run_mode=args.BB)

if "RMCN0Internal" in requested_processes or "RMCPhaseSpace0NInternal" in requested_processes:
norms["RMCN0Internal"] = norms["RMCPhaseSpace0NInternal"] = rmc_0n_normalization(livetime, args.rmcn0emin, internal=1, run_mode=args.BB)

if "RMCN1External" in requested_processes or "RMCPhaseSpace1NExternal" in requested_processes:
norms["RMCN1External"] = norms["RMCPhaseSpace1NExternal"] = rmc_1n_normalization(livetime, args.rmcn1emin, internal=0, run_mode=args.BB)

if "RMCN1Internal" in requested_processes or "RMCPhaseSpace1NInternal" in requested_processes:
norms["RMCN1Internal"] = norms["RMCPhaseSpace1NInternal"] = rmc_1n_normalization(livetime, args.rmcn1emin, internal=1, run_mode=args.BB)

if "IPAMichel" in requested_processes:
norms["IPAMichel"] = ipaMichel_normalization(livetime, args.ipaemin, args.BB)

starting_event_num = {}
max_possible_events = {}
Expand Down Expand Up @@ -210,6 +241,8 @@ def main(args):
parser.add_argument("--rpcemin", help="min energy cut rpc")
parser.add_argument("--ipaemin", help="min energy cut ipa")
parser.add_argument("--rmcemin", help="min energy cut rmc")
parser.add_argument("--rmcn0emin", help="min energy cut rmc 0N")
parser.add_argument("--rmcn1emin", help="min energy cut rmc 1N")
parser.add_argument("--rmckmax", help="kmax theory value")
parser.add_argument("--run", help="run number")
parser.add_argument("--samplingseed", help="samplingseed")
Expand Down
195 changes: 194 additions & 1 deletion JobConfig/ensemble/python/normalizations.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,17 @@ def set_verbose(verbose=True):
dutyfactor = 1.0
total_pot = 0.

# RMC 0N and 1N Physics Constants
RMC_BR_MUON_CAPTURE = 0.609
RMC_RATE_GT_57 = 1.41e-5 # RMC rate above 57 MeV, relative to OMC
RMC_BR_0N_FRAC_GT_57 = 0.099 # BR(0 knockout | E > 57) / BR(RMC | E > 57)
RMC_BR_1N_FRAC_GT_57 = 0.901 # BR(1 knockout | E > 57) / BR(RMC | E > 57)
RMC_SPECTRUM_FRAC_0N_57 = 0.22887 # R(0 knockout | E > 57) / R(0 knockout)
RMC_SPECTRUM_FRAC_1N_57 = 0.061620 # R(1 knockout | E > 57) / R(1 knockout)
RMC_SPECTRUM_FRAC_0N_80 = 0.03319 # R(0 knockout | E > 80) / R(0 knockout)
RMC_SPECTRUM_FRAC_1N_80 = 0.0013175 # R(1 knockout | E > 80) / R(1 knockout)
RMC_INTERNAL_EXTERNAL_RATIO = 0.0069 # rho = BR(internal) / BR(external)

#-------------------------------------------------------------------------------------#

# --- Database Interaction ---
Expand Down Expand Up @@ -500,7 +511,189 @@ def rmc_normalization(on_spill_time, internal, e_min, k_max=90.1, run_mode='1BB'

return base_physics_events

# get IPA Michel normalization:
#-------------------------------------------------------------------------------------#
# Plestid spectrum functions for RMC 0N and 1N

def plestid_integral(K_1, K_2, KMax, knockout):
"""
Calculates the integral of the Plestid phase-space approximation spectrum
between two energy points K_1 and K_2.

This implements the Plestid spectrum shape for RMC with different knockout modes.

Args:
K_1 (float): Lower energy bound for integration (MeV).
K_2 (float): Upper energy bound for integration (MeV).
KMax (float): Maximum possible RMC energy (MeV).
knockout (int): Knockout mode (0 for 0N knockout, 1 for 1N knockout).

Returns:
float: The integral of the spectrum between K_1 and K_2.
"""
if KMax <= 0.0:
return 0.0
if knockout < 0:
return 0.0

K_1 = max(0.0, min(KMax, K_1))
K_2 = max(0.0, min(KMax, K_2))

if K_1 >= K_2:
return 0.0

power = 2.0 + 1.5 * knockout
x_1 = K_1 / KMax
x_2 = K_2 / KMax

val_1 = (x_1 - 1.0) * pow(1.0 - x_1, power) * (power * x_1 + x_1 + 1.0)
val_2 = (x_2 - 1.0) * pow(1.0 - x_2, power) * (power * x_2 + x_2 + 1.0)

integral = val_2 - val_1
return integral


def plestid_spectrum(energy, kmax, knockout):
"""
Calculates the Plestid phase-space approximation spectrum value at a given energy.

Args:
energy (float): Energy point to evaluate the spectrum (MeV).
kmax (float): Maximum possible RMC energy (MeV).
knockout (int): Knockout mode (0 for 0N knockout, 1 for 1N knockout).

Returns:
float: The spectrum value at the given energy.
"""
if energy <= 0.0 or energy >= kmax:
return 0.0

power = 2.0 + 1.5 * knockout
norm = (power + 1.0) * (power + 2.0) / kmax
x = energy / kmax
p = norm * x * pow(1.0 - x, power)

return p


def rmc_0n_normalization(on_spill_time, e_min, internal=1, run_mode='1BB'):
"""
Calculates the expected number of RMC 0-nucleon knockout (0N) events
above a given energy threshold.

Uses the Plestid phase-space approximation spectrum shape.

Args:
on_spill_time (float): Time the beam was on spill (seconds).
e_min (float): Minimum energy threshold for the spectrum cut (MeV).
internal (int/bool): Flag (1 or 0) to include internal conversion scaling.
Defaults to 1.
run_mode (str): The operational mode ('1BB' or '2BB'). Defaults to '1BB'.

Returns:
float: The expected number of RMC 0N physics events passing the cuts.
"""
# 1. Calculate total Protons on Target (POT)
total_pot = get_pot(on_spill_time, run_mode)

# 2. Determine the spectrum fraction to use based on energy threshold
# Default: fraction from E > 57 MeV
e_threshold = 57.0
R_spectrum = RMC_SPECTRUM_FRAC_0N_57

# If threshold is higher, use interpolated fraction
# For E > 80 MeV: use the E > 80 spectrum fraction
if float(e_min) > 75.0:
Comment thread
sophiemiddleton marked this conversation as resolved.
Outdated
R_spectrum = RMC_SPECTRUM_FRAC_0N_80 / RMC_SPECTRUM_FRAC_0N_57

# 3. Calculate the branching ratio for 0N events above the energy threshold
br_0n_above_emin = (
RMC_BR_MUON_CAPTURE *
RMC_RATE_GT_57 *
RMC_BR_0N_FRAC_GT_57 *
R_spectrum
)

# 4. Calculate base physics events
# Note: br_0n_above_emin already includes RMC_BR_MUON_CAPTURE, so we do NOT multiply by CAPTURES_PER_STOPPED_MUON
base_physics_events = (
total_pot *
target_stopped_muons_per_pot *
br_0n_above_emin
)

# 5. Apply internal conversion scaling if requested
is_internal_conversion = bool(int(internal))

if is_internal_conversion:
if VERBOSE:
print("RMC_0N_emin=", e_min)
print("RMC_0N_spectrum_frac=", R_spectrum)
print("RMC_0N_BR=", br_0n_above_emin)

base_physics_events *= RMC_INTERNAL_EXTERNAL_RATIO

return base_physics_events


def rmc_1n_normalization(on_spill_time, e_min, internal=1, run_mode='1BB'):
"""
Calculates the expected number of RMC 1-nucleon knockout (1N) events
above a given energy threshold.

Uses the Plestid phase-space approximation spectrum shape.

Args:
on_spill_time (float): Time the beam was on spill (seconds).
e_min (float): Minimum energy threshold for the spectrum cut (MeV).
internal (int/bool): Flag (1 or 0) to include internal conversion scaling.
Defaults to 1.
run_mode (str): The operational mode ('1BB' or '2BB'). Defaults to '1BB'.

Returns:
float: The expected number of RMC 1N physics events passing the cuts.
"""
# 1. Calculate total Protons on Target (POT)
total_pot = get_pot(on_spill_time, run_mode)

# 2. Determine the spectrum fraction to use based on energy threshold
# Default: fraction from E > 57 MeV
e_threshold = 57.0
R_spectrum = RMC_SPECTRUM_FRAC_1N_57

# If threshold is higher, use interpolated fraction
# For E > 80 MeV: use the E > 80 spectrum fraction
if float(e_min) > 75.0:
Comment thread
sophiemiddleton marked this conversation as resolved.
Outdated
R_spectrum = RMC_SPECTRUM_FRAC_1N_80 / RMC_SPECTRUM_FRAC_1N_57

# 3. Calculate the branching ratio for 1N events above the energy threshold
br_1n_above_emin = (
RMC_BR_MUON_CAPTURE *
RMC_RATE_GT_57 *
RMC_BR_1N_FRAC_GT_57 *
R_spectrum
)

# 4. Calculate base physics events
# Note: br_1n_above_emin already includes RMC_BR_MUON_CAPTURE, so we do NOT multiply by CAPTURES_PER_STOPPED_MUON
base_physics_events = (
total_pot *
target_stopped_muons_per_pot *
br_1n_above_emin
)

# 5. Apply internal conversion scaling if requested
is_internal_conversion = bool(int(internal))

if is_internal_conversion:
if VERBOSE:
print("RMC_1N_emin=", e_min)
print("RMC_1N_spectrum_frac=", R_spectrum)
print("RMC_1N_BR=", br_1n_above_emin)

base_physics_events *= RMC_INTERNAL_EXTERNAL_RATIO

return base_physics_events

def ipaMichel_normalization(on_spill_time, ipa_de_min, run_mode='1BB'):
"""
Calculates the expected number of IPA (Incoming Particle Decay After Stopping)
Expand Down
Loading