use integral_math::am::{cart_components, cart_index, n_cart};
use crate::os::{overlap_into, Prim, Vec3, MAX_L};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Factor {
R(usize),
P(usize),
}
#[derive(Debug, Clone, PartialEq)]
pub struct Term {
pub coeff: f64,
pub factors: Vec<Factor>,
}
impl Term {
#[must_use]
pub fn new(coeff: f64, factors: Vec<Factor>) -> Self {
Term { coeff, factors }
}
#[must_use]
fn n_momentum(&self) -> usize {
self.factors
.iter()
.filter(|f| matches!(f, Factor::P(_)))
.count()
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct Operator {
pub terms: Vec<Term>,
pub origin: Vec3,
}
impl Operator {
#[must_use]
pub fn new(terms: Vec<Term>, origin: Vec3) -> Self {
Operator { terms, origin }
}
#[must_use]
pub fn identity() -> Self {
Operator::new(vec![Term::new(1.0, vec![])], [0.0; 3])
}
#[must_use]
pub fn dipole(axis: usize, origin: Vec3) -> Self {
Operator::new(vec![Term::new(1.0, vec![Factor::R(axis)])], origin)
}
#[must_use]
pub fn quadrupole(i: usize, j: usize, origin: Vec3) -> Self {
Operator::new(
vec![Term::new(1.0, vec![Factor::R(i), Factor::R(j)])],
origin,
)
}
#[must_use]
pub fn kinetic() -> Self {
let terms = (0..3)
.map(|ax| Term::new(0.5, vec![Factor::P(ax), Factor::P(ax)]))
.collect();
Operator::new(terms, [0.0; 3])
}
#[must_use]
pub fn momentum(axis: usize) -> Self {
Operator::new(vec![Term::new(1.0, vec![Factor::P(axis)])], [0.0; 3])
}
#[must_use]
pub fn angular_momentum(axis: usize, origin: Vec3) -> Self {
let (j, k) = match axis {
0 => (1, 2),
1 => (2, 0),
_ => (0, 1),
};
Operator::new(
vec![
Term::new(1.0, vec![Factor::R(j), Factor::P(k)]),
Term::new(-1.0, vec![Factor::R(k), Factor::P(j)]),
],
origin,
)
}
#[must_use]
pub fn degree(&self) -> usize {
self.terms
.iter()
.map(|t| t.factors.len())
.max()
.unwrap_or(0)
}
}
fn phase(n_p: usize) -> (bool, f64) {
match n_p % 4 {
0 => (false, 1.0),
1 => (true, 1.0),
2 => (false, -1.0),
_ => (true, -1.0),
}
}
fn apply_factor(
factor: Factor,
beta: f64,
b_minus_o: Vec3,
src: &[([usize; 3], f64)],
) -> Vec<([usize; 3], f64)> {
let mut out = Vec::with_capacity(src.len() * 2);
match factor {
Factor::R(ax) => {
for &(m, c) in src {
let mut up = m;
up[ax] += 1;
out.push((up, c));
out.push((m, c * b_minus_o[ax]));
}
}
Factor::P(ax) => {
for &(m, c) in src {
let mut up = m;
up[ax] += 1;
out.push((up, c * 2.0 * beta));
if m[ax] >= 1 {
let mut dn = m;
dn[ax] -= 1;
out.push((dn, -c * m[ax] as f64));
}
}
}
}
out
}
pub fn operator_into(
op: &Operator,
a: Prim,
b: Prim,
scale: f64,
out_re: &mut [f64],
out_im: &mut [f64],
) {
let (la, lb) = (a.l, b.l);
let beta = b.exp;
let deg = op.degree();
let lmax_ket = lb + deg;
debug_assert!(lmax_ket <= MAX_L, "operator raises ket beyond MAX_L");
let na = n_cart(la);
let nb = n_cart(lb);
debug_assert_eq!(out_re.len(), na * nb, "real block size");
debug_assert_eq!(out_im.len(), na * nb, "imag block size");
let ov: Vec<Vec<f64>> = (0..=lmax_ket)
.map(|lp| {
let mut blk = vec![0.0; na * n_cart(lp)];
overlap_into(
Prim::new(a.exp, a.center, la),
Prim::new(beta, b.center, lp),
1.0,
&mut blk,
);
blk
})
.collect();
let b_minus_o = [
b.center[0] - op.origin[0],
b.center[1] - op.origin[1],
b.center[2] - op.origin[2],
];
let comps_b = cart_components(lb);
for term in &op.terms {
let (is_imag, psign) = phase(term.n_momentum());
let out = if is_imag { &mut *out_im } else { &mut *out_re };
for (ib, &bm) in comps_b.iter().enumerate() {
let mut exp = vec![(bm, 1.0)];
for factor in term.factors.iter().rev() {
exp = apply_factor(*factor, beta, b_minus_o, &exp);
}
for (m, rc) in exp {
let lp = m[0] + m[1] + m[2];
let idx = cart_index(m);
let np = n_cart(lp);
let blk = &ov[lp];
let w = scale * psign * term.coeff * rc;
for ia in 0..na {
out[ia * nb + ib] += w * blk[ia * np + idx];
}
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn identity_is_overlap() {
let a = Prim::new(1.1, [0.0, 0.0, 0.0], 1);
let b = Prim::new(0.7, [0.3, -0.2, 0.5], 1);
let mut s = vec![0.0; 9];
overlap_into(a, b, 1.0, &mut s);
let (mut re, mut im) = (vec![0.0; 9], vec![0.0; 9]);
operator_into(&Operator::identity(), a, b, 1.0, &mut re, &mut im);
for k in 0..9 {
assert!((re[k] - s[k]).abs() < 1e-14, "{} vs {}", re[k], s[k]);
assert_eq!(im[k], 0.0);
}
}
#[test]
fn dipole_about_ket_center_is_pure_raise() {
let a = Prim::new(1.0, [0.1, 0.0, 0.0], 0);
let bc = [0.4, -0.2, 0.3];
let b = Prim::new(0.8, bc, 0);
let op = Operator::dipole(0, bc);
let (mut re, mut im) = (vec![0.0; 1], vec![0.0; 1]);
operator_into(&op, a, b, 1.0, &mut re, &mut im);
let mut raise = vec![0.0; 3];
overlap_into(a, Prim::new(0.8, bc, 1), 1.0, &mut raise);
assert!((re[0] - raise[0]).abs() < 1e-14);
assert_eq!(im[0], 0.0);
}
#[test]
fn momentum_same_center_s_is_zero() {
let c = [0.2, 0.1, -0.3];
let a = Prim::new(1.0, c, 0);
let b = Prim::new(1.0, c, 0);
let (mut re, mut im) = (vec![0.0; 1], vec![0.0; 1]);
operator_into(&Operator::momentum(0), a, b, 1.0, &mut re, &mut im);
assert_eq!(re[0], 0.0);
assert!(im[0].abs() < 1e-14, "im={}", im[0]);
}
#[test]
fn half_p_dot_p_is_kinetic() {
use crate::os::kinetic_into;
let a = Prim::new(1.3, [0.0, 0.0, 0.0], 2);
let b = Prim::new(0.6, [0.4, -0.3, 0.5], 2);
let mut t = vec![0.0; 36];
kinetic_into(a, b, 1.0, &mut t);
let (mut re, mut im) = (vec![0.0; 36], vec![0.0; 36]);
operator_into(&Operator::kinetic(), a, b, 1.0, &mut re, &mut im);
for k in 0..36 {
assert!((re[k] - t[k]).abs() < 1e-12, "k={k}: {} vs {}", re[k], t[k]);
assert_eq!(im[k], 0.0);
}
}
#[test]
fn degree_is_max_factors() {
assert_eq!(Operator::identity().degree(), 0);
assert_eq!(Operator::dipole(0, [0.0; 3]).degree(), 1);
assert_eq!(Operator::momentum(1).degree(), 1);
assert_eq!(Operator::quadrupole(0, 1, [0.0; 3]).degree(), 2);
assert_eq!(Operator::kinetic().degree(), 2);
assert_eq!(Operator::angular_momentum(2, [0.0; 3]).degree(), 2);
}
}