use ndarray::Array2;
use std::ops::Range;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct BetaEdge {
pub a: usize,
pub b: usize,
}
#[derive(Debug, Clone)]
pub struct BetaCouplingGraph {
pub num_blocks: usize,
edges: Vec<BetaEdge>,
adj_start: Vec<usize>,
adj_targets: Vec<usize>,
}
impl BetaCouplingGraph {
pub fn build<M>(block_offsets: &[Range<usize>], htbeta_rows: &[M]) -> Self
where
M: BlockHtbetaRow,
{
let num_blocks = block_offsets.len();
if num_blocks == 0 {
return Self {
num_blocks: 0,
edges: Vec::new(),
adj_start: vec![0],
adj_targets: Vec::new(),
};
}
let mut edge_set: Vec<(usize, usize)> = Vec::new();
for row in htbeta_rows {
let mut active: Vec<usize> = Vec::new();
for (b, range) in block_offsets.iter().enumerate() {
if row.has_nonzero_in_range(range.clone()) {
active.push(b);
}
}
for i in 0..active.len() {
for j in (i + 1)..active.len() {
let lo = active[i].min(active[j]);
let hi = active[i].max(active[j]);
edge_set.push((lo, hi));
}
}
}
edge_set.sort_unstable();
edge_set.dedup();
let edges: Vec<BetaEdge> = edge_set.iter().map(|&(a, b)| BetaEdge { a, b }).collect();
let mut degree = vec![0usize; num_blocks];
for &BetaEdge { a, b } in &edges {
degree[a] += 1;
degree[b] += 1;
}
let mut adj_start = vec![0usize; num_blocks + 1];
for i in 0..num_blocks {
adj_start[i + 1] = adj_start[i] + degree[i];
}
let total_adj = adj_start[num_blocks];
let mut adj_targets = vec![0usize; total_adj];
let mut cursor = adj_start[..num_blocks].to_vec();
for &BetaEdge { a, b } in &edges {
adj_targets[cursor[a]] = b;
cursor[a] += 1;
adj_targets[cursor[b]] = a;
cursor[b] += 1;
}
Self {
num_blocks,
edges,
adj_start,
adj_targets,
}
}
pub fn neighbours(&self, node: usize) -> &[usize] {
let start = self.adj_start[node];
let end = self.adj_start[node + 1];
&self.adj_targets[start..end]
}
pub fn edges(&self) -> &[BetaEdge] {
&self.edges
}
pub fn connected_components(&self) -> (Vec<usize>, usize) {
let n = self.num_blocks;
let mut parent: Vec<usize> = (0..n).collect();
let mut rank = vec![0u8; n];
fn find(parent: &mut Vec<usize>, mut x: usize) -> usize {
while parent[x] != x {
parent[x] = parent[parent[x]]; x = parent[x];
}
x
}
fn union(parent: &mut Vec<usize>, rank: &mut Vec<u8>, x: usize, y: usize) {
let rx = find(parent, x);
let ry = find(parent, y);
if rx == ry {
return;
}
if rank[rx] < rank[ry] {
parent[rx] = ry;
} else if rank[rx] > rank[ry] {
parent[ry] = rx;
} else {
parent[ry] = rx;
rank[rx] += 1;
}
}
for &BetaEdge { a, b } in &self.edges {
union(&mut parent, &mut rank, a, b);
}
let mut label_map = vec![usize::MAX; n];
let mut next_label = 0usize;
let mut labels = vec![0usize; n];
for i in 0..n {
let root = find(&mut parent, i);
if label_map[root] == usize::MAX {
label_map[root] = next_label;
next_label += 1;
}
labels[i] = label_map[root];
}
(labels, next_label)
}
pub fn component_partition(&self) -> Vec<Vec<usize>> {
let (labels, num_comp) = self.connected_components();
let mut parts: Vec<Vec<usize>> = vec![Vec::new(); num_comp];
for (b, &comp) in labels.iter().enumerate() {
parts[comp].push(b);
}
parts
}
pub fn expand_one_hop(&self, seed: &[usize]) -> Vec<usize> {
let mut expanded: Vec<usize> = seed.to_vec();
for &b in seed {
for &nb in self.neighbours(b) {
expanded.push(nb);
}
}
expanded.sort_unstable();
expanded.dedup();
expanded
}
}
pub trait BlockHtbetaRow {
fn has_nonzero_in_range(&self, range: Range<usize>) -> bool;
}
impl BlockHtbetaRow for Array2<f64> {
fn has_nonzero_in_range(&self, range: Range<usize>) -> bool {
let d = self.nrows();
for col in range {
for c in 0..d {
if self[[c, col]] != 0.0 {
return true;
}
}
}
false
}
}
#[cfg(test)]
mod tests {
use super::*;
fn make_htbeta(d: usize, k: usize, nonzeros: &[(usize, usize)]) -> Array2<f64> {
let mut m = Array2::<f64>::zeros((d, k));
for &(c, col) in nonzeros {
m[[c, col]] = 1.0;
}
m
}
#[test]
fn graph_three_blocks_one_component() {
let offsets: Vec<Range<usize>> = vec![0..2, 2..4, 4..6];
let rows = vec![
make_htbeta(1, 6, &[(0, 0), (0, 3)]),
make_htbeta(1, 6, &[(0, 2), (0, 5)]),
];
let g = BetaCouplingGraph::build(&offsets, &rows);
assert_eq!(g.num_blocks, 3);
assert_eq!(g.edges().len(), 2);
let (_, nc) = g.connected_components();
assert_eq!(nc, 1);
let parts = g.component_partition();
assert_eq!(parts.len(), 1);
assert_eq!(parts[0], vec![0, 1, 2]);
}
#[test]
fn graph_disconnected_two_components() {
let offsets: Vec<Range<usize>> = vec![0..2, 2..4, 4..6];
let rows = vec![
make_htbeta(1, 6, &[(0, 1)]),
make_htbeta(1, 6, &[(0, 2), (0, 4)]),
];
let g = BetaCouplingGraph::build(&offsets, &rows);
assert_eq!(g.edges().len(), 1); let (_, nc) = g.connected_components();
assert_eq!(nc, 2);
let parts = g.component_partition();
assert_eq!(parts.len(), 2);
}
#[test]
fn expand_one_hop_basic() {
let offsets: Vec<Range<usize>> = vec![0..1, 1..2, 2..3, 3..4];
let rows = vec![
make_htbeta(1, 4, &[(0, 0), (0, 1)]),
make_htbeta(1, 4, &[(0, 1), (0, 2)]),
make_htbeta(1, 4, &[(0, 2), (0, 3)]),
];
let g = BetaCouplingGraph::build(&offsets, &rows);
let expanded = g.expand_one_hop(&[1]);
assert_eq!(expanded, vec![0, 1, 2]);
}
}