#!/usr/bin/env python3
# -----------------------------------------------------------------------------
# Copyright (c) 2026 Melek Derman
#
# SPDX-License-Identifier: BSD-3-Clause
# -----------------------------------------------------------------------------
"""
Typed dataclass models for EPICS parsed datasets
Every model is a frozen-safe ``dataclass`` carrying scalar metadata and
NumPy arrays. Models are the sole output of the reader layer and the
sole input accepted by the converter layer, enforcing strict separation
of concerns.
Hierarchy
---------
::
CrossSectionRecord — energy / xs pair with optional interpolation info
DistributionRecord — flat (inc_energy, value, probability) triple
FormFactorRecord — x / y pair (momentum transfer / form factor)
SubshellTransition — single radiative or Auger transition
SubshellRelaxation — one subshell's relaxation data
EEDLDataset — full electron (EEDL) parsed output
EPDLDataset — full photon (EPDL) parsed output
EADLDataset — full atomic (EADL) parsed output
Units
-----
* Energies are in **eV** unless explicitly noted otherwise.
* Cross sections are in **barns** (ENDF convention).
* Momentum-transfer values are in **1/Å** (inverse ångström).
* Probabilities are dimensionless fractions summing to ≈ 1 per subshell.
"""
from __future__ import annotations
from dataclasses import dataclass, field
import numpy as np
# ---------------------------------------------------------------------------
# Atomic-level building blocks
# ---------------------------------------------------------------------------
[docs]
@dataclass
class CrossSectionRecord:
"""A single cross-section table (energy vs. σ)
Parameters
----------
label : str
Short mnemonic name (e.g. ``"xs_tot"``, ``"xs_K"``).
energy : numpy.ndarray
Incident-energy grid, shape ``(N,)``, units eV.
Must be monotonically non-decreasing.
cross_section : numpy.ndarray
Cross-section values, shape ``(N,)``, units barns.
Must be non-negative.
breakpoints : numpy.ndarray | None
ENDF TAB1 interpolation-region breakpoints, or ``None``.
interpolation : numpy.ndarray | None
ENDF TAB1 interpolation law codes, or ``None``.
"""
label: str
energy: np.ndarray
cross_section: np.ndarray
breakpoints: np.ndarray | None = None
interpolation: np.ndarray | None = None
[docs]
@dataclass
class DistributionRecord:
"""A flat ENDF distribution (incident energy → outgoing value / PDF)
The three arrays are aligned element-wise: for each index *k*,
``inc_energy[k]`` is the incident energy, ``value[k]`` is the
outgoing quantity (cosine μ or secondary energy E'), and
``probability[k]`` is the probability density.
Parameters
----------
label : str
Short mnemonic name (e.g. ``"ang_lge"``, ``"spec_K"``).
inc_energy : numpy.ndarray
Incident energies, shape ``(M,)``, units eV.
value : numpy.ndarray
Outgoing quantity, shape ``(M,)``.
probability : numpy.ndarray
Probability-density values, shape ``(M,)``.
"""
label: str
inc_energy: np.ndarray
value: np.ndarray
probability: np.ndarray
[docs]
@dataclass
class AverageEnergyLoss:
"""Average energy loss vs. incident energy
Parameters
----------
label : str
Short mnemonic name (e.g. ``"loss_exc"``).
energy : numpy.ndarray
Incident-energy grid, shape ``(N,)``, units eV.
avg_loss : numpy.ndarray
Average energy loss per collision, shape ``(N,)``, units eV.
"""
label: str
energy: np.ndarray
avg_loss: np.ndarray
# ---------------------------------------------------------------------------
# Atomic relaxation building blocks
# ---------------------------------------------------------------------------
[docs]
@dataclass
class SubshellTransition:
"""A single atomic-relaxation transition
Parameters
----------
origin_designator : int
EADL subshell designator of the originating electron.
origin_label : str
Human-readable label (e.g. ``"L1"``).
secondary_designator : int
Designator for the subshell that fills the vacancy.
Zero (0) indicates a radiative (X-ray) transition.
secondary_label : str
``"radiative"`` when *secondary_designator* is 0, else the
subshell label (e.g. ``"M2"``).
energy_eV : float
Transition energy (eV).
probability : float
Fractional probability of this transition occurring.
is_radiative : bool
``True`` for X-ray emission, ``False`` for Auger / Coster-Kronig.
"""
origin_designator: int
origin_label: str
secondary_designator: int
secondary_label: str
energy_eV: float
probability: float
is_radiative: bool
[docs]
@dataclass
class SubshellRelaxation:
"""Relaxation data for a single atomic subshell
Parameters
----------
designator : int
EADL numeric subshell designator (1 = K, 2 = L1, …).
name : str
Standard label (e.g. ``"K"``, ``"L1"``).
binding_energy_eV : float
Binding energy of the subshell (eV).
n_electrons : float
Number of electrons in the neutral atom.
transitions : list[SubshellTransition]
All radiative and non-radiative transitions from this subshell.
"""
designator: int
name: str
binding_energy_eV: float
n_electrons: float
transitions: list[SubshellTransition] = field(default_factory=list)
# ---------------------------------------------------------------------------
# Top-level dataset models (one per EPICS library)
# ---------------------------------------------------------------------------
[docs]
@dataclass
class EEDLDataset:
"""Complete parsed output of an EEDL (Evaluated Electron Data Library) file
Instances are returned by :meth:`EEDLReader.read` and consumed by
:func:`convert_dataset_to_hdf5`.
Parameters
----------
Z : int
Atomic number.
symbol : str
Element symbol (e.g. ``"Fe"``).
atomic_weight_ratio : float
AWR from the ENDF material header.
ZA : float
ZA identifier (Z × 1000 + A).
cross_sections : dict[str, CrossSectionRecord]
Keyed by abbreviation (e.g. ``"xs_tot"``, ``"xs_K"``).
distributions : dict[str, DistributionRecord]
Keyed by abbreviation (e.g. ``"ang_lge"``, ``"spec_K"``).
average_energy_losses : dict[str, AverageEnergyLoss]
Keyed by abbreviation (e.g. ``"loss_exc"``, ``"loss_brem_spec"``).
bremsstrahlung_spectra : DistributionRecord | None
Bremsstrahlung photon energy spectrum, if present.
"""
Z: int
symbol: str
atomic_weight_ratio: float
ZA: float
cross_sections: dict[str, CrossSectionRecord] = field(default_factory=dict)
distributions: dict[str, DistributionRecord] = field(default_factory=dict)
average_energy_losses: dict[str, AverageEnergyLoss] = field(default_factory=dict)
bremsstrahlung_spectra: DistributionRecord | None = None
[docs]
@dataclass
class EPDLDataset:
"""Complete parsed output of an EPDL (Evaluated Photon Data Library) file
Instances are returned by :meth:`EPDLReader.read` and consumed by
:func:`convert_dataset_to_hdf5`.
Parameters
----------
Z : int
Atomic number.
symbol : str
Element symbol.
atomic_weight_ratio : float
AWR from the ENDF material header.
ZA : float
ZA identifier.
cross_sections : dict[str, CrossSectionRecord]
Photon cross sections keyed by abbreviation.
form_factors : dict[str, FormFactorRecord]
Form factors / scattering functions keyed by abbreviation.
"""
Z: int
symbol: str
atomic_weight_ratio: float
ZA: float
cross_sections: dict[str, CrossSectionRecord] = field(default_factory=dict)
form_factors: dict[str, FormFactorRecord] = field(default_factory=dict)
[docs]
@dataclass
class EADLDataset:
"""Complete parsed output of an EADL (Evaluated Atomic Data Library) file
Instances are returned by :meth:`EADLReader.read` and consumed by
:func:`convert_dataset_to_hdf5`.
Parameters
----------
Z : int
Atomic number.
symbol : str
Element symbol.
atomic_weight_ratio : float
AWR from the ENDF material header.
ZA : float
ZA identifier.
n_subshells : int
Number of subshells for which relaxation data is given.
subshells : dict[str, SubshellRelaxation]
Relaxation data keyed by subshell label (e.g. ``"K"``).
"""
Z: int
symbol: str
atomic_weight_ratio: float
ZA: float
n_subshells: int = 0
subshells: dict[str, SubshellRelaxation] = field(default_factory=dict)