1use crate::builder::Circuit;
7use crate::dag::{circuit_to_dag, CircuitDag};
8use quantrs2_core::{
9 error::{QuantRS2Error, QuantRS2Result},
10 gate::GateOp,
11 qubit::QubitId,
12};
13use scirs2_core::Complex64;
14use serde::{Deserialize, Serialize};
15use std::collections::{HashMap, HashSet};
16use std::f64::consts::PI;
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 fn compute_unitary_similarity<const N: usize, const M: usize>(
496 circuit1: &Circuit<N>,
497 circuit2: &Circuit<M>,
498 ) -> QuantRS2Result<f64> {
499 if N != M {
500 return Ok(0.0);
502 }
503
504 let dimension = 1usize << N;
505 let mut inner_product = Complex64::new(0.0, 0.0);
510 for basis_state in 0..dimension {
511 let column1 = Self::simulate_circuit_from_basis_state(circuit1, basis_state)?;
512 let column2 = Self::simulate_circuit_from_basis_state(circuit2, basis_state)?;
513 inner_product += column1
514 .iter()
515 .zip(column2.iter())
516 .map(|(a, b)| a.conj() * b)
517 .sum::<Complex64>();
518 }
519
520 let d = dimension as f64;
521 Ok((inner_product.norm_sqr() / (d * d)).clamp(0.0, 1.0))
522 }
523
524 fn apply_gate_to_state(
532 state: &[Complex64],
533 gate: &dyn GateOp,
534 num_qubits: usize,
535 ) -> QuantRS2Result<Vec<Complex64>> {
536 let qubits = gate.qubits();
537 let touched: Vec<usize> = qubits.iter().map(|q| q.id() as usize).collect();
538 let k = touched.len();
539 let local_dim = 1usize << k;
540 let matrix = gate.matrix()?;
541 if matrix.len() != local_dim * local_dim {
542 return Err(QuantRS2Error::InvalidInput(format!(
543 "gate {} matrix has {} entries, expected {} for a {}-qubit gate",
544 gate.name(),
545 matrix.len(),
546 local_dim * local_dim,
547 k
548 )));
549 }
550
551 let dimension = state.len();
552 let mut new_state = vec![Complex64::new(0.0, 0.0); dimension];
553
554 let other: Vec<usize> = (0..num_qubits).filter(|q| !touched.contains(q)).collect();
555 let other_count = other.len();
556 let other_dim = 1usize << other_count;
557
558 let compose_index = |other_bits: usize, local_bits: usize| -> usize {
562 let mut idx = 0usize;
563 for (position, &qubit) in other.iter().enumerate() {
564 let bit = (other_bits >> (other_count - 1 - position)) & 1;
565 idx |= bit << (num_qubits - 1 - qubit);
566 }
567 for (position, &qubit) in touched.iter().enumerate() {
568 let bit = (local_bits >> (k - 1 - position)) & 1;
569 idx |= bit << (num_qubits - 1 - qubit);
570 }
571 idx
572 };
573
574 for other_bits in 0..other_dim {
575 for local_in in 0..local_dim {
576 let index_in = compose_index(other_bits, local_in);
577 let amplitude_in = state[index_in];
578 if amplitude_in == Complex64::new(0.0, 0.0) {
579 continue;
580 }
581 for local_out in 0..local_dim {
582 let index_out = compose_index(other_bits, local_out);
583 new_state[index_out] += matrix[local_out * local_dim + local_in] * amplitude_in;
584 }
585 }
586 }
587
588 Ok(new_state)
589 }
590
591 fn simulate_circuit_from_basis_state<const N: usize>(
597 circuit: &Circuit<N>,
598 initial_basis_state: usize,
599 ) -> QuantRS2Result<Vec<Complex64>> {
600 let dimension = 1usize << N;
601 let mut state = vec![Complex64::new(0.0, 0.0); dimension];
602 state[initial_basis_state.min(dimension - 1)] = Complex64::new(1.0, 0.0);
603
604 for gate in circuit.gates() {
605 state = Self::apply_gate_to_state(&state, gate.as_ref(), N)?;
606 }
607
608 Ok(state)
609 }
610
611 fn compute_graph_similarity(
613 features1: &CircuitFeatures,
614 features2: &CircuitFeatures,
615 algorithm: &GraphSimilarityAlgorithm,
616 ) -> QuantRS2Result<f64> {
617 match algorithm {
618 GraphSimilarityAlgorithm::GraphEditDistance => Self::compute_graph_edit_distance(
619 &features1.entanglement_structure.entanglement_graph,
620 &features2.entanglement_structure.entanglement_graph,
621 ),
622 GraphSimilarityAlgorithm::SpectralSimilarity => Self::compute_spectral_similarity(
623 &features1.entanglement_structure.entanglement_graph,
624 &features2.entanglement_structure.entanglement_graph,
625 ),
626 _ => {
627 Ok(0.5) }
630 }
631 }
632
633 fn compute_statistical_similarity(
635 features1: &CircuitFeatures,
636 features2: &CircuitFeatures,
637 ) -> QuantRS2Result<f64> {
638 let max_depth = features1.depth.max(features2.depth);
640 let depth_similarity = if max_depth > 0 {
641 1.0 - (features1.depth as f64 - features2.depth as f64).abs() / (max_depth as f64)
642 } else {
643 1.0 };
645
646 let max_two_qubit = features1.two_qubit_gates.max(features2.two_qubit_gates);
647 let two_qubit_similarity = if max_two_qubit > 0 {
648 1.0 - (features1.two_qubit_gates as f64 - features2.two_qubit_gates as f64).abs()
649 / (max_two_qubit as f64)
650 } else {
651 1.0 };
653
654 Ok(f64::midpoint(depth_similarity, two_qubit_similarity))
655 }
656
657 fn compute_ml_similarity<const N: usize, const M: usize>(
659 &mut self,
660 circuit1: &Circuit<N>,
661 circuit2: &Circuit<M>,
662 model_type: &MLModelType,
663 ) -> QuantRS2Result<f64> {
664 let embedding1 = self.generate_circuit_embedding(circuit1, model_type)?;
666 let embedding2 = self.generate_circuit_embedding(circuit2, model_type)?;
667
668 let similarity = Self::cosine_similarity(&embedding1, &embedding2);
670 Ok(similarity)
671 }
672
673 fn generate_circuit_embedding<const N: usize>(
675 &mut self,
676 circuit: &Circuit<N>,
677 model_type: &MLModelType,
678 ) -> QuantRS2Result<Vec<f64>> {
679 let id = format!("{}_{:?}", Self::generate_circuit_id(circuit), model_type);
680
681 if let Some(cached) = self.embedding_cache.get(&id) {
682 return Ok(cached.clone());
683 }
684
685 let embedding = match model_type {
694 MLModelType::VAE { latent_dim } => self.generate_vae_embedding(circuit, *latent_dim)?,
695 MLModelType::GCN { hidden_dims } => {
696 self.generate_gcn_embedding(circuit, hidden_dims)?
697 }
698 MLModelType::Transformer {
699 num_heads,
700 num_layers,
701 } => self.generate_transformer_embedding(circuit, *num_heads, *num_layers)?,
702 MLModelType::PreTrained { model_name } => {
703 self.generate_pretrained_embedding(circuit, model_name)?
704 }
705 };
706
707 self.embedding_cache.insert(id, embedding.clone());
708 Ok(embedding)
709 }
710
711 fn circuit_feature_vector(features: &CircuitFeatures) -> Vec<f64> {
715 const CANONICAL_GATES: [&str; 12] = [
716 "H", "X", "Y", "Z", "S", "T", "RX", "RY", "RZ", "CNOT", "CZ", "SWAP",
717 ];
718
719 let mut raw = vec![
720 features.depth as f64,
721 features.two_qubit_gates as f64,
722 features.entanglement_structure.max_entanglement_width as f64,
723 features.entanglement_structure.entangling_layers.len() as f64,
724 features.parallelism_profile.iter().sum::<usize>() as f64,
725 features
726 .parallelism_profile
727 .iter()
728 .copied()
729 .max()
730 .unwrap_or(0) as f64,
731 features.connectivity_pattern.len() as f64,
732 ];
733 for gate_name in CANONICAL_GATES {
734 raw.push(*features.gate_histogram.get(gate_name).unwrap_or(&0) as f64);
735 }
736 raw
737 }
738
739 fn project_feature_vector(raw_features: &[f64], output_dim: usize) -> Vec<f64> {
746 if output_dim == 0 {
747 return Vec::new();
748 }
749 let normalization = raw_features.len().max(1) as f64;
750 let basis_size = output_dim as f64 + 1.0;
751
752 let mut embedding = vec![0.0_f64; output_dim];
753 for (k, slot) in embedding.iter_mut().enumerate() {
754 let mut accumulator = 0.0_f64;
755 for (i, &value) in raw_features.iter().enumerate() {
756 let phase = 2.0 * PI * ((i + 1) as f64) * ((k + 1) as f64) / basis_size;
757 accumulator += value * phase.cos();
758 }
759 *slot = accumulator / normalization;
760 }
761
762 let norm: f64 = embedding.iter().map(|v| v * v).sum::<f64>().sqrt();
763 if norm > 0.0 {
764 for value in &mut embedding {
765 *value /= norm;
766 }
767 }
768 embedding
769 }
770
771 fn generate_vae_embedding<const N: usize>(
775 &mut self,
776 circuit: &Circuit<N>,
777 latent_dim: usize,
778 ) -> QuantRS2Result<Vec<f64>> {
779 let features = self.extract_circuit_features(circuit)?;
780 let raw = Self::circuit_feature_vector(&features);
781 Ok(Self::project_feature_vector(&raw, latent_dim))
782 }
783
784 fn generate_gcn_embedding<const N: usize>(
786 &mut self,
787 circuit: &Circuit<N>,
788 hidden_dims: &[usize],
789 ) -> QuantRS2Result<Vec<f64>> {
790 let output_dim = *hidden_dims.last().unwrap_or(&64);
791 let features = self.extract_circuit_features(circuit)?;
792 let raw = Self::circuit_feature_vector(&features);
793 Ok(Self::project_feature_vector(&raw, output_dim))
794 }
795
796 fn generate_transformer_embedding<const N: usize>(
798 &mut self,
799 circuit: &Circuit<N>,
800 num_heads: usize,
801 _num_layers: usize,
802 ) -> QuantRS2Result<Vec<f64>> {
803 let embedding_dim = num_heads * 64; let features = self.extract_circuit_features(circuit)?;
805 let raw = Self::circuit_feature_vector(&features);
806 Ok(Self::project_feature_vector(&raw, embedding_dim))
807 }
808
809 fn generate_pretrained_embedding<const N: usize>(
811 &mut self,
812 circuit: &Circuit<N>,
813 model_name: &str,
814 ) -> QuantRS2Result<Vec<f64>> {
815 let embedding_dim = match model_name {
816 "circuit_bert" => 768,
817 "quantum_gpt" => 512,
818 _ => 256,
819 };
820 let features = self.extract_circuit_features(circuit)?;
821 let raw = Self::circuit_feature_vector(&features);
822 Ok(Self::project_feature_vector(&raw, embedding_dim))
823 }
824
825 fn compute_structural_similarity(
827 features1: &CircuitFeatures,
828 features2: &CircuitFeatures,
829 ) -> QuantRS2Result<f64> {
830 let connectivity_similarity = Self::compare_connectivity_patterns(
832 &features1.connectivity_pattern,
833 &features2.connectivity_pattern,
834 );
835
836 let max_depth = features1.depth.max(features2.depth);
838 let depth_similarity = if max_depth > 0 {
839 1.0 - (features1.depth as f64 - features2.depth as f64).abs() / (max_depth as f64)
840 } else {
841 1.0
843 };
844
845 Ok(f64::midpoint(connectivity_similarity, depth_similarity))
846 }
847
848 fn compute_functional_similarity<const N: usize, const M: usize>(
856 circuit1: &Circuit<N>,
857 circuit2: &Circuit<M>,
858 ) -> QuantRS2Result<f64> {
859 if N != M {
860 return Ok(0.0);
861 }
862
863 let state1 = Self::simulate_circuit_from_basis_state(circuit1, 0)?;
864 let state2 = Self::simulate_circuit_from_basis_state(circuit2, 0)?;
865
866 let overlap: Complex64 = state1
867 .iter()
868 .zip(state2.iter())
869 .map(|(a, b)| a.conj() * b)
870 .sum();
871
872 Ok(overlap.norm_sqr().clamp(0.0, 1.0))
873 }
874
875 fn compute_sequence_similarity(
877 features1: &CircuitFeatures,
878 features2: &CircuitFeatures,
879 ) -> QuantRS2Result<f64> {
880 let edit_distance =
882 Self::string_edit_distance(&features1.critical_path, &features2.critical_path);
883 let max_length = features1
884 .critical_path
885 .len()
886 .max(features2.critical_path.len());
887
888 let similarity = if max_length > 0 {
889 1.0 - (edit_distance as f64 / max_length as f64)
890 } else {
891 1.0
892 };
893
894 Ok(similarity)
895 }
896
897 fn compute_topological_similarity(
899 features1: &CircuitFeatures,
900 features2: &CircuitFeatures,
901 ) -> QuantRS2Result<f64> {
902 let max_width = features1
904 .entanglement_structure
905 .max_entanglement_width
906 .max(features2.entanglement_structure.max_entanglement_width);
907
908 let width_similarity = if max_width > 0 {
909 1.0 - (features1.entanglement_structure.max_entanglement_width as f64
910 - features2.entanglement_structure.max_entanglement_width as f64)
911 .abs()
912 / (max_width as f64)
913 } else {
914 1.0 };
916
917 Ok(width_similarity)
918 }
919
920 fn generate_circuit_id<const N: usize>(circuit: &Circuit<N>) -> String {
924 use std::collections::hash_map::DefaultHasher;
925 use std::hash::{Hash, Hasher};
926
927 let mut hasher = DefaultHasher::new();
928 N.hash(&mut hasher);
929
930 for gate in circuit.gates() {
931 gate.name().hash(&mut hasher);
932 for qubit in gate.qubits() {
933 qubit.id().hash(&mut hasher);
934 }
935 }
936
937 format!("{:x}", hasher.finish())
938 }
939
940 fn compute_parallelism_profile<const N: usize>(
950 circuit: &Circuit<N>,
951 ) -> QuantRS2Result<Vec<usize>> {
952 let mut qubit_next_layer = vec![0usize; N];
954 let mut layer_widths: Vec<usize> = Vec::new();
956
957 for gate in circuit.gates() {
958 let qubits = gate.qubits();
959
960 let layer = qubits
962 .iter()
963 .map(|q| qubit_next_layer[q.id() as usize])
964 .max()
965 .unwrap_or(0);
966
967 if layer >= layer_widths.len() {
968 layer_widths.resize(layer + 1, 0);
969 }
970 layer_widths[layer] += 1;
971
972 for qubit in qubits {
974 qubit_next_layer[qubit.id() as usize] = layer + 1;
975 }
976 }
977
978 Ok(layer_widths)
979 }
980
981 fn analyze_entanglement_structure<const N: usize>(
983 circuit: &Circuit<N>,
984 ) -> QuantRS2Result<EntanglementStructure> {
985 let mut entangling_layers = Vec::new();
986 let mut current_layer = Vec::new();
987 let mut max_width = 0;
988
989 for gate in circuit.gates() {
990 if gate.qubits().len() == 2 {
991 let qubits: Vec<usize> = gate.qubits().iter().map(|q| q.id() as usize).collect();
992 current_layer.push((qubits[0], qubits[1]));
993 max_width = max_width.max(current_layer.len());
994 } else if !current_layer.is_empty() {
995 entangling_layers.push(current_layer);
996 current_layer = Vec::new();
997 }
998 }
999
1000 if !current_layer.is_empty() {
1001 entangling_layers.push(current_layer);
1002 }
1003
1004 let mut graph = SciRS2Graph {
1006 nodes: (0..N).collect(),
1007 edges: Vec::new(),
1008 node_attributes: HashMap::new(),
1009 edge_attributes: HashMap::new(),
1010 };
1011
1012 for layer in &entangling_layers {
1013 for &(q1, q2) in layer {
1014 graph.edges.push((q1, q2, 1.0));
1015 }
1016 }
1017
1018 Ok(EntanglementStructure {
1019 entangling_layers,
1020 max_entanglement_width: max_width,
1021 entanglement_graph: graph,
1022 })
1023 }
1024
1025 fn compare_connectivity_patterns(
1027 pattern1: &[(usize, usize)],
1028 pattern2: &[(usize, usize)],
1029 ) -> f64 {
1030 let set1: HashSet<_> = pattern1.iter().collect();
1031 let set2: HashSet<_> = pattern2.iter().collect();
1032
1033 let intersection = set1.intersection(&set2).count();
1034 let union = set1.union(&set2).count();
1035
1036 if union > 0 {
1037 intersection as f64 / union as f64
1038 } else {
1039 1.0
1040 }
1041 }
1042
1043 fn string_edit_distance(seq1: &[String], seq2: &[String]) -> usize {
1045 let m = seq1.len();
1046 let n = seq2.len();
1047 let mut dp = vec![vec![0; n + 1]; m + 1];
1048
1049 for i in 0..=m {
1051 dp[i][0] = i;
1052 }
1053 for j in 0..=n {
1054 dp[0][j] = j;
1055 }
1056
1057 for i in 1..=m {
1059 for j in 1..=n {
1060 if seq1[i - 1] == seq2[j - 1] {
1061 dp[i][j] = dp[i - 1][j - 1];
1062 } else {
1063 dp[i][j] = 1 + dp[i - 1][j].min(dp[i][j - 1]).min(dp[i - 1][j - 1]);
1064 }
1065 }
1066 }
1067
1068 dp[m][n]
1069 }
1070
1071 fn cosine_similarity(vec1: &[f64], vec2: &[f64]) -> f64 {
1073 if vec1.len() != vec2.len() {
1074 return 0.0;
1075 }
1076
1077 let dot_product: f64 = vec1.iter().zip(vec2.iter()).map(|(a, b)| a * b).sum();
1078 let norm1: f64 = vec1.iter().map(|x| x * x).sum::<f64>().sqrt();
1079 let norm2: f64 = vec2.iter().map(|x| x * x).sum::<f64>().sqrt();
1080
1081 if norm1 > 0.0 && norm2 > 0.0 {
1082 dot_product / (norm1 * norm2)
1083 } else {
1084 0.0
1085 }
1086 }
1087
1088 fn compute_edit_distance(
1090 features1: &CircuitFeatures,
1091 features2: &CircuitFeatures,
1092 ) -> QuantRS2Result<usize> {
1093 let distance =
1095 Self::string_edit_distance(&features1.critical_path, &features2.critical_path);
1096 Ok(distance)
1097 }
1098
1099 const fn compute_wasserstein_distance(
1101 _features1: &CircuitFeatures,
1102 _features2: &CircuitFeatures,
1103 ) -> QuantRS2Result<f64> {
1104 Ok(0.3) }
1108
1109 const fn compute_hausdorff_distance<const N: usize, const M: usize>(
1111 _circuit1: &Circuit<N>,
1112 _circuit2: &Circuit<M>,
1113 ) -> QuantRS2Result<f64> {
1114 Ok(0.25) }
1117
1118 const fn compute_earth_movers_distance(
1120 _features1: &CircuitFeatures,
1121 _features2: &CircuitFeatures,
1122 ) -> QuantRS2Result<f64> {
1123 Ok(0.2) }
1126
1127 const fn compute_process_fidelity_distance<const N: usize, const M: usize>(
1129 _circuit1: &Circuit<N>,
1130 _circuit2: &Circuit<M>,
1131 ) -> QuantRS2Result<f64> {
1132 if N != M {
1133 return Ok(1.0); }
1135
1136 Ok(0.1) }
1139
1140 fn compute_graph_edit_distance(
1142 graph1: &SciRS2Graph,
1143 graph2: &SciRS2Graph,
1144 ) -> QuantRS2Result<f64> {
1145 let node_diff = (graph1.nodes.len() as f64 - graph2.nodes.len() as f64).abs();
1147 let edge_diff = (graph1.edges.len() as f64 - graph2.edges.len() as f64).abs();
1148 let max_size = (graph1.nodes.len() + graph1.edges.len())
1149 .max(graph2.nodes.len() + graph2.edges.len()) as f64;
1150
1151 let distance = if max_size > 0.0 {
1152 (node_diff + edge_diff) / max_size
1153 } else {
1154 0.0 };
1156
1157 Ok(1.0 - distance) }
1159
1160 const fn compute_spectral_similarity(
1162 _graph1: &SciRS2Graph,
1163 _graph2: &SciRS2Graph,
1164 ) -> QuantRS2Result<f64> {
1165 Ok(0.7) }
1169}
1170
1171pub struct BatchSimilarityComputer {
1173 analyzer: CircuitSimilarityAnalyzer,
1174}
1175
1176impl BatchSimilarityComputer {
1177 #[must_use]
1179 pub fn new(config: SimilarityConfig) -> Self {
1180 Self {
1181 analyzer: CircuitSimilarityAnalyzer::new(config),
1182 }
1183 }
1184
1185 pub fn compute_pairwise_similarities<const N: usize>(
1187 &mut self,
1188 circuits: &[Circuit<N>],
1189 ) -> QuantRS2Result<Vec<Vec<f64>>> {
1190 let n_circuits = circuits.len();
1191 let mut similarity_matrix = vec![vec![0.0; n_circuits]; n_circuits];
1192
1193 for i in 0..n_circuits {
1194 similarity_matrix[i][i] = 1.0; for j in (i + 1)..n_circuits {
1197 let similarity = self
1198 .analyzer
1199 .compute_similarity(&circuits[i], &circuits[j])?;
1200 similarity_matrix[i][j] = similarity.overall_similarity;
1201 similarity_matrix[j][i] = similarity.overall_similarity; }
1203 }
1204
1205 Ok(similarity_matrix)
1206 }
1207
1208 pub fn find_most_similar<const N: usize>(
1210 &mut self,
1211 query_circuit: &Circuit<N>,
1212 dataset: &[Circuit<N>],
1213 top_k: usize,
1214 ) -> QuantRS2Result<Vec<(usize, f64)>> {
1215 let mut similarities = Vec::new();
1216
1217 for (i, circuit) in dataset.iter().enumerate() {
1218 let similarity = self.analyzer.compute_similarity(query_circuit, circuit)?;
1219 similarities.push((i, similarity.overall_similarity));
1220 }
1221
1222 similarities.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Equal));
1224 similarities.truncate(top_k);
1225
1226 Ok(similarities)
1227 }
1228}
1229
1230#[cfg(test)]
1231mod tests {
1232 use super::*;
1233 use quantrs2_core::gate::multi::CNOT;
1234 use quantrs2_core::gate::single::Hadamard;
1235
1236 #[test]
1237 fn test_similarity_analyzer_creation() {
1238 let analyzer = CircuitSimilarityAnalyzer::with_default_config();
1239 assert_eq!(analyzer.config.algorithms.len(), 3);
1240 }
1241
1242 #[test]
1243 fn test_parallelism_profile_disjoint_qubits() {
1244 let mut circuit = Circuit::<2>::new();
1246 circuit
1247 .add_gate(Hadamard { target: QubitId(0) })
1248 .expect("add H on q0");
1249 circuit
1250 .add_gate(Hadamard { target: QubitId(1) })
1251 .expect("add H on q1");
1252
1253 let profile = CircuitSimilarityAnalyzer::compute_parallelism_profile(&circuit)
1254 .expect("profile computation");
1255 assert_eq!(
1256 profile,
1257 vec![2],
1258 "disjoint gates share one layer of width 2"
1259 );
1260 }
1261
1262 #[test]
1263 fn test_parallelism_profile_serial_chain() {
1264 let mut circuit = Circuit::<1>::new();
1266 circuit
1267 .add_gate(Hadamard { target: QubitId(0) })
1268 .expect("add H");
1269 circuit
1270 .add_gate(Hadamard { target: QubitId(0) })
1271 .expect("add H");
1272
1273 let profile = CircuitSimilarityAnalyzer::compute_parallelism_profile(&circuit)
1274 .expect("profile computation");
1275 assert_eq!(
1276 profile,
1277 vec![1, 1],
1278 "serial gates occupy consecutive layers"
1279 );
1280 }
1281
1282 #[test]
1283 fn test_identical_circuits_similarity() {
1284 let mut analyzer = CircuitSimilarityAnalyzer::with_default_config();
1285
1286 let mut circuit = Circuit::<2>::new();
1287 circuit
1288 .add_gate(Hadamard { target: QubitId(0) })
1289 .expect("Failed to add Hadamard gate");
1290
1291 let similarity = analyzer
1292 .compute_similarity(&circuit, &circuit)
1293 .expect("Failed to compute similarity for identical circuits");
1294
1295 assert!(
1298 !similarity.overall_similarity.is_nan(),
1299 "Similarity should not be NaN for identical circuits. Actual value: {}",
1300 similarity.overall_similarity
1301 );
1302 assert!(
1303 !similarity.overall_similarity.is_infinite(),
1304 "Similarity should not be infinite for identical circuits. Actual value: {}",
1305 similarity.overall_similarity
1306 );
1307 assert!(
1308 similarity.structural_similarity >= 0.9,
1309 "Structural similarity should be high for identical circuits: {}",
1310 similarity.structural_similarity
1311 );
1312 assert!(
1313 similarity.sequence_similarity >= 0.9,
1314 "Sequence similarity should be high for identical circuits: {}",
1315 similarity.sequence_similarity
1316 );
1317 assert!(
1318 similarity.topological_similarity >= 0.9,
1319 "Topological similarity should be high for identical circuits: {}",
1320 similarity.topological_similarity
1321 );
1322 assert!(
1323 similarity.overall_similarity >= 0.8,
1324 "Overall similarity should be high for identical circuits: {}",
1325 similarity.overall_similarity
1326 );
1327 }
1328
1329 #[test]
1330 fn test_different_circuits_similarity() {
1331 let mut analyzer = CircuitSimilarityAnalyzer::with_default_config();
1332
1333 let mut circuit1 = Circuit::<2>::new();
1334 circuit1
1335 .add_gate(Hadamard { target: QubitId(0) })
1336 .expect("Failed to add Hadamard gate to circuit1");
1337
1338 let mut circuit2 = Circuit::<2>::new();
1339 circuit2
1340 .add_gate(CNOT {
1341 control: QubitId(0),
1342 target: QubitId(1),
1343 })
1344 .expect("Failed to add CNOT gate to circuit2");
1345
1346 let similarity = analyzer
1347 .compute_similarity(&circuit1, &circuit2)
1348 .expect("Failed to compute similarity for different circuits");
1349 assert!(similarity.overall_similarity < 1.0);
1350 }
1351
1352 #[test]
1353 fn test_distance_computation() {
1354 let mut analyzer = CircuitSimilarityAnalyzer::with_default_config();
1355
1356 let mut circuit1 = Circuit::<2>::new();
1357 circuit1
1358 .add_gate(Hadamard { target: QubitId(0) })
1359 .expect("Failed to add Hadamard gate to circuit1");
1360
1361 let mut circuit2 = Circuit::<2>::new();
1362 circuit2
1363 .add_gate(CNOT {
1364 control: QubitId(0),
1365 target: QubitId(1),
1366 })
1367 .expect("Failed to add CNOT gate to circuit2");
1368
1369 let distance = analyzer
1370 .compute_distance(&circuit1, &circuit2)
1371 .expect("Failed to compute distance between circuits");
1372 assert!(distance.edit_distance > 0);
1373 assert!(
1374 distance.normalized_edit_distance >= 0.0 && distance.normalized_edit_distance <= 1.0
1375 );
1376 }
1377
1378 #[test]
1379 fn test_feature_extraction() {
1380 let mut analyzer = CircuitSimilarityAnalyzer::with_default_config();
1381
1382 let mut circuit = Circuit::<2>::new();
1383 circuit
1384 .add_gate(Hadamard { target: QubitId(0) })
1385 .expect("Failed to add Hadamard gate");
1386 circuit
1387 .add_gate(CNOT {
1388 control: QubitId(0),
1389 target: QubitId(1),
1390 })
1391 .expect("Failed to add CNOT gate");
1392
1393 let features = analyzer
1394 .extract_circuit_features(&circuit)
1395 .expect("Failed to extract circuit features");
1396 assert_eq!(features.gate_histogram.get("H"), Some(&1));
1397 assert_eq!(features.gate_histogram.get("CNOT"), Some(&1));
1398 assert_eq!(features.two_qubit_gates, 1);
1399 }
1400
1401 #[test]
1402 fn test_batch_similarity_computation() {
1403 let mut computer = BatchSimilarityComputer::new(SimilarityConfig::default());
1404
1405 let mut circuit1 = Circuit::<2>::new();
1406 circuit1
1407 .add_gate(Hadamard { target: QubitId(0) })
1408 .expect("Failed to add Hadamard gate to circuit1");
1409
1410 let mut circuit2 = Circuit::<2>::new();
1411 circuit2
1412 .add_gate(CNOT {
1413 control: QubitId(0),
1414 target: QubitId(1),
1415 })
1416 .expect("Failed to add CNOT gate to circuit2");
1417
1418 let circuits = vec![circuit1, circuit2];
1419 let similarity_matrix = computer
1420 .compute_pairwise_similarities(&circuits)
1421 .expect("Failed to compute pairwise similarities");
1422
1423 assert_eq!(similarity_matrix.len(), 2);
1424 assert_eq!(similarity_matrix[0].len(), 2);
1425 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]); }
1429
1430 #[test]
1437 fn test_functional_similarity_identical_circuit_is_one() {
1438 let mut circuit = Circuit::<2>::new();
1439 circuit.add_gate(Hadamard { target: QubitId(0) }).unwrap();
1440 circuit
1441 .add_gate(CNOT {
1442 control: QubitId(0),
1443 target: QubitId(1),
1444 })
1445 .unwrap();
1446
1447 let similarity =
1448 CircuitSimilarityAnalyzer::compute_functional_similarity(&circuit, &circuit)
1449 .expect("compute_functional_similarity should succeed");
1450 assert!(
1451 (similarity - 1.0).abs() < 1e-9,
1452 "identical circuits must have functional similarity 1.0, got {similarity}"
1453 );
1454 }
1455
1456 #[test]
1457 fn test_functional_similarity_orthogonal_outputs_near_zero() {
1458 let identity_circuit = Circuit::<2>::new();
1461 let mut x_circuit = Circuit::<2>::new();
1462 x_circuit
1463 .add_gate(quantrs2_core::gate::single::PauliX { target: QubitId(0) })
1464 .unwrap();
1465
1466 let similarity =
1467 CircuitSimilarityAnalyzer::compute_functional_similarity(&identity_circuit, &x_circuit)
1468 .expect("compute_functional_similarity should succeed");
1469 assert!(
1470 similarity < 1e-9,
1471 "orthogonal output states must have ~0 functional similarity, got {similarity}"
1472 );
1473 }
1474
1475 #[test]
1476 fn test_unitary_similarity_identical_circuit_is_one() {
1477 let mut circuit = Circuit::<2>::new();
1478 circuit.add_gate(Hadamard { target: QubitId(0) }).unwrap();
1479 circuit
1480 .add_gate(CNOT {
1481 control: QubitId(0),
1482 target: QubitId(1),
1483 })
1484 .unwrap();
1485
1486 let similarity = CircuitSimilarityAnalyzer::compute_unitary_similarity(&circuit, &circuit)
1487 .expect("compute_unitary_similarity should succeed");
1488 assert!(
1489 (similarity - 1.0).abs() < 1e-9,
1490 "identical circuits must have unitary (process fidelity) similarity 1.0, got {similarity}"
1491 );
1492 }
1493
1494 #[test]
1495 fn test_unitary_similarity_different_circuits_less_than_one() {
1496 let mut circuit1 = Circuit::<1>::new();
1497 circuit1.add_gate(Hadamard { target: QubitId(0) }).unwrap();
1498
1499 let mut circuit2 = Circuit::<1>::new();
1500 circuit2
1501 .add_gate(quantrs2_core::gate::single::PauliX { target: QubitId(0) })
1502 .unwrap();
1503
1504 let similarity =
1505 CircuitSimilarityAnalyzer::compute_unitary_similarity(&circuit1, &circuit2)
1506 .expect("compute_unitary_similarity should succeed");
1507 assert!(
1508 similarity < 1.0 - 1e-9,
1509 "H and X are different single-qubit unitaries; similarity must be < 1.0, got {similarity}"
1510 );
1511 assert!((0.0..=1.0).contains(&similarity));
1512 }
1513
1514 #[test]
1515 fn test_unitary_similarity_mismatched_qubit_counts_is_zero() {
1516 let circuit1 = Circuit::<1>::new();
1517 let circuit2 = Circuit::<2>::new();
1518 let similarity =
1519 CircuitSimilarityAnalyzer::compute_unitary_similarity(&circuit1, &circuit2)
1520 .expect("compute_unitary_similarity should succeed");
1521 assert_eq!(similarity, 0.0);
1522 }
1523
1524 #[test]
1530 fn test_ml_embeddings_depend_on_circuit_content() {
1531 let mut analyzer = CircuitSimilarityAnalyzer::with_default_config();
1532
1533 let mut circuit1 = Circuit::<2>::new();
1534 circuit1.add_gate(Hadamard { target: QubitId(0) }).unwrap();
1535
1536 let mut circuit2 = Circuit::<2>::new();
1537 circuit2
1538 .add_gate(CNOT {
1539 control: QubitId(0),
1540 target: QubitId(1),
1541 })
1542 .unwrap();
1543 circuit2
1544 .add_gate(CNOT {
1545 control: QubitId(1),
1546 target: QubitId(0),
1547 })
1548 .unwrap();
1549
1550 let model_type = MLModelType::VAE { latent_dim: 8 };
1551 let embedding1 = analyzer
1552 .generate_circuit_embedding(&circuit1, &model_type)
1553 .expect("embedding generation should succeed");
1554 let embedding2 = analyzer
1555 .generate_circuit_embedding(&circuit2, &model_type)
1556 .expect("embedding generation should succeed");
1557
1558 assert_eq!(embedding1.len(), 8);
1559 assert_eq!(embedding2.len(), 8);
1560 assert_ne!(
1561 embedding1, embedding2,
1562 "embeddings for structurally different circuits must differ, not be a constant vector"
1563 );
1564
1565 assert!(embedding1.iter().any(|&v| (v - 0.5).abs() > 1e-6));
1567 }
1568
1569 #[test]
1570 fn test_ml_similarity_identical_circuits_is_one() {
1571 let mut analyzer = CircuitSimilarityAnalyzer::with_default_config();
1572
1573 let mut circuit = Circuit::<2>::new();
1574 circuit.add_gate(Hadamard { target: QubitId(0) }).unwrap();
1575 circuit
1576 .add_gate(CNOT {
1577 control: QubitId(0),
1578 target: QubitId(1),
1579 })
1580 .unwrap();
1581
1582 let model_type = MLModelType::GCN {
1583 hidden_dims: vec![32, 16],
1584 };
1585 let similarity = analyzer
1586 .compute_ml_similarity(&circuit, &circuit, &model_type)
1587 .expect("compute_ml_similarity should succeed");
1588 assert!(
1589 (similarity - 1.0).abs() < 1e-9,
1590 "an embedding compared with itself must have cosine similarity 1.0, got {similarity}"
1591 );
1592 }
1593}