LISE agreement

This commit is contained in:
James Szalkie 2026-07-20 13:51:50 -04:00
parent 26be64416c
commit 9848725f58
6 changed files with 30074 additions and 11023 deletions

View File

@ -104,6 +104,7 @@ int main(int argc, char **argv){
transfer.Setb(1, 1); // outgoing proton from the primary transfer
transfer.SetB(30, 14); // 21Na* heavy product
const double beamA = 27; // mass number of 27Al beam
const double beamE = 72.0 / 27.0; // beam energy in MeV
bool enableSequentialDecay = false; // turning to false to disable sequential decay for now, can be set to true to enable
const int decayDaughterA = 20;
@ -363,6 +364,7 @@ int main(int argc, char **argv){
beamEnergy = elossBeam->Eval(beamPath_cm); // MeV
beamEnergyLoss = elossBeam->Eval(0.0) - beamEnergy;
KEA = beamEnergy / beamA;
//KEA = gRandom->Uniform(0, beamE);
transfer.SetIncidentEnergyAngle(KEA, 0, 0);
transfer.CalReactionConstant();

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -84,6 +84,17 @@ def get_loss_table_path(medium, particle_label):
return os.path.join(script_dir, f"{medium}Loss", f"E_vs_x_{particle_label}.dat")
def normalize_particle_label(particle):
"""Return the canonical table label for a particle or isotope name."""
if isinstance(particle, str):
try:
_, _, _, label = resolve_particle(particle)
return label
except Exception:
return particle.strip()
return str(particle)
def clear_interpolator_cache():
"""Drop cached energy-loss interpolators and force Python to release memory."""
global interp_cache
@ -111,30 +122,23 @@ def make_E_vs_x(
p_pa = P_TORR * 133.322
molar_density = p_pa / (R * TEMP_K) # mol/m^3
# ---------------------------
# Medium definition
# ---------------------------
if medium == "He":
m_he = 4.0026
m_c = 12.0000
m_o = 15.9949
# FIX: do NOT double count oxygen
"""
material_def = [
(m_he, 2, 0.96),
((m_c + 2 * m_o), 18, 0.04) # CO2 lumped correctly
]
(m_c, 6, 0.04),
(m_o, 8, 0.08)
] """
material_def = [(m_he, 2, 1.0)]
#m_mix_avg = 0.96 * m_he + 0.04 * (m_c + 2 * m_o)
m_mix_avg = m_he
#rho_g_cm3 = (molar_density * m_mix_avg) / 1e6
if z == 2:
rho_g_cm3 = 0.00017
#rho_g_cm3 = 8.2928e-5
else:
rho_g_cm3 = 8.2928e-5
m_mix_avg = 1.0 * m_he
rho_g_cm3 = (molar_density * m_mix_avg) / 1e6
gas = catima.Material(material_def)
gas.density(rho_g_cm3)
@ -156,10 +160,10 @@ def make_E_vs_x(
E = np.linspace(0.1, emax_mev, npoints)
S_mass = np.zeros_like(E)
for i, energy in enumerate(E):
projectile.T(energy)
# CATIMA expects projectile energy in MeV/u, while the table axis stays in total MeV.
projectile.T(energy / mass_u)
S_mass[i] = catima.dedx(projectile, gas) # MeV/(g/cm^2)
# convert to MeV/cm
S_linear = S_mass * rho_g_cm3
invS = 1.0 / np.clip(S_linear, 1e-30, None)
x = cumulative_trapezoid(invS[::-1], E[::-1], initial=0)
@ -206,12 +210,38 @@ def load_table(filename):
def get_interpolators(particle, medium):
cache_key = (particle.lower(), medium.lower())
canonical_particle = normalize_particle_label(particle)
cache_key = (canonical_particle.lower(), medium.lower())
if cache_key in interp_cache:
return interp_cache[cache_key]
filename = get_loss_table_path(medium, particle)
candidate_labels = [canonical_particle]
if isinstance(particle, str):
raw_particle = particle.strip()
candidate_labels.extend([
raw_particle,
canonical_particle.replace("-", "")
])
filename = None
seen_candidates = set()
for label in candidate_labels:
normalized_label = label.strip()
if not normalized_label:
continue
key = normalized_label.lower()
if key in seen_candidates:
continue
seen_candidates.add(key)
candidate_filename = get_loss_table_path(medium, normalized_label)
if os.path.exists(candidate_filename):
filename = candidate_filename
break
if filename is None:
filename = get_loss_table_path(medium, canonical_particle)
x, E = load_table(filename)
@ -275,11 +305,29 @@ def resolve_particle(name):
name = name.lower().strip().rstrip("s")
if name in particles:
return particles[name]
match = re.match(r"([a-zA-Z]+)[-\s]?(\d+)", name)
match = re.match(r"([a-zA-Z]+)[-\s]?(\d+)$", name)
if match:
element_symbol = match.group(1).capitalize()
A = int(match.group(2))
try:
element = pt.elements.symbol(element_symbol)
isotope = element[A]
return (
isotope.number, # Z
isotope.mass, # mass in u
30.0,
f"{element_symbol}-{A}"
)
except Exception:
raise ValueError(f"Unknown isotope: {name}")
match = re.match(r"(\d+)[-\s]?([a-zA-Z]+)$", name)
if match:
A = int(match.group(1))
element_symbol = match.group(2).capitalize()
try:
element = pt.elements.symbol(element_symbol)
isotope = element[A]
@ -898,7 +946,7 @@ class MyInteractiveApp(cmd.Cmd):
if dl > 0:
print(f"\nChange in energy: {dE:.6f} MeV")
print(f"Distance travelled: {dl:.6f} cm\n")
print(f"{(dl * 10000):.6f} microns")
print(f"{(dl * 10):.6f} mm")
else:
print("Error: remake table with larger value, fallen off map")
except:
@ -1343,7 +1391,7 @@ class MyInteractiveApp(cmd.Cmd):
plt.figure(figsize=(7,6))
mask1 = ~np.isnan(Esx3) & ~np.isnan(Edet) & sx3_theta_plot_mask
plt.hist2d(Esx3[mask1], Edet[mask1], bins=300)
plt.hist2d(Esx3[mask1], Edet[mask1], bins=300, norm="log")
plt.xlabel("Esx3")
plt.ylabel("Edet")
plt.title("Esx3 vs Edet")
@ -1356,12 +1404,12 @@ class MyInteractiveApp(cmd.Cmd):
mask1 = ~np.isnan(qqqE) & ~np.isnan(EdetQ) & qqq_theta_plot_mask
plt.figure(figsize=(7,6))
plt.hist2d(qqqE[mask1], EdetQ[mask1], bins=300)
plt.hist2d(qqqE[mask1], EdetQ[mask1], bins=300, norm="log")
plt.xlabel("Eqqq")
plt.ylabel("Edet")
plt.title("Eqqq vs Edet")
plt.yscale("log")
plt.xscale("log")
#plt.yscale("log")
#plt.xscale("log")
plt.colorbar(label="counts")
plt.tight_layout()
plt.savefig(f"{base}/Eqqq_vs_Edet.png")
@ -1418,6 +1466,7 @@ class MyInteractiveApp(cmd.Cmd):
plt.plot(theta_deg[order], path_cm[order], color='red', linewidth=2, label='0.1/sin(theta)')
plt.legend()
plt.tight_layout()
plt.gca().invert_xaxis()
plt.savefig(f"{base}/Range_vs_theta_with_path.png", dpi=300)
plt.show()
except Exception: