use crate::core::{Graph, IgraphError, IgraphResult, VertexId};
#[derive(Debug, Clone)]
pub struct GetBiadjacencyResult {
pub matrix: Vec<Vec<f64>>,
pub row_ids: Vec<VertexId>,
pub col_ids: Vec<VertexId>,
}
pub fn get_biadjacency_matrix(graph: &Graph, types: &[bool]) -> IgraphResult<GetBiadjacencyResult> {
let vcount = graph.vcount();
if types.len() != vcount as usize {
return Err(IgraphError::InvalidArgument(format!(
"get_biadjacency_matrix: types length ({}) != vcount ({})",
types.len(),
vcount
)));
}
let mut n1: usize = 0;
let mut n2: usize = 0;
let mut row_ids: Vec<VertexId> = Vec::new();
let mut col_ids: Vec<VertexId> = Vec::new();
let mut vertex_to_idx: Vec<u32> = vec![0; vcount as usize];
for v in 0..vcount {
if types[v as usize] {
#[allow(clippy::cast_possible_truncation)]
let idx = n2 as u32;
vertex_to_idx[v as usize] = idx;
col_ids.push(v);
n2 += 1;
} else {
#[allow(clippy::cast_possible_truncation)]
let idx = n1 as u32;
vertex_to_idx[v as usize] = idx;
row_ids.push(v);
n1 += 1;
}
}
let mut matrix = vec![vec![0.0_f64; n2]; n1];
let ecount = graph.ecount();
for eid in 0..ecount {
#[allow(clippy::cast_possible_truncation)]
let e = eid as u32;
let (src, tgt) = graph.edge(e)?;
let src_type = types[src as usize];
let tgt_type = types[tgt as usize];
if src_type == tgt_type {
continue;
}
let (row_v, col_v) = if src_type { (tgt, src) } else { (src, tgt) };
let ri = vertex_to_idx[row_v as usize] as usize;
let ci = vertex_to_idx[col_v as usize] as usize;
matrix[ri][ci] += 1.0;
}
Ok(GetBiadjacencyResult {
matrix,
row_ids,
col_ids,
})
}
#[cfg(test)]
#[allow(clippy::float_cmp)]
mod tests {
use super::*;
use crate::algorithms::constructors::biadjacency::biadjacency;
use crate::algorithms::games::bipartite::BipartiteMode;
#[test]
fn empty_graph() {
let g = Graph::new(0, false).unwrap();
let r = get_biadjacency_matrix(&g, &[]).unwrap();
assert!(r.matrix.is_empty());
assert!(r.row_ids.is_empty());
assert!(r.col_ids.is_empty());
}
#[test]
fn single_vertex_row() {
let g = Graph::new(1, false).unwrap();
let r = get_biadjacency_matrix(&g, &[false]).unwrap();
assert_eq!(r.matrix.len(), 1);
assert!(r.matrix[0].is_empty()); assert_eq!(r.row_ids, vec![0]);
assert!(r.col_ids.is_empty());
}
#[test]
fn single_vertex_col() {
let g = Graph::new(1, false).unwrap();
let r = get_biadjacency_matrix(&g, &[true]).unwrap();
assert!(r.matrix.is_empty()); assert!(r.row_ids.is_empty());
assert_eq!(r.col_ids, vec![0]);
}
#[test]
fn types_length_mismatch() {
let g = Graph::new(3, false).unwrap();
let result = get_biadjacency_matrix(&g, &[false, true]);
assert!(result.is_err());
}
#[test]
fn roundtrip_simple() {
let matrix: &[&[f64]] = &[&[1.0, 0.0, 1.0], &[0.0, 1.0, 0.0]];
let built = biadjacency(matrix, false, BipartiteMode::All, false).unwrap();
let extracted = get_biadjacency_matrix(&built.graph, &built.types).unwrap();
assert_eq!(extracted.matrix.len(), 2);
assert_eq!(extracted.matrix[0].len(), 3);
assert_eq!(extracted.matrix[0][0], 1.0);
assert_eq!(extracted.matrix[0][1], 0.0);
assert_eq!(extracted.matrix[0][2], 1.0);
assert_eq!(extracted.matrix[1][0], 0.0);
assert_eq!(extracted.matrix[1][1], 1.0);
assert_eq!(extracted.matrix[1][2], 0.0);
}
#[test]
fn roundtrip_with_multiple() {
let matrix: &[&[f64]] = &[&[1.0, 0.0, 2.0], &[0.0, 1.0, 0.0]];
let built = biadjacency(matrix, false, BipartiteMode::All, true).unwrap();
let extracted = get_biadjacency_matrix(&built.graph, &built.types).unwrap();
assert_eq!(extracted.matrix[0][0], 1.0);
assert_eq!(extracted.matrix[0][2], 2.0);
assert_eq!(extracted.matrix[1][1], 1.0);
}
#[test]
fn directed_out() {
let matrix: &[&[f64]] = &[&[1.0, 1.0]];
let built = biadjacency(matrix, true, BipartiteMode::Out, false).unwrap();
let extracted = get_biadjacency_matrix(&built.graph, &built.types).unwrap();
assert_eq!(extracted.matrix[0][0], 1.0);
assert_eq!(extracted.matrix[0][1], 1.0);
}
#[test]
fn directed_in() {
let matrix: &[&[f64]] = &[&[1.0, 1.0]];
let built = biadjacency(matrix, true, BipartiteMode::In, false).unwrap();
let extracted = get_biadjacency_matrix(&built.graph, &built.types).unwrap();
assert_eq!(extracted.matrix[0][0], 1.0);
assert_eq!(extracted.matrix[0][1], 1.0);
}
#[test]
fn directed_all_mutual() {
let matrix: &[&[f64]] = &[&[1.0]];
let built = biadjacency(matrix, true, BipartiteMode::All, false).unwrap();
let extracted = get_biadjacency_matrix(&built.graph, &built.types).unwrap();
assert_eq!(extracted.matrix[0][0], 2.0);
}
#[test]
fn ignores_same_partition_edges() {
let mut g = Graph::new(4, false).unwrap();
g.add_edges(vec![(0, 2), (0, 1), (1, 3)]).unwrap();
let types = vec![false, false, true, true];
let r = get_biadjacency_matrix(&g, &types).unwrap();
assert_eq!(r.matrix[0][0], 1.0); assert_eq!(r.matrix[0][1], 0.0);
assert_eq!(r.matrix[1][0], 0.0);
assert_eq!(r.matrix[1][1], 1.0); }
#[test]
fn row_col_ids_ordering() {
let mut g = Graph::new(4, false).unwrap();
g.add_edges(vec![(1, 0), (3, 2)]).unwrap();
let types = vec![true, false, true, false];
let r = get_biadjacency_matrix(&g, &types).unwrap();
assert_eq!(r.row_ids, vec![1, 3]);
assert_eq!(r.col_ids, vec![0, 2]);
assert_eq!(r.matrix.len(), 2); assert_eq!(r.matrix[0].len(), 2); assert_eq!(r.matrix[0][0], 1.0); assert_eq!(r.matrix[1][1], 1.0); }
#[test]
fn no_edges() {
let g = Graph::new(4, false).unwrap();
let types = vec![false, false, true, true];
let r = get_biadjacency_matrix(&g, &types).unwrap();
assert_eq!(r.matrix, vec![vec![0.0, 0.0], vec![0.0, 0.0]]);
}
#[test]
fn self_loop_ignored() {
let mut g = Graph::new(3, false).unwrap();
g.add_edges(vec![(0, 0), (0, 2)]).unwrap();
let types = vec![false, false, true];
let r = get_biadjacency_matrix(&g, &types).unwrap();
assert_eq!(r.matrix[0][0], 1.0); assert_eq!(r.matrix[1][0], 0.0);
}
}