Skip to main content

phago_runtime/
export.rs

1//! Triple exporter — extract knowledge graph triples with Hebbian weights.
2//!
3//! Exports the colony's knowledge graph as (subject, predicate, object, weight)
4//! triples, suitable for downstream processing into training data.
5
6use crate::colony::Colony;
7use phago_core::topology::TopologyGraph;
8use serde::Serialize;
9
10/// A knowledge graph triple with weight.
11#[derive(Debug, Clone, Serialize)]
12pub struct WeightedTriple {
13    pub subject: String,
14    pub predicate: String,
15    pub object: String,
16    pub weight: f64,
17    pub co_activations: u64,
18}
19
20/// Export all edges as weighted triples.
21pub fn export_triples(colony: &Colony) -> Vec<WeightedTriple> {
22    let graph = colony.substrate().graph();
23    let mut triples = Vec::new();
24
25    for (from_id, to_id, edge) in graph.all_edges() {
26        let from_label = graph.get_node(&from_id)
27            .map(|n| n.label.clone())
28            .unwrap_or_else(|| "?".to_string());
29        let to_label = graph.get_node(&to_id)
30            .map(|n| n.label.clone())
31            .unwrap_or_else(|| "?".to_string());
32
33        triples.push(WeightedTriple {
34            subject: from_label,
35            predicate: "related_to".to_string(),
36            object: to_label,
37            weight: edge.weight,
38            co_activations: edge.co_activations,
39        });
40    }
41
42    // Sort by weight descending (most important triples first)
43    triples.sort_by(|a, b| b.weight.partial_cmp(&a.weight).unwrap_or(std::cmp::Ordering::Equal));
44    triples
45}
46
47/// Export triples with weight statistics.
48pub fn triple_stats(triples: &[WeightedTriple]) -> TripleStats {
49    if triples.is_empty() {
50        return TripleStats {
51            total: 0,
52            mean_weight: 0.0,
53            median_weight: 0.0,
54            max_weight: 0.0,
55            min_weight: 0.0,
56            mean_co_activations: 0.0,
57        };
58    }
59
60    let weights: Vec<f64> = triples.iter().map(|t| t.weight).collect();
61    let total = triples.len();
62    let mean_weight = weights.iter().sum::<f64>() / total as f64;
63    let max_weight = weights.iter().cloned().fold(0.0f64, f64::max);
64    let min_weight = weights.iter().cloned().fold(f64::MAX, f64::min);
65
66    let mut sorted = weights.clone();
67    sorted.sort_by(|a, b| a.partial_cmp(b).unwrap());
68    let median_weight = sorted[sorted.len() / 2];
69
70    let mean_co_activations = triples.iter()
71        .map(|t| t.co_activations as f64)
72        .sum::<f64>() / total as f64;
73
74    TripleStats {
75        total,
76        mean_weight,
77        median_weight,
78        max_weight,
79        min_weight,
80        mean_co_activations,
81    }
82}
83
84/// Statistics about exported triples.
85#[derive(Debug, Clone, Serialize)]
86pub struct TripleStats {
87    pub total: usize,
88    pub mean_weight: f64,
89    pub median_weight: f64,
90    pub max_weight: f64,
91    pub min_weight: f64,
92    pub mean_co_activations: f64,
93}