use crate::id::NodeId;
#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub enum EdgeProvenance {
Authored,
Knn,
}
#[derive(Clone, Debug, PartialEq)]
pub struct AdjEdge {
pub src: NodeId,
pub dst: NodeId,
pub weight: f32,
pub provenance: EdgeProvenance,
}
pub trait AdjacencyIndex {
fn iter_edges(&self) -> Box<dyn Iterator<Item = AdjEdge> + '_>;
fn edge_count(&self) -> usize;
}
#[derive(Clone, Debug)]
pub struct AuthoredSliceAdjacency<'a> {
edges: &'a [(NodeId, NodeId)],
}
impl<'a> AuthoredSliceAdjacency<'a> {
#[must_use]
pub const fn new(edges: &'a [(NodeId, NodeId)]) -> Self {
Self { edges }
}
}
impl AdjacencyIndex for AuthoredSliceAdjacency<'_> {
fn iter_edges(&self) -> Box<dyn Iterator<Item = AdjEdge> + '_> {
Box::new(self.edges.iter().map(|(s, d)| AdjEdge {
src: *s,
dst: *d,
weight: 1.0,
provenance: EdgeProvenance::Authored,
}))
}
fn edge_count(&self) -> usize {
self.edges.len()
}
}
pub trait KnnEdgeSource {
fn iter_knn(&self) -> Box<dyn Iterator<Item = (NodeId, NodeId, f32)> + '_>;
fn knn_len(&self) -> usize;
}
#[derive(Clone, Copy, Debug, Default)]
pub struct EmptyKnnSource;
impl KnnEdgeSource for EmptyKnnSource {
fn iter_knn(&self) -> Box<dyn Iterator<Item = (NodeId, NodeId, f32)> + '_> {
Box::new(std::iter::empty())
}
fn knn_len(&self) -> usize {
0
}
}
pub struct HybridAdjacency<A: AdjacencyIndex, K: KnnEdgeSource> {
pub authored: A,
pub knn: K,
}
impl<A: AdjacencyIndex, K: KnnEdgeSource> HybridAdjacency<A, K> {
pub const fn new(authored: A, knn: K) -> Self {
Self { authored, knn }
}
}
impl<A: AdjacencyIndex, K: KnnEdgeSource> AdjacencyIndex for HybridAdjacency<A, K> {
fn iter_edges(&self) -> Box<dyn Iterator<Item = AdjEdge> + '_> {
let authored_edges: Vec<AdjEdge> = self.authored.iter_edges().collect();
let mut seen: std::collections::HashSet<(NodeId, NodeId)> =
std::collections::HashSet::with_capacity(authored_edges.len());
for e in &authored_edges {
seen.insert((e.src, e.dst));
}
let knn_iter = self.knn.iter_knn().filter_map(move |(s, d, w)| {
if seen.contains(&(s, d)) {
None
} else {
Some(AdjEdge {
src: s,
dst: d,
weight: w,
provenance: EdgeProvenance::Knn,
})
}
});
Box::new(authored_edges.into_iter().chain(knn_iter))
}
fn edge_count(&self) -> usize {
self.authored.edge_count() + self.knn.knn_len()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn empty_knn_yields_authored_exactly() {
let a = NodeId::new_v7();
let b = NodeId::new_v7();
let c = NodeId::new_v7();
let authored_pairs = [(a, b), (b, c)];
let authored = AuthoredSliceAdjacency::new(&authored_pairs);
let hybrid = HybridAdjacency::new(authored.clone(), EmptyKnnSource);
let via_authored: Vec<AdjEdge> = authored.iter_edges().collect();
let via_hybrid: Vec<AdjEdge> = hybrid.iter_edges().collect();
assert_eq!(via_authored, via_hybrid);
}
#[test]
fn knn_edges_tagged_with_provenance() {
struct OneKnn(NodeId, NodeId);
impl KnnEdgeSource for OneKnn {
fn iter_knn(&self) -> Box<dyn Iterator<Item = (NodeId, NodeId, f32)> + '_> {
Box::new(std::iter::once((self.0, self.1, 0.75)))
}
fn knn_len(&self) -> usize {
1
}
}
let a = NodeId::new_v7();
let b = NodeId::new_v7();
let authored_pairs: [(NodeId, NodeId); 0] = [];
let hybrid =
HybridAdjacency::new(AuthoredSliceAdjacency::new(&authored_pairs), OneKnn(a, b));
let edges: Vec<AdjEdge> = hybrid.iter_edges().collect();
assert_eq!(edges.len(), 1);
assert_eq!(edges[0].provenance, EdgeProvenance::Knn);
assert!((edges[0].weight - 0.75).abs() < 1e-6);
}
}