weavatrix-graph 0.6.3

Deterministic, evidence-carrying graph core for Weavatrix repository intelligence
Documentation
use crate::{GraphError, IndexUndirectedGraphView, Measure, Result, String, Vec};
use core::cmp::Ordering;

pub(super) struct Candidate<M> {
    pub(super) weight: M,
    pub(super) partition: Vec<usize>,
    pub(super) complement: Vec<usize>,
}

pub(super) fn canonical_candidate<M: Measure>(
    weight: M,
    group: &[usize],
    nodes: &[usize],
) -> Candidate<M> {
    let mut partition = group.to_vec();
    partition.sort_unstable();
    let complement = nodes
        .iter()
        .copied()
        .filter(|node| partition.binary_search(node).is_err())
        .collect::<Vec<_>>();
    if partition.len() > complement.len()
        || (partition.len() == complement.len() && partition > complement)
    {
        Candidate {
            weight,
            partition: complement,
            complement: partition,
        }
    } else {
        Candidate {
            weight,
            partition,
            complement,
        }
    }
}

pub(super) fn better<M: Measure>(candidate: &Candidate<M>, best: Option<&Candidate<M>>) -> bool {
    let Some(best) = best else {
        return true;
    };
    match candidate.weight.compare(best.weight) {
        Some(Ordering::Less) => true,
        Some(Ordering::Equal) => candidate.partition < best.partition,
        _ => false,
    }
}

pub(super) fn nodes<G: IndexUndirectedGraphView>(graph: &G) -> (Vec<Option<G::Node>>, Vec<usize>) {
    let mut by_slot = vec![None; graph.node_bound()];
    let mut slots = Vec::with_capacity(graph.node_count());
    for node in graph.node_indices() {
        let slot = G::node_slot(node);
        by_slot[slot] = Some(node);
        slots.push(slot);
    }
    slots.sort_unstable();
    (by_slot, slots)
}

pub(super) fn map_nodes<Node: Copy>(slots: &[usize], nodes: &[Option<Node>]) -> Result<Vec<Node>> {
    slots
        .iter()
        .map(|&slot| {
            nodes.get(slot).copied().flatten().ok_or_else(|| {
                GraphError::InvalidAlgorithmParameter {
                    algorithm: "Stoer-Wagner",
                    parameter: "active node mapping",
                    value: String::from("active slot has no corresponding graph node"),
                }
            })
        })
        .collect()
}

pub(super) fn validate_weight<M: Measure>(weight: M) -> Result<()> {
    if weight.is_valid() && !weight.is_negative() {
        Ok(())
    } else {
        Err(GraphError::InvalidAlgorithmParameter {
            algorithm: "Stoer-Wagner",
            parameter: "edge weight",
            value: String::from("must be finite and non-negative"),
        })
    }
}