use burn::tensor::backend::Backend;
use burn::tensor::{Tensor, TensorData};
pub const T2H: usize = 0;
pub const H2H: usize = 1;
pub const H2T: usize = 2;
pub const T2T: usize = 3;
pub fn relation_graph<B: Backend>(
triples: &[(usize, usize, usize)],
num_relations: usize,
device: &B::Device,
) -> [Tensor<B, 2>; 4] {
use std::collections::HashMap;
let n = 2 * num_relations;
let mut heads: HashMap<usize, Vec<usize>> = HashMap::new(); let mut tails: HashMap<usize, Vec<usize>> = HashMap::new();
for &(h, r, t) in triples {
if r >= num_relations {
continue;
}
let inv = r + num_relations;
heads.entry(h).or_default().push(r);
tails.entry(t).or_default().push(r);
heads.entry(t).or_default().push(inv);
tails.entry(h).or_default().push(inv);
}
let mut mats = [
vec![0.0f32; n * n], vec![0.0f32; n * n], vec![0.0f32; n * n], vec![0.0f32; n * n], ];
let mark = |m: &mut Vec<f32>, from: &Vec<usize>, to: &Vec<usize>| {
for &a in from {
for &b in to {
m[a * n + b] = 1.0;
}
}
};
let entities: std::collections::BTreeSet<usize> =
heads.keys().chain(tails.keys()).copied().collect();
let empty: Vec<usize> = Vec::new();
for e in entities {
let h = heads.get(&e).unwrap_or(&empty);
let t = tails.get(&e).unwrap_or(&empty);
mark(&mut mats[T2H], t, h);
mark(&mut mats[H2H], h, h);
mark(&mut mats[H2T], h, t);
mark(&mut mats[T2T], t, t);
}
let [a, b, c, d] = mats;
[
Tensor::from_data(TensorData::new(a, [n, n]), device),
Tensor::from_data(TensorData::new(b, [n, n]), device),
Tensor::from_data(TensorData::new(c, [n, n]), device),
Tensor::from_data(TensorData::new(d, [n, n]), device),
]
}
#[derive(Debug, Clone, PartialEq)]
pub struct RelationGraphStats {
pub edges_per_type: [usize; 4],
pub isolated_nodes: usize,
pub self_loops: [usize; 4],
pub nodes: usize,
}
pub fn interaction_stats<B: Backend>(adjs: &[Tensor<B, 2>; 4]) -> RelationGraphStats {
let n = adjs[0].dims()[0];
let mut edges = [0usize; 4];
let mut loops = [0usize; 4];
let mut touched = vec![false; n];
for (k, adj) in adjs.iter().enumerate() {
let v: Vec<f32> = adj.clone().into_data().to_vec().unwrap();
for i in 0..n {
for j in 0..n {
if v[i * n + j] != 0.0 {
edges[k] += 1;
touched[i] = true;
touched[j] = true;
if i == j {
loops[k] += 1;
}
}
}
}
}
RelationGraphStats {
edges_per_type: edges,
isolated_nodes: touched.iter().filter(|&&t| !t).count(),
self_loops: loops,
nodes: n,
}
}
#[cfg(test)]
mod tests {
use super::*;
use burn_ndarray::NdArray;
type B = NdArray<f32>;
fn dev() -> <B as Backend>::Device {
<B as Backend>::Device::default()
}
fn at(m: &Tensor<B, 2>, i: usize, j: usize) -> f32 {
let n = m.dims()[1];
let v: Vec<f32> = m.clone().into_data().to_vec().unwrap();
v[i * n + j]
}
#[test]
fn chained_triples_interactions_by_hand() {
let triples = [(0usize, 0usize, 1usize), (1, 1, 2)];
let [t2h, h2h, h2t, t2t] = relation_graph::<B>(&triples, 2, &dev());
assert_eq!(at(&t2h, 0, 1), 1.0, "tail of r0 is head of r1");
assert_eq!(at(&t2h, 0, 2), 1.0, "tail of r0 is head of r0_inv");
assert_eq!(at(&h2h, 1, 2), 1.0, "r1 and r0_inv share head 1");
assert_eq!(at(&t2t, 0, 3), 1.0, "r0 and r1_inv share tail 1");
assert_eq!(at(&h2t, 1, 0), 1.0, "head of r1 is tail of r0");
assert_eq!(at(&h2h, 0, 1), 0.0);
assert_eq!(at(&t2h, 1, 0), 0.0);
}
#[test]
fn stats_flag_isolated_relations() {
let triples = [(0usize, 0usize, 1usize), (1, 1, 2)];
let g = relation_graph::<B>(&triples, 2, &dev());
let s = interaction_stats(&g);
assert_eq!(s.nodes, 4);
assert_eq!(s.isolated_nodes, 0);
assert!(s.edges_per_type.iter().sum::<usize>() > 0);
let g = relation_graph::<B>(&triples, 3, &dev());
let s = interaction_stats(&g);
assert_eq!(s.nodes, 6);
assert_eq!(s.isolated_nodes, 2, "unused relation + its inverse");
}
#[test]
fn relation_renaming_permutes_consistently() {
let orig = [(0usize, 0usize, 1usize), (1, 1, 2), (2, 0, 3)];
let renamed: Vec<_> = orig
.iter()
.map(|&(h, r, t)| (h, 1 - r, t)) .collect();
let a = relation_graph::<B>(&orig, 2, &dev());
let b = relation_graph::<B>(&renamed, 2, &dev());
let p = [1usize, 0, 3, 2];
for k in 0..4 {
for i in 0..4 {
for j in 0..4 {
assert_eq!(
at(&a[k], i, j),
at(&b[k], p[i], p[j]),
"matrix {k} cell ({i},{j}) must permute"
);
}
}
}
}
}