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 {
272 neighbor: 0,
273 edge_id: 0,
274 }; MAX_DEGREE]; MAX_SHARD_VERTICES],
275 }
276 }
277
278 pub fn clear(&mut self) {
280 for v in self.vertices.iter_mut() {
281 *v = VertexEntry::new();
282 v.flags = 0; }
284 for e in self.edges.iter_mut() {
285 e.flags = 0;
286 }
287 self.num_vertices = 0;
288 self.num_edges = 0;
289 self.free_edge_head = 0xFFFF;
290 self.generation = self.generation.wrapping_add(1);
291 self.num_components = 0;
292 self.status = Self::STATUS_VALID | Self::STATUS_DIRTY;
293 }
294
295 pub fn add_vertex(&mut self, v: TileVertexId) -> bool {
297 if v as usize >= MAX_SHARD_VERTICES {
298 return false;
299 }
300
301 let entry = &mut self.vertices[v as usize];
302 if entry.is_active() {
303 return false; }
305
306 entry.flags = VertexEntry::FLAG_ACTIVE;
307 entry.degree = 0;
308 entry.component = 0;
309 entry.first_edge_idx = 0xFFFF;
310 self.num_vertices += 1;
311 self.status |= Self::STATUS_DIRTY;
312 true
313 }
314
315 pub fn remove_vertex(&mut self, v: TileVertexId) -> bool {
317 if v as usize >= MAX_SHARD_VERTICES {
318 return false;
319 }
320
321 let entry = &mut self.vertices[v as usize];
322 if !entry.is_active() {
323 return false;
324 }
325
326 for i in 0..entry.degree as usize {
328 let adj = &self.adjacency[v as usize][i];
329 if adj.edge_id < MAX_SHARD_EDGES as u16 {
330 self.edges[adj.edge_id as usize].deactivate();
331 self.num_edges = self.num_edges.saturating_sub(1);
332 }
333 }
334
335 entry.flags = 0;
336 entry.degree = 0;
337 self.num_vertices = self.num_vertices.saturating_sub(1);
338 self.status |= Self::STATUS_DIRTY;
339 self.generation = self.generation.wrapping_add(1);
340 true
341 }
342
343 pub fn add_edge(
345 &mut self,
346 source: TileVertexId,
347 target: TileVertexId,
348 weight: FixedWeight,
349 ) -> Option<TileEdgeId> {
350 if source as usize >= MAX_SHARD_VERTICES || target as usize >= MAX_SHARD_VERTICES {
352 return None;
353 }
354 if source == target {
355 return None; }
357
358 if !self.vertices[source as usize].is_active() {
360 self.add_vertex(source);
361 }
362 if !self.vertices[target as usize].is_active() {
363 self.add_vertex(target);
364 }
365
366 let src_entry = &self.vertices[source as usize];
368 let tgt_entry = &self.vertices[target as usize];
369 if src_entry.degree as usize >= MAX_DEGREE || tgt_entry.degree as usize >= MAX_DEGREE {
370 return None;
371 }
372
373 let edge_id = self.allocate_edge()?;
375
376 self.edges[edge_id as usize] = ShardEdge::new(source, target, weight);
378
379 let src_deg = self.vertices[source as usize].degree as usize;
381 self.adjacency[source as usize][src_deg] = AdjEntry {
382 neighbor: target,
383 edge_id,
384 };
385 self.vertices[source as usize].degree += 1;
386
387 let tgt_deg = self.vertices[target as usize].degree as usize;
388 self.adjacency[target as usize][tgt_deg] = AdjEntry {
389 neighbor: source,
390 edge_id,
391 };
392 self.vertices[target as usize].degree += 1;
393
394 self.num_edges += 1;
395 self.status |= Self::STATUS_DIRTY;
396 self.generation = self.generation.wrapping_add(1);
397
398 Some(edge_id)
399 }
400
401 pub fn remove_edge(&mut self, source: TileVertexId, target: TileVertexId) -> bool {
403 let edge_id = self.find_edge(source, target);
405 if edge_id.is_none() {
406 return false;
407 }
408 let edge_id = edge_id.unwrap();
409
410 self.edges[edge_id as usize].deactivate();
412
413 self.remove_from_adjacency(source, target, edge_id);
415 self.remove_from_adjacency(target, source, edge_id);
416
417 self.free_edge(edge_id);
419
420 self.num_edges = self.num_edges.saturating_sub(1);
421 self.status |= Self::STATUS_DIRTY;
422 self.generation = self.generation.wrapping_add(1);
423 true
424 }
425
426 pub fn update_weight(
428 &mut self,
429 source: TileVertexId,
430 target: TileVertexId,
431 new_weight: FixedWeight,
432 ) -> bool {
433 if let Some(edge_id) = self.find_edge(source, target) {
434 self.edges[edge_id as usize].weight = new_weight;
435 self.status |= Self::STATUS_DIRTY;
436 true
437 } else {
438 false
439 }
440 }
441
442 #[inline]
447 pub fn find_edge(&self, source: TileVertexId, target: TileVertexId) -> Option<TileEdgeId> {
448 if source as usize >= MAX_SHARD_VERTICES {
449 return None;
450 }
451
452 let entry = unsafe { self.vertices.get_unchecked(source as usize) };
454 if !entry.is_active() {
455 return None;
456 }
457
458 let degree = entry.degree as usize;
459 let adj_list = unsafe { self.adjacency.get_unchecked(source as usize) };
461
462 for i in 0..degree {
463 let adj = unsafe { adj_list.get_unchecked(i) };
465 if adj.neighbor == target {
466 return Some(adj.edge_id);
467 }
468 }
469 None
470 }
471
472 #[inline(always)]
476 pub unsafe fn find_edge_unchecked(
477 &self,
478 source: TileVertexId,
479 target: TileVertexId,
480 ) -> Option<TileEdgeId> {
481 unsafe {
482 let entry = self.vertices.get_unchecked(source as usize);
483 let degree = entry.degree as usize;
484 let adj_list = self.adjacency.get_unchecked(source as usize);
485
486 for i in 0..degree {
487 let adj = adj_list.get_unchecked(i);
488 if adj.neighbor == target {
489 return Some(adj.edge_id);
490 }
491 }
492 None
493 }
494 }
495
496 pub fn edge_weight(&self, source: TileVertexId, target: TileVertexId) -> Option<FixedWeight> {
498 self.find_edge(source, target)
499 .map(|eid| self.edges[eid as usize].weight)
500 }
501
502 #[inline(always)]
506 pub fn degree(&self, v: TileVertexId) -> u8 {
507 if v as usize >= MAX_SHARD_VERTICES {
508 return 0;
509 }
510 let entry = unsafe { self.vertices.get_unchecked(v as usize) };
512 if entry.is_active() {
513 entry.degree
514 } else {
515 0
516 }
517 }
518
519 #[inline]
523 pub fn neighbors(&self, v: TileVertexId) -> &[AdjEntry] {
524 if v as usize >= MAX_SHARD_VERTICES {
525 return &[];
526 }
527 let entry = unsafe { self.vertices.get_unchecked(v as usize) };
529 if !entry.is_active() {
530 return &[];
531 }
532 let degree = entry.degree as usize;
533 unsafe {
535 self.adjacency
536 .get_unchecked(v as usize)
537 .get_unchecked(..degree)
538 }
539 }
540
541 #[inline(always)]
545 pub unsafe fn neighbors_unchecked(&self, v: TileVertexId) -> &[AdjEntry] {
546 unsafe {
547 let entry = self.vertices.get_unchecked(v as usize);
548 let degree = entry.degree as usize;
549 self.adjacency
550 .get_unchecked(v as usize)
551 .get_unchecked(..degree)
552 }
553 }
554
555 #[inline]
557 pub fn is_connected(&self) -> bool {
558 self.status & Self::STATUS_CONNECTED != 0
559 }
560
561 pub fn recompute_components(&mut self) -> u16 {
566 let mut parent = [0u16; MAX_SHARD_VERTICES];
568 let mut rank = [0u8; MAX_SHARD_VERTICES];
569
570 for i in 0..MAX_SHARD_VERTICES {
573 parent[i] = i as u16;
574 }
575
576 #[inline(always)]
579 fn find(parent: &mut [u16; MAX_SHARD_VERTICES], mut x: u16) -> u16 {
580 let mut root = x;
582 while unsafe { *parent.get_unchecked(root as usize) } != root {
584 root = unsafe { *parent.get_unchecked(root as usize) };
585 }
586 while x != root {
588 let next = unsafe { *parent.get_unchecked(x as usize) };
589 unsafe { *parent.get_unchecked_mut(x as usize) = root };
590 x = next;
591 }
592 root
593 }
594
595 #[inline(always)]
598 fn union(
599 parent: &mut [u16; MAX_SHARD_VERTICES],
600 rank: &mut [u8; MAX_SHARD_VERTICES],
601 x: u16,
602 y: u16,
603 ) {
604 let px = find(parent, x);
605 let py = find(parent, y);
606 if px == py {
607 return;
608 }
609 unsafe {
611 let rpx = *rank.get_unchecked(px as usize);
612 let rpy = *rank.get_unchecked(py as usize);
613 if rpx < rpy {
614 *parent.get_unchecked_mut(px as usize) = py;
615 } else if rpx > rpy {
616 *parent.get_unchecked_mut(py as usize) = px;
617 } else {
618 *parent.get_unchecked_mut(py as usize) = px;
619 *rank.get_unchecked_mut(px as usize) = rpx + 1;
620 }
621 }
622 }
623
624 for edge in self.edges.iter() {
627 if edge.is_active() {
628 union(&mut parent, &mut rank, edge.source, edge.target);
629 }
630 }
631
632 let mut component_count = 0u16;
634 let mut component_map = [0xFFFFu16; MAX_SHARD_VERTICES];
635
636 for i in 0..MAX_SHARD_VERTICES {
637 let vertex = unsafe { self.vertices.get_unchecked_mut(i) };
639 if vertex.is_active() {
640 let root = find(&mut parent, i as u16);
641 let mapped = unsafe { *component_map.get_unchecked(root as usize) };
643 if mapped == 0xFFFF {
644 unsafe { *component_map.get_unchecked_mut(root as usize) = component_count };
645 vertex.component = component_count;
646 component_count += 1;
647 } else {
648 vertex.component = mapped;
649 }
650 }
651 }
652
653 self.num_components = component_count;
654 if component_count <= 1 && self.num_vertices > 0 {
655 self.status |= Self::STATUS_CONNECTED;
656 } else {
657 self.status &= !Self::STATUS_CONNECTED;
658 }
659 self.status &= !Self::STATUS_DIRTY;
660
661 component_count
662 }
663
664 fn allocate_edge(&mut self) -> Option<TileEdgeId> {
666 if self.free_edge_head != 0xFFFF {
668 let edge_id = self.free_edge_head;
669 self.free_edge_head = self.edges[edge_id as usize].source;
671 return Some(edge_id);
672 }
673
674 for i in 0..MAX_SHARD_EDGES {
676 if !self.edges[i].is_active() {
677 return Some(i as TileEdgeId);
678 }
679 }
680
681 None }
683
684 fn free_edge(&mut self, edge_id: TileEdgeId) {
686 self.edges[edge_id as usize].source = self.free_edge_head;
688 self.free_edge_head = edge_id;
689 }
690
691 fn remove_from_adjacency(
693 &mut self,
694 v: TileVertexId,
695 neighbor: TileVertexId,
696 edge_id: TileEdgeId,
697 ) {
698 if v as usize >= MAX_SHARD_VERTICES {
699 return;
700 }
701 let degree = self.vertices[v as usize].degree as usize;
702
703 for i in 0..degree {
704 if self.adjacency[v as usize][i].neighbor == neighbor
705 && self.adjacency[v as usize][i].edge_id == edge_id
706 {
707 if i < degree - 1 {
709 self.adjacency[v as usize][i] = self.adjacency[v as usize][degree - 1];
710 }
711 self.vertices[v as usize].degree -= 1;
712 return;
713 }
714 }
715 }
716
717 pub const fn memory_size() -> usize {
719 size_of::<Self>()
720 }
721
722 #[inline]
734 pub fn for_each_active_vertex<F>(&self, mut f: F)
735 where
736 F: FnMut(TileVertexId, u8, u16),
737 {
738 const CHUNK_SIZE: usize = 8; for chunk_start in (0..MAX_SHARD_VERTICES).step_by(CHUNK_SIZE) {
742 let chunk_end = (chunk_start + CHUNK_SIZE).min(MAX_SHARD_VERTICES);
744
745 for i in chunk_start..chunk_end {
746 let entry = unsafe { self.vertices.get_unchecked(i) };
748 if entry.is_active() {
749 f(i as TileVertexId, entry.degree, entry.component);
750 }
751 }
752 }
753 }
754
755 #[inline]
762 pub fn for_each_active_edge<F>(&self, mut f: F)
763 where
764 F: FnMut(TileEdgeId, TileVertexId, TileVertexId, FixedWeight),
765 {
766 const CHUNK_SIZE: usize = 8;
768
769 for chunk_start in (0..MAX_SHARD_EDGES).step_by(CHUNK_SIZE) {
770 let chunk_end = (chunk_start + CHUNK_SIZE).min(MAX_SHARD_EDGES);
771
772 for i in chunk_start..chunk_end {
773 let edge = &self.edges[i];
774 if edge.is_active() {
775 f(i as TileEdgeId, edge.source, edge.target, edge.weight);
776 }
777 }
778 }
779 }
780
781 #[inline]
794 pub fn add_edges_batch(
795 &mut self,
796 edges: &[(TileVertexId, TileVertexId, FixedWeight)],
797 ) -> usize {
798 let mut added = 0usize;
799
800 for &(source, target, weight) in edges {
801 if self.add_edge(source, target, weight).is_some() {
802 added += 1;
803 }
804 }
805
806 if added > 0 {
808 self.generation = self.generation.wrapping_add(1);
809 }
810
811 added
812 }
813
814 #[inline]
822 pub fn active_edge_weights(&self) -> impl Iterator<Item = FixedWeight> + '_ {
823 self.edges
824 .iter()
825 .filter(|e| e.is_active())
826 .map(|e| e.weight)
827 }
828
829 #[inline]
833 pub fn total_weight_simd(&self) -> u64 {
834 let mut lanes = [0u64; 4];
835
836 for (i, edge) in self.edges.iter().enumerate() {
837 if edge.is_active() {
838 lanes[i % 4] += edge.weight as u64;
839 }
840 }
841
842 lanes[0] + lanes[1] + lanes[2] + lanes[3]
843 }
844
845 #[inline]
853 pub fn min_degree_vertex(&self) -> Option<(TileVertexId, u8)> {
854 let mut min_v: Option<TileVertexId> = None;
855 let mut min_deg = u8::MAX;
856
857 for i in 0..MAX_SHARD_VERTICES {
858 let entry = &self.vertices[i];
859 if entry.is_active() && entry.degree > 0 && entry.degree < min_deg {
861 min_deg = entry.degree;
862 min_v = Some(i as TileVertexId);
863
864 if min_deg == 1 {
866 break;
867 }
868 }
869 }
870
871 min_v.map(|v| (v, min_deg))
872 }
873}
874
875const _: () = assert!(size_of::<ShardEdge>() == 8, "ShardEdge must be 8 bytes");
877const _: () = assert!(size_of::<VertexEntry>() == 8, "VertexEntry must be 8 bytes");
878const _: () = assert!(size_of::<AdjEntry>() == 4, "AdjEntry must be 4 bytes");
879#[cfg(test)]
882mod tests {
883 use super::*;
884
885 #[test]
886 fn test_new_graph() {
887 let g = CompactGraph::new();
888 assert_eq!(g.num_vertices, 0);
889 assert_eq!(g.num_edges, 0);
890 }
891
892 #[test]
893 fn test_add_vertex() {
894 let mut g = CompactGraph::new();
895 assert!(g.add_vertex(0));
896 assert!(g.add_vertex(1));
897 assert!(!g.add_vertex(0)); assert_eq!(g.num_vertices, 2);
899 }
900
901 #[test]
902 fn test_add_edge() {
903 let mut g = CompactGraph::new();
904 let edge_id = g.add_edge(0, 1, 100);
905 assert!(edge_id.is_some());
906 assert_eq!(g.num_edges, 1);
907 assert_eq!(g.num_vertices, 2);
908 assert_eq!(g.degree(0), 1);
909 assert_eq!(g.degree(1), 1);
910 }
911
912 #[test]
913 fn test_find_edge() {
914 let mut g = CompactGraph::new();
915 g.add_edge(0, 1, 100);
916 assert!(g.find_edge(0, 1).is_some());
917 assert!(g.find_edge(1, 0).is_some());
918 assert!(g.find_edge(0, 2).is_none());
919 }
920
921 #[test]
922 fn test_remove_edge() {
923 let mut g = CompactGraph::new();
924 g.add_edge(0, 1, 100);
925 assert!(g.remove_edge(0, 1));
926 assert_eq!(g.num_edges, 0);
927 assert_eq!(g.degree(0), 0);
928 assert_eq!(g.degree(1), 0);
929 }
930
931 #[test]
932 fn test_update_weight() {
933 let mut g = CompactGraph::new();
934 g.add_edge(0, 1, 100);
935 assert!(g.update_weight(0, 1, 200));
936 assert_eq!(g.edge_weight(0, 1), Some(200));
937 }
938
939 #[test]
940 fn test_neighbors() {
941 let mut g = CompactGraph::new();
942 g.add_edge(0, 1, 100);
943 g.add_edge(0, 2, 200);
944 g.add_edge(0, 3, 300);
945
946 let neighbors = g.neighbors(0);
947 assert_eq!(neighbors.len(), 3);
948 }
949
950 #[test]
951 fn test_connected_components() {
952 let mut g = CompactGraph::new();
953 g.add_edge(0, 1, 100);
955 g.add_edge(1, 2, 100);
956 g.add_edge(3, 4, 100);
958
959 let count = g.recompute_components();
960 assert_eq!(count, 2);
961 assert!(!g.is_connected());
962 }
963
964 #[test]
965 fn test_connected_graph() {
966 let mut g = CompactGraph::new();
967 g.add_edge(0, 1, 100);
968 g.add_edge(1, 2, 100);
969 g.add_edge(2, 0, 100);
970
971 let count = g.recompute_components();
972 assert_eq!(count, 1);
973 assert!(g.is_connected());
974 }
975
976 #[test]
977 fn test_memory_size() {
978 let size = CompactGraph::memory_size();
980 assert!(size <= 65536, "CompactGraph exceeds 64KB: {} bytes", size);
981 }
982}