use super::snapshot::{DirectedGraphAdapter, DirectedSnapshot};
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
#[allow(missing_docs)] pub enum TriadType {
T003,
T012,
T102,
T021D,
T021U,
T021C,
T111D,
T111U,
T030T,
T030C,
T201,
T120D,
T120U,
T120C,
T210,
T300,
}
impl TriadType {
#[must_use]
pub fn all() -> [TriadType; 16] {
use TriadType::*;
[
T003, T012, T102, T021D, T021U, T021C, T111D, T111U, T030T, T030C, T201, T120D, T120U,
T120C, T210, T300,
]
}
#[must_use]
pub fn label(self) -> &'static str {
use TriadType::*;
match self {
T003 => "003",
T012 => "012",
T102 => "102",
T021D => "021D",
T021U => "021U",
T021C => "021C",
T111D => "111D",
T111U => "111U",
T030T => "030T",
T030C => "030C",
T201 => "201",
T120D => "120D",
T120U => "120U",
T120C => "120C",
T210 => "210",
T300 => "300",
}
}
fn index(self) -> usize {
TriadType::all().iter().position(|&t| t == self).unwrap()
}
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
enum Dyad {
Null,
Asym,
Mutual,
}
fn dyad(ab: bool, ba: bool) -> Dyad {
match (ab, ba) {
(false, false) => Dyad::Null,
(true, true) => Dyad::Mutual,
_ => Dyad::Asym,
}
}
#[must_use]
pub fn classify(x: usize, y: usize, z: usize, has_arc: impl Fn(usize, usize) -> bool) -> TriadType {
let dxy = dyad(has_arc(x, y), has_arc(y, x));
let dxz = dyad(has_arc(x, z), has_arc(z, x));
let dyz = dyad(has_arc(y, z), has_arc(z, y));
let dyads = [dxy, dxz, dyz];
let m = dyads.iter().filter(|&&d| d == Dyad::Mutual).count();
let a = dyads.iter().filter(|&&d| d == Dyad::Asym).count();
let pairs = [(x, y), (x, z), (y, z)];
let center_subtype = || -> TriadType {
let asym_pairs: Vec<(usize, usize)> = (0..3)
.filter(|&i| dyads[i] == Dyad::Asym)
.map(|i| pairs[i])
.collect();
let (p, q) = (asym_pairs[0], asym_pairs[1]);
let center = if p.0 == q.0 || p.0 == q.1 { p.0 } else { p.1 };
let other = |pair: (usize, usize)| if pair.0 == center { pair.1 } else { pair.0 };
let (o1, o2) = (other(p), other(q));
let out1 = has_arc(center, o1);
let out2 = has_arc(center, o2);
if out1 && out2 {
TriadType::T021D
} else if !out1 && !out2 {
TriadType::T021U
} else {
TriadType::T021C
}
};
match (m, a) {
(0, 0) => TriadType::T003,
(0, 1) => TriadType::T012,
(1, 0) => TriadType::T102,
(0, 2) => center_subtype(),
(1, 1) => {
let asym_idx = (0..3).find(|&i| dyads[i] == Dyad::Asym).unwrap();
let (u, v) = pairs[asym_idx];
if has_arc(u, v) {
let mutual_idx = (0..3).find(|&i| dyads[i] == Dyad::Mutual).unwrap();
let (mp, mq) = pairs[mutual_idx];
if v == mp || v == mq {
TriadType::T111D
} else {
TriadType::T111U
}
} else {
let mutual_idx = (0..3).find(|&i| dyads[i] == Dyad::Mutual).unwrap();
let (mp, mq) = pairs[mutual_idx];
if u == mp || u == mq {
TriadType::T111D
} else {
TriadType::T111U
}
}
}
(0, 3) => {
let out_deg = |v: usize| {
[x, y, z]
.iter()
.filter(|&&u| u != v && has_arc(v, u))
.count()
};
if [x, y, z].iter().any(|&v| out_deg(v) == 2) {
TriadType::T030T
} else {
TriadType::T030C
}
}
(2, 0) => TriadType::T201,
(1, 2) => center_subtype_120(&dyads, &pairs, &has_arc),
(2, 1) => TriadType::T210,
(3, 0) => TriadType::T300,
_ => unreachable!("M={m} A={a} impossible for 3 dyads"),
}
}
fn center_subtype_120(
dyads: &[Dyad; 3],
pairs: &[(usize, usize); 3],
has_arc: &impl Fn(usize, usize) -> bool,
) -> TriadType {
let asym_pairs: Vec<(usize, usize)> = (0..3)
.filter(|&i| dyads[i] == Dyad::Asym)
.map(|i| pairs[i])
.collect();
let (p, q) = (asym_pairs[0], asym_pairs[1]);
let center = if p.0 == q.0 || p.0 == q.1 { p.0 } else { p.1 };
let other = |pair: (usize, usize)| if pair.0 == center { pair.1 } else { pair.0 };
let (o1, o2) = (other(p), other(q));
let out1 = has_arc(center, o1);
let out2 = has_arc(center, o2);
if out1 && out2 {
TriadType::T120D
} else if !out1 && !out2 {
TriadType::T120U
} else {
TriadType::T120C
}
}
#[derive(Clone, Debug)]
pub struct TriadCensus {
counts: [u64; 16],
}
impl TriadCensus {
#[inline]
#[must_use]
pub fn get(&self, t: TriadType) -> u64 {
self.counts[t.index()]
}
#[must_use]
pub fn total(&self) -> u64 {
self.counts.iter().sum()
}
pub fn iter(&self) -> impl Iterator<Item = (TriadType, u64)> + '_ {
TriadType::all().into_iter().map(|t| (t, self.get(t)))
}
}
#[must_use]
pub fn triad_census<G>(g: G) -> TriadCensus
where
G: DirectedGraphAdapter,
{
let snapshot = DirectedSnapshot::new(g);
let n = snapshot.len();
let mut counts = [0u64; 16];
let has_arc = |a: usize, b: usize| snapshot.has_arc(a, b);
for x in 0..n {
for y in (x + 1)..n {
for z in (y + 1)..n {
let t = classify(x, y, z, has_arc);
counts[t.index()] += 1;
}
}
}
TriadCensus { counts }
}