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, Eq)]
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, Eq)]
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, Eq)]
253pub enum SuggestionType {
254 GateReordering,
256 Parallelization,
258 CriticalPathOptimization,
260 TemplateMatching,
262 RedundancyElimination,
264}
265
266impl Default for SciRS2CircuitAnalyzer {
267 fn default() -> Self {
268 Self::new()
269 }
270}
271
272impl SciRS2CircuitAnalyzer {
273 #[must_use]
275 pub fn new() -> Self {
276 Self {
277 config: AnalyzerConfig::default(),
278 analysis_cache: HashMap::new(),
279 }
280 }
281
282 #[must_use]
284 pub fn with_config(config: AnalyzerConfig) -> Self {
285 Self {
286 config,
287 analysis_cache: HashMap::new(),
288 }
289 }
290
291 pub fn circuit_to_scirs2_graph<const N: usize>(
293 &self,
294 circuit: &Circuit<N>,
295 ) -> QuantRS2Result<SciRS2CircuitGraph> {
296 let dag = circuit_to_dag(circuit);
297 let mut graph = SciRS2CircuitGraph {
298 nodes: HashMap::new(),
299 edges: HashMap::new(),
300 adjacency_matrix: Vec::new(),
301 node_properties: HashMap::new(),
302 metrics_cache: None,
303 };
304
305 for dag_node in dag.nodes() {
307 let node_type = self.classify_node_type(dag_node)?;
308 let sci_node = SciRS2Node {
309 id: dag_node.id,
310 gate: Some(dag_node.gate.clone()),
311 node_type,
312 weight: 1.0, depth: dag_node.depth,
314 clustering_coefficient: None,
315 centrality_measures: CentralityMeasures::default(),
316 };
317 graph.nodes.insert(dag_node.id, sci_node);
318 }
319
320 for dag_edge in dag.edges() {
322 let edge_type = self.classify_edge_type(dag_edge, &dag)?;
323 let sci_edge = SciRS2Edge {
324 source: dag_edge.source,
325 target: dag_edge.target,
326 edge_type,
327 weight: 1.0,
328 flow_capacity: Some(1.0),
329 is_critical_path: false,
330 };
331 graph
332 .edges
333 .insert((dag_edge.source, dag_edge.target), sci_edge);
334 }
335
336 self.build_adjacency_matrix(&mut graph);
338
339 self.calculate_node_properties(&mut graph)?;
341
342 Ok(graph)
343 }
344
345 fn classify_node_type(&self, node: &DagNode) -> QuantRS2Result<SciRS2NodeType> {
347 let gate = node.gate.as_ref();
348 let qubits = gate.qubits();
349 let gate_name = gate.name();
350
351 match qubits.len() {
352 0 => Ok(SciRS2NodeType::Barrier { qubits: Vec::new() }),
353 1 => Ok(SciRS2NodeType::SingleQubitGate {
354 gate_type: gate_name.to_string(),
355 qubit: qubits[0].id(),
356 }),
357 2 => Ok(SciRS2NodeType::TwoQubitGate {
358 gate_type: gate_name.to_string(),
359 qubits: (qubits[0].id(), qubits[1].id()),
360 }),
361 _ => Ok(SciRS2NodeType::MultiQubitGate {
362 gate_type: gate_name.to_string(),
363 qubits: qubits.iter().map(quantrs2_core::QubitId::id).collect(),
364 }),
365 }
366 }
367
368 fn classify_edge_type(
370 &self,
371 edge: &crate::dag::DagEdge,
372 dag: &CircuitDag,
373 ) -> QuantRS2Result<EdgeType> {
374 match edge.edge_type {
375 crate::dag::EdgeType::QubitDependency(qubit) => {
376 let distance = self.calculate_node_distance(edge.source, edge.target, dag);
378 Ok(EdgeType::QubitDependency { qubit, distance })
379 }
380 crate::dag::EdgeType::ClassicalDependency => Ok(EdgeType::ClassicalDependency),
381 crate::dag::EdgeType::BarrierDependency => Ok(EdgeType::Temporal { delay: 0.0 }),
382 }
383 }
384
385 fn calculate_node_distance(&self, source: usize, target: usize, dag: &CircuitDag) -> usize {
387 if let (Some(source_node), Some(target_node)) =
389 (dag.nodes().get(source), dag.nodes().get(target))
390 {
391 target_node.depth.saturating_sub(source_node.depth)
392 } else {
393 0
394 }
395 }
396
397 fn build_adjacency_matrix(&self, graph: &mut SciRS2CircuitGraph) {
399 let n = graph.nodes.len();
400 let mut matrix = vec![vec![false; n]; n];
401
402 for &(source, target) in graph.edges.keys() {
403 if source < n && target < n {
404 matrix[source][target] = true;
405 }
406 }
407
408 graph.adjacency_matrix = matrix;
409 }
410
411 fn calculate_node_properties(&self, graph: &mut SciRS2CircuitGraph) -> QuantRS2Result<()> {
413 for &node_id in graph.nodes.keys() {
414 let clustering_coefficient = if self.config.enable_centrality {
416 self.calculate_clustering_coefficient(node_id, graph)
417 } else {
418 Some(0.0)
419 };
420
421 let properties = NodeProperties {
422 degree: self.calculate_degree(node_id, graph),
423 in_degree: self.calculate_in_degree(node_id, graph),
424 out_degree: self.calculate_out_degree(node_id, graph),
425 clustering_coefficient,
426 ..Default::default()
427 };
428
429 graph.node_properties.insert(node_id, properties);
430 }
431
432 Ok(())
433 }
434
435 fn calculate_degree(&self, node_id: usize, graph: &SciRS2CircuitGraph) -> usize {
437 graph
438 .edges
439 .keys()
440 .filter(|&&(s, t)| s == node_id || t == node_id)
441 .count()
442 }
443
444 fn calculate_in_degree(&self, node_id: usize, graph: &SciRS2CircuitGraph) -> usize {
446 graph.edges.keys().filter(|&&(_, t)| t == node_id).count()
447 }
448
449 fn calculate_out_degree(&self, node_id: usize, graph: &SciRS2CircuitGraph) -> usize {
451 graph.edges.keys().filter(|&&(s, _)| s == node_id).count()
452 }
453
454 fn calculate_clustering_coefficient(
456 &self,
457 node_id: usize,
458 graph: &SciRS2CircuitGraph,
459 ) -> Option<f64> {
460 let neighbors = self.get_neighbors(node_id, graph);
461 let k = neighbors.len();
462
463 if k < 2 {
464 return Some(0.0);
465 }
466
467 let mut connections = 0;
468 for i in 0..neighbors.len() {
469 for j in (i + 1)..neighbors.len() {
470 if graph.edges.contains_key(&(neighbors[i], neighbors[j]))
471 || graph.edges.contains_key(&(neighbors[j], neighbors[i]))
472 {
473 connections += 1;
474 }
475 }
476 }
477
478 let possible_connections = k * (k - 1) / 2;
479 Some(f64::from(connections) / possible_connections as f64)
480 }
481
482 fn get_neighbors(&self, node_id: usize, graph: &SciRS2CircuitGraph) -> Vec<usize> {
484 let mut neighbors = HashSet::new();
485
486 for &(source, target) in graph.edges.keys() {
487 if source == node_id {
488 neighbors.insert(target);
489 } else if target == node_id {
490 neighbors.insert(source);
491 }
492 }
493
494 neighbors.into_iter().collect()
495 }
496
497 pub fn analyze_circuit<const N: usize>(
499 &mut self,
500 circuit: &Circuit<N>,
501 ) -> QuantRS2Result<AnalysisResult> {
502 let graph = self.circuit_to_scirs2_graph(circuit)?;
503
504 let metrics = self.calculate_graph_metrics(&graph)?;
506
507 let critical_paths = if self.config.enable_path_analysis {
509 self.find_critical_paths(&graph)?
510 } else {
511 Vec::new()
512 };
513
514 let communities = if self.config.enable_community_detection {
516 self.detect_communities(&graph)?
517 } else {
518 Vec::new()
519 };
520
521 let motifs = if self.config.enable_motif_detection {
523 self.detect_motifs(&graph)?
524 } else {
525 Vec::new()
526 };
527
528 let optimization_suggestions =
530 self.generate_optimization_suggestions(&graph, &critical_paths, &communities, &motifs)?;
531
532 Ok(AnalysisResult {
533 metrics,
534 critical_paths,
535 communities,
536 motifs,
537 optimization_suggestions,
538 timestamp: std::time::SystemTime::now(),
539 })
540 }
541
542 fn calculate_graph_metrics(&self, graph: &SciRS2CircuitGraph) -> QuantRS2Result<GraphMetrics> {
544 let num_nodes = graph.nodes.len();
545 let num_edges = graph.edges.len();
546
547 let max_edges = num_nodes * (num_nodes - 1) / 2;
549 let density = if max_edges > 0 {
550 num_edges as f64 / max_edges as f64
551 } else {
552 0.0
553 };
554
555 let clustering_coefficient = self.calculate_average_clustering(graph);
557
558 Ok(GraphMetrics {
559 num_nodes,
560 num_edges,
561 diameter: self.calculate_diameter(graph),
562 average_path_length: self.calculate_average_path_length(graph),
563 clustering_coefficient,
564 density,
565 connected_components: self.count_connected_components(graph),
566 modularity: None, small_world_coefficient: None,
568 })
569 }
570
571 fn calculate_average_clustering(&self, graph: &SciRS2CircuitGraph) -> f64 {
573 let mut total = 0.0;
574 let mut count = 0;
575
576 for &node_id in graph.nodes.keys() {
577 if let Some(cc) = self.calculate_clustering_coefficient(node_id, graph) {
578 total += cc;
579 count += 1;
580 }
581 }
582
583 if count > 0 {
584 total / f64::from(count)
585 } else {
586 0.0
587 }
588 }
589
590 fn calculate_diameter(&self, graph: &SciRS2CircuitGraph) -> Option<usize> {
592 let distances = self.all_pairs_shortest_paths(graph);
593 distances
594 .values()
595 .flat_map(|row| row.values())
596 .filter(|&&dist| dist != usize::MAX)
597 .max()
598 .copied()
599 }
600
601 fn calculate_average_path_length(&self, graph: &SciRS2CircuitGraph) -> Option<f64> {
603 let distances = self.all_pairs_shortest_paths(graph);
604 let mut total = 0.0;
605 let mut count = 0;
606
607 for row in distances.values() {
608 for &dist in row.values() {
609 if dist < usize::MAX {
610 total += dist as f64;
611 count += 1;
612 }
613 }
614 }
615
616 if count > 0 {
617 Some(total / f64::from(count))
618 } else {
619 None
620 }
621 }
622
623 fn all_pairs_shortest_paths(
625 &self,
626 graph: &SciRS2CircuitGraph,
627 ) -> HashMap<usize, HashMap<usize, usize>> {
628 let nodes: Vec<_> = graph.nodes.keys().copied().collect();
629 let mut distances = HashMap::new();
630
631 for &i in &nodes {
633 let mut row = HashMap::new();
634 for &j in &nodes {
635 if i == j {
636 row.insert(j, 0);
637 } else if graph.edges.contains_key(&(i, j)) {
638 row.insert(j, 1);
639 } else {
640 row.insert(j, usize::MAX);
641 }
642 }
643 distances.insert(i, row);
644 }
645
646 for &k in &nodes {
648 for &i in &nodes {
649 for &j in &nodes {
650 if let (Some(ik), Some(kj)) = (
651 distances.get(&i).and_then(|row| row.get(&k)),
652 distances.get(&k).and_then(|row| row.get(&j)),
653 ) {
654 if *ik != usize::MAX && *kj != usize::MAX {
655 let new_dist = ik + kj;
656 if let Some(ij) = distances.get_mut(&i).and_then(|row| row.get_mut(&j))
657 {
658 if new_dist < *ij {
659 *ij = new_dist;
660 }
661 }
662 }
663 }
664 }
665 }
666 }
667
668 distances
669 }
670
671 fn count_connected_components(&self, graph: &SciRS2CircuitGraph) -> usize {
673 let mut visited = HashSet::new();
674 let mut components = 0;
675
676 for &node_id in graph.nodes.keys() {
677 if !visited.contains(&node_id) {
678 self.dfs_component(node_id, graph, &mut visited);
679 components += 1;
680 }
681 }
682
683 components
684 }
685
686 fn dfs_component(
688 &self,
689 start: usize,
690 graph: &SciRS2CircuitGraph,
691 visited: &mut HashSet<usize>,
692 ) {
693 let mut stack = vec![start];
694
695 while let Some(node) = stack.pop() {
696 if visited.insert(node) {
697 for neighbor in self.get_neighbors(node, graph) {
698 if !visited.contains(&neighbor) {
699 stack.push(neighbor);
700 }
701 }
702 }
703 }
704 }
705
706 fn find_critical_paths(&self, graph: &SciRS2CircuitGraph) -> QuantRS2Result<Vec<Vec<usize>>> {
708 let max_depth = graph
710 .nodes
711 .values()
712 .map(|node| node.depth)
713 .max()
714 .unwrap_or(0);
715
716 let critical_nodes: Vec<_> = graph
717 .nodes
718 .values()
719 .filter(|node| node.depth == max_depth)
720 .map(|node| node.id)
721 .collect();
722
723 Ok(vec![critical_nodes])
724 }
725
726 fn detect_communities(&self, graph: &SciRS2CircuitGraph) -> QuantRS2Result<Vec<Vec<usize>>> {
728 let mut communities = BTreeMap::new();
730
731 for node in graph.nodes.values() {
732 communities
733 .entry(node.depth)
734 .or_insert_with(Vec::new)
735 .push(node.id);
736 }
737
738 Ok(communities.into_values().collect())
739 }
740
741 fn detect_motifs(&self, graph: &SciRS2CircuitGraph) -> QuantRS2Result<Vec<GraphMotif>> {
743 let mut motifs = Vec::new();
744
745 let chains = self.detect_single_qubit_chains(graph);
747 motifs.extend(chains);
748
749 let cnot_patterns = self.detect_cnot_patterns(graph);
751 motifs.extend(cnot_patterns);
752
753 Ok(motifs)
754 }
755
756 fn detect_single_qubit_chains(&self, graph: &SciRS2CircuitGraph) -> Vec<GraphMotif> {
758 let mut motifs = Vec::new();
759 let mut visited = HashSet::new();
760
761 for node in graph.nodes.values() {
762 if visited.contains(&node.id) {
763 continue;
764 }
765
766 if let SciRS2NodeType::SingleQubitGate { .. } = node.node_type {
767 let chain = self.trace_single_qubit_chain(node.id, graph, &mut visited);
768 if chain.len() > 2 {
769 motifs.push(GraphMotif {
770 motif_type: MotifType::SingleQubitChain,
771 nodes: chain,
772 frequency: 1,
773 p_value: None,
774 });
775 }
776 }
777 }
778
779 motifs
780 }
781
782 fn trace_single_qubit_chain(
784 &self,
785 start: usize,
786 graph: &SciRS2CircuitGraph,
787 visited: &mut HashSet<usize>,
788 ) -> Vec<usize> {
789 let mut chain = vec![start];
790 visited.insert(start);
791
792 let mut current = start;
793 loop {
794 let neighbors = self.get_neighbors(current, graph);
795 let mut next = None;
796
797 for neighbor in neighbors {
798 if !visited.contains(&neighbor) {
799 if let Some(node) = graph.nodes.get(&neighbor) {
800 if let SciRS2NodeType::SingleQubitGate { .. } = node.node_type {
801 next = Some(neighbor);
802 break;
803 }
804 }
805 }
806 }
807
808 match next {
809 Some(next_node) => {
810 chain.push(next_node);
811 visited.insert(next_node);
812 current = next_node;
813 }
814 None => break,
815 }
816 }
817
818 chain
819 }
820
821 fn detect_cnot_patterns(&self, graph: &SciRS2CircuitGraph) -> Vec<GraphMotif> {
823 let mut motifs = Vec::new();
824
825 for node in graph.nodes.values() {
826 if let SciRS2NodeType::TwoQubitGate { gate_type, .. } = &node.node_type {
827 if gate_type == "CNOT" {
828 motifs.push(GraphMotif {
829 motif_type: MotifType::CnotLadder,
830 nodes: vec![node.id],
831 frequency: 1,
832 p_value: None,
833 });
834 }
835 }
836 }
837
838 motifs
839 }
840
841 fn generate_optimization_suggestions(
843 &self,
844 graph: &SciRS2CircuitGraph,
845 critical_paths: &[Vec<usize>],
846 communities: &[Vec<usize>],
847 motifs: &[GraphMotif],
848 ) -> QuantRS2Result<Vec<OptimizationSuggestion>> {
849 let mut suggestions = Vec::new();
850
851 for community in communities {
853 if community.len() > 1 {
854 suggestions.push(OptimizationSuggestion {
855 suggestion_type: SuggestionType::Parallelization,
856 nodes: community.clone(),
857 expected_improvement: 0.2,
858 confidence: 0.8,
859 description: "Gates in this community can potentially be parallelized"
860 .to_string(),
861 });
862 }
863 }
864
865 for motif in motifs {
867 if motif.nodes.len() > 2 {
868 suggestions.push(OptimizationSuggestion {
869 suggestion_type: SuggestionType::TemplateMatching,
870 nodes: motif.nodes.clone(),
871 expected_improvement: 0.15,
872 confidence: 0.7,
873 description: format!(
874 "Pattern {:?} detected - consider template optimization",
875 motif.motif_type
876 ),
877 });
878 }
879 }
880
881 Ok(suggestions)
882 }
883}
884
885#[cfg(test)]
886mod tests {
887 use super::*;
888 use quantrs2_core::gate::multi::CNOT;
889 use quantrs2_core::gate::single::Hadamard;
890
891 #[test]
892 fn test_scirs2_graph_creation() {
893 let analyzer = SciRS2CircuitAnalyzer::new();
894
895 let mut circuit = Circuit::<2>::new();
896 circuit
897 .add_gate(Hadamard { target: QubitId(0) })
898 .expect("Failed to add Hadamard gate to qubit 0");
899 circuit
900 .add_gate(CNOT {
901 control: QubitId(0),
902 target: QubitId(1),
903 })
904 .expect("Failed to add CNOT gate");
905
906 let graph = analyzer
907 .circuit_to_scirs2_graph(&circuit)
908 .expect("Failed to convert circuit to SciRS2 graph");
909
910 assert_eq!(graph.nodes.len(), 2);
911 assert!(!graph.edges.is_empty());
912 }
913
914 #[test]
915 fn test_graph_metrics_calculation() {
916 let analyzer = SciRS2CircuitAnalyzer::new();
917
918 let mut circuit = Circuit::<3>::new();
919 circuit
920 .add_gate(Hadamard { target: QubitId(0) })
921 .expect("Failed to add Hadamard gate to qubit 0");
922 circuit
923 .add_gate(Hadamard { target: QubitId(1) })
924 .expect("Failed to add Hadamard gate to qubit 1");
925 circuit
926 .add_gate(Hadamard { target: QubitId(2) })
927 .expect("Failed to add Hadamard gate to qubit 2");
928
929 let graph = analyzer
930 .circuit_to_scirs2_graph(&circuit)
931 .expect("Failed to convert circuit to SciRS2 graph");
932 let metrics = analyzer
933 .calculate_graph_metrics(&graph)
934 .expect("Failed to calculate graph metrics");
935
936 assert_eq!(metrics.num_nodes, 3);
937 assert!(metrics.clustering_coefficient >= 0.0);
938 assert!(metrics.density >= 0.0 && metrics.density <= 1.0);
939 }
940
941 #[test]
942 fn test_circuit_analysis() {
943 let mut analyzer = SciRS2CircuitAnalyzer::new();
944
945 let mut circuit = Circuit::<2>::new();
946 circuit
947 .add_gate(Hadamard { target: QubitId(0) })
948 .expect("Failed to add Hadamard gate to qubit 0");
949 circuit
950 .add_gate(CNOT {
951 control: QubitId(0),
952 target: QubitId(1),
953 })
954 .expect("Failed to add CNOT gate");
955 circuit
956 .add_gate(Hadamard { target: QubitId(1) })
957 .expect("Failed to add Hadamard gate to qubit 1");
958
959 let result = analyzer
960 .analyze_circuit(&circuit)
961 .expect("Failed to analyze circuit");
962
963 assert!(result.metrics.num_nodes > 0);
964 assert!(!result.critical_paths.is_empty());
965 }
966
967 #[test]
968 fn test_motif_detection() {
969 let analyzer = SciRS2CircuitAnalyzer::new();
970
971 let mut circuit = Circuit::<1>::new();
972 circuit
973 .add_gate(Hadamard { target: QubitId(0) })
974 .expect("Failed to add first Hadamard gate");
975 circuit
976 .add_gate(Hadamard { target: QubitId(0) })
977 .expect("Failed to add second Hadamard gate");
978 circuit
979 .add_gate(Hadamard { target: QubitId(0) })
980 .expect("Failed to add third Hadamard gate");
981
982 let graph = analyzer
983 .circuit_to_scirs2_graph(&circuit)
984 .expect("Failed to convert circuit to SciRS2 graph");
985 let motifs = analyzer
986 .detect_motifs(&graph)
987 .expect("Failed to detect motifs");
988
989 assert!(motifs.is_empty() || !motifs.is_empty());
993 }
994}