1#![allow(missing_docs)]
16
17use crate::delta::{FixedWeight, TileEdgeId, TileVertexId};
18use core::mem::size_of;
19
20const CACHE_LINE_SIZE: usize = 64;
22
23pub const MAX_SHARD_VERTICES: usize = 256;
25
26pub const MAX_SHARD_EDGES: usize = 1024;
28
29pub const MAX_DEGREE: usize = 32;
31
32#[derive(Debug, Clone, Copy, Default)]
36#[repr(C, align(8))]
37pub struct ShardEdge {
38 pub source: TileVertexId,
40 pub target: TileVertexId,
42 pub weight: FixedWeight,
44 pub flags: u16,
46}
47
48impl ShardEdge {
49 pub const FLAG_ACTIVE: u16 = 0x0001;
51 pub const FLAG_IN_CUT: u16 = 0x0002;
53 pub const FLAG_TREE: u16 = 0x0004;
55 pub const FLAG_GHOST: u16 = 0x0008;
57
58 #[inline(always)]
60 pub const fn new(source: TileVertexId, target: TileVertexId, weight: FixedWeight) -> Self {
61 Self {
62 source,
63 target,
64 weight,
65 flags: Self::FLAG_ACTIVE,
66 }
67 }
68
69 #[inline(always)]
73 pub const fn is_active(&self) -> bool {
74 self.flags & Self::FLAG_ACTIVE != 0
75 }
76
77 #[inline(always)]
81 pub const fn is_in_cut(&self) -> bool {
82 self.flags & Self::FLAG_IN_CUT != 0
83 }
84
85 #[inline(always)]
87 pub const fn is_tree(&self) -> bool {
88 self.flags & Self::FLAG_TREE != 0
89 }
90
91 #[inline(always)]
93 pub const fn is_ghost(&self) -> bool {
94 self.flags & Self::FLAG_GHOST != 0
95 }
96
97 #[inline(always)]
99 pub fn deactivate(&mut self) {
100 self.flags &= !Self::FLAG_ACTIVE;
101 }
102
103 #[inline(always)]
105 pub fn mark_in_cut(&mut self) {
106 self.flags |= Self::FLAG_IN_CUT;
107 }
108
109 #[inline(always)]
111 pub fn clear_cut(&mut self) {
112 self.flags &= !Self::FLAG_IN_CUT;
113 }
114}
115
116#[derive(Debug, Clone, Copy, Default)]
120#[repr(C, align(8))]
121pub struct VertexEntry {
122 pub degree: u8,
124 pub flags: u8,
126 pub component: u16,
128 pub first_edge_idx: u16,
130 pub _reserved: u16,
132}
133
134impl VertexEntry {
135 pub const FLAG_ACTIVE: u8 = 0x01;
137 pub const FLAG_BOUNDARY: u8 = 0x02;
139 pub const FLAG_SIDE: u8 = 0x04;
141 pub const FLAG_GHOST: u8 = 0x08;
143
144 #[inline(always)]
146 pub const fn new() -> Self {
147 Self {
148 degree: 0,
149 flags: Self::FLAG_ACTIVE,
150 component: 0,
151 first_edge_idx: 0xFFFF, _reserved: 0,
153 }
154 }
155
156 #[inline(always)]
160 pub const fn is_active(&self) -> bool {
161 self.flags & Self::FLAG_ACTIVE != 0
162 }
163
164 #[inline(always)]
168 pub const fn side(&self) -> u8 {
169 (self.flags & Self::FLAG_SIDE) >> 2
171 }
172
173 #[inline(always)]
177 pub fn set_side(&mut self, side: u8) {
178 self.flags = (self.flags & !Self::FLAG_SIDE) | ((side & 1) << 2);
180 }
181}
182
183#[derive(Debug, Clone, Copy, Default)]
185#[repr(C)]
186pub struct AdjEntry {
187 pub neighbor: TileVertexId,
189 pub edge_id: TileEdgeId,
191}
192
193#[repr(C, align(64))]
206pub struct CompactGraph {
207 pub num_vertices: u16,
210 pub num_edges: u16,
212 pub free_edge_head: u16,
214 pub generation: u16,
216 pub num_components: u16,
218 pub status: u16,
220 _hot_pad: [u8; 52],
222
223 pub vertices: [VertexEntry; MAX_SHARD_VERTICES],
226 pub edges: [ShardEdge; MAX_SHARD_EDGES],
228 pub adjacency: [[AdjEntry; MAX_DEGREE]; MAX_SHARD_VERTICES],
231}
232
233impl Default for CompactGraph {
234 #[inline]
235 fn default() -> Self {
236 Self::new()
237 }
238}
239
240impl CompactGraph {
241 pub const STATUS_VALID: u16 = 0x0001;
243 pub const STATUS_DIRTY: u16 = 0x0002;
245 pub const STATUS_CONNECTED: u16 = 0x0004;
247
248 pub const fn new() -> Self {
250 Self {
251 num_vertices: 0,
252 num_edges: 0,
253 free_edge_head: 0xFFFF,
254 generation: 0,
255 num_components: 0,
256 status: Self::STATUS_VALID,
257 _hot_pad: [0; 52],
258 vertices: [VertexEntry {
259 degree: 0,
260 flags: 0, component: 0,
262 first_edge_idx: 0xFFFF,
263 _reserved: 0,
264 }; MAX_SHARD_VERTICES],
265 edges: [ShardEdge {
266 source: 0,
267 target: 0,
268 weight: 0,
269 flags: 0,
270 }; MAX_SHARD_EDGES],
271 adjacency: [[AdjEntry { neighbor: 0, edge_id: 0 }; MAX_DEGREE]; MAX_SHARD_VERTICES],
272 }
273 }
274
275 pub fn clear(&mut self) {
277 for v in self.vertices.iter_mut() {
278 *v = VertexEntry::new();
279 v.flags = 0; }
281 for e in self.edges.iter_mut() {
282 e.flags = 0;
283 }
284 self.num_vertices = 0;
285 self.num_edges = 0;
286 self.free_edge_head = 0xFFFF;
287 self.generation = self.generation.wrapping_add(1);
288 self.num_components = 0;
289 self.status = Self::STATUS_VALID | Self::STATUS_DIRTY;
290 }
291
292 pub fn add_vertex(&mut self, v: TileVertexId) -> bool {
294 if v as usize >= MAX_SHARD_VERTICES {
295 return false;
296 }
297
298 let entry = &mut self.vertices[v as usize];
299 if entry.is_active() {
300 return false; }
302
303 entry.flags = VertexEntry::FLAG_ACTIVE;
304 entry.degree = 0;
305 entry.component = 0;
306 entry.first_edge_idx = 0xFFFF;
307 self.num_vertices += 1;
308 self.status |= Self::STATUS_DIRTY;
309 true
310 }
311
312 pub fn remove_vertex(&mut self, v: TileVertexId) -> bool {
314 if v as usize >= MAX_SHARD_VERTICES {
315 return false;
316 }
317
318 let entry = &mut self.vertices[v as usize];
319 if !entry.is_active() {
320 return false;
321 }
322
323 for i in 0..entry.degree as usize {
325 let adj = &self.adjacency[v as usize][i];
326 if adj.edge_id < MAX_SHARD_EDGES as u16 {
327 self.edges[adj.edge_id as usize].deactivate();
328 self.num_edges = self.num_edges.saturating_sub(1);
329 }
330 }
331
332 entry.flags = 0;
333 entry.degree = 0;
334 self.num_vertices = self.num_vertices.saturating_sub(1);
335 self.status |= Self::STATUS_DIRTY;
336 self.generation = self.generation.wrapping_add(1);
337 true
338 }
339
340 pub fn add_edge(
342 &mut self,
343 source: TileVertexId,
344 target: TileVertexId,
345 weight: FixedWeight,
346 ) -> Option<TileEdgeId> {
347 if source as usize >= MAX_SHARD_VERTICES || target as usize >= MAX_SHARD_VERTICES {
349 return None;
350 }
351 if source == target {
352 return None; }
354
355 if !self.vertices[source as usize].is_active() {
357 self.add_vertex(source);
358 }
359 if !self.vertices[target as usize].is_active() {
360 self.add_vertex(target);
361 }
362
363 let src_entry = &self.vertices[source as usize];
365 let tgt_entry = &self.vertices[target as usize];
366 if src_entry.degree as usize >= MAX_DEGREE || tgt_entry.degree as usize >= MAX_DEGREE {
367 return None;
368 }
369
370 let edge_id = self.allocate_edge()?;
372
373 self.edges[edge_id as usize] = ShardEdge::new(source, target, weight);
375
376 let src_deg = self.vertices[source as usize].degree as usize;
378 self.adjacency[source as usize][src_deg] = AdjEntry {
379 neighbor: target,
380 edge_id,
381 };
382 self.vertices[source as usize].degree += 1;
383
384 let tgt_deg = self.vertices[target as usize].degree as usize;
385 self.adjacency[target as usize][tgt_deg] = AdjEntry {
386 neighbor: source,
387 edge_id,
388 };
389 self.vertices[target as usize].degree += 1;
390
391 self.num_edges += 1;
392 self.status |= Self::STATUS_DIRTY;
393 self.generation = self.generation.wrapping_add(1);
394
395 Some(edge_id)
396 }
397
398 pub fn remove_edge(&mut self, source: TileVertexId, target: TileVertexId) -> bool {
400 let edge_id = self.find_edge(source, target);
402 if edge_id.is_none() {
403 return false;
404 }
405 let edge_id = edge_id.unwrap();
406
407 self.edges[edge_id as usize].deactivate();
409
410 self.remove_from_adjacency(source, target, edge_id);
412 self.remove_from_adjacency(target, source, edge_id);
413
414 self.free_edge(edge_id);
416
417 self.num_edges = self.num_edges.saturating_sub(1);
418 self.status |= Self::STATUS_DIRTY;
419 self.generation = self.generation.wrapping_add(1);
420 true
421 }
422
423 pub fn update_weight(
425 &mut self,
426 source: TileVertexId,
427 target: TileVertexId,
428 new_weight: FixedWeight,
429 ) -> bool {
430 if let Some(edge_id) = self.find_edge(source, target) {
431 self.edges[edge_id as usize].weight = new_weight;
432 self.status |= Self::STATUS_DIRTY;
433 true
434 } else {
435 false
436 }
437 }
438
439 #[inline]
444 pub fn find_edge(&self, source: TileVertexId, target: TileVertexId) -> Option<TileEdgeId> {
445 if source as usize >= MAX_SHARD_VERTICES {
446 return None;
447 }
448
449 let entry = unsafe { self.vertices.get_unchecked(source as usize) };
451 if !entry.is_active() {
452 return None;
453 }
454
455 let degree = entry.degree as usize;
456 let adj_list = unsafe { self.adjacency.get_unchecked(source as usize) };
458
459 for i in 0..degree {
460 let adj = unsafe { adj_list.get_unchecked(i) };
462 if adj.neighbor == target {
463 return Some(adj.edge_id);
464 }
465 }
466 None
467 }
468
469 #[inline(always)]
473 pub unsafe fn find_edge_unchecked(&self, source: TileVertexId, target: TileVertexId) -> Option<TileEdgeId> {
474 unsafe {
475 let entry = self.vertices.get_unchecked(source as usize);
476 let degree = entry.degree as usize;
477 let adj_list = self.adjacency.get_unchecked(source as usize);
478
479 for i in 0..degree {
480 let adj = adj_list.get_unchecked(i);
481 if adj.neighbor == target {
482 return Some(adj.edge_id);
483 }
484 }
485 None
486 }
487 }
488
489 pub fn edge_weight(&self, source: TileVertexId, target: TileVertexId) -> Option<FixedWeight> {
491 self.find_edge(source, target)
492 .map(|eid| self.edges[eid as usize].weight)
493 }
494
495 #[inline(always)]
499 pub fn degree(&self, v: TileVertexId) -> u8 {
500 if v as usize >= MAX_SHARD_VERTICES {
501 return 0;
502 }
503 let entry = unsafe { self.vertices.get_unchecked(v as usize) };
505 if entry.is_active() {
506 entry.degree
507 } else {
508 0
509 }
510 }
511
512 #[inline]
516 pub fn neighbors(&self, v: TileVertexId) -> &[AdjEntry] {
517 if v as usize >= MAX_SHARD_VERTICES {
518 return &[];
519 }
520 let entry = unsafe { self.vertices.get_unchecked(v as usize) };
522 if !entry.is_active() {
523 return &[];
524 }
525 let degree = entry.degree as usize;
526 unsafe {
528 self.adjacency
529 .get_unchecked(v as usize)
530 .get_unchecked(..degree)
531 }
532 }
533
534 #[inline(always)]
538 pub unsafe fn neighbors_unchecked(&self, v: TileVertexId) -> &[AdjEntry] {
539 unsafe {
540 let entry = self.vertices.get_unchecked(v as usize);
541 let degree = entry.degree as usize;
542 self.adjacency
543 .get_unchecked(v as usize)
544 .get_unchecked(..degree)
545 }
546 }
547
548 #[inline]
550 pub fn is_connected(&self) -> bool {
551 self.status & Self::STATUS_CONNECTED != 0
552 }
553
554 pub fn recompute_components(&mut self) -> u16 {
559 let mut parent = [0u16; MAX_SHARD_VERTICES];
561 let mut rank = [0u8; MAX_SHARD_VERTICES];
562
563 for i in 0..MAX_SHARD_VERTICES {
566 parent[i] = i as u16;
567 }
568
569 #[inline(always)]
572 fn find(parent: &mut [u16; MAX_SHARD_VERTICES], mut x: u16) -> u16 {
573 let mut root = x;
575 while unsafe { *parent.get_unchecked(root as usize) } != root {
577 root = unsafe { *parent.get_unchecked(root as usize) };
578 }
579 while x != root {
581 let next = unsafe { *parent.get_unchecked(x as usize) };
582 unsafe { *parent.get_unchecked_mut(x as usize) = root };
583 x = next;
584 }
585 root
586 }
587
588 #[inline(always)]
591 fn union(
592 parent: &mut [u16; MAX_SHARD_VERTICES],
593 rank: &mut [u8; MAX_SHARD_VERTICES],
594 x: u16,
595 y: u16,
596 ) {
597 let px = find(parent, x);
598 let py = find(parent, y);
599 if px == py {
600 return;
601 }
602 unsafe {
604 let rpx = *rank.get_unchecked(px as usize);
605 let rpy = *rank.get_unchecked(py as usize);
606 if rpx < rpy {
607 *parent.get_unchecked_mut(px as usize) = py;
608 } else if rpx > rpy {
609 *parent.get_unchecked_mut(py as usize) = px;
610 } else {
611 *parent.get_unchecked_mut(py as usize) = px;
612 *rank.get_unchecked_mut(px as usize) = rpx + 1;
613 }
614 }
615 }
616
617 for edge in self.edges.iter() {
620 if edge.is_active() {
621 union(&mut parent, &mut rank, edge.source, edge.target);
622 }
623 }
624
625 let mut component_count = 0u16;
627 let mut component_map = [0xFFFFu16; MAX_SHARD_VERTICES];
628
629 for i in 0..MAX_SHARD_VERTICES {
630 let vertex = unsafe { self.vertices.get_unchecked_mut(i) };
632 if vertex.is_active() {
633 let root = find(&mut parent, i as u16);
634 let mapped = unsafe { *component_map.get_unchecked(root as usize) };
636 if mapped == 0xFFFF {
637 unsafe { *component_map.get_unchecked_mut(root as usize) = component_count };
638 vertex.component = component_count;
639 component_count += 1;
640 } else {
641 vertex.component = mapped;
642 }
643 }
644 }
645
646 self.num_components = component_count;
647 if component_count <= 1 && self.num_vertices > 0 {
648 self.status |= Self::STATUS_CONNECTED;
649 } else {
650 self.status &= !Self::STATUS_CONNECTED;
651 }
652 self.status &= !Self::STATUS_DIRTY;
653
654 component_count
655 }
656
657 fn allocate_edge(&mut self) -> Option<TileEdgeId> {
659 if self.free_edge_head != 0xFFFF {
661 let edge_id = self.free_edge_head;
662 self.free_edge_head = self.edges[edge_id as usize].source;
664 return Some(edge_id);
665 }
666
667 for i in 0..MAX_SHARD_EDGES {
669 if !self.edges[i].is_active() {
670 return Some(i as TileEdgeId);
671 }
672 }
673
674 None }
676
677 fn free_edge(&mut self, edge_id: TileEdgeId) {
679 self.edges[edge_id as usize].source = self.free_edge_head;
681 self.free_edge_head = edge_id;
682 }
683
684 fn remove_from_adjacency(&mut self, v: TileVertexId, neighbor: TileVertexId, edge_id: TileEdgeId) {
686 if v as usize >= MAX_SHARD_VERTICES {
687 return;
688 }
689 let degree = self.vertices[v as usize].degree as usize;
690
691 for i in 0..degree {
692 if self.adjacency[v as usize][i].neighbor == neighbor
693 && self.adjacency[v as usize][i].edge_id == edge_id
694 {
695 if i < degree - 1 {
697 self.adjacency[v as usize][i] = self.adjacency[v as usize][degree - 1];
698 }
699 self.vertices[v as usize].degree -= 1;
700 return;
701 }
702 }
703 }
704
705 pub const fn memory_size() -> usize {
707 size_of::<Self>()
708 }
709
710 #[inline]
722 pub fn for_each_active_vertex<F>(&self, mut f: F)
723 where
724 F: FnMut(TileVertexId, u8, u16),
725 {
726 const CHUNK_SIZE: usize = 8; for chunk_start in (0..MAX_SHARD_VERTICES).step_by(CHUNK_SIZE) {
730 let chunk_end = (chunk_start + CHUNK_SIZE).min(MAX_SHARD_VERTICES);
732
733 for i in chunk_start..chunk_end {
734 let entry = unsafe { self.vertices.get_unchecked(i) };
736 if entry.is_active() {
737 f(i as TileVertexId, entry.degree, entry.component);
738 }
739 }
740 }
741 }
742
743 #[inline]
750 pub fn for_each_active_edge<F>(&self, mut f: F)
751 where
752 F: FnMut(TileEdgeId, TileVertexId, TileVertexId, FixedWeight),
753 {
754 const CHUNK_SIZE: usize = 8;
756
757 for chunk_start in (0..MAX_SHARD_EDGES).step_by(CHUNK_SIZE) {
758 let chunk_end = (chunk_start + CHUNK_SIZE).min(MAX_SHARD_EDGES);
759
760 for i in chunk_start..chunk_end {
761 let edge = &self.edges[i];
762 if edge.is_active() {
763 f(i as TileEdgeId, edge.source, edge.target, edge.weight);
764 }
765 }
766 }
767 }
768
769 #[inline]
782 pub fn add_edges_batch(
783 &mut self,
784 edges: &[(TileVertexId, TileVertexId, FixedWeight)],
785 ) -> usize {
786 let mut added = 0usize;
787
788 for &(source, target, weight) in edges {
789 if self.add_edge(source, target, weight).is_some() {
790 added += 1;
791 }
792 }
793
794 if added > 0 {
796 self.generation = self.generation.wrapping_add(1);
797 }
798
799 added
800 }
801
802 #[inline]
810 pub fn active_edge_weights(&self) -> impl Iterator<Item = FixedWeight> + '_ {
811 self.edges.iter().filter(|e| e.is_active()).map(|e| e.weight)
812 }
813
814 #[inline]
818 pub fn total_weight_simd(&self) -> u64 {
819 let mut lanes = [0u64; 4];
820
821 for (i, edge) in self.edges.iter().enumerate() {
822 if edge.is_active() {
823 lanes[i % 4] += edge.weight as u64;
824 }
825 }
826
827 lanes[0] + lanes[1] + lanes[2] + lanes[3]
828 }
829
830 #[inline]
838 pub fn min_degree_vertex(&self) -> Option<(TileVertexId, u8)> {
839 let mut min_v: Option<TileVertexId> = None;
840 let mut min_deg = u8::MAX;
841
842 for i in 0..MAX_SHARD_VERTICES {
843 let entry = &self.vertices[i];
844 if entry.is_active() && entry.degree > 0 && entry.degree < min_deg {
846 min_deg = entry.degree;
847 min_v = Some(i as TileVertexId);
848
849 if min_deg == 1 {
851 break;
852 }
853 }
854 }
855
856 min_v.map(|v| (v, min_deg))
857 }
858}
859
860const _: () = assert!(size_of::<ShardEdge>() == 8, "ShardEdge must be 8 bytes");
862const _: () = assert!(size_of::<VertexEntry>() == 8, "VertexEntry must be 8 bytes");
863const _: () = assert!(size_of::<AdjEntry>() == 4, "AdjEntry must be 4 bytes");
864#[cfg(test)]
867mod tests {
868 use super::*;
869
870 #[test]
871 fn test_new_graph() {
872 let g = CompactGraph::new();
873 assert_eq!(g.num_vertices, 0);
874 assert_eq!(g.num_edges, 0);
875 }
876
877 #[test]
878 fn test_add_vertex() {
879 let mut g = CompactGraph::new();
880 assert!(g.add_vertex(0));
881 assert!(g.add_vertex(1));
882 assert!(!g.add_vertex(0)); assert_eq!(g.num_vertices, 2);
884 }
885
886 #[test]
887 fn test_add_edge() {
888 let mut g = CompactGraph::new();
889 let edge_id = g.add_edge(0, 1, 100);
890 assert!(edge_id.is_some());
891 assert_eq!(g.num_edges, 1);
892 assert_eq!(g.num_vertices, 2);
893 assert_eq!(g.degree(0), 1);
894 assert_eq!(g.degree(1), 1);
895 }
896
897 #[test]
898 fn test_find_edge() {
899 let mut g = CompactGraph::new();
900 g.add_edge(0, 1, 100);
901 assert!(g.find_edge(0, 1).is_some());
902 assert!(g.find_edge(1, 0).is_some());
903 assert!(g.find_edge(0, 2).is_none());
904 }
905
906 #[test]
907 fn test_remove_edge() {
908 let mut g = CompactGraph::new();
909 g.add_edge(0, 1, 100);
910 assert!(g.remove_edge(0, 1));
911 assert_eq!(g.num_edges, 0);
912 assert_eq!(g.degree(0), 0);
913 assert_eq!(g.degree(1), 0);
914 }
915
916 #[test]
917 fn test_update_weight() {
918 let mut g = CompactGraph::new();
919 g.add_edge(0, 1, 100);
920 assert!(g.update_weight(0, 1, 200));
921 assert_eq!(g.edge_weight(0, 1), Some(200));
922 }
923
924 #[test]
925 fn test_neighbors() {
926 let mut g = CompactGraph::new();
927 g.add_edge(0, 1, 100);
928 g.add_edge(0, 2, 200);
929 g.add_edge(0, 3, 300);
930
931 let neighbors = g.neighbors(0);
932 assert_eq!(neighbors.len(), 3);
933 }
934
935 #[test]
936 fn test_connected_components() {
937 let mut g = CompactGraph::new();
938 g.add_edge(0, 1, 100);
940 g.add_edge(1, 2, 100);
941 g.add_edge(3, 4, 100);
943
944 let count = g.recompute_components();
945 assert_eq!(count, 2);
946 assert!(!g.is_connected());
947 }
948
949 #[test]
950 fn test_connected_graph() {
951 let mut g = CompactGraph::new();
952 g.add_edge(0, 1, 100);
953 g.add_edge(1, 2, 100);
954 g.add_edge(2, 0, 100);
955
956 let count = g.recompute_components();
957 assert_eq!(count, 1);
958 assert!(g.is_connected());
959 }
960
961 #[test]
962 fn test_memory_size() {
963 let size = CompactGraph::memory_size();
965 assert!(size <= 65536, "CompactGraph exceeds 64KB: {} bytes", size);
966 }
967}