feat: polish CSV header format with improved metadata and column naming

This commit is contained in:
2026-07-03 14:58:19 +03:00
parent 876e1e3ab8
commit a7eef7bc20
7 changed files with 86 additions and 577 deletions
+68 -28
View File
@@ -628,11 +628,9 @@ class BetaSpectrumAnalyzer:
total = self.total_spectrum(normalize=True)
components = self.components
# Only include enabled corrections that are not basic components
# Basic components (phase_space and fermi) are always used and should not be listed
enabled = []
if self.config.use_phase_space:
enabled.append("phase_space")
if self.config.use_fermi:
enabled.append("fermi")
if self.config.use_screening:
enabled.append("screening")
if self.config.use_finite_size:
@@ -647,10 +645,19 @@ class BetaSpectrumAnalyzer:
parent_symbol = self._element_symbol(self.config.Z_parent)
daughter_symbol = self._element_symbol(self.config.Z_daughter)
# Get git commit hash
git_commit = get_git_short_hash()
# Format source line with paceENSDF URL if applicable
if source_type == "paceENSDF":
source_line = "# source: paceENSDF (https://pypi.org/project/paceENSDF/)"
else:
source_line = f"# source: {source_type}"
header_lines = [
f"# beta-spectrum v{__import__('beta_spectrum').__version__}",
f"# beta-spectrum v{__import__('beta_spectrum').__version__}(build.{git_commit})",
f"# timestamp: {datetime.now(timezone.utc).isoformat()}",
f"# source: {source_type}",
source_line,
f"# nuclide: {parent_symbol}{self.config.A_number} -> {daughter_symbol}{self.config.A_number} (Z={self.config.Z_parent}->{self.config.Z_daughter})",
f"# basic_endpoint: {self.config.endpoint_MeV * 1000:.1f} keV",
]
@@ -667,49 +674,82 @@ class BetaSpectrumAnalyzer:
header_lines.append(f"# corrections: {', '.join(enabled)}")
header_lines.append(f"# e_step: {self.config.e_step_MeV:.4f} MeV")
header_lines.append(f"# n_points: {len(self.energies_MeV)}")
# Add normalization mode
header_lines.append("# branch normalization: intensity")
# Separate branches into active and excluded lists
active_branches = []
excluded_branches = []
# Add transition types for all branches
transition_types = []
for i, branch in enumerate(self._unified_branches):
transition_types.append(f"branch_{i + 1}:{branch.transition_type}")
header_lines.append(f"# transition_types: {'; '.join(transition_types)}")
branch_endpoint_keV = branch.endpoint_MeV * 1000
neutrino_mass_keV = self.config.neutrino_mass_MeV * 1000
# Branch info — unified view
for i, branch in enumerate(self._unified_branches):
header_lines.append(
f"# branch_{i + 1}: E₀={branch.endpoint_MeV * 1000:.1f} keV, "
f"transition={branch.transition_type}, "
f"intensity={branch.intensity:.4f}"
)
# Check if branch is excluded due to neutrino mass
if branch_endpoint_keV <= neutrino_mass_keV:
excluded_branches.append(
f" branch_{i + 1}: E₀={branch_endpoint_keV:.1f} keV, transition={branch.transition_type}, intensity={branch.intensity:.4f} - excluded: endpoint < m_nu"
)
# Check if branch is excluded due to intensity cutoff
elif branch.intensity < self.config.intensity_cutoff:
excluded_branches.append(
f" branch_{i + 1}: E₀={branch_endpoint_keV:.1f} keV, transition={branch.transition_type}, intensity={branch.intensity:.4f} - excluded: intensity < cutoff"
)
else:
active_branches.append(
f" branch_{i + 1}: E₀={branch_endpoint_keV:.1f} keV, transition={branch.transition_type}, intensity={branch.intensity:.4f}"
)
header_lines.append(f"# git_commit: {get_git_short_hash()}")
# Add active branches list
if active_branches:
header_lines.append("# active branches:")
for branch_info in active_branches:
header_lines.append(f"# {branch_info}")
# Add excluded branches list if there are any
if excluded_branches:
header_lines.append("# excluded branches:")
for branch_info in excluded_branches:
header_lines.append(f"# {branch_info}")
# Create data dictionary with concise column headers
data: Dict[str, np.ndarray] = {
"energy_MeV": self.energies_MeV,
"spectrum": total,
"total_spectrum": total,
}
if csv_verbosity == "full":
# Per-branch spectra and components
# Per-branch spectra and components with concise branch naming
for i in range(self._n_branches):
data[f"branch_{i + 1}_spectrum"] = self._get_branch_spectrum(i)
data[f"branch {i + 1}"] = self._get_branch_spectrum(i)
for comp_name in ["PhaseSpace", "Radiative"]:
key = self._get_component_key(i, comp_name)
if key in components:
data[f"branch_{i + 1}_{comp_name}"] = components[key]
data[f"branch {i + 1}_{comp_name}"] = components[key]
# Universal components (no prefix)
for name, values in components.items():
if not name.startswith("branch_"):
data[name] = values
elif csv_verbosity == "concise":
# Only total spectrum and normalized branch components
# Only total spectrum and normalized branch components with concise naming
for i in range(self._n_branches):
data[f"branch_{i + 1}_spectrum_normalized"] = (
self._get_branch_spectrum(i) / np.sum(self._get_branch_spectrum(i))
if np.sum(self._get_branch_spectrum(i)) > 0
else self._get_branch_spectrum(i)
)
branch_spectrum = self._get_branch_spectrum(i)
# Check if branch is degenerate (neutrino mass >= branch endpoint)
branch_endpoint_keV = self._unified_branches[i].endpoint_MeV * 1000
neutrino_mass_keV = self.config.neutrino_mass_MeV * 1000
if branch_endpoint_keV <= neutrino_mass_keV:
# Branch is degenerate, spectrum should be all zeros
data[f"branch {i + 1}"] = np.zeros_like(branch_spectrum)
else:
# Branch is valid, normalize as usual
sum_spectrum = np.sum(branch_spectrum)
if sum_spectrum > 0:
data[f"branch {i + 1}"] = branch_spectrum / sum_spectrum
else:
data[f"branch {i + 1}"] = branch_spectrum
df = pd.DataFrame(data)
df.to_csv(filename, index=False, float_format="%.4e")