use crate::interface::DFTD4Error;
use crate::parameters::{
convert_to_damping_param, get_default_param_table, get_merged_param_table, normalize_version,
DFTD4DampingParam,
};
use toml::Table;
const META_FIELDS: &[&str] = &["version", "method", "atm"];
const VALID_FIELDS: &[&str] = &["s6", "s8", "s9", "a1", "a2", "alp"];
pub fn dftd4_parse_damping_param(input: &Table) -> DFTD4DampingParam {
dftd4_parse_damping_param_f(input).unwrap()
}
pub fn dftd4_parse_damping_param_f(input: &Table) -> Result<DFTD4DampingParam, DFTD4Error> {
let version_raw = input.get("version").and_then(|v| v.as_str()).unwrap_or("d4");
let version = normalize_version(version_raw);
let method = input.get("method").and_then(|v| v.as_str());
let atm = input.get("atm").and_then(|v| v.as_bool()).unwrap_or(true);
let s9_explicit = input.contains_key("s9");
let user_param_keys: Vec<&str> =
input.keys().map(|k| k.as_str()).filter(|k| !META_FIELDS.contains(k)).collect();
for key in &user_param_keys {
if !VALID_FIELDS.contains(key) {
return Err(DFTD4Error::ParametersError(format!(
"Unknown parameter '{}' for variant '{}'",
key, version
)));
}
}
let mut merged = if let Some(method) = method {
get_merged_param_table(method, &version)?
} else {
get_default_param_table(&version)?
};
for key in &user_param_keys {
if let Some(value) = input.get(*key) {
merged.insert((*key).to_string(), value.clone());
}
}
if !atm && !s9_explicit {
merged.insert("s9".to_string(), toml::Value::Float(0.0));
}
merged.remove("damping");
merged.remove("mbd");
convert_to_damping_param(&merged)
}
fn parse_toml_table(input: &str) -> Result<Table, DFTD4Error> {
let trimmed = input.trim();
if trimmed.starts_with('{') {
let wrapped = format!("x = {trimmed}");
let mut doc: Table = toml::from_str(&wrapped)
.map_err(|e| DFTD4Error::ParametersError(format!("TOML parsing error: {e}")))?;
if let Some(toml::Value::Table(table)) = doc.remove("x") {
Ok(table)
} else {
Err(DFTD4Error::ParametersError("Invalid TOML format".into()))
}
} else {
toml::from_str(trimmed)
.map_err(|e| DFTD4Error::ParametersError(format!("TOML parsing error: {e}")))
}
}
pub fn dftd4_parse_damping_param_from_toml(input: &str) -> DFTD4DampingParam {
dftd4_parse_damping_param_from_toml_f(input).unwrap()
}
pub fn dftd4_parse_damping_param_from_toml_f(input: &str) -> Result<DFTD4DampingParam, DFTD4Error> {
let table = parse_toml_table(input)?;
dftd4_parse_damping_param_f(&table)
}
#[test]
fn test_dftd4_parse_damping_param_from_toml_doc() {
use crate::prelude::*;
let input = r#"{version = "d4bj", method = "b3lyp"}"#;
let damping_param = dftd4_parse_damping_param_from_toml(input);
let dftd4_param = damping_param.new_param();
let atom_numbers = vec![8, 1, 1];
#[rustfmt::skip]
let coordinates = vec![
0.000000, 0.000000, 0.000000,
0.000000, 0.000000, 1.807355,
1.807355, 0.000000, -0.452500,
];
let model = DFTD4Model::new(&atom_numbers, &coordinates, None, None, None);
let res = model.get_dispersion(&dftd4_param, false);
let eng = res.energy;
println!("Dispersion energy: {eng}");
let input =
r#"{version = "d4bj", a1 = 0.40868035, s8 = 2.02929367, a2 = 4.53807137, atm = false}"#;
let damping_param = dftd4_parse_damping_param_from_toml(input);
let dftd4_param = damping_param.new_param();
let res = model.get_dispersion(&dftd4_param, false);
let eng = res.energy;
println!("Dispersion energy with custom params: {eng}");
}