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 listed_reciprocal_requires_handoff_even_when_real_follows_like_feff() -> anyhow::Result<()> {
    let input = FeffInput::parse_str(
        "feff.inp",
        r#"
RECIPROCAL
REAL
END
"#,
    )?;

    let error = FeffDocument::from_input(&input)
        .err()
        .context("listed RECIPROCAL should require reciprocal handoff cards")?;
    ensure!(
        error
            .to_string()
            .contains("KMESH and TARGET are required for RECIPROCAL card"),
        "unexpected error: {error}"
    );
    Ok(())
}

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

    let error = FeffDocument::from_input(&input)
        .err()
        .context("RECIPROCAL should still require reciprocal handoff cards")?;
    ensure!(
        error
            .to_string()
            .contains("KMESH and TARGET are required for RECIPROCAL card"),
        "unexpected error: {error}"
    );
    Ok(())
}

#[test]
fn rejects_reciprocal_without_lattice_or_cif_like_feff() -> anyhow::Result<()> {
    let input = FeffInput::parse_str(
        "feff.inp",
        r#"
RECIPROCAL
KMESH 10 0
TARGET 1
END
"#,
    )?;

    let error = FeffDocument::from_input(&input)
        .err()
        .context("RECIPROCAL should require one structure source")?;
    ensure!(
        error
            .to_string()
            .contains("use either LATTICE or CIF with RECIPROCAL card"),
        "unexpected error: {error}"
    );
    Ok(())
}

#[test]
fn rejects_reciprocal_with_lattice_and_cif_like_feff() -> anyhow::Result<()> {
    let input = FeffInput::parse_str(
        "feff.inp",
        r#"
RECIPROCAL
KMESH 10 0
TARGET 1
LATTICE P 1.0
CIF dummy.cif
END
"#,
    )?;

    let error = FeffDocument::from_input(&input)
        .err()
        .context("RECIPROCAL should reject simultaneous LATTICE and CIF")?;
    ensure!(
        error
            .to_string()
            .contains("use either LATTICE or CIF with RECIPROCAL card"),
        "unexpected error: {error}"
    );
    Ok(())
}

#[test]
fn rejects_incomplete_strfac_like_feff() -> anyhow::Result<()> {
    let input = FeffInput::parse_str(
        "feff.inp",
        r#"
RECIPROCAL
KMESH 10 0
TARGET 1
LATTICE P 1.0
STRFAC 1.0 2.0
END
"#,
    )?;

    let error = FeffDocument::from_input(&input)
        .err()
        .context("incomplete STRFAC should be rejected")?;
    ensure!(
        error.to_string().contains("STRFAC requires three values"),
        "unexpected error: {error}"
    );
    Ok(())
}

#[test]
fn rejects_standalone_incomplete_strfac_like_feff() -> anyhow::Result<()> {
    let input = FeffInput::parse_str("feff.inp", "STRFAC\nEND\n")?;

    let error = FeffDocument::from_input(&input)
        .err()
        .context("standalone incomplete STRFAC should be rejected")?;
    assert!(
        error.to_string().contains("STRFAC requires three values"),
        "{error}"
    );
    Ok(())
}

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

    let error = FeffDocument::from_input(&input)
        .err()
        .context("CGRID should require COMPTON or RHOZZP")?;
    ensure!(
        error
            .to_string()
            .contains("Cannot use CGRID without COMPTON or RHOZZP.  Exiting."),
        "unexpected error: {error}"
    );
    Ok(())
}

#[test]
fn rejects_hubbard_with_reciprocal_like_feff() -> anyhow::Result<()> {
    let input = FeffInput::parse_str(
        "feff.inp",
        r#"
RECIPROCAL
KMESH 10 0
TARGET 1
LATTICE P 1.0
HUBBARD 1.0 0.5 0.0 2
END
"#,
    )?;

    let error = FeffDocument::from_input(&input)
        .err()
        .context("HUBBARD should be rejected with RECIPROCAL")?;
    ensure!(
        error
            .to_string()
            .contains("Cannot use RECIPROCAL with HUBBARD."),
        "unexpected error: {error}"
    );
    Ok(())
}

#[test]
fn converts_reciprocal_lattice_coordinates_like_feff() -> anyhow::Result<()> {
    let input = FeffInput::parse_str(
        "feff.inp",
        r#"
RECIPROCAL
KMESH 10 0
TARGET 1
LATTICE P 2.0
1.0 0.0 0.0
0.0 1.0 0.0
0.0 0.0 1.0
COORDINATES 1
POTENTIALS
0 29 Cu0
1 29 Cu1
ATOMS
0.0 0.0 0.0 1 Cu0
1.0 0.0 0.0 1 Cu1
END
"#,
    )?;
    let doc = FeffDocument::from_input(&input)?;
    let reciprocal = doc
        .reciprocal_input
        .as_ref()
        .and_then(|input| input.cell.as_ref())
        .context("missing reciprocal cell")?;

    assert_eq!(doc.coordinate_mode, 1);
    assert_eq!(reciprocal.positions[0], [0.0, 0.0, 0.0]);
    assert_eq!(reciprocal.positions[1], [0.5, 0.0, 0.0]);
    assert!(
        doc.atoms
            .iter()
            .any(|atom| atom.ipot == 1 && (atom.x.abs() - 1.0).abs() < 1.0e-12)
    );
    Ok(())
}

#[test]
fn accepts_bare_reciprocal_sgroup_like_feff() -> anyhow::Result<()> {
    let input = FeffInput::parse_str(
        "feff.inp",
        r#"
RECIPROCAL
KMESH 10 0
TARGET 1
SGROUP
LATTICE P 2.0
1.0 0.0 0.0
0.0 1.0 0.0
0.0 0.0 1.0
ATOMS
0.0 0.0 0.0 1 Cu0
END
"#,
    )?;

    let doc = FeffDocument::from_input(&input)?;
    let reciprocal = doc
        .reciprocal_input
        .as_ref()
        .and_then(|input| input.cell.as_ref())
        .context("missing reciprocal cell")?;

    assert_eq!(reciprocal.space_group, 1);
    assert!(doc.active_cards.iter().any(|card| card == "SGROUP"));
    Ok(())
}