1use 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#[derive(Debug, Clone)]
24pub struct SciRS2Graph {
25 pub nodes: Vec<usize>,
27 pub edges: Vec<(usize, usize, f64)>,
29 pub node_attributes: HashMap<usize, HashMap<String, String>>,
31 pub edge_attributes: HashMap<(usize, usize), HashMap<String, f64>>,
33}
34
35#[derive(Debug, Clone, PartialEq, Eq)]
37pub enum GraphSimilarityAlgorithm {
38 GraphEditDistance,
40 SpectralSimilarity,
42 GraphKernel { kernel_type: GraphKernelType },
44 NetworkAlignment,
46 SubgraphIsomorphism,
48 GraphNeuralNetwork { embedding_dim: usize },
50}
51
52#[derive(Debug, Clone, PartialEq, Eq)]
54pub enum GraphKernelType {
55 RandomWalk { steps: usize },
57 WeisfeilerLehman { iterations: usize },
59 ShortestPath,
61 Graphlet { size: usize },
63}
64
65#[derive(Debug, Clone)]
67pub struct CircuitSimilarityMetrics {
68 pub structural_similarity: f64,
70 pub functional_similarity: f64,
72 pub sequence_similarity: f64,
74 pub topological_similarity: f64,
76 pub overall_similarity: f64,
78 pub detailed_metrics: HashMap<String, f64>,
80}
81
82#[derive(Debug, Clone)]
84pub struct CircuitDistanceMetrics {
85 pub edit_distance: usize,
87 pub normalized_edit_distance: f64,
89 pub wasserstein_distance: f64,
91 pub hausdorff_distance: f64,
93 pub earth_movers_distance: f64,
95 pub process_fidelity_distance: f64,
97}
98
99#[derive(Debug, Clone)]
101pub struct SimilarityConfig {
102 pub algorithms: Vec<SimilarityAlgorithm>,
104 pub weights: SimilarityWeights,
106 pub tolerance: f64,
108 pub normalize: bool,
110 pub cache_results: bool,
112 pub parallel: bool,
114}
115
116#[derive(Debug, Clone, PartialEq, Eq)]
118pub enum SimilarityAlgorithm {
119 GateLevel,
121 DAGStructure,
123 UnitaryMatrix,
125 GraphBased { algorithm: GraphSimilarityAlgorithm },
127 Statistical,
129 MLEmbeddings { model_type: MLModelType },
131}
132
133#[derive(Debug, Clone, PartialEq, Eq)]
135pub enum MLModelType {
136 VAE { latent_dim: usize },
138 GCN { hidden_dims: Vec<usize> },
140 Transformer { num_heads: usize, num_layers: usize },
142 PreTrained { model_name: String },
144}
145
146#[derive(Debug, Clone)]
148pub struct SimilarityWeights {
149 pub structural: f64,
151 pub functional: f64,
153 pub sequence: f64,
155 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
187pub struct CircuitSimilarityAnalyzer {
189 config: SimilarityConfig,
191 similarity_cache: HashMap<(String, String), CircuitSimilarityMetrics>,
193 embedding_cache: HashMap<String, Vec<f64>>,
195 feature_cache: HashMap<String, CircuitFeatures>,
197}
198
199#[derive(Debug, Clone)]
201pub struct CircuitFeatures {
202 pub gate_histogram: HashMap<String, usize>,
204 pub depth: usize,
206 pub two_qubit_gates: usize,
208 pub connectivity_pattern: Vec<(usize, usize)>,
210 pub critical_path: Vec<String>,
212 pub parallelism_profile: Vec<usize>,
214 pub entanglement_structure: EntanglementStructure,
216}
217
218#[derive(Debug, Clone)]
220pub struct EntanglementStructure {
221 pub entangling_layers: Vec<Vec<(usize, usize)>>,
223 pub max_entanglement_width: usize,
225 pub entanglement_graph: SciRS2Graph,
227}
228
229impl CircuitSimilarityAnalyzer {
230 #[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 #[must_use]
243 pub fn with_default_config() -> Self {
244 Self::new(SimilarityConfig::default())
245 }
246
247 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 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 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 let features1 = self.extract_circuit_features(circuit1)?;
267 let features2 = self.extract_circuit_features(circuit2)?;
268
269 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 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 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 if self.config.cache_results {
329 self.similarity_cache.insert(cache_key, result.clone());
330 }
331
332 Ok(result)
333 }
334
335 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 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 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 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 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 let parallelism_profile = Self::compute_parallelism_profile(circuit)?;
405
406 let entanglement_structure = Self::analyze_entanglement_structure(circuit)?;
408
409 let features = CircuitFeatures {
410 gate_histogram,
411 depth: circuit.gates().len(), 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 fn compute_gate_level_similarity(
425 features1: &CircuitFeatures,
426 features2: &CircuitFeatures,
427 ) -> QuantRS2Result<f64> {
428 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 fn compute_dag_similarity<const N: usize, const M: usize>(
460 circuit1: &Circuit<N>,
461 circuit2: &Circuit<M>,
462 ) -> QuantRS2Result<f64> {
463 let dag1 = circuit_to_dag(circuit1);
465 let dag2 = circuit_to_dag(circuit2);
466
467 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 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 return Ok(0.0);
495 }
496
497 let fidelity = 0.9; Ok(fidelity)
502 }
503
504 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 Ok(0.5) }
523 }
524 }
525
526 fn compute_statistical_similarity(
528 features1: &CircuitFeatures,
529 features2: &CircuitFeatures,
530 ) -> QuantRS2Result<f64> {
531 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 };
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 };
546
547 Ok(f64::midpoint(depth_similarity, two_qubit_similarity))
548 }
549
550 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 let embedding1 = self.generate_circuit_embedding(circuit1, model_type)?;
559 let embedding2 = self.generate_circuit_embedding(circuit2, model_type)?;
560
561 let similarity = Self::cosine_similarity(&embedding1, &embedding2);
563 Ok(similarity)
564 }
565
566 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 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 fn generate_vae_embedding<const N: usize>(
597 _circuit: &Circuit<N>,
598 latent_dim: usize,
599 ) -> QuantRS2Result<Vec<f64>> {
600 Ok(vec![0.5; latent_dim])
602 }
603
604 fn generate_gcn_embedding<const N: usize>(
606 _circuit: &Circuit<N>,
607 hidden_dims: &[usize],
608 ) -> QuantRS2Result<Vec<f64>> {
609 let output_dim = hidden_dims.last().unwrap_or(&64);
611 Ok(vec![0.5; *output_dim])
612 }
613
614 fn generate_transformer_embedding<const N: usize>(
616 _circuit: &Circuit<N>,
617 num_heads: usize,
618 _num_layers: usize,
619 ) -> QuantRS2Result<Vec<f64>> {
620 let embedding_dim = num_heads * 64; Ok(vec![0.5; embedding_dim])
623 }
624
625 fn generate_pretrained_embedding<const N: usize>(
627 _circuit: &Circuit<N>,
628 model_name: &str,
629 ) -> QuantRS2Result<Vec<f64>> {
630 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 fn compute_structural_similarity(
641 features1: &CircuitFeatures,
642 features2: &CircuitFeatures,
643 ) -> QuantRS2Result<f64> {
644 let connectivity_similarity = Self::compare_connectivity_patterns(
646 &features1.connectivity_pattern,
647 &features2.connectivity_pattern,
648 );
649
650 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 1.0
657 };
658
659 Ok(f64::midpoint(connectivity_similarity, depth_similarity))
660 }
661
662 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 Ok(0.8) }
675
676 fn compute_sequence_similarity(
678 features1: &CircuitFeatures,
679 features2: &CircuitFeatures,
680 ) -> QuantRS2Result<f64> {
681 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 fn compute_topological_similarity(
700 features1: &CircuitFeatures,
701 features2: &CircuitFeatures,
702 ) -> QuantRS2Result<f64> {
703 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 };
717
718 Ok(width_similarity)
719 }
720
721 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 fn compute_parallelism_profile<const N: usize>(
743 circuit: &Circuit<N>,
744 ) -> QuantRS2Result<Vec<usize>> {
745 let mut profile = Vec::new();
747 let gate_count = circuit.gates().len();
748
749 for _ in 0..gate_count {
751 profile.push(1);
752 }
753
754 Ok(profile)
755 }
756
757 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 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 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 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 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 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 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 fn compute_edit_distance(
866 features1: &CircuitFeatures,
867 features2: &CircuitFeatures,
868 ) -> QuantRS2Result<usize> {
869 let distance =
871 Self::string_edit_distance(&features1.critical_path, &features2.critical_path);
872 Ok(distance)
873 }
874
875 const fn compute_wasserstein_distance(
877 _features1: &CircuitFeatures,
878 _features2: &CircuitFeatures,
879 ) -> QuantRS2Result<f64> {
880 Ok(0.3) }
884
885 const fn compute_hausdorff_distance<const N: usize, const M: usize>(
887 _circuit1: &Circuit<N>,
888 _circuit2: &Circuit<M>,
889 ) -> QuantRS2Result<f64> {
890 Ok(0.25) }
893
894 const fn compute_earth_movers_distance(
896 _features1: &CircuitFeatures,
897 _features2: &CircuitFeatures,
898 ) -> QuantRS2Result<f64> {
899 Ok(0.2) }
902
903 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); }
911
912 Ok(0.1) }
915
916 fn compute_graph_edit_distance(
918 graph1: &SciRS2Graph,
919 graph2: &SciRS2Graph,
920 ) -> QuantRS2Result<f64> {
921 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 };
932
933 Ok(1.0 - distance) }
935
936 const fn compute_spectral_similarity(
938 _graph1: &SciRS2Graph,
939 _graph2: &SciRS2Graph,
940 ) -> QuantRS2Result<f64> {
941 Ok(0.7) }
945}
946
947pub struct BatchSimilarityComputer {
949 analyzer: CircuitSimilarityAnalyzer,
950}
951
952impl BatchSimilarityComputer {
953 #[must_use]
955 pub fn new(config: SimilarityConfig) -> Self {
956 Self {
957 analyzer: CircuitSimilarityAnalyzer::new(config),
958 }
959 }
960
961 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; 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; }
979 }
980
981 Ok(similarity_matrix)
982 }
983
984 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 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 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); assert_eq!(similarity_matrix[1][1], 1.0); assert_eq!(similarity_matrix[0][1], similarity_matrix[1][0]); }
1165}