libcint/
cint_prop.rs

1//! Properties of the [`CInt`] instance (compute and memory cost is not
2//! essential in this module).
3
4use crate::prelude::*;
5
6/// Properties of the `CInt` instance (compute and memory cost is not essential
7/// in this module).
8impl CInt {
9    /// Whether pseudo potential is used in the system.
10    ///
11    /// # PySCF Equivalent
12    ///
13    /// Method `Mole.has_ecp`
14    ///
15    /// # Examples
16    ///
17    /// ```rust
18    /// use libcint::prelude::*;
19    /// let cint_data = init_h2o_def2_tzvp();
20    /// assert!(!cint_data.has_ecp());
21    ///
22    /// let cint_data = init_sb2me4_cc_pvtz();
23    /// assert!(cint_data.has_ecp());
24    /// ```
25    #[inline]
26    pub fn has_ecp(&self) -> bool {
27        !self.ecpbas.is_empty()
28    }
29
30    /// Whether spin-orbit coupling is enabled in ECP.
31    ///
32    /// # PySCF Equivalent
33    ///
34    /// Method `Mole.has_ecp_soc`
35    ///
36    /// # Examples
37    /// ```rust
38    /// use libcint::prelude::*;
39    /// let cint_data = init_sb2me4_cc_pvtz();
40    /// assert!(!cint_data.has_ecp_soc());
41    /// ```
42    #[inline]
43    pub fn has_ecp_soc(&self) -> bool {
44        const SO_TYPE_OF: usize = cecp_ffi::SO_TYPE_OF as usize;
45        self.ecpbas.iter().any(|ecp| ecp[SO_TYPE_OF] == 1)
46    }
47
48    /// Number of shells in the system.
49    ///
50    /// This does not count ECP shells.
51    ///
52    /// Please note that, `self.bas.len()` may not be the actual number of
53    /// shells, because in some cases, ECP shells are merged into the GTO
54    /// shells.
55    ///
56    /// # PySCF Equivalent
57    ///
58    /// Attribute `Mole.nbas`
59    ///
60    /// # Examples
61    ///
62    /// ```rust
63    /// use libcint::prelude::*;
64    ///
65    /// let cint_data = init_h2o_def2_tzvp();
66    /// assert_eq!(cint_data.nbas(), 19);
67    ///
68    /// let cint_data = init_sb2me4_cc_pvtz();
69    /// assert_eq!(cint_data.nbas(), 130);
70    ///
71    /// # // This is test that when ecpbas is merged, the number of shells is still correct.
72    /// # let merged = cint_data.merge_ecpbas();
73    /// # assert_eq!(merged.nbas(), 130);
74    /// ```
75    #[inline]
76    pub fn nbas(&self) -> usize {
77        if self.is_ecp_merged() {
78            self.bas.len() - self.ecpbas.len()
79        } else {
80            self.bas.len()
81        }
82    }
83
84    /// Number of atoms in the system.
85    #[inline]
86    pub fn natm(&self) -> usize {
87        self.atm.len()
88    }
89
90    /// Pointer to the shell data.
91    #[inline]
92    pub fn bas_ptr(&self) -> *const c_int {
93        self.bas.as_ptr() as *const c_int
94    }
95
96    /// Pointer to the atom data.
97    #[inline]
98    pub fn atm_ptr(&self) -> *const c_int {
99        self.atm.as_ptr() as *const c_int
100    }
101
102    /// Pointer to the environment data.
103    #[inline]
104    pub fn env_ptr(&self) -> *const f64 {
105        self.env.as_ptr()
106    }
107
108    /// Nuclear effective charge of the given atom id.
109    ///
110    /// # Note
111    ///
112    /// `atom_charge != charge(atom_symbol)` when ECP is enabled.
113    ///
114    /// Number of electrons screened by ECP can be obtained by
115    /// `charge(atom_symbol) - atom_charge`.
116    ///
117    /// # PySCF Equivalent
118    ///
119    /// Method `Mole.atom_charge`
120    ///
121    /// # Examples
122    /// ```rust
123    /// use libcint::prelude::*;
124    /// let cint_data = init_h2o_def2_tzvp();
125    /// assert_eq!(cint_data.atom_charge(0), 8.0);  // O  (8)
126    /// assert_eq!(cint_data.atom_charge(1), 1.0);  // H  (1)
127    ///
128    /// let cint_data = init_sb2me4_cc_pvtz();
129    /// assert_eq!(cint_data.atom_charge(0), 23.0); // Sb (51) with ECP (-36)
130    /// assert_eq!(cint_data.atom_charge(2), 6.0);  // C  (6)
131    /// ```
132    #[inline]
133    pub fn atom_charge(&self, atm_id: usize) -> f64 {
134        const NUC_MOD_OF: usize = cint_ffi::NUC_MOD_OF as usize;
135        const CHARGE_OF: usize = cint_ffi::CHARGE_OF as usize;
136        const FRAC_CHARGE_NUC: u32 = cint_ffi::FRAC_CHARGE_NUC;
137        const PTR_FRAC_CHARGE: usize = cint_ffi::PTR_FRAC_CHARGE as usize;
138
139        if self.atm[atm_id][NUC_MOD_OF] as u32 != FRAC_CHARGE_NUC {
140            // regular QM atoms
141            self.atm[atm_id][CHARGE_OF] as f64
142        } else {
143            // MM atoms with fractional charges
144            self.env[self.atm[atm_id][PTR_FRAC_CHARGE] as usize]
145        }
146    }
147
148    /// List of Nuclear effective charge of all atoms in system.
149    ///
150    /// # Note
151    ///
152    /// `atom_charge != charge(atom_symbol)` when ECP is enabled.
153    ///
154    /// Number of electrons screened by ECP can be obtained by
155    /// `charge(atom_symbol) - atom_charge`.
156    ///
157    /// # PySCF Equivalent
158    ///
159    /// Method `Mole.atom_charges`
160    ///
161    /// # Examples
162    /// ```rust
163    /// use libcint::prelude::*;
164    /// let cint_data = init_h2o_def2_tzvp();
165    /// assert_eq!(cint_data.atom_charges(), vec![8., 1., 1.]);
166    ///
167    /// // For Sb2Me4, the first atom is Sb (51) with ECP (-36), so the charge is 23.
168    /// let cint_data = init_sb2me4_cc_pvtz();
169    /// assert_eq!(cint_data.atom_charges(), vec![23., 23., 6., 6., 6., 6., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.]);
170    /// ```
171    #[inline]
172    pub fn atom_charges(&self) -> Vec<f64> {
173        (0..self.atm.len()).map(|i| self.atom_charge(i)).collect()
174    }
175
176    /// The number of spinor associated with given angular momentum $l$ and
177    /// kappa $\kappa$.
178    ///
179    /// - If $\kappa = 0$, it returns $4 l + 2$.
180    /// - If $\kappa > 0$, it returns $2 l + 2$.
181    /// - If $\kappa < 0$, it returns $2 l$.
182    ///
183    /// # PySCF Equivalent
184    ///
185    /// Function `gto.mole.len_spinor`
186    #[inline]
187    pub fn len_spinor(l: i32, kappa: i32) -> usize {
188        if kappa == 0 {
189            (l * 4 + 2) as usize
190        } else if kappa > 0 {
191            (l * 2 + 2) as usize
192        } else {
193            (l * 2) as usize
194        }
195    }
196
197    /// The number of Cartesian function associated with given angular momentum
198    /// $l$.
199    ///
200    /// This will gives $\frac{(l + 1) (l + 2)}{2}$
201    ///
202    /// # PySCF Equivalent
203    ///
204    /// Function `gto.mole.len_cart`
205    #[inline]
206    pub fn len_cart(l: i32) -> usize {
207        ((l + 1) * (l + 2) / 2) as usize
208    }
209
210    /// The number of spherical function associated with given angular momentum
211    /// $l$.
212    ///
213    /// This will gives $2 l + 1$.
214    ///
215    /// # PySCF Equivalent
216    ///
217    /// Function `gto.mole.len_sph`
218    #[inline]
219    pub fn len_sph(l: i32) -> usize {
220        (l * 2 + 1) as usize
221    }
222
223    /// Location mapping from shell to basis.
224    ///
225    /// The type of integral is specified by struct field `cint_type`.
226    ///
227    /// Output vector is of length `nshl + 1`, where `nshl` is the number of
228    /// shells.
229    ///
230    /// # PySCF Equivalent
231    ///
232    /// This implementation follows `gto.moleintor.make_loc`.
233    ///
234    /// For `gto.Mole` object, methods `ao_loc`, `ao_loc_nr`, `ao_loc_2c` are
235    /// also relevant.
236    ///
237    /// # Examples
238    ///
239    /// ```rust
240    /// use libcint::prelude::*;
241    /// let cint_data = init_h2o_def2_tzvp();
242    ///
243    /// let loc_sph = cint_data.make_loc();
244    /// assert_eq!(loc_sph, vec![ 0,  1,  2,  3,  4,  5,  8, 11, 14, 19, 24, 31, 32, 33, 34, 37, 38, 39, 40, 43]);
245    /// ```
246    #[inline]
247    pub fn make_loc(&self) -> Vec<usize> {
248        self.make_loc_with_type(self.cint_type)
249    }
250
251    /// Location mapping from shell to basis.
252    ///
253    /// # See also
254    ///
255    /// [`CInt::make_loc`]
256    #[inline]
257    pub fn ao_loc(&self) -> Vec<usize> {
258        self.make_loc()
259    }
260
261    /// Location mapping from shell to basis (with integral type specified).
262    ///
263    /// This mapping is cumulated, and can be different for sph, cart and spinor
264    /// types.
265    ///
266    /// Output vector is of length `nshl + 1`, where `nshl` is the number of
267    /// shells.
268    ///
269    /// # PySCF Equivalent
270    ///
271    /// This implementation follows `gto.moleintor.make_loc`.
272    ///
273    /// For `gto.Mole` object, methods `ao_loc`, `ao_loc_nr`, `ao_loc_2c` are
274    /// also relevant.
275    ///
276    /// # Examples
277    ///
278    /// ```rust
279    /// use libcint::prelude::*;
280    /// let cint_data = init_h2o_def2_tzvp();
281    ///
282    /// let loc_sph = cint_data.make_loc_with_type(CIntType::Spheric);
283    /// assert_eq!(loc_sph, vec![ 0,  1,  2,  3,  4,  5,  8, 11, 14, 19, 24, 31, 32, 33, 34, 37, 38, 39, 40, 43]);
284    ///
285    /// let loc_cart = cint_data.make_loc_with_type(CIntType::Cartesian);
286    /// assert_eq!(loc_cart, vec![ 0,  1,  2,  3,  4,  5,  8, 11, 14, 20, 26, 36, 37, 38, 39, 42, 43, 44, 45, 48]);
287    ///
288    /// let loc_spinor = cint_data.make_loc_with_type(CIntType::Spinor);
289    /// assert_eq!(loc_spinor, vec![ 0,  2,  4,  6,  8, 10, 16, 22, 28, 38, 48, 62, 64, 66, 68, 74, 76, 78, 80, 86]);
290    /// ```
291    pub fn make_loc_with_type(&self, cint_type: CIntType) -> Vec<usize> {
292        const ANG_OF: usize = cint_ffi::ANG_OF as usize;
293        const KAPPA_OF: usize = cint_ffi::KAPPA_OF as usize;
294        const NCTR_OF: usize = cint_ffi::NCTR_OF as usize;
295
296        let mut ao_loc = vec![0];
297        let nbas = self.nbas();
298        for shl in 0..nbas {
299            let l = self.bas[shl][ANG_OF];
300            let k = self.bas[shl][KAPPA_OF];
301            let nctr = self.bas[shl][NCTR_OF] as usize;
302            let val = match cint_type {
303                Spheric => Self::len_sph(l) * nctr,
304                Cartesian => Self::len_cart(l) * nctr,
305                Spinor => Self::len_spinor(l, k) * nctr,
306            };
307            ao_loc.push(ao_loc[shl] + val);
308        }
309        ao_loc
310    }
311
312    /// Get the number of basis (atomic orbitals).
313    ///
314    /// The type of integral is specified by struct field `cint_type`.
315    ///
316    /// This value is the same to the last of [`CInt::make_loc_with_type`].
317    ///
318    /// # PySCF Equivalent
319    ///
320    /// For `gto.Mole` object, methods `nao_nr`, `nao_cart`, `nao_2c` are
321    /// relevant.
322    ///
323    ///
324    /// # Examples
325    ///
326    /// ```rust
327    /// use libcint::prelude::*;
328    /// let cint_data = init_h2o_def2_tzvp();
329    ///
330    /// let nao_sph = cint_data.nao();
331    /// assert_eq!(nao_sph, 43);
332    ///
333    /// let cint_data = init_sb2me4_cc_pvtz();
334    /// let nao_sph = cint_data.nao();
335    /// assert_eq!(nao_sph, 366);
336    ///
337    /// # // This is test that when ecpbas is merged, the number of
338    /// # // atomic orbitals is still correct.
339    /// # let merged = cint_data.merge_ecpbas();
340    /// # let nao_cart = merged.nao();
341    /// # assert_eq!(nao_cart, 366);
342    /// ```
343    #[inline]
344    pub fn nao(&self) -> usize {
345        self.nao_with_type(self.cint_type)
346    }
347
348    /// Get the number of basis (atomic orbitals, with integral type specified).
349    ///
350    /// This value is different for sph, cart and spinor types.
351    ///
352    /// This value is the same to the last of [`CInt::make_loc_with_type`].
353    ///
354    /// # PySCF Equivalent
355    ///
356    /// For `gto.Mole` object, methods `nao_nr`, `nao_cart`, `nao_2c` are
357    /// relevant.
358    ///
359    ///
360    /// # Examples
361    ///
362    /// ```rust
363    /// use libcint::prelude::*;
364    /// let cint_data = init_h2o_def2_tzvp();
365    ///
366    /// let nao_sph = cint_data.nao_with_type(CIntType::Spheric);
367    /// assert_eq!(nao_sph, 43);
368    ///
369    /// let nao_cart = cint_data.nao_with_type(CIntType::Cartesian);
370    /// assert_eq!(nao_cart, 48);
371    ///
372    /// let nao_spinor = cint_data.nao_with_type(CIntType::Spinor);
373    /// assert_eq!(nao_spinor, 86);
374    ///
375    /// let cint_data = init_sb2me4_cc_pvtz();
376    /// let nao_sph = cint_data.nao_with_type(CIntType::Spheric);
377    /// assert_eq!(nao_sph, 366);
378    ///
379    /// # // This is test that when ecpbas is merged, the number of
380    /// # // atomic orbitals is still correct.
381    /// # let merged = cint_data.merge_ecpbas();
382    /// # let nao_cart = merged.nao_with_type(CIntType::Spheric);
383    /// # assert_eq!(nao_cart, 366);
384    /// ```
385    #[inline]
386    pub fn nao_with_type(&self, cint_type: CIntType) -> usize {
387        const ANG_OF: usize = cint_ffi::ANG_OF as usize;
388        const KAPPA_OF: usize = cint_ffi::KAPPA_OF as usize;
389        const NCTR_OF: usize = cint_ffi::NCTR_OF as usize;
390
391        let mut nao = 0;
392        let nbas = self.nbas();
393        for shl in 0..nbas {
394            let l = self.bas[shl][ANG_OF];
395            let k = self.bas[shl][KAPPA_OF];
396            let nctr = self.bas[shl][NCTR_OF];
397            let val = match cint_type {
398                Spheric => Self::len_sph(l) as c_int * nctr,
399                Cartesian => Self::len_cart(l) as c_int * nctr,
400                Spinor => Self::len_spinor(l, k) as c_int * nctr,
401            };
402            nao += val as usize;
403        }
404        nao
405    }
406
407    /// Coordinates of the given atom id in unit Bohr (a.u.).
408    ///
409    /// # PySCF Equivalent
410    ///
411    /// Method `Mole.atom_coord` with `unit="Bohr"`.
412    #[inline]
413    pub fn atom_coord(&self, atm_id: usize) -> [f64; 3] {
414        const PTR_COORD: usize = cint_ffi::PTR_COORD as usize;
415
416        let ptr = self.atm[atm_id][PTR_COORD] as usize;
417        [self.env[ptr], self.env[ptr + 1], self.env[ptr + 2]]
418    }
419
420    /// Coordinates of all atoms in unit Bohr (a.u.).
421    ///
422    /// # PySCF Equivalent
423    ///
424    /// Method `Mole.atom_coords` with `unit="Bohr"`.
425    ///
426    /// # Examples
427    ///
428    /// ```rust
429    /// use libcint::prelude::*;
430    /// # use approx::assert_relative_eq;
431    /// let cint_data = init_h2o_def2_tzvp();
432    /// let coords = cint_data.atom_coords();
433    /// // Output:
434    /// // [[  0.000000,   0.000000,   0.000000],
435    /// //  [  1.776343,   0.000000,   0.000000],
436    /// //  [ -0.444761,   0.000000,   1.719762]]
437    /// assert_relative_eq!(cint_fingerprint(&coords), -2.4358371781626658, max_relative=1e-12);
438    /// ```
439    #[inline]
440    pub fn atom_coords(&self) -> Vec<[f64; 3]> {
441        (0..self.atm.len()).map(|i| self.atom_coord(i)).collect_vec()
442    }
443
444    /// Number of shells of the given atom
445    ///
446    /// # PySCF Equivalent
447    ///
448    /// Method `Mole.atom_nshells`
449    ///
450    /// # Examples
451    ///
452    /// ```rust
453    /// use libcint::prelude::*;
454    /// let cint_data = init_h2o_def2_tzvp();
455    /// assert_eq!(cint_data.atom_nshells(0), 11);
456    /// ```
457    #[inline]
458    pub fn atom_nshells(&self, atm_id: usize) -> usize {
459        const ATOM_OF: usize = cint_ffi::ATOM_OF as usize;
460
461        let nbas = self.nbas();
462        self.bas[..nbas].iter().filter(|bas| bas[ATOM_OF] as usize == atm_id).count()
463    }
464
465    /// List of shell ids of the given atom.
466    ///
467    /// # PySCF Equivalent
468    ///
469    /// Method `Mole.atom_shell_ids`
470    ///
471    /// # Examples
472    ///
473    /// ```rust
474    /// use libcint::prelude::*;
475    /// let cint_data = init_h2o_def2_tzvp();
476    /// assert_eq!(cint_data.atom_shell_ids(0), vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
477    /// ```
478    #[inline]
479    pub fn atom_shell_ids(&self, atm_id: usize) -> Vec<usize> {
480        const ATOM_OF: usize = cint_ffi::ATOM_OF as usize;
481
482        let nbas = self.nbas();
483        self.bas[..nbas].iter().enumerate().filter_map(|(i, bas)| if bas[ATOM_OF] as usize == atm_id { Some(i) } else { None }).collect()
484    }
485
486    #[inline]
487    fn check_bas_id(&self, bas_id: usize) {
488        let nbas = self.nbas();
489        if bas_id >= nbas {
490            panic!("Invalid bas_id: {bas_id}. Number of shells is {nbas}");
491        }
492    }
493
494    /// Coordinates of the given shell in unit Bohr (a.u.).
495    ///
496    /// # PySCF Equivalent
497    ///
498    /// Method `Mole.bas_coord`
499    #[inline]
500    pub fn bas_coord(&self, bas_id: usize) -> [f64; 3] {
501        const PTR_COORD: usize = cint_ffi::PTR_COORD as usize;
502
503        self.check_bas_id(bas_id);
504        let atm_id = self.bas_atom(bas_id);
505        let ptr = self.atm[atm_id][PTR_COORD] as usize;
506        [self.env[ptr], self.env[ptr + 1], self.env[ptr + 2]]
507    }
508
509    /// The atom (0-based id) that the given shell sits on.
510    ///
511    /// # PySCF Equivalent
512    ///
513    /// Method `Mole.bas_atom`
514    #[inline]
515    pub fn bas_atom(&self, bas_id: usize) -> usize {
516        const ATOM_OF: usize = cint_ffi::ATOM_OF as usize;
517
518        self.check_bas_id(bas_id);
519        self.bas[bas_id][ATOM_OF] as usize
520    }
521
522    /// The angular momentum associated with the given shell.
523    ///
524    /// # PySCF Equivalent
525    ///
526    /// Method `Mole.bas_angular`
527    #[inline]
528    pub fn bas_angular(&self, bas_id: usize) -> usize {
529        const ANG_OF: usize = cint_ffi::ANG_OF as usize;
530
531        self.check_bas_id(bas_id);
532        self.bas[bas_id][ANG_OF] as usize
533    }
534
535    /// The number of contracted GTOs for the given shell.
536    ///
537    /// # PySCF Equivalent
538    ///
539    /// Method `Mole.bas_nctr`
540    #[inline]
541    pub fn bas_nctr(&self, bas_id: usize) -> usize {
542        const NCTR_OF: usize = cint_ffi::NCTR_OF as usize;
543
544        self.check_bas_id(bas_id);
545        self.bas[bas_id][NCTR_OF] as usize
546    }
547
548    /// The number of primitive GTOs for the given shell.
549    ///
550    /// # PySCF Equivalent
551    ///
552    /// Method `Mole.bas_nprim`
553    #[inline]
554    pub fn bas_nprim(&self, bas_id: usize) -> usize {
555        const NPRIM_OF: usize = cint_ffi::NPRIM_OF as usize;
556
557        self.check_bas_id(bas_id);
558        self.bas[bas_id][NPRIM_OF] as usize
559    }
560
561    /// Kappa (if l < j, -l-1, else l) of the given shell.
562    ///
563    /// # PySCF Equivalent
564    ///
565    /// Method `Mole.bas_kappa`
566    #[inline]
567    pub fn bas_kappa(&self, bas_id: usize) -> c_int {
568        const KAPPA_OF: usize = cint_ffi::KAPPA_OF as usize;
569
570        self.check_bas_id(bas_id);
571        self.bas[bas_id][KAPPA_OF]
572    }
573
574    /// Exponents of the given shell.
575    ///
576    /// # PySCF Equivalent
577    ///
578    /// Method `Mole.bas_exp`
579    #[inline]
580    pub fn bas_exp(&self, bas_id: usize) -> &[f64] {
581        const PTR_EXP: usize = cint_ffi::PTR_EXP as usize;
582
583        self.check_bas_id(bas_id);
584        let ptr = self.bas[bas_id][PTR_EXP] as usize;
585        &self.env[ptr..ptr + self.bas_nprim(bas_id)]
586    }
587
588    /// Exponents of all shells.
589    ///
590    /// # PySCF Equivalent
591    ///
592    /// Method `Mole.bas_exps`
593    #[inline]
594    pub fn bas_exps(&self) -> Vec<Vec<f64>> {
595        (0..self.nbas()).map(|i| self.bas_exp(i).to_vec()).collect()
596    }
597
598    /// Shell and basis (atomic orbitals) offsets for each atom.
599    ///
600    /// This will give a list of `[shl_start, shl_end, ao_start, ao_end]` for
601    /// each atom.
602    ///
603    /// # PySCF Equivalent
604    ///
605    /// Method `Mole.aoslice_by_atom`. This implementation does not allow
606    /// arbitrary `ao_loc` (which can be obtained by [`CInt::make_loc`]).
607    /// However, user can provide `cint_type` to specify if you wish to use
608    /// sph, cart or spinor.
609    ///
610    /// # Examples
611    ///
612    /// ```rust
613    /// use libcint::prelude::*;
614    /// let cint_data = init_h2o_def2_tzvp();
615    /// let aoslice = cint_data.aoslice_by_atom();
616    /// assert_eq!(aoslice, vec![
617    ///     [ 0, 11,  0, 31],
618    ///     [11, 15, 31, 37],
619    ///     [15, 19, 37, 43],
620    /// ]);
621    /// ```
622    #[inline]
623    pub fn aoslice_by_atom(&self) -> Vec<[usize; 4]> {
624        self.aoslice_by_atom_with_type(self.cint_type)
625    }
626
627    /// Shell and basis (atomic orbitals) offsets for each atom (with integral
628    /// type specified).
629    ///
630    /// This will give a list of `[shl_start, shl_end, ao_start, ao_end]` for
631    /// each atom.
632    ///
633    /// # PySCF Equivalent
634    ///
635    /// Method `Mole.aoslice_by_atom`. This implementation does not allow
636    /// arbitrary `ao_loc` (which can be obtained by [`CInt::make_loc`]).
637    /// However, user can provide `cint_type` to specify if you wish to use
638    /// sph, cart or spinor.
639    ///
640    /// # Examples
641    ///
642    /// ```rust
643    /// use libcint::prelude::*;
644    /// let cint_data = init_h2o_def2_tzvp();
645    /// let aoslice = cint_data.aoslice_by_atom_with_type(CIntType::Spheric);
646    /// assert_eq!(aoslice, vec![
647    ///     [ 0, 11,  0, 31],
648    ///     [11, 15, 31, 37],
649    ///     [15, 19, 37, 43],
650    /// ]);
651    /// ```
652    pub fn aoslice_by_atom_with_type(&self, cint_type: CIntType) -> Vec<[usize; 4]> {
653        const ATOM_OF: usize = cint_ffi::ATOM_OF as usize;
654
655        let nbas = self.nbas();
656        let ao_loc = self.make_loc_with_type(cint_type);
657
658        // category atoms with basis index
659        // [atm_id, shl_start, shl_end]
660        let mut category = vec![];
661        if nbas > 0 {
662            let mut current_atm = self.bas[0][ATOM_OF];
663            let mut shl_start = 0;
664            for shl in 0..self.nbas() {
665                let atm_id = self.bas[shl][ATOM_OF];
666                if atm_id != current_atm {
667                    category.push([current_atm as usize, shl_start, shl]);
668                    current_atm = atm_id;
669                    shl_start = shl;
670                }
671            }
672            category.push([current_atm as usize, shl_start, nbas]);
673        }
674
675        // fill result based on category
676        let mut result = vec![];
677        let mut ptr_category = 0;
678        for atm_id in 0..self.atm.len() {
679            if ptr_category < category.len() && atm_id == category[ptr_category][0] {
680                // atom exists in bas
681                let shl_start = category[ptr_category][1];
682                let shl_end = category[ptr_category][2];
683                let ao_start = ao_loc[shl_start];
684                let ao_end = ao_loc[shl_end];
685                result.push([shl_start, shl_end, ao_start, ao_end]);
686                ptr_category += 1;
687            } else {
688                // atom missing in bas
689                // following unwrap occurs for first atom
690                let &[_, shl_end, _, ao_end] = result.last().unwrap_or(&[0; 4]);
691                result.push([shl_end, shl_end, ao_end, ao_end]);
692            }
693        }
694
695        // panic if I am wrong
696        // check result length
697        assert_eq!(result.len(), self.atm.len());
698        // check total number of basis
699        assert_eq!(result.last().unwrap()[3], self.nao_with_type(cint_type));
700        result
701    }
702
703    /// Number of grids that is used in `int1e_grids`.
704    ///
705    /// Note that this function is mostly for internal usage. User should
706    /// generally not call this function.
707    pub fn ngrids(&self) -> usize {
708        const NGRIDS: usize = crate::ffi::cint_ffi::NGRIDS as usize;
709        self.env[NGRIDS] as usize
710    }
711
712    /// Balances the partition of shells into blocks of a given size.
713    ///
714    /// This function can be useful if the full bulk of integrals is not
715    /// available in memory, and you have to generate them batch by batch.
716    ///
717    /// The outputs are
718    /// - `shl_start`: the starting index of the shell in the partition
719    ///   (included)
720    /// - `shl_end`: the ending index of the shell in the partition (not
721    ///   included)
722    /// - `nbatch_ao`: the number of AOs in the batch.
723    ///
724    /// # PySCF equivalent
725    ///
726    /// `ao2mo.outcore.balance_partition`
727    ///
728    /// # Example
729    ///
730    /// ```
731    /// use libcint::prelude::*;
732    /// let cint_data = init_h2o_def2_tzvp();
733    /// let partition = cint_data.balance_partition(20);
734    /// assert_eq!(partition, vec![[0, 9, 19], [9, 17, 20], [17, 19, 4]]);
735    /// ```
736    pub fn balance_partition(&self, block_size: usize) -> Vec<[usize; 3]> {
737        crate::util::balance_partition(self, block_size, None, None)
738    }
739
740    /// Balances the partition of shells into blocks of a given size.
741    ///
742    /// This function is similar to [`CInt::balance_partition`], but allows
743    /// user to specify the start and end id of the shells.
744    pub fn balance_partition_advanced(&self, block_size: usize, start_id: Option<usize>, end_id: Option<usize>) -> Vec<[usize; 3]> {
745        crate::util::balance_partition(self, block_size, start_id, end_id)
746    }
747}
748
749#[cfg(test)]
750mod test {
751    use crate::prelude::*;
752
753    #[test]
754    fn playground() {
755        let cint_data = init_h2o_def2_tzvp();
756        println!("{:?}", cint_data.aoslice_by_atom());
757    }
758}