1use crate::context::{ContextInner, CuestContext};
9use crate::error::{status_check, CuestError};
10use crate::parameters::CuestParameters;
11use crate::query::{self, AoshellAttribute};
12use cuest_ffi::ffi;
13use std::ffi::c_void;
14use std::ptr;
15use std::sync::Arc;
16
17#[derive(Debug, Clone, PartialEq)]
26pub struct CuestShellSpec {
27 pub l: u64,
29 pub pure: bool,
32 pub exponents: Vec<f64>,
34 pub coefficients: Vec<f64>,
36}
37
38impl CuestShellSpec {
39 pub fn new(
44 l: u64,
45 pure: bool,
46 exponents: Vec<f64>,
47 coefficients: Vec<f64>,
48 ) -> Result<Self, CuestError> {
49 check_primitive_lists(exponents.len(), coefficients.len())?;
50 Ok(Self { l, pure, exponents, coefficients })
51 }
52
53 pub fn normalized(&self, normalization: f64) -> Result<Self, CuestError> {
62 let coefficients =
63 normalize_coefficients(self.l, &self.exponents, &self.coefficients, normalization)?;
64 Ok(Self { coefficients, ..self.clone() })
65 }
66
67 pub fn num_primitive(&self) -> u64 {
69 self.exponents.len() as u64
70 }
71}
72
73pub fn normalize_coefficients(
95 l: u64,
96 exponents: &[f64],
97 coefficients: &[f64],
98 normalization: f64,
99) -> Result<Vec<f64>, CuestError> {
100 check_primitive_lists(exponents.len(), coefficients.len())?;
101 if l >= 9 {
102 return Err(CuestError::Rust(format!("cuEST cannot normalize L = {l} (L < 9 required)")));
103 }
104 if normalization <= 0.0 || normalization.is_nan() {
105 return Err(CuestError::Rust(format!("normalization {normalization} is not positive")));
106 }
107 for (i, &e) in exponents.iter().enumerate() {
108 if e <= 0.0 || e.is_nan() {
109 return Err(CuestError::Rust(format!("exponent {i} = {e} is not positive")));
110 }
111 }
112
113 let d: f64 = (1..=l).map(|k| (2 * k - 1) as f64).product();
115 let two_l = 2f64.powi(l as i32);
116 let pi32 = std::f64::consts::PI.powf(1.5);
117 let scale = (two_l / (pi32 * d)).sqrt();
118 let l32 = l as f64 + 1.5;
119
120 let mut normalized: Vec<f64> = (0..exponents.len())
121 .map(|k| coefficients[k] * scale * (2.0 * exponents[k]).powf(l32).sqrt())
122 .collect();
123
124 let mut v = 0.0;
126 for (&ei, &ci) in exponents.iter().zip(coefficients) {
127 for (&ej, &cj) in exponents.iter().zip(coefficients) {
128 v += ci * cj * (2.0 * (ei * ej).sqrt() / (ei + ej)).powf(l32);
129 }
130 }
131 if v <= 0.0 || v.is_nan() {
132 return Err(CuestError::Rust(
133 "shell contraction has non-positive self-overlap".to_string(),
134 ));
135 }
136
137 let q = (normalization / v).sqrt();
138 for c in &mut normalized {
139 *c *= q;
140 }
141 Ok(normalized)
142}
143
144fn check_primitive_lists(exponents_len: usize, coefficients_len: usize) -> Result<(), CuestError> {
149 if exponents_len == 0 || exponents_len != coefficients_len {
150 return Err(CuestError::Rust(format!(
151 "shell has {exponents_len} exponents but {coefficients_len} coefficients"
152 )));
153 }
154 Ok(())
155}
156
157struct ShellInner {
159 context: Arc<ContextInner>,
160 handle: ffi::cuestAOShell_t,
161 spec: CuestShellSpec,
162 num_ao: u64,
163 num_pure: u64,
164 num_cart: u64,
165}
166
167impl Drop for ShellInner {
168 fn drop(&mut self) {
169 let _ = unsafe { ffi::cuestAOShellDestroy(self.handle) };
171 }
172}
173
174pub struct CuestAOShell {
178 inner: Arc<ShellInner>,
179}
180
181impl CuestAOShell {
182 pub fn new(context: &CuestContext, spec: &CuestShellSpec) -> Result<Self, CuestError> {
188 check_primitive_lists(spec.exponents.len(), spec.coefficients.len())?;
189 let context_inner = context.inner().clone();
190 let params = CuestParameters::create(ffi::CUEST_AOSHELL_PARAMETERS)?;
191 let mut handle: ffi::cuestAOShell_t = ptr::null_mut();
192 status_check(
193 unsafe {
194 ffi::cuestAOShellCreate(
195 context_inner.handle(),
196 spec.pure as i32,
197 spec.l,
198 spec.num_primitive(),
199 spec.exponents.as_ptr(),
200 spec.coefficients.as_ptr(),
201 params.raw(),
202 &mut handle,
203 )
204 },
205 "cuestAOShellCreate",
206 )?;
207
208 match query_new_attributes(&context_inner, handle) {
209 Ok((num_ao, num_pure, num_cart)) => Ok(Self {
210 inner: Arc::new(ShellInner {
211 context: context_inner,
212 handle,
213 spec: spec.clone(),
214 num_ao,
215 num_pure,
216 num_cart,
217 }),
218 }),
219 Err(err) => {
220 let _ = unsafe { ffi::cuestAOShellDestroy(handle) };
221 Err(err)
222 },
223 }
224 }
225
226 pub fn spec(&self) -> &CuestShellSpec {
228 &self.inner.spec
229 }
230
231 pub fn l(&self) -> u64 {
233 self.inner.spec.l
234 }
235
236 pub fn is_pure(&self) -> bool {
238 self.inner.spec.pure
239 }
240
241 pub fn num_primitive(&self) -> u64 {
243 self.inner.spec.num_primitive()
244 }
245
246 pub fn num_ao(&self) -> u64 {
248 self.inner.num_ao
249 }
250
251 pub fn num_pure(&self) -> u64 {
253 self.inner.num_pure
254 }
255
256 pub fn num_cart(&self) -> u64 {
258 self.inner.num_cart
259 }
260
261 pub fn query<A: AoshellAttribute>(&self) -> Result<A::Value, CuestError> {
263 query::query_attribute(
264 self.inner.context.handle(),
265 ffi::CUEST_AOSHELL,
266 self.inner.handle as *const c_void,
267 A::ID,
268 )
269 }
270
271 pub fn raw_handle(&self) -> ffi::cuestAOShell_t {
273 self.inner.handle
274 }
275
276 pub(crate) fn context(&self) -> &Arc<ContextInner> {
277 &self.inner.context
278 }
279}
280
281impl Clone for CuestAOShell {
282 fn clone(&self) -> Self {
283 Self { inner: self.inner.clone() }
284 }
285}
286
287impl std::fmt::Debug for CuestAOShell {
288 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
289 f.debug_struct("CuestAOShell")
290 .field("l", &self.l())
291 .field("pure", &self.is_pure())
292 .field("num_primitive", &self.num_primitive())
293 .field("num_ao", &self.num_ao())
294 .finish_non_exhaustive()
295 }
296}
297
298unsafe impl Send for CuestAOShell {}
300
301fn query_new_attributes(
302 context: &ContextInner,
303 handle: ffi::cuestAOShell_t,
304) -> Result<(u64, u64, u64), CuestError> {
305 let query = |id: std::ffi::c_int| {
306 query::query_attribute::<u64>(
307 context.handle(),
308 ffi::CUEST_AOSHELL,
309 handle as *const c_void,
310 id,
311 )
312 };
313 Ok((
314 query(ffi::CUEST_AOSHELL_NUM_AO.0 as std::ffi::c_int)?,
315 query(ffi::CUEST_AOSHELL_NUM_PURE.0 as std::ffi::c_int)?,
316 query(ffi::CUEST_AOSHELL_NUM_CART.0 as std::ffi::c_int)?,
317 ))
318}
319
320#[cfg(test)]
321mod tests {
322 use super::*;
323
324 fn primitive_norm(l: u64, e: f64) -> f64 {
327 let d: f64 = (1..=l).map(|k| (2 * k - 1) as f64).product();
328 (2f64.powi(l as i32) * (2.0 * e).powf(l as f64 + 1.5)
329 / (std::f64::consts::PI.powf(1.5) * d))
330 .sqrt()
331 }
332
333 fn primitive_overlap(l: u64, ei: f64, ej: f64) -> f64 {
335 (2.0 * (ei * ej).sqrt() / (ei + ej)).powf(l as f64 + 1.5)
336 }
337
338 fn self_overlap_normalized_primitives(l: u64, exponents: &[f64], coefficients: &[f64]) -> f64 {
341 let mut v = 0.0;
342 for (&ei, &ci) in exponents.iter().zip(coefficients) {
343 for (&ej, &cj) in exponents.iter().zip(coefficients) {
344 v += ci * cj * primitive_overlap(l, ei, ej);
345 }
346 }
347 v
348 }
349
350 fn self_overlap_raw_primitives(l: u64, exponents: &[f64], coefficients: &[f64]) -> f64 {
355 let mut v = 0.0;
356 for (&ei, &ci) in exponents.iter().zip(coefficients) {
357 for (&ej, &cj) in exponents.iter().zip(coefficients) {
358 v += ci * cj * primitive_overlap(l, ei, ej)
359 / (primitive_norm(l, ei) * primitive_norm(l, ej));
360 }
361 }
362 v
363 }
364
365 #[test]
366 fn cpu_normalize_coefficients() {
367 let exponents = [3.42525091, 0.62391373, 0.16885540];
371 let published = [0.15432897, 0.53532814, 0.44463454];
372 let nearly_one = self_overlap_normalized_primitives(0, &exponents, &published);
373 assert!(
374 (nearly_one - 1.0).abs() < 1e-4,
375 "published coefficients are ~normalized: {nearly_one}"
376 );
377
378 let normalized = normalize_coefficients(0, &exponents, &published, 1.0).unwrap();
381 assert!((self_overlap_raw_primitives(0, &exponents, &normalized) - 1.0).abs() < 1e-12);
382
383 let normalized = normalize_coefficients(1, &exponents, &published, 2.5).unwrap();
385 assert!((self_overlap_raw_primitives(1, &exponents, &normalized) - 2.5).abs() < 1e-12);
386
387 let scaled: Vec<f64> = published.iter().map(|c| c * 3.0).collect();
389 let normalized_scaled = normalize_coefficients(1, &exponents, &scaled, 1.0).unwrap();
390 assert!(
391 (self_overlap_raw_primitives(1, &exponents, &normalized_scaled) - 1.0).abs() < 1e-12
392 );
393 }
394
395 #[test]
396 fn cpu_normalize_coefficients_errors() {
397 let exps = [1.0];
398 assert!(normalize_coefficients(0, &[], &[], 1.0).is_err());
399 assert!(normalize_coefficients(0, &exps, &[1.0, 2.0], 1.0).is_err());
400 assert!(normalize_coefficients(9, &exps, &[1.0], 1.0).is_err());
401 assert!(normalize_coefficients(0, &exps, &[1.0], 0.0).is_err());
402 assert!(normalize_coefficients(0, &[-1.0], &[1.0], 1.0).is_err());
403 }
404
405 #[test]
406 fn cpu_check_primitive_lists() {
407 assert!(check_primitive_lists(3, 3).is_ok());
408 assert!(check_primitive_lists(0, 0).is_err());
409 assert!(check_primitive_lists(2, 3).is_err());
410 }
411
412 #[test]
413 fn cpu_shell_spec() {
414 let spec = CuestShellSpec::new(0, true, vec![1.0, 2.0], vec![0.5, 0.5]).unwrap();
415 assert_eq!(spec.num_primitive(), 2);
416 let normalized = spec.normalized(1.0).unwrap();
417 assert_eq!(normalized.l, 0);
418 assert_eq!(normalized.coefficients.len(), 2);
419 assert_ne!(normalized.coefficients, spec.coefficients);
420 assert!(CuestShellSpec::new(0, true, vec![], vec![]).is_err());
421 assert!(CuestShellSpec::new(0, true, vec![1.0], vec![1.0, 2.0]).is_err());
422 }
423}