use std::{collections::HashMap, fmt::Debug, sync::Arc};
use nalgebra::DMatrix;
use crate::quiver_algebra::{
checked_arith::{Field, Ring},
quiver::BasisElt,
quiver_with_mon_rels::{NonMonomialIdeal, QuiverWithMonomialRelations},
quiver_with_rels::QuiverWithRelations,
};
#[derive(Clone, Debug, PartialEq)]
pub struct PeirceElement<V, Coeffs> {
pub left: V,
pub right: V,
pub coords: Vec<Coeffs>,
}
impl<V: Clone, Coeffs: Field> PeirceElement<V, Coeffs> {
pub fn zero(left: V, right: V, dim: usize) -> Self {
Self {
left,
right,
coords: vec![Coeffs::zero(); dim],
}
}
pub fn basis_vec(left: V, right: V, dim: usize, i: usize) -> Self {
let mut coords = vec![Coeffs::zero(); dim];
coords[i] = Coeffs::one();
Self {
left,
right,
coords,
}
}
pub fn is_zero(&self) -> bool {
self.coords.iter().all(Coeffs::is_zero)
}
pub fn add_assign(&mut self, other: &Self) {
debug_assert_eq!(self.coords.len(), other.coords.len());
for (s, o) in self.coords.iter_mut().zip(&other.coords) {
*s += o.clone();
}
}
pub fn scale_assign(&mut self, c: &Coeffs) {
for coord in &mut self.coords {
*coord *= c.clone();
}
}
}
pub trait QuiverBimodule<V, E, Coeffs, BasisLabel, const OP_ALG: bool>
where
V: std::hash::Hash + Eq + Clone,
E: Eq + std::hash::Hash + Clone,
Coeffs: Ring,
{
fn algebra(&self) -> &Arc<QuiverWithRelations<V, E, Coeffs, OP_ALG>>;
fn peirce_basis(&self, v: &V, w: &V) -> &[BasisLabel];
fn peirce_dim(&self, v: &V, w: &V) -> usize {
self.peirce_basis(v, w).len()
}
fn left_act(&self, alpha: &E, elt: &PeirceElement<V, Coeffs>) -> PeirceElement<V, Coeffs>;
fn right_act(&self, beta: &E, elt: &PeirceElement<V, Coeffs>) -> PeirceElement<V, Coeffs>;
fn left_act_algebra_elt(
&self,
b: &BasisElt<V, E>,
elt: &PeirceElement<V, Coeffs>,
) -> PeirceElement<V, Coeffs>
where
Coeffs: Field,
{
let expected_right = elt.right.clone();
let (expected_left, _expected_middle) = self
.algebra()
.quiver()
.basis_endpoints(b)
.expect("This is a path in the quiver");
let to_return = match b {
BasisElt::Idempotent(v) => {
if &elt.left == v {
elt.clone()
} else {
PeirceElement::zero(
v.clone(),
elt.right.clone(),
self.peirce_dim(v, &elt.right),
)
}
}
BasisElt::Path(word) => {
let mut m = elt.clone();
for arrow in word.iter().rev() {
m = self.left_act(arrow, &m);
}
m
}
};
debug_assert!(to_return.left == expected_left);
debug_assert!(to_return.right == expected_right);
to_return
}
fn right_act_algebra_elt(
&self,
b: &BasisElt<V, E>,
elt: &PeirceElement<V, Coeffs>,
) -> PeirceElement<V, Coeffs>
where
Coeffs: Field,
{
let expected_left = elt.left.clone();
let (_expected_middle, expected_right) = self
.algebra()
.quiver()
.basis_endpoints(b)
.expect("This is a path in the quiver");
let to_return = match b {
BasisElt::Idempotent(v) => {
if &elt.right == v {
elt.clone()
} else {
PeirceElement::zero(elt.left.clone(), v.clone(), self.peirce_dim(&elt.left, v))
}
}
BasisElt::Path(word) => {
let mut m = elt.clone();
for arrow in word.iter() {
m = self.right_act(arrow, &m);
}
m
}
};
debug_assert!(to_return.left == expected_left);
debug_assert!(to_return.right == expected_right);
to_return
}
fn check_bimodule_axioms(&self) -> Vec<BimoduleAxiomViolation<V, E>>
where
V: Debug,
E: Debug,
Coeffs: Field,
BasisLabel: Clone,
{
let mut violations = Vec::new();
let algebra = self.algebra();
let quiver = algebra.quiver();
for (rel_idx, rel) in algebra.relations().enumerate() {
let Some((src_rel, tgt_rel)) = rel.all_parallel()
.expect("The relations are well formed. Combining r1 + r2 with different endpoints, actually means r1 and r2 should be in the generators of the ideal separately") else {
continue;
};
for w in quiver.vertex_labels() {
let dim_in = self.peirce_dim(&tgt_rel, w);
let dim_out = self.peirce_dim(&src_rel, w);
'basis: for i in 0..dim_in {
let ei = PeirceElement::basis_vec(tgt_rel.clone(), w.clone(), dim_in, i);
let mut sum = PeirceElement::zero(src_rel.clone(), w.clone(), dim_out);
for (basis_elt, coeff) in rel.iter() {
if coeff.is_zero() {
continue;
}
let mut term = self.left_act_algebra_elt(basis_elt, &ei);
term.scale_assign(coeff);
sum.add_assign(&term);
}
if !sum.is_zero() {
violations.push(BimoduleAxiomViolation::LeftRelationFails {
relation_index: rel_idx,
right_vertex: w.clone(),
});
break 'basis;
}
}
}
}
for (rel_idx, rel) in algebra.relations().enumerate() {
let Some((src_rel, tgt_rel)) = rel.all_parallel()
.expect("The relations are well formed. Combining r1 + r2 with different endpoints, actually means r1 and r2 should be in the generators of the ideal separately") else {
continue;
};
for v in quiver.vertex_labels() {
let dim_in = self.peirce_dim(v, &src_rel);
let dim_out = self.peirce_dim(v, &tgt_rel);
'basis: for i in 0..dim_in {
let ei = PeirceElement::basis_vec(v.clone(), src_rel.clone(), dim_in, i);
let mut sum = PeirceElement::zero(v.clone(), tgt_rel.clone(), dim_out);
for (basis_elt, coeff) in rel.iter() {
if coeff.is_zero() {
continue;
}
let mut term = self.right_act_algebra_elt(basis_elt, &ei);
term.scale_assign(coeff);
sum.add_assign(&term);
}
if !sum.is_zero() {
violations.push(BimoduleAxiomViolation::RightRelationFails {
relation_index: rel_idx,
left_vertex: v.clone(),
});
break 'basis;
}
}
}
}
for alpha in quiver.edge_labels() {
let Some((_src_a, tgt_a)) = quiver.edge_endpoint_labels(alpha) else {
continue;
};
for beta in quiver.edge_labels() {
let Some((src_b, _tgt_b)) = quiver.edge_endpoint_labels(beta) else {
continue;
};
let domain_basis_len = self.peirce_dim(&tgt_a, &src_b);
'basis: for i in 0..domain_basis_len {
let b =
PeirceElement::basis_vec(tgt_a.clone(), src_b.clone(), domain_basis_len, i);
let lhs = self.left_act(alpha, &self.right_act(beta, &b));
let rhs = self.right_act(beta, &self.left_act(alpha, &b));
if lhs != rhs {
violations.push(BimoduleAxiomViolation::CommutativityFails {
left_arrow: alpha.clone(),
right_arrow: beta.clone(),
});
break 'basis;
}
}
}
}
violations
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum BimoduleAxiomViolation<V, E> {
LeftRelationFails {
relation_index: usize,
right_vertex: V,
},
RightRelationFails {
relation_index: usize,
left_vertex: V,
},
CommutativityFails { left_arrow: E, right_arrow: E },
}
#[derive(Debug, Clone, PartialEq)]
pub enum DiagonalBimoduleError {
NonMonomialRelations(NonMonomialIdeal),
TooManyPaths { max_paths: usize },
}
pub struct DiagonalBimodule<V, E, Coeffs, const OP_ALG: bool>
where
V: std::hash::Hash + Eq + Clone,
E: Eq + std::hash::Hash + Clone,
Coeffs: Ring,
{
algebra: Arc<QuiverWithRelations<V, E, Coeffs, OP_ALG>>,
basis_by_pair: HashMap<(V, V), Vec<Vec<E>>>,
left_matrices: HashMap<(E, V), DMatrix<bool>>,
right_matrices: HashMap<(E, V), DMatrix<bool>>,
}
impl<V, E, Coeffs, const OP_ALG: bool> DiagonalBimodule<V, E, Coeffs, OP_ALG>
where
V: std::hash::Hash + Eq + Clone + Debug,
E: std::hash::Hash + Eq + Clone + Debug,
Coeffs: Field,
{
pub fn try_new(
algebra: Arc<QuiverWithRelations<V, E, Coeffs, OP_ALG>>,
max_paths: usize,
) -> Result<Self, DiagonalBimoduleError> {
let mon: QuiverWithMonomialRelations<V, E> = (&*algebra)
.try_into()
.map_err(DiagonalBimoduleError::NonMonomialRelations)?;
let (basis_by_pair, left_matrices, right_matrices) =
build_action_matrices(&mon, max_paths)?;
Ok(Self {
algebra,
basis_by_pair,
left_matrices,
right_matrices,
})
}
}
impl<V, E, Coeffs, const OP_ALG: bool> QuiverBimodule<V, E, Coeffs, Vec<E>, OP_ALG>
for DiagonalBimodule<V, E, Coeffs, OP_ALG>
where
V: std::hash::Hash + Eq + Clone,
E: std::hash::Hash + Eq + Clone,
Coeffs: Field,
{
fn algebra(&self) -> &Arc<QuiverWithRelations<V, E, Coeffs, OP_ALG>> {
&self.algebra
}
fn peirce_basis(&self, v: &V, w: &V) -> &[Vec<E>] {
self.basis_by_pair
.get(&(v.clone(), w.clone()))
.map_or(&[], Vec::as_slice)
}
fn left_act(&self, alpha: &E, elt: &PeirceElement<V, Coeffs>) -> PeirceElement<V, Coeffs> {
let Some((src_a, tgt_a)) = self.algebra.quiver().edge_endpoint_labels(alpha) else {
return PeirceElement::zero(elt.left.clone(), elt.right.clone(), 0);
};
let out_dim = self.peirce_dim(&src_a, &elt.right);
if elt.left != tgt_a {
return PeirceElement::zero(src_a, elt.right.clone(), out_dim);
}
let mat = &self.left_matrices[&(alpha.clone(), elt.right.clone())];
let mut out = vec![Coeffs::zero(); out_dim];
for j in 0..elt.coords.len() {
if elt.coords[j].is_zero() {
continue;
}
for i in 0..out_dim {
if mat[(i, j)] {
out[i] += elt.coords[j].clone();
}
}
}
PeirceElement {
left: src_a,
right: elt.right.clone(),
coords: out,
}
}
fn right_act(&self, beta: &E, elt: &PeirceElement<V, Coeffs>) -> PeirceElement<V, Coeffs> {
let Some((src_b, tgt_b)) = self.algebra.quiver().edge_endpoint_labels(beta) else {
return PeirceElement::zero(elt.left.clone(), elt.right.clone(), 0);
};
let out_dim = self.peirce_dim(&elt.left, &tgt_b);
if elt.right != src_b {
return PeirceElement::zero(elt.left.clone(), tgt_b, out_dim);
}
let mat = &self.right_matrices[&(beta.clone(), elt.left.clone())];
let mut out = vec![Coeffs::zero(); out_dim];
for j in 0..elt.coords.len() {
if elt.coords[j].is_zero() {
continue;
}
for i in 0..out_dim {
if mat[(i, j)] {
out[i] += elt.coords[j].clone();
}
}
}
PeirceElement {
left: elt.left.clone(),
right: tgt_b,
coords: out,
}
}
}
#[allow(clippy::type_complexity)]
fn build_action_matrices<V, E>(
algebra: &QuiverWithMonomialRelations<V, E>,
max_paths: usize,
) -> Result<
(
HashMap<(V, V), Vec<Vec<E>>>,
HashMap<(E, V), DMatrix<bool>>,
HashMap<(E, V), DMatrix<bool>>,
),
DiagonalBimoduleError,
>
where
V: std::hash::Hash + Eq + Clone + Debug,
E: std::hash::Hash + Eq + Clone + Debug,
{
use std::collections::HashSet;
let all_vertices: Vec<V> = algebra.vertices().collect();
let all_edges: Vec<E> = algebra.edge_labels().collect();
let mut basis_by_pair: HashMap<(V, V), Vec<Vec<E>>> = HashMap::new();
let mut basis_idx: HashMap<(V, V), HashMap<Vec<E>, usize>> = HashMap::new();
for v in &all_vertices {
let pair = (v.clone(), v.clone());
basis_by_pair.entry(pair.clone()).or_default().push(vec![]);
basis_idx.entry(pair).or_default().insert(vec![], 0);
}
let mut seen: HashSet<Vec<E>> = HashSet::new();
let mut frontier: Vec<Vec<E>> = Vec::new();
let mut total_paths = 0usize;
for e in &all_edges {
let word = vec![e.clone()];
if !algebra.is_zero_path_word(&word) && seen.insert(word.clone()) {
if let Some((src, tgt)) = algebra.path_source_target(&word) {
let pair = (src, tgt);
let idx_map = basis_idx.entry(pair.clone()).or_default();
let paths = basis_by_pair.entry(pair).or_default();
idx_map.insert(word.clone(), paths.len());
paths.push(word.clone());
total_paths += 1;
}
frontier.push(word);
}
}
while let Some(path) = frontier.pop() {
if total_paths > max_paths {
return Err(DiagonalBimoduleError::TooManyPaths { max_paths });
}
for next_e in &all_edges {
let mut new_path = path.clone();
new_path.push(next_e.clone());
if seen.contains(&new_path) {
continue;
}
if !algebra.is_zero_path_word(&new_path) {
seen.insert(new_path.clone());
if let Some((src, tgt)) = algebra.path_source_target(&new_path) {
let pair = (src, tgt);
let idx_map = basis_idx.entry(pair.clone()).or_default();
let paths = basis_by_pair.entry(pair).or_default();
idx_map.insert(new_path.clone(), paths.len());
paths.push(new_path.clone());
total_paths += 1;
}
frontier.push(new_path);
}
}
}
let empty_basis: Vec<Vec<E>> = Vec::new();
let mut left_matrices: HashMap<(E, V), DMatrix<bool>> = HashMap::new();
let mut right_matrices: HashMap<(E, V), DMatrix<bool>> = HashMap::new();
for alpha in &all_edges {
let Some((src_a, tgt_a)) = algebra.edge_endpoint_labels(alpha) else {
continue;
};
for w in &all_vertices {
let dom_basis = basis_by_pair
.get(&(tgt_a.clone(), w.clone()))
.unwrap_or(&empty_basis);
let cod_pair = (src_a.clone(), w.clone());
let cod_idx = basis_idx.get(&cod_pair);
let nrows = basis_by_pair.get(&cod_pair).map_or(0, Vec::len);
let mut mat = DMatrix::<bool>::from_element(nrows, dom_basis.len(), false);
for (j, q) in dom_basis.iter().enumerate() {
let mut new_path = vec![alpha.clone()];
new_path.extend_from_slice(q);
if let Some(i) = cod_idx.and_then(|m| m.get(&new_path)).copied() {
mat[(i, j)] = true;
}
}
left_matrices.insert((alpha.clone(), w.clone()), mat);
}
}
for beta in &all_edges {
let Some((src_b, tgt_b)) = algebra.edge_endpoint_labels(beta) else {
continue;
};
for v in &all_vertices {
let dom_basis = basis_by_pair
.get(&(v.clone(), src_b.clone()))
.unwrap_or(&empty_basis);
let cod_pair = (v.clone(), tgt_b.clone());
let cod_idx = basis_idx.get(&cod_pair);
let nrows = basis_by_pair.get(&cod_pair).map_or(0, Vec::len);
let mut mat = DMatrix::from_element(nrows, dom_basis.len(), false);
for (j, q) in dom_basis.iter().enumerate() {
let mut new_path = q.clone();
new_path.push(beta.clone());
if let Some(i) = cod_idx.and_then(|m| m.get(&new_path)).copied() {
mat[(i, j)] = true;
}
}
right_matrices.insert((beta.clone(), v.clone()), mat);
}
}
Ok((basis_by_pair, left_matrices, right_matrices))
}
#[cfg(test)]
mod tests {
use std::sync::Arc;
use super::*;
use crate::quiver_algebra::path_algebra::PathAlgebra;
use crate::quiver_algebra::quiver::{BasisElt, Quiver, tests::make_kronecker_quiver};
fn make_a3_with_rel() -> Arc<QuiverWithRelations<&'static str, &'static str, f64, true>> {
let mut q = Quiver::new();
q.add_edge("0", "1", "a");
q.add_edge("1", "2", "b");
let q = Arc::new(q);
let rel = PathAlgebra::singleton(
q.clone(),
BasisElt::Path(nonempty::nonempty!["a", "b"]),
1.0_f64,
);
Arc::new(QuiverWithRelations::new(
q,
vec![rel],
Some(|x: &f64| *x == 0.0),
))
}
#[test]
fn a2_peirce_basis_dimensions() {
let q = crate::quiver_algebra::quiver::tests::make_a2_quiver();
let qwr =
Arc::new(QuiverWithRelations::<_, _, f64, true>::from_quiver_no_relations(Arc::new(q)));
let bim = DiagonalBimodule::try_new(qwr, 100).unwrap();
assert_eq!(bim.peirce_basis(&"alpha", &"alpha").len(), 1);
assert_eq!(bim.peirce_basis(&"alpha", &"beta").len(), 1);
assert_eq!(bim.peirce_basis(&"beta", &"alpha").len(), 0);
assert_eq!(bim.peirce_basis(&"beta", &"beta").len(), 1);
}
#[test]
fn a2_peirce_basis_labels() {
let q = crate::quiver_algebra::quiver::tests::make_a2_quiver();
let qwr =
Arc::new(QuiverWithRelations::<_, _, f64, true>::from_quiver_no_relations(Arc::new(q)));
let bim = DiagonalBimodule::try_new(qwr, 100).unwrap();
assert_eq!(bim.peirce_basis(&"alpha", &"alpha"), &[vec![] as Vec<&str>]);
assert_eq!(bim.peirce_basis(&"alpha", &"beta"), &[vec!["a"]]);
}
#[test]
fn a2_left_act_on_basis_element() {
let q = crate::quiver_algebra::quiver::tests::make_a2_quiver();
let qwr =
Arc::new(QuiverWithRelations::<_, _, f64, true>::from_quiver_no_relations(Arc::new(q)));
let bim = DiagonalBimodule::try_new(qwr, 100).unwrap();
let e_beta = PeirceElement::basis_vec("beta", "beta", 1, 0);
let result = bim.left_act(&"a", &e_beta);
assert_eq!(result.left, "alpha");
assert_eq!(result.right, "beta");
assert_eq!(result.coords, vec![1.0]);
}
#[test]
fn a2_right_act_on_basis_element() {
let q = crate::quiver_algebra::quiver::tests::make_a2_quiver();
let qwr =
Arc::new(QuiverWithRelations::<_, _, f64, true>::from_quiver_no_relations(Arc::new(q)));
let bim = DiagonalBimodule::try_new(qwr, 100).unwrap();
let e_alpha = PeirceElement::basis_vec("alpha", "alpha", 1, 0);
let result = bim.right_act(&"a", &e_alpha);
assert_eq!(result.left, "alpha");
assert_eq!(result.right, "beta");
assert_eq!(result.coords, vec![1.0]);
}
#[test]
fn left_act_wrong_peirce_index_returns_zero() {
let q = crate::quiver_algebra::quiver::tests::make_a2_quiver();
let qwr =
Arc::new(QuiverWithRelations::<_, _, f64, true>::from_quiver_no_relations(Arc::new(q)));
let bim = DiagonalBimodule::try_new(qwr, 100).unwrap();
let wrong = PeirceElement::basis_vec("alpha", "beta", 1, 0);
let result = bim.left_act(&"a", &wrong);
assert!(result.is_zero());
}
#[test]
fn a2_diagonal_satisfies_axioms() {
let q = crate::quiver_algebra::quiver::tests::make_a2_quiver();
let qwr =
Arc::new(QuiverWithRelations::<_, _, f64, true>::from_quiver_no_relations(Arc::new(q)));
let bim = DiagonalBimodule::try_new(qwr, 100).unwrap();
let v = bim.check_bimodule_axioms();
assert!(v.is_empty(), "{v:?}");
}
#[test]
fn kronecker_diagonal_satisfies_axioms() {
let qwr = Arc::new(
QuiverWithRelations::<_, _, f64, true>::from_quiver_no_relations(Arc::new(
make_kronecker_quiver(),
)),
);
let bim = DiagonalBimodule::try_new(qwr, 100).unwrap();
let v = bim.check_bimodule_axioms();
assert!(v.is_empty(), "{v:?}");
}
#[test]
fn a3_with_relation_satisfies_axioms() {
let qwr = make_a3_with_rel();
let bim = DiagonalBimodule::try_new(qwr, 100).unwrap();
let v = bim.check_bimodule_axioms();
assert!(v.is_empty(), "{v:?}");
}
#[test]
fn a3_with_rel_zero_peirce_piece() {
let qwr = make_a3_with_rel();
let bim = DiagonalBimodule::try_new(qwr, 100).unwrap();
assert_eq!(bim.peirce_dim(&"0", &"2"), 0);
}
#[test]
fn kronecker_peirce_piece_dimensions() {
let qwr = Arc::new(
QuiverWithRelations::<_, _, f64, true>::from_quiver_no_relations(Arc::new(
make_kronecker_quiver(),
)),
);
let bim = DiagonalBimodule::try_new(qwr, 100).unwrap();
assert_eq!(bim.peirce_dim(&"alpha", &"beta"), 2);
assert_eq!(bim.peirce_dim(&"alpha", &"alpha"), 1);
assert_eq!(bim.peirce_dim(&"beta", &"beta"), 1);
assert_eq!(bim.peirce_dim(&"beta", &"alpha"), 0);
}
}