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 super::*;

#[test]
fn writes_rdinp_log_dat_for_copper_exafs_summary() -> Result<()> {
    let input = FeffInput::parse_str(
        "feff.inp",
        r#"
TITLE Cu crystal
DEBYE 190 315 0
EDGE K
S02 1.0
CONTROL 1 1 1 1 1 1
PRINT 0 0 0 0 0 0
LDOS -30 20 0.1
EXAFS 20.0
RPATH 5.5
POTENTIALS
0 29 Cu
1 29 Cu
ATOMS
0.0 0.0 0.0 0 Cu0
1.805 1.805 0.0 1 Cu1
END
"#,
    )?;
    let doc = FeffDocument::from_input(&input)?;
    let log = rdinp_log_dat_string(&doc)?;

    assert_eq!(
        log,
        concat!(
            "Launching FEFF version FEFF 10.0.0\n",
            "Core hole lifetime is   1.729 eV.\n",
            "Your calculation:\n",
            " Cu crystal\n",
            "Cu K edge EXAFS using FSR corehole.\n",
            "Using:     * Debye-Waller factors\n",
            "Using cards:   ATOMS CONTROL TITLE RPATH DEBYE PRINT POTENTIALS EXAFS EDGE LDOS S02\n",
            "\n",
        )
    );
    assert_eq!(parse_log_dat(&log)?.features, vec!["Debye-Waller factors"]);
    Ok(())
}

#[test]
fn writes_rdinp_stdout_only_absorber_spin_default_diagnostic() -> Result<()> {
    let input = FeffInput::parse_str(
        "feff.inp",
        r#"
TITLE Gd_L1 hcp
XMCD
EDGE L1
SPIN 1
POTENTIALS
0 64 Gd
1 64 Gd
ATOMS
0.0 0.0 0.0 0 Gd0
1.0 0.0 0.0 1 Gd1
END
"#,
    )?;
    let doc = FeffDocument::from_input(&input)?;
    let log = rdinp_log_dat_string(&doc)?;
    let stdout = rdinp_stdout_string(&doc)?;

    assert!(!log.contains("\n           1\n"));
    assert!(stdout.contains("Core hole lifetime is   5.533 eV.\n           1\n"));
    assert!(stdout.contains("No spin set in POTENTIALS card. Using default spins:\n"));
    Ok(())
}

#[test]
fn writes_rdinp_error_log_for_highz_template_failure() -> Result<()> {
    let input = FeffInput::parse_str(
        "feff.inp",
        r#"
TITLE test_element
HIGHZ
POTENTIALS
       0    XXX   Te
END
"#,
    )?;
    let error = FeffDocument::from_input(&input)
        .err()
        .ok_or_else(|| IoError::Parse {
            path: "feff.inp".into(),
            line: 0,
            message: "HIGHZ template should fail".to_string(),
        })?;

    assert_eq!(
        rdinp_error_log_string(&input, &error)?,
        concat!(
            "Launching FEFF version FEFF 10.0.0\n",
            "Using finite nucleus.\n",
            " Error reading input, bad line follows:\n",
            " 0    XXX   Te\n",
            "RDINP fatal error.\n",
        )
    );
    Ok(())
}

#[test]
fn writes_legacy_rdinp_error_logs_for_blank_context_cards() -> Result<()> {
    let cases = [
        (
            "HOLE",
            concat!(
                "Launching FEFF version FEFF 10.0.0\n",
                " Use NOHOLE to calculate without core hole.  Only ihole greater than zero are allowed.\n",
                "RDINP\n",
            ),
        ),
        (
            "OVERLAP",
            concat!(
                "Launching FEFF version FEFF 10.0.0\n",
                " Cannot use ATOMS and OVERLAP in the same feff.inp.\n",
                "RDINP\n",
            ),
        ),
        (
            "RCONV",
            concat!(
                "Launching FEFF version FEFF 10.0.0\n",
                " RCONV\n",
                " RCONV\n",
                " Token        0\n",
                " Keyword unrecognized.\n",
                " See FEFF document -- some old features are no longer available.\n",
                "RDINP-2\n",
            ),
        ),
        (
            "BAND",
            concat!(
                "Launching FEFF version FEFF 10.0.0\n",
                "BANDSTRUCTURE card is experimental.\n",
                "BANDSTRUCTURE requires at least: emin  emax  estep  ikpath\n",
                "\n",
            ),
        ),
        (
            "COORDINATES",
            concat!(
                "Launching FEFF version FEFF 10.0.0\n",
                "Attempt to enter funky lattice coordinates.\n",
                "Please stick to one of the formats described in the manual.\n",
                "Exiting now.\n",
            ),
        ),
        (
            "MDFF",
            concat!(
                "Launching FEFF version FEFF 10.0.0\n",
                "NRIXS type MDFF calculation selected - summed over all q,q' pairs.\n",
                "ERROR - the selected MDFF option is only available with the NRIXS card.\n",
                "RDINP\n",
            ),
        ),
        ("SCREEN", "Launching FEFF version FEFF 10.0.0\n"),
        (
            "SCXC",
            concat!(
                "Launching FEFF version FEFF 10.0.0\n",
                "Error: iscfxc should take one of the values 11 for vBH, 12 for PZ, 21 for PDW, or 22 for KSDT ... stopping\n",
            ),
        ),
        (
            "LJMAX",
            concat!(
                "Launching FEFF version FEFF 10.0.0\n",
                "Core hole lifetime is   1.729 eV.\n",
            ),
        ),
        (
            "CGRID",
            concat!(
                "Launching FEFF version FEFF 10.0.0\n",
                "Core hole lifetime is   1.729 eV.\n",
            ),
        ),
        (
            "ELNES",
            concat!(
                "Launching FEFF version FEFF 10.0.0\n",
                " Error reading input, bad line follows:\n",
                " POTENTIALS\n",
                "RDINP fatal error.\n",
            ),
        ),
    ];

    for (card, expected) in cases {
        let input = FeffInput::parse_str("feff.inp", &context_card_input(card))?;
        let error = FeffDocument::from_input(&input)
            .err()
            .ok_or_else(|| IoError::Parse {
                path: "feff.inp".into(),
                line: 0,
                message: format!("{card} should fail in context audit"),
            })?;

        assert_eq!(rdinp_error_log_string(&input, &error)?, expected, "{card}");
    }
    Ok(())
}

fn context_card_input(card: &str) -> String {
    let mut input = String::new();
    input.push_str("TITLE Context audit\n");
    if card != "EDGE" {
        input.push_str("EDGE K\n");
    }
    input.push_str("EXAFS 20\n");
    input.push_str(card);
    input.push('\n');
    input.push_str("POTENTIALS\n0 29 Cu\nATOMS\n0.0 0.0 0.0 0 Cu\nEND\n");
    input
}