use crate::Label;
use std::collections::{HashMap, HashSet};
#[derive(Debug, Clone)]
pub struct IncidenceList<V, E>
where
V: Clone + Eq + std::hash::Hash,
E: Clone + Eq + std::hash::Hash,
{
v2e: HashMap<V, Vec<E>>,
e2v: HashMap<E, Vec<V>>,
openedges: HashSet<E>,
}
impl<V, E> IncidenceList<V, E>
where
V: Clone + Eq + std::hash::Hash,
E: Clone + Eq + std::hash::Hash,
{
pub fn new(v2e: HashMap<V, Vec<E>>, openedges: Vec<E>) -> Self {
let mut e2v: HashMap<E, Vec<V>> = HashMap::new();
for (v, es) in &v2e {
for e in es {
e2v.entry(e.clone()).or_default().push(v.clone());
}
}
Self {
v2e,
e2v,
openedges: openedges.into_iter().collect(),
}
}
pub fn from_eincode<L: Label>(ixs: &[Vec<L>], iy: &[L]) -> IncidenceList<usize, L> {
let v2e: HashMap<usize, Vec<L>> = ixs
.iter()
.enumerate()
.map(|(i, ix)| (i, ix.clone()))
.collect();
IncidenceList::new(v2e, iy.to_vec())
}
#[inline]
pub fn nv(&self) -> usize {
self.v2e.len()
}
#[inline]
pub fn ne(&self) -> usize {
self.e2v.len()
}
#[inline]
pub fn is_empty(&self) -> bool {
self.v2e.is_empty()
}
pub fn vertices(&self) -> impl Iterator<Item = &V> {
self.v2e.keys()
}
pub fn edges_all(&self) -> impl Iterator<Item = &E> {
self.e2v.keys()
}
pub fn edges(&self, v: &V) -> Option<&Vec<E>> {
self.v2e.get(v)
}
pub fn vertices_of_edge(&self, e: &E) -> Option<&Vec<V>> {
self.e2v.get(e)
}
#[inline]
pub fn is_open(&self, e: &E) -> bool {
self.openedges.contains(e)
}
pub fn neighbors(&self, v: &V) -> Vec<V> {
let mut result = Vec::new();
let mut seen = HashSet::new();
seen.insert(v.clone());
if let Some(edges) = self.v2e.get(v) {
for e in edges {
if let Some(verts) = self.e2v.get(e) {
for vj in verts {
if seen.insert(vj.clone()) {
result.push(vj.clone());
}
}
}
}
}
result
}
pub fn are_neighbors(&self, vi: &V, vj: &V) -> bool {
if let (Some(ei), Some(ej)) = (self.v2e.get(vi), self.v2e.get(vj)) {
let set_j: HashSet<_> = ej.iter().collect();
ei.iter().any(|e| set_j.contains(e))
} else {
false
}
}
pub fn shared_edges(&self, vi: &V, vj: &V) -> Vec<E> {
if let (Some(ei), Some(ej)) = (self.v2e.get(vi), self.v2e.get(vj)) {
let set_j: HashSet<_> = ej.iter().collect();
ei.iter().filter(|e| set_j.contains(e)).cloned().collect()
} else {
Vec::new()
}
}
pub fn is_internal(&self, e: &E, vi: &V, vj: &V) -> bool {
if self.is_open(e) {
return false;
}
if let Some(verts) = self.e2v.get(e) {
verts.len() == 2 && verts.contains(vi) && verts.contains(vj)
} else {
false
}
}
pub fn is_external(&self, e: &E, vi: &V, vj: &V) -> bool {
if self.is_open(e) {
return true;
}
if let Some(verts) = self.e2v.get(e) {
verts.iter().any(|v| v != vi && v != vj)
} else {
false
}
}
pub fn delete_vertex(&mut self, v: &V) {
if let Some(edges) = self.v2e.remove(v) {
for e in edges {
if let Some(verts) = self.e2v.get_mut(&e) {
verts.retain(|x| x != v);
if verts.is_empty() || (verts.len() == 1 && !self.is_open(&e)) {
self.e2v.remove(&e);
}
}
}
}
}
pub fn set_edges(&mut self, v: V, new_edges: Vec<E>) {
if let Some(old_edges) = self.v2e.get(&v) {
for e in old_edges.clone() {
if let Some(verts) = self.e2v.get_mut(&e) {
verts.retain(|x| x != &v);
if verts.is_empty() {
self.e2v.remove(&e);
}
}
}
}
for e in &new_edges {
self.e2v.entry(e.clone()).or_default().push(v.clone());
}
self.v2e.insert(v, new_edges);
}
pub fn remove_edges(&mut self, edges: &[E]) {
for e in edges {
if let Some(verts) = self.e2v.remove(e) {
for v in verts {
if let Some(v_edges) = self.v2e.get_mut(&v) {
v_edges.retain(|x| x != e);
}
}
}
self.openedges.remove(e);
}
}
pub fn replace_vertex(&mut self, old_v: &V, new_v: V) {
if old_v == &new_v {
return;
}
if let Some(edges) = self.v2e.remove(old_v) {
for e in &edges {
if let Some(verts) = self.e2v.get_mut(e) {
for v in verts.iter_mut() {
if v == old_v {
*v = new_v.clone();
}
}
}
}
let mut all_edges = self.v2e.remove(&new_v).unwrap_or_default();
for e in edges {
if !all_edges.contains(&e) {
all_edges.push(e);
}
}
self.v2e.insert(new_v, all_edges);
}
}
}
#[derive(Debug, Clone)]
pub struct ContractionDims<E> {
pub d1: f64,
pub d2: f64,
pub d12: f64,
pub d01: f64,
pub d02: f64,
pub d012: f64,
pub edges_out: Vec<E>,
pub edges_remove: Vec<E>,
}
impl<E: Clone + Eq + std::hash::Hash> ContractionDims<E> {
pub fn compute<V: Clone + Eq + std::hash::Hash>(
il: &IncidenceList<V, E>,
log2_sizes: &HashMap<E, f64>,
vi: &V,
vj: &V,
) -> Self {
let mut d1 = 0.0;
let mut d2 = 0.0;
let mut d12 = 0.0;
let mut d01 = 0.0;
let mut d02 = 0.0;
let mut d012 = 0.0;
let mut edges_out = Vec::new();
let mut edges_remove = Vec::new();
let ei = il.edges(vi).cloned().unwrap_or_default();
let ej = il.edges(vj).cloned().unwrap_or_default();
let ej_set: HashSet<_> = ej.iter().collect();
let mut processed: HashSet<E> = HashSet::new();
for e in &ei {
if !processed.insert(e.clone()) {
continue;
}
let size = log2_sizes.get(e).copied().unwrap_or(0.0);
let in_j = ej_set.contains(e);
let is_ext = il.is_external(e, vi, vj);
match (in_j, is_ext) {
(false, false) => d1 += size,
(false, true) => {
d01 += size;
edges_out.push(e.clone());
}
(true, false) => {
d12 += size;
edges_remove.push(e.clone());
}
(true, true) => {
d012 += size;
edges_out.push(e.clone());
}
}
}
for e in &ej {
if !processed.insert(e.clone()) {
continue;
}
let size = log2_sizes.get(e).copied().unwrap_or(0.0);
let is_ext = il.is_external(e, vi, vj);
if is_ext {
d02 += size;
edges_out.push(e.clone());
} else {
d2 += size;
}
}
Self {
d1,
d2,
d12,
d01,
d02,
d012,
edges_out,
edges_remove,
}
}
#[inline]
pub fn time_complexity(&self) -> f64 {
self.d12 + self.d01 + self.d02 + self.d012
}
#[inline]
pub fn space_complexity(&self) -> f64 {
self.d01 + self.d02 + self.d012
}
}
#[cfg(test)]
mod tests {
use super::*;
fn simple_v2e() -> HashMap<usize, Vec<char>> {
let mut v2e = HashMap::new();
v2e.insert(0, vec!['i', 'j']);
v2e.insert(1, vec!['j', 'k']);
v2e.insert(2, vec!['k', 'l']);
v2e
}
#[test]
fn test_new_incidence_list() {
let v2e = simple_v2e();
let il: IncidenceList<usize, char> = IncidenceList::new(v2e, vec!['i', 'l']);
assert_eq!(il.nv(), 3);
assert_eq!(il.ne(), 4);
assert!(il.is_open(&'i'));
assert!(il.is_open(&'l'));
assert!(!il.is_open(&'j'));
}
#[test]
fn test_neighbors() {
let v2e = simple_v2e();
let il: IncidenceList<usize, char> = IncidenceList::new(v2e, vec!['i', 'l']);
let neighbors_0 = il.neighbors(&0);
assert_eq!(neighbors_0.len(), 1);
assert!(neighbors_0.contains(&1));
let neighbors_1 = il.neighbors(&1);
assert_eq!(neighbors_1.len(), 2);
assert!(neighbors_1.contains(&0));
assert!(neighbors_1.contains(&2));
}
#[test]
fn test_shared_edges() {
let v2e = simple_v2e();
let il: IncidenceList<usize, char> = IncidenceList::new(v2e, vec!['i', 'l']);
let shared = il.shared_edges(&0, &1);
assert_eq!(shared, vec!['j']);
let shared_none = il.shared_edges(&0, &2);
assert!(shared_none.is_empty());
}
#[test]
fn test_is_internal() {
let v2e = simple_v2e();
let il: IncidenceList<usize, char> = IncidenceList::new(v2e, vec!['i', 'l']);
assert!(il.is_internal(&'j', &0, &1));
assert!(!il.is_internal(&'i', &0, &1));
}
#[test]
fn test_delete_vertex() {
let v2e = simple_v2e();
let mut il: IncidenceList<usize, char> = IncidenceList::new(v2e, vec!['i', 'l']);
il.delete_vertex(&1);
assert_eq!(il.nv(), 2);
assert!(!il.are_neighbors(&0, &2));
}
#[test]
fn test_from_eincode() {
let ixs = vec![vec!['i', 'j'], vec!['j', 'k']];
let iy = vec!['i', 'k'];
let il = IncidenceList::<usize, char>::from_eincode(&ixs, &iy);
assert_eq!(il.nv(), 2);
assert!(il.are_neighbors(&0, &1));
assert!(il.is_open(&'i'));
assert!(il.is_open(&'k'));
assert!(!il.is_open(&'j'));
}
#[test]
fn test_contraction_dims() {
let ixs = vec![vec!['i', 'j'], vec!['j', 'k']];
let iy = vec!['i', 'k'];
let il = IncidenceList::<usize, char>::from_eincode(&ixs, &iy);
let mut log2_sizes = HashMap::new();
log2_sizes.insert('i', 2.0); log2_sizes.insert('j', 3.0); log2_sizes.insert('k', 2.0);
let dims = ContractionDims::compute(&il, &log2_sizes, &0, &1);
assert!((dims.d12 - 3.0).abs() < 1e-10);
assert!((dims.d01 - 2.0).abs() < 1e-10);
assert!((dims.d02 - 2.0).abs() < 1e-10);
assert!((dims.time_complexity() - 7.0).abs() < 1e-10);
assert!((dims.space_complexity() - 4.0).abs() < 1e-10);
}
#[test]
fn test_edges_method() {
let v2e = simple_v2e();
let il: IncidenceList<usize, char> = IncidenceList::new(v2e, vec!['i', 'l']);
let edges_0 = il.edges(&0).unwrap();
assert_eq!(edges_0.len(), 2);
assert!(edges_0.contains(&'i'));
assert!(edges_0.contains(&'j'));
}
#[test]
fn test_vertices_iter() {
let v2e = simple_v2e();
let il: IncidenceList<usize, char> = IncidenceList::new(v2e, vec!['i', 'l']);
let vertices: Vec<_> = il.vertices().collect();
assert_eq!(vertices.len(), 3);
}
#[test]
fn test_no_common_edges() {
let v2e = simple_v2e();
let il: IncidenceList<usize, char> = IncidenceList::new(v2e, vec!['i', 'l']);
assert!(!il.are_neighbors(&0, &2));
let shared = il.shared_edges(&0, &2);
assert!(shared.is_empty());
}
#[test]
fn test_is_external() {
let v2e = simple_v2e();
let il: IncidenceList<usize, char> = IncidenceList::new(v2e, vec!['i', 'l']);
assert!(il.is_external(&'i', &0, &1));
assert!(!il.is_external(&'j', &0, &1));
}
#[test]
fn test_single_tensor() {
let mut v2e = HashMap::new();
v2e.insert(0, vec!['i', 'j']);
let il: IncidenceList<usize, char> = IncidenceList::new(v2e, vec!['i', 'j']);
assert_eq!(il.nv(), 1);
assert_eq!(il.ne(), 2);
assert!(il.is_open(&'i'));
assert!(il.is_open(&'j'));
}
#[test]
fn test_vertices_of_edge() {
let v2e = simple_v2e();
let il: IncidenceList<usize, char> = IncidenceList::new(v2e, vec!['i', 'l']);
let vertices = il.vertices_of_edge(&'j').unwrap();
assert_eq!(vertices.len(), 2);
assert!(vertices.contains(&0));
assert!(vertices.contains(&1));
}
#[test]
fn test_replace_vertex() {
let v2e = simple_v2e();
let mut il: IncidenceList<usize, char> = IncidenceList::new(v2e, vec!['i', 'l']);
il.replace_vertex(&0, 100);
assert_eq!(il.nv(), 3);
assert!(il.edges(&100).is_some());
assert!(il.edges(&0).is_none());
}
#[test]
fn test_set_edges() {
let v2e = simple_v2e();
let mut il: IncidenceList<usize, char> = IncidenceList::new(v2e, vec!['i', 'l']);
il.set_edges(0, vec!['a', 'b']);
let edges = il.edges(&0).unwrap();
assert_eq!(edges.len(), 2);
assert!(edges.contains(&'a'));
assert!(edges.contains(&'b'));
}
#[test]
fn test_is_empty() {
let il: IncidenceList<usize, char> = IncidenceList::new(HashMap::new(), vec![]);
assert!(il.is_empty());
let v2e = simple_v2e();
let il2: IncidenceList<usize, char> = IncidenceList::new(v2e, vec!['i', 'l']);
assert!(!il2.is_empty());
}
#[test]
fn test_edges_all() {
let v2e = simple_v2e();
let il: IncidenceList<usize, char> = IncidenceList::new(v2e, vec!['i', 'l']);
let edges: Vec<_> = il.edges_all().collect();
assert_eq!(edges.len(), 4);
assert!(edges.contains(&&'i'));
assert!(edges.contains(&&'j'));
assert!(edges.contains(&&'k'));
assert!(edges.contains(&&'l'));
}
#[test]
fn test_remove_edges() {
let v2e = simple_v2e();
let mut il: IncidenceList<usize, char> = IncidenceList::new(v2e, vec!['i', 'l']);
il.remove_edges(&['j']);
assert!(il.vertices_of_edge(&'j').is_none());
assert!(!il.are_neighbors(&0, &1));
}
#[test]
fn test_remove_open_edge() {
let v2e = simple_v2e();
let mut il: IncidenceList<usize, char> = IncidenceList::new(v2e, vec!['i', 'l']);
assert!(il.is_open(&'i'));
il.remove_edges(&['i']);
assert!(!il.is_open(&'i'));
}
#[test]
fn test_replace_vertex_same() {
let v2e = simple_v2e();
let mut il: IncidenceList<usize, char> = IncidenceList::new(v2e, vec!['i', 'l']);
il.replace_vertex(&0, 0);
assert!(il.edges(&0).is_some());
assert_eq!(il.nv(), 3);
}
#[test]
fn test_replace_vertex_merge() {
let v2e = simple_v2e();
let mut il: IncidenceList<usize, char> = IncidenceList::new(v2e, vec!['i', 'l']);
il.replace_vertex(&0, 1);
assert!(il.edges(&0).is_none());
let edges = il.edges(&1).unwrap();
assert!(edges.contains(&'i') || edges.contains(&'j'));
}
#[test]
fn test_delete_vertex_removes_empty_edges() {
let mut v2e = HashMap::new();
v2e.insert(0, vec!['a', 'b']);
v2e.insert(1, vec!['b']); v2e.insert(2, vec!['b', 'c']); let mut il: IncidenceList<usize, char> = IncidenceList::new(v2e, vec!['a', 'c']);
il.delete_vertex(&0);
assert_eq!(il.nv(), 2);
assert!(il.vertices_of_edge(&'a').is_none());
let b_verts = il.vertices_of_edge(&'b').unwrap();
assert_eq!(b_verts.len(), 2);
}
#[test]
fn test_neighbors_no_neighbors() {
let mut v2e = HashMap::new();
v2e.insert(0, vec!['a']);
v2e.insert(1, vec!['b']); let il: IncidenceList<usize, char> = IncidenceList::new(v2e, vec!['a', 'b']);
let neighbors = il.neighbors(&0);
assert!(neighbors.is_empty());
}
#[test]
fn test_shared_edges_nonexistent_vertex() {
let v2e = simple_v2e();
let il: IncidenceList<usize, char> = IncidenceList::new(v2e, vec!['i', 'l']);
let shared = il.shared_edges(&0, &99);
assert!(shared.is_empty());
}
#[test]
fn test_is_internal_nonexistent_edge() {
let v2e = simple_v2e();
let il: IncidenceList<usize, char> = IncidenceList::new(v2e, vec!['i', 'l']);
assert!(!il.is_internal(&'z', &0, &1));
}
#[test]
fn test_is_external_nonexistent_edge() {
let v2e = simple_v2e();
let il: IncidenceList<usize, char> = IncidenceList::new(v2e, vec!['i', 'l']);
assert!(!il.is_external(&'z', &0, &1));
}
#[test]
fn test_set_edges_new_vertex() {
let v2e = simple_v2e();
let mut il: IncidenceList<usize, char> = IncidenceList::new(v2e, vec!['i', 'l']);
il.set_edges(99, vec!['x', 'y']);
assert_eq!(il.nv(), 4);
let edges = il.edges(&99).unwrap();
assert!(edges.contains(&'x'));
assert!(edges.contains(&'y'));
}
#[test]
fn test_contraction_dims_d1_d2() {
let mut v2e = HashMap::new();
v2e.insert(0, vec!['a', 'j']);
v2e.insert(1, vec!['j', 'b']);
let il: IncidenceList<usize, char> = IncidenceList::new(v2e, vec![]);
let mut log2_sizes = HashMap::new();
log2_sizes.insert('a', 2.0);
log2_sizes.insert('j', 3.0);
log2_sizes.insert('b', 4.0);
let dims = ContractionDims::compute(&il, &log2_sizes, &0, &1);
assert!((dims.d1 - 2.0).abs() < 1e-10);
assert!((dims.d2 - 4.0).abs() < 1e-10);
assert!((dims.d12 - 3.0).abs() < 1e-10);
}
#[test]
fn test_contraction_dims_d012() {
let mut v2e = HashMap::new();
v2e.insert(0, vec!['b', 'j']);
v2e.insert(1, vec!['j', 'b']);
v2e.insert(2, vec!['b']); let il: IncidenceList<usize, char> = IncidenceList::new(v2e, vec!['b']);
let mut log2_sizes = HashMap::new();
log2_sizes.insert('b', 2.0);
log2_sizes.insert('j', 3.0);
let dims = ContractionDims::compute(&il, &log2_sizes, &0, &1);
assert!((dims.d012 - 2.0).abs() < 1e-10);
}
#[test]
fn test_neighbors_nonexistent_vertex() {
let v2e = simple_v2e();
let il: IncidenceList<usize, char> = IncidenceList::new(v2e, vec!['i', 'l']);
let neighbors = il.neighbors(&99);
assert!(neighbors.is_empty());
}
#[test]
fn test_are_neighbors_nonexistent() {
let v2e = simple_v2e();
let il: IncidenceList<usize, char> = IncidenceList::new(v2e, vec!['i', 'l']);
assert!(!il.are_neighbors(&0, &99));
assert!(!il.are_neighbors(&99, &0));
}
#[test]
fn test_replace_vertex_nonexistent() {
let v2e = simple_v2e();
let mut il: IncidenceList<usize, char> = IncidenceList::new(v2e, vec!['i', 'l']);
il.replace_vertex(&99, 100);
assert_eq!(il.nv(), 3);
assert!(il.edges(&99).is_none());
assert!(il.edges(&100).is_none());
}
#[test]
fn test_edges_nonexistent_vertex() {
let v2e = simple_v2e();
let il: IncidenceList<usize, char> = IncidenceList::new(v2e, vec!['i', 'l']);
assert!(il.edges(&99).is_none());
}
#[test]
fn test_contraction_dims_missing_size() {
let ixs = vec![vec!['i', 'j'], vec!['j', 'k']];
let iy = vec!['i', 'k'];
let il = IncidenceList::<usize, char>::from_eincode(&ixs, &iy);
let mut log2_sizes = HashMap::new();
log2_sizes.insert('i', 2.0);
let dims = ContractionDims::compute(&il, &log2_sizes, &0, &1);
assert!(dims.d01 >= 0.0 || !dims.d01.is_nan());
}
}