Skip to main content

exo_core/
genomic.rs

1//! Genomic integration — ADR-029 bridge from ruDNA .rvdna to EXO-AI patterns.
2//!
3//! .rvdna files contain pre-computed:
4//! - 64-dim health risk profiles (HealthProfile64)
5//! - 512-dim GNN protein embeddings
6//! - k-mer vectors
7//! - polygenic risk scores
8//! - Horvath epigenetic clock (353 CpG sites → biological age)
9//!
10//! This module provides:
11//! 1. RvDnaPattern: a genomic pattern for EXO-AI memory
12//! 2. HorvathClock: biological age → SubstrateTime mapping
13//! 3. PharmacogenomicWeights: gene variants → synaptic weight modifiers
14//! 4. GenomicPatternStore: in-memory store with Phi-weighted recall
15
16/// A genomic pattern compatible with EXO-AI memory substrate.
17/// Derived from .rvdna sequence data via the ruDNA pipeline.
18#[derive(Debug, Clone)]
19pub struct RvDnaPattern {
20    /// Unique pattern identifier (from sequence hash)
21    pub id: u64,
22    /// 64-dimensional health risk profile embedding
23    pub health_embedding: [f32; 64],
24    /// Polygenic risk score (0.0–1.0, higher = higher risk)
25    pub polygenic_risk: f32,
26    /// Estimated biological age via Horvath clock (years)
27    pub biological_age: f32,
28    /// Chronological age at sample collection (years)
29    pub chronological_age: f32,
30    /// Sample identifier hash
31    pub sample_hash: [u8; 32],
32    /// Neurotransmitter-relevant gene activity scores
33    pub neuro_profile: NeurotransmitterProfile,
34}
35
36/// Neurotransmitter-relevant gene activity (relevant for cognitive substrate)
37#[derive(Debug, Clone, Default)]
38pub struct NeurotransmitterProfile {
39    /// Dopamine pathway activity (DRD2, COMT, SLC6A3) — 0.0–1.0
40    pub dopamine: f32,
41    /// Serotonin pathway activity (SLC6A4, MAOA, TPH2) — 0.0–1.0
42    pub serotonin: f32,
43    /// GABA/Glutamate balance (GRIN2A, GABRA1, SLC1A2) — 0.0–1.0
44    pub gaba_glutamate_ratio: f32,
45    /// Neuroplasticity score (BDNF, NRXN1, SHANK3) — 0.0–1.0
46    pub plasticity_score: f32,
47    /// Circadian regulation (PER1, CLOCK, ARNTL) — 0.0–1.0
48    pub circadian_regulation: f32,
49}
50
51impl NeurotransmitterProfile {
52    /// Overall neuronal excitability score for IIT Φ weighting
53    pub fn excitability_score(&self) -> f32 {
54        (self.dopamine * 0.3
55            + self.serotonin * 0.2
56            + self.gaba_glutamate_ratio * 0.2
57            + self.plasticity_score * 0.3)
58            .clamp(0.0, 1.0)
59    }
60
61    /// Circadian phase offset (maps to Kuramoto phase in NeuromorphicBackend)
62    pub fn circadian_phase_rad(&self) -> f32 {
63        self.circadian_regulation * 2.0 * std::f32::consts::PI
64    }
65}
66
67/// Horvath epigenetic clock — maps biological age to cognitive substrate time.
68/// Based on 353 CpG site methylation levels (Horvath 2013, Genome Biology).
69pub struct HorvathClock {
70    /// Intercept from Horvath's original regression
71    pub intercept: f64,
72    /// Age transformation function
73    adult_age_transform: f64,
74}
75
76impl HorvathClock {
77    pub fn new() -> Self {
78        Self {
79            intercept: 0.696,
80            adult_age_transform: 20.0,
81        }
82    }
83
84    /// Predict biological age from methylation levels (simplified model)
85    /// Full model uses 353 CpG sites — this uses a compressed 10-site proxy
86    pub fn predict_age(&self, methylation_proxy: &[f32]) -> f32 {
87        if methylation_proxy.is_empty() {
88            return 30.0;
89        }
90        // Anti-correlated sites accelerate aging; correlated sites decelerate
91        let signal: f64 = methylation_proxy
92            .iter()
93            .enumerate()
94            .map(|(i, &m)| {
95                // Alternating positive/negative weights (simplified from full model)
96                let w = if i % 2 == 0 { 1.5 } else { -0.8 };
97                w * m as f64
98            })
99            .sum::<f64>()
100            / methylation_proxy.len() as f64;
101
102        // Horvath transformation: anti-log transform for age > 20
103        let transformed = self.intercept + signal;
104        if transformed < 0.0 {
105            (self.adult_age_transform * 2.0_f64.powf(transformed) - 1.0) as f32
106        } else {
107            (self.adult_age_transform * (transformed + 1.0)) as f32
108        }
109    }
110
111    /// Compute age acceleration (biological - chronological)
112    pub fn age_acceleration(&self, methylation: &[f32], chronological_age: f32) -> f32 {
113        let bio_age = self.predict_age(methylation);
114        bio_age - chronological_age
115    }
116}
117
118impl Default for HorvathClock {
119    fn default() -> Self {
120        Self::new()
121    }
122}
123
124/// Pharmacogenomic weight modifiers for IIT Φ computation.
125/// Maps gene variants to synaptic weight scaling factors.
126pub struct PharmacogenomicWeights {
127    #[allow(dead_code)]
128    clock: HorvathClock,
129}
130
131impl PharmacogenomicWeights {
132    pub fn new() -> Self {
133        Self {
134            clock: HorvathClock::new(),
135        }
136    }
137
138    /// Compute Φ-weighting factor from neurotransmitter profile.
139    /// Higher excitability + high plasticity → higher Φ weight (more consciousness).
140    pub fn phi_weight(&self, neuro: &NeurotransmitterProfile) -> f64 {
141        let excit = neuro.excitability_score() as f64;
142        let plastic = neuro.plasticity_score as f64;
143        // Φ ∝ excitability × plasticity (both needed for high integrated information)
144        (1.0 + 3.0 * excit * plastic).min(5.0)
145    }
146
147    /// Connection weight scaling for IIT substrate.
148    /// Maps gene activity to network edge weights.
149    pub fn connection_weight_scale(&self, neuro: &NeurotransmitterProfile) -> f32 {
150        let da_effect = 1.0 + 0.5 * neuro.dopamine; // Dopamine increases connection strength
151        let gaba_effect = 1.0 - 0.3 * neuro.gaba_glutamate_ratio; // GABA inhibits
152        (da_effect * gaba_effect).clamp(0.3, 2.5)
153    }
154
155    /// Age-dependent memory decay rate (young = slower decay, old = faster)
156    pub fn memory_decay_rate(&self, bio_age: f32) -> f64 {
157        // Logistic: fast decay for >50, slow for <30
158        1.0 / (1.0 + (-0.1 * (bio_age as f64 - 40.0)).exp())
159    }
160}
161
162impl Default for PharmacogenomicWeights {
163    fn default() -> Self {
164        Self::new()
165    }
166}
167
168/// In-memory genomic pattern store with pharmacogenomic-weighted retrieval
169pub struct GenomicPatternStore {
170    patterns: Vec<RvDnaPattern>,
171    weights: PharmacogenomicWeights,
172}
173
174#[derive(Debug)]
175pub struct GenomicSearchResult {
176    pub id: u64,
177    pub similarity: f32,
178    pub phi_weight: f64,
179    pub weighted_score: f64,
180}
181
182impl GenomicPatternStore {
183    pub fn new() -> Self {
184        Self {
185            patterns: Vec::new(),
186            weights: PharmacogenomicWeights::new(),
187        }
188    }
189
190    pub fn insert(&mut self, pattern: RvDnaPattern) {
191        self.patterns.push(pattern);
192    }
193
194    /// Cosine similarity between health embeddings
195    fn cosine_similarity(a: &[f32; 64], b: &[f32; 64]) -> f32 {
196        let dot: f32 = a.iter().zip(b.iter()).map(|(x, y)| x * y).sum();
197        let na: f32 = a.iter().map(|x| x * x).sum::<f32>().sqrt().max(1e-8);
198        let nb: f32 = b.iter().map(|x| x * x).sum::<f32>().sqrt().max(1e-8);
199        dot / (na * nb)
200    }
201
202    /// Search with pharmacogenomic Φ-weighting
203    pub fn search(&self, query: &RvDnaPattern, k: usize) -> Vec<GenomicSearchResult> {
204        let mut results: Vec<GenomicSearchResult> = self
205            .patterns
206            .iter()
207            .map(|p| {
208                let sim = Self::cosine_similarity(&query.health_embedding, &p.health_embedding);
209                let phi_w = self.weights.phi_weight(&p.neuro_profile);
210                GenomicSearchResult {
211                    id: p.id,
212                    similarity: sim,
213                    phi_weight: phi_w,
214                    weighted_score: sim as f64 * phi_w,
215                }
216            })
217            .collect();
218        results.sort_unstable_by(|a, b| {
219            b.weighted_score
220                .partial_cmp(&a.weighted_score)
221                .unwrap_or(std::cmp::Ordering::Equal)
222        });
223        results.truncate(k);
224        results
225    }
226
227    pub fn len(&self) -> usize {
228        self.patterns.len()
229    }
230}
231
232impl Default for GenomicPatternStore {
233    fn default() -> Self {
234        Self::new()
235    }
236}
237
238/// Create a test pattern from synthetic data (for testing without actual .rvdna files)
239pub fn synthetic_rvdna_pattern(id: u64, seed: u64) -> RvDnaPattern {
240    let mut health = [0.0f32; 64];
241    let mut s = seed.wrapping_mul(0x9e3779b97f4a7c15);
242    for h in health.iter_mut() {
243        s = s
244            .wrapping_mul(6364136223846793005)
245            .wrapping_add(1442695040888963407);
246        *h = (s >> 33) as f32 / (u32::MAX as f32);
247    }
248    let neuro = NeurotransmitterProfile {
249        dopamine: (seed as f32 * 0.1) % 1.0,
250        serotonin: ((seed + 1) as f32 * 0.15) % 1.0,
251        gaba_glutamate_ratio: 0.5,
252        plasticity_score: ((seed + 2) as f32 * 0.07) % 1.0,
253        circadian_regulation: ((seed + 3) as f32 * 0.13) % 1.0,
254    };
255    RvDnaPattern {
256        id,
257        health_embedding: health,
258        polygenic_risk: (seed as f32 * 0.003) % 1.0,
259        biological_age: 20.0 + (seed as f32 * 0.5) % 40.0,
260        chronological_age: 25.0 + (seed as f32 * 0.4) % 35.0,
261        sample_hash: [0u8; 32],
262        neuro_profile: neuro,
263    }
264}
265
266#[cfg(test)]
267mod tests {
268    use super::*;
269
270    #[test]
271    fn test_horvath_clock_adult_age() {
272        let clock = HorvathClock::new();
273        let methylation = vec![0.5f32; 10];
274        let age = clock.predict_age(&methylation);
275        assert!(
276            age > 0.0 && age < 120.0,
277            "Biological age should be in [0, 120]: {}",
278            age
279        );
280    }
281
282    #[test]
283    fn test_phi_weight_scales_with_excitability() {
284        let weights = PharmacogenomicWeights::new();
285        let low_neuro = NeurotransmitterProfile {
286            dopamine: 0.1,
287            serotonin: 0.1,
288            gaba_glutamate_ratio: 0.1,
289            plasticity_score: 0.1,
290            circadian_regulation: 0.5,
291        };
292        let high_neuro = NeurotransmitterProfile {
293            dopamine: 0.9,
294            serotonin: 0.8,
295            gaba_glutamate_ratio: 0.5,
296            plasticity_score: 0.9,
297            circadian_regulation: 0.5,
298        };
299        let low_phi = weights.phi_weight(&low_neuro);
300        let high_phi = weights.phi_weight(&high_neuro);
301        assert!(
302            high_phi > low_phi,
303            "High excitability should yield higher Φ weight"
304        );
305    }
306
307    #[test]
308    fn test_genomic_store_search() {
309        let mut store = GenomicPatternStore::new();
310        for i in 0..10u64 {
311            store.insert(synthetic_rvdna_pattern(i, i * 13));
312        }
313        let query = synthetic_rvdna_pattern(0, 0);
314        let results = store.search(&query, 3);
315        assert!(!results.is_empty());
316        assert!(
317            results[0].weighted_score >= results.last().map(|r| r.weighted_score).unwrap_or(0.0)
318        );
319    }
320
321    #[test]
322    fn test_neuro_circadian_phase() {
323        let neuro = NeurotransmitterProfile {
324            circadian_regulation: 0.5,
325            ..Default::default()
326        };
327        let phase = neuro.circadian_phase_rad();
328        assert!(phase >= 0.0 && phase <= 2.0 * std::f32::consts::PI);
329    }
330}