1use crate::basis::CuestAOBasis;
4use crate::context::ContextInner;
5use crate::error::{status_check, CuestError};
6use crate::pair_list::CuestAOPairList;
7use crate::parameters::CuestParameters;
8use crate::workspace::{new_descriptor, Workspace};
9use cudarc::driver::{CudaSlice, DevicePtr, DevicePtrMut};
10use cuest_ffi::ffi;
11use std::ffi::c_int;
12use std::ptr;
13use std::sync::Arc;
14
15#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
17#[non_exhaustive]
18pub enum CuestFfloatUsageMode {
19 Default,
22 On,
24 Off,
26}
27
28impl CuestFfloatUsageMode {
29 fn to_raw(self) -> ffi::cuestFfloatUsageMode_t {
30 match self {
31 Self::Default => ffi::CUEST_FFLOAT_USAGE_MODE_DEFAULT,
32 Self::On => ffi::CUEST_FFLOAT_USAGE_MODE_ON,
33 Self::Off => ffi::CUEST_FFLOAT_USAGE_MODE_OFF,
34 }
35 }
36}
37
38#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
40#[non_exhaustive]
41pub enum CuestJitUsageMode {
42 On,
45 Off,
47}
48
49impl CuestJitUsageMode {
50 fn to_raw(self) -> ffi::cuestJITUsageMode_t {
51 match self {
52 Self::On => ffi::CUEST_JIT_USAGE_MODE_ON,
53 Self::Off => ffi::CUEST_JIT_USAGE_MODE_OFF,
54 }
55 }
56}
57
58#[derive(Debug, Default, Clone)]
65pub struct CuestPotentialComputeOptions {
66 pub ffloat_usage_mode: Option<CuestFfloatUsageMode>,
68 pub jit_usage_mode: Option<CuestJitUsageMode>,
70}
71
72struct PlanInner {
74 context: Arc<ContextInner>,
75 handle: ffi::cuestOEIntPlan_t,
76 _basis: CuestAOBasis,
79 _pair_list: CuestAOPairList,
80 _persistent_workspace: Workspace,
81 nao: u64,
82}
83
84impl PlanInner {
85 fn temporary_workspace(
88 &self,
89 query: impl FnOnce(&mut ffi::cuestWorkspaceDescriptor_t) -> ffi::cuestStatus_t,
90 name: &'static str,
91 ) -> Result<Workspace, CuestError> {
92 let mut desc = new_descriptor();
93 status_check(query(&mut desc), name)?;
94 Workspace::new(self.context.stream(), &desc)
95 }
96}
97
98impl Drop for PlanInner {
99 fn drop(&mut self) {
100 let _ = self.context.stream().context().bind_to_thread();
103 let _ = unsafe { ffi::cuestOEIntPlanDestroy(self.handle) };
104 }
105}
106
107pub struct CuestOeIntPlan {
114 inner: Arc<PlanInner>,
115}
116
117impl CuestOeIntPlan {
118 pub fn new(basis: &CuestAOBasis, pair_list: &CuestAOPairList) -> Result<Self, CuestError> {
123 if !Arc::ptr_eq(basis.inner(), pair_list.inner().basis.inner()) {
124 return Err(CuestError::Rust(
125 "pair list was built from a different basis than the one given".to_string(),
126 ));
127 }
128 let context = basis.inner().context.clone();
129 let params = CuestParameters::create(ffi::CUEST_OEINTPLAN_PARAMETERS)?;
130 let (mut persistent_desc, mut temporary_desc) = (new_descriptor(), new_descriptor());
131 status_check(
132 unsafe {
133 ffi::cuestOEIntPlanCreateWorkspaceQuery(
134 context.handle(),
135 basis.raw_handle(),
136 pair_list.raw_handle(),
137 params.raw(),
138 &mut persistent_desc,
139 &mut temporary_desc,
140 ptr::null_mut(),
141 )
142 },
143 "cuestOEIntPlanCreateWorkspaceQuery",
144 )?;
145
146 let stream = context.stream().clone();
147 let persistent = Workspace::new(&stream, &persistent_desc)?;
148 let temporary = Workspace::new(&stream, &temporary_desc)?;
149 let mut handle: ffi::cuestOEIntPlan_t = ptr::null_mut();
150 {
151 let (mut persistent_raw, _persistent_guard) = persistent.raw(&stream);
152 let (mut temporary_raw, _temporary_guard) = temporary.raw(&stream);
153 status_check(
154 unsafe {
155 ffi::cuestOEIntPlanCreate(
156 context.handle(),
157 basis.raw_handle(),
158 pair_list.raw_handle(),
159 params.raw(),
160 &mut persistent_raw,
161 &mut temporary_raw,
162 &mut handle,
163 )
164 },
165 "cuestOEIntPlanCreate",
166 )?;
167 }
168 Ok(Self {
171 inner: Arc::new(PlanInner {
172 context,
173 handle,
174 _basis: basis.clone(),
175 _pair_list: pair_list.clone(),
176 _persistent_workspace: persistent,
177 nao: basis.num_ao(),
178 }),
179 })
180 }
181
182 pub fn nao(&self) -> u64 {
184 self.inner.nao
185 }
186
187 pub fn overlap(&self) -> Result<CudaSlice<f64>, CuestError> {
190 let mut out = self.alloc_output()?;
191 self.overlap_into(&mut out)?;
192 Ok(out)
193 }
194
195 pub fn overlap_into(&self, out: &mut impl DevicePtrMut<f64>) -> Result<(), CuestError> {
198 self.inner.context.check_stream(out.stream(), "out")?;
199 check_output_len(self.inner.nao, out.len())?;
200 let params = CuestParameters::create(ffi::CUEST_OVERLAPCOMPUTE_PARAMETERS)?;
201 let temp = self.inner.temporary_workspace(
202 |desc| unsafe {
203 ffi::cuestOverlapComputeWorkspaceQuery(
204 self.inner.context.handle(),
205 self.inner.handle,
206 params.raw(),
207 desc,
208 ptr::null_mut(),
209 )
210 },
211 "cuestOverlapComputeWorkspaceQuery",
212 )?;
213
214 let stream = self.inner.context.stream();
215 let (out_ptr, _out_guard) = out.device_ptr_mut(stream);
216 let (mut ws, _ws_guard) = temp.raw(stream);
217 status_check(
218 unsafe {
219 ffi::cuestOverlapCompute(
220 self.inner.context.handle(),
221 self.inner.handle,
222 params.raw(),
223 &mut ws,
224 out_ptr as *mut f64,
225 )
226 },
227 "cuestOverlapCompute",
228 )
229 }
230
231 pub fn kinetic(&self) -> Result<CudaSlice<f64>, CuestError> {
234 let mut out = self.alloc_output()?;
235 self.kinetic_into(&mut out)?;
236 Ok(out)
237 }
238
239 pub fn kinetic_into(&self, out: &mut impl DevicePtrMut<f64>) -> Result<(), CuestError> {
242 self.inner.context.check_stream(out.stream(), "out")?;
243 check_output_len(self.inner.nao, out.len())?;
244 let params = CuestParameters::create(ffi::CUEST_KINETICCOMPUTE_PARAMETERS)?;
245 let temp = self.inner.temporary_workspace(
246 |desc| unsafe {
247 ffi::cuestKineticComputeWorkspaceQuery(
248 self.inner.context.handle(),
249 self.inner.handle,
250 params.raw(),
251 desc,
252 ptr::null_mut(),
253 )
254 },
255 "cuestKineticComputeWorkspaceQuery",
256 )?;
257
258 let stream = self.inner.context.stream();
259 let (out_ptr, _out_guard) = out.device_ptr_mut(stream);
260 let (mut ws, _ws_guard) = temp.raw(stream);
261 status_check(
262 unsafe {
263 ffi::cuestKineticCompute(
264 self.inner.context.handle(),
265 self.inner.handle,
266 params.raw(),
267 &mut ws,
268 out_ptr as *mut f64,
269 )
270 },
271 "cuestKineticCompute",
272 )
273 }
274
275 pub fn potential(
284 &self,
285 xyz: &impl DevicePtr<f64>,
286 charges: &impl DevicePtr<f64>,
287 ) -> Result<CudaSlice<f64>, CuestError> {
288 self.potential_with_options(&CuestPotentialComputeOptions::default(), xyz, charges)
289 }
290
291 pub fn potential_into(
293 &self,
294 out: &mut impl DevicePtrMut<f64>,
295 xyz: &impl DevicePtr<f64>,
296 charges: &impl DevicePtr<f64>,
297 ) -> Result<(), CuestError> {
298 self.potential_with_options_into(
299 &CuestPotentialComputeOptions::default(),
300 out,
301 xyz,
302 charges,
303 )
304 }
305
306 pub fn potential_with_options(
308 &self,
309 options: &CuestPotentialComputeOptions,
310 xyz: &impl DevicePtr<f64>,
311 charges: &impl DevicePtr<f64>,
312 ) -> Result<CudaSlice<f64>, CuestError> {
313 let mut out = self.alloc_output()?;
314 self.potential_with_options_into(options, &mut out, xyz, charges)?;
315 Ok(out)
316 }
317
318 pub fn potential_with_options_into(
320 &self,
321 options: &CuestPotentialComputeOptions,
322 out: &mut impl DevicePtrMut<f64>,
323 xyz: &impl DevicePtr<f64>,
324 charges: &impl DevicePtr<f64>,
325 ) -> Result<(), CuestError> {
326 self.inner.context.check_stream(out.stream(), "out")?;
327 self.inner.context.check_stream(xyz.stream(), "xyz")?;
328 self.inner.context.check_stream(charges.stream(), "charges")?;
329 check_output_len(self.inner.nao, out.len())?;
330 if xyz.len() == 0 || xyz.len() % 3 != 0 {
331 return Err(CuestError::Rust(format!(
332 "`xyz` has {} coordinates; expected 3 per charge",
333 xyz.len()
334 )));
335 }
336 let num_charges = xyz.len() / 3;
337 if charges.len() != num_charges {
338 return Err(CuestError::Rust(format!(
339 "`charges` has {} entries for {num_charges} charge positions",
340 charges.len()
341 )));
342 }
343
344 let params = CuestParameters::create(ffi::CUEST_POTENTIALCOMPUTE_PARAMETERS)?;
345 if let Some(mode) = options.ffloat_usage_mode {
346 params.configure_i32(
347 ffi::CUEST_POTENTIALCOMPUTE_PARAMETERS_FFLOAT_USAGE_MODE.0 as c_int,
348 mode.to_raw().0 as i32,
349 )?;
350 }
351 if let Some(mode) = options.jit_usage_mode {
352 params.configure_i32(
353 ffi::CUEST_POTENTIALCOMPUTE_PARAMETERS_JIT_USAGE_MODE.0 as c_int,
354 mode.to_raw().0 as i32,
355 )?;
356 }
357
358 let num_charges = num_charges as u64;
359 let temp = self.inner.temporary_workspace(
360 |desc| unsafe {
361 ffi::cuestPotentialComputeWorkspaceQuery(
362 self.inner.context.handle(),
363 self.inner.handle,
364 params.raw(),
365 desc,
366 num_charges,
367 ptr::null(),
368 ptr::null(),
369 ptr::null_mut(),
370 )
371 },
372 "cuestPotentialComputeWorkspaceQuery",
373 )?;
374
375 let stream = self.inner.context.stream();
376 let (xyz_ptr, _xyz_guard) = xyz.device_ptr(stream);
377 let (charges_ptr, _charges_guard) = charges.device_ptr(stream);
378 let (out_ptr, _out_guard) = out.device_ptr_mut(stream);
379 let (mut ws, _ws_guard) = temp.raw(stream);
380 status_check(
381 unsafe {
382 ffi::cuestPotentialCompute(
383 self.inner.context.handle(),
384 self.inner.handle,
385 params.raw(),
386 &mut ws,
387 num_charges,
388 xyz_ptr as *const f64,
389 charges_ptr as *const f64,
390 out_ptr as *mut f64,
391 )
392 },
393 "cuestPotentialCompute",
394 )
395 }
396
397 fn alloc_output(&self) -> Result<CudaSlice<f64>, CuestError> {
399 let count = (self.inner.nao * self.inner.nao) as usize;
400 Ok(self.inner.context.stream().alloc_zeros::<f64>(count)?)
401 }
402
403 pub fn raw_handle(&self) -> ffi::cuestOEIntPlan_t {
405 self.inner.handle
406 }
407}
408
409impl Clone for CuestOeIntPlan {
410 fn clone(&self) -> Self {
411 Self { inner: self.inner.clone() }
412 }
413}
414
415impl std::fmt::Debug for CuestOeIntPlan {
416 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
417 f.debug_struct("CuestOeIntPlan").field("nao", &self.nao()).finish_non_exhaustive()
418 }
419}
420
421unsafe impl Send for CuestOeIntPlan {}
423
424fn check_output_len(nao: u64, len: usize) -> Result<(), CuestError> {
426 let needed = (nao * nao) as usize;
427 if len < needed {
428 Err(CuestError::Rust(format!(
429 "output matrix has {len} elements, expected at least {needed} (nao = {nao})"
430 )))
431 } else {
432 Ok(())
433 }
434}
435
436#[cfg(test)]
437mod tests {
438 use super::*;
439
440 #[test]
441 fn cpu_check_output_len() {
442 assert!(check_output_len(3, 9).is_ok());
443 assert!(check_output_len(3, 10).is_ok());
444 assert!(check_output_len(3, 8).is_err());
445 assert!(check_output_len(0, 0).is_ok());
446 }
447}