1use crate::builder::Circuit;
7use crate::dag::{circuit_to_dag, CircuitDag, DagNode};
8use quantrs2_core::{
9 error::{QuantRS2Error, QuantRS2Result},
10 gate::GateOp,
11 qubit::QubitId,
12};
13use serde::{Deserialize, Serialize};
14use std::collections::{BTreeMap, HashMap, HashSet, VecDeque};
15use std::sync::Arc;
16
17#[derive(Debug, Clone)]
19pub struct SciRS2CircuitGraph {
20 pub nodes: HashMap<usize, SciRS2Node>,
22 pub edges: HashMap<(usize, usize), SciRS2Edge>,
24 pub adjacency_matrix: Vec<Vec<bool>>,
26 pub node_properties: HashMap<usize, NodeProperties>,
28 pub metrics_cache: Option<GraphMetrics>,
30}
31
32#[derive(Debug, Clone)]
34pub struct SciRS2Node {
35 pub id: usize,
36 pub gate: Option<Box<dyn GateOp>>,
37 pub node_type: SciRS2NodeType,
38 pub weight: f64,
39 pub depth: usize,
40 pub clustering_coefficient: Option<f64>,
41 pub centrality_measures: CentralityMeasures,
42}
43
44#[derive(Debug, Clone, PartialEq)]
46pub enum SciRS2NodeType {
47 Input { qubit: u32 },
49 Output { qubit: u32 },
51 SingleQubitGate { gate_type: String, qubit: u32 },
53 TwoQubitGate {
55 gate_type: String,
56 qubits: (u32, u32),
57 },
58 MultiQubitGate { gate_type: String, qubits: Vec<u32> },
60 Measurement { qubit: u32 },
62 Barrier { qubits: Vec<u32> },
64}
65
66#[derive(Debug, Clone)]
68pub struct SciRS2Edge {
69 pub source: usize,
70 pub target: usize,
71 pub edge_type: EdgeType,
72 pub weight: f64,
73 pub flow_capacity: Option<f64>,
74 pub is_critical_path: bool,
75}
76
77#[derive(Debug, Clone, PartialEq)]
79pub enum EdgeType {
80 QubitDependency { qubit: u32, distance: usize },
82 ClassicalDependency,
84 Commutation { strength: f64 },
86 Entanglement { strength: f64 },
88 Temporal { delay: f64 },
90}
91
92#[derive(Debug, Clone, Default)]
94pub struct NodeProperties {
95 pub degree: usize,
97 pub in_degree: usize,
99 pub out_degree: usize,
101 pub eccentricity: Option<usize>,
103 pub clustering_coefficient: Option<f64>,
105 pub community: Option<usize>,
107 pub execution_cost: f64,
109 pub error_rate: f64,
111}
112
113#[derive(Debug, Clone, Default)]
115pub struct CentralityMeasures {
116 pub degree: f64,
118 pub betweenness: Option<f64>,
120 pub closeness: Option<f64>,
122 pub eigenvector: Option<f64>,
124 pub pagerank: Option<f64>,
126}
127
128#[derive(Debug, Clone, Serialize, Deserialize)]
130pub struct GraphMetrics {
131 pub num_nodes: usize,
133 pub num_edges: usize,
135 pub diameter: Option<usize>,
137 pub average_path_length: Option<f64>,
139 pub clustering_coefficient: f64,
141 pub density: f64,
143 pub connected_components: usize,
145 pub modularity: Option<f64>,
147 pub small_world_coefficient: Option<f64>,
149}
150
151pub struct SciRS2CircuitAnalyzer {
153 pub config: AnalyzerConfig,
155 analysis_cache: HashMap<String, AnalysisResult>,
157}
158
159#[derive(Debug, Clone, Serialize, Deserialize)]
161pub struct AnalyzerConfig {
162 pub enable_community_detection: bool,
164 pub enable_centrality: bool,
166 pub enable_path_analysis: bool,
168 pub enable_motif_detection: bool,
170 pub max_path_length: usize,
172 pub clustering_resolution: f64,
174}
175
176impl Default for AnalyzerConfig {
177 fn default() -> Self {
178 Self {
179 enable_community_detection: true,
180 enable_centrality: true,
181 enable_path_analysis: true,
182 enable_motif_detection: true,
183 max_path_length: 10,
184 clustering_resolution: 1.0,
185 }
186 }
187}
188
189#[derive(Debug, Clone)]
191pub struct AnalysisResult {
192 pub metrics: GraphMetrics,
194 pub critical_paths: Vec<Vec<usize>>,
196 pub communities: Vec<Vec<usize>>,
198 pub motifs: Vec<GraphMotif>,
200 pub optimization_suggestions: Vec<OptimizationSuggestion>,
202 pub timestamp: std::time::SystemTime,
204}
205
206#[derive(Debug, Clone)]
208pub struct GraphMotif {
209 pub motif_type: MotifType,
211 pub nodes: Vec<usize>,
213 pub frequency: usize,
215 pub p_value: Option<f64>,
217}
218
219#[derive(Debug, Clone, PartialEq)]
221pub enum MotifType {
222 SingleQubitChain,
224 CnotLadder,
226 BellPairPreparation,
228 QftPattern,
230 GroverDiffusion,
232 Custom { name: String, pattern: String },
234}
235
236#[derive(Debug, Clone)]
238pub struct OptimizationSuggestion {
239 pub suggestion_type: SuggestionType,
241 pub nodes: Vec<usize>,
243 pub expected_improvement: f64,
245 pub confidence: f64,
247 pub description: String,
249}
250
251#[derive(Debug, Clone, PartialEq)]
253pub enum SuggestionType {
254 GateReordering,
256 Parallelization,
258 CriticalPathOptimization,
260 TemplateMatching,
262 RedundancyElimination,
264}
265
266impl SciRS2CircuitAnalyzer {
267 pub fn new() -> Self {
269 Self {
270 config: AnalyzerConfig::default(),
271 analysis_cache: HashMap::new(),
272 }
273 }
274
275 pub fn with_config(config: AnalyzerConfig) -> Self {
277 Self {
278 config,
279 analysis_cache: HashMap::new(),
280 }
281 }
282
283 pub fn circuit_to_scirs2_graph<const N: usize>(
285 &self,
286 circuit: &Circuit<N>,
287 ) -> QuantRS2Result<SciRS2CircuitGraph> {
288 let dag = circuit_to_dag(circuit);
289 let mut graph = SciRS2CircuitGraph {
290 nodes: HashMap::new(),
291 edges: HashMap::new(),
292 adjacency_matrix: Vec::new(),
293 node_properties: HashMap::new(),
294 metrics_cache: None,
295 };
296
297 for dag_node in dag.nodes() {
299 let node_type = self.classify_node_type(dag_node)?;
300 let sci_node = SciRS2Node {
301 id: dag_node.id,
302 gate: Some(dag_node.gate.clone()),
303 node_type,
304 weight: 1.0, depth: dag_node.depth,
306 clustering_coefficient: None,
307 centrality_measures: CentralityMeasures::default(),
308 };
309 graph.nodes.insert(dag_node.id, sci_node);
310 }
311
312 for dag_edge in dag.edges() {
314 let edge_type = self.classify_edge_type(dag_edge, &dag)?;
315 let sci_edge = SciRS2Edge {
316 source: dag_edge.source,
317 target: dag_edge.target,
318 edge_type,
319 weight: 1.0,
320 flow_capacity: Some(1.0),
321 is_critical_path: false,
322 };
323 graph
324 .edges
325 .insert((dag_edge.source, dag_edge.target), sci_edge);
326 }
327
328 self.build_adjacency_matrix(&mut graph);
330
331 self.calculate_node_properties(&mut graph)?;
333
334 Ok(graph)
335 }
336
337 fn classify_node_type(&self, node: &DagNode) -> QuantRS2Result<SciRS2NodeType> {
339 let gate = node.gate.as_ref();
340 let qubits = gate.qubits();
341 let gate_name = gate.name();
342
343 match qubits.len() {
344 0 => Ok(SciRS2NodeType::Barrier { qubits: Vec::new() }),
345 1 => Ok(SciRS2NodeType::SingleQubitGate {
346 gate_type: gate_name.to_string(),
347 qubit: qubits[0].id(),
348 }),
349 2 => Ok(SciRS2NodeType::TwoQubitGate {
350 gate_type: gate_name.to_string(),
351 qubits: (qubits[0].id(), qubits[1].id()),
352 }),
353 _ => Ok(SciRS2NodeType::MultiQubitGate {
354 gate_type: gate_name.to_string(),
355 qubits: qubits.iter().map(|q| q.id()).collect(),
356 }),
357 }
358 }
359
360 fn classify_edge_type(
362 &self,
363 edge: &crate::dag::DagEdge,
364 dag: &CircuitDag,
365 ) -> QuantRS2Result<EdgeType> {
366 match edge.edge_type {
367 crate::dag::EdgeType::QubitDependency(qubit) => {
368 let distance = self.calculate_node_distance(edge.source, edge.target, dag);
370 Ok(EdgeType::QubitDependency { qubit, distance })
371 }
372 crate::dag::EdgeType::ClassicalDependency => Ok(EdgeType::ClassicalDependency),
373 crate::dag::EdgeType::BarrierDependency => Ok(EdgeType::Temporal { delay: 0.0 }),
374 }
375 }
376
377 fn calculate_node_distance(&self, source: usize, target: usize, dag: &CircuitDag) -> usize {
379 if let (Some(source_node), Some(target_node)) =
381 (dag.nodes().get(source), dag.nodes().get(target))
382 {
383 target_node.depth.saturating_sub(source_node.depth)
384 } else {
385 0
386 }
387 }
388
389 fn build_adjacency_matrix(&self, graph: &mut SciRS2CircuitGraph) {
391 let n = graph.nodes.len();
392 let mut matrix = vec![vec![false; n]; n];
393
394 for &(source, target) in graph.edges.keys() {
395 if source < n && target < n {
396 matrix[source][target] = true;
397 }
398 }
399
400 graph.adjacency_matrix = matrix;
401 }
402
403 fn calculate_node_properties(&self, graph: &mut SciRS2CircuitGraph) -> QuantRS2Result<()> {
405 for &node_id in graph.nodes.keys() {
406 let mut properties = NodeProperties::default();
407
408 properties.degree = self.calculate_degree(node_id, graph);
410 properties.in_degree = self.calculate_in_degree(node_id, graph);
411 properties.out_degree = self.calculate_out_degree(node_id, graph);
412
413 if self.config.enable_centrality {
415 properties.clustering_coefficient =
416 self.calculate_clustering_coefficient(node_id, graph);
417 }
418
419 graph.node_properties.insert(node_id, properties);
420 }
421
422 Ok(())
423 }
424
425 fn calculate_degree(&self, node_id: usize, graph: &SciRS2CircuitGraph) -> usize {
427 graph
428 .edges
429 .keys()
430 .filter(|&&(s, t)| s == node_id || t == node_id)
431 .count()
432 }
433
434 fn calculate_in_degree(&self, node_id: usize, graph: &SciRS2CircuitGraph) -> usize {
436 graph.edges.keys().filter(|&&(_, t)| t == node_id).count()
437 }
438
439 fn calculate_out_degree(&self, node_id: usize, graph: &SciRS2CircuitGraph) -> usize {
441 graph.edges.keys().filter(|&&(s, _)| s == node_id).count()
442 }
443
444 fn calculate_clustering_coefficient(
446 &self,
447 node_id: usize,
448 graph: &SciRS2CircuitGraph,
449 ) -> Option<f64> {
450 let neighbors = self.get_neighbors(node_id, graph);
451 let k = neighbors.len();
452
453 if k < 2 {
454 return Some(0.0);
455 }
456
457 let mut connections = 0;
458 for i in 0..neighbors.len() {
459 for j in (i + 1)..neighbors.len() {
460 if graph.edges.contains_key(&(neighbors[i], neighbors[j]))
461 || graph.edges.contains_key(&(neighbors[j], neighbors[i]))
462 {
463 connections += 1;
464 }
465 }
466 }
467
468 let possible_connections = k * (k - 1) / 2;
469 Some(connections as f64 / possible_connections as f64)
470 }
471
472 fn get_neighbors(&self, node_id: usize, graph: &SciRS2CircuitGraph) -> Vec<usize> {
474 let mut neighbors = HashSet::new();
475
476 for &(source, target) in graph.edges.keys() {
477 if source == node_id {
478 neighbors.insert(target);
479 } else if target == node_id {
480 neighbors.insert(source);
481 }
482 }
483
484 neighbors.into_iter().collect()
485 }
486
487 pub fn analyze_circuit<const N: usize>(
489 &mut self,
490 circuit: &Circuit<N>,
491 ) -> QuantRS2Result<AnalysisResult> {
492 let graph = self.circuit_to_scirs2_graph(circuit)?;
493
494 let metrics = self.calculate_graph_metrics(&graph)?;
496
497 let critical_paths = if self.config.enable_path_analysis {
499 self.find_critical_paths(&graph)?
500 } else {
501 Vec::new()
502 };
503
504 let communities = if self.config.enable_community_detection {
506 self.detect_communities(&graph)?
507 } else {
508 Vec::new()
509 };
510
511 let motifs = if self.config.enable_motif_detection {
513 self.detect_motifs(&graph)?
514 } else {
515 Vec::new()
516 };
517
518 let optimization_suggestions =
520 self.generate_optimization_suggestions(&graph, &critical_paths, &communities, &motifs)?;
521
522 Ok(AnalysisResult {
523 metrics,
524 critical_paths,
525 communities,
526 motifs,
527 optimization_suggestions,
528 timestamp: std::time::SystemTime::now(),
529 })
530 }
531
532 fn calculate_graph_metrics(&self, graph: &SciRS2CircuitGraph) -> QuantRS2Result<GraphMetrics> {
534 let num_nodes = graph.nodes.len();
535 let num_edges = graph.edges.len();
536
537 let max_edges = num_nodes * (num_nodes - 1) / 2;
539 let density = if max_edges > 0 {
540 num_edges as f64 / max_edges as f64
541 } else {
542 0.0
543 };
544
545 let clustering_coefficient = self.calculate_average_clustering(&graph);
547
548 Ok(GraphMetrics {
549 num_nodes,
550 num_edges,
551 diameter: self.calculate_diameter(&graph),
552 average_path_length: self.calculate_average_path_length(&graph),
553 clustering_coefficient,
554 density,
555 connected_components: self.count_connected_components(&graph),
556 modularity: None, small_world_coefficient: None,
558 })
559 }
560
561 fn calculate_average_clustering(&self, graph: &SciRS2CircuitGraph) -> f64 {
563 let mut total = 0.0;
564 let mut count = 0;
565
566 for &node_id in graph.nodes.keys() {
567 if let Some(cc) = self.calculate_clustering_coefficient(node_id, graph) {
568 total += cc;
569 count += 1;
570 }
571 }
572
573 if count > 0 {
574 total / count as f64
575 } else {
576 0.0
577 }
578 }
579
580 fn calculate_diameter(&self, graph: &SciRS2CircuitGraph) -> Option<usize> {
582 let distances = self.all_pairs_shortest_paths(graph);
583 distances
584 .values()
585 .flat_map(|row| row.values())
586 .filter(|&&dist| dist != usize::MAX)
587 .max()
588 .copied()
589 }
590
591 fn calculate_average_path_length(&self, graph: &SciRS2CircuitGraph) -> Option<f64> {
593 let distances = self.all_pairs_shortest_paths(graph);
594 let mut total = 0.0;
595 let mut count = 0;
596
597 for row in distances.values() {
598 for &dist in row.values() {
599 if dist < usize::MAX {
600 total += dist as f64;
601 count += 1;
602 }
603 }
604 }
605
606 if count > 0 {
607 Some(total / count as f64)
608 } else {
609 None
610 }
611 }
612
613 fn all_pairs_shortest_paths(
615 &self,
616 graph: &SciRS2CircuitGraph,
617 ) -> HashMap<usize, HashMap<usize, usize>> {
618 let nodes: Vec<_> = graph.nodes.keys().cloned().collect();
619 let mut distances = HashMap::new();
620
621 for &i in &nodes {
623 let mut row = HashMap::new();
624 for &j in &nodes {
625 if i == j {
626 row.insert(j, 0);
627 } else if graph.edges.contains_key(&(i, j)) {
628 row.insert(j, 1);
629 } else {
630 row.insert(j, usize::MAX);
631 }
632 }
633 distances.insert(i, row);
634 }
635
636 for &k in &nodes {
638 for &i in &nodes {
639 for &j in &nodes {
640 if let (Some(ik), Some(kj)) = (
641 distances.get(&i).and_then(|row| row.get(&k)),
642 distances.get(&k).and_then(|row| row.get(&j)),
643 ) {
644 if *ik != usize::MAX && *kj != usize::MAX {
645 let new_dist = ik + kj;
646 if let Some(ij) = distances.get_mut(&i).and_then(|row| row.get_mut(&j))
647 {
648 if new_dist < *ij {
649 *ij = new_dist;
650 }
651 }
652 }
653 }
654 }
655 }
656 }
657
658 distances
659 }
660
661 fn count_connected_components(&self, graph: &SciRS2CircuitGraph) -> usize {
663 let mut visited = HashSet::new();
664 let mut components = 0;
665
666 for &node_id in graph.nodes.keys() {
667 if !visited.contains(&node_id) {
668 self.dfs_component(node_id, graph, &mut visited);
669 components += 1;
670 }
671 }
672
673 components
674 }
675
676 fn dfs_component(
678 &self,
679 start: usize,
680 graph: &SciRS2CircuitGraph,
681 visited: &mut HashSet<usize>,
682 ) {
683 let mut stack = vec![start];
684
685 while let Some(node) = stack.pop() {
686 if visited.insert(node) {
687 for neighbor in self.get_neighbors(node, graph) {
688 if !visited.contains(&neighbor) {
689 stack.push(neighbor);
690 }
691 }
692 }
693 }
694 }
695
696 fn find_critical_paths(&self, graph: &SciRS2CircuitGraph) -> QuantRS2Result<Vec<Vec<usize>>> {
698 let max_depth = graph
700 .nodes
701 .values()
702 .map(|node| node.depth)
703 .max()
704 .unwrap_or(0);
705
706 let critical_nodes: Vec<_> = graph
707 .nodes
708 .values()
709 .filter(|node| node.depth == max_depth)
710 .map(|node| node.id)
711 .collect();
712
713 Ok(vec![critical_nodes])
714 }
715
716 fn detect_communities(&self, graph: &SciRS2CircuitGraph) -> QuantRS2Result<Vec<Vec<usize>>> {
718 let mut communities = BTreeMap::new();
720
721 for node in graph.nodes.values() {
722 communities
723 .entry(node.depth)
724 .or_insert_with(Vec::new)
725 .push(node.id);
726 }
727
728 Ok(communities.into_values().collect())
729 }
730
731 fn detect_motifs(&self, graph: &SciRS2CircuitGraph) -> QuantRS2Result<Vec<GraphMotif>> {
733 let mut motifs = Vec::new();
734
735 let chains = self.detect_single_qubit_chains(graph);
737 motifs.extend(chains);
738
739 let cnot_patterns = self.detect_cnot_patterns(graph);
741 motifs.extend(cnot_patterns);
742
743 Ok(motifs)
744 }
745
746 fn detect_single_qubit_chains(&self, graph: &SciRS2CircuitGraph) -> Vec<GraphMotif> {
748 let mut motifs = Vec::new();
749 let mut visited = HashSet::new();
750
751 for node in graph.nodes.values() {
752 if visited.contains(&node.id) {
753 continue;
754 }
755
756 if let SciRS2NodeType::SingleQubitGate { .. } = node.node_type {
757 let chain = self.trace_single_qubit_chain(node.id, graph, &mut visited);
758 if chain.len() > 2 {
759 motifs.push(GraphMotif {
760 motif_type: MotifType::SingleQubitChain,
761 nodes: chain,
762 frequency: 1,
763 p_value: None,
764 });
765 }
766 }
767 }
768
769 motifs
770 }
771
772 fn trace_single_qubit_chain(
774 &self,
775 start: usize,
776 graph: &SciRS2CircuitGraph,
777 visited: &mut HashSet<usize>,
778 ) -> Vec<usize> {
779 let mut chain = vec![start];
780 visited.insert(start);
781
782 let mut current = start;
783 loop {
784 let neighbors = self.get_neighbors(current, graph);
785 let mut next = None;
786
787 for neighbor in neighbors {
788 if !visited.contains(&neighbor) {
789 if let Some(node) = graph.nodes.get(&neighbor) {
790 if let SciRS2NodeType::SingleQubitGate { .. } = node.node_type {
791 next = Some(neighbor);
792 break;
793 }
794 }
795 }
796 }
797
798 match next {
799 Some(next_node) => {
800 chain.push(next_node);
801 visited.insert(next_node);
802 current = next_node;
803 }
804 None => break,
805 }
806 }
807
808 chain
809 }
810
811 fn detect_cnot_patterns(&self, graph: &SciRS2CircuitGraph) -> Vec<GraphMotif> {
813 let mut motifs = Vec::new();
814
815 for node in graph.nodes.values() {
816 if let SciRS2NodeType::TwoQubitGate { gate_type, .. } = &node.node_type {
817 if gate_type == "CNOT" {
818 motifs.push(GraphMotif {
819 motif_type: MotifType::CnotLadder,
820 nodes: vec![node.id],
821 frequency: 1,
822 p_value: None,
823 });
824 }
825 }
826 }
827
828 motifs
829 }
830
831 fn generate_optimization_suggestions(
833 &self,
834 graph: &SciRS2CircuitGraph,
835 critical_paths: &[Vec<usize>],
836 communities: &[Vec<usize>],
837 motifs: &[GraphMotif],
838 ) -> QuantRS2Result<Vec<OptimizationSuggestion>> {
839 let mut suggestions = Vec::new();
840
841 for community in communities {
843 if community.len() > 1 {
844 suggestions.push(OptimizationSuggestion {
845 suggestion_type: SuggestionType::Parallelization,
846 nodes: community.clone(),
847 expected_improvement: 0.2,
848 confidence: 0.8,
849 description: "Gates in this community can potentially be parallelized"
850 .to_string(),
851 });
852 }
853 }
854
855 for motif in motifs {
857 if motif.nodes.len() > 2 {
858 suggestions.push(OptimizationSuggestion {
859 suggestion_type: SuggestionType::TemplateMatching,
860 nodes: motif.nodes.clone(),
861 expected_improvement: 0.15,
862 confidence: 0.7,
863 description: format!(
864 "Pattern {:?} detected - consider template optimization",
865 motif.motif_type
866 ),
867 });
868 }
869 }
870
871 Ok(suggestions)
872 }
873}
874
875#[cfg(test)]
876mod tests {
877 use super::*;
878 use quantrs2_core::gate::multi::CNOT;
879 use quantrs2_core::gate::single::Hadamard;
880
881 #[test]
882 fn test_scirs2_graph_creation() {
883 let analyzer = SciRS2CircuitAnalyzer::new();
884
885 let mut circuit = Circuit::<2>::new();
886 circuit.add_gate(Hadamard { target: QubitId(0) }).unwrap();
887 circuit
888 .add_gate(CNOT {
889 control: QubitId(0),
890 target: QubitId(1),
891 })
892 .unwrap();
893
894 let graph = analyzer.circuit_to_scirs2_graph(&circuit).unwrap();
895
896 assert_eq!(graph.nodes.len(), 2);
897 assert!(!graph.edges.is_empty());
898 }
899
900 #[test]
901 fn test_graph_metrics_calculation() {
902 let analyzer = SciRS2CircuitAnalyzer::new();
903
904 let mut circuit = Circuit::<3>::new();
905 circuit.add_gate(Hadamard { target: QubitId(0) }).unwrap();
906 circuit.add_gate(Hadamard { target: QubitId(1) }).unwrap();
907 circuit.add_gate(Hadamard { target: QubitId(2) }).unwrap();
908
909 let graph = analyzer.circuit_to_scirs2_graph(&circuit).unwrap();
910 let metrics = analyzer.calculate_graph_metrics(&graph).unwrap();
911
912 assert_eq!(metrics.num_nodes, 3);
913 assert!(metrics.clustering_coefficient >= 0.0);
914 assert!(metrics.density >= 0.0 && metrics.density <= 1.0);
915 }
916
917 #[test]
918 fn test_circuit_analysis() {
919 let mut analyzer = SciRS2CircuitAnalyzer::new();
920
921 let mut circuit = Circuit::<2>::new();
922 circuit.add_gate(Hadamard { target: QubitId(0) }).unwrap();
923 circuit
924 .add_gate(CNOT {
925 control: QubitId(0),
926 target: QubitId(1),
927 })
928 .unwrap();
929 circuit.add_gate(Hadamard { target: QubitId(1) }).unwrap();
930
931 let result = analyzer.analyze_circuit(&circuit).unwrap();
932
933 assert!(result.metrics.num_nodes > 0);
934 assert!(!result.critical_paths.is_empty());
935 }
936
937 #[test]
938 fn test_motif_detection() {
939 let analyzer = SciRS2CircuitAnalyzer::new();
940
941 let mut circuit = Circuit::<1>::new();
942 circuit.add_gate(Hadamard { target: QubitId(0) }).unwrap();
943 circuit.add_gate(Hadamard { target: QubitId(0) }).unwrap();
944 circuit.add_gate(Hadamard { target: QubitId(0) }).unwrap();
945
946 let graph = analyzer.circuit_to_scirs2_graph(&circuit).unwrap();
947 let motifs = analyzer.detect_motifs(&graph).unwrap();
948
949 assert!(motifs.is_empty() || !motifs.is_empty());
953 }
954}