Skip to main content

trustformers_debug/
architecture_analysis.rs

1//! Architecture Analysis
2//!
3//! Comprehensive analysis tools for neural network architectures including
4//! parameter counting, receptive field calculation, and connectivity analysis.
5// reason: debug/profiling scaffolding — structs are constructed and their fields/methods
6// are retained for the data model, serialization completeness, and future consumers that
7// do not yet read every member. Consolidated from many item-level #[allow(dead_code)].
8#![allow(dead_code)]
9
10use anyhow::Result;
11use serde::{Deserialize, Serialize};
12use std::collections::HashMap;
13
14/// Configuration for architecture analysis
15#[derive(Debug, Clone, Serialize, Deserialize)]
16pub struct ArchitectureAnalysisConfig {
17    /// Enable parameter counting
18    pub enable_parameter_counting: bool,
19    /// Enable receptive field calculation
20    pub enable_receptive_field_calculation: bool,
21    /// Enable depth/width analysis
22    pub enable_depth_width_analysis: bool,
23    /// Enable connectivity pattern detection
24    pub enable_connectivity_patterns: bool,
25    /// Enable symmetry detection
26    pub enable_symmetry_detection: bool,
27    /// Maximum depth to analyze for receptive fields
28    pub max_receptive_field_depth: usize,
29    /// Sampling rate for large models (0.0 to 1.0)
30    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/// Layer type for architecture analysis
48#[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/// Information about a single layer
65#[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/// Receptive field information
80#[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/// Connectivity pattern between layers
89#[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/// Symmetry information in the architecture
108#[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/// Architecture analysis results
126#[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/// Model efficiency metrics
143#[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/// Architecture analyzer
153#[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    /// Create a new architecture analyzer
163    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    /// Register a layer for analysis
173    pub fn register_layer(&mut self, layer: LayerInfo) {
174        self.layers.push(layer);
175    }
176
177    /// Add a connection between layers
178    pub fn add_connection(&mut self, pattern: ConnectivityPattern) {
179        self.connections.push(pattern);
180    }
181
182    /// Analyze the registered architecture
183    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    /// Count parameters in all layers
232    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        // Estimate model size (4 bytes per float32 parameter)
245        report.model_size_mb = (report.total_parameters * 4) as f32 / (1024.0 * 1024.0);
246
247        // Calculate total FLOPS
248        report.total_flops = self.layers.iter().map(|l| l.flops).sum();
249    }
250
251    /// Calculate receptive fields for convolutional layers
252    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    /// Compute receptive field for a convolutional layer (static version)
268    fn compute_receptive_field_static(layer_type: &LayerType) -> ReceptiveField {
269        match layer_type {
270            LayerType::Conv2D => {
271                // Simple 2D convolution receptive field calculation
272                let kernel_size = vec![3, 3]; // Default 3x3 kernel
273                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                // Simple 3D convolution receptive field calculation
285                let kernel_size = vec![3, 3, 3]; // Default 3x3x3 kernel
286                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                // For non-conv layers, receptive field is 1
298                ReceptiveField {
299                    size: vec![1],
300                    stride: vec![1],
301                    padding: vec![0],
302                    effective_size: vec![1],
303                }
304            },
305        }
306    }
307
308    /// Compute receptive field for a convolutional layer
309    fn compute_receptive_field(&self, layer: &LayerInfo) -> ReceptiveField {
310        Self::compute_receptive_field_static(&layer.layer_type)
311    }
312
313    /// Analyze model depth and width
314    fn analyze_depth_width(&self, report: &mut ArchitectureAnalysisReport) {
315        // Calculate depth (number of sequential layers)
316        report.model_depth = self.layers.len();
317
318        // Calculate width (maximum number of parameters in a single layer)
319        report.model_width = self.layers.iter().map(|l| l.parameters).max().unwrap_or(0);
320    }
321
322    /// Analyze connectivity patterns
323    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        // Find unusual connectivity patterns
331        for (connection_type, count) in pattern_types {
332            if count > self.layers.len() / 2 {
333                // High connectivity, might indicate bottlenecks
334                report.bottlenecks.push(format!(
335                    "High {:?} connectivity: {} connections",
336                    connection_type, count
337                ));
338            }
339        }
340    }
341
342    /// Detect architectural symmetries
343    fn detect_symmetries(&self, report: &mut ArchitectureAnalysisReport) {
344        // Detect block symmetries (repeated layer patterns)
345        let mut block_patterns: HashMap<Vec<LayerType>, Vec<usize>> = HashMap::new();
346
347        // Look for patterns of 2-5 consecutive layers
348        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        // Find repeated patterns
358        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                    // At least 10% of the model
364                    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        // Detect parameter symmetries
382        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    /// Calculate efficiency metrics
406    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        // Parameter efficiency: fewer parameters for same capability is better
413        report.efficiency_metrics.parameter_efficiency = if total_params > 0.0 {
414            1.0 / (total_params / 1_000_000.0).log10().max(1.0) // Inverse log scale
415        } else {
416            1.0
417        };
418
419        // FLOPS efficiency: fewer FLOPS for same capability is better
420        report.efficiency_metrics.flops_efficiency = if total_flops > 0.0 {
421            1.0 / (total_flops / 1_000_000_000.0).log10().max(1.0) // Inverse log scale
422        } else {
423            1.0
424        };
425
426        // Memory efficiency: less memory usage is better
427        report.efficiency_metrics.memory_efficiency = if memory > 0.0 {
428            1.0 / (memory / 100.0).log10().max(1.0) // Inverse log scale
429        } else {
430            1.0
431        };
432
433        // Depth efficiency: moderate depth is best (not too shallow, not too deep)
434        report.efficiency_metrics.depth_efficiency = if depth > 0.0 {
435            let optimal_depth = 20.0; // Assumed optimal depth
436            1.0 - ((depth - optimal_depth).abs() / optimal_depth).min(1.0)
437        } else {
438            0.0
439        };
440
441        // Overall efficiency score (weighted average)
442        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    /// Identify potential bottlenecks
450    fn identify_bottlenecks(&self, report: &mut ArchitectureAnalysisReport) {
451        // Find layers with disproportionately high parameter counts
452        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        // Find layers with very large memory usage
468        for layer in &self.layers {
469            if layer.memory_usage > 100 * 1024 * 1024 {
470                // > 100MB
471                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        // Find layers with very high FLOPS
480        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    /// Quick architecture analysis for simplified interface
497    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        // Estimate model size in MB (4 bytes per float32 parameter)
502        let model_size_mb = (total_parameters as f64 * 4.0) / (1024.0 * 1024.0);
503
504        // Calculate efficiency score based on parameters to FLOPS ratio
505        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    /// Generate a comprehensive report
535    pub async fn generate_report(&self) -> Result<ArchitectureAnalysisReport> {
536        // Create a temporary clone to avoid mutable borrow issues
537        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    /// Clear all registered layers and connections
548    pub fn clear(&mut self) {
549        self.layers.clear();
550        self.connections.clear();
551        self.analysis_cache.clear();
552    }
553
554    /// Get summary statistics
555    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/// Summary statistics for architecture
580#[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
589/// Convenience function to create a layer info
590pub 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; // 4 bytes per float32
599    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, // Assume all parameters are trainable by default
609        memory_usage,
610        flops,
611        receptive_field: None,
612    }
613}
614
615/// Estimate FLOPS for a layer
616fn 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// ─────────────────────────────────────────────────────────────────────────────
664// Tests
665// ─────────────────────────────────────────────────────────────────────────────
666
667#[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    // ── Config ────────────────────────────────────────────────────────────
683
684    #[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    // ── ArchitectureAnalyzer ───────────────────────────────────────────────
697
698    #[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    // ── LayerInfo via create_layer_info ────────────────────────────────────
738
739    #[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    // ── ArchitectureSummary ────────────────────────────────────────────────
775
776    #[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    // ── LayerType variants ─────────────────────────────────────────────────
799
800    #[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    // ── ConnectionType variants ────────────────────────────────────────────
822
823    #[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    // ── SymmetryType variants ──────────────────────────────────────────────
839
840    #[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    // ── EfficiencyMetrics ──────────────────────────────────────────────────
855
856    #[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    // ── async analyze ──────────────────────────────────────────────────────
869
870    #[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        // Register 6 identical layers — should trigger permutation symmetry detection.
912        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        // Symmetry detection is heuristic; just verify the report is populated
917        assert_eq!(report.total_parameters, 6 * 256);
918    }
919}