graphix/graphix.rs
1use std::collections::HashMap;
2
3pub struct GraphRep<K> {
4 v: Vec<usize>,
5 //src: Vec<usize>,
6 e: Vec<(usize, K, usize)>,
7 //w: Vec<(usize, K)>,
8 pub id: Vec<(usize, usize, K)>,
9 pub mapping: Vec<Vec<usize>>, //maps current verts to original verts
10}
11
12impl<K: PartialOrd + Copy> GraphRep<K> {
13 pub fn edges_from(&self, vertex: usize) -> &[(usize, K, usize)] {
14 if vertex + 1 >= self.v.len() {
15 panic!(
16 "edges_from(): vertex {} out of range (v.len() = {})",
17 vertex,
18 self.v.len()
19 );
20 }
21 let edges_start = self.v[vertex];
22 let edges_end = self.v[vertex + 1];
23
24 &self.e[edges_start..edges_end]
25 }
26
27 pub fn original_edge(&self, edge_id: usize) -> Option<&(usize, usize, K)> {
28 self.id.get(edge_id)
29 }
30
31 pub fn num_vertices(&self) -> usize {
32 self.v.len() - 1
33 }
34
35 pub fn num_edges(&self) -> usize {
36 self.e.len() / 2
37 }
38
39 pub fn v_len(&self) -> usize {
40 self.v.len()
41 }
42
43 pub fn e_len(&self) -> usize {
44 self.e.len()
45 }
46
47 //function that can create a graph from a vec<(vertex, vertex, weight)>
48 pub fn from_list(edges: Vec<(usize, usize, K)>) -> Self {
49 let m = edges.len();
50 if m == 0 {
51 // zero vertices, zero edges, empty id
52 return GraphRep {
53 v: vec![0], // one offset, so edges_from(u) is never OOB
54 e: Vec::new(),
55 id: Vec::new(),
56 mapping: Vec::new(),
57 };
58 }
59 let id = edges;
60 let n = id
61 .iter()
62 .flat_map(|&(u, v, _)| [u, v])
63 .max()
64 .map_or(0, |mx| mx + 1);
65
66 // Initialize mapping: each vertex maps to itself
67 let mapping = (0..n).map(|i| vec![i]).collect();
68
69 // number of edges at every vertex
70 let mut v = vec![0; n + 1];
71 for &(u, vtx, _) in &id {
72 v[u] += 1;
73 v[vtx] += 1;
74 }
75
76 // 3) fused prefix‐sum into `v` *and* init `write_cursor`
77 let mut write_cursor = Vec::with_capacity(v.len());
78 let mut running_sum = 0;
79 for slot in &mut v {
80 let deg = *slot; // old count
81 write_cursor.push(running_sum);
82 *slot = running_sum; // now CSR offset
83 running_sum += deg;
84 }
85
86 // 4) allocate and scatter into `e`
87 let dummy = (0, id[0].2, 0);
88 let mut e = vec![dummy; 2 * m];
89 for (edge_id, &(src, dst, weight)) in id.iter().enumerate() {
90 // half‐edge src → dst
91 let pos = write_cursor[src];
92 e[pos] = (dst, weight, edge_id);
93 write_cursor[src] += 1;
94
95 // half‐edge dst → src
96 let pos_back = write_cursor[dst];
97 e[pos_back] = (src, weight, edge_id);
98 write_cursor[dst] += 1;
99 }
100
101 GraphRep { v, e, id, mapping }
102 }
103
104 pub fn update_v_e(&mut self, edges: &[(usize, usize, K, usize)]) {
105 if edges.is_empty() {
106 self.v = vec![0]; // no vertices left
107 self.e.clear(); // clear edge list
108 self.mapping.clear();
109 return; // exit early
110 }
111
112 // 1. Determine new supernodes from current mapping
113 let mut supernode_map: HashMap<usize, Vec<usize>> = HashMap::new();
114 for (idx, originals) in self.mapping.iter().enumerate() {
115 supernode_map.entry(idx).or_insert(originals.clone());
116 }
117
118 // 2. Rebuild mapping for contracted graph
119 let new_mapping = Vec::new();
120
121 // Determine number of vertices (based on max index seen in edge list)
122 let n = edges
123 .iter()
124 .flat_map(|&(u, v, _, _)| [u, v])
125 .max()
126 .unwrap_or(0)
127 + 1;
128
129 // Count the degree of each vertex to prepare CSR offsets
130 let mut deg = vec![0; n + 1];
131 for &(u, v, _, _) in edges {
132 deg[u] += 1;
133 deg[v] += 1;
134 }
135
136 // Compute prefix sums over degrees to build CSR offset vector `v`
137 let mut v = vec![0; n + 1];
138 for i in 1..=n {
139 v[i] = v[i - 1] + deg[i - 1];
140 }
141 // Clone `v` as write cursor to track where to insert each neighbor
142 let mut cursor = v.clone();
143
144 // Allocate edge list of twice the input size (undirected = 2 half-edges)
145 let mut e = vec![(0, edges[0].2, 0); 2 * edges.len()];
146
147 // Scatter edges into CSR structure using cursor positions
148 for &(u, vtx, w, id) in edges {
149 e[cursor[u]] = (vtx, w, id);
150 cursor[u] += 1;
151
152 e[cursor[vtx]] = (u, w, id);
153 cursor[vtx] += 1;
154 }
155
156 // Replace current CSR layout with newly built one
157 self.v = v;
158 self.e = e;
159 self.mapping = new_mapping;
160 }
161
162 ///returns all original edges
163 pub fn all_edges(&self) -> Vec<(usize, usize, K, usize)> {
164 let mut result = Vec::new();
165
166 for (eid, &(u, v, w)) in self.id.iter().enumerate() {
167 if u < v {
168 result.push((u, v, w, eid));
169 } else {
170 result.push((v, u, w, eid));
171 }
172 }
173 result
174 }
175
176 pub fn current_edges(&self) -> Vec<(usize, usize, K, usize)> {
177 let mut out = Vec::with_capacity(self.num_edges()); // m edges
178 let n = self.num_vertices();
179
180 for u in 0..n {
181 for &(v, w, eid) in self.edges_from(u) {
182 if u < v {
183 // keep one direction only
184 out.push((u, v, w, eid));
185 }
186 }
187 }
188 out
189 }
190
191 // pub fn contract_cc(&mut self, cc_ids: &[isize]) {
192 // // old number of vertices and number of supernodes
193 // let org_vert_count = self.num_vertices();
194 // debug_assert_eq!(cc_ids.len(), org_vert_count);
195
196 // let supernode_count = cc_ids
197 // .iter()
198 // .map(|&label| label as usize)
199 // .max()
200 // .unwrap_or(0)
201 // + 1;
202
203 // //create a new offsett array (v), and couint half edges
204 // let mut new_offsets = vec![0usize; supernode_count + 1];
205
206 // for old_vert_index in 0..org_vert_count {
207 // let super_node_id = cc_ids[old_vert_index] as usize;
208 // for &(neighbor_old_index, _edge_weight, _org_edge_id) in self.edges_from(old_vert_index)
209 // {
210 // let neighbor_super_id = cc_ids[neighbor_old_index] as usize;
211
212 // if super_node_id != neighbor_super_id {
213 // new_offsets[super_node_id + 1] += 1;
214 // }
215 // }
216 // }
217
218 // for i in 1..=supernode_count {
219 // new_offsets[i] += new_offsets[i - 1];
220 // }
221
222 // let total_edges = new_offsets[supernode_count];
223 // let dummy_edges = (0, self.e[0].1, 0);
224 // let mut new_edges = vec![dummy_edges; total_edges];
225
226 // let mut write_cursor = new_offsets.clone();
227
228 // for old_vert_index in 0..org_vert_count {
229 // let super_node_id = cc_ids[old_vert_index] as usize;
230 // for &(neighbor_old_index, edge_weight, org_edge_id) in self.edges_from(old_vert_index) {
231 // let neighbor_super_id = cc_ids[neighbor_old_index] as usize;
232 // if super_node_id != neighbor_super_id {
233 // let write_pos = write_cursor[super_node_id];
234 // new_edges[write_pos] = (neighbor_super_id, edge_weight, org_edge_id);
235 // write_cursor[super_node_id] += 1;
236 // }
237 // }
238 // }
239 // self.v = new_offsets;
240 // self.e = new_edges
241 // }
242}
243
244// #[cfg(test)]
245// mod tests {
246// use super::GraphRep;
247
248// #[test]
249// fn test_empty() {
250// let g: GraphRep<i32> = GraphRep::from_list(Vec::new());
251// assert_eq!(g.num_vertices(), 0);
252// assert_eq!(g.num_edges(), 0);
253// assert!(g.id.is_empty());
254// }
255
256// #[test]
257// fn test_small_graph() {
258// // edges = [(0–1,1), (1–2,2), (2–0,3)]
259// let edges = vec![(0, 1, 1), (1, 2, 2), (2, 0, 3)];
260// let g = GraphRep::from_list(edges.clone());
261
262// assert_eq!(g.num_vertices(), 3);
263// assert_eq!(g.num_edges(), 3);
264// assert_eq!(g.id, edges);
265
266// let degs: Vec<_> = (0..3).map(|u| g.edges_from(u).len()).collect();
267// assert_eq!(degs, vec![2, 2, 2]);
268
269// let mut adj0: Vec<_> = g.edges_from(0).iter().cloned().collect();
270// adj0.sort_by_key(|&(to, _, eid)| (to, eid));
271// assert_eq!(adj0, vec![(1, 1, 0), (2, 3, 2)]);
272// }
273// }
274
275// #[cfg(test)]
276// mod contract_tests {
277// use super::GraphRep;
278
279// #[test]
280// fn test_contract_cc_chain() {
281// // Original graph: a path 0–1–2–3 with weights 10, 20, 30
282// let edges = vec![(0, 1, 10), (1, 2, 20), (2, 3, 30)];
283// let mut g: GraphRep<i32> = GraphRep::from_list(edges.clone());
284
285// // Verify original graph structure
286// assert_eq!(g.num_vertices(), 4);
287// assert_eq!(g.num_edges(), 3);
288
289// // Define CC IDs that merge {0,1} into super-node 0 and {2,3} into super-node 1
290// let cc_ids = vec![0isize, 0, 1, 1];
291
292// // Contract in-place
293// g.contract_cc(&cc_ids);
294
295// // After contraction we expect:
296// // - 2 super-nodes (0 and 1)
297// // - 1 undirected edge between them (the former 1–2 edge)
298// assert_eq!(g.num_vertices(), 2);
299// assert_eq!(g.num_edges(), 1);
300
301// // CSR offsets should be [0, 1, 2]:
302// // node 0 has 1 half-edge, node 1 has 1 half-edge
303// assert_eq!(g.v, vec![0, 1, 2]);
304
305// // The only remaining edge should have:
306// // - from super-node 0 → 1, weight 20, orig_eid = 1
307// let out0 = g.edges_from(0);
308// assert_eq!(out0.len(), 1);
309// assert_eq!(out0[0], (1, 20, 1));
310
311// // And the symmetric half-edge from 1 → 0
312// let out1 = g.edges_from(1);
313// assert_eq!(out1.len(), 1);
314// assert_eq!(out1[0], (0, 20, 1));
315// }
316
317// #[test]
318// fn test_contract_cc_identity() {
319// // If cc_ids is [0,1,2,3], nothing should change
320// let edges = vec![(0, 1, 5), (1, 2, 6), (2, 3, 7)];
321// let mut g1: GraphRep<i32> = GraphRep::from_list(edges.clone());
322// let original_v = g1.v.clone();
323// let original_e = g1.e.clone();
324
325// let cc_ids = vec![0isize, 1, 2, 3];
326// g1.contract_cc(&cc_ids);
327
328// // Vertex and edge counts unchanged
329// assert_eq!(g1.num_vertices(), 4);
330// assert_eq!(g1.num_edges(), 3);
331// // CSR arrays identical
332// assert_eq!(g1.v, original_v);
333// assert_eq!(g1.e, original_e);
334// }
335// }