Add massive neutrino support and improve CSV/CLI output

- Add neutrino mass parameter to SpectrumConfig and CLI interface
- Update PhaseSpace component to handle neutrino mass effects properly
- Update CSV headers to show basic endpoint and all transition types
- Add --csv-verbosity option to CLI with 'full' and 'concise' options
- Fix comparison plots to properly show linear scale variations
- Add comprehensive tests for neutrino mass functionality
This commit is contained in:
2026-07-02 10:33:28 +03:00
parent 406fd57bcd
commit 876e1e3ab8
48 changed files with 5213 additions and 97 deletions
+144
View File
@@ -0,0 +1,144 @@
#!/usr/bin/env python3
"""
Script to create a comparison plot of total beta spectra for edge neutrino masses (1.5 keV and 2.5 keV)
"""
import pandas as pd
import matplotlib.pyplot as plt
import numpy as np
# Input files
mass_1_5_csv = "/home/unzhakov/workspace/massive_nu_pr/pr144_nu_mass_1_50keV.csv"
mass_2_45_csv = "/home/unzhakov/workspace/massive_nu_pr/pr144_nu_mass_2_45keV.csv"
# Output file
output_png = (
"/home/unzhakov/workspace/massive_nu_pr/pr144_comparison_1_5_vs_2_45_keV.png"
)
print(f"Reading data from {mass_1_5_csv} and {mass_2_45_csv}...")
# Read CSV files, skipping metadata headers
# The metadata headers start with '#'
def read_csv_with_metadata(filepath):
with open(filepath, "r") as f:
lines = f.readlines()
# Find the line where data starts (not starting with '#')
data_start_line = 0
for i, line in enumerate(lines):
if not line.startswith("#"):
data_start_line = i
break
# Read the data
df = pd.read_csv(filepath, skiprows=data_start_line)
return df
# Read the data
df_1_5 = read_csv_with_metadata(mass_1_5_csv)
df_2_45 = read_csv_with_metadata(mass_2_45_csv)
print("Data loaded successfully.")
# Extract energy and spectrum data
energies_1_5 = df_1_5["energy_MeV"].values
spectrum_1_5 = df_1_5["spectrum"].values
energies_2_45 = df_2_45["energy_MeV"].values
spectrum_2_45 = df_2_45["spectrum"].values
print(
f"Energy range: {energies_1_5[0] * 1000:.1f} eV to {energies_1_5[-1] * 1000:.1f} eV"
)
# Create comparison plot
fig, ax = plt.subplots(figsize=(12, 8))
# Plot both spectra
ax.plot(energies_1_5 * 1000, spectrum_1_5, "b-", lw=2, label="m_ν = 1.5 keV")
ax.plot(energies_2_45 * 1000, spectrum_2_45, "r--", lw=2, label="m_ν = 2.45 keV")
# Set labels and title
ax.set_xlabel(r"Electron kinetic energy $E$ [keV]", fontsize=12)
ax.set_ylabel("Normalized Spectrum", fontsize=12)
ax.set_title(
"Pr144 -> Nd144 Beta Decay: Comparison of Neutrino Mass Effects\n"
"(1.5 keV vs 2.45 keV)",
fontsize=14,
)
# Set log scale for y-axis to see the endpoint region clearly
ax.set_yscale("log")
# Add grid
ax.grid(True, alpha=0.3)
# Add legend
ax.legend(fontsize=12)
# Focus on the endpoint region (last part of the spectrum)
# Find the energy range near the endpoint
max_energy_keV = max(energies_1_5) * 1000
# Show last 10 keV of the spectrum
start_energy_keV = max_energy_keV - 10.0
ax.set_xlim(start_energy_keV, max_energy_keV)
# Save the plot
plt.tight_layout()
plt.savefig(output_png, dpi=150, bbox_inches="tight")
print(f"Comparison plot saved to {output_png}")
# Also create a linear scale plot for the endpoint region
fig_linear, ax_linear = plt.subplots(figsize=(12, 6))
# Plot both spectra in linear scale
ax_linear.plot(energies_1_5 * 1000, spectrum_1_5, "b-", lw=2, label="m_ν = 1.5 keV")
ax_linear.plot(energies_2_45 * 1000, spectrum_2_45, "r--", lw=2, label="m_ν = 2.45 keV")
# Set labels and title
ax_linear.set_xlabel(r"Electron kinetic energy $E$ [keV]", fontsize=12)
ax_linear.set_ylabel("Normalized Spectrum", fontsize=12)
ax_linear.set_title(
"Pr144 -> Nd144 Beta Decay: Comparison of Neutrino Mass Effects (Endpoint Region)\n"
"(1.5 keV vs 2.45 keV) - Linear Scale",
fontsize=14,
)
# Set linear scale for y-axis
ax_linear.set_yscale("linear")
# Set y-axis limits to show the variation in the spectrum
# Find the min and max values in the endpoint region to set appropriate limits
min_spec_1_5 = np.min(spectrum_1_5[-100:]) # Last 100 points
max_spec_1_5 = np.max(spectrum_1_5[-100:])
min_spec_2_45 = np.min(spectrum_2_45[-100:])
max_spec_2_45 = np.max(spectrum_2_45[-100:])
overall_min = (
min(min_spec_1_5, min_spec_2_45) * 0.9
if min(min_spec_1_5, min_spec_2_45) > 0
else 0
)
overall_max = max(max_spec_1_5, max_spec_2_45) * 1.1
ax_linear.set_ylim(overall_min, overall_max)
# Add grid
ax_linear.grid(True, alpha=0.3)
# Add legend
ax_linear.legend(fontsize=12)
# Focus on the endpoint region (last part of the spectrum)
ax_linear.set_xlim(start_energy_keV, max_energy_keV)
# Save the linear plot
plt.tight_layout()
linear_output_png = (
"/home/unzhakov/workspace/massive_nu_pr/pr144_comparison_1_5_vs_2_45_keV_linear.png"
)
plt.savefig(linear_output_png, dpi=150, bbox_inches="tight")
print(f"Linear comparison plot saved to {linear_output_png}")