use crate::interface::*;
use serde::Deserialize;
use std::collections::HashMap;
use toml::Table;
const PARAMETERS_TOML: &str = include_str!("parameters.toml");
#[derive(Debug, Clone, Deserialize, Default)]
struct D3Variants {
d3: D3MethodParams,
}
#[derive(Debug, Clone, Deserialize, Default)]
struct D3MethodParams {
bj: Option<Table>,
zero: Option<Table>,
bjm: Option<Table>,
zerom: Option<Table>,
#[serde(default)]
op: Option<Table>,
#[serde(default)]
cso: Option<Table>,
}
#[derive(Debug, Clone, Deserialize)]
struct ParameterDataBase {
default: DefaultSection,
parameter: HashMap<String, D3Variants>,
}
#[derive(Debug, Clone, Deserialize)]
struct DefaultSection {
#[allow(dead_code)]
d3: Vec<String>,
parameter: DefaultParameterSection,
}
#[derive(Debug, Clone, Deserialize)]
struct DefaultParameterSection {
d3: D3DefaultParams,
}
#[derive(Debug, Clone, Deserialize)]
struct D3DefaultParams {
bj: Table,
zero: Table,
bjm: Table,
zerom: Table,
#[serde(default)]
op: Option<Table>,
#[serde(default)]
cso: Option<Table>,
}
#[derive(Debug, Clone)]
pub struct DFTD3DampingParam {
pub param: DFTD3DampingParamEnum,
pub doi: Option<String>,
}
#[derive(Debug, Clone)]
pub enum DFTD3DampingParamEnum {
#[cfg(feature = "api-v0_4")]
Rational(DFTD3RationalDampingParam),
#[cfg(feature = "api-v0_4")]
Zero(DFTD3ZeroDampingParam),
#[cfg(feature = "api-v0_4")]
ModifiedRational(DFTD3ModifiedRationalDampingParam),
#[cfg(feature = "api-v0_4")]
ModifiedZero(DFTD3ModifiedZeroDampingParam),
#[cfg(feature = "api-v0_5")]
OptimizedPower(DFTD3OptimizedPowerDampingParam),
#[cfg(feature = "api-v1_3")]
CSO(DFTD3CSODampingParam),
}
impl DFTD3DampingParamEnum {
pub fn s6(&self) -> f64 {
match self {
#[cfg(feature = "api-v0_4")]
DFTD3DampingParamEnum::Rational(data) => data.s6,
#[cfg(feature = "api-v0_4")]
DFTD3DampingParamEnum::Zero(data) => data.s6,
#[cfg(feature = "api-v0_4")]
DFTD3DampingParamEnum::ModifiedRational(data) => data.s6,
#[cfg(feature = "api-v0_4")]
DFTD3DampingParamEnum::ModifiedZero(data) => data.s6,
#[cfg(feature = "api-v0_5")]
DFTD3DampingParamEnum::OptimizedPower(data) => data.s6,
#[cfg(feature = "api-v1_3")]
DFTD3DampingParamEnum::CSO(data) => data.s6,
}
}
pub fn s9(&self) -> f64 {
match self {
#[cfg(feature = "api-v0_4")]
DFTD3DampingParamEnum::Rational(data) => data.s9,
#[cfg(feature = "api-v0_4")]
DFTD3DampingParamEnum::Zero(data) => data.s9,
#[cfg(feature = "api-v0_4")]
DFTD3DampingParamEnum::ModifiedRational(data) => data.s9,
#[cfg(feature = "api-v0_4")]
DFTD3DampingParamEnum::ModifiedZero(data) => data.s9,
#[cfg(feature = "api-v0_5")]
DFTD3DampingParamEnum::OptimizedPower(data) => data.s9,
#[cfg(feature = "api-v1_3")]
DFTD3DampingParamEnum::CSO(data) => data.s9,
}
}
pub fn alp(&self) -> f64 {
match self {
#[cfg(feature = "api-v0_4")]
DFTD3DampingParamEnum::Rational(data) => data.alp,
#[cfg(feature = "api-v0_4")]
DFTD3DampingParamEnum::Zero(data) => data.alp,
#[cfg(feature = "api-v0_4")]
DFTD3DampingParamEnum::ModifiedRational(data) => data.alp,
#[cfg(feature = "api-v0_4")]
DFTD3DampingParamEnum::ModifiedZero(data) => data.alp,
#[cfg(feature = "api-v0_5")]
DFTD3DampingParamEnum::OptimizedPower(data) => data.alp,
#[cfg(feature = "api-v1_3")]
DFTD3DampingParamEnum::CSO(data) => data.alp,
}
}
pub fn s8(&self) -> Option<f64> {
match self {
#[cfg(feature = "api-v0_4")]
DFTD3DampingParamEnum::Rational(data) => Some(data.s8),
#[cfg(feature = "api-v0_4")]
DFTD3DampingParamEnum::Zero(data) => Some(data.s8),
#[cfg(feature = "api-v0_4")]
DFTD3DampingParamEnum::ModifiedRational(data) => Some(data.s8),
#[cfg(feature = "api-v0_4")]
DFTD3DampingParamEnum::ModifiedZero(data) => Some(data.s8),
#[cfg(feature = "api-v0_5")]
DFTD3DampingParamEnum::OptimizedPower(data) => Some(data.s8),
#[cfg(feature = "api-v1_3")]
DFTD3DampingParamEnum::CSO(_) => None, }
}
}
#[cfg(feature = "api-v0_4")]
impl DFTD3ParamAPI for DFTD3DampingParamEnum {
fn new_param_f(self) -> Result<DFTD3Param, DFTD3Error> {
match self {
DFTD3DampingParamEnum::Rational(data) => data.new_param_f(),
DFTD3DampingParamEnum::Zero(data) => data.new_param_f(),
DFTD3DampingParamEnum::ModifiedRational(data) => data.new_param_f(),
DFTD3DampingParamEnum::ModifiedZero(data) => data.new_param_f(),
#[cfg(feature = "api-v0_5")]
DFTD3DampingParamEnum::OptimizedPower(data) => data.new_param_f(),
#[cfg(feature = "api-v1_3")]
DFTD3DampingParamEnum::CSO(data) => data.new_param_f(),
}
}
}
#[cfg(feature = "api-v0_4")]
impl DFTD3ParamAPI for DFTD3DampingParam {
fn new_param_f(self) -> Result<DFTD3Param, DFTD3Error> {
self.param.new_param_f()
}
}
fn load_data_base() -> Result<ParameterDataBase, DFTD3Error> {
toml::from_str(PARAMETERS_TOML)
.map_err(|e| DFTD3Error::ParametersError(format!("TOML parsing error: {}", e)))
}
pub fn dftd3_get_damping_param(method: &str, version: &str) -> DFTD3DampingParam {
dftd3_get_damping_param_f(method, version).unwrap()
}
pub fn dftd3_get_damping_param_f(
method: &str,
version: &str,
) -> Result<DFTD3DampingParam, DFTD3Error> {
let db = load_data_base()?;
let method_lower = normalize_method(method);
let version_normalized = normalize_version(version);
let method_entry = db.parameter.get(&method_lower).ok_or_else(|| {
DFTD3Error::ParametersError(format!("Method '{}' not found in database", method))
})?;
let (entry_raw, default_entry) = get_variant_entry(method_entry, &version_normalized, &db)?;
let merged = merge_tables(&entry_raw, &default_entry);
convert_to_damping_param(&merged, &version_normalized)
}
pub(crate) fn get_merged_param_table(method: &str, version: &str) -> Result<Table, DFTD3Error> {
let db = load_data_base()?;
let method_lower = normalize_method(method);
let version_normalized = normalize_version(version);
let method_entry = db.parameter.get(&method_lower).ok_or_else(|| {
DFTD3Error::ParametersError(format!("Method '{}' not found in database", method))
})?;
let (entry_raw, default_entry) = get_variant_entry(method_entry, &version_normalized, &db)?;
Ok(merge_tables(&entry_raw, &default_entry))
}
pub(crate) fn normalize_method(method: &str) -> String {
method.to_lowercase().replace(['-', '_', ' '], "")
}
pub(crate) fn get_default_param_table(version: &str) -> Result<Table, DFTD3Error> {
let db = load_data_base()?;
let version_normalized = normalize_version(version);
let (_, default_entry) = get_variant_entry_for_defaults(&version_normalized, &db)?;
Ok(default_entry)
}
pub fn dftd3_get_all_damping_params(version: &str) -> HashMap<String, DFTD3DampingParam> {
dftd3_get_all_damping_params_f(version).unwrap()
}
pub fn dftd3_get_all_damping_params_f(
version: &str,
) -> Result<HashMap<String, DFTD3DampingParam>, DFTD3Error> {
let db = load_data_base()?;
let version_normalized = normalize_version(version);
let (_, default_entry) = get_variant_entry_for_defaults(&version_normalized, &db)?;
let mut result = HashMap::new();
for (method, method_entry) in &db.parameter {
if let Ok((entry_raw, _)) = get_variant_entry(method_entry, &version_normalized, &db) {
let merged = merge_tables(&entry_raw, &default_entry);
if let Ok(param) = convert_to_damping_param(&merged, &version_normalized) {
result.insert(method.clone(), param);
}
}
}
Ok(result)
}
pub fn dftd3_list_methods() -> Vec<String> {
let db = load_data_base().unwrap_or_else(|_| {
ParameterDataBase {
default: DefaultSection {
d3: vec!["bj".to_string(), "zero".to_string()],
parameter: DefaultParameterSection {
d3: D3DefaultParams {
bj: Table::new(),
zero: Table::new(),
bjm: Table::new(),
zerom: Table::new(),
op: None,
cso: None,
},
},
},
parameter: HashMap::new(),
}
});
db.parameter.keys().cloned().collect()
}
pub(crate) fn normalize_version(version: &str) -> String {
let version_lower = version.to_lowercase().replace(['-', '_', ' '], "");
match version_lower.as_str() {
"d3bj" | "bj" => "bj",
"d3zero" | "zero" => "zero",
"d3bjm" | "bjm" | "d3mbj" | "mbj" => "bjm",
"d3zerom" | "zerom" | "d3mzero" | "mzero" => "zerom",
"d3op" | "op" => "op",
"d3cso" | "cso" => "cso",
_ => &version_lower,
}
.to_string()
}
fn get_variant_entry(
method_entry: &D3Variants,
version: &str,
db: &ParameterDataBase,
) -> Result<(Table, Table), DFTD3Error> {
let d3_params = &method_entry.d3;
let entry = match version {
"bj" => d3_params.bj.clone(),
"zero" => d3_params.zero.clone(),
"bjm" => d3_params.bjm.clone(),
"zerom" => d3_params.zerom.clone(),
"op" => d3_params.op.clone(),
"cso" => d3_params.cso.clone(),
_ => None,
};
let entry = entry.ok_or_else(|| {
DFTD3Error::ParametersError(format!("Variant '{}' not found for this method", version))
})?;
let default_entry = match version {
"bj" => db.default.parameter.d3.bj.clone(),
"zero" => db.default.parameter.d3.zero.clone(),
"bjm" => db.default.parameter.d3.bjm.clone(),
"zerom" => db.default.parameter.d3.zerom.clone(),
"op" => db.default.parameter.d3.op.clone().unwrap_or_default(),
"cso" => db.default.parameter.d3.cso.clone().unwrap_or_default(),
_ => Table::new(),
};
Ok((entry, default_entry))
}
fn get_variant_entry_for_defaults(
version: &str,
db: &ParameterDataBase,
) -> Result<(Option<Table>, Table), DFTD3Error> {
let default_entry = match version {
"bj" => db.default.parameter.d3.bj.clone(),
"zero" => db.default.parameter.d3.zero.clone(),
"bjm" => db.default.parameter.d3.bjm.clone(),
"zerom" => db.default.parameter.d3.zerom.clone(),
"op" => db.default.parameter.d3.op.clone().unwrap_or_default(),
"cso" => db.default.parameter.d3.cso.clone().unwrap_or_default(),
_ => {
return Err(DFTD3Error::ParametersError(format!(
"Variant '{version}' not found in defaults",
)))
},
};
Ok((None, default_entry))
}
pub(crate) fn merge_tables(entry: &Table, defaults: &Table) -> Table {
let mut merged = defaults.clone();
for (key, value) in entry {
merged.insert(key.clone(), value.clone());
}
merged
}
fn extract_doi(table: &Table) -> Option<String> {
table.get("doi").and_then(|v| v.as_str()).map(|s| s.to_string())
}
#[cfg(feature = "api-v0_4")]
pub(crate) fn convert_to_damping_param(
merged: &Table,
version: &str,
) -> Result<DFTD3DampingParam, DFTD3Error> {
let doi = extract_doi(merged);
let param = match version {
"bj" => DFTD3DampingParamEnum::Rational(deserialize_table(merged)?),
"zero" => DFTD3DampingParamEnum::Zero(deserialize_table(merged)?),
"bjm" => DFTD3DampingParamEnum::ModifiedRational(deserialize_table(merged)?),
"zerom" => DFTD3DampingParamEnum::ModifiedZero(deserialize_table(merged)?),
#[cfg(feature = "api-v0_5")]
"op" => DFTD3DampingParamEnum::OptimizedPower(deserialize_table(merged)?),
#[cfg(feature = "api-v1_3")]
"cso" => DFTD3DampingParamEnum::CSO(deserialize_table(merged)?),
#[cfg(not(feature = "api-v0_5"))]
"op" => {
return Err(DFTD3Error::ParametersError(format!(
"Variant '{version}' requires api-v0_5 feature",
)))
},
#[cfg(not(feature = "api-v1_3"))]
"cso" => {
return Err(DFTD3Error::ParametersError(format!(
"Variant '{version}' requires api-v1_3 feature",
)))
},
_ => return Err(DFTD3Error::ParametersError(format!("Unknown variant: {version}"))),
};
Ok(DFTD3DampingParam { param, doi })
}
pub(crate) fn deserialize_table<T: for<'de> Deserialize<'de>>(
table: &Table,
) -> Result<T, DFTD3Error> {
T::deserialize(table.clone()).map_err(|e| e.into())
}
impl From<toml::de::Error> for DFTD3Error {
fn from(e: toml::de::Error) -> Self {
DFTD3Error::ParametersError(format!("Deserialization error: {e}"))
}
}
#[cfg(not(feature = "api-v0_4"))]
pub(crate) fn convert_to_damping_param(
_merged: &Table,
version: &str,
) -> Result<DFTD3DampingParam, DFTD3Error> {
Err(DFTD3Error::ParametersError(format!(
"Variant '{}' requires api-v0_4 feature or higher",
version
)))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_load_data_base() {
let db = load_data_base();
match &db {
Ok(db) => {
assert!(db.parameter.contains_key("b3lyp"));
assert!(db.parameter.contains_key("pbe"));
assert!(db.parameter.contains_key("r2scan"));
},
Err(e) => {
println!("Error: {:?}", e);
panic!("TOML parsing failed");
},
}
}
#[test]
fn test_normalize_version() {
assert_eq!(normalize_version("d3bj"), "bj");
assert_eq!(normalize_version("d3zero"), "zero");
assert_eq!(normalize_version("bj"), "bj");
assert_eq!(normalize_version("d3op"), "op");
}
}