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, Eq)]
pub enum WeightType {
F64,
F32,
}
impl WeightType {
pub const fn as_str(&self) -> &'static str {
match self {
WeightType::F64 => "f64",
WeightType::F32 => "f32",
}
}
pub fn parse(s: &str) -> StorageResult<Self> {
match s {
"f64" => Ok(WeightType::F64),
"f32" => Ok(WeightType::F32),
other => Err(StorageError::Invalid(format!(
"unknown weight width '{other}' (expected f32 or f64)"
))),
}
}
pub(crate) const fn data_type(self) -> arrow::datatypes::DataType {
match self {
WeightType::F64 => arrow::datatypes::DataType::Float64,
WeightType::F32 => arrow::datatypes::DataType::Float32,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct GraphEdge {
pub src: u64,
pub dst: u64,
pub weight: Option<f64>,
}
impl GraphEdge {
pub fn weighted(src: u64, dst: u64, weight: f64) -> 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 weight_type: WeightType,
pub weight_range: Option<(f64, f64)>,
pub num_nodes: Option<u64>,
pub properties: BTreeMap<String, String>,
}
impl Default for GraphWriteOptions {
fn default() -> Self {
Self {
node_id_width: NodeIdWidth::U32,
weight_type: WeightType::F64,
weight_range: None,
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 weight_type: WeightType,
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 = edge.weight.unwrap_or(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; 5] = [
"kind",
"node_id_width",
"weighted",
"num_nodes",
"weight_type",
];