use std::collections::BTreeMap;
use std::collections::HashMap;
use super::linalg::{matmul, qr_positive_q, svd, tmatmul, Dense};
use super::seeds::Seed;
use super::{directproduct, Irrep, Series};
const EPS_SWEEP: f64 = 1e-8;
const EPS_VERIFY: f64 = 1e-10;
const CG_EPS1: f64 = 1e-10;
const FIXRATIONAL_TOL: f64 = 1e-6;
const EPS_MW_UNIQUE: f64 = 1e-8;
#[derive(Clone, Debug, PartialEq)]
pub enum SweepError {
InvalidGeneratorCounts {
np: usize,
nz: usize,
rank: usize,
},
GeneratorMismatch,
SeedNotWeightVector {
multiplet: usize,
cartan: usize,
},
OverlapWithVspace {
residual: f64,
},
OverlapWithUspace {
residual: f64,
},
SpaceOutOfBounds,
IncompleteDecomposition {
dim: usize,
covered: usize,
},
NotOrthonormal {
residual: f64,
},
NonDiagonalCartan {
block: usize,
residual: f64,
},
NonIntegerWeight {
block: usize,
value: f64,
},
MaxWeightNotUnique {
block: usize,
},
InvalidDiscoveredLabel {
dynkin: Vec<i64>,
},
MultiplicityMismatch {
dynkin: Vec<i64>,
expected: u32,
found: u32,
},
CommutatorResidual {
block: usize,
residual: f64,
},
Linalg(String),
}
impl std::fmt::Display for SweepError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
SweepError::InvalidGeneratorCounts { np, nz, rank } => {
write!(f, "invalid generator counts: np={np}, nz={nz}, rank={rank}")
}
SweepError::GeneratorMismatch => {
write!(f, "product of generator sets of mismatched groups/dims")
}
SweepError::SeedNotWeightVector { multiplet, cartan } => write!(
f,
"seed for multiplet {multiplet} is not an Sz[{cartan}] eigenvector \
(cannot determine symmetry labels)"
),
SweepError::OverlapWithVspace { residual } => {
write!(
f,
"lowered vector overlaps the multiplet space (residual {residual:e})"
)
}
SweepError::OverlapWithUspace { residual } => {
write!(
f,
"lowered vector overlaps the accumulated space (residual {residual:e})"
)
}
SweepError::SpaceOutOfBounds => write!(f, "accumulated space exceeded D"),
SweepError::IncompleteDecomposition { dim, covered } => write!(
f,
"sweep covered {covered} of {dim} dimensions (incomplete decomposition)"
),
SweepError::NotOrthonormal { residual } => {
write!(
f,
"accumulated space not orthonormal: U†U−I residual {residual:e}"
)
}
SweepError::NonDiagonalCartan { block, residual } => write!(
f,
"block {block}: projected Cartan not diagonal (residual {residual:e})"
),
SweepError::NonIntegerWeight { block, value } => {
write!(f, "block {block}: non-integer weight/label {value}")
}
SweepError::MaxWeightNotUnique { block } => {
write!(f, "block {block}: maximum-weight state not unique")
}
SweepError::InvalidDiscoveredLabel { dynkin } => {
write!(f, "discovered irrep has an invalid Dynkin label {dynkin:?}")
}
SweepError::MultiplicityMismatch {
dynkin,
expected,
found,
} => write!(
f,
"multiplicity gate: irrep {dynkin:?} exact N={expected} but sweep M={found}"
),
SweepError::CommutatorResidual { block, residual } => write!(
f,
"block {block}: projected generators fail commutators (residual {residual:e})"
),
SweepError::Linalg(msg) => write!(f, "dense factorization failed: {msg}"),
}
}
}
impl std::error::Error for SweepError {}
#[derive(Clone, Debug, PartialEq)]
pub struct Generators {
series: Series,
rank: usize,
dim: usize,
sp: Vec<Dense>,
sz: Vec<Vec<f64>>,
}
impl Generators {
pub fn series(&self) -> Series {
self.series
}
pub fn rank(&self) -> usize {
self.rank
}
pub(crate) fn cartan_diag(&self, i: usize) -> &[f64] {
&self.sz[i]
}
#[cfg(test)]
pub(crate) fn raising(&self, i: usize) -> &Dense {
&self.sp[i]
}
pub(crate) fn coherence_residual(&self, other: &Generators) -> f64 {
if self.dim != other.dim || self.rank != other.rank {
return f64::INFINITY;
}
let mut worst = 0.0f64;
for i in 0..self.rank {
let (a, b) = (&self.sp[i], &other.sp[i]);
for r in 0..self.dim {
for c in 0..self.dim {
worst = worst.max((a.at(r, c) - b.at(r, c)).abs());
}
worst = worst.max((self.sz[i][r] - other.sz[i][r]).abs());
}
}
worst
}
#[cfg(test)]
pub(crate) fn max_commutator_residual(&self) -> f64 {
commutator_residual(&self.sp, &self.sz, self.dim)
}
pub fn dim(&self) -> usize {
self.dim
}
pub fn from_seed(seed: &Seed) -> Generators {
let d = seed.dim();
let sp: Vec<Dense> = seed
.raising()
.iter()
.map(|recs| {
let mut m = Dense::zeros(d, d);
for &(row, col, v) in recs {
m.set(row, col, v as f64);
}
m
})
.collect();
let sz: Vec<Vec<f64>> = seed
.cartan()
.iter()
.map(|diag| diag.iter().map(|&x| x as f64).collect())
.collect();
Generators {
series: seed.series(),
rank: seed.rank(),
dim: d,
sp,
sz,
}
}
pub fn trivial(series: Series, r: usize) -> Generators {
Generators {
series,
rank: r,
dim: 1,
sp: (0..r).map(|_| Dense::zeros(1, 1)).collect(),
sz: (0..r).map(|_| vec![0.0]).collect(),
}
}
pub fn product(a: &Generators, b: &Generators) -> Result<Generators, SweepError> {
if a.series != b.series || a.rank != b.rank {
return Err(SweepError::GeneratorMismatch);
}
let r = a.rank;
let (da, db) = (a.dim, b.dim);
let d = da * db;
let comb = |ma: usize, mb: usize| ma + da * mb;
let mut sp = Vec::with_capacity(r);
for i in 0..r {
let mut m = Dense::zeros(d, d);
for c in 0..da {
for rr in 0..da {
let v = a.sp[i].at(rr, c);
if v != 0.0 {
for mb in 0..db {
let cur = m.at(comb(rr, mb), comb(c, mb));
m.set(comb(rr, mb), comb(c, mb), cur + v);
}
}
}
}
for c in 0..db {
for rr in 0..db {
let v = b.sp[i].at(rr, c);
if v != 0.0 {
for ma in 0..da {
let cur = m.at(comb(ma, rr), comb(ma, c));
m.set(comb(ma, rr), comb(ma, c), cur + v);
}
}
}
}
sp.push(m);
}
let mut sz = Vec::with_capacity(r);
for i in 0..r {
let mut diag = vec![0.0; d];
for mb in 0..db {
for ma in 0..da {
diag[comb(ma, mb)] = a.sz[i][ma] + b.sz[i][mb];
}
}
sz.push(diag);
}
Ok(Generators {
series: a.series,
rank: r,
dim: d,
sp,
sz,
})
}
fn check_counts(&self) -> Result<(), SweepError> {
let (np, nz) = (self.sp.len(), self.sz.len());
if nz == 0 || np > nz || np != self.rank || nz != self.rank {
return Err(SweepError::InvalidGeneratorCounts {
np,
nz,
rank: self.rank,
});
}
Ok(())
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct Block {
irrep: Irrep,
cgc: Dense,
gens: Generators,
z: Dense,
om: (usize, usize),
}
impl Block {
pub fn irrep(&self) -> &Irrep {
&self.irrep
}
pub fn dim(&self) -> usize {
self.irrep.dim().try_into().unwrap_or(usize::MAX)
}
pub fn cgc(&self) -> &[f64] {
&self.cgc.data
}
pub fn cgc_shape(&self) -> (usize, usize) {
(self.cgc.rows, self.cgc.cols)
}
pub fn generators(&self) -> &Generators {
&self.gens
}
pub fn weight(&self, s: usize, j: usize) -> f64 {
self.z.at(s, j)
}
pub fn outer_multiplicity(&self) -> (usize, usize) {
self.om
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct Decomposition {
blocks: Vec<Block>,
}
impl Decomposition {
pub fn blocks(&self) -> &[Block] {
&self.blocks
}
pub fn multiplicities(&self) -> BTreeMap<Irrep, u32> {
let mut m = BTreeMap::new();
for b in &self.blocks {
*m.entry(b.irrep.clone()).or_insert(0) += 1;
}
m
}
}
pub fn decompose_defining_product(a: &Seed, b: &Seed) -> Result<Decomposition, SweepError> {
let ga = Generators::from_seed(a);
let gb = Generators::from_seed(b);
let prod = Generators::product(&ga, &gb)?;
let mut dynkin = vec![0i64; a.rank()];
dynkin[0] = 1;
let ia = Irrep::from_dynkin(a.series(), &dynkin).map_err(|_| {
SweepError::InvalidDiscoveredLabel {
dynkin: dynkin.clone(),
}
})?;
let expected = directproduct(&ia, &ia).map_err(|_| SweepError::GeneratorMismatch)?;
decompose(&prod, &expected)
}
pub fn decompose(
product: &Generators,
expected: &BTreeMap<Irrep, u32>,
) -> Result<Decomposition, SweepError> {
let blocks = get_symmetry_states(product)?;
let found: BTreeMap<Irrep, u32> = {
let mut m = BTreeMap::new();
for b in &blocks {
*m.entry(b.irrep.clone()).or_insert(0) += 1;
}
m
};
for (c, &n) in expected {
let got = found.get(c).copied().unwrap_or(0);
if got != n {
return Err(SweepError::MultiplicityMismatch {
dynkin: c.dynkin(),
expected: n,
found: got,
});
}
}
for (c, &got) in &found {
if expected.get(c).copied().unwrap_or(0) != got {
return Err(SweepError::MultiplicityMismatch {
dynkin: c.dynkin(),
expected: expected.get(c).copied().unwrap_or(0),
found: got,
});
}
}
Ok(Decomposition { blocks })
}
fn get_symmetry_states(g: &Generators) -> Result<Vec<Block>, SweepError> {
g.check_counts()?;
let d = g.dim;
let r = g.rank;
let nz = g.sz.len();
let mut u = Dense::zeros(d, 0);
let mut multiplets: Vec<Dense> = Vec::new();
let mut i0: usize = 0;
let mut it: usize = 0;
while it < d {
let mut v0 = Dense::zeros(d, 1);
let mut have_seed = false;
while i0 < d {
let seed = Dense::unit(d, i0);
if i0 == 0 && it == 0 {
v0 = seed;
have_seed = true;
break;
}
let proj = tmatmul(&u, &seed)?;
if (proj.norm() - 1.0).abs() < EPS_SWEEP {
i0 += 1;
continue;
}
v0 = seed;
have_seed = true;
break;
}
if !have_seed {
break;
}
if it > 0 {
project_out(&u, &mut v0)?;
normalize(&mut v0);
project_out(&u, &mut v0)?;
normalize(&mut v0);
}
for (j, szj) in g.sz.iter().enumerate() {
let mut vi = v0.clone();
apply_diag(szj, &mut vi);
if vi.norm() < EPS_SWEEP {
continue;
}
if !parallel(&vi, &v0) {
return Err(SweepError::SeedNotWeightVector {
multiplet: it,
cartan: j,
});
}
}
let mut found = true;
while found {
found = false;
for spi in &g.sp {
let mut vi = apply(spi, &v0)?;
let x = vi.norm();
if x > EPS_SWEEP {
scale(&mut vi, 1.0 / x);
v0 = vi;
found = true;
}
}
}
let mut vblock = v0.clone(); let mut frontier = v0; loop {
let mut level = Dense::zeros(d, 0); let mut any = false;
for spi in &g.sp {
let mut vi = apply_dagger(spi, &frontier)?;
if col_rms(&vi) < EPS_SWEEP {
continue;
}
project_out(&level, &mut vi)?;
let ov = overlap(&vblock, &vi)?;
if ov > EPS_SWEEP {
return Err(SweepError::OverlapWithVspace { residual: ov });
}
project_out(&vblock, &mut vi)?;
let ou = overlap(&u, &vi)?;
if ou > EPS_SWEEP {
return Err(SweepError::OverlapWithUspace { residual: ou });
}
project_out(&u, &mut vi)?;
vi = skip_tiny_cols(&vi, EPS_SWEEP);
if vi.cols == 0 {
continue;
}
vi = qr_positive_q(&vi, CG_EPS1)?;
project_out(&u, &mut vi)?;
project_out(&vblock, &mut vi)?;
project_out(&level, &mut vi)?;
vi = qr_positive_q(&vi, CG_EPS1)?;
level.cat_cols(&vi);
if level.cols > d {
return Err(SweepError::SpaceOutOfBounds);
}
any = true;
}
if any {
vblock.cat_cols(&level);
if vblock.cols > d {
return Err(SweepError::SpaceOutOfBounds);
}
frontier = level;
} else {
break;
}
}
u.cat_cols(&vblock);
if u.cols > d {
return Err(SweepError::SpaceOutOfBounds);
}
multiplets.push(vblock);
it += 1;
if u.cols == d {
break;
}
}
let nt = multiplets.len();
if u.cols != d || nt == 0 {
return Err(SweepError::IncompleteDecomposition {
dim: d,
covered: u.cols,
});
}
{
let utu = tmatmul(&u, &u)?;
let mut worst = 0.0f64;
for i in 0..utu.rows {
for j in 0..utu.cols {
let target = if i == j { 1.0 } else { 0.0 };
worst = worst.max((utu.at(i, j) - target).abs());
}
}
if worst > EPS_VERIFY {
return Err(SweepError::NotOrthonormal { residual: worst });
}
}
let mut blocks: Vec<Block> = Vec::with_capacity(nt);
for (bi, v) in multiplets.into_iter().enumerate() {
let d0 = v.cols;
let mut rsp: Vec<Dense> = Vec::with_capacity(r);
for spi in &g.sp {
let spv = apply(spi, &v)?;
rsp.push(tmatmul(&v, &spv)?);
}
let mut rsz_diag: Vec<Vec<f64>> = Vec::with_capacity(nz);
let mut zmat = Dense::zeros(d0, nz);
for (j, szj) in g.sz.iter().enumerate() {
let mut szv = v.clone();
apply_diag(szj, &mut szv);
let rszj = tmatmul(&v, &szv)?; let mut worst = 0.0f64;
for a in 0..d0 {
for b in 0..d0 {
if a != b {
worst = worst.max(rszj.at(a, b).abs());
}
}
}
if worst > EPS_VERIFY {
return Err(SweepError::NonDiagonalCartan {
block: bi,
residual: worst,
});
}
let diag: Vec<f64> = (0..d0).map(|a| snap_int(rszj.at(a, a))).collect();
for (a, &val) in diag.iter().enumerate() {
if (val - rszj.at(a, a)).abs() > FIXRATIONAL_TOL {
return Err(SweepError::NonIntegerWeight {
block: bi,
value: rszj.at(a, a),
});
}
zmat.set(a, j, val);
}
rsz_diag.push(diag);
}
let (irrep, perm) = find_max_weight(g.series, r, &zmat, bi)?;
let v_sorted = permute_cols(&v, &perm);
let z_sorted = permute_rows(&zmat, &perm);
let rsp_sorted: Vec<Dense> = rsp.iter().map(|m| permute_both(m, &perm)).collect();
let rsz_sorted: Vec<Vec<f64>> = rsz_diag
.iter()
.map(|diag| perm.iter().map(|&p| diag[p]).collect())
.collect();
let mut v_signed = v_sorted;
range_sign_convention(&mut v_signed.data);
for x in v_signed.data.iter_mut() {
*x = snap_int(*x);
}
let residual = commutator_residual(&rsp_sorted, &rsz_sorted, d0);
if residual > EPS_SWEEP {
return Err(SweepError::CommutatorResidual {
block: bi,
residual,
});
}
let gens = Generators {
series: g.series,
rank: r,
dim: d0,
sp: rsp_sorted,
sz: rsz_sorted,
};
blocks.push(Block {
irrep,
cgc: v_signed,
gens,
z: z_sorted,
om: (0, 1), });
}
assign_outer_multiplicity(&mut blocks);
Ok(blocks)
}
fn find_max_weight(
series: Series,
r: usize,
z: &Dense,
block: usize,
) -> Result<(Irrep, Vec<usize>), SweepError> {
let d0 = z.rows;
let nz = z.cols;
let perm = descending_weight_perm(z);
let k = perm[0];
if d0 > 1 {
let k2 = perm[1];
let diff2: f64 = (0..nz).map(|c| (z.at(k, c) - z.at(k2, c)).powi(2)).sum();
if diff2 <= EPS_MW_UNIQUE {
return Err(SweepError::MaxWeightNotUnique { block });
}
}
let qm: Vec<f64> = (0..nz).map(|c| z.at(k, c)).collect();
let dynkin = to_dynkin(series, r, &qm, block)?;
let irrep = Irrep::from_dynkin(series, &dynkin)
.map_err(|_| SweepError::InvalidDiscoveredLabel { dynkin })?;
Ok((irrep, perm))
}
fn descending_weight_perm(z: &Dense) -> Vec<usize> {
let nz = z.cols;
let mut perm: Vec<usize> = (0..z.rows).collect();
perm.sort_by(|&a, &b| {
for c in (0..nz).rev() {
match z
.at(b, c)
.partial_cmp(&z.at(a, c))
.unwrap_or(std::cmp::Ordering::Equal)
{
std::cmp::Ordering::Equal => {}
ord => return ord,
}
}
a.cmp(&b) });
perm
}
fn to_dynkin(series: Series, r: usize, qm: &[f64], block: usize) -> Result<Vec<i64>, SweepError> {
let int = |x: f64| -> Result<i64, SweepError> {
let q = x.round();
if (x - q).abs() > FIXRATIONAL_TOL {
return Err(SweepError::NonIntegerWeight { block, value: x });
}
Ok(q as i64)
};
let mut q = qm.to_vec();
match series {
Series::C => {
let mut out = vec![0i64; r];
for i in (1..r).rev() {
out[i] = int((q[i] - q[i - 1]) / ((i + 1) as f64))?;
}
out[0] = int(q[0])?;
Ok(out)
}
Series::B => {
let l = (r - 1) / 2;
let x = int(2.0 * q[0])?;
let mut out = vec![0i64; r];
for i in 1..r {
out[i - 1] = int(q[i] - q[i - 1])?;
}
out[r - 1] = x;
for i in 0..l {
out.swap(i, r - 2 - i);
}
Ok(out)
}
Series::D => {
let l = (r - 1) / 2;
let x = int(q[0] + q[1])?;
let mut out = vec![0i64; r];
for i in 1..r {
out[i - 1] = int(q[i] - q[i - 1])?;
}
out[r - 1] = x;
for i in 0..l {
out.swap(i, r - 2 - i);
}
let _ = &mut q;
Ok(out)
}
}
}
fn sign_first_val(d: &[f64]) -> i32 {
for &x in d {
if x.abs() > CG_EPS1 {
return if x < 0.0 { -1 } else { 1 };
}
}
1
}
fn range_sign_convention(d: &mut [f64]) {
if sign_first_val(d) < 0 {
for x in d.iter_mut() {
*x = -*x;
}
}
}
fn assign_outer_multiplicity(blocks: &mut [Block]) {
let mut counts: BTreeMap<Irrep, usize> = BTreeMap::new();
for b in blocks.iter() {
*counts.entry(b.irrep.clone()).or_insert(0) += 1;
}
let mut seen: BTreeMap<Irrep, usize> = BTreeMap::new();
for b in blocks.iter_mut() {
let size = counts[&b.irrep];
let idx = seen.entry(b.irrep.clone()).or_insert(0);
b.om = (*idx, size);
*idx += 1;
}
}
fn commutator_residual(sp: &[Dense], sz_diag: &[Vec<f64>], d0: usize) -> f64 {
let r = sp.len();
let mut worst = 0.0f64;
let szd: Vec<Dense> = sz_diag
.iter()
.map(|diag| {
let mut m = Dense::zeros(d0, d0);
for (a, &v) in diag.iter().enumerate() {
m.set(a, a, v);
}
m
})
.collect();
for spi in sp.iter() {
for szj in szd.iter() {
let c = dense_commutator(szj, spi);
let (mut br, mut bc, mut bv) = (0usize, 0usize, 0.0f64);
for a in 0..d0 {
for b in 0..d0 {
if spi.at(a, b).abs() > bv.abs() {
bv = spi.at(a, b);
br = a;
bc = b;
}
}
}
if bv.abs() < EPS_SWEEP {
continue;
}
let dz = c.at(br, bc) / bv;
for a in 0..d0 {
for b in 0..d0 {
worst = worst.max((c.at(a, b) - dz * spi.at(a, b)).abs());
}
}
}
}
for spi in sp.iter() {
let spt = spi.transpose();
let comm = dense_commutator(spi, &spt);
let mut recon = Dense::zeros(d0, d0);
for szj in szd.iter() {
let mut num = 0.0;
let mut den = 0.0;
for a in 0..d0 {
num += comm.at(a, a) * szj.at(a, a);
den += szj.at(a, a) * szj.at(a, a);
}
let f = if den > EPS_SWEEP { num / den } else { 0.0 };
for a in 0..d0 {
let cur = recon.at(a, a);
recon.set(a, a, cur + f * szj.at(a, a));
}
}
for a in 0..d0 {
for b in 0..d0 {
worst = worst.max((comm.at(a, b) - recon.at(a, b)).abs());
}
}
}
let _ = r;
worst
}
fn dense_commutator(a: &Dense, b: &Dense) -> Dense {
let d = a.rows;
let mut c = Dense::zeros(d, d);
for i in 0..d {
for k in 0..d {
let aik = a.at(i, k);
let bik = b.at(i, k);
if aik == 0.0 && bik == 0.0 {
continue;
}
for j in 0..d {
let cur = c.at(i, j);
c.set(i, j, cur + aik * b.at(k, j) - bik * a.at(k, j));
}
}
}
c
}
fn apply(sp: &Dense, x: &Dense) -> Result<Dense, SweepError> {
let d = sp.rows;
let mut out = Dense::zeros(d, x.cols);
for i in 0..d {
for k in 0..d {
let v = sp.at(i, k);
if v == 0.0 {
continue;
}
for c in 0..x.cols {
let cur = out.at(i, c);
out.set(i, c, cur + v * x.at(k, c));
}
}
}
Ok(out)
}
fn apply_dagger(sp: &Dense, x: &Dense) -> Result<Dense, SweepError> {
let d = sp.rows;
let mut out = Dense::zeros(d, x.cols);
for k in 0..d {
for i in 0..d {
let v = sp.at(k, i); if v == 0.0 {
continue;
}
for c in 0..x.cols {
let cur = out.at(i, c);
out.set(i, c, cur + v * x.at(k, c));
}
}
}
Ok(out)
}
fn apply_diag(diag: &[f64], x: &mut Dense) {
for c in 0..x.cols {
for (i, &d) in diag.iter().enumerate() {
let cur = x.at(i, c);
x.set(i, c, cur * d);
}
}
}
fn project_out(q: &Dense, x: &mut Dense) -> Result<(), SweepError> {
if q.cols == 0 {
return Ok(());
}
let qtx = tmatmul(q, x)?; let qqtx = matmul(q, &qtx)?; for i in 0..x.data.len() {
x.data[i] -= qqtx.data[i];
}
Ok(())
}
fn overlap(q: &Dense, x: &Dense) -> Result<f64, SweepError> {
if q.cols == 0 {
return Ok(0.0);
}
let qtx = tmatmul(q, x)?;
Ok(qtx.data.iter().fold(0.0f64, |m, &v| m.max(v.abs())))
}
fn normalize(v: &mut Dense) {
let n = v.norm();
if n > 0.0 {
scale(v, 1.0 / n);
}
}
fn scale(v: &mut Dense, s: f64) {
for x in v.data.iter_mut() {
*x *= s;
}
}
fn col_rms(x: &Dense) -> f64 {
if x.cols == 0 {
return 0.0;
}
(x.data.iter().map(|v| v * v).sum::<f64>() / x.cols as f64).sqrt()
}
fn parallel(a: &Dense, b: &Dense) -> bool {
let na = a.norm();
let nb = b.norm();
if na < EPS_SWEEP || nb < EPS_SWEEP {
return true; }
let dot: f64 = a.data.iter().zip(&b.data).map(|(x, y)| x * y).sum();
(na * nb - dot.abs()).abs() < EPS_SWEEP * na * nb
}
fn skip_tiny_cols(x: &Dense, eps: f64) -> Dense {
let keep: Vec<usize> = (0..x.cols)
.filter(|&j| x.col(j).iter().map(|v| v * v).sum::<f64>().sqrt() >= eps)
.collect();
x.select_cols(&keep)
}
fn permute_cols(m: &Dense, perm: &[usize]) -> Dense {
m.select_cols(perm)
}
fn permute_rows(m: &Dense, perm: &[usize]) -> Dense {
let mut out = Dense::zeros(m.rows, m.cols);
for (ro, &r) in perm.iter().enumerate() {
for c in 0..m.cols {
out.set(ro, c, m.at(r, c));
}
}
out
}
fn permute_both(m: &Dense, perm: &[usize]) -> Dense {
let mut out = Dense::zeros(m.rows, m.cols);
for (ro, &r) in perm.iter().enumerate() {
for (co, &c) in perm.iter().enumerate() {
out.set(ro, co, m.at(r, c));
}
}
out
}
fn snap_int(x: f64) -> f64 {
let q = x.round();
if (x - q).abs() <= FIXRATIONAL_TOL {
q
} else {
x
}
}
const ALIGN_COUPLING_TOL: f64 = 1e-6;
fn identity(d: usize) -> Dense {
let mut m = Dense::zeros(d, d);
for i in 0..d {
m.set(i, i, 1.0);
}
m
}
pub(crate) fn align_block(
block: &Block,
canonical: &Generators,
) -> Result<(Dense, f64), SweepError> {
let w = intertwiner(&block.gens, canonical)?;
let aligned = conjugate_generators(&block.gens, &w)?;
let residual = aligned.coherence_residual(canonical);
let mut cgc = matmul(&block.cgc, &w.transpose())?;
range_sign_convention(&mut cgc.data);
for x in cgc.data.iter_mut() {
*x = snap_int(*x);
}
Ok((cgc, residual))
}
fn conjugate_generators(g: &Generators, w: &Dense) -> Result<Generators, SweepError> {
let wt = w.transpose();
let mut sp = Vec::with_capacity(g.rank);
for spi in &g.sp {
let ws = matmul(w, spi)?;
sp.push(matmul(&ws, &wt)?);
}
Ok(Generators {
series: g.series,
rank: g.rank,
dim: g.dim,
sp,
sz: g.sz.clone(),
})
}
fn intertwiner(block: &Generators, canonical: &Generators) -> Result<Dense, SweepError> {
let d = canonical.dim;
let r = canonical.rank;
let nz = canonical.sz.len();
let w = identity(d);
if block.dim != d || block.rank != r || nz == 0 {
return Ok(w);
}
let weight = |g: &Generators, s: usize| -> Vec<i64> {
(0..nz).map(|j| g.sz[j][s].round() as i64).collect()
};
for s in 0..d {
if weight(block, s) != weight(canonical, s) {
return Ok(w);
}
}
let mut spaces: Vec<Vec<usize>> = Vec::new();
let mut space_of: HashMap<Vec<i64>, usize> = HashMap::new();
let mut state_space = vec![0usize; d];
#[allow(clippy::needless_range_loop)]
for s in 0..d {
let key = weight(canonical, s);
let idx = *space_of.entry(key).or_insert_with(|| {
spaces.push(Vec::new());
spaces.len() - 1
});
spaces[idx].push(s);
state_space[s] = idx;
}
let mut w = w;
let mut wblocks: Vec<Option<Dense>> = vec![None; spaces.len()];
for ti in 0..spaces.len() {
let target = &spaces[ti];
let n_t = target.len();
let mut c_cols: Vec<f64> = Vec::new();
let mut b_cols: Vec<f64> = Vec::new();
let mut cols = 0usize;
for i in 0..r {
let mut src: Option<usize> = None;
'find: for &t in target {
#[allow(clippy::needless_range_loop)]
for s in 0..d {
if canonical.sp[i].at(s, t).abs() > ALIGN_COUPLING_TOL {
src = Some(state_space[s]);
break 'find;
}
}
}
let Some(si) = src else { continue };
let Some(ws) = wblocks[si].clone() else {
continue;
};
let source = &spaces[si];
let n_s = source.len();
let mut a = Dense::zeros(n_t, n_s);
let mut bmat = Dense::zeros(n_t, n_s);
for (tl, &t) in target.iter().enumerate() {
for (sl, &s) in source.iter().enumerate() {
a.set(tl, sl, canonical.sp[i].at(s, t));
bmat.set(tl, sl, block.sp[i].at(s, t));
}
}
let c = matmul(&a, &ws)?;
c_cols.extend_from_slice(&c.data);
b_cols.extend_from_slice(&bmat.data);
cols += n_s;
}
let wblock = if cols == 0 {
identity(n_t)
} else {
let cmat = Dense {
rows: n_t,
cols,
data: c_cols,
};
let bmat = Dense {
rows: n_t,
cols,
data: b_cols,
};
let m = matmul(&cmat, &bmat.transpose())?;
let (u, _s, vt) = svd(&m)?;
matmul(&u, &vt)?
};
for (tl, &t) in target.iter().enumerate() {
for (ul, &u2) in target.iter().enumerate() {
w.set(t, u2, wblock.at(tl, ul));
}
}
wblocks[ti] = Some(wblock);
}
Ok(w)
}
#[cfg(test)]
mod tests;