1#[derive(Debug, Clone)]
19pub struct RvDnaPattern {
20 pub id: u64,
22 pub health_embedding: [f32; 64],
24 pub polygenic_risk: f32,
26 pub biological_age: f32,
28 pub chronological_age: f32,
30 pub sample_hash: [u8; 32],
32 pub neuro_profile: NeurotransmitterProfile,
34}
35
36#[derive(Debug, Clone, Default)]
38pub struct NeurotransmitterProfile {
39 pub dopamine: f32,
41 pub serotonin: f32,
43 pub gaba_glutamate_ratio: f32,
45 pub plasticity_score: f32,
47 pub circadian_regulation: f32,
49}
50
51impl NeurotransmitterProfile {
52 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 pub fn circadian_phase_rad(&self) -> f32 {
63 self.circadian_regulation * 2.0 * std::f32::consts::PI
64 }
65}
66
67pub struct HorvathClock {
70 pub intercept: f64,
72 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 pub fn predict_age(&self, methylation_proxy: &[f32]) -> f32 {
87 if methylation_proxy.is_empty() {
88 return 30.0;
89 }
90 let signal: f64 = methylation_proxy
92 .iter()
93 .enumerate()
94 .map(|(i, &m)| {
95 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 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 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
124pub 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 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 (1.0 + 3.0 * excit * plastic).min(5.0)
145 }
146
147 pub fn connection_weight_scale(&self, neuro: &NeurotransmitterProfile) -> f32 {
150 let da_effect = 1.0 + 0.5 * neuro.dopamine; let gaba_effect = 1.0 - 0.3 * neuro.gaba_glutamate_ratio; (da_effect * gaba_effect).clamp(0.3, 2.5)
153 }
154
155 pub fn memory_decay_rate(&self, bio_age: f32) -> f64 {
157 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
168pub 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 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 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
238pub 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}