1use crate::geometry::Geometry;
15use crate::lattice::PaddedTileLattice;
16use crate::semiring::{semiring_matmul, Boolean, Counting, Semiring, Tropical};
17
18#[derive(Clone)]
20pub struct TensorGraph {
21 n: usize,
22 geom: Geometry,
23 edges: Vec<(usize, usize, f32)>,
24}
25
26impl TensorGraph {
27 pub fn new(n: usize) -> Self {
29 TensorGraph {
30 n,
31 geom: Geometry::TPU_V,
32 edges: Vec::new(),
33 }
34 }
35
36 #[inline]
38 pub fn nodes(&self) -> usize {
39 self.n
40 }
41
42 #[inline]
44 pub fn edge_count(&self) -> usize {
45 self.edges.len()
46 }
47
48 pub fn add_edge(&mut self, u: usize, v: usize, weight: f32) {
50 assert!(u < self.n && v < self.n, "endpoints must be in range");
51 self.edges.push((u, v, weight));
52 }
53
54 pub fn add_edge_unweighted(&mut self, u: usize, v: usize) {
56 self.add_edge(u, v, 1.0);
57 }
58
59 fn reflexive_boolean(&self) -> PaddedTileLattice<f32> {
62 let mut dense = vec![0.0f32; self.n * self.n];
63 for i in 0..self.n {
64 dense[i * self.n + i] = 1.0;
65 }
66 for &(u, v, _) in &self.edges {
67 dense[u * self.n + v] = 1.0;
68 }
69 PaddedTileLattice::from_dense(self.n, self.n, &dense, self.geom).unwrap()
70 }
71
72 fn tropical_adjacency(&self) -> PaddedTileLattice<f32> {
75 let mut dense = vec![f32::INFINITY; self.n * self.n];
76 for i in 0..self.n {
77 dense[i * self.n + i] = 0.0;
78 }
79 for &(u, v, w) in &self.edges {
80 let slot = &mut dense[u * self.n + v];
81 *slot = slot.min(w);
82 }
83 PaddedTileLattice::from_dense(self.n, self.n, &dense, self.geom).unwrap()
84 }
85
86 fn plain_adjacency(&self) -> PaddedTileLattice<f32> {
88 let mut dense = vec![0.0f32; self.n * self.n];
89 for &(u, v, _) in &self.edges {
90 dense[u * self.n + v] = 1.0;
91 }
92 PaddedTileLattice::from_dense(self.n, self.n, &dense, self.geom).unwrap()
93 }
94
95 fn squarings(&self) -> u32 {
97 let mut steps = 0;
99 let mut reach = 1usize;
100 while reach < self.n.max(1) {
101 reach *= 2;
102 steps += 1;
103 }
104 steps
105 }
106
107 fn close<S: Semiring>(&self, mut m: PaddedTileLattice<f32>) -> PaddedTileLattice<f32> {
109 for _ in 0..self.squarings() {
110 m = semiring_matmul::<S>(&m, &m).unwrap();
111 }
112 m
113 }
114
115 pub fn reachability(&self) -> PaddedTileLattice<f32> {
118 self.close::<Boolean>(self.reflexive_boolean())
119 }
120
121 pub fn reachable(&self, u: usize, v: usize) -> bool {
123 *self.reachability().get(u, v).unwrap() != 0.0
124 }
125
126 pub fn shortest_paths(&self) -> PaddedTileLattice<f32> {
129 self.close::<Tropical>(self.tropical_adjacency())
130 }
131
132 pub fn distance(&self, u: usize, v: usize) -> Option<f32> {
134 let d = *self.shortest_paths().get(u, v).unwrap();
135 if d.is_finite() {
136 Some(d)
137 } else {
138 None
139 }
140 }
141
142 pub fn walk_counts(&self, k: usize) -> PaddedTileLattice<f32> {
145 let adj = self.plain_adjacency();
146 if k == 0 {
147 let mut dense = vec![0.0f32; self.n * self.n];
149 for i in 0..self.n {
150 dense[i * self.n + i] = 1.0;
151 }
152 return PaddedTileLattice::from_dense(self.n, self.n, &dense, self.geom).unwrap();
153 }
154 let mut acc = adj.clone();
155 for _ in 1..k {
156 acc = semiring_matmul::<Counting>(&acc, &adj).unwrap();
157 }
158 acc
159 }
160}
161
162impl core::fmt::Debug for TensorGraph {
163 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
164 write!(
165 f,
166 "TensorGraph {{ nodes: {}, edges: {} }}",
167 self.n,
168 self.edges.len()
169 )
170 }
171}