refeff-io 0.2.0

FEFF file-format readers and writers (feff.inp, .dat/.bin handoffs, PAD encoding) for the refeff FEFF10 port
Documentation
use num_complex::Complex64;

use crate::error::{IoError, Result};

use super::common::{check_fixed_int, i64_from_usize, invalid_feff_bin, validate_label};
use super::types::{FeffBinData, FeffBinPath};

pub(super) fn validate_feff_bin(data: &FeffBinData) -> Result<()> {
    if data.pad_width <= 2 {
        return Err(IoError::InvalidPadWidth(data.pad_width));
    }
    if data.energy_count() == 0 {
        return Err(invalid_feff_bin("ne", "at least one energy is required"));
    }
    if data.potential_count() == 0 {
        return Err(invalid_feff_bin(
            "npot",
            "at least one potential is required",
        ));
    }
    if data.version.trim().is_empty() || !data.version.is_ascii() {
        return Err(invalid_feff_bin(
            "version",
            "version must be non-empty ASCII text",
        ));
    }
    validate_len("ck", data.complex_momentum.len(), data.energy_count())?;
    validate_len("xk", data.real_momentum.len(), data.energy_count())?;
    validate_finite_complex("phc", data.central_phase_shift.iter().copied())?;
    validate_finite_complex("ck", data.complex_momentum.iter().copied())?;
    validate_finite_reals("xk", data.real_momentum.iter().copied())?;
    validate_finite_reals(
        "misc",
        [
            data.average_norman_radius,
            data.fermi_level,
            data.edge_energy,
        ],
    )?;

    for potential in &data.potentials {
        validate_label(&potential.label)?;
        check_fixed_int(i64_from_usize(potential.atomic_number, "iz")?, 3, "iz")?;
    }
    for path in &data.paths {
        validate_path(path, data.energy_count(), data.potential_count())?;
    }
    Ok(())
}

fn validate_path(path: &FeffBinPath, energy_count: usize, potential_count: usize) -> Result<()> {
    let leg_count = path.leg_count();
    if leg_count == 0 {
        return Err(invalid_feff_bin(
            "nleg",
            "at least one path leg is required",
        ));
    }
    check_fixed_int(i64_from_usize(path.index, "index")?, 6, "index")?;
    check_fixed_int(i64_from_usize(leg_count, "nleg")?, 3, "nleg")?;
    validate_shape2("rat", path.positions.dim(), (leg_count, 3))?;
    validate_len("beta", path.beta.len(), leg_count)?;
    validate_len("eta", path.eta.len(), leg_count)?;
    validate_len("ri", path.leg_distances.len(), leg_count)?;
    validate_len("achi", path.amplitude.len(), energy_count)?;
    validate_len("phchi", path.phase.len(), energy_count)?;
    validate_finite_reals(
        "path",
        [
            path.degeneracy,
            path.effective_half_path_length_bohr,
            path.criterion,
        ],
    )?;
    validate_finite_reals("rat", path.positions.iter().copied())?;
    validate_finite_reals("beta", path.beta.iter().copied())?;
    validate_finite_reals("eta", path.eta.iter().copied())?;
    validate_finite_reals("ri", path.leg_distances.iter().copied())?;
    validate_finite_reals("achi", path.amplitude.iter().copied())?;
    validate_finite_reals("phchi", path.phase.iter().copied())?;
    for &potential in &path.potential_indices {
        if potential >= potential_count {
            return Err(invalid_feff_bin(
                "ipot",
                format!("potential index {potential} is outside 0..{potential_count}"),
            ));
        }
        check_fixed_int(i64_from_usize(potential, "ipot")?, 2, "ipot")?;
    }
    Ok(())
}

fn validate_len(field: &'static str, actual: usize, expected: usize) -> Result<()> {
    if actual == expected {
        Ok(())
    } else {
        Err(IoError::FeffBinShape {
            field,
            actual: vec![actual],
            expected: vec![expected],
        })
    }
}

fn validate_shape2(
    field: &'static str,
    actual: (usize, usize),
    expected: (usize, usize),
) -> Result<()> {
    if actual == expected {
        Ok(())
    } else {
        Err(IoError::FeffBinShape {
            field,
            actual: vec![actual.0, actual.1],
            expected: vec![expected.0, expected.1],
        })
    }
}

fn validate_finite_reals(field: &'static str, values: impl IntoIterator<Item = f64>) -> Result<()> {
    for value in values {
        if !value.is_finite() {
            return Err(invalid_feff_bin(
                field,
                format!("value must be finite, got {value}"),
            ));
        }
    }
    Ok(())
}

fn validate_finite_complex(
    field: &'static str,
    values: impl IntoIterator<Item = Complex64>,
) -> Result<()> {
    for value in values {
        if !value.re.is_finite() || !value.im.is_finite() {
            return Err(invalid_feff_bin(
                field,
                format!("value must be finite, got {value}"),
            ));
        }
    }
    Ok(())
}