use std::sync::Arc;
use super::catalog::CatalogCgc;
use super::{directproduct, CanonicalCatalog, CatalogError, Irrep};
use crate::frcore::{
self, f_block_raw, f_unitarity_residual, hexagon_residual, pentagon_residual, r_block_raw,
Family, MEntry,
};
pub use crate::frcore::{FBlock, RBlock};
#[derive(Clone, Debug, PartialEq)]
pub enum FrError {
Catalog(CatalogError),
FNotUnitary {
residual: f64,
},
PentagonViolation {
residual: f64,
},
HexagonViolation {
residual: f64,
},
}
impl std::fmt::Display for FrError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
FrError::Catalog(e) => write!(f, "{e}"),
FrError::FNotUnitary { residual } => {
write!(f, "B/C/D F-move matrix not unitary (residual {residual:e})")
}
FrError::PentagonViolation { residual } => {
write!(
f,
"B/C/D pentagon identity violated (residual {residual:e})"
)
}
FrError::HexagonViolation { residual } => {
write!(f, "B/C/D hexagon identity violated (residual {residual:e})")
}
}
}
}
impl std::error::Error for FrError {}
impl From<CatalogError> for FrError {
fn from(e: CatalogError) -> Self {
FrError::Catalog(e)
}
}
pub(crate) static CGC_SWEEPS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
struct BcdFamily<'a> {
cat: &'a mut CanonicalCatalog,
}
impl Family for BcdFamily<'_> {
type Irrep = Irrep;
type Error = CatalogError;
fn mult(&mut self, a: &Irrep, b: &Irrep, c: &Irrep) -> Result<usize, CatalogError> {
bcd_mult(a, b, c)
}
fn cgc_entries(
&mut self,
a: &Irrep,
b: &Irrep,
c: &Irrep,
) -> Result<Vec<MEntry>, CatalogError> {
let tier = crate::cache::cache_bcd_cgc();
if let Some(hit) = tier.get(&(a.clone(), b.clone(), c.clone())) {
return Ok(sparse_entries(a, &hit));
}
CGC_SWEEPS.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
let channels = self.cat.cgc_product(a, b)?;
let mut wanted: Option<Arc<CatalogCgc>> = None;
for ch in channels {
let key = (a.clone(), b.clone(), ch.s3().clone());
let stored = tier.insert(key, Arc::new(ch));
if stored.s3() == c {
wanted = Some(stored);
}
}
let cgc = wanted.expect("c is a coupled channel of a⊗b (mult>0 checked by caller)");
Ok(sparse_entries(a, &cgc))
}
fn products(&mut self, a: &Irrep, b: &Irrep) -> Result<Vec<Irrep>, CatalogError> {
Ok(directproduct(a, b)?.into_keys().collect())
}
}
fn bcd_mult(a: &Irrep, b: &Irrep, c: &Irrep) -> Result<usize, CatalogError> {
Ok(directproduct(a, b)?.get(c).copied().unwrap_or(0) as usize)
}
fn sparse_entries(s1: &Irrep, cgc: &CatalogCgc) -> Vec<MEntry> {
let (rows, d3) = cgc.copy_shape();
let d1 = usize::try_from(s1.dim()).expect("irrep dim fits usize for tractable ranks");
let mult = cgc.multiplicity();
let mut out = Vec::new();
for mu in 0..mult {
let copy = cgc.copy(mu);
for col in 0..d3 {
for row in 0..rows {
let v = copy[col * rows + row];
if v != 0.0 {
out.push(MEntry {
m1: (row % d1) as u32,
m2: (row / d1) as u32,
m3: col as u32,
mu: mu as u32,
value: v,
});
}
}
}
}
out
}
fn require_catalog_family(cat: &CanonicalCatalog, labels: &[&Irrep]) -> Result<(), CatalogError> {
for s in labels {
if s.series() != cat.series() || s.rank() != cat.rank() {
return Err(CatalogError::WrongGroup {
catalog: (cat.series(), cat.rank()),
got: (s.series(), s.rank()),
});
}
}
Ok(())
}
#[allow(clippy::too_many_arguments)]
pub fn f_symbol(
cat: &mut CanonicalCatalog,
a: &Irrep,
b: &Irrep,
c: &Irrep,
d: &Irrep,
e: &Irrep,
f: &Irrep,
) -> Result<FBlock, FrError> {
require_catalog_family(cat, &[a, b, c, d, e, f])?;
let vertices = [(a, b, e), (e, c, d), (b, c, f), (a, f, d)];
for (x, y, z) in vertices {
if bcd_mult(x, y, z)? == 0 {
return Err(FrError::Catalog(CatalogError::ZeroFusionChannel {
a: x.dynkin(),
b: y.dynkin(),
c: z.dynkin(),
}));
}
}
let cache = crate::cache::cache_bcd_f();
let key = (
a.clone(),
b.clone(),
c.clone(),
d.clone(),
e.clone(),
f.clone(),
);
if let Some(hit) = cache.get(&key) {
return Ok((*hit).clone());
}
let block = {
let mut fam = BcdFamily { cat };
f_block_raw(&mut fam, a, b, c, d, e, f)?
};
let stored = cache.insert(key, Arc::new(block));
Ok((*stored).clone())
}
pub fn r_symbol(
cat: &mut CanonicalCatalog,
a: &Irrep,
b: &Irrep,
c: &Irrep,
) -> Result<RBlock, FrError> {
require_catalog_family(cat, &[a, b, c])?;
if bcd_mult(a, b, c)? == 0 {
return Err(FrError::Catalog(CatalogError::ZeroFusionChannel {
a: a.dynkin(),
b: b.dynkin(),
c: c.dynkin(),
}));
}
let mut fam = BcdFamily { cat };
Ok(r_block_raw(&mut fam, a, b, c)?)
}
pub fn check_f_unitarity(
cat: &mut CanonicalCatalog,
a: &Irrep,
b: &Irrep,
c: &Irrep,
d: &Irrep,
) -> Result<(), FrError> {
require_catalog_family(cat, &[a, b, c, d])?;
let mut fam = BcdFamily { cat };
let worst = f_unitarity_residual(&mut fam, a, b, c, d)?;
if worst > frcore::TOL_F_UNITARY {
return Err(FrError::FNotUnitary { residual: worst });
}
Ok(())
}
pub fn check_pentagon(
cat: &mut CanonicalCatalog,
a: &Irrep,
b: &Irrep,
c: &Irrep,
d: &Irrep,
) -> Result<(), FrError> {
require_catalog_family(cat, &[a, b, c, d])?;
let mut fam = BcdFamily { cat };
let worst = pentagon_residual(&mut fam, a, b, c, d)?;
if worst > frcore::TOL_PENTAGON {
return Err(FrError::PentagonViolation { residual: worst });
}
Ok(())
}
pub fn check_hexagon(
cat: &mut CanonicalCatalog,
a: &Irrep,
b: &Irrep,
c: &Irrep,
) -> Result<(), FrError> {
require_catalog_family(cat, &[a, b, c])?;
let mut fam = BcdFamily { cat };
let worst = hexagon_residual(&mut fam, a, b, c)?;
if worst > frcore::TOL_HEXAGON {
return Err(FrError::HexagonViolation { residual: worst });
}
Ok(())
}
#[cfg(test)]
mod tests;
#[doc(hidden)]
pub fn cgc_sweeps() -> u64 {
CGC_SWEEPS.load(std::sync::atomic::Ordering::Relaxed)
}