use std::collections::BTreeMap;
use sprs::{CsMat, TriMat};
use crate::{StorageError, StorageResult};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum NodeIdWidth {
U32,
U64,
}
impl NodeIdWidth {
pub const fn as_str(&self) -> &'static str {
match self {
NodeIdWidth::U32 => "u32",
NodeIdWidth::U64 => "u64",
}
}
pub fn parse(s: &str) -> StorageResult<Self> {
match s {
"u32" => Ok(NodeIdWidth::U32),
"u64" => Ok(NodeIdWidth::U64),
other => Err(StorageError::Invalid(format!(
"unknown node-id width '{other}' (expected u32 or u64)"
))),
}
}
pub(crate) const fn data_type(self) -> arrow::datatypes::DataType {
match self {
NodeIdWidth::U32 => arrow::datatypes::DataType::UInt32,
NodeIdWidth::U64 => arrow::datatypes::DataType::UInt64,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct GraphEdge {
pub src: u64,
pub dst: u64,
pub weight: Option<f32>,
}
impl GraphEdge {
pub fn weighted(src: u64, dst: u64, weight: f32) -> Self {
Self {
src,
dst,
weight: Some(weight),
}
}
pub fn unweighted(src: u64, dst: u64) -> Self {
Self {
src,
dst,
weight: None,
}
}
}
#[derive(Debug, Clone)]
pub struct GraphWriteOptions {
pub node_id_width: NodeIdWidth,
pub num_nodes: Option<u64>,
pub properties: BTreeMap<String, String>,
}
impl Default for GraphWriteOptions {
fn default() -> Self {
Self {
node_id_width: NodeIdWidth::U32,
num_nodes: None,
properties: BTreeMap::new(),
}
}
}
impl GraphWriteOptions {
pub fn with_width(node_id_width: NodeIdWidth) -> Self {
Self {
node_id_width,
..Default::default()
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct StoredGraph {
pub edges: Vec<GraphEdge>,
pub node_id_width: NodeIdWidth,
pub num_nodes: u64,
pub weighted: bool,
}
impl StoredGraph {
pub fn to_csr(&self) -> StorageResult<CsMat<f64>> {
let n = self.num_nodes;
if n > usize::MAX as u64 {
return Err(StorageError::Overflow(format!(
"node count {n} exceeds the addressable size"
)));
}
let n = n as usize;
let mut trimat = TriMat::new((n, n));
trimat.reserve(self.edges.len());
for edge in &self.edges {
let (src, dst) = (edge.src as usize, edge.dst as usize);
if src >= n || dst >= n {
return Err(StorageError::Invalid(format!(
"edge ({}, {}) out of bounds for {}-node graph",
edge.src, edge.dst, n
)));
}
let weight = match edge.weight {
Some(w) => w as f64,
None => 1.0,
};
trimat.add_triplet(src, dst, weight);
}
let csr = trimat.to_csr();
if csr.rows() != n || csr.cols() != n {
return Err(StorageError::Invalid(format!(
"dimension mismatch after CSR conversion: expected {n}x{n}, got {}x{}",
csr.rows(),
csr.cols()
)));
}
Ok(csr)
}
}
pub(crate) const RESERVED_METADATA_KEYS: [&str; 4] =
["kind", "node_id_width", "weighted", "num_nodes"];