use std::collections::HashMap;
use std::hash::Hash;
type GroupBy4 = HashMap<u32, Vec<(u32, u32, u32, f64)>>;
type PairGroup = HashMap<(u32, u32, u32), Vec<(u32, u32, f64)>>;
pub(crate) const TOL_F_UNITARY: f64 = 1.0e-9;
pub(crate) const TOL_PENTAGON: f64 = 1.0e-8;
pub(crate) const TOL_HEXAGON: f64 = 1.0e-8;
#[derive(Clone, Copy, Debug)]
pub(crate) struct MEntry {
pub m1: u32,
pub m2: u32,
pub m3: u32,
pub mu: u32,
pub value: f64,
}
pub(crate) trait Family {
type Irrep: Clone + Eq + Hash;
type Error;
fn mult(
&mut self,
a: &Self::Irrep,
b: &Self::Irrep,
c: &Self::Irrep,
) -> Result<usize, Self::Error>;
fn cgc_entries(
&mut self,
a: &Self::Irrep,
b: &Self::Irrep,
c: &Self::Irrep,
) -> Result<Vec<MEntry>, Self::Error>;
fn products(
&mut self,
a: &Self::Irrep,
b: &Self::Irrep,
) -> Result<Vec<Self::Irrep>, Self::Error>;
}
#[derive(Clone, Debug, PartialEq)]
pub struct FBlock {
dims: [usize; 4],
data: Vec<f64>,
}
impl FBlock {
fn zeros(dims: [usize; 4]) -> Self {
FBlock {
dims,
data: vec![0.0; dims[0] * dims[1] * dims[2] * dims[3]],
}
}
#[inline]
fn flat(dims: [usize; 4], mu: usize, nu: usize, kappa: usize, lambda: usize) -> usize {
((mu * dims[1] + nu) * dims[2] + kappa) * dims[3] + lambda
}
pub fn dims(&self) -> [usize; 4] {
self.dims
}
pub fn data(&self) -> &[f64] {
&self.data
}
pub fn at(&self, mu: usize, nu: usize, kappa: usize, lambda: usize) -> f64 {
assert!(
mu < self.dims[0] && nu < self.dims[1] && kappa < self.dims[2] && lambda < self.dims[3],
"FBlock index out of range"
);
self.data[Self::flat(self.dims, mu, nu, kappa, lambda)]
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct RBlock {
n: usize,
data: Vec<f64>,
}
impl RBlock {
fn zeros(n: usize) -> Self {
RBlock {
n,
data: vec![0.0; n * n],
}
}
pub fn dim(&self) -> usize {
self.n
}
pub fn data(&self) -> &[f64] {
&self.data
}
pub fn at(&self, mu: usize, nu: usize) -> f64 {
assert!(mu < self.n && nu < self.n, "RBlock index out of range");
self.data[mu * self.n + nu]
}
}
#[allow(clippy::too_many_arguments)]
pub(crate) fn f_block_raw<F: Family>(
fam: &mut F,
a: &F::Irrep,
b: &F::Irrep,
c: &F::Irrep,
d: &F::Irrep,
e: &F::Irrep,
f: &F::Irrep,
) -> Result<FBlock, F::Error> {
let n1 = fam.mult(a, b, e)?;
let n2 = fam.mult(e, c, d)?;
let n3 = fam.mult(b, c, f)?;
let n4 = fam.mult(a, f, d)?;
let dims = [n1, n2, n3, n4];
if n1 == 0 || n2 == 0 || n3 == 0 || n4 == 0 {
return Ok(FBlock::zeros(dims));
}
let cab = fam.cgc_entries(a, b, e)?; let cecd = fam.cgc_entries(e, c, d)?; let cbcf = fam.cgc_entries(b, c, f)?; let cafd = fam.cgc_entries(a, f, d)?;
let mut a_by_me: GroupBy4 = HashMap::new();
for x in &cab {
a_by_me
.entry(x.m3)
.or_default()
.push((x.m1, x.m2, x.mu, x.value)); }
let mut b_by_me: HashMap<u32, Vec<(u32, u32, f64)>> = HashMap::new();
for x in &cecd {
if x.m3 == 0 {
b_by_me.entry(x.m1).or_default().push((x.m2, x.mu, x.value)); }
}
let mut ab: PairGroup = HashMap::new();
for (me, alist) in &a_by_me {
let Some(blist) = b_by_me.get(me) else {
continue;
};
for &(ma, mb, mu, va) in alist {
for &(mc, nu, vb) in blist {
ab.entry((ma, mb, mc)).or_default().push((mu, nu, va * vb));
}
}
}
let mut c_by_mf: GroupBy4 = HashMap::new();
for x in &cbcf {
c_by_mf
.entry(x.m3)
.or_default()
.push((x.m1, x.m2, x.mu, x.value)); }
let mut d_by_mf: HashMap<u32, Vec<(u32, u32, f64)>> = HashMap::new();
for x in &cafd {
if x.m3 == 0 {
d_by_mf.entry(x.m2).or_default().push((x.m1, x.mu, x.value)); }
}
let mut cd: PairGroup = HashMap::new();
for (mf, clist) in &c_by_mf {
let Some(dlist) = d_by_mf.get(mf) else {
continue;
};
for &(mb, mc, kappa, vc) in clist {
for &(ma, lambda, vd) in dlist {
cd.entry((ma, mb, mc))
.or_default()
.push((kappa, lambda, vc * vd));
}
}
}
let mut block = FBlock::zeros(dims);
for (key, ablist) in &ab {
let Some(cdlist) = cd.get(key) else {
continue;
};
for &(mu, nu, vab) in ablist {
for &(kappa, lambda, vcd) in cdlist {
let idx = FBlock::flat(
dims,
mu as usize,
nu as usize,
kappa as usize,
lambda as usize,
);
block.data[idx] += vab * vcd;
}
}
}
Ok(block)
}
pub(crate) fn r_block_raw<F: Family>(
fam: &mut F,
a: &F::Irrep,
b: &F::Irrep,
c: &F::Irrep,
) -> Result<RBlock, F::Error> {
let n1 = fam.mult(a, b, c)?; let n2 = fam.mult(b, a, c)?; if n1 == 0 || n2 == 0 {
return Ok(RBlock::zeros(n1.max(n2)));
}
debug_assert_eq!(n1, n2, "N^c_ab == N^c_ba");
let cab = fam.cgc_entries(a, b, c)?; let cba = fam.cgc_entries(b, a, c)?;
let mut a_map: HashMap<(u32, u32), Vec<(u32, f64)>> = HashMap::new();
for x in &cab {
if x.m3 == 0 {
a_map.entry((x.m1, x.m2)).or_default().push((x.mu, x.value));
}
}
let mut b_map: HashMap<(u32, u32), Vec<(u32, f64)>> = HashMap::new();
for x in &cba {
if x.m3 == 0 {
b_map.entry((x.m2, x.m1)).or_default().push((x.mu, x.value));
}
}
let mut block = RBlock::zeros(n1);
for (key, alist) in &a_map {
let Some(blist) = b_map.get(key) else {
continue;
};
for &(mu, va) in alist {
for &(nu, vb) in blist {
block.data[mu as usize * n1 + nu as usize] += va * vb;
}
}
}
Ok(block)
}
fn intersect_products<F: Family>(
fam: &mut F,
a: &F::Irrep,
b: &F::Irrep,
c: &F::Irrep,
d: &F::Irrep,
) -> Result<Vec<F::Irrep>, F::Error> {
let left = fam.products(a, b)?;
let right = fam.products(c, d)?;
Ok(left.into_iter().filter(|k| right.contains(k)).collect())
}
struct BlockMemo<I: Clone + Eq + Hash> {
f: HashMap<[I; 6], FBlock>,
r: HashMap<[I; 3], RBlock>,
}
impl<I: Clone + Eq + Hash> Default for BlockMemo<I> {
fn default() -> Self {
BlockMemo {
f: HashMap::new(),
r: HashMap::new(),
}
}
}
impl<I: Clone + Eq + Hash> BlockMemo<I> {
#[allow(clippy::too_many_arguments)]
fn f_block<F: Family<Irrep = I>>(
&mut self,
fam: &mut F,
a: &I,
b: &I,
c: &I,
d: &I,
e: &I,
f: &I,
) -> Result<FBlock, F::Error> {
let key = [
a.clone(),
b.clone(),
c.clone(),
d.clone(),
e.clone(),
f.clone(),
];
if let Some(bl) = self.f.get(&key) {
return Ok(bl.clone());
}
let bl = f_block_raw(fam, a, b, c, d, e, f)?;
self.f.insert(key, bl.clone());
Ok(bl)
}
fn r_block<F: Family<Irrep = I>>(
&mut self,
fam: &mut F,
a: &I,
b: &I,
c: &I,
) -> Result<RBlock, F::Error> {
let key = [a.clone(), b.clone(), c.clone()];
if let Some(bl) = self.r.get(&key) {
return Ok(bl.clone());
}
let bl = r_block_raw(fam, a, b, c)?;
self.r.insert(key, bl.clone());
Ok(bl)
}
}
pub(crate) fn f_unitarity_residual<F: Family>(
fam: &mut F,
a: &F::Irrep,
b: &F::Irrep,
c: &F::Irrep,
d: &F::Irrep,
) -> Result<f64, F::Error> {
let mut rows: Vec<(F::Irrep, usize, usize)> = Vec::new();
for e in fam.products(a, b)? {
let n_ab_e = fam.mult(a, b, &e)?;
let n_ec_d = fam.mult(&e, c, d)?;
for mu in 0..n_ab_e {
for nu in 0..n_ec_d {
rows.push((e.clone(), mu, nu));
}
}
}
let mut cols: Vec<(F::Irrep, usize, usize)> = Vec::new();
for f in fam.products(b, c)? {
let n_bc_f = fam.mult(b, c, &f)?;
let n_af_d = fam.mult(a, &f, d)?;
for kappa in 0..n_bc_f {
for lambda in 0..n_af_d {
cols.push((f.clone(), kappa, lambda));
}
}
}
let nr = rows.len();
let nc = cols.len();
let mut m = vec![0.0f64; nr * nc];
let mut memo = BlockMemo::default();
for (ri, (e, mu, nu)) in rows.iter().enumerate() {
for (ci, (f, kappa, lambda)) in cols.iter().enumerate() {
let block = memo.f_block(fam, a, b, c, d, e, f)?;
m[ri * nc + ci] = block.at(*mu, *nu, *kappa, *lambda);
}
}
let mut worst = 0.0f64;
for i in 0..nr {
for j in 0..nr {
let mut dot = 0.0;
for k in 0..nc {
dot += m[i * nc + k] * m[j * nc + k];
}
let target = if i == j { 1.0 } else { 0.0 };
worst = worst.max((dot - target).abs());
}
}
Ok(worst)
}
pub(crate) fn pentagon_residual<F: Family>(
fam: &mut F,
a: &F::Irrep,
b: &F::Irrep,
c: &F::Irrep,
d: &F::Irrep,
) -> Result<f64, F::Error> {
let mut worst = 0.0f64;
let mut memo = BlockMemo::default();
for f in fam.products(a, b)? {
for h in fam.products(c, d)? {
for g in fam.products(&f, c)? {
for i in fam.products(b, &h)? {
for e in intersect_products(fam, &g, d, a, &i)? {
let n_lambda = fam.mult(&f, c, &g)?;
let n_mu = fam.mult(&g, d, &e)?;
let n_nu = fam.mult(c, d, &h)?;
let n_kappa = fam.mult(a, b, &f)?;
let n_rho = fam.mult(b, &h, &i)?;
let n_sigma = fam.mult(a, &i, &e)?;
if [n_lambda, n_mu, n_nu, n_kappa, n_rho, n_sigma].contains(&0) {
continue; }
let f1 = memo.f_block(fam, &f, c, d, &e, &g, &h)?; let f2 = memo.f_block(fam, a, b, &h, &e, &f, &i)?; let n_tau = f1.dims()[3];
let mut p2_terms: Vec<(FBlock, FBlock, FBlock)> = Vec::new();
for j in fam.products(b, c)? {
let g1 = memo.f_block(fam, a, b, c, &g, &f, &j)?; let g2 = memo.f_block(fam, a, &j, d, &e, &g, &i)?; let g3 = memo.f_block(fam, b, c, d, &i, &j, &h)?; p2_terms.push((g1, g2, g3));
}
for lambda in 0..n_lambda {
for mu in 0..n_mu {
for nu in 0..n_nu {
for kappa in 0..n_kappa {
for rho in 0..n_rho {
for sigma in 0..n_sigma {
let mut p1 = 0.0;
for tau in 0..n_tau {
p1 += f1.at(lambda, mu, nu, tau)
* f2.at(kappa, tau, rho, sigma);
}
let mut p2 = 0.0;
for (g1, g2, g3) in &p2_terms {
let n_alpha = g1.dims()[2];
let n_beta = g1.dims()[3];
let n_taup = g2.dims()[2];
for alpha in 0..n_alpha {
for beta in 0..n_beta {
for taup in 0..n_taup {
p2 += g1
.at(kappa, lambda, alpha, beta)
* g2.at(beta, mu, taup, sigma)
* g3.at(alpha, taup, nu, rho);
}
}
}
}
worst = worst.max((p1 - p2).abs());
}
}
}
}
}
}
}
}
}
}
}
Ok(worst)
}
pub(crate) fn hexagon_residual<F: Family>(
fam: &mut F,
a: &F::Irrep,
b: &F::Irrep,
c: &F::Irrep,
) -> Result<f64, F::Error> {
let mut worst = 0.0f64;
let mut memo = BlockMemo::default();
for e in fam.products(c, a)? {
let rcae = memo.r_block(fam, c, a, &e)?; let race = memo.r_block(fam, a, c, &e)?; for f in fam.products(c, b)? {
let rcbf = memo.r_block(fam, c, b, &f)?; let rbcf = memo.r_block(fam, b, c, &f)?; for d in intersect_products(fam, &e, b, a, &f)? {
let n_alpha = fam.mult(c, a, &e)?;
let n_beta = fam.mult(&e, b, &d)?;
let n_mu = fam.mult(b, c, &f)?;
let n_nu = fam.mult(a, &f, &d)?;
if [n_alpha, n_beta, n_mu, n_nu].contains(&0) {
continue;
}
let facb = memo.f_block(fam, a, c, b, &d, &e, &f)?; let n_lam = facb.dims()[0]; let n_gam = facb.dims()[2];
let mut frf_terms: Vec<(FBlock, RBlock, RBlock, FBlock)> = Vec::new();
for g in fam.products(a, b)? {
let rcgd = memo.r_block(fam, c, &g, &d)?;
let rgcd = memo.r_block(fam, &g, c, &d)?;
let fcab = memo.f_block(fam, c, a, b, &d, &e, &g)?; let fabc = memo.f_block(fam, a, b, c, &d, &g, &f)?; frf_terms.push((fcab, rcgd, rgcd, fabc));
}
for alpha in 0..n_alpha {
for beta in 0..n_beta {
for mu in 0..n_mu {
for nu in 0..n_nu {
let mut rfr1 = 0.0;
let mut rfr2 = 0.0;
for lam in 0..n_lam {
for gam in 0..n_gam {
let fv = facb.at(lam, beta, gam, nu);
rfr1 += rcae.at(alpha, lam) * fv * rcbf.at(gam, mu);
rfr2 += race.at(alpha, lam) * fv * rbcf.at(gam, mu);
}
}
let mut frf1 = 0.0;
let mut frf2 = 0.0;
for (fcab, rcgd, rgcd, fabc) in &frf_terms {
let n_delta = fcab.dims()[2]; let n_sigma = fcab.dims()[3]; let n_psi = rcgd.dim(); for delta in 0..n_delta {
for sigma in 0..n_sigma {
let fc = fcab.at(alpha, beta, delta, sigma);
for psi in 0..n_psi {
let fa = fabc.at(delta, psi, mu, nu);
frf1 += fc * rcgd.at(sigma, psi) * fa;
frf2 += fc * rgcd.at(sigma, psi) * fa;
}
}
}
}
worst = worst.max((rfr1 - frf1).abs());
worst = worst.max((rfr2 - frf2).abs());
}
}
}
}
}
}
}
Ok(worst)
}