use std::fmt::Write as _;
use std::path::Path;
use ndarray::Array1;
use crate::error::{IoError, Result};
use crate::format::write_fortran_exp;
const DANES_DAT_ROW_WIDTH: usize = 7;
#[derive(Debug, Clone, PartialEq)]
pub struct DanesDatData {
pub header_lines: Vec<String>,
pub energy_ev: Array1<f64>,
pub matsubara: Array1<f64>,
pub sommerfeld: Array1<f64>,
pub anomalous: Array1<f64>,
pub tail: Array1<f64>,
pub total: Array1<f64>,
pub difference: Array1<f64>,
}
impl DanesDatData {
#[must_use]
pub fn point_count(&self) -> usize {
self.energy_ev.len()
}
}
pub fn danes_dat_string(data: &DanesDatData) -> Result<String> {
validate_danes_dat(data)?;
let mut out = String::new();
for line in &data.header_lines {
writeln!(out, "{line}")?;
}
for ((((((energy, matsubara), sommerfeld), anomalous), tail), total), difference) in data
.energy_ev
.iter()
.zip(data.matsubara.iter())
.zip(data.sommerfeld.iter())
.zip(data.anomalous.iter())
.zip(data.tail.iter())
.zip(data.total.iter())
.zip(data.difference.iter())
{
out.push(' ');
write_fortran_exp(&mut out, *energy, 11, 4)?;
out.push(' ');
write_fortran_exp(&mut out, *matsubara, 11, 4)?;
out.push(' ');
write_fortran_exp(&mut out, *sommerfeld, 11, 4)?;
out.push(' ');
write_fortran_exp(&mut out, *anomalous, 11, 4)?;
out.push(' ');
write_fortran_exp(&mut out, *tail, 11, 4)?;
out.push(' ');
write_fortran_exp(&mut out, *total, 11, 4)?;
out.push(' ');
write_fortran_exp(&mut out, *difference, 11, 4)?;
out.push('\n');
}
Ok(out)
}
pub fn parse_danes_dat(text: &str) -> Result<DanesDatData> {
let mut header_lines = Vec::new();
let mut energy_ev = Vec::new();
let mut matsubara = Vec::new();
let mut sommerfeld = Vec::new();
let mut anomalous = Vec::new();
let mut tail = Vec::new();
let mut total = Vec::new();
let mut difference = Vec::new();
for (index, raw) in text.lines().enumerate() {
let line_number = index + 1;
let line = raw.trim_end();
let tokens = line.split_whitespace().collect::<Vec<_>>();
if tokens.first().is_some_and(|token| is_numeric_token(token)) {
if tokens.len() != DANES_DAT_ROW_WIDTH {
return Err(IoError::DanesDatRowWidth {
line: line_number,
actual: tokens.len(),
expected: DANES_DAT_ROW_WIDTH,
});
}
energy_ev.push(parse_f64(line_number, "energy", tokens[0])?);
matsubara.push(parse_f64(line_number, "matsubara", tokens[1])?);
sommerfeld.push(parse_f64(line_number, "sommerfeld", tokens[2])?);
anomalous.push(parse_f64(line_number, "anomalous", tokens[3])?);
tail.push(parse_f64(line_number, "tail", tokens[4])?);
total.push(parse_f64(line_number, "total", tokens[5])?);
difference.push(parse_f64(line_number, "difference", tokens[6])?);
} else {
header_lines.push(line.to_string());
}
}
let data = DanesDatData {
header_lines,
energy_ev: Array1::from_vec(energy_ev),
matsubara: Array1::from_vec(matsubara),
sommerfeld: Array1::from_vec(sommerfeld),
anomalous: Array1::from_vec(anomalous),
tail: Array1::from_vec(tail),
total: Array1::from_vec(total),
difference: Array1::from_vec(difference),
};
validate_danes_dat(&data)?;
Ok(data)
}
pub fn write_danes_dat(path: impl AsRef<Path>, data: &DanesDatData) -> Result<()> {
let path = path.as_ref();
std::fs::write(path, danes_dat_string(data)?).map_err(|source| IoError::io(path, source))
}
pub fn read_danes_dat(path: impl AsRef<Path>) -> Result<DanesDatData> {
let path = path.as_ref();
let text = std::fs::read_to_string(path).map_err(|source| IoError::io(path, source))?;
parse_danes_dat(&text)
}
fn validate_danes_dat(data: &DanesDatData) -> Result<()> {
let point_count = data.point_count();
if point_count == 0 {
return Err(invalid_danes_dat(
"rows",
"at least one spectrum row is required",
));
}
validate_len("matsubara", data.matsubara.len(), point_count)?;
validate_len("sommerfeld", data.sommerfeld.len(), point_count)?;
validate_len("anomalous", data.anomalous.len(), point_count)?;
validate_len("tail", data.tail.len(), point_count)?;
validate_len("total", data.total.len(), point_count)?;
validate_len("difference", data.difference.len(), point_count)?;
for (row, ((((((energy, matsubara), sommerfeld), anomalous), tail), total), difference)) in data
.energy_ev
.iter()
.zip(data.matsubara.iter())
.zip(data.sommerfeld.iter())
.zip(data.anomalous.iter())
.zip(data.tail.iter())
.zip(data.total.iter())
.zip(data.difference.iter())
.enumerate()
{
let row = row + 1;
validate_finite_row("energy", *energy, row)?;
validate_finite_row("matsubara", *matsubara, row)?;
validate_finite_row("sommerfeld", *sommerfeld, row)?;
validate_finite_row("anomalous", *anomalous, row)?;
validate_finite_row("tail", *tail, row)?;
validate_finite_row("total", *total, row)?;
validate_finite_row("difference", *difference, row)?;
}
Ok(())
}
fn validate_len(field: &'static str, actual: usize, expected: usize) -> Result<()> {
if actual == expected {
Ok(())
} else {
Err(IoError::DanesDatShape {
field,
actual,
expected,
})
}
}
fn parse_f64(line: usize, field: &'static str, token: &str) -> Result<f64> {
token
.replace(['D', 'd'], "E")
.parse::<f64>()
.map_err(|_| IoError::DanesDatParse {
field,
line,
token: token.to_string(),
})
}
fn validate_finite_row(field: &'static str, value: f64, row: usize) -> Result<()> {
if value.is_finite() {
Ok(())
} else {
Err(IoError::InvalidDanesDat {
field,
message: format!("row {row} value must be finite"),
})
}
}
fn invalid_danes_dat(field: &'static str, message: impl Into<String>) -> IoError {
IoError::InvalidDanesDat {
field,
message: message.into(),
}
}
fn is_numeric_token(token: &str) -> bool {
token.replace(['D', 'd'], "E").parse::<f64>().is_ok()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parses_feff_danes_reference_shape() -> Result<()> {
let data = parse_danes_dat(DANES_DAT)?;
assert_eq!(data.point_count(), 3);
assert_eq!(data.energy_ev[0], -18.690);
assert_eq!(data.matsubara[1], 0.0);
assert_eq!(data.sommerfeld[2], 0.0);
assert_eq!(data.anomalous[0], 10.097);
assert_eq!(data.tail[1], 4.9442);
assert_eq!(data.total[2], 5.2935);
assert_eq!(data.difference[0], -5.4576);
Ok(())
}
#[test]
fn roundtrips_danes_text() -> Result<()> {
let data = parse_danes_dat(DANES_DAT)?;
let rendered = danes_dat_string(&data)?;
assert_eq!(parse_danes_dat(&rendered)?, data);
assert_eq!(rendered, DANES_DAT);
Ok(())
}
#[test]
fn rejects_bad_danes_inputs() {
assert!(parse_danes_dat("# no data\n").is_err());
assert!(parse_danes_dat("1 2 3 4 5 6\n").is_err());
assert!(parse_danes_dat("1 2 3 NaN 5 6 7\n").is_err());
}
const DANES_DAT: &str = r#"# E matsub. sommerf. anomal. tale, total, differ.
-1.8690E+01 0.0000E+00 0.0000E+00 1.0097E+01 4.6396E+00 4.6396E+00 -5.4576E+00
-1.7122E+01 0.0000E+00 0.0000E+00 1.0603E+01 4.9442E+00 4.9442E+00 -5.6591E+00
-1.5703E+01 0.0000E+00 0.0000E+00 1.1159E+01 5.2935E+00 5.2935E+00 -5.8651E+00
"#;
}