quantrs2_circuit/
scirs2_similarity.rs

1//! Circuit similarity metrics using `SciRS2`
2//!
3//! This module implements sophisticated quantum circuit similarity and distance metrics
4//! leveraging `SciRS2`'s graph algorithms, numerical analysis, and machine learning capabilities.
5
6use crate::builder::Circuit;
7use crate::dag::{circuit_to_dag, CircuitDag};
8use crate::scirs2_matrices::SparseMatrix;
9use quantrs2_core::{
10    error::{QuantRS2Error, QuantRS2Result},
11    gate::GateOp,
12    qubit::QubitId,
13};
14use scirs2_core::Complex64;
15use serde::{Deserialize, Serialize};
16use std::collections::{HashMap, HashSet};
17use std::sync::Arc;
18
19// Placeholder types representing SciRS2 graph and ML interface
20// In the real implementation, these would be imported from SciRS2
21
22/// Graph representation for `SciRS2` integration
23#[derive(Debug, Clone)]
24pub struct SciRS2Graph {
25    /// Node identifiers
26    pub nodes: Vec<usize>,
27    /// Edge list (source, target, weight)
28    pub edges: Vec<(usize, usize, f64)>,
29    /// Node attributes
30    pub node_attributes: HashMap<usize, HashMap<String, String>>,
31    /// Edge attributes
32    pub edge_attributes: HashMap<(usize, usize), HashMap<String, f64>>,
33}
34
35/// Graph similarity algorithms available in `SciRS2`
36#[derive(Debug, Clone, PartialEq, Eq)]
37pub enum GraphSimilarityAlgorithm {
38    /// Graph edit distance
39    GraphEditDistance,
40    /// Spectral similarity based on eigenvalues
41    SpectralSimilarity,
42    /// Graph kernel methods
43    GraphKernel { kernel_type: GraphKernelType },
44    /// Network alignment
45    NetworkAlignment,
46    /// Subgraph isomorphism
47    SubgraphIsomorphism,
48    /// Graph neural network embeddings
49    GraphNeuralNetwork { embedding_dim: usize },
50}
51
52/// Graph kernel types
53#[derive(Debug, Clone, PartialEq, Eq)]
54pub enum GraphKernelType {
55    /// Random walk kernel
56    RandomWalk { steps: usize },
57    /// Weisfeiler-Lehman kernel
58    WeisfeilerLehman { iterations: usize },
59    /// Shortest path kernel
60    ShortestPath,
61    /// Graphlet kernel
62    Graphlet { size: usize },
63}
64
65/// Circuit similarity metrics
66#[derive(Debug, Clone)]
67pub struct CircuitSimilarityMetrics {
68    /// Structural similarity (0.0 to 1.0)
69    pub structural_similarity: f64,
70    /// Functional similarity (0.0 to 1.0)
71    pub functional_similarity: f64,
72    /// Gate sequence similarity (0.0 to 1.0)
73    pub sequence_similarity: f64,
74    /// Topological similarity (0.0 to 1.0)
75    pub topological_similarity: f64,
76    /// Overall similarity score (0.0 to 1.0)
77    pub overall_similarity: f64,
78    /// Detailed breakdown by metric type
79    pub detailed_metrics: HashMap<String, f64>,
80}
81
82/// Circuit distance measures
83#[derive(Debug, Clone)]
84pub struct CircuitDistanceMetrics {
85    /// Edit distance (minimum operations to transform one circuit to another)
86    pub edit_distance: usize,
87    /// Normalized edit distance (0.0 to 1.0)
88    pub normalized_edit_distance: f64,
89    /// Wasserstein distance between gate distributions
90    pub wasserstein_distance: f64,
91    /// Hausdorff distance between circuit embeddings
92    pub hausdorff_distance: f64,
93    /// Earth mover's distance
94    pub earth_movers_distance: f64,
95    /// Quantum process fidelity distance
96    pub process_fidelity_distance: f64,
97}
98
99/// Configuration for similarity computation
100#[derive(Debug, Clone)]
101pub struct SimilarityConfig {
102    /// Algorithms to use for comparison
103    pub algorithms: Vec<SimilarityAlgorithm>,
104    /// Weight for different similarity aspects
105    pub weights: SimilarityWeights,
106    /// Tolerance for numerical comparisons
107    pub tolerance: f64,
108    /// Whether to normalize results
109    pub normalize: bool,
110    /// Cache intermediate results
111    pub cache_results: bool,
112    /// Use parallel computation
113    pub parallel: bool,
114}
115
116/// Similarity computation algorithms
117#[derive(Debug, Clone, PartialEq, Eq)]
118pub enum SimilarityAlgorithm {
119    /// Gate-level comparison
120    GateLevel,
121    /// DAG structure comparison
122    DAGStructure,
123    /// Unitary matrix comparison
124    UnitaryMatrix,
125    /// Graph-based comparison
126    GraphBased { algorithm: GraphSimilarityAlgorithm },
127    /// Statistical comparison
128    Statistical,
129    /// Machine learning embeddings
130    MLEmbeddings { model_type: MLModelType },
131}
132
133/// Machine learning model types for embeddings
134#[derive(Debug, Clone, PartialEq, Eq)]
135pub enum MLModelType {
136    /// Variational autoencoder
137    VAE { latent_dim: usize },
138    /// Graph convolutional network
139    GCN { hidden_dims: Vec<usize> },
140    /// Transformer model
141    Transformer { num_heads: usize, num_layers: usize },
142    /// Pre-trained circuit embedding model
143    PreTrained { model_name: String },
144}
145
146/// Weights for combining different similarity measures
147#[derive(Debug, Clone)]
148pub struct SimilarityWeights {
149    /// Weight for structural similarity
150    pub structural: f64,
151    /// Weight for functional similarity
152    pub functional: f64,
153    /// Weight for gate sequence similarity
154    pub sequence: f64,
155    /// Weight for topological similarity
156    pub topological: f64,
157}
158
159impl Default for SimilarityWeights {
160    fn default() -> Self {
161        Self {
162            structural: 0.3,
163            functional: 0.4,
164            sequence: 0.2,
165            topological: 0.1,
166        }
167    }
168}
169
170impl Default for SimilarityConfig {
171    fn default() -> Self {
172        Self {
173            algorithms: vec![
174                SimilarityAlgorithm::GateLevel,
175                SimilarityAlgorithm::DAGStructure,
176                SimilarityAlgorithm::UnitaryMatrix,
177            ],
178            weights: SimilarityWeights::default(),
179            tolerance: 1e-12,
180            normalize: true,
181            cache_results: true,
182            parallel: false,
183        }
184    }
185}
186
187/// Circuit similarity analyzer using `SciRS2`
188pub struct CircuitSimilarityAnalyzer {
189    /// Configuration for similarity computation
190    config: SimilarityConfig,
191    /// Cache for computed similarities
192    similarity_cache: HashMap<(String, String), CircuitSimilarityMetrics>,
193    /// Cache for circuit embeddings
194    embedding_cache: HashMap<String, Vec<f64>>,
195    /// Pre-computed circuit features
196    feature_cache: HashMap<String, CircuitFeatures>,
197}
198
199/// Circuit features for similarity computation
200#[derive(Debug, Clone)]
201pub struct CircuitFeatures {
202    /// Gate type histogram
203    pub gate_histogram: HashMap<String, usize>,
204    /// Circuit depth
205    pub depth: usize,
206    /// Two-qubit gate count
207    pub two_qubit_gates: usize,
208    /// Connectivity pattern
209    pub connectivity_pattern: Vec<(usize, usize)>,
210    /// Critical path information
211    pub critical_path: Vec<String>,
212    /// Parallelism profile
213    pub parallelism_profile: Vec<usize>,
214    /// Entanglement structure
215    pub entanglement_structure: EntanglementStructure,
216}
217
218/// Entanglement structure representation
219#[derive(Debug, Clone)]
220pub struct EntanglementStructure {
221    /// Entangling gates by layer
222    pub entangling_layers: Vec<Vec<(usize, usize)>>,
223    /// Maximum entanglement width
224    pub max_entanglement_width: usize,
225    /// Entanglement graph
226    pub entanglement_graph: SciRS2Graph,
227}
228
229impl CircuitSimilarityAnalyzer {
230    /// Create a new circuit similarity analyzer
231    #[must_use]
232    pub fn new(config: SimilarityConfig) -> Self {
233        Self {
234            config,
235            similarity_cache: HashMap::new(),
236            embedding_cache: HashMap::new(),
237            feature_cache: HashMap::new(),
238        }
239    }
240
241    /// Create analyzer with default configuration
242    #[must_use]
243    pub fn with_default_config() -> Self {
244        Self::new(SimilarityConfig::default())
245    }
246
247    /// Compute comprehensive similarity between two circuits
248    pub fn compute_similarity<const N: usize, const M: usize>(
249        &mut self,
250        circuit1: &Circuit<N>,
251        circuit2: &Circuit<M>,
252    ) -> QuantRS2Result<CircuitSimilarityMetrics> {
253        // Generate unique identifiers for caching
254        let id1 = Self::generate_circuit_id(circuit1);
255        let id2 = Self::generate_circuit_id(circuit2);
256        let cache_key = if id1 < id2 { (id1, id2) } else { (id2, id1) };
257
258        // Check cache
259        if self.config.cache_results {
260            if let Some(cached) = self.similarity_cache.get(&cache_key) {
261                return Ok(cached.clone());
262            }
263        }
264
265        // Extract features
266        let features1 = self.extract_circuit_features(circuit1)?;
267        let features2 = self.extract_circuit_features(circuit2)?;
268
269        // Compute individual similarity measures
270        let mut detailed_metrics = HashMap::new();
271        let mut similarities = Vec::new();
272
273        let algorithms = self.config.algorithms.clone();
274        for algorithm in &algorithms {
275            let similarity = match algorithm {
276                SimilarityAlgorithm::GateLevel => {
277                    Self::compute_gate_level_similarity(&features1, &features2)?
278                }
279                SimilarityAlgorithm::DAGStructure => {
280                    Self::compute_dag_similarity(circuit1, circuit2)?
281                }
282                SimilarityAlgorithm::UnitaryMatrix => {
283                    Self::compute_unitary_similarity(circuit1, circuit2)?
284                }
285                SimilarityAlgorithm::GraphBased {
286                    algorithm: graph_alg,
287                } => Self::compute_graph_similarity(&features1, &features2, graph_alg)?,
288                SimilarityAlgorithm::Statistical => {
289                    Self::compute_statistical_similarity(&features1, &features2)?
290                }
291                SimilarityAlgorithm::MLEmbeddings { model_type } => {
292                    self.compute_ml_similarity(circuit1, circuit2, model_type)?
293                }
294            };
295
296            detailed_metrics.insert(format!("{algorithm:?}"), similarity);
297            similarities.push(similarity);
298        }
299
300        // Compute component similarities
301        let structural_similarity = Self::compute_structural_similarity(&features1, &features2)?;
302        let functional_similarity = Self::compute_functional_similarity(circuit1, circuit2)?;
303        let sequence_similarity = Self::compute_sequence_similarity(&features1, &features2)?;
304        let topological_similarity = Self::compute_topological_similarity(&features1, &features2)?;
305
306        // Compute overall similarity using weighted combination
307        let overall_similarity = self.config.weights.topological.mul_add(
308            topological_similarity,
309            self.config.weights.sequence.mul_add(
310                sequence_similarity,
311                self.config.weights.structural.mul_add(
312                    structural_similarity,
313                    self.config.weights.functional * functional_similarity,
314                ),
315            ),
316        );
317
318        let result = CircuitSimilarityMetrics {
319            structural_similarity,
320            functional_similarity,
321            sequence_similarity,
322            topological_similarity,
323            overall_similarity,
324            detailed_metrics,
325        };
326
327        // Cache result
328        if self.config.cache_results {
329            self.similarity_cache.insert(cache_key, result.clone());
330        }
331
332        Ok(result)
333    }
334
335    /// Compute distance metrics between circuits
336    pub fn compute_distance<const N: usize, const M: usize>(
337        &mut self,
338        circuit1: &Circuit<N>,
339        circuit2: &Circuit<M>,
340    ) -> QuantRS2Result<CircuitDistanceMetrics> {
341        let features1 = self.extract_circuit_features(circuit1)?;
342        let features2 = self.extract_circuit_features(circuit2)?;
343
344        // Compute edit distance
345        let edit_distance = Self::compute_edit_distance(&features1, &features2)?;
346        let max_gates = features1
347            .gate_histogram
348            .values()
349            .sum::<usize>()
350            .max(features2.gate_histogram.values().sum::<usize>());
351        let normalized_edit_distance = if max_gates > 0 {
352            edit_distance as f64 / max_gates as f64
353        } else {
354            0.0
355        };
356
357        // Compute other distance measures
358        let wasserstein_distance = Self::compute_wasserstein_distance(&features1, &features2)?;
359        let hausdorff_distance = Self::compute_hausdorff_distance(circuit1, circuit2)?;
360        let earth_movers_distance = Self::compute_earth_movers_distance(&features1, &features2)?;
361        let process_fidelity_distance =
362            Self::compute_process_fidelity_distance(circuit1, circuit2)?;
363
364        Ok(CircuitDistanceMetrics {
365            edit_distance,
366            normalized_edit_distance,
367            wasserstein_distance,
368            hausdorff_distance,
369            earth_movers_distance,
370            process_fidelity_distance,
371        })
372    }
373
374    /// Extract comprehensive features from a circuit
375    fn extract_circuit_features<const N: usize>(
376        &mut self,
377        circuit: &Circuit<N>,
378    ) -> QuantRS2Result<CircuitFeatures> {
379        let id = Self::generate_circuit_id(circuit);
380
381        if let Some(cached) = self.feature_cache.get(&id) {
382            return Ok(cached.clone());
383        }
384
385        let mut gate_histogram = HashMap::new();
386        let mut connectivity_pattern = Vec::new();
387        let mut critical_path = Vec::new();
388        let mut two_qubit_gates = 0;
389
390        // Analyze gates
391        for gate in circuit.gates() {
392            let gate_name = gate.name();
393            *gate_histogram.entry(gate_name.to_string()).or_insert(0) += 1;
394            critical_path.push(gate_name.to_string());
395
396            if gate.qubits().len() == 2 {
397                two_qubit_gates += 1;
398                let qubits: Vec<usize> = gate.qubits().iter().map(|q| q.id() as usize).collect();
399                connectivity_pattern.push((qubits[0], qubits[1]));
400            }
401        }
402
403        // Compute parallelism profile
404        let parallelism_profile = Self::compute_parallelism_profile(circuit)?;
405
406        // Analyze entanglement structure
407        let entanglement_structure = Self::analyze_entanglement_structure(circuit)?;
408
409        let features = CircuitFeatures {
410            gate_histogram,
411            depth: circuit.gates().len(), // Simplified depth
412            two_qubit_gates,
413            connectivity_pattern,
414            critical_path,
415            parallelism_profile,
416            entanglement_structure,
417        };
418
419        self.feature_cache.insert(id, features.clone());
420        Ok(features)
421    }
422
423    /// Compute gate-level similarity
424    fn compute_gate_level_similarity(
425        features1: &CircuitFeatures,
426        features2: &CircuitFeatures,
427    ) -> QuantRS2Result<f64> {
428        // Compare gate histograms using cosine similarity
429        let mut dot_product = 0.0;
430        let mut norm1 = 0.0;
431        let mut norm2 = 0.0;
432
433        let all_gates: HashSet<String> = features1
434            .gate_histogram
435            .keys()
436            .chain(features2.gate_histogram.keys())
437            .cloned()
438            .collect();
439
440        for gate in all_gates {
441            let count1 = *features1.gate_histogram.get(&gate).unwrap_or(&0) as f64;
442            let count2 = *features2.gate_histogram.get(&gate).unwrap_or(&0) as f64;
443
444            dot_product += count1 * count2;
445            norm1 += count1 * count1;
446            norm2 += count2 * count2;
447        }
448
449        let similarity = if norm1 > 0.0 && norm2 > 0.0 {
450            dot_product / (norm1.sqrt() * norm2.sqrt())
451        } else {
452            0.0
453        };
454
455        Ok(similarity)
456    }
457
458    /// Compute DAG structure similarity
459    fn compute_dag_similarity<const N: usize, const M: usize>(
460        circuit1: &Circuit<N>,
461        circuit2: &Circuit<M>,
462    ) -> QuantRS2Result<f64> {
463        // Convert circuits to DAGs and compare structure
464        let dag1 = circuit_to_dag(circuit1);
465        let dag2 = circuit_to_dag(circuit2);
466
467        // Compare DAG properties
468        let nodes_similarity = if dag1.nodes().len() == dag2.nodes().len() {
469            1.0
470        } else {
471            let min_nodes = dag1.nodes().len().min(dag2.nodes().len()) as f64;
472            let max_nodes = dag1.nodes().len().max(dag2.nodes().len()) as f64;
473            min_nodes / max_nodes
474        };
475
476        let edges_similarity = if dag1.edges().len() == dag2.edges().len() {
477            1.0
478        } else {
479            let min_edges = dag1.edges().len().min(dag2.edges().len()) as f64;
480            let max_edges = dag1.edges().len().max(dag2.edges().len()) as f64;
481            min_edges / max_edges
482        };
483
484        Ok(f64::midpoint(nodes_similarity, edges_similarity))
485    }
486
487    /// Compute unitary matrix similarity
488    const fn compute_unitary_similarity<const N: usize, const M: usize>(
489        _circuit1: &Circuit<N>,
490        _circuit2: &Circuit<M>,
491    ) -> QuantRS2Result<f64> {
492        if N != M {
493            // Circuits with different qubit counts have zero unitary similarity
494            return Ok(0.0);
495        }
496
497        // Convert circuits to unitary matrices and compute fidelity
498        // This is a simplified placeholder - would use actual matrix conversion
499        let fidelity = 0.9; // Placeholder for unitary similarity
500
501        Ok(fidelity)
502    }
503
504    /// Compute graph-based similarity
505    fn compute_graph_similarity(
506        features1: &CircuitFeatures,
507        features2: &CircuitFeatures,
508        algorithm: &GraphSimilarityAlgorithm,
509    ) -> QuantRS2Result<f64> {
510        match algorithm {
511            GraphSimilarityAlgorithm::GraphEditDistance => Self::compute_graph_edit_distance(
512                &features1.entanglement_structure.entanglement_graph,
513                &features2.entanglement_structure.entanglement_graph,
514            ),
515            GraphSimilarityAlgorithm::SpectralSimilarity => Self::compute_spectral_similarity(
516                &features1.entanglement_structure.entanglement_graph,
517                &features2.entanglement_structure.entanglement_graph,
518            ),
519            _ => {
520                // Other graph algorithms would be implemented
521                Ok(0.5) // Placeholder
522            }
523        }
524    }
525
526    /// Compute statistical similarity
527    fn compute_statistical_similarity(
528        features1: &CircuitFeatures,
529        features2: &CircuitFeatures,
530    ) -> QuantRS2Result<f64> {
531        // Compare statistical properties of circuits with division by zero protection
532        let max_depth = features1.depth.max(features2.depth);
533        let depth_similarity = if max_depth > 0 {
534            1.0 - (features1.depth as f64 - features2.depth as f64).abs() / (max_depth as f64)
535        } else {
536            1.0 // Both have zero depth - identical
537        };
538
539        let max_two_qubit = features1.two_qubit_gates.max(features2.two_qubit_gates);
540        let two_qubit_similarity = if max_two_qubit > 0 {
541            1.0 - (features1.two_qubit_gates as f64 - features2.two_qubit_gates as f64).abs()
542                / (max_two_qubit as f64)
543        } else {
544            1.0 // Both have zero two-qubit gates - identical
545        };
546
547        Ok(f64::midpoint(depth_similarity, two_qubit_similarity))
548    }
549
550    /// Compute ML-based similarity using embeddings
551    fn compute_ml_similarity<const N: usize, const M: usize>(
552        &mut self,
553        circuit1: &Circuit<N>,
554        circuit2: &Circuit<M>,
555        model_type: &MLModelType,
556    ) -> QuantRS2Result<f64> {
557        // Generate circuit embeddings using ML models
558        let embedding1 = self.generate_circuit_embedding(circuit1, model_type)?;
559        let embedding2 = self.generate_circuit_embedding(circuit2, model_type)?;
560
561        // Compute cosine similarity between embeddings
562        let similarity = Self::cosine_similarity(&embedding1, &embedding2);
563        Ok(similarity)
564    }
565
566    /// Generate circuit embedding using ML model
567    fn generate_circuit_embedding<const N: usize>(
568        &mut self,
569        circuit: &Circuit<N>,
570        model_type: &MLModelType,
571    ) -> QuantRS2Result<Vec<f64>> {
572        let id = format!("{}_{:?}", Self::generate_circuit_id(circuit), model_type);
573
574        if let Some(cached) = self.embedding_cache.get(&id) {
575            return Ok(cached.clone());
576        }
577
578        // Generate embedding based on model type
579        let embedding = match model_type {
580            MLModelType::VAE { latent_dim } => Self::generate_vae_embedding(circuit, *latent_dim)?,
581            MLModelType::GCN { hidden_dims } => Self::generate_gcn_embedding(circuit, hidden_dims)?,
582            MLModelType::Transformer {
583                num_heads,
584                num_layers,
585            } => Self::generate_transformer_embedding(circuit, *num_heads, *num_layers)?,
586            MLModelType::PreTrained { model_name } => {
587                Self::generate_pretrained_embedding(circuit, model_name)?
588            }
589        };
590
591        self.embedding_cache.insert(id, embedding.clone());
592        Ok(embedding)
593    }
594
595    /// Generate VAE embedding (placeholder)
596    fn generate_vae_embedding<const N: usize>(
597        _circuit: &Circuit<N>,
598        latent_dim: usize,
599    ) -> QuantRS2Result<Vec<f64>> {
600        // Placeholder for VAE-based circuit embedding
601        Ok(vec![0.5; latent_dim])
602    }
603
604    /// Generate GCN embedding (placeholder)
605    fn generate_gcn_embedding<const N: usize>(
606        _circuit: &Circuit<N>,
607        hidden_dims: &[usize],
608    ) -> QuantRS2Result<Vec<f64>> {
609        // Placeholder for GCN-based circuit embedding
610        let output_dim = hidden_dims.last().unwrap_or(&64);
611        Ok(vec![0.5; *output_dim])
612    }
613
614    /// Generate Transformer embedding (placeholder)
615    fn generate_transformer_embedding<const N: usize>(
616        _circuit: &Circuit<N>,
617        num_heads: usize,
618        _num_layers: usize,
619    ) -> QuantRS2Result<Vec<f64>> {
620        // Placeholder for Transformer-based circuit embedding
621        let embedding_dim = num_heads * 64; // Typical dimension
622        Ok(vec![0.5; embedding_dim])
623    }
624
625    /// Generate pre-trained model embedding (placeholder)
626    fn generate_pretrained_embedding<const N: usize>(
627        _circuit: &Circuit<N>,
628        model_name: &str,
629    ) -> QuantRS2Result<Vec<f64>> {
630        // Placeholder for pre-trained model embedding
631        let embedding_dim = match model_name {
632            "circuit_bert" => 768,
633            "quantum_gpt" => 512,
634            _ => 256,
635        };
636        Ok(vec![0.5; embedding_dim])
637    }
638
639    /// Compute structural similarity
640    fn compute_structural_similarity(
641        features1: &CircuitFeatures,
642        features2: &CircuitFeatures,
643    ) -> QuantRS2Result<f64> {
644        // Compare circuit structure
645        let connectivity_similarity = Self::compare_connectivity_patterns(
646            &features1.connectivity_pattern,
647            &features2.connectivity_pattern,
648        );
649
650        // Handle depth comparison with division by zero protection
651        let max_depth = features1.depth.max(features2.depth);
652        let depth_similarity = if max_depth > 0 {
653            1.0 - (features1.depth as f64 - features2.depth as f64).abs() / (max_depth as f64)
654        } else {
655            // Both circuits have zero depth - they are identical in this metric
656            1.0
657        };
658
659        Ok(f64::midpoint(connectivity_similarity, depth_similarity))
660    }
661
662    /// Compute functional similarity
663    const fn compute_functional_similarity<const N: usize, const M: usize>(
664        _circuit1: &Circuit<N>,
665        _circuit2: &Circuit<M>,
666    ) -> QuantRS2Result<f64> {
667        if N != M {
668            return Ok(0.0);
669        }
670
671        // Compare functional behavior (simplified)
672        // In practice, this would compute unitary similarity or process fidelity
673        Ok(0.8) // Placeholder
674    }
675
676    /// Compute sequence similarity
677    fn compute_sequence_similarity(
678        features1: &CircuitFeatures,
679        features2: &CircuitFeatures,
680    ) -> QuantRS2Result<f64> {
681        // Compare gate sequences using edit distance
682        let edit_distance =
683            Self::string_edit_distance(&features1.critical_path, &features2.critical_path);
684        let max_length = features1
685            .critical_path
686            .len()
687            .max(features2.critical_path.len());
688
689        let similarity = if max_length > 0 {
690            1.0 - (edit_distance as f64 / max_length as f64)
691        } else {
692            1.0
693        };
694
695        Ok(similarity)
696    }
697
698    /// Compute topological similarity
699    fn compute_topological_similarity(
700        features1: &CircuitFeatures,
701        features2: &CircuitFeatures,
702    ) -> QuantRS2Result<f64> {
703        // Compare entanglement topology with division by zero protection
704        let max_width = features1
705            .entanglement_structure
706            .max_entanglement_width
707            .max(features2.entanglement_structure.max_entanglement_width);
708
709        let width_similarity = if max_width > 0 {
710            1.0 - (features1.entanglement_structure.max_entanglement_width as f64
711                - features2.entanglement_structure.max_entanglement_width as f64)
712                .abs()
713                / (max_width as f64)
714        } else {
715            1.0 // Both have zero entanglement width - identical
716        };
717
718        Ok(width_similarity)
719    }
720
721    /// Helper methods
722
723    /// Generate unique circuit identifier
724    fn generate_circuit_id<const N: usize>(circuit: &Circuit<N>) -> String {
725        use std::collections::hash_map::DefaultHasher;
726        use std::hash::{Hash, Hasher};
727
728        let mut hasher = DefaultHasher::new();
729        N.hash(&mut hasher);
730
731        for gate in circuit.gates() {
732            gate.name().hash(&mut hasher);
733            for qubit in gate.qubits() {
734                qubit.id().hash(&mut hasher);
735            }
736        }
737
738        format!("{:x}", hasher.finish())
739    }
740
741    /// Compute parallelism profile
742    fn compute_parallelism_profile<const N: usize>(
743        circuit: &Circuit<N>,
744    ) -> QuantRS2Result<Vec<usize>> {
745        // Simplified parallelism analysis
746        let mut profile = Vec::new();
747        let gate_count = circuit.gates().len();
748
749        // For now, assume linear execution (no parallelism)
750        for _ in 0..gate_count {
751            profile.push(1);
752        }
753
754        Ok(profile)
755    }
756
757    /// Analyze entanglement structure
758    fn analyze_entanglement_structure<const N: usize>(
759        circuit: &Circuit<N>,
760    ) -> QuantRS2Result<EntanglementStructure> {
761        let mut entangling_layers = Vec::new();
762        let mut current_layer = Vec::new();
763        let mut max_width = 0;
764
765        for gate in circuit.gates() {
766            if gate.qubits().len() == 2 {
767                let qubits: Vec<usize> = gate.qubits().iter().map(|q| q.id() as usize).collect();
768                current_layer.push((qubits[0], qubits[1]));
769                max_width = max_width.max(current_layer.len());
770            } else if !current_layer.is_empty() {
771                entangling_layers.push(current_layer);
772                current_layer = Vec::new();
773            }
774        }
775
776        if !current_layer.is_empty() {
777            entangling_layers.push(current_layer);
778        }
779
780        // Create entanglement graph
781        let mut graph = SciRS2Graph {
782            nodes: (0..N).collect(),
783            edges: Vec::new(),
784            node_attributes: HashMap::new(),
785            edge_attributes: HashMap::new(),
786        };
787
788        for layer in &entangling_layers {
789            for &(q1, q2) in layer {
790                graph.edges.push((q1, q2, 1.0));
791            }
792        }
793
794        Ok(EntanglementStructure {
795            entangling_layers,
796            max_entanglement_width: max_width,
797            entanglement_graph: graph,
798        })
799    }
800
801    /// Compare connectivity patterns
802    fn compare_connectivity_patterns(
803        pattern1: &[(usize, usize)],
804        pattern2: &[(usize, usize)],
805    ) -> f64 {
806        let set1: HashSet<_> = pattern1.iter().collect();
807        let set2: HashSet<_> = pattern2.iter().collect();
808
809        let intersection = set1.intersection(&set2).count();
810        let union = set1.union(&set2).count();
811
812        if union > 0 {
813            intersection as f64 / union as f64
814        } else {
815            1.0
816        }
817    }
818
819    /// Compute edit distance between strings
820    fn string_edit_distance(seq1: &[String], seq2: &[String]) -> usize {
821        let m = seq1.len();
822        let n = seq2.len();
823        let mut dp = vec![vec![0; n + 1]; m + 1];
824
825        // Initialize base cases
826        for i in 0..=m {
827            dp[i][0] = i;
828        }
829        for j in 0..=n {
830            dp[0][j] = j;
831        }
832
833        // Fill DP table
834        for i in 1..=m {
835            for j in 1..=n {
836                if seq1[i - 1] == seq2[j - 1] {
837                    dp[i][j] = dp[i - 1][j - 1];
838                } else {
839                    dp[i][j] = 1 + dp[i - 1][j].min(dp[i][j - 1]).min(dp[i - 1][j - 1]);
840                }
841            }
842        }
843
844        dp[m][n]
845    }
846
847    /// Compute cosine similarity between vectors
848    fn cosine_similarity(vec1: &[f64], vec2: &[f64]) -> f64 {
849        if vec1.len() != vec2.len() {
850            return 0.0;
851        }
852
853        let dot_product: f64 = vec1.iter().zip(vec2.iter()).map(|(a, b)| a * b).sum();
854        let norm1: f64 = vec1.iter().map(|x| x * x).sum::<f64>().sqrt();
855        let norm2: f64 = vec2.iter().map(|x| x * x).sum::<f64>().sqrt();
856
857        if norm1 > 0.0 && norm2 > 0.0 {
858            dot_product / (norm1 * norm2)
859        } else {
860            0.0
861        }
862    }
863
864    /// Compute edit distance between circuit features
865    fn compute_edit_distance(
866        features1: &CircuitFeatures,
867        features2: &CircuitFeatures,
868    ) -> QuantRS2Result<usize> {
869        // Simplified edit distance based on gate operations
870        let distance =
871            Self::string_edit_distance(&features1.critical_path, &features2.critical_path);
872        Ok(distance)
873    }
874
875    /// Compute Wasserstein distance
876    const fn compute_wasserstein_distance(
877        _features1: &CircuitFeatures,
878        _features2: &CircuitFeatures,
879    ) -> QuantRS2Result<f64> {
880        // Simplified Wasserstein distance computation
881        // In practice, would use SciRS2's optimal transport algorithms
882        Ok(0.3) // Placeholder
883    }
884
885    /// Compute Hausdorff distance
886    const fn compute_hausdorff_distance<const N: usize, const M: usize>(
887        _circuit1: &Circuit<N>,
888        _circuit2: &Circuit<M>,
889    ) -> QuantRS2Result<f64> {
890        // Placeholder for Hausdorff distance computation
891        Ok(0.25) // Placeholder
892    }
893
894    /// Compute Earth Mover's distance
895    const fn compute_earth_movers_distance(
896        _features1: &CircuitFeatures,
897        _features2: &CircuitFeatures,
898    ) -> QuantRS2Result<f64> {
899        // Placeholder for Earth Mover's distance computation
900        Ok(0.2) // Placeholder
901    }
902
903    /// Compute process fidelity distance
904    const fn compute_process_fidelity_distance<const N: usize, const M: usize>(
905        _circuit1: &Circuit<N>,
906        _circuit2: &Circuit<M>,
907    ) -> QuantRS2Result<f64> {
908        if N != M {
909            return Ok(1.0); // Maximum distance for different dimensions
910        }
911
912        // Placeholder for process fidelity computation
913        Ok(0.1) // Placeholder
914    }
915
916    /// Compute graph edit distance
917    fn compute_graph_edit_distance(
918        graph1: &SciRS2Graph,
919        graph2: &SciRS2Graph,
920    ) -> QuantRS2Result<f64> {
921        // Simplified graph edit distance
922        let node_diff = (graph1.nodes.len() as f64 - graph2.nodes.len() as f64).abs();
923        let edge_diff = (graph1.edges.len() as f64 - graph2.edges.len() as f64).abs();
924        let max_size = (graph1.nodes.len() + graph1.edges.len())
925            .max(graph2.nodes.len() + graph2.edges.len()) as f64;
926
927        let distance = if max_size > 0.0 {
928            (node_diff + edge_diff) / max_size
929        } else {
930            0.0 // Both graphs are empty - identical
931        };
932
933        Ok(1.0 - distance) // Convert to similarity
934    }
935
936    /// Compute spectral similarity
937    const fn compute_spectral_similarity(
938        _graph1: &SciRS2Graph,
939        _graph2: &SciRS2Graph,
940    ) -> QuantRS2Result<f64> {
941        // Placeholder for spectral similarity computation
942        // Would compute eigenvalues of graph Laplacians and compare
943        Ok(0.7) // Placeholder
944    }
945}
946
947/// Batch similarity computation for multiple circuits
948pub struct BatchSimilarityComputer {
949    analyzer: CircuitSimilarityAnalyzer,
950}
951
952impl BatchSimilarityComputer {
953    /// Create new batch computer
954    #[must_use]
955    pub fn new(config: SimilarityConfig) -> Self {
956        Self {
957            analyzer: CircuitSimilarityAnalyzer::new(config),
958        }
959    }
960
961    /// Compute pairwise similarities for a set of circuits
962    pub fn compute_pairwise_similarities<const N: usize>(
963        &mut self,
964        circuits: &[Circuit<N>],
965    ) -> QuantRS2Result<Vec<Vec<f64>>> {
966        let n_circuits = circuits.len();
967        let mut similarity_matrix = vec![vec![0.0; n_circuits]; n_circuits];
968
969        for i in 0..n_circuits {
970            similarity_matrix[i][i] = 1.0; // Self-similarity
971
972            for j in (i + 1)..n_circuits {
973                let similarity = self
974                    .analyzer
975                    .compute_similarity(&circuits[i], &circuits[j])?;
976                similarity_matrix[i][j] = similarity.overall_similarity;
977                similarity_matrix[j][i] = similarity.overall_similarity; // Symmetric
978            }
979        }
980
981        Ok(similarity_matrix)
982    }
983
984    /// Find most similar circuits in a dataset
985    pub fn find_most_similar<const N: usize>(
986        &mut self,
987        query_circuit: &Circuit<N>,
988        dataset: &[Circuit<N>],
989        top_k: usize,
990    ) -> QuantRS2Result<Vec<(usize, f64)>> {
991        let mut similarities = Vec::new();
992
993        for (i, circuit) in dataset.iter().enumerate() {
994            let similarity = self.analyzer.compute_similarity(query_circuit, circuit)?;
995            similarities.push((i, similarity.overall_similarity));
996        }
997
998        // Sort by similarity and return top-k
999        similarities.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Equal));
1000        similarities.truncate(top_k);
1001
1002        Ok(similarities)
1003    }
1004}
1005
1006#[cfg(test)]
1007mod tests {
1008    use super::*;
1009    use quantrs2_core::gate::multi::CNOT;
1010    use quantrs2_core::gate::single::Hadamard;
1011
1012    #[test]
1013    fn test_similarity_analyzer_creation() {
1014        let analyzer = CircuitSimilarityAnalyzer::with_default_config();
1015        assert_eq!(analyzer.config.algorithms.len(), 3);
1016    }
1017
1018    #[test]
1019    fn test_identical_circuits_similarity() {
1020        let mut analyzer = CircuitSimilarityAnalyzer::with_default_config();
1021
1022        let mut circuit = Circuit::<2>::new();
1023        circuit
1024            .add_gate(Hadamard { target: QubitId(0) })
1025            .expect("Failed to add Hadamard gate");
1026
1027        let similarity = analyzer
1028            .compute_similarity(&circuit, &circuit)
1029            .expect("Failed to compute similarity for identical circuits");
1030
1031        // Fixed: Overall similarity should be 1.0 for identical circuits
1032        // All component similarities should also be 1.0 or very close to it
1033        assert!(
1034            !similarity.overall_similarity.is_nan(),
1035            "Similarity should not be NaN for identical circuits. Actual value: {}",
1036            similarity.overall_similarity
1037        );
1038        assert!(
1039            !similarity.overall_similarity.is_infinite(),
1040            "Similarity should not be infinite for identical circuits. Actual value: {}",
1041            similarity.overall_similarity
1042        );
1043        assert!(
1044            similarity.structural_similarity >= 0.9,
1045            "Structural similarity should be high for identical circuits: {}",
1046            similarity.structural_similarity
1047        );
1048        assert!(
1049            similarity.sequence_similarity >= 0.9,
1050            "Sequence similarity should be high for identical circuits: {}",
1051            similarity.sequence_similarity
1052        );
1053        assert!(
1054            similarity.topological_similarity >= 0.9,
1055            "Topological similarity should be high for identical circuits: {}",
1056            similarity.topological_similarity
1057        );
1058        assert!(
1059            similarity.overall_similarity >= 0.8,
1060            "Overall similarity should be high for identical circuits: {}",
1061            similarity.overall_similarity
1062        );
1063    }
1064
1065    #[test]
1066    fn test_different_circuits_similarity() {
1067        let mut analyzer = CircuitSimilarityAnalyzer::with_default_config();
1068
1069        let mut circuit1 = Circuit::<2>::new();
1070        circuit1
1071            .add_gate(Hadamard { target: QubitId(0) })
1072            .expect("Failed to add Hadamard gate to circuit1");
1073
1074        let mut circuit2 = Circuit::<2>::new();
1075        circuit2
1076            .add_gate(CNOT {
1077                control: QubitId(0),
1078                target: QubitId(1),
1079            })
1080            .expect("Failed to add CNOT gate to circuit2");
1081
1082        let similarity = analyzer
1083            .compute_similarity(&circuit1, &circuit2)
1084            .expect("Failed to compute similarity for different circuits");
1085        assert!(similarity.overall_similarity < 1.0);
1086    }
1087
1088    #[test]
1089    fn test_distance_computation() {
1090        let mut analyzer = CircuitSimilarityAnalyzer::with_default_config();
1091
1092        let mut circuit1 = Circuit::<2>::new();
1093        circuit1
1094            .add_gate(Hadamard { target: QubitId(0) })
1095            .expect("Failed to add Hadamard gate to circuit1");
1096
1097        let mut circuit2 = Circuit::<2>::new();
1098        circuit2
1099            .add_gate(CNOT {
1100                control: QubitId(0),
1101                target: QubitId(1),
1102            })
1103            .expect("Failed to add CNOT gate to circuit2");
1104
1105        let distance = analyzer
1106            .compute_distance(&circuit1, &circuit2)
1107            .expect("Failed to compute distance between circuits");
1108        assert!(distance.edit_distance > 0);
1109        assert!(
1110            distance.normalized_edit_distance >= 0.0 && distance.normalized_edit_distance <= 1.0
1111        );
1112    }
1113
1114    #[test]
1115    fn test_feature_extraction() {
1116        let mut analyzer = CircuitSimilarityAnalyzer::with_default_config();
1117
1118        let mut circuit = Circuit::<2>::new();
1119        circuit
1120            .add_gate(Hadamard { target: QubitId(0) })
1121            .expect("Failed to add Hadamard gate");
1122        circuit
1123            .add_gate(CNOT {
1124                control: QubitId(0),
1125                target: QubitId(1),
1126            })
1127            .expect("Failed to add CNOT gate");
1128
1129        let features = analyzer
1130            .extract_circuit_features(&circuit)
1131            .expect("Failed to extract circuit features");
1132        assert_eq!(features.gate_histogram.get("H"), Some(&1));
1133        assert_eq!(features.gate_histogram.get("CNOT"), Some(&1));
1134        assert_eq!(features.two_qubit_gates, 1);
1135    }
1136
1137    #[test]
1138    fn test_batch_similarity_computation() {
1139        let mut computer = BatchSimilarityComputer::new(SimilarityConfig::default());
1140
1141        let mut circuit1 = Circuit::<2>::new();
1142        circuit1
1143            .add_gate(Hadamard { target: QubitId(0) })
1144            .expect("Failed to add Hadamard gate to circuit1");
1145
1146        let mut circuit2 = Circuit::<2>::new();
1147        circuit2
1148            .add_gate(CNOT {
1149                control: QubitId(0),
1150                target: QubitId(1),
1151            })
1152            .expect("Failed to add CNOT gate to circuit2");
1153
1154        let circuits = vec![circuit1, circuit2];
1155        let similarity_matrix = computer
1156            .compute_pairwise_similarities(&circuits)
1157            .expect("Failed to compute pairwise similarities");
1158
1159        assert_eq!(similarity_matrix.len(), 2);
1160        assert_eq!(similarity_matrix[0].len(), 2);
1161        assert_eq!(similarity_matrix[0][0], 1.0); // Self-similarity
1162        assert_eq!(similarity_matrix[1][1], 1.0); // Self-similarity
1163        assert_eq!(similarity_matrix[0][1], similarity_matrix[1][0]); // Symmetry
1164    }
1165}