1use 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#[derive(Debug, Clone, Serialize, Deserialize)]
23pub struct NeuroEvolutionConfig {
24 pub population_size: usize,
26 pub num_generations: usize,
28 pub mutation_rate: f64,
30 pub crossover_rate: f64,
32 pub selection_pressure: f64,
34 pub elite_ratio: f64,
36 pub tournament_size: usize,
38 pub max_depth: usize,
40 pub max_width: usize,
42 pub diversity_threshold: f64,
44 pub hardware_constraints: HardwareConstraints,
46 pub objective_weights: ObjectiveWeights,
48 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#[derive(Debug, Clone, Serialize, Deserialize)]
74pub struct HardwareConstraints {
75 pub max_memory_mb: usize,
77 pub max_inference_time_ms: f64,
79 pub max_parameters: usize,
81 pub max_flops: usize,
83 pub target_platform: HardwarePlatform,
85}
86
87impl Default for HardwareConstraints {
88 fn default() -> Self {
89 Self {
90 max_memory_mb: 8192, max_inference_time_ms: 100.0,
92 max_parameters: 10_000_000, max_flops: 1_000_000_000, target_platform: HardwarePlatform::GPU,
95 }
96 }
97}
98
99#[derive(Debug, Clone, Serialize, Deserialize)]
101pub enum HardwarePlatform {
102 CPU,
103 GPU,
104 TPU,
105 Mobile,
106 Edge,
107}
108
109#[derive(Debug, Clone, Serialize, Deserialize)]
111pub struct ObjectiveWeights {
112 pub accuracy: f64,
114 pub efficiency: f64,
116 pub memory: f64,
118 pub generalization: f64,
120 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#[derive(Debug, Clone, Serialize, Deserialize)]
138pub struct NeuralArchitecture {
139 pub id: Uuid,
141 pub layers: Vec<ArchitectureLayer>,
143 pub skip_connections: Vec<SkipConnection>,
145 pub hyperparameters: ArchitectureHyperparameters,
147 pub performance: Option<PerformanceMetrics>,
149 pub complexity: ArchitectureComplexity,
151}
152
153impl NeuralArchitecture {
154 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 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 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 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 for _conn in skip_connections {
218 parameters += 1000; 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 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 self.complexity = Self::calculate_complexity(&self.layers, &self.skip_connections);
245 }
246 }
247
248 fn mutate_layers(&mut self, config: &NeuroEvolutionConfig, rng: &mut Random) {
250 match rng.random_range(0..3) {
251 0 => {
252 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 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 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 fn mutate_skip_connections(&mut self, rng: &mut Random) {
279 match rng.random_range(0..3) {
280 0 => {
281 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 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 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 fn mutate_hyperparameters(&mut self, rng: &mut Random) {
314 self.hyperparameters.mutate(rng);
315 }
316
317 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 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 } 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 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 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 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#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
424pub struct ArchitectureLayer {
425 pub layer_type: LayerType,
427 pub input_size: usize,
429 pub output_size: usize,
431 pub parameters: LayerParameters,
433}
434
435impl ArchitectureLayer {
436 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 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 pub fn mutate_parameters(&mut self, rng: &mut Random) {
467 self.parameters.mutate(rng);
468 }
469
470 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; self.input_size * self.output_size * 3 + self.output_size * head_dim
477 }
478 LayerType::Convolution => {
479 let kernel_size = 3; 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 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 pub fn estimate_memory_mb(&self) -> usize {
514 let params = self.estimate_parameters();
515 let activations = self.output_size * 1000; (params + activations) * 4 / (1024 * 1024) }
518}
519
520#[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 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#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
550pub struct LayerParameters {
551 pub activation: ActivationFunction,
553 pub dropout: f64,
555 pub normalization: NormalizationType,
557 pub settings: HashMap<String, f64>,
559}
560
561impl LayerParameters {
562 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 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 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#[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#[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#[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#[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#[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 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); let memory_score = 1.0 / (1.0 + self.memory_usage_mb / 1000.0); 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 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#[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#[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 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 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 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 pub fn evolve(&mut self, config: &NeuroEvolutionConfig) -> Result<()> {
951 let mut rng = Random::default();
952
953 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 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 while new_population.len() < config.population_size {
980 if rng.random_bool_with_chance(config.crossover_rate) {
981 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 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 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 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 pub fn get_best(&self) -> Option<&NeuralArchitecture> {
1039 self.individuals.first()
1040 }
1041}
1042
1043#[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 pub async fn evaluate(&self, architecture: &NeuralArchitecture) -> Result<PerformanceMetrics> {
1060 if let Some(cached) = self.evaluation_cache.get(&architecture.id) {
1062 return Ok(cached.clone());
1063 }
1064
1065 let metrics = self.simulate_evaluation(architecture)?;
1067
1068 Ok(metrics)
1069 }
1070
1071 fn simulate_evaluation(&self, architecture: &NeuralArchitecture) -> Result<PerformanceMetrics> {
1073 let mut rng = Random::default();
1074
1075 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, };
1096
1097 Ok(metrics)
1098 }
1099
1100 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#[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 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 pub async fn evolve(&mut self) -> Result<NeuralArchitecture> {
1135 for generation in 0..self.config.num_generations {
1136 self.population.evaluate(&self.evaluator).await?;
1138
1139 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 if self.check_convergence() {
1151 break;
1152 }
1153
1154 if generation < self.config.num_generations - 1 {
1156 self.population.evolve(&self.config)?;
1157 }
1158 }
1159
1160 self.population
1162 .get_best()
1163 .cloned()
1164 .ok_or_else(|| anyhow::anyhow!("No best architecture found"))
1165 }
1166
1167 fn check_convergence(&self) -> bool {
1169 if self.evolution_history.len() < 10 {
1170 return false;
1171 }
1172
1173 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 }
1185
1186 pub fn get_stats(&self) -> &[EvolutionStats] {
1188 &self.evolution_history
1189 }
1190}
1191
1192#[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); }
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; 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 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, num_generations: 2, max_depth: 3, max_width: 16, ..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}