hartree-cli 0.1.1

Command-line driver for hartree: an XYZ file plus a basis and method, to an SCF energy.
Documentation
use std::process::Command;

const WATER_DIMER: &str = "6\nwater dimer\n\
O -1.551007 -0.114520  0.000000\n\
H -1.934259  0.762503  0.000000\n\
H -0.599677  0.040712  0.000000\n\
O  1.350625  0.111469  0.000000\n\
H  1.680398 -0.373741 -0.758561\n\
H  1.680398 -0.373741  0.758561\n";

fn run(xyz_name: &str, xyz: &str, args: &[&str]) -> (bool, String, String) {
    let path = std::env::temp_dir().join(xyz_name);
    std::fs::write(&path, xyz).unwrap();
    let output = Command::new(env!("CARGO_BIN_EXE_hartree"))
        .arg(&path)
        .args(args)
        .output()
        .expect("run hartree");
    (
        output.status.success(),
        String::from_utf8_lossy(&output.stdout).into_owned(),
        String::from_utf8_lossy(&output.stderr).into_owned(),
    )
}

#[test]
fn cp_water_dimer_prints_full_table() {
    let (ok, stdout, stderr) = run(
        "hartree_cli_cp_dimer.xyz",
        WATER_DIMER,
        &["--basis", "sto-3g", "--method", "rhf", "--cp", "3"],
    );
    assert!(ok, "exit failure.\nstdout:\n{stdout}\nstderr:\n{stderr}");
    for needle in [
        "counterpoise (Boys-Bernardi)",
        "E_AB^(AB)",
        "E_A^(AB)",
        "E_B^(AB)",
        "E_A^(A)",
        "E_B^(B)",
        "dE_int (uncorrected)",
        "dE_int^CP (corrected)",
        "delta_BSSE",
    ] {
        assert!(stdout.contains(needle), "missing {needle:?}:\n{stdout}");
    }
}

#[test]
fn ghost_xyz_runs_single_point() {
    let xyz =
        "4\nwater + ghost\nO 0 0 0.1178\nH 0 0.7555 -0.4712\nH 0 -0.7555 -0.4712\n@O 0 0 3.0\n";
    let (ok, stdout, stderr) = run(
        "hartree_cli_ghost.xyz",
        xyz,
        &["--basis", "sto-3g", "--method", "rhf"],
    );
    assert!(ok, "exit failure.\nstdout:\n{stdout}\nstderr:\n{stderr}");
    assert!(
        stdout.contains("electrons: 10"),
        "ghost added electrons:\n{stdout}"
    );
    assert!(stdout.contains("converged in"));
}

#[test]
fn standalone_gcp_prints_breakdown() {
    let (ok, stdout, stderr) = run(
        "hartree_cli_gcp_dimer.xyz",
        WATER_DIMER,
        &["--basis", "sto-3g", "--method", "rhf", "--gcp", "r2scan-3c"],
    );
    assert!(ok, "exit failure.\nstdout:\n{stdout}\nstderr:\n{stderr}");
    assert!(
        stdout.contains("gCP (def2-mTZVPP)"),
        "no gCP line:\n{stdout}"
    );
    assert!(stdout.contains("total energy incl. corrections"));
}

#[test]
fn gcp_misuse_rejected() {
    let (ok, _, stderr) = run(
        "hartree_cli_gcp_bad1.xyz",
        WATER_DIMER,
        &["--method", "r2scan-3c", "--gcp", "r2scan-3c"],
    );
    assert!(!ok && stderr.contains("composite"), "stderr:\n{stderr}");
    let (ok, _, stderr) = run(
        "hartree_cli_gcp_bad2.xyz",
        WATER_DIMER,
        &["--basis", "sto-3g", "--gcp", "nosuchset"],
    );
    assert!(!ok && stderr.contains("unknown gCP"), "stderr:\n{stderr}");
}

#[test]
fn dftc_keyword_cleanly_blocked() {
    for args in [
        &["--basis", "def2-svpd", "--method", "b97m-v-dftc"][..],
        &["--basis", "def2-svpd", "--method", "dft-c"][..],
        &["--basis", "def2-svpd", "--method", "rhf", "--gcp", "dft-c"][..],
    ] {
        let (ok, stdout, stderr) = run("hartree_cli_dftc.xyz", WATER_DIMER, args);
        assert!(!ok, "DFT-C must be refused.\nstdout:\n{stdout}");
        assert!(
            stderr.contains("DFT-C is not implemented")
                && stderr.contains("supplementary material")
                && stderr.contains("10.1063/1.4986962"),
            "blocked message missing for {args:?}:\n{stderr}"
        );
    }
}