rexafs 0.2.14

Rust-powered X-ray absorption spectroscopy analysis and EXAFS fitting
Documentation
#![cfg(feature = "refeff-runner")]

use std::fs;
use std::path::{Path, PathBuf};
use std::time::{SystemTime, UNIX_EPOCH};

use rexafs::prelude::{feffpath, run_feff, FeffExecutionMode, FeffFlavor, FeffRunRequest};
use rexafs::xafs::fitting::FittingError;
use rexafs::xafs::XAFSError;

struct TestWorkspace(PathBuf);

impl TestWorkspace {
    fn new() -> Self {
        let unique = format!(
            "rexafs-refeff-{}-{}",
            std::process::id(),
            SystemTime::now()
                .duration_since(UNIX_EPOCH)
                .expect("system clock")
                .as_nanos()
        );
        let path = std::env::temp_dir().join(unique);
        fs::create_dir_all(&path).expect("create test workspace");
        Self(path)
    }
}

impl Drop for TestWorkspace {
    fn drop(&mut self) {
        let _ = fs::remove_dir_all(&self.0);
    }
}

#[test]
fn refeff_in_memory_generates_only_loadable_path_files_by_default() {
    let crate_dir = Path::new(env!("CARGO_MANIFEST_DIR"));
    let input = crate_dir.join("tests/testfiles/xraylarch_d867/feffit/Feff_ZnSe/feff.inp");
    let workspace = TestWorkspace::new();
    let request = FeffRunRequest {
        executable_path: PathBuf::new(),
        workspace_dir: workspace.0.clone(),
        feffinp: Some(input),
        mode: FeffExecutionMode::RefeffPipeline,
        timeout_sec: None,
        use_sfconv: false,
        keep_all_outputs: false,
    };

    let result = run_feff(&request).expect("ReFEFF calculation");

    assert_eq!(result.mode, FeffExecutionMode::RefeffPipeline);
    assert!(!result.path_files.is_empty());
    assert!(result.logs.is_empty());
    assert!(result
        .resolved
        .modules
        .iter()
        .all(|module| { module.executable.to_string_lossy().starts_with("refeff::") }));
    assert!(result.path_files.iter().all(|path| {
        path.file_name()
            .and_then(|name| name.to_str())
            .is_some_and(|name| name.starts_with("feff") && name.ends_with(".dat"))
    }));

    let workspace_files: Vec<_> = fs::read_dir(&workspace.0)
        .expect("read output workspace")
        .map(|entry| entry.expect("workspace entry").path())
        .collect();
    assert_eq!(workspace_files.len(), result.path_files.len());

    feffpath(
        result.path_files[0].to_string_lossy().as_ref(),
        FeffFlavor::Feff85L,
    )
    .expect("ReFEFF path output must load in the existing fitting parser");
}

#[test]
fn refeff_expired_deadline_leaves_no_partial_path_outputs() {
    let workspace = TestWorkspace::new();
    let request = FeffRunRequest {
        executable_path: PathBuf::new(),
        workspace_dir: workspace.0.clone(),
        feffinp: Some(
            Path::new(env!("CARGO_MANIFEST_DIR"))
                .join("tests/testfiles/xraylarch_d867/feffit/Feff_ZnSe/feff.inp"),
        ),
        mode: FeffExecutionMode::RefeffPipeline,
        timeout_sec: Some(0),
        use_sfconv: false,
        keep_all_outputs: true,
    };
    assert!(matches!(
        run_feff(&request),
        Err(XAFSError::Fitting(FittingError::ProcessTimedOut {
            timeout_sec: 0,
            ..
        }))
    ));
    assert_eq!(fs::read_dir(&workspace.0).unwrap().count(), 0);
}

#[test]
fn refeff_verbose_output_mode_materializes_full_feff_artifacts() {
    let crate_dir = Path::new(env!("CARGO_MANIFEST_DIR"));
    let input = crate_dir.join("tests/testfiles/xraylarch_d867/feffit/Feff_ZnSe/feff.inp");
    let workspace = TestWorkspace::new();
    let request = FeffRunRequest {
        executable_path: PathBuf::new(),
        workspace_dir: workspace.0.clone(),
        feffinp: Some(input),
        mode: FeffExecutionMode::RefeffPipeline,
        timeout_sec: None,
        use_sfconv: false,
        keep_all_outputs: true,
    };

    let result = run_feff(&request).expect("verbose ReFEFF calculation");

    assert!(!result.path_files.is_empty());
    assert!(workspace.0.join("pot.bin").is_file());
    assert!(workspace.0.join("phase.bin").is_file());
    assert!(workspace.0.join("chi.dat").is_file());
    assert!(!workspace.0.join("feff.inp").exists());
    assert!(
        fs::read_dir(&workspace.0)
            .expect("read verbose output workspace")
            .count()
            > result.path_files.len()
    );
}