Skip to main content

cuda_biology/
lib.rs

1/*!
2# cuda-biology
3
4Biological agent runtime — maps instinct engine to instruction set.
5
6The complete pipeline:
7```
8Environment → Sensors → Membrane → Enzymes → Genes → RNA → Proteins → FLUX bytecode → Action → Feedback
9```
10
11Every operation costs ATP. Rest instinct generates ATP. Circadian rhythm
12modulates instinct strength. Apoptosis terminates agents that can't sustain themselves.
13*/
14
15use serde::{Deserialize, Serialize};
16use std::collections::HashMap;
17
18/// The 10 biological instincts with energy profiles
19#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
20pub enum Instinct {
21    Survive,    // HALT, TRAP, RESOURCE_ACQUIRE — costs 0, generates nothing
22    Perceive,   // IO_READ, SENSOR_ACQUIRE, FUSE_CONF — cheap sensing
23    Navigate,   // JMP, CALL, RET — movement through state space
24    Communicate,// TELL, ASK, BROADCAST — agent messaging
25    Learn,      // BOX, UNBOX, REGION_CREATE — memory formation
26    Defend,     // MEMBRANE_CHK, VERIFY, CAP_REQ — security
27    Rest,       // ATP_GEN — generates energy, no action
28    Play,       // Explore unknown states, try new gene combinations
29    Create,     // Compose new genes from existing patterns
30    Socialize,  // TRUST_UPDATE, DELEGATE — fleet coordination
31}
32
33impl Instinct {
34    pub fn all() -> &'static [Instinct] {
35        &[Instinct::Survive, Instinct::Perceive, Instinct::Navigate, Instinct::Communicate,
36          Instinct::Learn, Instinct::Defend, Instinct::Rest, Instinct::Play,
37          Instinct::Create, Instinct::Socialize]
38    }
39
40    pub fn id(self) -> u8 {
41        match self {
42            Instinct::Survive => 0, Instinct::Perceive => 1, Instinct::Navigate => 2,
43            Instinct::Communicate => 3, Instinct::Learn => 4, Instinct::Defend => 5,
44            Instinct::Rest => 6, Instinct::Play => 7, Instinct::Create => 8,
45            Instinct::Socialize => 9,
46        }
47    }
48
49    pub fn from_id(id: u8) -> Option<Self> {
50        match id {
51            0 => Some(Instinct::Survive), 1 => Some(Instinct::Perceive),
52            2 => Some(Instinct::Navigate), 3 => Some(Instinct::Communicate),
53            4 => Some(Instinct::Learn), 5 => Some(Instinct::Defend),
54            6 => Some(Instinct::Rest), 7 => Some(Instinct::Play),
55            8 => Some(Instinct::Create), 9 => Some(Instinct::Socialize),
56            _ => None,
57        }
58    }
59
60    /// Base energy cost per activation cycle
61    pub fn energy_cost(self) -> f64 {
62        match self {
63            Instinct::Survive => 0.0,
64            Instinct::Perceive => 0.3,
65            Instinct::Navigate => 0.5,
66            Instinct::Communicate => 0.8,
67            Instinct::Learn => 0.6,
68            Instinct::Defend => 0.2,
69            Instinct::Rest => -1.0,   // generates ATP
70            Instinct::Play => 0.7,
71            Instinct::Create => 1.2,
72            Instinct::Socialize => 0.4,
73        }
74    }
75
76    pub fn name(self) -> &'static str {
77        match self {
78            Instinct::Survive => "survive", Instinct::Perceive => "perceive",
79            Instinct::Navigate => "navigate", Instinct::Communicate => "communicate",
80            Instinct::Learn => "learn", Instinct::Defend => "defend",
81            Instinct::Rest => "rest", Instinct::Play => "play",
82            Instinct::Create => "create", Instinct::Socialize => "socialize",
83        }
84    }
85}
86
87/// A gene — the fundamental unit of behavioral patterns
88#[derive(Clone, Debug, Serialize, Deserialize)]
89pub struct Gene {
90    pub name: String,
91    /// Which instinct(s) this gene serves
92    pub instinct: Instinct,
93    /// How well this gene matches its signal pattern [0,1]
94    pub signal_affinity: f64,
95    /// How strongly this gene is expressed [0,1]
96    pub expression: f64,
97    /// Accumulated fitness score from outcomes
98    pub fitness: f64,
99    /// How many times this gene has been activated
100    pub use_count: u32,
101    /// How many times activation led to success
102    pub success_count: u32,
103    /// The behavioral pattern encoded as instruction bytes
104    pub bytecode: Vec<u8>,
105    /// Confidence in this gene's effectiveness
106    pub confidence: f64,
107}
108
109impl Gene {
110    pub fn new(name: &str, instinct: Instinct) -> Self {
111        Gene {
112            name: name.to_string(),
113            instinct,
114            signal_affinity: 0.5,
115            expression: 0.5,
116            fitness: 0.5,
117            use_count: 0,
118            success_count: 0,
119            bytecode: vec![],
120            confidence: 0.5,
121        }
122    }
123
124    /// Check if gene should be auto-quarantined
125    /// Conditions: fitness < 0.1 AND used > 10 times AND success rate < 15%
126    pub fn should_quarantine(&self) -> bool {
127        if self.use_count < 10 { return false; }
128        let success_rate = if self.use_count > 0 { self.success_count as f64 / self.use_count as f64 } else { 0.0 };
129        self.fitness < 0.1 && success_rate < 0.15
130    }
131
132    /// Success rate
133    pub fn success_rate(&self) -> f64 {
134        if self.use_count == 0 { return 0.0; }
135        self.success_count as f64 / self.use_count as f64
136    }
137
138    /// Update fitness based on outcome
139    pub fn record_outcome(&mut self, success: bool) {
140        self.use_count += 1;
141        if success { self.success_count += 1; }
142        // Exponential moving average
143        let rate = self.success_rate();
144        let alpha = 0.1;
145        self.fitness = self.fitness * (1.0 - alpha) + rate * alpha;
146        // Update confidence based on consistency
147        if self.use_count > 5 {
148            let variance = (rate - 0.5).abs() * 2.0; // 0=consistent 50%, 1=always or never
149            self.confidence = self.confidence * 0.9 + (1.0 - variance) * 0.1;
150        }
151    }
152}
153
154/// An enzyme — matches signals to genes for activation
155#[derive(Clone, Debug, Serialize, Deserialize)]
156pub struct Enzyme {
157    pub name: String,
158    /// Signal pattern this enzyme responds to
159    pub signal_pattern: Vec<u8>,
160    /// Genes this enzyme can activate
161    pub target_genes: Vec<String>,
162    /// Binding threshold — signal must exceed this to bind
163    pub threshold: f64,
164}
165
166impl Enzyme {
167    pub fn new(name: &str, pattern: Vec<u8>, genes: Vec<&str>) -> Self {
168        Enzyme { name: name.to_string(), signal_pattern: pattern, target_genes: genes.iter().map(|s| s.to_string()).collect(), threshold: 0.3 }
169    }
170
171    /// Try to bind a signal. Returns binding strength [0,1].
172    pub fn try_bind(&self, signal: &[u8]) -> f64 {
173        if signal.len() != self.signal_pattern.len() { return 0.0; }
174        let matches = signal.iter().zip(self.signal_pattern.iter()).filter(|(s,p)| s == p).count();
175        let strength = matches as f64 / signal.len() as f64;
176        if strength >= self.threshold { strength } else { 0.0 }
177    }
178}
179
180/// RNA messenger — translates gene into executable protein (bytecode)
181#[derive(Clone, Debug, Serialize, Deserialize)]
182pub struct RnaMessenger {
183    pub source_gene: String,
184    pub translated_bytecode: Vec<u8>,
185    pub expression_level: f64,
186    pub confidence: f64,
187}
188
189impl RnaMessenger {
190    pub fn translate(gene: &Gene) -> Self {
191        RnaMessenger {
192            source_gene: gene.name.clone(),
193            translated_bytecode: gene.bytecode.clone(),
194            expression_level: gene.expression,
195            confidence: gene.confidence,
196        }
197    }
198}
199
200/// Membrane — self/other boundary with antibody security
201#[derive(Clone, Debug, Serialize, Deserialize)]
202pub struct Membrane {
203    pub antibodies: Vec<MembraneAntibody>,
204}
205
206/// An antibody that blocks dangerous signals
207#[derive(Clone, Debug, Serialize, Deserialize)]
208pub struct MembraneAntibody {
209    pub pattern: Vec<u8>,
210    pub reason: String,
211}
212
213impl Membrane {
214    pub fn new() -> Self { Membrane { antibodies: vec![] } }
215
216    pub fn add_antibody(&mut self, pattern: Vec<u8>, reason: &str) {
217        self.antibodies.push(MembraneAntibody { pattern, reason: reason.to_string() });
218    }
219
220    /// Default dangerous patterns
221    pub fn default_antibodies() -> Self {
222        let mut m = Membrane::new();
223        // Block obvious dangerous operations encoded as byte patterns
224        m.add_antibody(b"rm -rf".to_vec(), "destructive filesystem operation");
225        m.add_antibody(b"format".to_vec(), "disk format");
226        m.add_antibody(b"drop_all".to_vec(), "database destruction");
227        m.add_antibody(b"DELETE FROM".to_vec(), "SQL injection");
228        m.add_antibody(b"sudo rm".to_vec(), "privileged deletion");
229        m
230    }
231
232    /// Check if a signal passes the membrane. Returns true if safe.
233    pub fn check(&self, signal: &[u8]) -> bool {
234        for ab in &self.antibodies {
235            if signal.len() >= ab.pattern.len() {
236                for i in 0..=signal.len() - ab.pattern.len() {
237                    if &signal[i..i+ab.pattern.len()] == ab.pattern.as_slice() {
238                        return false; // blocked
239                    }
240                }
241            }
242        }
243        true
244    }
245}
246
247/// The biological agent — complete pipeline from instinct to action
248#[derive(Clone, Debug, Serialize, Deserialize)]
249pub struct BiologicalAgent {
250    pub id: String,
251    /// Current energy (ATP)
252    pub energy: f64,
253    pub max_energy: f64,
254    /// Gene pool
255    pub genes: HashMap<String, Gene>,
256    /// Enzymes
257    pub enzymes: Vec<Enzyme>,
258    /// Membrane
259    pub membrane: Membrane,
260    /// Circadian phase (0.0-24.0 hours)
261    pub circadian_hour: f64,
262    /// Whether apoptosis has triggered
263    pub dead: bool,
264    /// Death reason
265    pub death_reason: String,
266    /// Activity log
267    pub log: Vec<String>,
268    /// Consecutive low-energy ticks
269    pub low_energy_ticks: u32,
270    /// Apoptosis threshold
271    pub apoptosis_patience: u32,
272    /// Total actions taken
273    pub actions_taken: u64,
274    pub successful_actions: u64,
275}
276
277impl BiologicalAgent {
278    pub fn new(id: &str, max_energy: f64) -> Self {
279        BiologicalAgent {
280            id: id.to_string(),
281            energy: max_energy,
282            max_energy,
283            genes: HashMap::new(),
284            enzymes: vec![],
285            membrane: Membrane::default_antibodies(),
286            circadian_hour: 12.0,
287            dead: false,
288            death_reason: String::new(),
289            log: vec![],
290            low_energy_ticks: 0,
291            apoptosis_patience: 10,
292            actions_taken: 0,
293            successful_actions: 0,
294        }
295    }
296
297    pub fn add_gene(&mut self, gene: Gene) { self.genes.insert(gene.name.clone(), gene); }
298
299    pub fn add_enzyme(&mut self, enzyme: Enzyme) { self.enzymes.push(enzyme); }
300
301    /// Circadian modulation factor for an instinct
302    pub fn instinct_modulation(&self, instinct: Instinct) -> f64 {
303        let peak = match instinct {
304            Instinct::Navigate | Instinct::Play | Instinct::Create => 12.0,
305            Instinct::Perceive | Instinct::Communicate | Instinct::Socialize => 14.0,
306            Instinct::Rest => 2.0,
307            Instinct::Survive | Instinct::Defend => 0.0, // always available
308            Instinct::Learn => 10.0,
309        };
310        let phase = ((self.circadian_hour - peak) / 24.0) * 2.0 * std::f64::consts::PI;
311        let raw = (phase.cos() + 1.0) / 2.0;
312        0.1 + raw * 0.9 // floor at 0.1
313    }
314
315    /// Try to activate an instinct. Returns (success, energy_spent, bytecode_produced)
316    pub fn activate_instinct(&mut self, instinct: Instinct, signal: &[u8]) -> (bool, f64, Vec<u8>) {
317        if self.dead { return (false, 0.0, vec![]); }
318
319        // Check membrane for dangerous signals
320        if !self.membrane.check(signal) {
321            self.log.push(format!("MEMBRANE BLOCKED signal for {}", instinct.name()));
322            return (false, 0.0, vec![]);
323        }
324
325        // Calculate energy cost with circadian modulation
326        let base_cost = instinct.energy_cost();
327        let modulation = self.instinct_modulation(instinct);
328
329        if base_cost > 0.0 {
330            // Consuming energy: cost modulated by circadian
331            let cost = base_cost * (0.5 + modulation * 0.5);
332            if !self.spend_energy(cost) { return (false, 0.0, vec![]); }
333            self.log.push(format!("ACTIVATED {} (cost={:.2}, mod={:.2})", instinct.name(), cost, modulation));
334            return (true, cost, vec![]);
335        } else {
336            // Generating energy (Rest instinct)
337            let gen = -base_cost * (0.5 + modulation * 0.5);
338            self.energy = (self.energy + gen).min(self.max_energy);
339            self.log.push(format!("REST generated {:.2} ATP (mod={:.2})", gen, modulation));
340            return (true, 0.0, vec![]);
341        }
342    }
343
344    /// Find best gene for a signal via enzyme binding
345    pub fn find_gene(&self, signal: &[u8]) -> Option<(String, f64)> {
346        let mut best: Option<(String, f64)> = None;
347        for enzyme in &self.enzymes {
348            let strength = enzyme.try_bind(signal);
349            if strength > 0.0 {
350                for gene_name in &enzyme.target_genes {
351                    if let Some(gene) = self.genes.get(gene_name) {
352                        let score = strength * gene.fitness * gene.expression;
353                        match &best {
354                            None => best = Some((gene_name.clone(), score)),
355                            Some((_, best_score)) if score > *best_score => best = Some((gene_name.clone(), score)),
356                            _ => {}
357                        }
358                    }
359                }
360            }
361        }
362        best
363    }
364
365    /// Execute a gene's bytecode (conceptual — returns the action bytes)
366    pub fn execute_gene(&mut self, gene_name: &str) -> Option<Vec<u8>> {
367        let gene = self.genes.get_mut(gene_name)?;
368        if gene.bytecode.is_empty() { return None; }
369        gene.use_count += 1;
370        let bytecode = gene.bytecode.clone();
371        self.actions_taken += 1;
372        Some(bytecode)
373    }
374
375    /// Record outcome for a gene
376    pub fn record_outcome(&mut self, gene_name: &str, success: bool) {
377        if let Some(gene) = self.genes.get_mut(gene_name) {
378            gene.record_outcome(success);
379        }
380        if success { self.successful_actions += 1; }
381    }
382
383    fn spend_energy(&mut self, amount: f64) -> bool {
384        if self.energy < amount {
385            self.low_energy_ticks += 1;
386            if self.low_energy_ticks >= self.apoptosis_patience {
387                self.trigger_apoptosis("Energy depleted");
388            }
389            return false;
390        }
391        self.energy -= amount;
392        self.low_energy_ticks = self.low_energy_ticks.saturating_sub(1);
393        true
394    }
395
396    fn trigger_apoptosis(&mut self, reason: &str) {
397        self.dead = true;
398        self.death_reason = reason.to_string();
399        self.log.push(format!("APOPTOSIS: {}", reason));
400    }
401
402    pub fn tick(&mut self) {
403        if self.dead { return; }
404        // Advance circadian clock
405        self.circadian_hour = (self.circadian_hour + 0.1) % 24.0;
406    }
407
408    /// Quarantine genes that should be auto-quarantined
409    pub fn quarantine_bad_genes(&mut self) -> Vec<String> {
410        let mut quarantined = vec![];
411        let names: Vec<String> = self.genes.keys().cloned().collect();
412        for name in names {
413            if let Some(gene) = self.genes.get(&name) {
414                if gene.should_quarantine() {
415                    quarantined.push(name.clone());
416                }
417            }
418        }
419        for name in &quarantined {
420            self.genes.remove(name);
421            self.log.push(format!("QUARANTINED gene: {}", name));
422        }
423        quarantined
424    }
425
426    /// Gene crossover for reproduction
427    pub fn crossover_genes(&self, other: &Self, rate: f64) -> Vec<Gene> {
428        let mut children = vec![];
429        let all_names: Vec<&String> = self.genes.keys().chain(other.genes.keys()).collect();
430        for name in all_names {
431            let parent1 = self.genes.get(name);
432            let parent2 = other.genes.get(name);
433            let gene = match (parent1, parent2) {
434                (Some(a), Some(b)) if rand() < rate => {
435                    let mut child = a.clone();
436                    child.bytecode = if rand() < 0.5 { a.bytecode.clone() } else { b.bytecode.clone() };
437                    child.fitness = (a.fitness + b.fitness) / 2.0;
438                    child.confidence = (a.confidence + b.confidence) / 2.0 * 0.9;
439                    child.use_count = 0;
440                    child.success_count = 0;
441                    child
442                }
443                (Some(a), _) => a.clone(),
444                (_, Some(b)) => b.clone(),
445                _ => continue,
446            };
447            children.push(gene);
448        }
449        children
450    }
451
452    /// Overall fitness score
453    pub fn overall_fitness(&self) -> f64 {
454        if self.actions_taken == 0 { return 0.5; }
455        let action_rate = self.successful_actions as f64 / self.actions_taken as f64;
456        let energy_ratio = self.energy / self.max_energy;
457        let gene_fitness: f64 = self.genes.values().map(|g| g.fitness).sum::<f64>() / self.genes.len().max(1) as f64;
458        (action_rate * 0.4 + energy_ratio * 0.3 + gene_fitness * 0.3).clamp(0.0, 1.0)
459    }
460}
461
462fn rand() -> f64 {
463    // Simple pseudo-random (not crypto-grade, fine for gene simulation)
464    use std::time::{SystemTime, UNIX_EPOCH};
465    let seed = SystemTime::now().duration_since(UNIX_EPOCH).unwrap_or_default().as_nanos();
466    // xorshift
467    let mut s = (seed as u64).wrapping_mul(6364136223846793005);
468    s ^= s >> 22;
469    s = s.wrapping_mul(0x5bd1e995);
470    s ^= s >> 15;
471    s ^= s >> 27;
472    s = s.wrapping_mul(0x5bd1e995);
473    (s >> 33) as f64 / u32::MAX as f64
474}
475
476#[cfg(test)]
477mod tests {
478    use super::*;
479
480    #[test]
481    fn test_instinct_energy_costs() {
482        assert!(Instinct::Rest.energy_cost() < 0.0); // generates energy
483        assert!(Instinct::Perceive.energy_cost() > 0.0);
484        assert!(Instinct::Navigate.energy_cost() > 0.0);
485        assert!(Instinct::Create.energy_cost() > Instinct::Communicate.energy_cost());
486    }
487
488    #[test]
489    fn test_gene_quarantine() {
490        let mut gene = Gene::new("bad_gene", Instinct::Navigate);
491        gene.use_count = 20;
492        gene.success_count = 1; // 5% success rate
493        gene.fitness = 0.05;
494        assert!(gene.should_quarantine());
495        gene.success_count = 3; // 15% — at boundary
496        assert!(!gene.should_quarantine());
497    }
498
499    #[test]
500    fn test_enzyme_binding() {
501        let enzyme = Enzyme::new("nav_enzyme", vec![1, 0, 1, 0], vec!["navigate_gene"]);
502        assert_eq!(enzyme.try_bind(&[1, 0, 1, 0]), 1.0); // perfect match
503        assert!(enzyme.try_bind(&[1, 0, 0, 0]) >= 0.3); // 50% match, passes threshold
504        assert_eq!(enzyme.try_bind(&[1, 0]), 0.0); // wrong length
505    }
506
507    #[test]
508    fn test_membrane_blocking() {
509        let m = Membrane::default_antibodies();
510        assert!(!m.check(b"sudo rm -rf /")); // blocked
511        assert!(m.check(b"read file")); // safe
512        assert!(!m.check(b"format disk")); // blocked
513    }
514
515    #[test]
516    fn test_biological_agent_basic() {
517        let mut agent = BiologicalAgent::new("agent-1", 100.0);
518        let gene = Gene::new("nav_gene", Instinct::Navigate);
519        agent.add_gene(gene);
520        let (ok, cost, _) = agent.activate_instinct(Instinct::Navigate, b"some_signal");
521        assert!(ok);
522        assert!(cost > 0.0);
523        assert!(agent.energy < 100.0);
524    }
525
526    #[test]
527    fn test_rest_generates_energy() {
528        let mut agent = BiologicalAgent::new("agent-1", 100.0);
529        agent.energy = 50.0;
530        let (ok, cost, _) = agent.activate_instinct(Instinct::Rest, b"");
531        assert!(ok);
532        assert!(agent.energy > 50.0);
533    }
534
535    #[test]
536    fn test_apoptosis() {
537        let mut agent = BiologicalAgent::new("agent-1", 100.0);
538        agent.energy = 0.01;
539        agent.apoptosis_patience = 3;
540        agent.activate_instinct(Instinct::Navigate, b"sig");
541        agent.activate_instinct(Instinct::Navigate, b"sig");
542        assert!(!agent.dead);
543        agent.activate_instinct(Instinct::Navigate, b"sig");
544        assert!(agent.dead);
545        assert!(agent.death_reason.contains("Energy"));
546    }
547
548    #[test]
549    fn test_circadian_modulation() {
550        let mut agent = BiologicalAgent::new("agent-1", 100.0);
551        let noon = agent.instinct_modulation(Instinct::Navigate);
552        agent.circadian_hour = 0.0;
553        let midnight = agent.instinct_modulation(Instinct::Navigate);
554        assert!(noon > midnight);
555    }
556
557    #[test]
558    fn test_gene_outcome() {
559        let mut gene = Gene::new("test", Instinct::Perceive);
560        gene.record_outcome(true);
561        gene.record_outcome(true);
562        gene.record_outcome(false);
563        assert!((gene.success_rate() - 0.666).abs() < 0.01);
564        assert!(gene.fitness > 0.5);
565    }
566
567    #[test]
568    fn test_rna_translation() {
569        let mut gene = Gene::new("test", Instinct::Navigate);
570        gene.bytecode = vec![0x03, 0x00, 0x10, 0x00];
571        let rna = RnaMessenger::translate(&gene);
572        assert_eq!(rna.translated_bytecode, gene.bytecode);
573        assert_eq!(rna.source_gene, "test");
574    }
575
576    #[test]
577    fn test_quarantine_in_agent() {
578        let mut agent = BiologicalAgent::new("agent-1", 100.0);
579        let mut bad = Gene::new("bad", Instinct::Navigate);
580        bad.use_count = 20; bad.success_count = 1; bad.fitness = 0.05;
581        agent.add_gene(bad);
582        let q = agent.quarantine_bad_genes();
583        assert_eq!(q.len(), 1);
584        assert!(agent.genes.get("bad").is_none());
585    }
586
587    #[test]
588    fn test_gene_crossover() {
589        let mut a = BiologicalAgent::new("a", 100.0);
590        let mut b = BiologicalAgent::new("b", 100.0);
591        let mut g1 = Gene::new("shared", Instinct::Navigate);
592        g1.bytecode = vec![1, 2, 3];
593        g1.fitness = 0.8;
594        let mut g2 = Gene::new("shared", Instinct::Navigate);
595        g2.bytecode = vec![4, 5, 6];
596        g2.fitness = 0.6;
597        a.add_gene(g1);
598        b.add_gene(g2);
599        let children = a.crossover_genes(&b, 1.0);
600        assert!(!children.is_empty());
601    }
602
603    #[test]
604    fn test_overall_fitness() {
605        let mut agent = BiologicalAgent::new("agent-1", 100.0);
606        let f = agent.overall_fitness();
607        assert!(f >= 0.0 && f <= 1.0);
608    }
609
610    #[test]
611    fn test_tick_circadian() {
612        let mut agent = BiologicalAgent::new("agent-1", 100.0);
613        let h1 = agent.circadian_hour;
614        agent.tick();
615        let h2 = agent.circadian_hour;
616        assert!(h2 > h1);
617    }
618
619    #[test]
620    fn test_find_gene() {
621        let mut agent = BiologicalAgent::new("agent-1", 100.0);
622        let gene = Gene::new("nav", Instinct::Navigate);
623        agent.add_gene(gene);
624        let enzyme = Enzyme::new("nav_enz", vec![1, 0], vec!["nav"]);
625        agent.add_enzyme(enzyme);
626        let found = agent.find_gene(&[1, 0]);
627        assert!(found.is_some());
628        assert_eq!(found.unwrap().0, "nav");
629    }
630}