use crate::ffi;
use derive_builder::{Builder, UninitializedFieldError};
use serde::{Deserialize, Serialize};
use serde_inline_default::serde_inline_default;
use std::ffi::{c_char, c_int, CStr};
use std::ptr::{null, null_mut};
use std::result::Result;
pub fn dftd4_get_api_version() -> String {
let version = unsafe { ffi::dftd4_get_version() };
format!("{}.{}.{}", version / 10000, version / 100 % 100, version % 100)
}
pub fn dftd4_get_api_version_compact() -> [usize; 3] {
let version = unsafe { ffi::dftd4_get_version() } as usize;
[version / 10000, version / 100 % 100, version % 100]
}
pub enum DFTD4Error {
C(ffi::dftd4_error),
Rust(String),
BuilderError(UninitializedFieldError),
ParametersError(String),
}
impl From<UninitializedFieldError> for DFTD4Error {
fn from(ufe: UninitializedFieldError) -> DFTD4Error {
DFTD4Error::BuilderError(ufe)
}
}
impl Drop for DFTD4Error {
fn drop(&mut self) {
if let DFTD4Error::C(ptr) = self {
unsafe { ffi::dftd4_delete_error(&mut ptr.clone()) }
}
}
}
impl Default for DFTD4Error {
fn default() -> Self {
DFTD4Error::new()
}
}
impl std::error::Error for DFTD4Error {}
impl DFTD4Error {
pub fn new() -> Self {
let ptr = unsafe { ffi::dftd4_new_error() };
DFTD4Error::C(ptr)
}
pub fn check(&self) -> bool {
match self {
DFTD4Error::C(ptr) => unsafe { ffi::dftd4_check_error(*ptr) != 0 },
_ => true,
}
}
pub fn get_c_ptr(&mut self) -> ffi::dftd4_error {
match self {
DFTD4Error::C(ptr) => *ptr,
_ => std::ptr::null_mut(),
}
}
pub fn get_message(&self) -> String {
match self {
DFTD4Error::C(ptr) => {
const LEN_BUFFER: usize = 512;
let buffer = [0u8; LEN_BUFFER];
let raw = buffer.as_ptr() as *mut c_char;
let msg = unsafe {
ffi::dftd4_get_error(*ptr, raw, &(LEN_BUFFER as c_int));
CStr::from_ptr(raw)
};
msg.to_string_lossy().to_string()
},
DFTD4Error::Rust(msg) => msg.clone(),
DFTD4Error::BuilderError(ufe) => {
format!("Builder error: {:?}", ufe)
},
DFTD4Error::ParametersError(msg) => msg.clone(),
}
}
}
impl std::fmt::Debug for DFTD4Error {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
if self.check() {
write!(f, "DFTD4Error: {}", self.get_message())
} else {
write!(f, "DFTD4Error: No error")
}
}
}
impl std::fmt::Display for DFTD4Error {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
if self.check() {
write!(f, "DFTD4Error: {}", self.get_message())
} else {
write!(f, "")
}
}
}
pub struct DFTD4Structure {
pub(crate) ptr: ffi::dftd4_structure,
natoms: usize,
}
impl Drop for DFTD4Structure {
fn drop(&mut self) {
unsafe { ffi::dftd4_delete_structure(&mut self.ptr) };
}
}
impl DFTD4Structure {
pub fn new(
numbers: &[usize],
positions: &[f64],
charge: Option<f64>,
lattice: Option<&[f64]>,
periodic: Option<&[bool]>,
) -> Self {
Self::new_f(numbers, positions, charge, lattice, periodic).unwrap()
}
pub fn update(&mut self, positions: &[f64], lattice: Option<&[f64]>) {
self.update_f(positions, lattice).unwrap()
}
pub fn get_natoms(&self) -> usize {
self.natoms
}
pub fn new_f(
numbers: &[usize],
positions: &[f64],
charge: Option<f64>,
lattice: Option<&[f64]>,
periodic: Option<&[bool]>,
) -> Result<Self, DFTD4Error> {
let natoms = numbers.len();
if positions.len() != 3 * natoms {
return Err(DFTD4Error::Rust(format!(
"Invalid dimension for positions, expected {}, got {}",
3 * natoms,
positions.len()
)));
}
if lattice.is_some_and(|lattice| lattice.len() != 9) {
return Err(DFTD4Error::Rust(format!(
"Invalid dimension for lattice, expected 9, got {}",
lattice.unwrap().len()
)));
}
if periodic.is_some_and(|periodic| periodic.len() != 3) {
return Err(DFTD4Error::Rust(format!(
"Invalid dimension for periodic, expected 3, got {}",
periodic.unwrap().len()
)));
}
let charge_ptr = charge.map_or(null(), |x| &x as *const f64);
let lattice_ptr = lattice.map_or(null(), |x| x.as_ptr());
let periodic_ptr = periodic.map_or(null(), |x| x.as_ptr());
let natoms_c_int = natoms as c_int;
let atomic_numbers = numbers.iter().map(|&x| x as c_int).collect::<Vec<c_int>>();
let mut error = DFTD4Error::new();
let ptr = unsafe {
ffi::dftd4_new_structure(
error.get_c_ptr(),
natoms_c_int,
atomic_numbers.as_ptr(),
positions.as_ptr(),
charge_ptr,
lattice_ptr,
periodic_ptr,
)
};
match error.check() {
true => Err(error),
false => Ok(Self { ptr, natoms }),
}
}
pub fn update_f(
&mut self,
positions: &[f64],
lattice: Option<&[f64]>,
) -> Result<(), DFTD4Error> {
if positions.len() != 3 * self.natoms {
return Err(DFTD4Error::Rust(format!(
"Invalid dimension for positions, expected {}, got {}",
3 * self.natoms,
positions.len()
)));
}
if lattice.is_some_and(|lattice| lattice.len() != 9) {
return Err(DFTD4Error::Rust(format!(
"Invalid dimension for lattice, expected 9, got {}",
lattice.unwrap().len()
)));
}
let lattice_ptr = lattice.map_or(null(), |x| x.as_ptr());
let mut error = DFTD4Error::new();
unsafe {
ffi::dftd4_update_structure(
error.get_c_ptr(),
self.ptr,
positions.as_ptr(),
lattice_ptr,
)
};
match error.check() {
true => Err(error),
false => Ok(()),
}
}
}
pub struct DFTD4Param {
ptr: ffi::dftd4_param,
}
impl Drop for DFTD4Param {
fn drop(&mut self) {
unsafe { ffi::dftd4_delete_param(&mut self.ptr) };
}
}
impl DFTD4Param {
pub fn new_rational_damping(s6: f64, s8: f64, s9: f64, a1: f64, a2: f64, alp: f64) -> Self {
Self::new_rational_damping_f(s6, s8, s9, a1, a2, alp).unwrap()
}
pub fn new_rational_damping_f(
s6: f64,
s8: f64,
s9: f64,
a1: f64,
a2: f64,
alp: f64,
) -> Result<Self, DFTD4Error> {
let mut error = DFTD4Error::new();
let ptr =
unsafe { ffi::dftd4_new_rational_damping(error.get_c_ptr(), s6, s8, s9, a1, a2, alp) };
match error.check() {
true => Err(error),
false => Ok(Self { ptr }),
}
}
pub fn load_rational_damping(name: &str, atm: bool) -> Self {
Self::load_rational_damping_f(name, atm).unwrap()
}
pub fn load_rational_damping_f(name: &str, atm: bool) -> Result<Self, DFTD4Error> {
let mut error = DFTD4Error::new();
let name = std::ffi::CString::new(name).unwrap();
let ptr = unsafe {
ffi::dftd4_load_rational_damping(error.get_c_ptr(), name.as_ptr() as *mut c_char, atm)
};
match error.check() {
true => Err(error),
false => Ok(Self { ptr }),
}
}
}
pub trait DFTD4ParamAPI: Sized {
fn new_param(self) -> DFTD4Param {
self.new_param_f().unwrap()
}
fn new_param_f(self) -> Result<DFTD4Param, DFTD4Error>;
}
pub trait DFTD4LoadParamAPI {
fn load_param_f(method: &str, atm: bool) -> Result<DFTD4Param, DFTD4Error>;
}
#[doc = include_str!("damping_param_usage.md")]
#[serde_inline_default]
#[derive(Builder, Debug, Clone, Deserialize, Serialize)]
#[builder(pattern = "owned", build_fn(error = "DFTD4Error"))]
pub struct DFTD4RationalDampingParam {
#[builder(default = 1.0)]
#[serde_inline_default(1.0)]
#[doc = r"optional, default 1.0"]
pub s6: f64,
pub s8: f64,
#[builder(default = 1.0)]
#[serde_inline_default(1.0)]
#[doc = r"optional, default 1.0"]
pub s9: f64,
pub a1: f64,
pub a2: f64,
#[builder(default = 16.0)]
#[serde_inline_default(16.0)]
#[doc = r"optional, default 16.0"]
pub alp: f64,
}
impl DFTD4ParamAPI for DFTD4RationalDampingParam {
fn new_param_f(self) -> Result<DFTD4Param, DFTD4Error> {
let Self { s6, s8, s9, a1, a2, alp } = self;
DFTD4Param::new_rational_damping_f(s6, s8, s9, a1, a2, alp)
}
}
impl DFTD4LoadParamAPI for DFTD4RationalDampingParam {
fn load_param_f(method: &str, atm: bool) -> Result<DFTD4Param, DFTD4Error> {
DFTD4Param::load_rational_damping_f(method, atm)
}
}
macro_rules! impl_damping_param_builder {
($feature:literal: $type:ty) => {
#[cfg(feature = $feature)]
impl $type {
pub fn init(self) -> DFTD4Param {
self.init_f().unwrap()
}
pub fn init_f(self) -> Result<DFTD4Param, DFTD4Error> {
self.build()?.new_param_f()
}
}
};
}
impl_damping_param_builder!("api-v3_0": DFTD4RationalDampingParamBuilder);
pub fn dftd4_load_param(variant: &str, method: &str, atm: bool) -> DFTD4Param {
match variant {
"d4bj" => DFTD4RationalDampingParam::load_param_f(method, atm).unwrap(),
_ => panic!("Unknown damping variant: {}", variant),
}
}
pub struct DFTD4Output {
pub energy: f64,
pub grad: Option<Vec<f64>>,
pub sigma: Option<Vec<f64>>,
}
impl From<DFTD4Output> for (f64, Option<Vec<f64>>, Option<Vec<f64>>) {
fn from(output: DFTD4Output) -> Self {
(output.energy, output.grad, output.sigma)
}
}
#[cfg(feature = "api-v3_2")]
pub struct DFTD4PairwiseOutput {
pub pair_energy2: Vec<f64>,
pub pair_energy3: Vec<f64>,
}
#[cfg(feature = "api-v3_2")]
impl From<DFTD4PairwiseOutput> for (Vec<f64>, Vec<f64>) {
fn from(output: DFTD4PairwiseOutput) -> Self {
(output.pair_energy2, output.pair_energy3)
}
}
#[cfg(feature = "api-v3_1")]
pub struct DFTD4PropertyOutput {
pub cn: Vec<f64>,
pub charges: Vec<f64>,
pub c6: Vec<f64>,
pub alpha: Vec<f64>,
}
#[cfg(feature = "api-v3_1")]
impl From<DFTD4PropertyOutput> for (Vec<f64>, Vec<f64>, Vec<f64>, Vec<f64>) {
fn from(output: DFTD4PropertyOutput) -> Self {
(output.cn, output.charges, output.c6, output.alpha)
}
}
pub struct DFTD4Model {
ptr: ffi::dftd4_model,
structure: DFTD4Structure,
}
impl Drop for DFTD4Model {
fn drop(&mut self) {
unsafe { ffi::dftd4_delete_model(&mut self.ptr) };
}
}
impl DFTD4Model {
pub fn new(
numbers: &[usize],
positions: &[f64],
charge: Option<f64>,
lattice: Option<&[f64]>,
periodic: Option<&[bool]>,
) -> Self {
Self::new_f(numbers, positions, charge, lattice, periodic).unwrap()
}
#[cfg(feature = "api-v4_0")]
pub fn new_d4s(
numbers: &[usize],
positions: &[f64],
charge: Option<f64>,
lattice: Option<&[f64]>,
periodic: Option<&[bool]>,
) -> Self {
Self::new_d4s_f(numbers, positions, charge, lattice, periodic).unwrap()
}
#[cfg(feature = "api-v4_0")]
pub fn custom_d4s(
numbers: &[usize],
positions: &[f64],
ga: f64,
gc: f64,
charge: Option<f64>,
lattice: Option<&[f64]>,
periodic: Option<&[bool]>,
) -> Self {
Self::custom_d4s_f(numbers, positions, ga, gc, charge, lattice, periodic).unwrap()
}
#[cfg(feature = "api-v3_1")]
#[allow(clippy::too_many_arguments)]
pub fn custom_d4(
numbers: &[usize],
positions: &[f64],
ga: f64,
gc: f64,
wf: f64,
charge: Option<f64>,
lattice: Option<&[f64]>,
periodic: Option<&[bool]>,
) -> Self {
Self::custom_d4_f(numbers, positions, ga, gc, wf, charge, lattice, periodic).unwrap()
}
#[cfg(feature = "api-v4_2")]
pub fn set_realspace_cutoff(&mut self, disp2: f64, disp3: f64, cn: f64) {
self.set_realspace_cutoff_f(disp2, disp3, cn).unwrap()
}
#[cfg(feature = "api-v4_2")]
pub fn set_realspace_cutoff_smooth(
&mut self,
disp2: f64,
disp3: f64,
cn: f64,
width2: f64,
width3: f64,
) {
self.set_realspace_cutoff_smooth_f(disp2, disp3, cn, width2, width3).unwrap()
}
pub fn get_dispersion(&self, param: &DFTD4Param, eval_grad: bool) -> DFTD4Output {
self.get_dispersion_f(param, eval_grad).unwrap()
}
#[cfg(feature = "api-v3_2")]
pub fn get_pairwise_dispersion(&self, param: &DFTD4Param) -> DFTD4PairwiseOutput {
self.get_pairwise_dispersion_f(param).unwrap()
}
#[cfg(feature = "api-v3_5")]
pub fn get_numerical_hessian(&self, param: &DFTD4Param) -> Vec<f64> {
self.get_numerical_hessian_f(param).unwrap()
}
#[cfg(feature = "api-v3_1")]
pub fn get_properties(&self) -> DFTD4PropertyOutput {
self.get_properties_f().unwrap()
}
pub fn from_structure(structure: DFTD4Structure) -> Self {
Self::from_structure_f(structure).unwrap()
}
pub fn update(&mut self, positions: &[f64], lattice: Option<&[f64]>) {
self.structure.update(positions, lattice)
}
pub fn new_f(
numbers: &[usize],
positions: &[f64],
charge: Option<f64>,
lattice: Option<&[f64]>,
periodic: Option<&[bool]>,
) -> Result<Self, DFTD4Error> {
let structure = DFTD4Structure::new_f(numbers, positions, charge, lattice, periodic)?;
let mut error = DFTD4Error::new();
let ptr = unsafe { ffi::dftd4_new_d4_model(error.get_c_ptr(), structure.ptr) };
match error.check() {
true => Err(error),
false => Ok(Self { ptr, structure }),
}
}
#[cfg(feature = "api-v4_0")]
pub fn new_d4s_f(
numbers: &[usize],
positions: &[f64],
charge: Option<f64>,
lattice: Option<&[f64]>,
periodic: Option<&[bool]>,
) -> Result<Self, DFTD4Error> {
let structure = DFTD4Structure::new_f(numbers, positions, charge, lattice, periodic)?;
let mut error = DFTD4Error::new();
let ptr = unsafe { ffi::dftd4_new_d4s_model(error.get_c_ptr(), structure.ptr) };
match error.check() {
true => Err(error),
false => Ok(Self { ptr, structure }),
}
}
#[cfg(feature = "api-v4_0")]
pub fn custom_d4s_f(
numbers: &[usize],
positions: &[f64],
ga: f64,
gc: f64,
charge: Option<f64>,
lattice: Option<&[f64]>,
periodic: Option<&[bool]>,
) -> Result<Self, DFTD4Error> {
let structure = DFTD4Structure::new_f(numbers, positions, charge, lattice, periodic)?;
let mut error = DFTD4Error::new();
let ptr = unsafe { ffi::dftd4_custom_d4s_model(error.get_c_ptr(), structure.ptr, ga, gc) };
match error.check() {
true => Err(error),
false => Ok(Self { ptr, structure }),
}
}
#[cfg(feature = "api-v3_1")]
#[allow(clippy::too_many_arguments)]
pub fn custom_d4_f(
numbers: &[usize],
positions: &[f64],
ga: f64,
gc: f64,
wf: f64,
charge: Option<f64>,
lattice: Option<&[f64]>,
periodic: Option<&[bool]>,
) -> Result<Self, DFTD4Error> {
let structure = DFTD4Structure::new_f(numbers, positions, charge, lattice, periodic)?;
let mut error = DFTD4Error::new();
let ptr =
unsafe { ffi::dftd4_custom_d4_model(error.get_c_ptr(), structure.ptr, ga, gc, wf) };
match error.check() {
true => Err(error),
false => Ok(Self { ptr, structure }),
}
}
#[cfg(feature = "api-v4_2")]
pub fn set_realspace_cutoff_f(
&mut self,
disp2: f64,
disp3: f64,
cn: f64,
) -> Result<(), DFTD4Error> {
let mut error = DFTD4Error::new();
unsafe {
ffi::dftd4_set_model_realspace_cutoff(error.get_c_ptr(), self.ptr, disp2, disp3, cn)
};
match error.check() {
true => Err(error),
false => Ok(()),
}
}
#[cfg(feature = "api-v4_2")]
pub fn set_realspace_cutoff_smooth_f(
&mut self,
disp2: f64,
disp3: f64,
cn: f64,
width2: f64,
width3: f64,
) -> Result<(), DFTD4Error> {
let mut error = DFTD4Error::new();
unsafe {
ffi::dftd4_set_model_realspace_cutoff_smooth(
error.get_c_ptr(),
self.ptr,
disp2,
disp3,
cn,
width2,
width3,
)
};
match error.check() {
true => Err(error),
false => Ok(()),
}
}
pub fn get_dispersion_f(
&self,
param: &DFTD4Param,
eval_grad: bool,
) -> Result<DFTD4Output, DFTD4Error> {
let structure = &self.structure;
let natoms = structure.get_natoms();
let mut energy = 0.0;
let mut grad = match eval_grad {
true => Some(vec![0.0; 3 * natoms]),
false => None,
};
let mut sigma = match eval_grad {
true => Some(vec![0.0; 9]),
false => None,
};
let mut error = DFTD4Error::new();
unsafe {
ffi::dftd4_get_dispersion(
error.get_c_ptr(),
structure.ptr,
self.ptr,
param.ptr,
&mut energy,
grad.as_mut().map_or(null_mut(), |x| x.as_mut_ptr()),
sigma.as_mut().map_or(null_mut(), |x| x.as_mut_ptr()),
)
};
match error.check() {
true => Err(error),
false => Ok(DFTD4Output { energy, grad, sigma }),
}
}
#[cfg(feature = "api-v3_2")]
pub fn get_pairwise_dispersion_f(
&self,
param: &DFTD4Param,
) -> Result<DFTD4PairwiseOutput, DFTD4Error> {
let structure = &self.structure;
let natoms = structure.get_natoms();
let mut pair_energy2 = vec![0.0; natoms * natoms];
let mut pair_energy3 = vec![0.0; natoms * natoms];
let mut error = DFTD4Error::new();
unsafe {
ffi::dftd4_get_pairwise_dispersion(
error.get_c_ptr(),
structure.ptr,
self.ptr,
param.ptr,
pair_energy2.as_mut_ptr(),
pair_energy3.as_mut_ptr(),
)
};
match error.check() {
true => Err(error),
false => Ok(DFTD4PairwiseOutput { pair_energy2, pair_energy3 }),
}
}
#[cfg(feature = "api-v3_5")]
pub fn get_numerical_hessian_f(&self, param: &DFTD4Param) -> Result<Vec<f64>, DFTD4Error> {
let structure = &self.structure;
let natoms = structure.get_natoms();
let n = 3 * natoms;
let mut hess = vec![0.0; n * n];
let mut error = DFTD4Error::new();
unsafe {
ffi::dftd4_get_numerical_hessian(
error.get_c_ptr(),
structure.ptr,
self.ptr,
param.ptr,
hess.as_mut_ptr(),
)
};
match error.check() {
true => Err(error),
false => Ok(hess),
}
}
#[cfg(feature = "api-v3_1")]
pub fn get_properties_f(&self) -> Result<DFTD4PropertyOutput, DFTD4Error> {
let structure = &self.structure;
let natoms = structure.get_natoms();
let mut cn = vec![0.0; natoms];
let mut charges = vec![0.0; natoms];
let mut c6 = vec![0.0; natoms * natoms];
let mut alpha = vec![0.0; natoms];
let mut error = DFTD4Error::new();
unsafe {
ffi::dftd4_get_properties(
error.get_c_ptr(),
structure.ptr,
self.ptr,
cn.as_mut_ptr(),
charges.as_mut_ptr(),
c6.as_mut_ptr(),
alpha.as_mut_ptr(),
)
};
match error.check() {
true => Err(error),
false => Ok(DFTD4PropertyOutput { cn, charges, c6, alpha }),
}
}
pub fn from_structure_f(structure: DFTD4Structure) -> Result<Self, DFTD4Error> {
let mut error = DFTD4Error::new();
let ptr = unsafe { ffi::dftd4_new_d4_model(error.get_c_ptr(), structure.ptr) };
match error.check() {
true => Err(error),
false => Ok(Self { ptr, structure }),
}
}
pub fn update_f(
&mut self,
positions: &[f64],
lattice: Option<&[f64]>,
) -> Result<(), DFTD4Error> {
self.structure.update_f(positions, lattice)
}
}
#[cfg(test)]
mod tests {
use crate::ffi::dftd4_load_rational_damping;
use super::*;
#[test]
fn test_get_api_version() {
println!("API version: {}", dftd4_get_api_version());
}
#[test]
fn test_get_api_version_compact() {
println!("API version: {:?}", dftd4_get_api_version_compact());
}
#[test]
fn test_dftd4_error() {
let mut error = DFTD4Error::new();
println!("Error check : {}", error.check());
println!("Error message : {}", error.get_message());
let token = std::ffi::CString::new("Hello").unwrap();
unsafe { dftd4_load_rational_damping(error.get_c_ptr(), token.into_raw(), false) };
println!("Error check : {}", error.check());
println!("Error message : {}", error.get_message());
let token = std::ffi::CString::new("B3LYP").unwrap();
unsafe { dftd4_load_rational_damping(error.get_c_ptr(), token.into_raw(), false) };
println!("Error check : {}", error.check());
println!("Error message : {}", error.get_message());
}
#[test]
fn test_get_dispersion() {
let numbers = vec![1, 1];
let positions = vec![0.0, 0.0, 0.0, 0.0, 0.0, 1.0];
let model = DFTD4Model::new(&numbers, &positions, None, None, None);
let param = DFTD4Param::load_rational_damping("B3LYP", false);
let (energy, grad, sigma) = model.get_dispersion(¶m, true).into();
println!("Dispersion energy: {}", energy);
println!("Dispersion gradient: {:?}", grad);
println!("Dispersion sigma: {:?}", sigma);
}
}