use crate::prelude::*;
unsafe fn cstr_to_string(ptr: *const c_char) -> String {
if ptr.is_null() {
return String::new();
}
CStr::from_ptr(ptr).to_string_lossy().into_owned()
}
#[derive(Debug, Clone)]
pub struct LibXCReference {
pub ref_text: String,
pub doi: String,
pub bibtex: String,
pub key: String,
}
pub struct LibXCFunctional {
pub(crate) ptr: *mut ffi::xc_func_type,
}
impl LibXCFunctional {
pub fn from_identifier(name: &str, spin: LibXCSpin) -> Self {
Self::from_identifier_f(name, spin).unwrap()
}
pub fn from_identifier_f(name: &str, spin: LibXCSpin) -> Result<Self, LibXCError> {
let func_id = crate::util::libxc_functional_get_number(name)
.ok_or_else(|| LibXCError::NotFound(format!("functional '{name}'")))?;
Self::from_number_f(func_id, spin)
}
pub fn from_number(func_id: i32, spin: LibXCSpin) -> Self {
Self::from_number_f(func_id, spin).unwrap()
}
pub fn from_number_f(func_id: i32, spin: LibXCSpin) -> Result<Self, LibXCError> {
unsafe {
let ptr = ffi::xc_func_alloc();
if ptr.is_null() {
return Err(LibXCError::InitError { func_id, spin });
}
let rc = Self::init_func(ptr, func_id, spin);
if rc != 0 {
ffi::xc_func_free(ptr);
return Err(LibXCError::InitError { func_id, spin });
}
Ok(Self { ptr })
}
}
unsafe fn init_func(ptr: *mut ffi::xc_func_type, func_id: i32, spin: LibXCSpin) -> c_int {
#[cfg(not(feature = "api-v7_1"))]
{
ffi::xc_func_init(ptr, func_id as c_int, spin as c_int)
}
#[cfg(feature = "api-v7_1")]
{
ffi::xc_func_init_flags(
ptr,
func_id as c_int,
spin as c_int,
LibXCDeviceFlag::OnHost as c_int,
)
}
}
}
#[cfg(feature = "cuda")]
impl LibXCFunctional {
pub fn from_identifier_with_device(
name: &str,
spin: LibXCSpin,
device: LibXCDeviceFlag,
) -> Self {
Self::from_identifier_with_device_f(name, spin, device).unwrap()
}
pub fn from_identifier_with_device_f(
name: &str,
spin: LibXCSpin,
device: LibXCDeviceFlag,
) -> Result<Self, LibXCError> {
let func_id = crate::util::libxc_functional_get_number(name)
.ok_or_else(|| LibXCError::NotFound(format!("functional '{name}'")))?;
Self::from_number_with_device_f(func_id, spin, device)
}
pub fn from_number_with_device(func_id: i32, spin: LibXCSpin, device: LibXCDeviceFlag) -> Self {
Self::from_number_with_device_f(func_id, spin, device).unwrap()
}
pub fn from_number_with_device_f(
func_id: i32,
spin: LibXCSpin,
device: LibXCDeviceFlag,
) -> Result<Self, LibXCError> {
unsafe {
let ptr = ffi::xc_func_alloc();
if ptr.is_null() {
return Err(LibXCError::InitError { func_id, spin });
}
let rc = {
#[cfg(feature = "api-v7_1")]
{
ffi::xc_func_init_flags(ptr, func_id as c_int, spin as c_int, device as c_int)
}
#[cfg(not(feature = "api-v7_1"))]
{
ffi::xc_func_init(ptr, func_id as c_int, spin as c_int)
}
};
if rc != 0 {
ffi::xc_func_free(ptr);
return Err(LibXCError::InitError { func_id, spin });
}
(*(*ptr).info).flags |= device as i32;
Ok(Self { ptr })
}
}
pub fn device_flag(&self) -> Option<LibXCDeviceFlag> {
let flags = self.flags();
if flags.contains(LibXCFlags::OnDevice) {
Some(LibXCDeviceFlag::OnDevice)
} else if flags.contains(LibXCFlags::OnHost) {
Some(LibXCDeviceFlag::OnHost)
} else {
None
}
}
pub fn is_on_device(&self) -> bool {
self.flags().contains(LibXCFlags::OnDevice)
}
}
impl LibXCFunctional {
pub fn as_ptr(&self) -> *const ffi::xc_func_type {
self.ptr
}
pub fn info(&self) -> *const ffi::xc_func_info_type {
unsafe { ffi::xc_func_get_info(self.ptr) }
}
pub fn number(&self) -> i32 {
unsafe { ffi::xc_func_info_get_number(self.info()) as i32 }
}
pub fn kind(&self) -> LibXCFunctionalKind {
let k = unsafe { ffi::xc_func_info_get_kind(self.info()) } as u32;
match k {
ffi::XC_EXCHANGE => LibXCFunctionalKind::Exchange,
ffi::XC_CORRELATION => LibXCFunctionalKind::Correlation,
ffi::XC_EXCHANGE_CORRELATION => LibXCFunctionalKind::ExchangeCorrelation,
ffi::XC_KINETIC => LibXCFunctionalKind::Kinetic,
_ => panic!("Unknown functional kind code: {k}"),
}
}
pub fn identifier(&self) -> String {
crate::util::libxc_functional_get_name(self.number()).unwrap_or_default()
}
pub fn info_name(&self) -> String {
unsafe { cstr_to_string(ffi::xc_func_info_get_name(self.info())) }
}
pub fn family(&self) -> LibXCFamily {
let f = unsafe { ffi::xc_func_info_get_family(self.info()) } as u32;
match f {
ffi::XC_FAMILY_LDA => LibXCFamily::LDA,
ffi::XC_FAMILY_GGA => LibXCFamily::GGA,
ffi::XC_FAMILY_MGGA => LibXCFamily::MGGA,
ffi::XC_FAMILY_LCA => LibXCFamily::LCA,
ffi::XC_FAMILY_OEP => LibXCFamily::OEP,
ffi::XC_FAMILY_HYB_GGA => LibXCFamily::HybGGA,
ffi::XC_FAMILY_HYB_MGGA => LibXCFamily::HybMGGA,
ffi::XC_FAMILY_HYB_LDA => LibXCFamily::HybLDA,
_ => panic!("Unknown functional family code: {f}"),
}
}
pub fn flags(&self) -> BitFlags<LibXCFlags> {
let f = unsafe { ffi::xc_func_info_get_flags(self.info()) };
BitFlags::from_bits(f as u32).unwrap_or_else(|_| BitFlags::empty())
}
pub fn spin(&self) -> LibXCSpin {
match unsafe { (*self.ptr).nspin as u32 } {
ffi::XC_UNPOLARIZED => LibXCSpin::Unpolarized,
ffi::XC_POLARIZED => LibXCSpin::Polarized,
n => panic!("Unknown spin code: {n}"),
}
}
pub fn dim(&self) -> &ffi::xc_dimensions {
unsafe { &(*self.ptr).dim }
}
pub(crate) fn has_flag(&self, flag: LibXCFlags) -> bool {
self.flags().contains(flag)
}
pub fn has_exc(&self) -> bool {
self.has_flag(LibXCFlags::HaveEXC)
}
pub fn has_vxc(&self) -> bool {
self.has_flag(LibXCFlags::HaveVXC)
}
pub fn has_fxc(&self) -> bool {
self.has_flag(LibXCFlags::HaveFXC)
}
pub fn has_kxc(&self) -> bool {
self.has_flag(LibXCFlags::HaveKXC)
}
pub fn has_lxc(&self) -> bool {
self.has_flag(LibXCFlags::HaveLXC)
}
pub fn needs_laplacian(&self) -> bool {
self.has_flag(LibXCFlags::NeedsLaplacian)
}
pub fn needs_tau(&self) -> bool {
self.has_flag(LibXCFlags::NeedsTau)
}
pub fn is_hyb_cam(&self) -> bool {
self.has_flag(LibXCFlags::HybCAM)
|| self.has_flag(LibXCFlags::HybCAMY)
|| self.has_flag(LibXCFlags::HybLC)
|| self.has_flag(LibXCFlags::HybLCY)
}
pub fn references(&self) -> Vec<LibXCReference> {
let mut refs = Vec::new();
for i in 0..(ffi::XC_MAX_REFERENCES as i32) {
let ref_ptr = unsafe { ffi::xc_func_info_get_references(self.info(), i) };
if ref_ptr.is_null() {
break;
}
refs.push(LibXCReference {
ref_text: unsafe { cstr_to_string(ffi::xc_func_reference_get_ref(ref_ptr)) },
doi: unsafe { cstr_to_string(ffi::xc_func_reference_get_doi(ref_ptr)) },
bibtex: unsafe { cstr_to_string(ffi::xc_func_reference_get_bibtex(ref_ptr)) },
key: unsafe { cstr_to_string(ffi::xc_func_reference_get_key(ref_ptr)) },
});
}
refs
}
pub fn describe(&self) -> String {
use crate::enums::libxc_enum_items::*;
let flag_all_deriv = HaveEXC | HaveVXC | HaveFXC | HaveKXC | HaveLXC;
let flag_all_dim = Dim1 | Dim2 | Dim3;
let flag_all_cam = HybCAM | HybCAMY | HybLC | HybLCY;
let flag_vv10 = VV10;
let flag_all_mgga = NeedsLaplacian | NeedsTau | EnforceFHC;
let flag_all_device = OnHost | OnDevice;
let references = self.references();
let mut lst = vec![
format!("ID Number : {}", self.number()),
format!("Identifier : {}", self.identifier()),
format!("Description : {}", self.info_name()),
"Attributes".to_string(),
format!(" Kind : {:?}", self.kind()),
format!(" Family : {:?}", self.family()),
format!(" Spin : {:?}", self.spin()),
"Flags".to_string(),
format!(" Derivative : {}", self.flags() & flag_all_deriv),
format!(" Dimension : {}", self.flags() & flag_all_dim),
format!(" CAM : {}", self.flags() & flag_all_cam),
format!(" VV10 : {}", self.flags() & flag_vv10),
format!(" MGGA : {}", self.flags() & flag_all_mgga),
format!(" Device : {}", self.flags() & flag_all_device),
];
let default_func = Self::from_number_f(self.number(), self.spin()).unwrap();
if let Some(hyb_exx_coef) = self.hyb_exx_coef() {
let default_hyb_exx_coef = default_func.hyb_exx_coef().unwrap();
lst.push("Hybrid Functional (non-CAM-type)".to_string());
if hyb_exx_coef == default_hyb_exx_coef {
lst.push(format!(" hyb_exx_coef: {hyb_exx_coef}"));
} else {
lst.push(format!(
" hyb_exx_coef: {hyb_exx_coef} (default: {default_hyb_exx_coef})"
));
}
}
if let Some((cam_alpha, cam_beta, cam_omega)) = self.cam_coef() {
let (def_alpha, def_beta, def_omega) = default_func.cam_coef().unwrap();
lst.push("Hybrid Functional (CAM-type)".to_string());
if (cam_alpha, cam_beta, cam_omega) == (def_alpha, def_beta, def_omega) {
lst.push(format!(" cam_alpha : {cam_alpha}"));
lst.push(format!(" cam_beta : {cam_beta}"));
lst.push(format!(" cam_omega : {cam_omega}"));
} else {
lst.push(format!(" cam_alpha : {cam_alpha} (default: {def_alpha})"));
lst.push(format!(" cam_beta : {cam_beta} (default: {def_beta})"));
lst.push(format!(" cam_omega : {cam_omega} (default: {def_omega})"));
}
}
if let Some((nlc_b, nlc_c)) = self.vv10_coef() {
let (def_nlc_b, def_nlc_c) = default_func.vv10_coef().unwrap();
lst.push("VV10 Functional".to_string());
if (nlc_b, nlc_c) == (def_nlc_b, def_nlc_c) {
lst.push(format!(" nlc_b : {nlc_b}"));
lst.push(format!(" nlc_C : {nlc_c}"));
} else {
lst.push(format!(" nlc_b : {nlc_b} (default: {def_nlc_b})"));
lst.push(format!(" nlc_C : {nlc_c} (default: {def_nlc_c})"));
}
}
let trim_float =
|s: f64| format!("{:20.15}", s).trim_end_matches('0').trim_end_matches('.').to_string();
let ext_param_number = self.n_ext_params();
if ext_param_number > 0 {
let ext_param_names = self.ext_param_names();
let ext_param_values = self.ext_param_values();
let ext_param_descriptions = self.ext_param_descriptions();
let ext_param_default = self.ext_param_default_values();
lst.push("External Parameters".to_string());
for i in 0..ext_param_number as usize {
if ext_param_values[i] == ext_param_default[i] {
lst.push(format!(
" - {:>9} = {:<20} {}",
ext_param_names[i],
trim_float(ext_param_values[i]),
ext_param_descriptions[i],
));
} else {
lst.push(format!(
" - {:>9} = {:<20} {:<50} (default: {:<20})",
ext_param_names[i],
trim_float(ext_param_values[i]),
ext_param_descriptions[i],
trim_float(ext_param_default[i])
))
};
}
}
if !references.is_empty() {
lst.push("References".to_string());
for r in references {
lst.extend([format!(" - {}", r.ref_text), format!(" DOI: {}", r.doi)]);
}
}
lst.join("\n")
}
}
impl LibXCFunctional {
pub fn n_ext_params(&self) -> i32 {
unsafe { ffi::xc_func_info_get_n_ext_params(self.info()) as i32 }
}
pub fn ext_param_names(&self) -> Vec<String> {
let n = self.n_ext_params();
(0..n)
.map(|i| unsafe {
cstr_to_string(ffi::xc_func_info_get_ext_params_name(self.info(), i as c_int))
})
.collect()
}
pub fn ext_param_descriptions(&self) -> Vec<String> {
let n = self.n_ext_params();
(0..n)
.map(|i| unsafe {
cstr_to_string(ffi::xc_func_info_get_ext_params_description(
self.info(),
i as c_int,
))
})
.collect()
}
pub fn ext_param_default_values(&self) -> Vec<f64> {
let n = self.n_ext_params();
(0..n)
.map(|i| unsafe {
ffi::xc_func_info_get_ext_params_default_value(self.info(), i as c_int)
})
.collect()
}
pub fn ext_param_values(&self) -> Vec<f64> {
let n = self.n_ext_params();
(0..n).map(|i| unsafe { ffi::xc_func_get_ext_params_value(self.ptr, i as c_int) }).collect()
}
pub fn ext_param_default_map(&self) -> IndexMap<String, (f64, String)> {
let names = self.ext_param_names();
let descriptions = self.ext_param_descriptions();
let default_values = self.ext_param_default_values();
let mut map = IndexMap::new();
for i in 0..names.len() {
map.insert(names[i].clone(), (default_values[i], descriptions[i].clone()));
}
map
}
pub fn ext_param_map(&self) -> IndexMap<String, f64> {
let names = self.ext_param_names();
let values = self.ext_param_values();
let mut map = IndexMap::new();
for i in 0..names.len() {
map.insert(names[i].clone(), values[i]);
}
map
}
pub fn set_ext_params(&mut self, params: &[f64]) {
self.set_ext_params_f(params).unwrap()
}
pub fn set_ext_params_f(&mut self, params: &[f64]) -> Result<(), LibXCError> {
let n = self.n_ext_params() as usize;
if params.len() != n {
return Err(LibXCError::ParamSetError {
param_name: "ext_params".to_string(),
details: format!("expected {n} parameters, got {}", params.len()),
});
}
unsafe {
ffi::xc_func_set_ext_params(self.ptr, params.as_ptr());
}
Ok(())
}
pub fn set_ext_param_map(
&mut self,
param_map: impl Iterator<Item = (impl AsRef<str>, impl Borrow<f64>)>,
) {
self.set_ext_param_map_f(param_map).unwrap()
}
pub fn set_ext_param_map_f(
&mut self,
param_map: impl Iterator<Item = (impl AsRef<str>, impl Borrow<f64>)>,
) -> Result<(), LibXCError> {
let mut map = self.ext_param_map();
for (key, val) in param_map.into_iter() {
let (key, val) = (key.as_ref(), *val.borrow());
if !map.contains_key(key) {
return Err(LibXCError::ParamSetError {
param_name: key.to_string(),
details: "external parameter not found".to_string(),
});
}
map.insert(key.to_string(), val);
}
let params: Vec<f64> = map.values().cloned().collect();
self.set_ext_params_f(¶ms)?;
Ok(())
}
pub fn set_ext_param_by_name(&mut self, name: &str, value: f64) {
self.set_ext_param_by_name_f(name, value).unwrap()
}
pub fn set_ext_param_by_name_f(&mut self, name: &str, value: f64) -> Result<(), LibXCError> {
if !self.ext_param_names().contains(&name.to_string()) {
return Err(LibXCError::ParamSetError {
param_name: name.to_string(),
details: "external parameter not found".to_string(),
});
}
let c_name = CString::new(name).expect("parameter name contains null byte");
unsafe { ffi::xc_func_set_ext_params_name(self.ptr, c_name.as_ptr(), value) };
Ok(())
}
}
impl LibXCFunctional {
pub fn dens_threshold(&self) -> f64 {
unsafe { (*self.ptr).dens_threshold }
}
pub fn set_dens_threshold(&mut self, threshold: f64) {
unsafe { ffi::xc_func_set_dens_threshold(self.ptr, threshold) }
}
pub fn zeta_threshold(&self) -> f64 {
unsafe { (*self.ptr).zeta_threshold }
}
pub fn set_zeta_threshold(&mut self, threshold: f64) {
unsafe { ffi::xc_func_set_zeta_threshold(self.ptr, threshold) }
}
pub fn sigma_threshold(&self) -> f64 {
unsafe { (*self.ptr).sigma_threshold }
}
pub fn set_sigma_threshold(&mut self, threshold: f64) {
unsafe { ffi::xc_func_set_sigma_threshold(self.ptr, threshold) }
}
pub fn tau_threshold(&self) -> f64 {
unsafe { (*self.ptr).tau_threshold }
}
pub fn set_tau_threshold(&mut self, threshold: f64) {
unsafe { ffi::xc_func_set_tau_threshold(self.ptr, threshold) }
}
#[cfg(feature = "api-v7_0")]
pub fn set_fhc_enforcement(&mut self, on: bool) {
unsafe { ffi::xc_func_set_fhc_enforcement(self.ptr, on as c_int) }
}
}
impl LibXCFunctional {
pub fn hyb_exx_coef(&self) -> Option<f64> {
if matches!(self.family(), LibXCFamily::HybGGA | LibXCFamily::HybMGGA | LibXCFamily::HybLDA)
&& !self.is_hyb_cam()
{
Some(unsafe { ffi::xc_hyb_exx_coef(self.ptr) })
} else {
None
}
}
pub fn cam_coef(&self) -> Option<(f64, f64, f64)> {
if matches!(self.family(), LibXCFamily::HybGGA | LibXCFamily::HybMGGA | LibXCFamily::HybLDA)
&& self.is_hyb_cam()
{
let mut omega: f64 = 0.0;
let mut alpha: f64 = 0.0;
let mut beta: f64 = 0.0;
unsafe { ffi::xc_hyb_cam_coef(self.ptr, &mut omega, &mut alpha, &mut beta) };
Some((omega, alpha, beta))
} else {
None
}
}
pub fn vv10_coef(&self) -> Option<(f64, f64)> {
if self.has_flag(LibXCFlags::VV10) {
let mut nlc_b: f64 = 0.0;
#[allow(non_snake_case)]
let mut nlc_C: f64 = 0.0;
unsafe {
ffi::xc_nlc_coef(self.ptr, &mut nlc_b, &mut nlc_C);
}
Some((nlc_b, nlc_C))
} else {
None
}
}
pub fn aux_funcs(&self) -> Vec<(String, f64)> {
self.aux_funcs_by_id()
.into_iter()
.map(|(id, weight)| {
let name = crate::util::libxc_functional_get_name(id).unwrap_or_default();
(name, weight)
})
.collect()
}
pub fn aux_funcs_by_id(&self) -> Vec<(i32, f64)> {
let n = unsafe { ffi::xc_num_aux_funcs(self.ptr) as i32 };
let mut ids = vec![0 as c_int; n as usize];
let mut weights = vec![0.0f64; n as usize];
unsafe { ffi::xc_aux_func_ids(self.ptr, ids.as_mut_ptr()) }
unsafe { ffi::xc_aux_func_weights(self.ptr, weights.as_mut_ptr()) }
ids.into_iter().zip(weights).collect()
}
}
impl Drop for LibXCFunctional {
fn drop(&mut self) {
unsafe {
ffi::xc_func_end(self.ptr);
ffi::xc_func_free(self.ptr);
}
}
}
impl core::fmt::Debug for LibXCFunctional {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("LibXCFunctional")
.field("name", &self.identifier())
.field("number", &self.number())
.field("family", &self.family())
.field("spin", &self.spin())
.finish()
}
}