"""
Validation functions for XRayLabTool.
This module contains functions for validating input parameters,
chemical formulas, energy ranges, and other data used in calculations.
"""
from __future__ import annotations
import math
import numbers
import re
from typing import TYPE_CHECKING
from xraylabtool.backend import ops
from xraylabtool.exceptions import EnergyError, FormulaError, ValidationError
if TYPE_CHECKING:
import numpy as np
[docs]
def validate_energy_range(
energies: float | np.ndarray,
min_energy: float = 0.1,
max_energy: float = 100.0,
) -> np.ndarray:
"""
Validate X-ray energy values.
Args:
energies: Energy value(s) in keV
min_energy: Minimum allowed energy in keV
max_energy: Maximum allowed energy in keV
Returns:
Validated energy array
Raises:
EnergyError: If energies are invalid
"""
energy_array = ops.asarray(energies)
# Check for NaN or infinite values
if ops.any(~ops.isfinite(energy_array)):
raise EnergyError(
"Energy values must be finite", valid_range=f"{min_energy}-{max_energy} keV"
)
# Check for positive values
if ops.any(energy_array <= 0):
raise EnergyError(
"Energy values must be positive",
valid_range=f"{min_energy}-{max_energy} keV",
)
# Check energy range
if ops.any(energy_array < min_energy):
raise EnergyError(
"Energy below minimum allowed value",
energy=float(energy_array.min()),
valid_range=f"{min_energy}-{max_energy} keV",
)
if ops.any(energy_array > max_energy):
raise EnergyError(
"Energy above maximum allowed value",
energy=float(energy_array.max()),
valid_range=f"{min_energy}-{max_energy} keV",
)
return energy_array # type: ignore[no-any-return]
[docs]
def validate_density(
density: float, min_density: float = 0.001, max_density: float = 30.0
) -> float:
"""
Validate material density value.
Args:
density: Density in g/cm³
min_density: Minimum allowed density
max_density: Maximum allowed density
Returns:
Validated density value
Raises:
ValidationError: If density is invalid
"""
if not isinstance(density, int | float | numbers.Number):
raise ValidationError(
"Density must be a numeric value", parameter="density", value=density
)
if not math.isfinite(density):
raise ValidationError(
"Density must be finite", parameter="density", value=density
)
if density <= 0:
raise ValidationError(
"Density must be positive", parameter="density", value=density
)
if density < min_density:
raise ValidationError(
f"Density below minimum allowed value ({min_density} g/cm³)",
parameter="density",
value=density,
)
if density > max_density:
raise ValidationError(
f"Density above maximum allowed value ({max_density} g/cm³)",
parameter="density",
value=density,
)
return float(density)
[docs]
def validate_calculation_parameters(
formula: str, energies: float | np.ndarray, density: float
) -> tuple[str, np.ndarray, float]:
"""
Validate all parameters for X-ray calculations.
Args:
formula: Chemical formula
energies: Energy values in keV
density: Material density in g/cm³
Returns:
Tuple of validated (formula, energies, density)
Raises:
ValidationError: If any parameters are invalid
"""
# Validate formula
validate_chemical_formula(formula)
# Validate energies
validated_energies = validate_energy_range(energies)
# Validate density
validated_density = validate_density(density)
return formula, validated_energies, validated_density
def _parse_formula(formula: str) -> dict[str, float]:
"""Parse chemical formula into elements and quantities.
Delegates to the canonical :func:`xraylabtool.utils.parse_formula`.
"""
from xraylabtool.utils import parse_formula
symbols, counts = parse_formula(formula)
return dict(zip(symbols, counts, strict=True))
def _get_valid_element_symbols() -> set[str]:
"""Get set of valid element symbols."""
# Simplified list - in production, would use complete periodic table
return {
"H",
"He",
"Li",
"Be",
"B",
"C",
"N",
"O",
"F",
"Ne",
"Na",
"Mg",
"Al",
"Si",
"P",
"S",
"Cl",
"Ar",
"K",
"Ca",
"Sc",
"Ti",
"V",
"Cr",
"Mn",
"Fe",
"Co",
"Ni",
"Cu",
"Zn",
"Ga",
"Ge",
"As",
"Se",
"Br",
"Kr",
"Rb",
"Sr",
"Y",
"Zr",
"Nb",
"Mo",
"Tc",
"Ru",
"Rh",
"Pd",
"Ag",
"Cd",
"In",
"Sn",
"Sb",
"Te",
"I",
"Xe",
"Cs",
"Ba",
"La",
"Ce",
"Pr",
"Nd",
"Pm",
"Sm",
"Eu",
"Gd",
"Tb",
"Dy",
"Ho",
"Er",
"Tm",
"Yb",
"Lu",
"Hf",
"Ta",
"W",
"Re",
"Os",
"Ir",
"Pt",
"Au",
"Hg",
"Tl",
"Pb",
"Bi",
"Po",
"At",
"Rn",
}