@@ -51,9 +51,12 @@ inline SigmaPShell sigma_p(const Shell& s) { return {s}; }
5151// / BraKet::xs_xx — 3-center (K | a b)
5252// / BraKet::xx_xs — 3-center (a b | K)
5353// /
54- // / Component ordering: index = bra_q * n_ket_comp + ket_q
55- // / For 2 (σ.p) on a particle: quaternion {S=0, X=1, Y=2, Z=3}
56- // / For 1 (σ.p): Cartesian {x=0, y=1, z=2}
54+ // / Component structure based on total (σ.p) count:
55+ // / 0 (σ.p) → 1 component (standard integral)
56+ // / 1 (σ.p) → 3 Cartesian components {x=0, y=1, z=2} (3-center DF only)
57+ // / 2 (σ.p) → 4 quaternion components {S=0, X=1, Y=2, Z=3}
58+ // / (same or different particles, e.g. (LL|SS) or (SL|SL))
59+ // / 4 (σ.p) → 16 components, index = 4*bra_q + ket_q (SS|SS)
5760class SigmaPEngine {
5861 public:
5962 using real_t = double ;
@@ -164,17 +167,20 @@ class SigmaPEngine {
164167 return cart_idx (am + delta, nx, ny);
165168 }
166169
167- // / Number of output components for a particle with n (σ.p) centers
168- static int particle_ncomp (int n_sp) {
169- switch (n_sp) {
170+ // / Number of output components based on TOTAL (σ.p) count.
171+ // / 0 → 1, 1 → 3 Cartesian, 2 → 4 quaternion, 4 → 16 (two pairs)
172+ static int total_ncomp (int total_sp) {
173+ switch (total_sp) {
170174 case 0 :
171175 return 1 ;
172176 case 1 :
173177 return 3 ;
174178 case 2 :
175179 return 4 ;
180+ case 4 :
181+ return 16 ;
176182 default :
177- assert (false );
183+ assert (false && " unsupported (σ.p) count " );
178184 return 0 ;
179185 }
180186 }
@@ -210,18 +216,16 @@ class SigmaPEngine {
210216 const int nc = ncart (lc), nd = ncart (ld);
211217 const int n_out = na * nb * nc * nd;
212218
213- const int bra_sp = (sp_a ? 1 : 0 ) + (sp_b ? 1 : 0 );
214- const int ket_sp = (sp_c ? 1 : 0 ) + (sp_d ? 1 : 0 );
215- const int n_bra = particle_ncomp (bra_sp);
216- const int n_ket = particle_ncomp (ket_sp);
217- const int n_comp = n_bra * n_ket;
219+ const int total_sp =
220+ (sp_a ? 1 : 0 ) + (sp_b ? 1 : 0 ) + (sp_c ? 1 : 0 ) + (sp_d ? 1 : 0 );
221+ const int n_comp = total_ncomp (total_sp);
218222
219223 buf_.assign (n_comp * n_out, 0.0 );
220224 targets_.resize (n_comp);
221225 for (int q = 0 ; q < n_comp; ++q) targets_[q] = buf_.data () + q * n_out;
222226
223227 // No (σ.p) → standard ERI
224- if (n_comp == 1 ) {
228+ if (total_sp == 0 ) {
225229 const auto & r = coulomb_engine_.compute (a, b, c, d);
226230 if (r[0 ]) std::memcpy (buf_.data (), r[0 ], n_out * sizeof (real_t ));
227231 return targets_;
@@ -300,73 +304,65 @@ class SigmaPEngine {
300304 }
301305
302306 // ---- Contraction ----
303- for (int bq = 0 ; bq < n_bra; ++bq) {
304- for (int kq = 0 ; kq < n_ket; ++kq) {
305- int comp = bq * n_ket + kq;
306- real_t * out = buf_.data () + comp * n_out;
307-
308- // T4 sign (only for 2+2 (σ.p) case)
309- int t4 = (bra_sp == 2 && ket_sp == 2 && bq > 0 && kq > 0 ) ? -1 : 1 ;
310-
311- // Get direction pairs for bra and ket
312- DirPair bp[3 ], kp[3 ];
313- int nbp, nkp;
314- get_particle_pairs (sp_a, sp_b, bra_sp, bq, bp, nbp);
315- get_particle_pairs (sp_c, sp_d, ket_sp, kq, kp, nkp);
316-
317- for (int ib = 0 ; ib < nbp; ++ib) {
318- // Directions: r[i] for center i. -1 means "not (σ.p), no direction"
319- int r[4 ];
320- r[0 ] = sp_a ? bp[ib].r1 : -1 ;
321- r[1 ] = sp_b ? bp[ib].r2 : -1 ;
322- const int bsign = bp[ib].sign ;
323-
324- for (int ik = 0 ; ik < nkp; ++ik) {
325- r[2 ] = sp_c ? kp[ik].r1 : -1 ;
326- r[3 ] = sp_d ? kp[ik].r2 : -1 ;
327- const int overall_sign = bsign * kp[ik].sign * t4;
328-
329- // Loop over AM-shift combinations for each (σ.p) center
330- // delta[i]: +1 (up) or -1 (down). 0 if no (σ.p).
331- // di: 0=up, 1=down in the eri index
332- for (int d0 = 0 ; d0 <= (sp[0 ] ? 1 : 0 ); ++d0) {
333- if (d0 == 1 && !dn[0 ]) continue ;
334- const int delta0 = sp[0 ] ? (d0 == 0 ? +1 : -1 ) : 0 ;
335-
336- for (int d1 = 0 ; d1 <= (sp[1 ] ? 1 : 0 ); ++d1) {
337- if (d1 == 1 && !dn[1 ]) continue ;
338- const int delta1 = sp[1 ] ? (d1 == 0 ? +1 : -1 ) : 0 ;
339-
340- for (int d2 = 0 ; d2 <= (sp[2 ] ? 1 : 0 ); ++d2) {
341- if (d2 == 1 && !dn[2 ]) continue ;
342- const int delta2 = sp[2 ] ? (d2 == 0 ? +1 : -1 ) : 0 ;
343-
344- for (int d3 = 0 ; d3 <= (sp[3 ] ? 1 : 0 ); ++d3) {
345- if (d3 == 1 && !dn[3 ]) continue ;
346- const int delta3 = sp[3 ] ? (d3 == 0 ? +1 : -1 ) : 0 ;
347-
348- // Down-shifts contribute -1 sign each
349- const int shift_sign =
350- (delta0 < 0 ? -1 : 1 ) * (delta1 < 0 ? -1 : 1 ) *
351- (delta2 < 0 ? -1 : 1 ) * (delta3 < 0 ? -1 : 1 );
352-
353- const int eidx = d0 * 8 + d1 * 4 + d2 * 2 + d3;
354- const real_t * e = eri[eidx];
355- if (!e) continue ;
356-
357- const int sns = eri_nb[eidx] * eri_nc[eidx] * eri_nd[eidx];
358- const int snc = eri_nc[eidx] * eri_nd[eidx];
359- const int snd = eri_nd[eidx];
360-
361- const int deltas[4 ] = {delta0, delta1, delta2, delta3};
362- const int sign = overall_sign * shift_sign;
363-
364- // Inner loop over output CGFs
365- accumulate (out, e, sign, shells, sp, am, r, deltas, na, nb,
366- nc, nd, sns, snc, snd);
367- }
368- }
369- }
307+ // Identify which centers have (σ.p) — these form quaternion pairs.
308+ // sp_centers[i] = index of i-th (σ.p) center in {0=a,1=b,2=c,3=d}
309+ int sp_centers[4 ], n_sp_centers = 0 ;
310+ for (int i = 0 ; i < 4 ; ++i)
311+ if (sp[i]) sp_centers[n_sp_centers++] = i;
312+
313+ if (total_sp == 1 ) {
314+ // Single unpaired (σ.p): 3 Cartesian components (DF only)
315+ const int ci = sp_centers[0 ]; // the (σ.p) center
316+ for (int r_dir = 0 ; r_dir < 3 ; ++r_dir) {
317+ real_t * out = buf_.data () + r_dir * n_out;
318+ int r[4 ] = {-1 , -1 , -1 , -1 };
319+ r[ci] = r_dir;
320+ contract_shifts (out, eri, eri_nb, eri_nc, eri_nd, sp, dn, am, r, 1 , na,
321+ nb, nc, nd);
322+ }
323+ } else if (total_sp == 2 ) {
324+ // One quaternion pair: 4 components {S, X, Y, Z}
325+ const int c1 = sp_centers[0 ], c2 = sp_centers[1 ];
326+ for (int q = 0 ; q < 4 ; ++q) {
327+ real_t * out = buf_.data () + q * n_out;
328+ DirPair pairs[3 ];
329+ int npairs;
330+ get_qpairs (q, pairs, npairs);
331+ for (int ip = 0 ; ip < npairs; ++ip) {
332+ int r[4 ] = {-1 , -1 , -1 , -1 };
333+ r[c1] = pairs[ip].r1 ;
334+ r[c2] = pairs[ip].r2 ;
335+ contract_shifts (out, eri, eri_nb, eri_nc, eri_nd, sp, dn, am, r,
336+ pairs[ip].sign , na, nb, nc, nd);
337+ }
338+ }
339+ } else if (total_sp == 4 ) {
340+ // Two quaternion pairs: 16 components, index = 4*bra_q + ket_q
341+ // Bra pair: first two (σ.p) centers, Ket pair: last two
342+ // With standard ordering (a,b,c,d), bra=(0,1), ket=(2,3)
343+ const int b1 = sp_centers[0 ], b2 = sp_centers[1 ];
344+ const int k1 = sp_centers[2 ], k2 = sp_centers[3 ];
345+ for (int bq = 0 ; bq < 4 ; ++bq) {
346+ DirPair bp[3 ];
347+ int nbp;
348+ get_qpairs (bq, bp, nbp);
349+ for (int kq = 0 ; kq < 4 ; ++kq) {
350+ int comp = bq * 4 + kq;
351+ real_t * out = buf_.data () + comp * n_out;
352+ // T4 sign: -1 when both quaternion indices are cross-product
353+ int t4 = (bq > 0 && kq > 0 ) ? -1 : 1 ;
354+ DirPair kp[3 ];
355+ int nkp;
356+ get_qpairs (kq, kp, nkp);
357+ for (int ib = 0 ; ib < nbp; ++ib) {
358+ for (int ik = 0 ; ik < nkp; ++ik) {
359+ int r[4 ] = {-1 , -1 , -1 , -1 };
360+ r[b1] = bp[ib].r1 ;
361+ r[b2] = bp[ib].r2 ;
362+ r[k1] = kp[ik].r1 ;
363+ r[k2] = kp[ik].r2 ;
364+ contract_shifts (out, eri, eri_nb, eri_nc, eri_nd, sp, dn, am, r,
365+ bp[ib].sign * kp[ik].sign * t4, na, nb, nc, nd);
370366 }
371367 }
372368 }
@@ -376,30 +372,53 @@ class SigmaPEngine {
376372 return targets_;
377373 }
378374
379- // / Get direction pairs for a particle based on (σ.p) config
380- static void get_particle_pairs (bool sp_f1, bool sp_f2, int n_sp, int q_idx,
381- DirPair* pairs, int & npairs) {
382- if (n_sp == 0 ) {
383- pairs[0 ] = {0 , 0 , 1 };
384- npairs = 1 ;
385- } else if (n_sp == 1 ) {
386- // Single (σ.p): q_idx is the Cartesian direction (x=0, y=1, z=2)
387- if (sp_f1) {
388- pairs[0 ] = {q_idx, 0 , 1 };
389- } else {
390- pairs[0 ] = {0 , q_idx, 1 };
375+ // / Contract AM-shift terms for a fixed set of directions r[0..3].
376+ // / Loops over all up/down shift combinations and accumulates into out.
377+ void contract_shifts (real_t * out, const std::array<const real_t *, 16 >& eri,
378+ const std::array<int , 16 >& eri_nb,
379+ const std::array<int , 16 >& eri_nc,
380+ const std::array<int , 16 >& eri_nd, const bool sp[4 ],
381+ const Shell* dn[4 ], const int am[4 ], const int r[4 ],
382+ int dir_sign, int na, int nb, int nc, int nd) {
383+ for (int d0 = 0 ; d0 <= (sp[0 ] ? 1 : 0 ); ++d0) {
384+ if (d0 == 1 && !dn[0 ]) continue ;
385+ const int delta0 = sp[0 ] ? (d0 == 0 ? +1 : -1 ) : 0 ;
386+ for (int d1 = 0 ; d1 <= (sp[1 ] ? 1 : 0 ); ++d1) {
387+ if (d1 == 1 && !dn[1 ]) continue ;
388+ const int delta1 = sp[1 ] ? (d1 == 0 ? +1 : -1 ) : 0 ;
389+ for (int d2 = 0 ; d2 <= (sp[2 ] ? 1 : 0 ); ++d2) {
390+ if (d2 == 1 && !dn[2 ]) continue ;
391+ const int delta2 = sp[2 ] ? (d2 == 0 ? +1 : -1 ) : 0 ;
392+ for (int d3 = 0 ; d3 <= (sp[3 ] ? 1 : 0 ); ++d3) {
393+ if (d3 == 1 && !dn[3 ]) continue ;
394+ const int delta3 = sp[3 ] ? (d3 == 0 ? +1 : -1 ) : 0 ;
395+
396+ const int shift_sign =
397+ (delta0 < 0 ? -1 : 1 ) * (delta1 < 0 ? -1 : 1 ) *
398+ (delta2 < 0 ? -1 : 1 ) * (delta3 < 0 ? -1 : 1 );
399+
400+ const int eidx = d0 * 8 + d1 * 4 + d2 * 2 + d3;
401+ const real_t * e = eri[eidx];
402+ if (!e) continue ;
403+
404+ const int sns = eri_nb[eidx] * eri_nc[eidx] * eri_nd[eidx];
405+ const int snc = eri_nc[eidx] * eri_nd[eidx];
406+ const int snd = eri_nd[eidx];
407+ const int deltas[4 ] = {delta0, delta1, delta2, delta3};
408+
409+ accumulate (out, e, dir_sign * shift_sign, sp, am, r, deltas, na, nb,
410+ nc, nd, sns, snc, snd);
411+ }
412+ }
391413 }
392- npairs = 1 ;
393- } else {
394- get_qpairs (q_idx, pairs, npairs);
395414 }
396415 }
397416
398417 // / Accumulate one AM-shift term into the output buffer
399418 void accumulate (real_t * out, const real_t * eri_buf, int sign,
400- const Shell* shells [4 ], const bool sp [4 ], const int am [4 ],
401- const int r [4 ], const int delta[ 4 ] , int na , int nb , int nc ,
402- int nd, int sns, int snc, int snd) {
419+ const bool sp [4 ], const int am [4 ], const int r [4 ],
420+ const int delta [4 ], int na, int nb , int nc , int nd , int sns ,
421+ int snc, int snd) {
403422 int oa = 0 ;
404423 for (int ax = am[0 ]; ax >= 0 ; --ax) {
405424 for (int ay = am[0 ] - ax; ay >= 0 ; --ay) {
0 commit comments