feat: polish CSV header format with improved metadata and column naming
This commit is contained in:
@@ -15,6 +15,7 @@ from beta_spectrum.components.screening import ScreeningCorrection
|
||||
from beta_spectrum.components.exchange import ExchangeCorrection
|
||||
from beta_spectrum.components.radiative import RadiativeCorrection
|
||||
from beta_spectrum.utils import T_to_W
|
||||
from beta_spectrum.constants import ME_MEV
|
||||
|
||||
|
||||
@dataclass
|
||||
@@ -250,11 +251,13 @@ class BetaSpectrum:
|
||||
W0 = float(T_to_W(config.endpoint_MeV))
|
||||
|
||||
if config.use_phase_space:
|
||||
# Convert neutrino mass from MeV to natural units
|
||||
m_nu_natural = config.neutrino_mass_MeV / ME_MEV
|
||||
components.append(
|
||||
PhaseSpace(
|
||||
W0=W0,
|
||||
transition_type=config.transition_type,
|
||||
m_nu=config.neutrino_mass_MeV,
|
||||
m_nu=m_nu_natural,
|
||||
logger=logger,
|
||||
)
|
||||
)
|
||||
|
||||
@@ -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")
|
||||
|
||||
Reference in New Issue
Block a user