oxirs_embed/
neuro_evolution.rs

1//! Neuro-Evolution for Automated Neural Architecture Search
2//!
3//! This module implements cutting-edge evolutionary algorithms for automatically
4//! discovering optimal neural network architectures for knowledge graph embeddings.
5//!
6//! Key innovations:
7//! - Multi-objective evolutionary optimization (accuracy vs. efficiency)
8//! - Hierarchical architecture encoding with genetic programming
9//! - Co-evolution of architectures and hyperparameters
10//! - Hardware-aware architecture search with efficiency constraints
11//! - Progressive complexity evolution with diversity preservation
12
13use anyhow::Result;
14#[allow(unused_imports)]
15use scirs2_core::random::{Random, Rng};
16use serde::{Deserialize, Serialize};
17use std::collections::{HashMap, HashSet};
18use std::fmt;
19use uuid::Uuid;
20
21/// Configuration for neuro-evolution
22#[derive(Debug, Clone, Serialize, Deserialize)]
23pub struct NeuroEvolutionConfig {
24    /// Population size
25    pub population_size: usize,
26    /// Number of generations
27    pub num_generations: usize,
28    /// Mutation rate
29    pub mutation_rate: f64,
30    /// Crossover rate
31    pub crossover_rate: f64,
32    /// Selection pressure
33    pub selection_pressure: f64,
34    /// Elite ratio (percentage of best individuals to preserve)
35    pub elite_ratio: f64,
36    /// Tournament size for selection
37    pub tournament_size: usize,
38    /// Maximum architecture depth
39    pub max_depth: usize,
40    /// Maximum architecture width
41    pub max_width: usize,
42    /// Diversity threshold
43    pub diversity_threshold: f64,
44    /// Hardware constraints
45    pub hardware_constraints: HardwareConstraints,
46    /// Multi-objective weights
47    pub objective_weights: ObjectiveWeights,
48    /// Complexity penalty
49    pub complexity_penalty: f64,
50}
51
52impl Default for NeuroEvolutionConfig {
53    fn default() -> Self {
54        Self {
55            population_size: 50,
56            num_generations: 100,
57            mutation_rate: 0.1,
58            crossover_rate: 0.8,
59            selection_pressure: 2.0,
60            elite_ratio: 0.1,
61            tournament_size: 3,
62            max_depth: 10,
63            max_width: 512,
64            diversity_threshold: 0.7,
65            hardware_constraints: HardwareConstraints::default(),
66            objective_weights: ObjectiveWeights::default(),
67            complexity_penalty: 0.01,
68        }
69    }
70}
71
72/// Hardware constraints for architecture search
73#[derive(Debug, Clone, Serialize, Deserialize)]
74pub struct HardwareConstraints {
75    /// Maximum memory usage in MB
76    pub max_memory_mb: usize,
77    /// Maximum inference time in ms
78    pub max_inference_time_ms: f64,
79    /// Maximum model parameters
80    pub max_parameters: usize,
81    /// Maximum FLOPs
82    pub max_flops: usize,
83    /// Target hardware platform
84    pub target_platform: HardwarePlatform,
85}
86
87impl Default for HardwareConstraints {
88    fn default() -> Self {
89        Self {
90            max_memory_mb: 8192, // 8GB
91            max_inference_time_ms: 100.0,
92            max_parameters: 10_000_000, // 10M parameters
93            max_flops: 1_000_000_000,   // 1B FLOPs
94            target_platform: HardwarePlatform::GPU,
95        }
96    }
97}
98
99/// Target hardware platforms
100#[derive(Debug, Clone, Serialize, Deserialize)]
101pub enum HardwarePlatform {
102    CPU,
103    GPU,
104    TPU,
105    Mobile,
106    Edge,
107}
108
109/// Multi-objective optimization weights
110#[derive(Debug, Clone, Serialize, Deserialize)]
111pub struct ObjectiveWeights {
112    /// Accuracy weight
113    pub accuracy: f64,
114    /// Efficiency weight (speed)
115    pub efficiency: f64,
116    /// Memory weight
117    pub memory: f64,
118    /// Generalization weight
119    pub generalization: f64,
120    /// Robustness weight
121    pub robustness: f64,
122}
123
124impl Default for ObjectiveWeights {
125    fn default() -> Self {
126        Self {
127            accuracy: 0.4,
128            efficiency: 0.3,
129            memory: 0.1,
130            generalization: 0.1,
131            robustness: 0.1,
132        }
133    }
134}
135
136/// Neural architecture representation
137#[derive(Debug, Clone, Serialize, Deserialize)]
138pub struct NeuralArchitecture {
139    /// Unique identifier
140    pub id: Uuid,
141    /// Architecture layers
142    pub layers: Vec<ArchitectureLayer>,
143    /// Skip connections
144    pub skip_connections: Vec<SkipConnection>,
145    /// Hyperparameters
146    pub hyperparameters: ArchitectureHyperparameters,
147    /// Performance metrics
148    pub performance: Option<PerformanceMetrics>,
149    /// Architecture complexity
150    pub complexity: ArchitectureComplexity,
151}
152
153impl NeuralArchitecture {
154    /// Create a new random architecture
155    pub fn random(config: &NeuroEvolutionConfig, rng: &mut Random) -> Self {
156        let mut layers = Vec::new();
157        let depth = rng.random_range(1..config.max_depth + 1);
158
159        for i in 0..depth {
160            let layer = ArchitectureLayer::random(config, i, rng);
161            layers.push(layer);
162        }
163
164        let skip_connections = Self::generate_skip_connections(&layers, rng);
165        let hyperparameters = ArchitectureHyperparameters::random(rng);
166        let complexity = Self::calculate_complexity(&layers, &skip_connections);
167
168        Self {
169            id: Uuid::new_v4(),
170            layers,
171            skip_connections,
172            hyperparameters,
173            performance: None,
174            complexity,
175        }
176    }
177
178    /// Generate skip connections
179    fn generate_skip_connections(
180        layers: &[ArchitectureLayer],
181        rng: &mut Random,
182    ) -> Vec<SkipConnection> {
183        let mut connections = Vec::new();
184
185        for i in 0..layers.len() {
186            for j in (i + 2)..layers.len() {
187                if rng.random_bool_with_chance(0.2) {
188                    // 20% chance of skip connection
189                    connections.push(SkipConnection {
190                        from_layer: i,
191                        to_layer: j,
192                        connection_type: SkipConnectionType::random(rng),
193                    });
194                }
195            }
196        }
197
198        connections
199    }
200
201    /// Calculate architecture complexity
202    fn calculate_complexity(
203        layers: &[ArchitectureLayer],
204        skip_connections: &[SkipConnection],
205    ) -> ArchitectureComplexity {
206        let mut parameters = 0;
207        let mut flops = 0;
208        let mut memory_mb = 0;
209
210        for layer in layers {
211            parameters += layer.estimate_parameters();
212            flops += layer.estimate_flops();
213            memory_mb += layer.estimate_memory_mb();
214        }
215
216        // Add skip connection overhead
217        for _conn in skip_connections {
218            parameters += 1000; // Approximate overhead
219            flops += 10000;
220            memory_mb += 1;
221        }
222
223        ArchitectureComplexity {
224            parameters,
225            flops,
226            memory_mb,
227            depth: layers.len(),
228            width: layers.iter().map(|l| l.output_size).max().unwrap_or(0),
229        }
230    }
231
232    /// Mutate the architecture
233    pub fn mutate(&mut self, config: &NeuroEvolutionConfig, rng: &mut Random) {
234        if rng.random_bool_with_chance(config.mutation_rate) {
235            match rng.random_range(0..4) {
236                0 => self.mutate_layers(config, rng),
237                1 => self.mutate_skip_connections(rng),
238                2 => self.mutate_hyperparameters(rng),
239                3 => self.mutate_layer_parameters(rng),
240                _ => unreachable!(),
241            }
242
243            // Recalculate complexity
244            self.complexity = Self::calculate_complexity(&self.layers, &self.skip_connections);
245        }
246    }
247
248    /// Mutate layers
249    fn mutate_layers(&mut self, config: &NeuroEvolutionConfig, rng: &mut Random) {
250        match rng.random_range(0..3) {
251            0 => {
252                // Add layer
253                if self.layers.len() < config.max_depth {
254                    let position = rng.random_range(0..self.layers.len() + 1);
255                    let layer = ArchitectureLayer::random(config, position, rng);
256                    self.layers.insert(position, layer);
257                }
258            }
259            1 => {
260                // Remove layer
261                if self.layers.len() > 1 {
262                    let position = rng.random_range(0..self.layers.len());
263                    self.layers.remove(position);
264                }
265            }
266            2 => {
267                // Modify existing layer
268                if !self.layers.is_empty() {
269                    let position = rng.random_range(0..self.layers.len());
270                    self.layers[position].mutate(config, rng);
271                }
272            }
273            _ => unreachable!(),
274        }
275    }
276
277    /// Mutate skip connections
278    fn mutate_skip_connections(&mut self, rng: &mut Random) {
279        match rng.random_range(0..3) {
280            0 => {
281                // Add skip connection
282                if self.layers.len() >= 3 {
283                    let from = rng.random_range(0..self.layers.len() - 2);
284                    let to = rng.random_range(from + 2..self.layers.len());
285                    let connection = SkipConnection {
286                        from_layer: from,
287                        to_layer: to,
288                        connection_type: SkipConnectionType::random(rng),
289                    };
290                    self.skip_connections.push(connection);
291                }
292            }
293            1 => {
294                // Remove skip connection
295                if !self.skip_connections.is_empty() {
296                    let position = rng.random_range(0..self.skip_connections.len());
297                    self.skip_connections.remove(position);
298                }
299            }
300            2 => {
301                // Modify skip connection
302                if !self.skip_connections.is_empty() {
303                    let position = rng.random_range(0..self.skip_connections.len());
304                    self.skip_connections[position].connection_type =
305                        SkipConnectionType::random(rng);
306                }
307            }
308            _ => unreachable!(),
309        }
310    }
311
312    /// Mutate hyperparameters
313    fn mutate_hyperparameters(&mut self, rng: &mut Random) {
314        self.hyperparameters.mutate(rng);
315    }
316
317    /// Mutate layer parameters
318    fn mutate_layer_parameters(&mut self, rng: &mut Random) {
319        if !self.layers.is_empty() {
320            let layer_idx = rng.random_range(0..self.layers.len());
321            self.layers[layer_idx].mutate_parameters(rng);
322        }
323    }
324
325    /// Crossover with another architecture
326    pub fn crossover(&self, other: &Self, rng: &mut Random) -> (Self, Self) {
327        let min_layers = self.layers.len().min(other.layers.len());
328        let crossover_point = if min_layers <= 1 {
329            0 // No crossover possible with 0 or 1 layers
330        } else {
331            rng.random_range(1..min_layers)
332        };
333
334        let mut child1_layers = self.layers[..crossover_point].to_vec();
335        child1_layers.extend_from_slice(&other.layers[crossover_point..]);
336
337        let mut child2_layers = other.layers[..crossover_point].to_vec();
338        child2_layers.extend_from_slice(&self.layers[crossover_point..]);
339
340        let child1 = Self {
341            id: Uuid::new_v4(),
342            layers: child1_layers,
343            skip_connections: self.skip_connections.clone(),
344            hyperparameters: self.hyperparameters.crossover(&other.hyperparameters, rng),
345            performance: None,
346            complexity: ArchitectureComplexity::default(),
347        };
348
349        let child2 = Self {
350            id: Uuid::new_v4(),
351            layers: child2_layers,
352            skip_connections: other.skip_connections.clone(),
353            hyperparameters: other.hyperparameters.crossover(&self.hyperparameters, rng),
354            performance: None,
355            complexity: ArchitectureComplexity::default(),
356        };
357
358        (child1, child2)
359    }
360
361    /// Calculate diversity distance to another architecture
362    pub fn diversity_distance(&self, other: &Self) -> f64 {
363        let layer_distance = self.layer_distance(other);
364        let connection_distance = self.connection_distance(other);
365        let hyperparameter_distance = self.hyperparameters.distance(&other.hyperparameters);
366
367        (layer_distance + connection_distance + hyperparameter_distance) / 3.0
368    }
369
370    /// Calculate layer structure distance
371    fn layer_distance(&self, other: &Self) -> f64 {
372        let max_len = self.layers.len().max(other.layers.len());
373        if max_len == 0 {
374            return 0.0;
375        }
376
377        let mut differences = 0;
378        for i in 0..max_len {
379            match (self.layers.get(i), other.layers.get(i)) {
380                (Some(l1), Some(l2)) => {
381                    if l1.layer_type != l2.layer_type || l1.output_size != l2.output_size {
382                        differences += 1;
383                    }
384                }
385                (None, Some(_)) | (Some(_), None) => differences += 1,
386                (None, None) => continue,
387            }
388        }
389
390        differences as f64 / max_len as f64
391    }
392
393    /// Calculate skip connection distance
394    fn connection_distance(&self, other: &Self) -> f64 {
395        let max_connections = self
396            .skip_connections
397            .len()
398            .max(other.skip_connections.len());
399        if max_connections == 0 {
400            return 0.0;
401        }
402
403        let self_set: HashSet<_> = self
404            .skip_connections
405            .iter()
406            .map(|c| (c.from_layer, c.to_layer))
407            .collect();
408
409        let other_set: HashSet<_> = other
410            .skip_connections
411            .iter()
412            .map(|c| (c.from_layer, c.to_layer))
413            .collect();
414
415        let intersection = self_set.intersection(&other_set).count();
416        let union = self_set.union(&other_set).count();
417
418        1.0 - (intersection as f64 / union as f64)
419    }
420}
421
422/// Individual layer in the architecture
423#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
424pub struct ArchitectureLayer {
425    /// Layer type
426    pub layer_type: LayerType,
427    /// Input size
428    pub input_size: usize,
429    /// Output size
430    pub output_size: usize,
431    /// Layer-specific parameters
432    pub parameters: LayerParameters,
433}
434
435impl ArchitectureLayer {
436    /// Create a random layer
437    pub fn random(config: &NeuroEvolutionConfig, layer_index: usize, rng: &mut Random) -> Self {
438        let layer_type = LayerType::random(rng);
439        let output_size = rng.random_range(16..config.max_width + 1);
440        let input_size = if layer_index == 0 {
441            128
442        } else {
443            output_size / 2
444        };
445        let parameters = LayerParameters::random(&layer_type, rng);
446
447        Self {
448            layer_type,
449            input_size,
450            output_size,
451            parameters,
452        }
453    }
454
455    /// Mutate the layer
456    pub fn mutate(&mut self, config: &NeuroEvolutionConfig, rng: &mut Random) {
457        match rng.random_range(0..3) {
458            0 => self.layer_type = LayerType::random(rng),
459            1 => self.output_size = rng.random_range(16..config.max_width + 1),
460            2 => self.parameters.mutate(rng),
461            _ => unreachable!(),
462        }
463    }
464
465    /// Mutate layer parameters only
466    pub fn mutate_parameters(&mut self, rng: &mut Random) {
467        self.parameters.mutate(rng);
468    }
469
470    /// Estimate number of parameters
471    pub fn estimate_parameters(&self) -> usize {
472        match self.layer_type {
473            LayerType::Dense => self.input_size * self.output_size + self.output_size,
474            LayerType::Attention => {
475                let head_dim = self.output_size / 8; // Assume 8 heads
476                self.input_size * self.output_size * 3 + self.output_size * head_dim
477            }
478            LayerType::Convolution => {
479                let kernel_size = 3; // Assume 3x3 kernels
480                kernel_size * kernel_size * self.input_size * self.output_size + self.output_size
481            }
482            LayerType::GraphConv => self.input_size * self.output_size + self.output_size,
483            LayerType::LSTM => {
484                4 * (self.input_size * self.output_size + self.output_size * self.output_size)
485            }
486            LayerType::Transformer => {
487                let ff_size = self.output_size * 4;
488                self.input_size * self.output_size * 3
489                    + self.output_size * ff_size
490                    + ff_size * self.output_size
491            }
492            LayerType::Embedding => self.input_size * self.output_size,
493        }
494    }
495
496    /// Estimate FLOPs
497    pub fn estimate_flops(&self) -> usize {
498        match self.layer_type {
499            LayerType::Dense => self.input_size * self.output_size * 2,
500            LayerType::Attention => self.input_size * self.output_size * 6,
501            LayerType::Convolution => {
502                let kernel_size = 3;
503                kernel_size * kernel_size * self.input_size * self.output_size * 2
504            }
505            LayerType::GraphConv => self.input_size * self.output_size * 3,
506            LayerType::LSTM => self.input_size * self.output_size * 8,
507            LayerType::Transformer => self.input_size * self.output_size * 12,
508            LayerType::Embedding => self.input_size,
509        }
510    }
511
512    /// Estimate memory usage in MB
513    pub fn estimate_memory_mb(&self) -> usize {
514        let params = self.estimate_parameters();
515        let activations = self.output_size * 1000; // Assume batch size of 1000
516        (params + activations) * 4 / (1024 * 1024) // 4 bytes per float, convert to MB
517    }
518}
519
520/// Types of neural network layers
521#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
522pub enum LayerType {
523    Dense,
524    Attention,
525    Convolution,
526    GraphConv,
527    LSTM,
528    Transformer,
529    Embedding,
530}
531
532impl LayerType {
533    /// Generate random layer type
534    pub fn random(rng: &mut Random) -> Self {
535        match rng.random_range(0..7) {
536            0 => LayerType::Dense,
537            1 => LayerType::Attention,
538            2 => LayerType::Convolution,
539            3 => LayerType::GraphConv,
540            4 => LayerType::LSTM,
541            5 => LayerType::Transformer,
542            6 => LayerType::Embedding,
543            _ => unreachable!(),
544        }
545    }
546}
547
548/// Layer-specific parameters
549#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
550pub struct LayerParameters {
551    /// Activation function
552    pub activation: ActivationFunction,
553    /// Dropout rate
554    pub dropout: f64,
555    /// Normalization type
556    pub normalization: NormalizationType,
557    /// Layer-specific settings
558    pub settings: HashMap<String, f64>,
559}
560
561impl LayerParameters {
562    /// Create random parameters
563    pub fn random(layer_type: &LayerType, rng: &mut Random) -> Self {
564        let activation = ActivationFunction::random(rng);
565        let dropout = rng.gen_range(0.0..0.5);
566        let normalization = NormalizationType::random(rng);
567        let mut settings = HashMap::new();
568
569        match layer_type {
570            LayerType::Attention => {
571                settings.insert("num_heads".to_string(), rng.random_range(1..16) as f64);
572                settings.insert("head_dim".to_string(), rng.random_range(32..128) as f64);
573            }
574            LayerType::Convolution => {
575                settings.insert("kernel_size".to_string(), rng.random_range(1..7) as f64);
576                settings.insert("stride".to_string(), rng.random_range(1..3) as f64);
577            }
578            LayerType::LSTM => {
579                settings.insert(
580                    "bidirectional".to_string(),
581                    if rng.random_bool_with_chance(0.5) {
582                        1.0
583                    } else {
584                        0.0
585                    },
586                );
587            }
588            _ => {}
589        }
590
591        Self {
592            activation,
593            dropout,
594            normalization,
595            settings,
596        }
597    }
598
599    /// Mutate parameters
600    pub fn mutate(&mut self, rng: &mut Random) {
601        match rng.random_range(0..4) {
602            0 => self.activation = ActivationFunction::random(rng),
603            1 => self.dropout = rng.gen_range(0.0..0.5),
604            2 => self.normalization = NormalizationType::random(rng),
605            3 => {
606                // Mutate settings
607                for value in self.settings.values_mut() {
608                    if rng.random_bool_with_chance(0.3) {
609                        *value *= rng.gen_range(0.8..1.2);
610                    }
611                }
612            }
613            _ => unreachable!(),
614        }
615    }
616}
617
618/// Activation functions
619#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
620pub enum ActivationFunction {
621    ReLU,
622    GELU,
623    Swish,
624    Tanh,
625    Sigmoid,
626    LeakyReLU,
627    ELU,
628}
629
630impl ActivationFunction {
631    pub fn random(rng: &mut Random) -> Self {
632        match rng.random_range(0..7) {
633            0 => ActivationFunction::ReLU,
634            1 => ActivationFunction::GELU,
635            2 => ActivationFunction::Swish,
636            3 => ActivationFunction::Tanh,
637            4 => ActivationFunction::Sigmoid,
638            5 => ActivationFunction::LeakyReLU,
639            6 => ActivationFunction::ELU,
640            _ => unreachable!(),
641        }
642    }
643}
644
645/// Normalization types
646#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
647pub enum NormalizationType {
648    None,
649    LayerNorm,
650    BatchNorm,
651    GroupNorm,
652    RMSNorm,
653}
654
655impl NormalizationType {
656    pub fn random(rng: &mut Random) -> Self {
657        match rng.random_range(0..5) {
658            0 => NormalizationType::None,
659            1 => NormalizationType::LayerNorm,
660            2 => NormalizationType::BatchNorm,
661            3 => NormalizationType::GroupNorm,
662            4 => NormalizationType::RMSNorm,
663            _ => unreachable!(),
664        }
665    }
666}
667
668/// Skip connection types
669#[derive(Debug, Clone, Serialize, Deserialize)]
670pub struct SkipConnection {
671    pub from_layer: usize,
672    pub to_layer: usize,
673    pub connection_type: SkipConnectionType,
674}
675
676#[derive(Debug, Clone, Serialize, Deserialize)]
677pub enum SkipConnectionType {
678    Add,
679    Concat,
680    Multiply,
681    Gate,
682}
683
684impl SkipConnectionType {
685    pub fn random(rng: &mut Random) -> Self {
686        match rng.random_range(0..4) {
687            0 => SkipConnectionType::Add,
688            1 => SkipConnectionType::Concat,
689            2 => SkipConnectionType::Multiply,
690            3 => SkipConnectionType::Gate,
691            _ => unreachable!(),
692        }
693    }
694}
695
696/// Architecture hyperparameters
697#[derive(Debug, Clone, Serialize, Deserialize)]
698pub struct ArchitectureHyperparameters {
699    pub learning_rate: f64,
700    pub batch_size: usize,
701    pub weight_decay: f64,
702    pub gradient_clipping: f64,
703    pub optimizer: OptimizerType,
704    pub scheduler: SchedulerType,
705}
706
707impl ArchitectureHyperparameters {
708    pub fn random(rng: &mut Random) -> Self {
709        Self {
710            learning_rate: rng.random_f64() * (1e-2 - 1e-5) + 1e-5,
711            batch_size: {
712                let options = [16, 32, 64, 128, 256];
713                let idx = rng.random_range(0..options.len());
714                options[idx]
715            },
716            weight_decay: rng.random_f64() * (1e-3 - 1e-6) + 1e-6,
717            gradient_clipping: rng.random_f64() * (10.0 - 0.1) + 0.1,
718            optimizer: OptimizerType::random(rng),
719            scheduler: SchedulerType::random(rng),
720        }
721    }
722
723    pub fn mutate(&mut self, rng: &mut Random) {
724        match rng.random_range(0..6) {
725            0 => self.learning_rate *= rng.random_f64() * (2.0 - 0.5) + 0.5,
726            1 => {
727                let options = [16, 32, 64, 128, 256];
728                let idx = rng.random_range(0..options.len());
729                self.batch_size = options[idx];
730            }
731            2 => self.weight_decay *= rng.random_f64() * (2.0 - 0.5) + 0.5,
732            3 => self.gradient_clipping *= rng.random_f64() * (2.0 - 0.5) + 0.5,
733            4 => self.optimizer = OptimizerType::random(rng),
734            5 => self.scheduler = SchedulerType::random(rng),
735            _ => unreachable!(),
736        }
737    }
738
739    pub fn crossover(&self, other: &Self, rng: &mut Random) -> Self {
740        Self {
741            learning_rate: if rng.random_bool_with_chance(0.5) {
742                self.learning_rate
743            } else {
744                other.learning_rate
745            },
746            batch_size: if rng.random_bool_with_chance(0.5) {
747                self.batch_size
748            } else {
749                other.batch_size
750            },
751            weight_decay: if rng.random_bool_with_chance(0.5) {
752                self.weight_decay
753            } else {
754                other.weight_decay
755            },
756            gradient_clipping: if rng.random_bool_with_chance(0.5) {
757                self.gradient_clipping
758            } else {
759                other.gradient_clipping
760            },
761            optimizer: if rng.random_bool_with_chance(0.5) {
762                self.optimizer.clone()
763            } else {
764                other.optimizer.clone()
765            },
766            scheduler: if rng.random_bool_with_chance(0.5) {
767                self.scheduler.clone()
768            } else {
769                other.scheduler.clone()
770            },
771        }
772    }
773
774    pub fn distance(&self, other: &Self) -> f64 {
775        let lr_diff = (self.learning_rate - other.learning_rate).abs()
776            / self.learning_rate.max(other.learning_rate);
777        let batch_diff = (self.batch_size as f64 - other.batch_size as f64).abs()
778            / (self.batch_size as f64).max(other.batch_size as f64);
779        let wd_diff = (self.weight_decay - other.weight_decay).abs()
780            / self.weight_decay.max(other.weight_decay);
781
782        (lr_diff + batch_diff + wd_diff) / 3.0
783    }
784}
785
786#[derive(Debug, Clone, Serialize, Deserialize)]
787pub enum OptimizerType {
788    Adam,
789    AdamW,
790    SGD,
791    RMSprop,
792    AdaGrad,
793}
794
795impl OptimizerType {
796    pub fn random(rng: &mut Random) -> Self {
797        match rng.random_range(0..5) {
798            0 => OptimizerType::Adam,
799            1 => OptimizerType::AdamW,
800            2 => OptimizerType::SGD,
801            3 => OptimizerType::RMSprop,
802            4 => OptimizerType::AdaGrad,
803            _ => unreachable!(),
804        }
805    }
806}
807
808#[derive(Debug, Clone, Serialize, Deserialize)]
809pub enum SchedulerType {
810    Constant,
811    Linear,
812    Cosine,
813    Exponential,
814    StepLR,
815}
816
817impl SchedulerType {
818    pub fn random(rng: &mut Random) -> Self {
819        match rng.random_range(0..5) {
820            0 => SchedulerType::Constant,
821            1 => SchedulerType::Linear,
822            2 => SchedulerType::Cosine,
823            3 => SchedulerType::Exponential,
824            4 => SchedulerType::StepLR,
825            _ => unreachable!(),
826        }
827    }
828}
829
830/// Performance metrics for evaluation
831#[derive(Debug, Clone, Serialize, Deserialize)]
832pub struct PerformanceMetrics {
833    pub accuracy: f64,
834    pub inference_time_ms: f64,
835    pub memory_usage_mb: f64,
836    pub parameter_count: usize,
837    pub flops: usize,
838    pub generalization_score: f64,
839    pub robustness_score: f64,
840    pub multi_objective_score: f64,
841}
842
843impl PerformanceMetrics {
844    /// Calculate multi-objective fitness score
845    pub fn calculate_fitness(&self, weights: &ObjectiveWeights, complexity_penalty: f64) -> f64 {
846        let accuracy_score = self.accuracy;
847        let efficiency_score = 1.0 / (1.0 + self.inference_time_ms / 100.0); // Normalize to [0,1]
848        let memory_score = 1.0 / (1.0 + self.memory_usage_mb / 1000.0); // Normalize to [0,1]
849        let generalization_score = self.generalization_score;
850        let robustness_score = self.robustness_score;
851
852        let weighted_score = weights.accuracy * accuracy_score
853            + weights.efficiency * efficiency_score
854            + weights.memory * memory_score
855            + weights.generalization * generalization_score
856            + weights.robustness * robustness_score;
857
858        // Apply complexity penalty
859        let complexity_factor =
860            1.0 / (1.0 + complexity_penalty * self.parameter_count as f64 / 1e6);
861
862        weighted_score * complexity_factor
863    }
864}
865
866/// Architecture complexity metrics
867#[derive(Debug, Clone, Serialize, Deserialize, Default)]
868pub struct ArchitectureComplexity {
869    pub parameters: usize,
870    pub flops: usize,
871    pub memory_mb: usize,
872    pub depth: usize,
873    pub width: usize,
874}
875
876/// Population of neural architectures
877#[derive(Debug, Clone)]
878pub struct Population {
879    pub individuals: Vec<NeuralArchitecture>,
880    pub generation: usize,
881    pub best_fitness: f64,
882    pub average_fitness: f64,
883    pub diversity_score: f64,
884}
885
886impl Population {
887    /// Create initial random population
888    pub fn initialize(config: &NeuroEvolutionConfig) -> Self {
889        let mut rng = Random::default();
890        let mut individuals = Vec::new();
891
892        for _ in 0..config.population_size {
893            individuals.push(NeuralArchitecture::random(config, &mut rng));
894        }
895
896        Self {
897            individuals,
898            generation: 0,
899            best_fitness: 0.0,
900            average_fitness: 0.0,
901            diversity_score: 0.0,
902        }
903    }
904
905    /// Evaluate population fitness
906    pub async fn evaluate(&mut self, evaluator: &ArchitectureEvaluator) -> Result<()> {
907        let mut total_fitness = 0.0;
908        let mut best_fitness: f64 = 0.0;
909
910        for individual in &mut self.individuals {
911            let performance = evaluator.evaluate(individual).await?;
912            individual.performance = Some(performance.clone());
913
914            let fitness = performance.calculate_fitness(
915                &evaluator.config.objective_weights,
916                evaluator.config.complexity_penalty,
917            );
918
919            total_fitness += fitness;
920            best_fitness = best_fitness.max(fitness);
921        }
922
923        self.best_fitness = best_fitness;
924        self.average_fitness = total_fitness / self.individuals.len() as f64;
925        self.diversity_score = self.calculate_diversity();
926
927        Ok(())
928    }
929
930    /// Calculate population diversity
931    fn calculate_diversity(&self) -> f64 {
932        let mut total_distance = 0.0;
933        let mut count = 0;
934
935        for i in 0..self.individuals.len() {
936            for j in (i + 1)..self.individuals.len() {
937                total_distance += self.individuals[i].diversity_distance(&self.individuals[j]);
938                count += 1;
939            }
940        }
941
942        if count > 0 {
943            total_distance / count as f64
944        } else {
945            0.0
946        }
947    }
948
949    /// Evolve to next generation
950    pub fn evolve(&mut self, config: &NeuroEvolutionConfig) -> Result<()> {
951        let mut rng = Random::default();
952
953        // Sort by fitness
954        self.individuals.sort_by(|a, b| {
955            let fitness_a = a
956                .performance
957                .as_ref()
958                .expect("performance should be evaluated before evolve")
959                .calculate_fitness(&config.objective_weights, config.complexity_penalty);
960            let fitness_b = b
961                .performance
962                .as_ref()
963                .expect("performance should be evaluated before evolve")
964                .calculate_fitness(&config.objective_weights, config.complexity_penalty);
965            fitness_b
966                .partial_cmp(&fitness_a)
967                .expect("fitness values should be finite")
968        });
969
970        let mut new_population = Vec::new();
971
972        // Elite preservation
973        let elite_count = (config.population_size as f64 * config.elite_ratio) as usize;
974        for i in 0..elite_count {
975            new_population.push(self.individuals[i].clone());
976        }
977
978        // Generate offspring
979        while new_population.len() < config.population_size {
980            if rng.random_bool_with_chance(config.crossover_rate) {
981                // Crossover
982                let parent1 = self.tournament_selection(config, &mut rng);
983                let parent2 = self.tournament_selection(config, &mut rng);
984                let (mut child1, mut child2) = parent1.crossover(parent2, &mut rng);
985
986                // Mutation
987                child1.mutate(config, &mut rng);
988                child2.mutate(config, &mut rng);
989
990                new_population.push(child1);
991                if new_population.len() < config.population_size {
992                    new_population.push(child2);
993                }
994            } else {
995                // Mutation only
996                let parent = self.tournament_selection(config, &mut rng);
997                let mut child = parent.clone();
998                child.id = Uuid::new_v4();
999                child.mutate(config, &mut rng);
1000                new_population.push(child);
1001            }
1002        }
1003
1004        self.individuals = new_population;
1005        self.generation += 1;
1006
1007        Ok(())
1008    }
1009
1010    /// Tournament selection
1011    fn tournament_selection(
1012        &self,
1013        config: &NeuroEvolutionConfig,
1014        rng: &mut Random,
1015    ) -> &NeuralArchitecture {
1016        let mut best = &self.individuals[0];
1017        let mut best_fitness = 0.0;
1018
1019        for _ in 0..config.tournament_size {
1020            let candidate_idx = rng.random_range(0..self.individuals.len());
1021            let candidate = &self.individuals[candidate_idx];
1022
1023            if let Some(ref performance) = candidate.performance {
1024                let fitness = performance
1025                    .calculate_fitness(&config.objective_weights, config.complexity_penalty);
1026
1027                if fitness > best_fitness {
1028                    best = candidate;
1029                    best_fitness = fitness;
1030                }
1031            }
1032        }
1033
1034        best
1035    }
1036
1037    /// Get the best individual
1038    pub fn get_best(&self) -> Option<&NeuralArchitecture> {
1039        self.individuals.first()
1040    }
1041}
1042
1043/// Architecture evaluator
1044#[derive(Debug, Clone)]
1045pub struct ArchitectureEvaluator {
1046    pub config: NeuroEvolutionConfig,
1047    pub evaluation_cache: HashMap<Uuid, PerformanceMetrics>,
1048}
1049
1050impl ArchitectureEvaluator {
1051    pub fn new(config: NeuroEvolutionConfig) -> Self {
1052        Self {
1053            config,
1054            evaluation_cache: HashMap::new(),
1055        }
1056    }
1057
1058    /// Evaluate a single architecture
1059    pub async fn evaluate(&self, architecture: &NeuralArchitecture) -> Result<PerformanceMetrics> {
1060        // Check cache first
1061        if let Some(cached) = self.evaluation_cache.get(&architecture.id) {
1062            return Ok(cached.clone());
1063        }
1064
1065        // Simulate architecture evaluation
1066        let metrics = self.simulate_evaluation(architecture)?;
1067
1068        Ok(metrics)
1069    }
1070
1071    /// Simulate architecture evaluation (replace with actual training/evaluation)
1072    fn simulate_evaluation(&self, architecture: &NeuralArchitecture) -> Result<PerformanceMetrics> {
1073        let mut rng = Random::default();
1074
1075        // Simulate based on architecture properties
1076        let base_accuracy = 0.7 + rng.gen_range(0.0..0.2);
1077        let complexity_factor = 1.0 / (1.0 + architecture.complexity.parameters as f64 / 1e6);
1078        let accuracy = (base_accuracy * (0.8 + 0.4 * complexity_factor)).min(1.0);
1079
1080        let inference_time = 10.0 + architecture.complexity.parameters as f64 / 1e5;
1081        let memory_usage = architecture.complexity.memory_mb as f64;
1082
1083        let generalization_score = (accuracy * (0.9 + 0.1 * rng.gen_range(0.0..1.0))).min(1.0);
1084        let robustness_score = (accuracy * (0.85 + 0.15 * rng.gen_range(0.0..1.0))).min(1.0);
1085
1086        let metrics = PerformanceMetrics {
1087            accuracy,
1088            inference_time_ms: inference_time,
1089            memory_usage_mb: memory_usage,
1090            parameter_count: architecture.complexity.parameters,
1091            flops: architecture.complexity.flops,
1092            generalization_score,
1093            robustness_score,
1094            multi_objective_score: 0.0, // Will be calculated by fitness function
1095        };
1096
1097        Ok(metrics)
1098    }
1099
1100    /// Validate architecture against hardware constraints
1101    pub fn validate_constraints(&self, architecture: &NeuralArchitecture) -> bool {
1102        let constraints = &self.config.hardware_constraints;
1103
1104        architecture.complexity.memory_mb <= constraints.max_memory_mb
1105            && architecture.complexity.parameters <= constraints.max_parameters
1106            && architecture.complexity.flops <= constraints.max_flops
1107    }
1108}
1109
1110/// Main neuro-evolution system
1111#[derive(Debug, Clone)]
1112pub struct NeuroEvolutionSystem {
1113    pub config: NeuroEvolutionConfig,
1114    pub population: Population,
1115    pub evaluator: ArchitectureEvaluator,
1116    pub evolution_history: Vec<EvolutionStats>,
1117}
1118
1119impl NeuroEvolutionSystem {
1120    /// Create new neuro-evolution system
1121    pub fn new(config: NeuroEvolutionConfig) -> Self {
1122        let population = Population::initialize(&config);
1123        let evaluator = ArchitectureEvaluator::new(config.clone());
1124
1125        Self {
1126            config,
1127            population,
1128            evaluator,
1129            evolution_history: Vec::new(),
1130        }
1131    }
1132
1133    /// Run evolution for specified number of generations
1134    pub async fn evolve(&mut self) -> Result<NeuralArchitecture> {
1135        for generation in 0..self.config.num_generations {
1136            // Evaluate population
1137            self.population.evaluate(&self.evaluator).await?;
1138
1139            // Record statistics
1140            let stats = EvolutionStats {
1141                generation,
1142                best_fitness: self.population.best_fitness,
1143                average_fitness: self.population.average_fitness,
1144                diversity_score: self.population.diversity_score,
1145                best_architecture: self.population.get_best().cloned(),
1146            };
1147            self.evolution_history.push(stats);
1148
1149            // Check convergence
1150            if self.check_convergence() {
1151                break;
1152            }
1153
1154            // Evolve to next generation
1155            if generation < self.config.num_generations - 1 {
1156                self.population.evolve(&self.config)?;
1157            }
1158        }
1159
1160        // Return best architecture
1161        self.population
1162            .get_best()
1163            .cloned()
1164            .ok_or_else(|| anyhow::anyhow!("No best architecture found"))
1165    }
1166
1167    /// Check convergence criteria
1168    fn check_convergence(&self) -> bool {
1169        if self.evolution_history.len() < 10 {
1170            return false;
1171        }
1172
1173        // Check if fitness improvement has stagnated
1174        let recent_best: Vec<f64> = self
1175            .evolution_history
1176            .iter()
1177            .rev()
1178            .take(10)
1179            .map(|s| s.best_fitness)
1180            .collect();
1181
1182        let improvement = recent_best[0] - recent_best[9];
1183        improvement < 0.001 // Very small improvement threshold
1184    }
1185
1186    /// Get evolution statistics
1187    pub fn get_stats(&self) -> &[EvolutionStats] {
1188        &self.evolution_history
1189    }
1190}
1191
1192/// Evolution statistics for tracking progress
1193#[derive(Debug, Clone)]
1194pub struct EvolutionStats {
1195    pub generation: usize,
1196    pub best_fitness: f64,
1197    pub average_fitness: f64,
1198    pub diversity_score: f64,
1199    pub best_architecture: Option<NeuralArchitecture>,
1200}
1201
1202impl fmt::Display for EvolutionStats {
1203    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1204        write!(
1205            f,
1206            "Gen {}: Best={:.4}, Avg={:.4}, Diversity={:.4}",
1207            self.generation, self.best_fitness, self.average_fitness, self.diversity_score
1208        )
1209    }
1210}
1211
1212#[cfg(test)]
1213mod tests {
1214    use super::*;
1215
1216    #[test]
1217    fn test_neuro_evolution_config() {
1218        let config = NeuroEvolutionConfig::default();
1219        assert_eq!(config.population_size, 50);
1220        assert_eq!(config.num_generations, 100);
1221        assert!(config.mutation_rate > 0.0);
1222    }
1223
1224    #[test]
1225    fn test_neural_architecture_creation() {
1226        let config = NeuroEvolutionConfig::default();
1227        let mut rng = Random::default();
1228        let arch = NeuralArchitecture::random(&config, &mut rng);
1229
1230        assert!(!arch.layers.is_empty());
1231        assert!(arch.layers.len() <= config.max_depth);
1232        assert!(arch.complexity.parameters > 0);
1233    }
1234
1235    #[test]
1236    fn test_architecture_mutation() {
1237        let config = NeuroEvolutionConfig::default();
1238        let mut rng = Random::default();
1239        let mut arch = NeuralArchitecture::random(&config, &mut rng);
1240        let original_id = arch.id;
1241
1242        arch.mutate(&config, &mut rng);
1243        assert_eq!(arch.id, original_id); // ID should not change during mutation
1244    }
1245
1246    #[test]
1247    fn test_architecture_crossover() {
1248        let config = NeuroEvolutionConfig::default();
1249        let mut rng = Random::default();
1250        let parent1 = NeuralArchitecture::random(&config, &mut rng);
1251        let parent2 = NeuralArchitecture::random(&config, &mut rng);
1252
1253        let (child1, child2) = parent1.crossover(&parent2, &mut rng);
1254        assert_ne!(child1.id, parent1.id);
1255        assert_ne!(child2.id, parent2.id);
1256        assert_ne!(child1.id, child2.id);
1257    }
1258
1259    #[test]
1260    fn test_layer_parameter_estimation() {
1261        let layer = ArchitectureLayer {
1262            layer_type: LayerType::Dense,
1263            input_size: 128,
1264            output_size: 256,
1265            parameters: LayerParameters {
1266                activation: ActivationFunction::ReLU,
1267                dropout: 0.1,
1268                normalization: NormalizationType::LayerNorm,
1269                settings: HashMap::new(),
1270            },
1271        };
1272
1273        let params = layer.estimate_parameters();
1274        let expected = 128 * 256 + 256; // weights + biases
1275        assert_eq!(params, expected);
1276    }
1277
1278    #[test]
1279    fn test_population_initialization() {
1280        let config = NeuroEvolutionConfig::default();
1281        let population = Population::initialize(&config);
1282
1283        assert_eq!(population.individuals.len(), config.population_size);
1284        assert_eq!(population.generation, 0);
1285    }
1286
1287    #[test]
1288    fn test_diversity_calculation() {
1289        let config = NeuroEvolutionConfig::default();
1290        let mut rng = Random::default();
1291        let arch1 = NeuralArchitecture::random(&config, &mut rng);
1292        let arch2 = NeuralArchitecture::random(&config, &mut rng);
1293
1294        let distance = arch1.diversity_distance(&arch2);
1295        assert!((0.0..=1.0).contains(&distance));
1296    }
1297
1298    #[tokio::test]
1299    async fn test_architecture_evaluation() {
1300        let config = NeuroEvolutionConfig::default();
1301        let evaluator = ArchitectureEvaluator::new(config.clone());
1302        let mut rng = Random::default();
1303        let arch = NeuralArchitecture::random(&config, &mut rng);
1304
1305        let metrics = evaluator.evaluate(&arch).await.unwrap();
1306        assert!(metrics.accuracy >= 0.0 && metrics.accuracy <= 1.0);
1307        assert!(metrics.inference_time_ms > 0.0);
1308    }
1309
1310    #[test]
1311    fn test_hardware_constraints() {
1312        let config = NeuroEvolutionConfig::default();
1313        let evaluator = ArchitectureEvaluator::new(config.clone());
1314        let mut rng = Random::default();
1315        let arch = NeuralArchitecture::random(&config, &mut rng);
1316
1317        let is_valid = evaluator.validate_constraints(&arch);
1318        // Should be valid for reasonable random architectures
1319        assert!(
1320            is_valid || arch.complexity.parameters > config.hardware_constraints.max_parameters
1321        );
1322    }
1323
1324    #[tokio::test]
1325    async fn test_neuro_evolution_system() {
1326        let config = NeuroEvolutionConfig {
1327            population_size: 5, // Very small population for testing
1328            num_generations: 2, // Minimal generations for testing
1329            max_depth: 3,       // Limit architecture complexity
1330            max_width: 16,      // Limit architecture size
1331            ..Default::default()
1332        };
1333
1334        let mut system = NeuroEvolutionSystem::new(config);
1335        let best_arch = system.evolve().await.unwrap();
1336
1337        assert!(!best_arch.layers.is_empty());
1338        assert!(system.evolution_history.len() <= 2);
1339    }
1340}