use std::convert::{Into, TryFrom};
use std::mem;
use std::num::Wrapping;
use std::ptr;
use std::slice;
use crate::error::GraphRoxError;
use crate::graph::graph_traits::GraphRepresentation;
use crate::graph::standard::StandardGraph;
use crate::matrix::{CsrSquareMatrix, MatrixRepresentation};
use crate::util::{self, constants::*};
const COMPRESSED_GRAPH_BYTES_MAGIC_NUMBER: u32 = 0x71ff7aed;
const COMPRESSED_GRAPH_BYTES_VERSION: u32 = 2;
#[repr(C, packed)]
struct CompressedGraphBytesHeader {
magic_number: u32,
version: u32,
adjacency_matrix_dimension: u64,
adjacency_matrix_entry_count: u64,
edge_count: u64,
vertex_count: u64,
is_undirected: u8,
compression_level: u8,
}
#[derive(Clone, Debug)]
pub struct CompressedGraph {
edge_count: u64,
vertex_count: u64,
adjacency_matrix: CsrSquareMatrix<u64>,
is_undirected: bool,
compression_level: u8,
}
impl CompressedGraph {
pub fn decompress(&self) -> StandardGraph {
let mut graph = if self.is_undirected {
StandardGraph::new_undirected()
} else {
StandardGraph::new_directed()
};
graph.add_vertex(self.vertex_count - 1, None);
for (entry, col, row) in &self.adjacency_matrix {
let mut curr = Wrapping(1);
for i in 0..64 {
if entry & curr.0 == curr.0 {
let absolute_col =
col * COMPRESSION_BLOCK_DIMENSION + i as u64 % COMPRESSION_BLOCK_DIMENSION;
let absolute_row =
row * COMPRESSION_BLOCK_DIMENSION + i as u64 / COMPRESSION_BLOCK_DIMENSION;
graph.add_edge(absolute_col, absolute_row);
}
curr <<= 1;
}
}
graph
}
pub fn compression_level(&self) -> u8 {
self.compression_level
}
pub fn get_compressed_matrix_entry(&self, col: u64, row: u64) -> u64 {
self.adjacency_matrix.get_entry(col, row)
}
}
impl GraphRepresentation for CompressedGraph {
fn is_undirected(&self) -> bool {
self.is_undirected
}
fn vertex_count(&self) -> u64 {
self.vertex_count
}
fn edge_count(&self) -> u64 {
self.edge_count
}
fn matrix_string(&self) -> String {
self.adjacency_matrix.to_string()
}
fn does_edge_exist(&self, from_vertex_id: u64, to_vertex_id: u64) -> bool {
let col = from_vertex_id / COMPRESSION_BLOCK_DIMENSION;
let row = to_vertex_id / COMPRESSION_BLOCK_DIMENSION;
let entry = self.adjacency_matrix.get_entry(col, row);
if entry == 0 {
return false;
}
let col_pos_in_entry = from_vertex_id % COMPRESSION_BLOCK_DIMENSION;
let row_pos_in_entry = to_vertex_id % COMPRESSION_BLOCK_DIMENSION;
let pos_in_entry = COMPRESSION_BLOCK_DIMENSION * row_pos_in_entry + col_pos_in_entry;
let bit_at_pos = (entry >> pos_in_entry) & 1;
bit_at_pos == 1
}
fn to_bytes(&self) -> Vec<u8> {
let header = CompressedGraphBytesHeader {
magic_number: COMPRESSED_GRAPH_BYTES_MAGIC_NUMBER.to_be(),
version: COMPRESSED_GRAPH_BYTES_VERSION.to_be(),
adjacency_matrix_dimension: self.adjacency_matrix.dimension().to_be(),
adjacency_matrix_entry_count: self.adjacency_matrix.entry_count().to_be(),
edge_count: self.edge_count.to_be(),
vertex_count: self.vertex_count.to_be(),
is_undirected: u8::from(self.is_undirected).to_be(),
compression_level: self.compression_level.to_be(),
};
let buffer_size = (self.adjacency_matrix.entry_count() * 3) as usize
* mem::size_of::<u64>()
+ mem::size_of::<CompressedGraphBytesHeader>();
let mut buffer = mem::MaybeUninit::new(Vec::with_capacity(buffer_size));
let buffer_ptr = unsafe {
(*buffer.as_mut_ptr()).set_len((*buffer.as_mut_ptr()).capacity());
(*buffer.as_mut_ptr()).as_mut_ptr() as *mut u8
};
let header_bytes = unsafe { util::as_byte_slice(&header) };
let mut pos: usize = 0;
for byte in header_bytes {
unsafe {
ptr::write(buffer_ptr.add(pos), *byte);
pos += 1;
}
}
for (entry, col, row) in &self.adjacency_matrix {
let entry_be = entry.to_be();
let col_be = col.to_be();
let row_be = row.to_be();
unsafe {
let entry_bytes = util::as_byte_slice(&entry_be);
let col_bytes = util::as_byte_slice(&col_be);
let row_bytes = util::as_byte_slice(&row_be);
for byte in entry_bytes {
ptr::write(buffer_ptr.add(pos), *byte);
pos += 1;
}
for byte in col_bytes {
ptr::write(buffer_ptr.add(pos), *byte);
pos += 1;
}
for byte in row_bytes {
ptr::write(buffer_ptr.add(pos), *byte);
pos += 1;
}
}
}
unsafe { buffer.assume_init() }
}
}
#[allow(clippy::from_over_into)]
impl Into<Vec<u8>> for CompressedGraph {
fn into(self) -> Vec<u8> {
self.to_bytes()
}
}
impl TryFrom<&[u8]> for CompressedGraph {
type Error = GraphRoxError;
fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
const HEADER_SIZE: usize = mem::size_of::<CompressedGraphBytesHeader>();
if bytes.len() < HEADER_SIZE {
return Err(GraphRoxError::InvalidFormat(String::from(
"Slice is too short to contain CompressedGraph header",
)));
}
let (head, header_slice, _) =
unsafe { bytes[0..HEADER_SIZE].align_to::<CompressedGraphBytesHeader>() };
if !head.is_empty() {
return Err(GraphRoxError::InvalidFormat(String::from(
"CompressedGraph header bytes were unaligned",
)));
}
let header = CompressedGraphBytesHeader {
magic_number: u32::from_be(header_slice[0].magic_number),
version: u32::from_be(header_slice[0].version),
adjacency_matrix_dimension: u64::from_be(header_slice[0].adjacency_matrix_dimension),
adjacency_matrix_entry_count: u64::from_be(
header_slice[0].adjacency_matrix_entry_count,
),
edge_count: u64::from_be(header_slice[0].edge_count),
vertex_count: u64::from_be(header_slice[0].vertex_count),
is_undirected: u8::from_be(header_slice[0].is_undirected),
compression_level: util::clamp_compression_level(u8::from_be(
header_slice[0].compression_level,
)),
};
if header.magic_number != COMPRESSED_GRAPH_BYTES_MAGIC_NUMBER {
return Err(GraphRoxError::InvalidFormat(String::from(
"Incorrect magic number",
)));
}
if header.version < COMPRESSED_GRAPH_BYTES_VERSION {
return Err(GraphRoxError::InvalidFormat(String::from(
"Outdated CompressedGraph version",
)));
} else if header.version != COMPRESSED_GRAPH_BYTES_VERSION {
return Err(GraphRoxError::InvalidFormat(String::from(
"Unrecognized CompressedGraph version",
)));
}
let expected_buffer_size = (header.adjacency_matrix_entry_count * 3) as usize
* mem::size_of::<u64>()
+ HEADER_SIZE;
#[allow(clippy::comparison_chain)]
if bytes.len() < expected_buffer_size {
return Err(GraphRoxError::InvalidFormat(String::from(
"Slice is too short to contain all expected graph edges",
)));
} else if bytes.len() > expected_buffer_size {
return Err(GraphRoxError::InvalidFormat(String::from(
"Slice is too long represent the expected graph edges",
)));
}
let mut compressed_graph_builder = CompressedGraphBuilder::new(
header.is_undirected == 1,
header.vertex_count,
header.compression_level,
);
let mut pos = HEADER_SIZE;
let bytes_ptr = bytes.as_ptr();
while pos < expected_buffer_size {
let entry_slice =
unsafe { slice::from_raw_parts(bytes_ptr.add(pos), mem::size_of::<u64>()) };
pos += mem::size_of::<u64>();
let col_slice =
unsafe { slice::from_raw_parts(bytes_ptr.add(pos), mem::size_of::<u64>()) };
pos += mem::size_of::<u64>();
let row_slice =
unsafe { slice::from_raw_parts(bytes_ptr.add(pos), mem::size_of::<u64>()) };
pos += mem::size_of::<u64>();
let entry = unsafe { u64::from_be_bytes(entry_slice.try_into().unwrap_unchecked()) };
let col = unsafe { u64::from_be_bytes(col_slice.try_into().unwrap_unchecked()) };
let row = unsafe { u64::from_be_bytes(row_slice.try_into().unwrap_unchecked()) };
compressed_graph_builder.add_compressed_matrix_entry(entry, col, row, None);
}
unsafe { Ok(compressed_graph_builder.finish()) }
}
}
pub struct CompressedGraphBuilder {
graph: CompressedGraph,
}
impl CompressedGraphBuilder {
pub fn new(is_undirected: bool, vertex_count: u64, compression_level: u8) -> Self {
let mut adjacency_matrix = CsrSquareMatrix::default();
let mut column_count = vertex_count / COMPRESSION_BLOCK_DIMENSION;
if vertex_count % COMPRESSION_BLOCK_DIMENSION == 0 && column_count != 0 {
column_count -= 1;
};
adjacency_matrix.set_entry(0, column_count, 0);
Self {
graph: CompressedGraph {
edge_count: 0,
vertex_count,
adjacency_matrix,
is_undirected,
compression_level: util::clamp_compression_level(compression_level),
},
}
}
pub fn add_compressed_matrix_entry(
&mut self,
entry: u64,
col: u64,
row: u64,
hamming_weight: Option<u64>,
) {
self.graph.edge_count += if let Some(w) = hamming_weight {
w
} else {
find_hamming_weight(entry)
};
self.graph.adjacency_matrix.set_entry(entry, col, row);
}
pub unsafe fn finish(self) -> CompressedGraph {
self.graph
}
}
#[inline]
fn find_hamming_weight(num: u64) -> u64 {
let mut weight = 0;
let mut curr = Wrapping(1);
for _ in 0..64 {
if num & curr.0 == curr.0 {
weight += 1;
}
curr <<= 1;
}
weight
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_compressed_graph_is_undirected() {
let graph = unsafe { CompressedGraphBuilder::new(true, 8, 7).finish() };
assert!(graph.is_undirected());
let graph = unsafe { CompressedGraphBuilder::new(false, 9, 8).finish() };
assert!(!graph.is_undirected());
}
#[test]
fn test_compressed_graph_vertex_count() {
let graph = unsafe { CompressedGraphBuilder::new(true, 8, 8).finish() };
assert_eq!(graph.vertex_count(), 8);
let graph = unsafe { CompressedGraphBuilder::new(false, 100, 15).finish() };
assert_eq!(graph.vertex_count(), 100);
}
#[test]
fn test_compressed_graph_edge_count() {
let mut graph = CompressedGraphBuilder::new(false, 15, 42);
graph.add_compressed_matrix_entry(u64::MAX, 1, 1, None);
let graph = unsafe { graph.finish() };
assert_eq!(graph.edge_count(), 64);
}
#[test]
fn test_compressed_graph_compression_level() {
let graph = unsafe { CompressedGraphBuilder::new(true, 9, 43).finish() };
assert_eq!(graph.compression_level(), 43);
let graph = unsafe { CompressedGraphBuilder::new(true, 9, 0).finish() };
assert_eq!(graph.compression_level(), 1);
let graph = unsafe { CompressedGraphBuilder::new(true, 9, 65).finish() };
assert_eq!(graph.compression_level(), 64);
}
#[test]
fn test_compressed_graph_get_compressed_matrix_entry() {
let mut graph = CompressedGraphBuilder::new(false, 15, 5);
graph.add_compressed_matrix_entry(42, 0, 0, None);
graph.add_compressed_matrix_entry(67, 0, 1, None);
graph.add_compressed_matrix_entry(u64::MAX, 1, 1, None);
let graph = unsafe { graph.finish() };
assert_eq!(graph.get_compressed_matrix_entry(0, 0), 42);
assert_eq!(graph.get_compressed_matrix_entry(0, 1), 67);
assert_eq!(graph.get_compressed_matrix_entry(1, 1), u64::MAX);
}
#[test]
fn test_compressed_graph_matrix_string() {
let mut graph = CompressedGraphBuilder::new(false, 16, 9);
graph.add_compressed_matrix_entry(300, 1, 1, None);
graph.add_compressed_matrix_entry(10, 2, 1, None);
let graph = unsafe { graph.finish() };
let expected = "[ 0, 0, 0 ]\r\n[ 0, 300, 10 ]\r\n[ 0, 0, 0 ]";
assert_eq!(expected, graph.matrix_string());
let mut graph = CompressedGraphBuilder::new(false, 27, 6);
graph.add_compressed_matrix_entry(9, 1, 1, None);
let graph = unsafe { graph.finish() };
let expected = "[ 0, 0, 0, 0 ]\r\n[ 0, 9, 0, 0 ]\r\n[ 0, 0, 0, 0 ]\r\n[ 0, 0, 0, 0 ]";
assert_eq!(expected, graph.matrix_string());
}
#[test]
fn test_compressed_graph_does_edge_exist() {
let mut graph = CompressedGraphBuilder::new(false, 16, 13);
graph.add_compressed_matrix_entry(u64::MAX, 1, 1, None);
let graph = unsafe { graph.finish() };
assert!(!graph.does_edge_exist(8, 7));
assert!(!graph.does_edge_exist(7, 8));
assert!(!graph.does_edge_exist(15, 16));
assert!(!graph.does_edge_exist(16, 15));
assert!(!graph.does_edge_exist(7, 15));
assert!(!graph.does_edge_exist(8, 16));
assert!(!graph.does_edge_exist(15, 7));
assert!(!graph.does_edge_exist(16, 8));
assert!(graph.does_edge_exist(8, 8));
assert!(graph.does_edge_exist(15, 15));
assert!(graph.does_edge_exist(15, 8));
assert!(graph.does_edge_exist(8, 15));
assert!(graph.does_edge_exist(10, 8));
assert!(graph.does_edge_exist(10, 10));
assert!(graph.does_edge_exist(14, 15));
}
#[test]
fn test_compressed_graph_to_from_bytes() {
let mut graph = CompressedGraphBuilder::new(false, 100, 18);
graph.add_compressed_matrix_entry(300, 1, 1, None);
graph.add_compressed_matrix_entry(10, 2, 1, None);
graph.add_compressed_matrix_entry(8, 4, 3, None);
let graph = unsafe { graph.finish() };
let bytes = graph.to_bytes();
let graph_from_bytes = CompressedGraph::try_from(bytes.as_slice()).unwrap();
assert_eq!(graph.is_undirected, graph_from_bytes.is_undirected);
assert_eq!(graph.compression_level, graph_from_bytes.compression_level);
assert_eq!(graph.edge_count, graph_from_bytes.edge_count);
assert_eq!(graph.vertex_count, graph_from_bytes.vertex_count);
assert_eq!(
graph.adjacency_matrix.dimension(),
graph_from_bytes.adjacency_matrix.dimension()
);
assert_eq!(
graph.adjacency_matrix.entry_count(),
graph_from_bytes.adjacency_matrix.entry_count()
);
let graph_matrix_entries = graph.adjacency_matrix.into_iter().collect::<Vec<_>>();
let graph_from_bytes_matrix_entries = graph_from_bytes
.adjacency_matrix
.into_iter()
.collect::<Vec<_>>();
assert_eq!(
graph_from_bytes_matrix_entries.len(),
graph_matrix_entries.len()
);
for entry in &graph_from_bytes.adjacency_matrix {
assert!(graph_matrix_entries.contains(&entry));
}
}
#[test]
fn test_compressed_graph_decompress() {
let mut graph = CompressedGraphBuilder::new(false, 16, 20);
graph.add_compressed_matrix_entry(u64::MAX, 1, 1, None);
let graph = unsafe { graph.finish() };
let decompressed_graph = graph.decompress();
assert_eq!(graph.is_undirected(), decompressed_graph.is_undirected());
assert_eq!(graph.vertex_count(), decompressed_graph.vertex_count());
assert_eq!(
decompressed_graph.into_iter().count() as u64,
graph.edge_count
);
for (col, row) in &decompressed_graph {
assert!(graph.does_edge_exist(col, row));
}
}
#[test]
fn test_compressed_graph_builder_new() {
let builder = CompressedGraphBuilder::new(true, 47, 30);
assert!(builder.graph.is_undirected);
assert_eq!(builder.graph.compression_level, 30);
assert_eq!(builder.graph.edge_count, 0);
assert_eq!(builder.graph.vertex_count(), 47);
assert_eq!(
builder.graph.adjacency_matrix.dimension(),
47 / COMPRESSION_BLOCK_DIMENSION + 1
);
let builder = CompressedGraphBuilder::new(true, 47, 200);
assert_eq!(builder.graph.compression_level, 64);
let builder = CompressedGraphBuilder::new(true, 47, 0);
assert_eq!(builder.graph.compression_level, 1);
}
#[test]
fn test_compressed_graph_builder_add_compressed_matrix_entry() {
let mut builder = CompressedGraphBuilder::new(false, 10, 29);
builder.add_compressed_matrix_entry(0x00000000000000ff, 2, 1, None);
assert_eq!(builder.graph.edge_count, 8);
builder.add_compressed_matrix_entry(0x00000000000ff001, 0, 1, Some(9));
assert_eq!(builder.graph.edge_count, 17);
assert_eq!(
builder.graph.adjacency_matrix.get_entry(0, 1),
0x00000000000ff001
);
assert_eq!(
builder.graph.adjacency_matrix.get_entry(2, 1),
0x00000000000000ff
);
}
#[test]
fn test_compressed_graph_builder_finish() {
let mut builder = CompressedGraphBuilder::new(false, 10, 2);
builder.add_compressed_matrix_entry(u64::MAX, 1, 1, None);
let builder_is_undirected = builder.graph.is_undirected;
let builder_compression_level = builder.graph.compression_level;
let builder_edge_count = builder.graph.edge_count;
let builder_vertex_count = builder.graph.vertex_count;
let builder_dimension = builder.graph.adjacency_matrix.dimension();
let builder_entry_count = builder.graph.adjacency_matrix.entry_count();
let graph = unsafe { builder.finish() };
assert_eq!(builder_is_undirected, graph.is_undirected);
assert_eq!(builder_compression_level, graph.compression_level);
assert_eq!(builder_edge_count, graph.edge_count);
assert_eq!(builder_vertex_count, graph.vertex_count);
assert_eq!(builder_dimension, graph.adjacency_matrix.dimension());
assert_eq!(builder_entry_count, graph.adjacency_matrix.entry_count());
assert!(!graph.does_edge_exist(8, 7));
assert!(!graph.does_edge_exist(7, 8));
assert!(!graph.does_edge_exist(15, 16));
assert!(!graph.does_edge_exist(16, 15));
assert!(!graph.does_edge_exist(7, 15));
assert!(!graph.does_edge_exist(8, 16));
assert!(!graph.does_edge_exist(15, 7));
assert!(!graph.does_edge_exist(16, 8));
assert!(graph.does_edge_exist(8, 8));
assert!(graph.does_edge_exist(15, 15));
assert!(graph.does_edge_exist(15, 8));
assert!(graph.does_edge_exist(8, 15));
assert!(graph.does_edge_exist(10, 8));
assert!(graph.does_edge_exist(10, 10));
assert!(graph.does_edge_exist(14, 15));
}
#[test]
fn test_find_hamming_weight() {
assert_eq!(find_hamming_weight(0), 0);
assert_eq!(find_hamming_weight(1), 1);
assert_eq!(find_hamming_weight(2), 1);
assert_eq!(find_hamming_weight(3), 2);
assert_eq!(find_hamming_weight(4), 1);
assert_eq!(find_hamming_weight(5), 2);
assert_eq!(find_hamming_weight(6), 2);
assert_eq!(find_hamming_weight(7), 3);
assert_eq!(find_hamming_weight(8), 1);
assert_eq!(find_hamming_weight(9), 2);
assert_eq!(find_hamming_weight(0x1111111111111111), 16);
assert_eq!(find_hamming_weight(0x2222222222222222), 16);
assert_eq!(find_hamming_weight(0x3333333333333333), 32);
assert_eq!(find_hamming_weight(0x7777777777777777), 48);
assert_eq!(find_hamming_weight(0xeeeeeeeeeeeeeeee), 48);
assert_eq!(find_hamming_weight(u64::MAX), 64);
}
}