use crate::parse::basis::{BasisInput, BasisSpec};
use crate::parse::mole::{CIntMol, CIntMolInput};
use crate::prelude::*;
#[derive(Debug, Clone, Default, Deserialize)]
struct TomlInputWithCustomTables {
#[serde(flatten)]
input: CIntMolInput,
#[serde(default, rename = "basis-custom")]
basis_custom: HashMap<String, String>,
#[serde(default, rename = "ecp-custom")]
ecp_custom: HashMap<String, String>,
}
pub fn parse_toml_input(toml_str: &str) -> Result<CIntMol, CIntError> {
let raw_toml: toml::Value = toml::from_str(toml_str).map_err(|e| cint_error!(ParseError, "Failed to parse TOML: {e}"))?;
let basis_is_custom = check_is_custom(&raw_toml, "basis");
let ecp_is_custom = check_is_custom(&raw_toml, "ecp");
let raw: TomlInputWithCustomTables = toml::from_str(toml_str).map_err(|e| cint_error!(ParseError, "Failed to parse TOML: {e}"))?;
if !raw.basis_custom.is_empty() && !basis_is_custom {
return cint_raise!(ParseError, "[basis-custom] table defined but basis is not set to \"custom\"");
}
if !raw.ecp_custom.is_empty() && !ecp_is_custom {
return cint_raise!(ParseError, "[ecp-custom] table defined but ecp is not set to \"custom\"");
}
let basis = if basis_is_custom {
if raw.basis_custom.is_empty() {
return cint_raise!(ParseError, "basis = \"custom\" specified but no [basis-custom] table found");
}
let dict: IndexMap<String, BasisInput> =
raw.basis_custom.into_iter().map(|(k, v)| (k.to_ascii_uppercase(), BasisInput::String(v))).collect();
BasisSpec::Dict(dict)
} else {
raw.input.basis
};
let ecp = if ecp_is_custom {
if raw.ecp_custom.is_empty() {
return cint_raise!(ParseError, "ecp = \"custom\" specified but no [ecp-custom] table found");
}
let dict: IndexMap<String, BasisInput> =
raw.ecp_custom.into_iter().map(|(k, v)| (k.to_ascii_uppercase(), BasisInput::String(v))).collect();
BasisSpec::Dict(dict)
} else {
raw.input.ecp
};
let input = CIntMolInput {
atom: raw.input.atom,
basis,
ecp,
unit: raw.input.unit,
cart: raw.input.cart,
ghost_ecp: raw.input.ghost_ecp,
allow_empty_basis: raw.input.allow_empty_basis,
};
input.create_mol_f()
}
fn check_is_custom(toml: &toml::Value, field: &str) -> bool {
if let toml::Value::Table(table) = toml {
if let Some(toml::Value::String(s)) = table.get(field) {
return s == "custom";
}
}
false
}
#[cfg(test)]
mod tests {
use crate::parse::atom::Unit;
use crate::parse::basis::{BasisInput, BasisSpec};
use crate::parse::mole::{CIntMol, CIntMolInput};
#[test]
fn test_cint_mol_input_json() {
let json = r#"{
"atom": "O 0 0 0; H 0 0 0.9572; H 0 0.9266 -0.239987",
"basis": "STO-3G",
"unit": "angstrom",
"cart": false
}"#;
let input: CIntMolInput = serde_json::from_str(json).unwrap();
assert_eq!(input.atom, "O 0 0 0; H 0 0 0.9572; H 0 0.9266 -0.239987");
assert!(matches!(input.basis, BasisSpec::Uniform(BasisInput::String(_))));
assert_eq!(input.unit, Unit::Angstrom);
assert!(!input.cart);
}
#[test]
fn test_cint_mol_input_toml() {
let toml = r#"
atom = "O 0 0 0; H 0 0 0.9572; H 0 0.9266 -0.239987"
basis = "STO-3G"
unit = "angstrom"
cart = false
"#;
let input: CIntMolInput = toml::from_str(toml).unwrap();
assert_eq!(input.atom, "O 0 0 0; H 0 0 0.9572; H 0 0.9266 -0.239987");
assert!(matches!(input.basis, BasisSpec::Uniform(BasisInput::String(_))));
assert_eq!(input.unit, Unit::Angstrom);
assert!(!input.cart);
}
#[test]
fn test_toml_custom_basis() {
let toml = r#"
atom = "O 0 0 0; H 0 0 0.9572"
basis = "custom"
[basis-custom]
O = "STO-3G"
default = "6-31G"
"#;
let mol = CIntMol::from_toml(toml);
assert!(mol.cint.nao() > 0);
}
#[test]
fn test_toml_list_basis() {
let toml = r#"
atom = "O 0 0 0; H 0 0 0.9572"
basis = ["STO-3G", "6-31G"]
"#;
let input: CIntMolInput = toml::from_str(toml).unwrap();
assert!(matches!(input.basis, BasisSpec::List(_)));
}
#[test]
fn test_basis_spec_serialize() {
let spec = BasisSpec::Uniform(BasisInput::String("STO-3G".to_string()));
let json = serde_json::to_string(&spec).unwrap();
assert_eq!(json, "\"STO-3G\"");
}
#[test]
fn test_cint_mol_input_serialize() {
let input = CIntMolInput {
atom: "O 0 0 0".to_string(),
basis: BasisSpec::Uniform(BasisInput::String("STO-3G".to_string())),
ecp: BasisSpec::Uniform(BasisInput::None),
unit: Unit::Angstrom,
cart: false,
ghost_ecp: false,
allow_empty_basis: false,
};
let json = serde_json::to_string(&input).unwrap();
assert!(json.contains("\"atom\""));
assert!(json.contains("\"basis\""));
}
}