#![allow(missing_docs)]
use crate::delta::{FixedWeight, TileEdgeId, TileVertexId};
use core::mem::size_of;
const CACHE_LINE_SIZE: usize = 64;
pub const MAX_SHARD_VERTICES: usize = 256;
pub const MAX_SHARD_EDGES: usize = 1024;
pub const MAX_DEGREE: usize = 32;
#[derive(Debug, Clone, Copy, Default)]
#[repr(C, align(8))]
pub struct ShardEdge {
pub source: TileVertexId,
pub target: TileVertexId,
pub weight: FixedWeight,
pub flags: u16,
}
impl ShardEdge {
pub const FLAG_ACTIVE: u16 = 0x0001;
pub const FLAG_IN_CUT: u16 = 0x0002;
pub const FLAG_TREE: u16 = 0x0004;
pub const FLAG_GHOST: u16 = 0x0008;
#[inline(always)]
pub const fn new(source: TileVertexId, target: TileVertexId, weight: FixedWeight) -> Self {
Self {
source,
target,
weight,
flags: Self::FLAG_ACTIVE,
}
}
#[inline(always)]
pub const fn is_active(&self) -> bool {
self.flags & Self::FLAG_ACTIVE != 0
}
#[inline(always)]
pub const fn is_in_cut(&self) -> bool {
self.flags & Self::FLAG_IN_CUT != 0
}
#[inline(always)]
pub const fn is_tree(&self) -> bool {
self.flags & Self::FLAG_TREE != 0
}
#[inline(always)]
pub const fn is_ghost(&self) -> bool {
self.flags & Self::FLAG_GHOST != 0
}
#[inline(always)]
pub fn deactivate(&mut self) {
self.flags &= !Self::FLAG_ACTIVE;
}
#[inline(always)]
pub fn mark_in_cut(&mut self) {
self.flags |= Self::FLAG_IN_CUT;
}
#[inline(always)]
pub fn clear_cut(&mut self) {
self.flags &= !Self::FLAG_IN_CUT;
}
}
#[derive(Debug, Clone, Copy, Default)]
#[repr(C, align(8))]
pub struct VertexEntry {
pub degree: u8,
pub flags: u8,
pub component: u16,
pub first_edge_idx: u16,
pub _reserved: u16,
}
impl VertexEntry {
pub const FLAG_ACTIVE: u8 = 0x01;
pub const FLAG_BOUNDARY: u8 = 0x02;
pub const FLAG_SIDE: u8 = 0x04;
pub const FLAG_GHOST: u8 = 0x08;
#[inline(always)]
pub const fn new() -> Self {
Self {
degree: 0,
flags: Self::FLAG_ACTIVE,
component: 0,
first_edge_idx: 0xFFFF, _reserved: 0,
}
}
#[inline(always)]
pub const fn is_active(&self) -> bool {
self.flags & Self::FLAG_ACTIVE != 0
}
#[inline(always)]
pub const fn side(&self) -> u8 {
(self.flags & Self::FLAG_SIDE) >> 2
}
#[inline(always)]
pub fn set_side(&mut self, side: u8) {
self.flags = (self.flags & !Self::FLAG_SIDE) | ((side & 1) << 2);
}
}
#[derive(Debug, Clone, Copy, Default)]
#[repr(C)]
pub struct AdjEntry {
pub neighbor: TileVertexId,
pub edge_id: TileEdgeId,
}
#[repr(C, align(64))]
pub struct CompactGraph {
pub num_vertices: u16,
pub num_edges: u16,
pub free_edge_head: u16,
pub generation: u16,
pub num_components: u16,
pub status: u16,
_hot_pad: [u8; 52],
pub vertices: [VertexEntry; MAX_SHARD_VERTICES],
pub edges: [ShardEdge; MAX_SHARD_EDGES],
pub adjacency: [[AdjEntry; MAX_DEGREE]; MAX_SHARD_VERTICES],
}
impl Default for CompactGraph {
#[inline]
fn default() -> Self {
Self::new()
}
}
impl CompactGraph {
pub const STATUS_VALID: u16 = 0x0001;
pub const STATUS_DIRTY: u16 = 0x0002;
pub const STATUS_CONNECTED: u16 = 0x0004;
pub const fn new() -> Self {
Self {
num_vertices: 0,
num_edges: 0,
free_edge_head: 0xFFFF,
generation: 0,
num_components: 0,
status: Self::STATUS_VALID,
_hot_pad: [0; 52],
vertices: [VertexEntry {
degree: 0,
flags: 0, component: 0,
first_edge_idx: 0xFFFF,
_reserved: 0,
}; MAX_SHARD_VERTICES],
edges: [ShardEdge {
source: 0,
target: 0,
weight: 0,
flags: 0,
}; MAX_SHARD_EDGES],
adjacency: [[AdjEntry {
neighbor: 0,
edge_id: 0,
}; MAX_DEGREE]; MAX_SHARD_VERTICES],
}
}
pub fn clear(&mut self) {
for v in self.vertices.iter_mut() {
*v = VertexEntry::new();
v.flags = 0; }
for e in self.edges.iter_mut() {
e.flags = 0;
}
self.num_vertices = 0;
self.num_edges = 0;
self.free_edge_head = 0xFFFF;
self.generation = self.generation.wrapping_add(1);
self.num_components = 0;
self.status = Self::STATUS_VALID | Self::STATUS_DIRTY;
}
pub fn add_vertex(&mut self, v: TileVertexId) -> bool {
if v as usize >= MAX_SHARD_VERTICES {
return false;
}
let entry = &mut self.vertices[v as usize];
if entry.is_active() {
return false; }
entry.flags = VertexEntry::FLAG_ACTIVE;
entry.degree = 0;
entry.component = 0;
entry.first_edge_idx = 0xFFFF;
self.num_vertices += 1;
self.status |= Self::STATUS_DIRTY;
true
}
pub fn remove_vertex(&mut self, v: TileVertexId) -> bool {
if v as usize >= MAX_SHARD_VERTICES {
return false;
}
let entry = &mut self.vertices[v as usize];
if !entry.is_active() {
return false;
}
for i in 0..entry.degree as usize {
let adj = &self.adjacency[v as usize][i];
if adj.edge_id < MAX_SHARD_EDGES as u16 {
self.edges[adj.edge_id as usize].deactivate();
self.num_edges = self.num_edges.saturating_sub(1);
}
}
entry.flags = 0;
entry.degree = 0;
self.num_vertices = self.num_vertices.saturating_sub(1);
self.status |= Self::STATUS_DIRTY;
self.generation = self.generation.wrapping_add(1);
true
}
pub fn add_edge(
&mut self,
source: TileVertexId,
target: TileVertexId,
weight: FixedWeight,
) -> Option<TileEdgeId> {
if source as usize >= MAX_SHARD_VERTICES || target as usize >= MAX_SHARD_VERTICES {
return None;
}
if source == target {
return None; }
if !self.vertices[source as usize].is_active() {
self.add_vertex(source);
}
if !self.vertices[target as usize].is_active() {
self.add_vertex(target);
}
let src_entry = &self.vertices[source as usize];
let tgt_entry = &self.vertices[target as usize];
if src_entry.degree as usize >= MAX_DEGREE || tgt_entry.degree as usize >= MAX_DEGREE {
return None;
}
let edge_id = self.allocate_edge()?;
self.edges[edge_id as usize] = ShardEdge::new(source, target, weight);
let src_deg = self.vertices[source as usize].degree as usize;
self.adjacency[source as usize][src_deg] = AdjEntry {
neighbor: target,
edge_id,
};
self.vertices[source as usize].degree += 1;
let tgt_deg = self.vertices[target as usize].degree as usize;
self.adjacency[target as usize][tgt_deg] = AdjEntry {
neighbor: source,
edge_id,
};
self.vertices[target as usize].degree += 1;
self.num_edges += 1;
self.status |= Self::STATUS_DIRTY;
self.generation = self.generation.wrapping_add(1);
Some(edge_id)
}
pub fn remove_edge(&mut self, source: TileVertexId, target: TileVertexId) -> bool {
let edge_id = self.find_edge(source, target);
if edge_id.is_none() {
return false;
}
let edge_id = edge_id.unwrap();
self.edges[edge_id as usize].deactivate();
self.remove_from_adjacency(source, target, edge_id);
self.remove_from_adjacency(target, source, edge_id);
self.free_edge(edge_id);
self.num_edges = self.num_edges.saturating_sub(1);
self.status |= Self::STATUS_DIRTY;
self.generation = self.generation.wrapping_add(1);
true
}
pub fn update_weight(
&mut self,
source: TileVertexId,
target: TileVertexId,
new_weight: FixedWeight,
) -> bool {
if let Some(edge_id) = self.find_edge(source, target) {
self.edges[edge_id as usize].weight = new_weight;
self.status |= Self::STATUS_DIRTY;
true
} else {
false
}
}
#[inline]
pub fn find_edge(&self, source: TileVertexId, target: TileVertexId) -> Option<TileEdgeId> {
if source as usize >= MAX_SHARD_VERTICES {
return None;
}
let entry = unsafe { self.vertices.get_unchecked(source as usize) };
if !entry.is_active() {
return None;
}
let degree = entry.degree as usize;
let adj_list = unsafe { self.adjacency.get_unchecked(source as usize) };
for i in 0..degree {
let adj = unsafe { adj_list.get_unchecked(i) };
if adj.neighbor == target {
return Some(adj.edge_id);
}
}
None
}
#[inline(always)]
pub unsafe fn find_edge_unchecked(
&self,
source: TileVertexId,
target: TileVertexId,
) -> Option<TileEdgeId> {
unsafe {
let entry = self.vertices.get_unchecked(source as usize);
let degree = entry.degree as usize;
let adj_list = self.adjacency.get_unchecked(source as usize);
for i in 0..degree {
let adj = adj_list.get_unchecked(i);
if adj.neighbor == target {
return Some(adj.edge_id);
}
}
None
}
}
pub fn edge_weight(&self, source: TileVertexId, target: TileVertexId) -> Option<FixedWeight> {
self.find_edge(source, target)
.map(|eid| self.edges[eid as usize].weight)
}
#[inline(always)]
pub fn degree(&self, v: TileVertexId) -> u8 {
if v as usize >= MAX_SHARD_VERTICES {
return 0;
}
let entry = unsafe { self.vertices.get_unchecked(v as usize) };
if entry.is_active() {
entry.degree
} else {
0
}
}
#[inline]
pub fn neighbors(&self, v: TileVertexId) -> &[AdjEntry] {
if v as usize >= MAX_SHARD_VERTICES {
return &[];
}
let entry = unsafe { self.vertices.get_unchecked(v as usize) };
if !entry.is_active() {
return &[];
}
let degree = entry.degree as usize;
unsafe {
self.adjacency
.get_unchecked(v as usize)
.get_unchecked(..degree)
}
}
#[inline(always)]
pub unsafe fn neighbors_unchecked(&self, v: TileVertexId) -> &[AdjEntry] {
unsafe {
let entry = self.vertices.get_unchecked(v as usize);
let degree = entry.degree as usize;
self.adjacency
.get_unchecked(v as usize)
.get_unchecked(..degree)
}
}
#[inline]
pub fn is_connected(&self) -> bool {
self.status & Self::STATUS_CONNECTED != 0
}
pub fn recompute_components(&mut self) -> u16 {
let mut parent = [0u16; MAX_SHARD_VERTICES];
let mut rank = [0u8; MAX_SHARD_VERTICES];
for i in 0..MAX_SHARD_VERTICES {
parent[i] = i as u16;
}
#[inline(always)]
fn find(parent: &mut [u16; MAX_SHARD_VERTICES], mut x: u16) -> u16 {
let mut root = x;
while unsafe { *parent.get_unchecked(root as usize) } != root {
root = unsafe { *parent.get_unchecked(root as usize) };
}
while x != root {
let next = unsafe { *parent.get_unchecked(x as usize) };
unsafe { *parent.get_unchecked_mut(x as usize) = root };
x = next;
}
root
}
#[inline(always)]
fn union(
parent: &mut [u16; MAX_SHARD_VERTICES],
rank: &mut [u8; MAX_SHARD_VERTICES],
x: u16,
y: u16,
) {
let px = find(parent, x);
let py = find(parent, y);
if px == py {
return;
}
unsafe {
let rpx = *rank.get_unchecked(px as usize);
let rpy = *rank.get_unchecked(py as usize);
if rpx < rpy {
*parent.get_unchecked_mut(px as usize) = py;
} else if rpx > rpy {
*parent.get_unchecked_mut(py as usize) = px;
} else {
*parent.get_unchecked_mut(py as usize) = px;
*rank.get_unchecked_mut(px as usize) = rpx + 1;
}
}
}
for edge in self.edges.iter() {
if edge.is_active() {
union(&mut parent, &mut rank, edge.source, edge.target);
}
}
let mut component_count = 0u16;
let mut component_map = [0xFFFFu16; MAX_SHARD_VERTICES];
for i in 0..MAX_SHARD_VERTICES {
let vertex = unsafe { self.vertices.get_unchecked_mut(i) };
if vertex.is_active() {
let root = find(&mut parent, i as u16);
let mapped = unsafe { *component_map.get_unchecked(root as usize) };
if mapped == 0xFFFF {
unsafe { *component_map.get_unchecked_mut(root as usize) = component_count };
vertex.component = component_count;
component_count += 1;
} else {
vertex.component = mapped;
}
}
}
self.num_components = component_count;
if component_count <= 1 && self.num_vertices > 0 {
self.status |= Self::STATUS_CONNECTED;
} else {
self.status &= !Self::STATUS_CONNECTED;
}
self.status &= !Self::STATUS_DIRTY;
component_count
}
fn allocate_edge(&mut self) -> Option<TileEdgeId> {
if self.free_edge_head != 0xFFFF {
let edge_id = self.free_edge_head;
self.free_edge_head = self.edges[edge_id as usize].source;
return Some(edge_id);
}
for i in 0..MAX_SHARD_EDGES {
if !self.edges[i].is_active() {
return Some(i as TileEdgeId);
}
}
None }
fn free_edge(&mut self, edge_id: TileEdgeId) {
self.edges[edge_id as usize].source = self.free_edge_head;
self.free_edge_head = edge_id;
}
fn remove_from_adjacency(
&mut self,
v: TileVertexId,
neighbor: TileVertexId,
edge_id: TileEdgeId,
) {
if v as usize >= MAX_SHARD_VERTICES {
return;
}
let degree = self.vertices[v as usize].degree as usize;
for i in 0..degree {
if self.adjacency[v as usize][i].neighbor == neighbor
&& self.adjacency[v as usize][i].edge_id == edge_id
{
if i < degree - 1 {
self.adjacency[v as usize][i] = self.adjacency[v as usize][degree - 1];
}
self.vertices[v as usize].degree -= 1;
return;
}
}
}
pub const fn memory_size() -> usize {
size_of::<Self>()
}
#[inline]
pub fn for_each_active_vertex<F>(&self, mut f: F)
where
F: FnMut(TileVertexId, u8, u16),
{
const CHUNK_SIZE: usize = 8;
for chunk_start in (0..MAX_SHARD_VERTICES).step_by(CHUNK_SIZE) {
let chunk_end = (chunk_start + CHUNK_SIZE).min(MAX_SHARD_VERTICES);
for i in chunk_start..chunk_end {
let entry = unsafe { self.vertices.get_unchecked(i) };
if entry.is_active() {
f(i as TileVertexId, entry.degree, entry.component);
}
}
}
}
#[inline]
pub fn for_each_active_edge<F>(&self, mut f: F)
where
F: FnMut(TileEdgeId, TileVertexId, TileVertexId, FixedWeight),
{
const CHUNK_SIZE: usize = 8;
for chunk_start in (0..MAX_SHARD_EDGES).step_by(CHUNK_SIZE) {
let chunk_end = (chunk_start + CHUNK_SIZE).min(MAX_SHARD_EDGES);
for i in chunk_start..chunk_end {
let edge = &self.edges[i];
if edge.is_active() {
f(i as TileEdgeId, edge.source, edge.target, edge.weight);
}
}
}
}
#[inline]
pub fn add_edges_batch(
&mut self,
edges: &[(TileVertexId, TileVertexId, FixedWeight)],
) -> usize {
let mut added = 0usize;
for &(source, target, weight) in edges {
if self.add_edge(source, target, weight).is_some() {
added += 1;
}
}
if added > 0 {
self.generation = self.generation.wrapping_add(1);
}
added
}
#[inline]
pub fn active_edge_weights(&self) -> impl Iterator<Item = FixedWeight> + '_ {
self.edges
.iter()
.filter(|e| e.is_active())
.map(|e| e.weight)
}
#[inline]
pub fn total_weight_simd(&self) -> u64 {
let mut lanes = [0u64; 4];
for (i, edge) in self.edges.iter().enumerate() {
if edge.is_active() {
lanes[i % 4] += edge.weight as u64;
}
}
lanes[0] + lanes[1] + lanes[2] + lanes[3]
}
#[inline]
pub fn min_degree_vertex(&self) -> Option<(TileVertexId, u8)> {
let mut min_v: Option<TileVertexId> = None;
let mut min_deg = u8::MAX;
for i in 0..MAX_SHARD_VERTICES {
let entry = &self.vertices[i];
if entry.is_active() && entry.degree > 0 && entry.degree < min_deg {
min_deg = entry.degree;
min_v = Some(i as TileVertexId);
if min_deg == 1 {
break;
}
}
}
min_v.map(|v| (v, min_deg))
}
}
const _: () = assert!(size_of::<ShardEdge>() == 8, "ShardEdge must be 8 bytes");
const _: () = assert!(size_of::<VertexEntry>() == 8, "VertexEntry must be 8 bytes");
const _: () = assert!(size_of::<AdjEntry>() == 4, "AdjEntry must be 4 bytes");
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_new_graph() {
let g = CompactGraph::new();
assert_eq!(g.num_vertices, 0);
assert_eq!(g.num_edges, 0);
}
#[test]
fn test_add_vertex() {
let mut g = CompactGraph::new();
assert!(g.add_vertex(0));
assert!(g.add_vertex(1));
assert!(!g.add_vertex(0)); assert_eq!(g.num_vertices, 2);
}
#[test]
fn test_add_edge() {
let mut g = CompactGraph::new();
let edge_id = g.add_edge(0, 1, 100);
assert!(edge_id.is_some());
assert_eq!(g.num_edges, 1);
assert_eq!(g.num_vertices, 2);
assert_eq!(g.degree(0), 1);
assert_eq!(g.degree(1), 1);
}
#[test]
fn test_find_edge() {
let mut g = CompactGraph::new();
g.add_edge(0, 1, 100);
assert!(g.find_edge(0, 1).is_some());
assert!(g.find_edge(1, 0).is_some());
assert!(g.find_edge(0, 2).is_none());
}
#[test]
fn test_remove_edge() {
let mut g = CompactGraph::new();
g.add_edge(0, 1, 100);
assert!(g.remove_edge(0, 1));
assert_eq!(g.num_edges, 0);
assert_eq!(g.degree(0), 0);
assert_eq!(g.degree(1), 0);
}
#[test]
fn test_update_weight() {
let mut g = CompactGraph::new();
g.add_edge(0, 1, 100);
assert!(g.update_weight(0, 1, 200));
assert_eq!(g.edge_weight(0, 1), Some(200));
}
#[test]
fn test_neighbors() {
let mut g = CompactGraph::new();
g.add_edge(0, 1, 100);
g.add_edge(0, 2, 200);
g.add_edge(0, 3, 300);
let neighbors = g.neighbors(0);
assert_eq!(neighbors.len(), 3);
}
#[test]
fn test_connected_components() {
let mut g = CompactGraph::new();
g.add_edge(0, 1, 100);
g.add_edge(1, 2, 100);
g.add_edge(3, 4, 100);
let count = g.recompute_components();
assert_eq!(count, 2);
assert!(!g.is_connected());
}
#[test]
fn test_connected_graph() {
let mut g = CompactGraph::new();
g.add_edge(0, 1, 100);
g.add_edge(1, 2, 100);
g.add_edge(2, 0, 100);
let count = g.recompute_components();
assert_eq!(count, 1);
assert!(g.is_connected());
}
#[test]
fn test_memory_size() {
let size = CompactGraph::memory_size();
assert!(size <= 65536, "CompactGraph exceeds 64KB: {} bytes", size);
}
}