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 extracts_sfconv_alias_and_controls_like_feff() -> anyhow::Result<()> {
    let input = FeffInput::parse_str(
        "feff.inp",
        r#"
XANES
SO2C
SELF
SFSE 2.5
RCONV 10.25 longfilename.dat
END
"#,
    )?;

    let doc = FeffDocument::from_input(&input)?;
    assert!(doc.sfconv);
    assert_eq!(
        doc.sfconv_input.control,
        SfconvControl {
            msfconv: 1,
            ipse: 1,
            ipsk: 1,
        }
    );
    assert_eq!(doc.sfconv_input.window.wsigk, 2.5);
    assert_eq!(doc.sfconv_input.window.cen, 10.25);
    assert_eq!(doc.sfconv_input.spectrum.ispec, 1);
    assert_eq!(doc.sfconv_input.spectrum.ipr6, 0);
    assert_eq!(doc.sfconv_input.cfname, "longfilename");
    assert_eq!(
        doc.active_cards,
        ["XANES", "SFCONV", "SELF", "SFSE", "RCONV"]
    );
    assert_eq!(
        doc.input_cards,
        ["XANES", "SFCONV", "SELF", "SFSE", "RCONV"]
    );
    Ok(())
}

#[test]
fn extracts_genfmt_and_real_phase_switches() -> anyhow::Result<()> {
    let input = FeffInput::parse_str(
        "feff.inp",
        r#"
IORDER 4
POLARIZATION 1 0 0
RPHASES
NSTAR
END
"#,
    )?;

    let doc = FeffDocument::from_input(&input)?;
    assert_eq!(doc.iorder, 4);
    assert_eq!(doc.lreal, 2);
    assert!(doc.nstar);
    assert_eq!(
        doc.active_cards,
        ["IORD", "POLARIZATION", "RPHASES", "NSTAR"]
    );
    Ok(())
}

#[test]
fn disables_nstar_without_linear_polarization_like_feff() -> anyhow::Result<()> {
    let input = FeffInput::parse_str(
        "feff.inp",
        r#"
NSTAR
END
"#,
    )?;

    let doc = FeffDocument::from_input(&input)?;
    assert!(!doc.nstar);
    assert_eq!(doc.active_cards, ["NSTAR"]);
    Ok(())
}

#[test]
fn extracts_path_symmetry_controls() -> anyhow::Result<()> {
    let input = FeffInput::parse_str(
        "feff.inp",
        r#"
SYMMETRY 3
END
"#,
    )?;

    let doc = FeffDocument::from_input(&input)?;
    assert_eq!(doc.path_symmetry, 3);
    assert_eq!(doc.active_cards, ["SYMMETRY"]);
    Ok(())
}

#[test]
fn clamps_invalid_path_symmetry_like_feff() -> anyhow::Result<()> {
    let input = FeffInput::parse_str(
        "feff.inp",
        r#"
SYMMETRY 9
END
"#,
    )?;

    let doc = FeffDocument::from_input(&input)?;
    assert_eq!(doc.path_symmetry, -1);
    assert_eq!(doc.active_cards, ["SYMMETRY"]);
    Ok(())
}

#[test]
fn extracts_bandstructure_handoff_controls() -> anyhow::Result<()> {
    let input = FeffInput::parse_str(
        "feff.inp",
        r#"
BANDSTRUCTURE -5.0 10.0 0.25 2 64 T
END
"#,
    )?;

    let doc = FeffDocument::from_input(&input)?;
    assert_eq!(doc.band_input.mband, 1);
    assert_eq!(doc.band_input.energy_mesh.emin, -5.0);
    assert_eq!(doc.band_input.energy_mesh.emax, 10.0);
    assert_eq!(doc.band_input.energy_mesh.estep, 0.25);
    assert_eq!(doc.band_input.ikpath, 2);
    assert_eq!(doc.band_input.nkp, 64);
    assert!(doc.band_input.freeprop);
    assert_eq!(doc.active_cards, ["BAND"]);
    Ok(())
}

#[test]
fn rejects_incomplete_bandstructure_like_feff() -> anyhow::Result<()> {
    let input = FeffInput::parse_str(
        "feff.inp",
        r#"
BANDSTRUCTURE -5.0 10.0
END
"#,
    )?;

    let error = FeffDocument::from_input(&input)
        .err()
        .context("incomplete BANDSTRUCTURE should be rejected")?;
    ensure!(
        error
            .to_string()
            .contains("BANDSTRUCTURE requires emin emax estep ikpath"),
        "unexpected error: {error}"
    );
    Ok(())
}

#[test]
fn extracts_fullspectrum_handoff_switch() -> anyhow::Result<()> {
    let input = FeffInput::parse_str(
        "feff.inp",
        r#"
FULLSPECTRUM
END
"#,
    )?;

    let doc = FeffDocument::from_input(&input)?;
    assert_eq!(doc.full_spectrum_input.m_full_spectrum, 1);
    assert_eq!(doc.active_cards, ["FULLSPECTRUM"]);
    Ok(())
}

#[test]
fn extracts_scxc_handoff_controls_in_input_order() -> anyhow::Result<()> {
    let input = FeffInput::parse_str(
        "feff.inp",
        r#"
SCXC 22
TEMP 0.25 12
SCXC 21
END
"#,
    )?;

    let doc = FeffDocument::from_input(&input)?;
    assert_eq!(doc.electronic_temperature, 0.25);
    assert_eq!(doc.iscfxc, 21);
    assert_eq!(doc.active_cards, ["TEMP", "SCXC"]);
    assert_eq!(doc.input_cards, ["SCXC", "TEMP", "SCXC"]);
    Ok(())
}

#[test]
fn rejects_invalid_scxc_like_feff() -> anyhow::Result<()> {
    let input = FeffInput::parse_str(
        "feff.inp",
        r#"
SCXC 99
END
"#,
    )?;

    let error = FeffDocument::from_input(&input)
        .err()
        .context("invalid SCXC should be rejected")?;
    ensure!(
        error
            .to_string()
            .contains("SCXC iscfxc must be one of 11, 12, 21, or 22"),
        "unexpected error: {error}"
    );
    Ok(())
}

#[test]
fn extracts_screen_handoff_controls() -> anyhow::Result<()> {
    let input = FeffInput::parse_str(
        "feff.inp",
        r#"
SCREEN rfms 5.5
SCREEN ner 64.4
SCREEN lfxc 2.4
SCREEN ermin 2e-3
END
"#,
    )?;

    let doc = FeffDocument::from_input(&input)?;
    assert_eq!(doc.screen_input.rfms, 5.5);
    assert_eq!(doc.screen_input.ner, 64);
    assert_eq!(doc.screen_input.lfxc, 2);
    assert_eq!(doc.screen_input.ermin, 0.002);
    assert_eq!(doc.screen_input.nei, 20);
    assert_eq!(doc.active_cards, ["SCREEN"]);
    assert_eq!(doc.input_cards, ["SCREEN", "SCREEN", "SCREEN", "SCREEN"]);
    Ok(())
}

#[test]
fn rejects_incomplete_screen_card_like_feff() -> anyhow::Result<()> {
    let input = FeffInput::parse_str(
        "feff.inp",
        r#"
SCREEN rfms
END
"#,
    )?;

    let error = FeffDocument::from_input(&input)
        .err()
        .context("incomplete SCREEN card should be rejected")?;
    ensure!(
        error
            .to_string()
            .contains("SCREEN requires keyword and value"),
        "unexpected error: {error}"
    );
    Ok(())
}

#[test]
fn rejects_unknown_screen_keyword_like_feff() -> anyhow::Result<()> {
    let input = FeffInput::parse_str(
        "feff.inp",
        r#"
SCREEN unknown 1.0
END
"#,
    )?;

    let error = FeffDocument::from_input(&input)
        .err()
        .context("unknown SCREEN keyword should be rejected")?;
    ensure!(
        error
            .to_string()
            .contains("unrecognized SCREEN keyword \"unknown\""),
        "unexpected error: {error}"
    );
    Ok(())
}

#[test]
fn extracts_xsph_handoff_controls() -> anyhow::Result<()> {
    let input = FeffInput::parse_str(
        "feff.inp",
        r#"
CHBROADENING 1
SETEDGE
EPS0 -2.0
EGAP 1.25
CHWIDTH 0.75
RLPRINT
ICORE 3
END
"#,
    )?;

    let doc = FeffDocument::from_input(&input)?;
    assert_eq!(doc.xsph_handoff.core_hole_broadening, 1);
    assert_eq!(doc.xsph_handoff.core_state, 3);
    assert_eq!(doc.xsph_handoff.eps0, -2.0);
    assert_eq!(doc.xsph_handoff.egap, 1.25);
    assert_eq!(doc.xsph_handoff.core_hole_width, Some(0.75));
    assert!(doc.xsph_handoff.set_edge);
    assert!(doc.xsph_handoff.print_radial_wavefunctions);
    assert_eq!(
        doc.active_cards,
        [
            "CHBROADENING",
            "SETE",
            "EPS0",
            "EGAP",
            "CHWIDTH",
            "RLPR",
            "ICOR"
        ]
    );
    Ok(())
}

#[test]
fn extracts_opcons_number_density_controls() -> anyhow::Result<()> {
    let input = FeffInput::parse_str(
        "feff.inp",
        r#"
OPCONS
NUMDENS 0 8.5
NUMDENS 2 4.25
PREPS
POTENTIALS
0 29 Cu0
1 29 Cu1
2 8 O
END
"#,
    )?;

    let doc = FeffDocument::from_input(&input)?;
    assert!(doc.opcons);
    assert!(doc.opcons_input.run_opcons);
    assert!(doc.opcons_input.print_eps);
    assert_eq!(doc.opcons_input.number_densities, vec![8.5, -1.0, 4.25]);
    assert_eq!(doc.active_cards, ["POTENTIALS", "OPCONS", "NUMD", "PREP"]);
    Ok(())
}

#[test]
fn extracts_opcons_alias_controls_like_feff() -> anyhow::Result<()> {
    let input = FeffInput::parse_str(
        "feff.inp",
        r#"
OPCO
NUMD 0 8.5
PREP
POTENTIALS
0 29 Cu0
END
"#,
    )?;

    let doc = FeffDocument::from_input(&input)?;
    assert!(doc.opcons);
    assert!(doc.opcons_input.run_opcons);
    assert!(doc.opcons_input.print_eps);
    assert_eq!(doc.opcons_input.number_densities, vec![8.5, -1.0]);
    assert_eq!(doc.active_cards, ["POTENTIALS", "OPCONS", "NUMD", "PREP"]);
    assert_eq!(doc.input_cards, ["OPCONS", "NUMD", "PREP", "POTENTIALS"]);
    Ok(())
}