1#![allow(dead_code)]
9
10use anyhow::Result;
11use serde::{Deserialize, Serialize};
12use std::collections::HashMap;
13
14#[derive(Debug, Clone, Serialize, Deserialize)]
16pub struct ArchitectureAnalysisConfig {
17 pub enable_parameter_counting: bool,
19 pub enable_receptive_field_calculation: bool,
21 pub enable_depth_width_analysis: bool,
23 pub enable_connectivity_patterns: bool,
25 pub enable_symmetry_detection: bool,
27 pub max_receptive_field_depth: usize,
29 pub sampling_rate: f32,
31}
32
33impl Default for ArchitectureAnalysisConfig {
34 fn default() -> Self {
35 Self {
36 enable_parameter_counting: true,
37 enable_receptive_field_calculation: true,
38 enable_depth_width_analysis: true,
39 enable_connectivity_patterns: true,
40 enable_symmetry_detection: true,
41 max_receptive_field_depth: 50,
42 sampling_rate: 1.0,
43 }
44 }
45}
46
47#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
49pub enum LayerType {
50 Linear,
51 Conv2D,
52 Conv3D,
53 BatchNorm,
54 LayerNorm,
55 Attention,
56 Embedding,
57 Dropout,
58 Activation,
59 Pooling,
60 Residual,
61 Unknown,
62}
63
64#[derive(Debug, Clone, Serialize, Deserialize)]
66pub struct LayerInfo {
67 pub id: String,
68 pub name: String,
69 pub layer_type: LayerType,
70 pub input_shape: Vec<usize>,
71 pub output_shape: Vec<usize>,
72 pub parameters: usize,
73 pub trainable_parameters: usize,
74 pub memory_usage: usize,
75 pub flops: u64,
76 pub receptive_field: Option<ReceptiveField>,
77}
78
79#[derive(Debug, Clone, Serialize, Deserialize)]
81pub struct ReceptiveField {
82 pub size: Vec<usize>,
83 pub stride: Vec<usize>,
84 pub padding: Vec<usize>,
85 pub effective_size: Vec<usize>,
86}
87
88#[derive(Debug, Clone, Serialize, Deserialize)]
90pub struct ConnectivityPattern {
91 pub from_layer: String,
92 pub to_layer: String,
93 pub connection_type: ConnectionType,
94 pub strength: f32,
95}
96
97#[derive(Debug, Clone, Serialize, Deserialize, Hash, Eq, PartialEq)]
98pub enum ConnectionType {
99 Sequential,
100 Residual,
101 Attention,
102 Skip,
103 Recurrent,
104 Branching,
105}
106
107#[derive(Debug, Clone, Serialize, Deserialize)]
109pub struct SymmetryInfo {
110 pub symmetry_type: SymmetryType,
111 pub symmetric_layers: Vec<String>,
112 pub confidence: f32,
113 pub description: String,
114}
115
116#[derive(Debug, Clone, Serialize, Deserialize)]
117pub enum SymmetryType {
118 Translational,
119 Rotational,
120 Reflection,
121 Permutation,
122 Block,
123}
124
125#[derive(Debug, Clone, Serialize, Deserialize)]
127pub struct ArchitectureAnalysisReport {
128 pub total_parameters: usize,
129 pub trainable_parameters: usize,
130 pub model_size_mb: f32,
131 pub total_flops: u64,
132 pub model_depth: usize,
133 pub model_width: usize,
134 pub layers: Vec<LayerInfo>,
135 pub connectivity_patterns: Vec<ConnectivityPattern>,
136 pub symmetries: Vec<SymmetryInfo>,
137 pub parameter_distribution: HashMap<LayerType, usize>,
138 pub bottlenecks: Vec<String>,
139 pub efficiency_metrics: EfficiencyMetrics,
140}
141
142#[derive(Debug, Clone, Serialize, Deserialize)]
144pub struct EfficiencyMetrics {
145 pub parameter_efficiency: f32,
146 pub flops_efficiency: f32,
147 pub memory_efficiency: f32,
148 pub depth_efficiency: f32,
149 pub overall_score: f32,
150}
151
152#[derive(Debug)]
154pub struct ArchitectureAnalyzer {
155 config: ArchitectureAnalysisConfig,
156 layers: Vec<LayerInfo>,
157 connections: Vec<ConnectivityPattern>,
158 analysis_cache: HashMap<String, ArchitectureAnalysisReport>,
159}
160
161impl ArchitectureAnalyzer {
162 pub fn new(config: ArchitectureAnalysisConfig) -> Self {
164 Self {
165 config,
166 layers: Vec::new(),
167 connections: Vec::new(),
168 analysis_cache: HashMap::new(),
169 }
170 }
171
172 pub fn register_layer(&mut self, layer: LayerInfo) {
174 self.layers.push(layer);
175 }
176
177 pub fn add_connection(&mut self, pattern: ConnectivityPattern) {
179 self.connections.push(pattern);
180 }
181
182 pub async fn analyze(&mut self) -> Result<ArchitectureAnalysisReport> {
184 let mut report = ArchitectureAnalysisReport {
185 total_parameters: 0,
186 trainable_parameters: 0,
187 model_size_mb: 0.0,
188 total_flops: 0,
189 model_depth: 0,
190 model_width: 0,
191 layers: self.layers.clone(),
192 connectivity_patterns: self.connections.clone(),
193 symmetries: Vec::new(),
194 parameter_distribution: HashMap::new(),
195 bottlenecks: Vec::new(),
196 efficiency_metrics: EfficiencyMetrics {
197 parameter_efficiency: 0.0,
198 flops_efficiency: 0.0,
199 memory_efficiency: 0.0,
200 depth_efficiency: 0.0,
201 overall_score: 0.0,
202 },
203 };
204
205 if self.config.enable_parameter_counting {
206 self.count_parameters(&mut report);
207 }
208
209 if self.config.enable_receptive_field_calculation {
210 self.calculate_receptive_fields(&mut report).await?;
211 }
212
213 if self.config.enable_depth_width_analysis {
214 self.analyze_depth_width(&mut report);
215 }
216
217 if self.config.enable_connectivity_patterns {
218 self.analyze_connectivity_patterns(&mut report);
219 }
220
221 if self.config.enable_symmetry_detection {
222 self.detect_symmetries(&mut report);
223 }
224
225 self.calculate_efficiency_metrics(&mut report);
226 self.identify_bottlenecks(&mut report);
227
228 Ok(report)
229 }
230
231 fn count_parameters(&self, report: &mut ArchitectureAnalysisReport) {
233 let mut param_distribution: HashMap<LayerType, usize> = HashMap::new();
234
235 for layer in &self.layers {
236 report.total_parameters += layer.parameters;
237 report.trainable_parameters += layer.trainable_parameters;
238
239 *param_distribution.entry(layer.layer_type.clone()).or_insert(0) += layer.parameters;
240 }
241
242 report.parameter_distribution = param_distribution;
243
244 report.model_size_mb = (report.total_parameters * 4) as f32 / (1024.0 * 1024.0);
246
247 report.total_flops = self.layers.iter().map(|l| l.flops).sum();
249 }
250
251 async fn calculate_receptive_fields(
253 &mut self,
254 report: &mut ArchitectureAnalysisReport,
255 ) -> Result<()> {
256 for layer in &mut self.layers {
257 if matches!(layer.layer_type, LayerType::Conv2D | LayerType::Conv3D) {
258 layer.receptive_field =
259 Some(Self::compute_receptive_field_static(&layer.layer_type));
260 }
261 }
262
263 report.layers = self.layers.clone();
264 Ok(())
265 }
266
267 fn compute_receptive_field_static(layer_type: &LayerType) -> ReceptiveField {
269 match layer_type {
270 LayerType::Conv2D => {
271 let kernel_size = vec![3, 3]; let stride = vec![1, 1];
274 let padding = vec![1, 1];
275
276 ReceptiveField {
277 size: kernel_size.clone(),
278 stride,
279 padding,
280 effective_size: kernel_size,
281 }
282 },
283 LayerType::Conv3D => {
284 let kernel_size = vec![3, 3, 3]; let stride = vec![1, 1, 1];
287 let padding = vec![1, 1, 1];
288
289 ReceptiveField {
290 size: kernel_size.clone(),
291 stride,
292 padding,
293 effective_size: kernel_size,
294 }
295 },
296 _ => {
297 ReceptiveField {
299 size: vec![1],
300 stride: vec![1],
301 padding: vec![0],
302 effective_size: vec![1],
303 }
304 },
305 }
306 }
307
308 fn compute_receptive_field(&self, layer: &LayerInfo) -> ReceptiveField {
310 Self::compute_receptive_field_static(&layer.layer_type)
311 }
312
313 fn analyze_depth_width(&self, report: &mut ArchitectureAnalysisReport) {
315 report.model_depth = self.layers.len();
317
318 report.model_width = self.layers.iter().map(|l| l.parameters).max().unwrap_or(0);
320 }
321
322 fn analyze_connectivity_patterns(&self, report: &mut ArchitectureAnalysisReport) {
324 let mut pattern_types: HashMap<ConnectionType, usize> = HashMap::new();
325
326 for connection in &self.connections {
327 *pattern_types.entry(connection.connection_type.clone()).or_insert(0) += 1;
328 }
329
330 for (connection_type, count) in pattern_types {
332 if count > self.layers.len() / 2 {
333 report.bottlenecks.push(format!(
335 "High {:?} connectivity: {} connections",
336 connection_type, count
337 ));
338 }
339 }
340 }
341
342 fn detect_symmetries(&self, report: &mut ArchitectureAnalysisReport) {
344 let mut block_patterns: HashMap<Vec<LayerType>, Vec<usize>> = HashMap::new();
346
347 for window_size in 2..=5.min(self.layers.len()) {
349 for i in 0..=(self.layers.len() - window_size) {
350 let pattern: Vec<LayerType> =
351 self.layers[i..i + window_size].iter().map(|l| l.layer_type.clone()).collect();
352
353 block_patterns.entry(pattern).or_default().push(i);
354 }
355 }
356
357 for (pattern, positions) in block_patterns {
359 if positions.len() > 1 {
360 let confidence = positions.len() as f32 / self.layers.len() as f32;
361
362 if confidence > 0.1 {
363 report.symmetries.push(SymmetryInfo {
365 symmetry_type: SymmetryType::Block,
366 symmetric_layers: positions
367 .iter()
368 .map(|&i| format!("block_{}", i))
369 .collect(),
370 confidence,
371 description: format!(
372 "Repeated block pattern: {:?} appears {} times",
373 pattern,
374 positions.len()
375 ),
376 });
377 }
378 }
379 }
380
381 let mut param_groups: HashMap<usize, Vec<String>> = HashMap::new();
383 for layer in &self.layers {
384 param_groups.entry(layer.parameters).or_default().push(layer.id.clone());
385 }
386
387 for (param_count, layer_ids) in param_groups {
388 if layer_ids.len() > 2 && param_count > 0 {
389 let confidence = layer_ids.len() as f32 / self.layers.len() as f32;
390
391 report.symmetries.push(SymmetryInfo {
392 symmetry_type: SymmetryType::Permutation,
393 symmetric_layers: layer_ids.clone(),
394 confidence,
395 description: format!(
396 "Parameter symmetry: {} layers with {} parameters each",
397 layer_ids.len(),
398 param_count
399 ),
400 });
401 }
402 }
403 }
404
405 fn calculate_efficiency_metrics(&self, report: &mut ArchitectureAnalysisReport) {
407 let total_params = report.total_parameters as f32;
408 let total_flops = report.total_flops as f32;
409 let depth = report.model_depth as f32;
410 let memory = report.model_size_mb;
411
412 report.efficiency_metrics.parameter_efficiency = if total_params > 0.0 {
414 1.0 / (total_params / 1_000_000.0).log10().max(1.0) } else {
416 1.0
417 };
418
419 report.efficiency_metrics.flops_efficiency = if total_flops > 0.0 {
421 1.0 / (total_flops / 1_000_000_000.0).log10().max(1.0) } else {
423 1.0
424 };
425
426 report.efficiency_metrics.memory_efficiency = if memory > 0.0 {
428 1.0 / (memory / 100.0).log10().max(1.0) } else {
430 1.0
431 };
432
433 report.efficiency_metrics.depth_efficiency = if depth > 0.0 {
435 let optimal_depth = 20.0; 1.0 - ((depth - optimal_depth).abs() / optimal_depth).min(1.0)
437 } else {
438 0.0
439 };
440
441 report.efficiency_metrics.overall_score = 0.3
443 * report.efficiency_metrics.parameter_efficiency
444 + 0.3 * report.efficiency_metrics.flops_efficiency
445 + 0.2 * report.efficiency_metrics.memory_efficiency
446 + 0.2 * report.efficiency_metrics.depth_efficiency;
447 }
448
449 fn identify_bottlenecks(&self, report: &mut ArchitectureAnalysisReport) {
451 if let Some(_max_params) = self.layers.iter().map(|l| l.parameters).max() {
453 let avg_params = report.total_parameters / self.layers.len().max(1);
454
455 for layer in &self.layers {
456 if layer.parameters > avg_params * 5 {
457 report.bottlenecks.push(format!(
458 "Parameter bottleneck: Layer '{}' has {} parameters ({}x average)",
459 layer.name,
460 layer.parameters,
461 layer.parameters / avg_params.max(1)
462 ));
463 }
464 }
465 }
466
467 for layer in &self.layers {
469 if layer.memory_usage > 100 * 1024 * 1024 {
470 report.bottlenecks.push(format!(
472 "Memory bottleneck: Layer '{}' uses {:.1}MB memory",
473 layer.name,
474 layer.memory_usage as f32 / (1024.0 * 1024.0)
475 ));
476 }
477 }
478
479 if let Some(_max_flops) = self.layers.iter().map(|l| l.flops).max() {
481 let avg_flops = report.total_flops / self.layers.len().max(1) as u64;
482
483 for layer in &self.layers {
484 if layer.flops > avg_flops * 10 {
485 report.bottlenecks.push(format!(
486 "Computation bottleneck: Layer '{}' requires {} FLOPS ({}x average)",
487 layer.name,
488 layer.flops,
489 layer.flops / avg_flops.max(1)
490 ));
491 }
492 }
493 }
494 }
495
496 pub async fn quick_analysis(&self) -> Result<crate::QuickArchitectureSummary> {
498 let total_parameters = self.layers.iter().map(|l| l.parameters as u64).sum::<u64>();
499 let total_flops = self.layers.iter().map(|l| l.flops).sum::<u64>();
500
501 let model_size_mb = (total_parameters as f64 * 4.0) / (1024.0 * 1024.0);
503
504 let efficiency_score = if total_flops > 0 {
506 (total_parameters as f64 / total_flops as f64 * 1000.0).min(100.0)
507 } else {
508 50.0
509 };
510
511 let mut recommendations = Vec::new();
512 if total_parameters > 1_000_000_000 {
513 recommendations
514 .push("Consider model compression techniques for large model".to_string());
515 }
516 if efficiency_score < 30.0 {
517 recommendations.push("Model architecture could be more efficient".to_string());
518 }
519 if model_size_mb > 1000.0 {
520 recommendations.push("Large model size may impact deployment".to_string());
521 }
522 if recommendations.is_empty() {
523 recommendations.push("Architecture appears well-balanced".to_string());
524 }
525
526 Ok(crate::QuickArchitectureSummary {
527 total_parameters,
528 model_size_mb,
529 efficiency_score,
530 recommendations,
531 })
532 }
533
534 pub async fn generate_report(&self) -> Result<ArchitectureAnalysisReport> {
536 let mut temp_analyzer = ArchitectureAnalyzer {
538 config: self.config.clone(),
539 layers: self.layers.clone(),
540 connections: self.connections.clone(),
541 analysis_cache: HashMap::new(),
542 };
543
544 temp_analyzer.analyze().await
545 }
546
547 pub fn clear(&mut self) {
549 self.layers.clear();
550 self.connections.clear();
551 self.analysis_cache.clear();
552 }
553
554 pub fn get_summary(&self) -> ArchitectureSummary {
556 let total_params: usize = self.layers.iter().map(|l| l.parameters).sum();
557 let total_flops: u64 = self.layers.iter().map(|l| l.flops).sum();
558
559 ArchitectureSummary {
560 total_layers: self.layers.len(),
561 total_parameters: total_params,
562 total_flops,
563 average_layer_size: if !self.layers.is_empty() {
564 total_params / self.layers.len()
565 } else {
566 0
567 },
568 layer_type_distribution: {
569 let mut dist = HashMap::new();
570 for layer in &self.layers {
571 *dist.entry(layer.layer_type.clone()).or_insert(0) += 1;
572 }
573 dist
574 },
575 }
576 }
577}
578
579#[derive(Debug, Clone, Serialize, Deserialize)]
581pub struct ArchitectureSummary {
582 pub total_layers: usize,
583 pub total_parameters: usize,
584 pub total_flops: u64,
585 pub average_layer_size: usize,
586 pub layer_type_distribution: HashMap<LayerType, usize>,
587}
588
589pub fn create_layer_info(
591 id: String,
592 name: String,
593 layer_type: LayerType,
594 input_shape: Vec<usize>,
595 output_shape: Vec<usize>,
596 parameters: usize,
597) -> LayerInfo {
598 let memory_usage = parameters * 4; let flops = estimate_flops(&layer_type, &input_shape, &output_shape, parameters);
600
601 LayerInfo {
602 id,
603 name,
604 layer_type,
605 input_shape,
606 output_shape,
607 parameters,
608 trainable_parameters: parameters, memory_usage,
610 flops,
611 receptive_field: None,
612 }
613}
614
615fn estimate_flops(
617 layer_type: &LayerType,
618 input_shape: &[usize],
619 output_shape: &[usize],
620 parameters: usize,
621) -> u64 {
622 match layer_type {
623 LayerType::Linear => {
624 if input_shape.len() >= 2 && output_shape.len() >= 2 {
625 let batch_size = input_shape[0] as u64;
626 let input_features = input_shape[1] as u64;
627 let output_features = output_shape[1] as u64;
628 batch_size * input_features * output_features * 2
629 } else {
630 parameters as u64 * 2
631 }
632 },
633 LayerType::Conv2D => {
634 if output_shape.len() >= 4 {
635 let batch_size = output_shape[0] as u64;
636 let output_channels = output_shape[1] as u64;
637 let output_h = output_shape[2] as u64;
638 let output_w = output_shape[3] as u64;
639 batch_size
640 * output_channels
641 * output_h
642 * output_w
643 * (parameters as u64 / output_channels).max(1)
644 * 2
645 } else {
646 parameters as u64 * 2
647 }
648 },
649 LayerType::Attention => {
650 if input_shape.len() >= 3 {
651 let batch_size = input_shape[0] as u64;
652 let seq_len = input_shape[1] as u64;
653 let hidden_size = input_shape[2] as u64;
654 batch_size * seq_len * seq_len * hidden_size * 4
655 } else {
656 parameters as u64 * 4
657 }
658 },
659 _ => parameters as u64,
660 }
661}
662
663#[cfg(test)]
668mod tests {
669 use super::*;
670
671 fn make_linear_layer(id: &str, params: usize) -> LayerInfo {
672 create_layer_info(
673 id.to_string(),
674 format!("{}_layer", id),
675 LayerType::Linear,
676 vec![1, params],
677 vec![1, params],
678 params,
679 )
680 }
681
682 #[test]
685 fn test_config_default() {
686 let cfg = ArchitectureAnalysisConfig::default();
687 assert!(cfg.enable_parameter_counting);
688 assert!(cfg.enable_receptive_field_calculation);
689 assert!(cfg.enable_depth_width_analysis);
690 assert!(cfg.enable_connectivity_patterns);
691 assert!(cfg.enable_symmetry_detection);
692 assert!(cfg.max_receptive_field_depth > 0);
693 assert!((cfg.sampling_rate - 1.0).abs() < 1e-6);
694 }
695
696 #[test]
699 fn test_analyzer_new_empty() {
700 let analyzer = ArchitectureAnalyzer::new(ArchitectureAnalysisConfig::default());
701 let summary = analyzer.get_summary();
702 assert_eq!(summary.total_layers, 0);
703 assert_eq!(summary.total_parameters, 0);
704 }
705
706 #[test]
707 fn test_register_layer_accumulates() {
708 let mut analyzer = ArchitectureAnalyzer::new(ArchitectureAnalysisConfig::default());
709 analyzer.register_layer(make_linear_layer("l0", 512));
710 analyzer.register_layer(make_linear_layer("l1", 256));
711 assert_eq!(analyzer.get_summary().total_layers, 2);
712 }
713
714 #[test]
715 fn test_add_connection() {
716 let mut analyzer = ArchitectureAnalyzer::new(ArchitectureAnalysisConfig::default());
717 analyzer.register_layer(make_linear_layer("a", 128));
718 analyzer.register_layer(make_linear_layer("b", 128));
719 analyzer.add_connection(ConnectivityPattern {
720 from_layer: "a".to_string(),
721 to_layer: "b".to_string(),
722 connection_type: ConnectionType::Sequential,
723 strength: 1.0,
724 });
725 let summary = analyzer.get_summary();
726 assert_eq!(summary.total_layers, 2);
727 }
728
729 #[test]
730 fn test_clear_resets_state() {
731 let mut analyzer = ArchitectureAnalyzer::new(ArchitectureAnalysisConfig::default());
732 analyzer.register_layer(make_linear_layer("l0", 64));
733 analyzer.clear();
734 assert_eq!(analyzer.get_summary().total_layers, 0);
735 }
736
737 #[test]
740 fn test_create_layer_info_parameters() {
741 let layer = make_linear_layer("dense", 1024);
742 assert_eq!(layer.parameters, 1024);
743 assert_eq!(layer.trainable_parameters, 1024);
744 assert_eq!(layer.memory_usage, 1024 * 4);
745 assert!(layer.receptive_field.is_none());
746 }
747
748 #[test]
749 fn test_create_layer_info_conv2d_flops() {
750 let layer = create_layer_info(
751 "conv".to_string(),
752 "conv_layer".to_string(),
753 LayerType::Conv2D,
754 vec![1, 3, 224, 224],
755 vec![1, 64, 112, 112],
756 64 * 3 * 3 * 3,
757 );
758 assert!(layer.flops > 0);
759 }
760
761 #[test]
762 fn test_create_layer_info_attention_flops() {
763 let layer = create_layer_info(
764 "attn".to_string(),
765 "attention".to_string(),
766 LayerType::Attention,
767 vec![1, 128, 768],
768 vec![1, 128, 768],
769 768 * 768 * 4,
770 );
771 assert!(layer.flops > 0);
772 }
773
774 #[test]
777 fn test_summary_totals() {
778 let mut analyzer = ArchitectureAnalyzer::new(ArchitectureAnalysisConfig::default());
779 analyzer.register_layer(make_linear_layer("l0", 100));
780 analyzer.register_layer(make_linear_layer("l1", 200));
781 let s = analyzer.get_summary();
782 assert_eq!(s.total_parameters, 300);
783 assert_eq!(s.average_layer_size, 150);
784 }
785
786 #[test]
787 fn test_summary_layer_type_distribution() {
788 let mut analyzer = ArchitectureAnalyzer::new(ArchitectureAnalysisConfig::default());
789 analyzer.register_layer(make_linear_layer("l0", 64));
790 analyzer.register_layer(make_linear_layer("l1", 64));
791 let s = analyzer.get_summary();
792 assert_eq!(
793 s.layer_type_distribution.get(&LayerType::Linear).copied().unwrap_or(0),
794 2
795 );
796 }
797
798 #[test]
801 fn test_layer_type_all_variants() {
802 let types = [
803 LayerType::Linear,
804 LayerType::Conv2D,
805 LayerType::Conv3D,
806 LayerType::BatchNorm,
807 LayerType::LayerNorm,
808 LayerType::Attention,
809 LayerType::Embedding,
810 LayerType::Dropout,
811 LayerType::Activation,
812 LayerType::Pooling,
813 LayerType::Residual,
814 LayerType::Unknown,
815 ];
816 for t in &types {
817 assert!(!format!("{:?}", t).is_empty());
818 }
819 }
820
821 #[test]
824 fn test_connection_type_variants() {
825 let types = [
826 ConnectionType::Sequential,
827 ConnectionType::Residual,
828 ConnectionType::Attention,
829 ConnectionType::Skip,
830 ConnectionType::Recurrent,
831 ConnectionType::Branching,
832 ];
833 for t in &types {
834 assert!(!format!("{:?}", t).is_empty());
835 }
836 }
837
838 #[test]
841 fn test_symmetry_type_variants() {
842 let types = [
843 SymmetryType::Translational,
844 SymmetryType::Rotational,
845 SymmetryType::Reflection,
846 SymmetryType::Permutation,
847 SymmetryType::Block,
848 ];
849 for t in &types {
850 assert!(!format!("{:?}", t).is_empty());
851 }
852 }
853
854 #[test]
857 fn test_efficiency_metrics_construction() {
858 let metrics = EfficiencyMetrics {
859 parameter_efficiency: 0.8,
860 flops_efficiency: 0.7,
861 memory_efficiency: 0.9,
862 depth_efficiency: 0.6,
863 overall_score: 0.75,
864 };
865 assert!((metrics.overall_score - 0.75).abs() < 1e-6);
866 }
867
868 #[tokio::test]
871 async fn test_analyze_empty() {
872 let mut analyzer = ArchitectureAnalyzer::new(ArchitectureAnalysisConfig::default());
873 let report = analyzer.analyze().await.expect("analyze should succeed");
874 assert_eq!(report.total_parameters, 0);
875 assert_eq!(report.model_depth, 0);
876 }
877
878 #[tokio::test]
879 async fn test_analyze_parameter_counting() {
880 let mut analyzer = ArchitectureAnalyzer::new(ArchitectureAnalysisConfig::default());
881 analyzer.register_layer(make_linear_layer("l0", 512));
882 analyzer.register_layer(make_linear_layer("l1", 512));
883 let report = analyzer.analyze().await.expect("analyze should succeed");
884 assert_eq!(report.total_parameters, 1024);
885 assert_eq!(report.trainable_parameters, 1024);
886 }
887
888 #[tokio::test]
889 async fn test_analyze_depth_width() {
890 let mut analyzer = ArchitectureAnalyzer::new(ArchitectureAnalysisConfig::default());
891 analyzer.register_layer(make_linear_layer("l0", 128));
892 analyzer.register_layer(make_linear_layer("l1", 256));
893 analyzer.register_layer(make_linear_layer("l2", 64));
894 let report = analyzer.analyze().await.expect("analyze should succeed");
895 assert_eq!(report.model_depth, 3);
896 assert_eq!(report.model_width, 256);
897 }
898
899 #[tokio::test]
900 async fn test_quick_analysis_returns_summary() {
901 let mut analyzer = ArchitectureAnalyzer::new(ArchitectureAnalysisConfig::default());
902 analyzer.register_layer(make_linear_layer("l0", 1000));
903 let qs = analyzer.quick_analysis().await.expect("quick_analysis should succeed");
904 assert_eq!(qs.total_parameters, 1000);
905 assert!(!qs.recommendations.is_empty());
906 }
907
908 #[tokio::test]
909 async fn test_generate_report_symmetry_detection() {
910 let mut analyzer = ArchitectureAnalyzer::new(ArchitectureAnalysisConfig::default());
911 for i in 0..6 {
913 analyzer.register_layer(make_linear_layer(&format!("l{}", i), 256));
914 }
915 let report = analyzer.generate_report().await.expect("report should succeed");
916 assert_eq!(report.total_parameters, 6 * 256);
918 }
919}