use crate::basis::CuestAOBasis;
use crate::context::ContextInner;
use crate::error::{status_check, CuestError};
use crate::pair_list::CuestAOPairList;
use crate::parameters::CuestParameters;
use crate::workspace::{new_descriptor, Workspace};
use cudarc::driver::{CudaSlice, DevicePtr, DevicePtrMut};
use cuest_ffi::ffi;
use std::ffi::c_int;
use std::ptr;
use std::sync::Arc;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum CuestFfloatUsageMode {
Default,
On,
Off,
}
impl CuestFfloatUsageMode {
fn to_raw(self) -> ffi::cuestFfloatUsageMode_t {
match self {
Self::Default => ffi::CUEST_FFLOAT_USAGE_MODE_DEFAULT,
Self::On => ffi::CUEST_FFLOAT_USAGE_MODE_ON,
Self::Off => ffi::CUEST_FFLOAT_USAGE_MODE_OFF,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum CuestJitUsageMode {
On,
Off,
}
impl CuestJitUsageMode {
fn to_raw(self) -> ffi::cuestJITUsageMode_t {
match self {
Self::On => ffi::CUEST_JIT_USAGE_MODE_ON,
Self::Off => ffi::CUEST_JIT_USAGE_MODE_OFF,
}
}
}
#[derive(Debug, Default, Clone)]
pub struct CuestPotentialComputeOptions {
pub ffloat_usage_mode: Option<CuestFfloatUsageMode>,
pub jit_usage_mode: Option<CuestJitUsageMode>,
}
struct PlanInner {
context: Arc<ContextInner>,
handle: ffi::cuestOEIntPlan_t,
_basis: CuestAOBasis,
_pair_list: CuestAOPairList,
_persistent_workspace: Workspace,
nao: u64,
}
impl PlanInner {
fn temporary_workspace(
&self,
query: impl FnOnce(&mut ffi::cuestWorkspaceDescriptor_t) -> ffi::cuestStatus_t,
name: &'static str,
) -> Result<Workspace, CuestError> {
let mut desc = new_descriptor();
status_check(query(&mut desc), name)?;
Workspace::new(self.context.stream(), &desc)
}
}
impl Drop for PlanInner {
fn drop(&mut self) {
let _ = self.context.stream().context().bind_to_thread();
let _ = unsafe { ffi::cuestOEIntPlanDestroy(self.handle) };
}
}
pub struct CuestOeIntPlan {
inner: Arc<PlanInner>,
}
impl CuestOeIntPlan {
pub fn new(basis: &CuestAOBasis, pair_list: &CuestAOPairList) -> Result<Self, CuestError> {
if !Arc::ptr_eq(basis.inner(), pair_list.inner().basis.inner()) {
return Err(CuestError::Rust(
"pair list was built from a different basis than the one given".to_string(),
));
}
let context = basis.inner().context.clone();
let params = CuestParameters::create(ffi::CUEST_OEINTPLAN_PARAMETERS)?;
let (mut persistent_desc, mut temporary_desc) = (new_descriptor(), new_descriptor());
status_check(
unsafe {
ffi::cuestOEIntPlanCreateWorkspaceQuery(
context.handle(),
basis.raw_handle(),
pair_list.raw_handle(),
params.raw(),
&mut persistent_desc,
&mut temporary_desc,
ptr::null_mut(),
)
},
"cuestOEIntPlanCreateWorkspaceQuery",
)?;
let stream = context.stream().clone();
let persistent = Workspace::new(&stream, &persistent_desc)?;
let temporary = Workspace::new(&stream, &temporary_desc)?;
let mut handle: ffi::cuestOEIntPlan_t = ptr::null_mut();
{
let (mut persistent_raw, _persistent_guard) = persistent.raw(&stream);
let (mut temporary_raw, _temporary_guard) = temporary.raw(&stream);
status_check(
unsafe {
ffi::cuestOEIntPlanCreate(
context.handle(),
basis.raw_handle(),
pair_list.raw_handle(),
params.raw(),
&mut persistent_raw,
&mut temporary_raw,
&mut handle,
)
},
"cuestOEIntPlanCreate",
)?;
}
Ok(Self {
inner: Arc::new(PlanInner {
context,
handle,
_basis: basis.clone(),
_pair_list: pair_list.clone(),
_persistent_workspace: persistent,
nao: basis.num_ao(),
}),
})
}
pub fn nao(&self) -> u64 {
self.inner.nao
}
pub fn overlap(&self) -> Result<CudaSlice<f64>, CuestError> {
let mut out = self.alloc_output()?;
self.overlap_into(&mut out)?;
Ok(out)
}
pub fn overlap_into(&self, out: &mut impl DevicePtrMut<f64>) -> Result<(), CuestError> {
self.inner.context.check_stream(out.stream(), "out")?;
check_output_len(self.inner.nao, out.len())?;
let params = CuestParameters::create(ffi::CUEST_OVERLAPCOMPUTE_PARAMETERS)?;
let temp = self.inner.temporary_workspace(
|desc| unsafe {
ffi::cuestOverlapComputeWorkspaceQuery(
self.inner.context.handle(),
self.inner.handle,
params.raw(),
desc,
ptr::null_mut(),
)
},
"cuestOverlapComputeWorkspaceQuery",
)?;
let stream = self.inner.context.stream();
let (out_ptr, _out_guard) = out.device_ptr_mut(stream);
let (mut ws, _ws_guard) = temp.raw(stream);
status_check(
unsafe {
ffi::cuestOverlapCompute(
self.inner.context.handle(),
self.inner.handle,
params.raw(),
&mut ws,
out_ptr as *mut f64,
)
},
"cuestOverlapCompute",
)
}
pub fn kinetic(&self) -> Result<CudaSlice<f64>, CuestError> {
let mut out = self.alloc_output()?;
self.kinetic_into(&mut out)?;
Ok(out)
}
pub fn kinetic_into(&self, out: &mut impl DevicePtrMut<f64>) -> Result<(), CuestError> {
self.inner.context.check_stream(out.stream(), "out")?;
check_output_len(self.inner.nao, out.len())?;
let params = CuestParameters::create(ffi::CUEST_KINETICCOMPUTE_PARAMETERS)?;
let temp = self.inner.temporary_workspace(
|desc| unsafe {
ffi::cuestKineticComputeWorkspaceQuery(
self.inner.context.handle(),
self.inner.handle,
params.raw(),
desc,
ptr::null_mut(),
)
},
"cuestKineticComputeWorkspaceQuery",
)?;
let stream = self.inner.context.stream();
let (out_ptr, _out_guard) = out.device_ptr_mut(stream);
let (mut ws, _ws_guard) = temp.raw(stream);
status_check(
unsafe {
ffi::cuestKineticCompute(
self.inner.context.handle(),
self.inner.handle,
params.raw(),
&mut ws,
out_ptr as *mut f64,
)
},
"cuestKineticCompute",
)
}
pub fn potential(
&self,
xyz: &impl DevicePtr<f64>,
charges: &impl DevicePtr<f64>,
) -> Result<CudaSlice<f64>, CuestError> {
self.potential_with_options(&CuestPotentialComputeOptions::default(), xyz, charges)
}
pub fn potential_into(
&self,
out: &mut impl DevicePtrMut<f64>,
xyz: &impl DevicePtr<f64>,
charges: &impl DevicePtr<f64>,
) -> Result<(), CuestError> {
self.potential_with_options_into(
&CuestPotentialComputeOptions::default(),
out,
xyz,
charges,
)
}
pub fn potential_with_options(
&self,
options: &CuestPotentialComputeOptions,
xyz: &impl DevicePtr<f64>,
charges: &impl DevicePtr<f64>,
) -> Result<CudaSlice<f64>, CuestError> {
let mut out = self.alloc_output()?;
self.potential_with_options_into(options, &mut out, xyz, charges)?;
Ok(out)
}
pub fn potential_with_options_into(
&self,
options: &CuestPotentialComputeOptions,
out: &mut impl DevicePtrMut<f64>,
xyz: &impl DevicePtr<f64>,
charges: &impl DevicePtr<f64>,
) -> Result<(), CuestError> {
self.inner.context.check_stream(out.stream(), "out")?;
self.inner.context.check_stream(xyz.stream(), "xyz")?;
self.inner.context.check_stream(charges.stream(), "charges")?;
check_output_len(self.inner.nao, out.len())?;
if xyz.len() == 0 || xyz.len() % 3 != 0 {
return Err(CuestError::Rust(format!(
"`xyz` has {} coordinates; expected 3 per charge",
xyz.len()
)));
}
let num_charges = xyz.len() / 3;
if charges.len() != num_charges {
return Err(CuestError::Rust(format!(
"`charges` has {} entries for {num_charges} charge positions",
charges.len()
)));
}
let params = CuestParameters::create(ffi::CUEST_POTENTIALCOMPUTE_PARAMETERS)?;
if let Some(mode) = options.ffloat_usage_mode {
params.configure_i32(
ffi::CUEST_POTENTIALCOMPUTE_PARAMETERS_FFLOAT_USAGE_MODE.0 as c_int,
mode.to_raw().0 as i32,
)?;
}
if let Some(mode) = options.jit_usage_mode {
params.configure_i32(
ffi::CUEST_POTENTIALCOMPUTE_PARAMETERS_JIT_USAGE_MODE.0 as c_int,
mode.to_raw().0 as i32,
)?;
}
let num_charges = num_charges as u64;
let temp = self.inner.temporary_workspace(
|desc| unsafe {
ffi::cuestPotentialComputeWorkspaceQuery(
self.inner.context.handle(),
self.inner.handle,
params.raw(),
desc,
num_charges,
ptr::null(),
ptr::null(),
ptr::null_mut(),
)
},
"cuestPotentialComputeWorkspaceQuery",
)?;
let stream = self.inner.context.stream();
let (xyz_ptr, _xyz_guard) = xyz.device_ptr(stream);
let (charges_ptr, _charges_guard) = charges.device_ptr(stream);
let (out_ptr, _out_guard) = out.device_ptr_mut(stream);
let (mut ws, _ws_guard) = temp.raw(stream);
status_check(
unsafe {
ffi::cuestPotentialCompute(
self.inner.context.handle(),
self.inner.handle,
params.raw(),
&mut ws,
num_charges,
xyz_ptr as *const f64,
charges_ptr as *const f64,
out_ptr as *mut f64,
)
},
"cuestPotentialCompute",
)
}
fn alloc_output(&self) -> Result<CudaSlice<f64>, CuestError> {
let count = (self.inner.nao * self.inner.nao) as usize;
Ok(self.inner.context.stream().alloc_zeros::<f64>(count)?)
}
pub fn raw_handle(&self) -> ffi::cuestOEIntPlan_t {
self.inner.handle
}
}
impl Clone for CuestOeIntPlan {
fn clone(&self) -> Self {
Self { inner: self.inner.clone() }
}
}
impl std::fmt::Debug for CuestOeIntPlan {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("CuestOeIntPlan").field("nao", &self.nao()).finish_non_exhaustive()
}
}
unsafe impl Send for CuestOeIntPlan {}
fn check_output_len(nao: u64, len: usize) -> Result<(), CuestError> {
let needed = (nao * nao) as usize;
if len < needed {
Err(CuestError::Rust(format!(
"output matrix has {len} elements, expected at least {needed} (nao = {nao})"
)))
} else {
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn cpu_check_output_len() {
assert!(check_output_len(3, 9).is_ok());
assert!(check_output_len(3, 10).is_ok());
assert!(check_output_len(3, 8).is_err());
assert!(check_output_len(0, 0).is_ok());
}
}