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>>> {
715 let max_depth = graph
716 .nodes
717 .values()
718 .map(|node| node.depth)
719 .max()
720 .unwrap_or(0);
721
722 let mut sinks: Vec<usize> = graph
724 .nodes
725 .values()
726 .filter(|node| node.depth == max_depth)
727 .map(|node| node.id)
728 .collect();
729 sinks.sort_unstable();
730
731 let mut paths = Vec::with_capacity(sinks.len());
732
733 for sink in sinks {
734 let mut path = vec![sink];
735 let mut current = sink;
736
737 while let Some(current_depth) = graph
741 .nodes
742 .get(¤t)
743 .map(|n| n.depth)
744 .filter(|&d| d > 0)
745 {
746 let predecessor = graph
748 .edges
749 .keys()
750 .filter(|&&(_, target)| target == current)
751 .map(|&(source, _)| source)
752 .filter(|src| {
753 graph
754 .nodes
755 .get(src)
756 .is_some_and(|n| n.depth + 1 == current_depth)
757 })
758 .min();
759
760 match predecessor {
761 Some(prev) => {
762 path.push(prev);
763 current = prev;
764 }
765 None => break,
766 }
767 }
768
769 path.reverse();
770 paths.push(path);
771 }
772
773 Ok(paths)
774 }
775
776 fn detect_communities(&self, graph: &SciRS2CircuitGraph) -> QuantRS2Result<Vec<Vec<usize>>> {
778 let mut communities = BTreeMap::new();
780
781 for node in graph.nodes.values() {
782 communities
783 .entry(node.depth)
784 .or_insert_with(Vec::new)
785 .push(node.id);
786 }
787
788 Ok(communities.into_values().collect())
789 }
790
791 fn detect_motifs(&self, graph: &SciRS2CircuitGraph) -> QuantRS2Result<Vec<GraphMotif>> {
793 let mut motifs = Vec::new();
794
795 let chains = self.detect_single_qubit_chains(graph);
797 motifs.extend(chains);
798
799 let cnot_patterns = self.detect_cnot_patterns(graph);
801 motifs.extend(cnot_patterns);
802
803 Ok(motifs)
804 }
805
806 fn detect_single_qubit_chains(&self, graph: &SciRS2CircuitGraph) -> Vec<GraphMotif> {
808 let mut motifs = Vec::new();
809 let mut visited = HashSet::new();
810
811 for node in graph.nodes.values() {
812 if visited.contains(&node.id) {
813 continue;
814 }
815
816 if let SciRS2NodeType::SingleQubitGate { .. } = node.node_type {
817 let chain = self.trace_single_qubit_chain(node.id, graph, &mut visited);
818 if chain.len() > 2 {
819 motifs.push(GraphMotif {
820 motif_type: MotifType::SingleQubitChain,
821 nodes: chain,
822 frequency: 1,
823 p_value: None,
824 });
825 }
826 }
827 }
828
829 motifs
830 }
831
832 fn trace_single_qubit_chain(
834 &self,
835 start: usize,
836 graph: &SciRS2CircuitGraph,
837 visited: &mut HashSet<usize>,
838 ) -> Vec<usize> {
839 let mut chain = vec![start];
840 visited.insert(start);
841
842 let mut current = start;
843 loop {
844 let neighbors = self.get_neighbors(current, graph);
845 let mut next = None;
846
847 for neighbor in neighbors {
848 if !visited.contains(&neighbor) {
849 if let Some(node) = graph.nodes.get(&neighbor) {
850 if let SciRS2NodeType::SingleQubitGate { .. } = node.node_type {
851 next = Some(neighbor);
852 break;
853 }
854 }
855 }
856 }
857
858 match next {
859 Some(next_node) => {
860 chain.push(next_node);
861 visited.insert(next_node);
862 current = next_node;
863 }
864 None => break,
865 }
866 }
867
868 chain
869 }
870
871 fn detect_cnot_patterns(&self, graph: &SciRS2CircuitGraph) -> Vec<GraphMotif> {
873 let mut motifs = Vec::new();
874
875 for node in graph.nodes.values() {
876 if let SciRS2NodeType::TwoQubitGate { gate_type, .. } = &node.node_type {
877 if gate_type == "CNOT" {
878 motifs.push(GraphMotif {
879 motif_type: MotifType::CnotLadder,
880 nodes: vec![node.id],
881 frequency: 1,
882 p_value: None,
883 });
884 }
885 }
886 }
887
888 motifs
889 }
890
891 fn generate_optimization_suggestions(
893 &self,
894 graph: &SciRS2CircuitGraph,
895 critical_paths: &[Vec<usize>],
896 communities: &[Vec<usize>],
897 motifs: &[GraphMotif],
898 ) -> QuantRS2Result<Vec<OptimizationSuggestion>> {
899 let mut suggestions = Vec::new();
900
901 for community in communities {
903 if community.len() > 1 {
904 suggestions.push(OptimizationSuggestion {
905 suggestion_type: SuggestionType::Parallelization,
906 nodes: community.clone(),
907 expected_improvement: 0.2,
908 confidence: 0.8,
909 description: "Gates in this community can potentially be parallelized"
910 .to_string(),
911 });
912 }
913 }
914
915 for motif in motifs {
917 if motif.nodes.len() > 2 {
918 suggestions.push(OptimizationSuggestion {
919 suggestion_type: SuggestionType::TemplateMatching,
920 nodes: motif.nodes.clone(),
921 expected_improvement: 0.15,
922 confidence: 0.7,
923 description: format!(
924 "Pattern {:?} detected - consider template optimization",
925 motif.motif_type
926 ),
927 });
928 }
929 }
930
931 Ok(suggestions)
932 }
933}
934
935#[cfg(test)]
936mod tests {
937 use super::*;
938 use quantrs2_core::gate::multi::CNOT;
939 use quantrs2_core::gate::single::Hadamard;
940
941 #[test]
942 fn test_scirs2_graph_creation() {
943 let 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
956 let graph = analyzer
957 .circuit_to_scirs2_graph(&circuit)
958 .expect("Failed to convert circuit to SciRS2 graph");
959
960 assert_eq!(graph.nodes.len(), 2);
961 assert!(!graph.edges.is_empty());
962 }
963
964 #[test]
965 fn test_graph_metrics_calculation() {
966 let analyzer = SciRS2CircuitAnalyzer::new();
967
968 let mut circuit = Circuit::<3>::new();
969 circuit
970 .add_gate(Hadamard { target: QubitId(0) })
971 .expect("Failed to add Hadamard gate to qubit 0");
972 circuit
973 .add_gate(Hadamard { target: QubitId(1) })
974 .expect("Failed to add Hadamard gate to qubit 1");
975 circuit
976 .add_gate(Hadamard { target: QubitId(2) })
977 .expect("Failed to add Hadamard gate to qubit 2");
978
979 let graph = analyzer
980 .circuit_to_scirs2_graph(&circuit)
981 .expect("Failed to convert circuit to SciRS2 graph");
982 let metrics = analyzer
983 .calculate_graph_metrics(&graph)
984 .expect("Failed to calculate graph metrics");
985
986 assert_eq!(metrics.num_nodes, 3);
987 assert!(metrics.clustering_coefficient >= 0.0);
988 assert!(metrics.density >= 0.0 && metrics.density <= 1.0);
989 }
990
991 #[test]
992 fn test_circuit_analysis() {
993 let mut analyzer = SciRS2CircuitAnalyzer::new();
994
995 let mut circuit = Circuit::<2>::new();
996 circuit
997 .add_gate(Hadamard { target: QubitId(0) })
998 .expect("Failed to add Hadamard gate to qubit 0");
999 circuit
1000 .add_gate(CNOT {
1001 control: QubitId(0),
1002 target: QubitId(1),
1003 })
1004 .expect("Failed to add CNOT gate");
1005 circuit
1006 .add_gate(Hadamard { target: QubitId(1) })
1007 .expect("Failed to add Hadamard gate to qubit 1");
1008
1009 let result = analyzer
1010 .analyze_circuit(&circuit)
1011 .expect("Failed to analyze circuit");
1012
1013 assert!(result.metrics.num_nodes > 0);
1014 assert!(!result.critical_paths.is_empty());
1015 }
1016
1017 #[test]
1018 fn test_critical_path_is_connected_chain() {
1019 let analyzer = SciRS2CircuitAnalyzer::new();
1020
1021 let mut circuit = Circuit::<1>::new();
1024 for _ in 0..3 {
1025 circuit
1026 .add_gate(Hadamard { target: QubitId(0) })
1027 .expect("Failed to add Hadamard gate");
1028 }
1029
1030 let graph = analyzer
1031 .circuit_to_scirs2_graph(&circuit)
1032 .expect("Failed to convert circuit to SciRS2 graph");
1033 let paths = analyzer
1034 .find_critical_paths(&graph)
1035 .expect("Failed to find critical paths");
1036
1037 assert!(!paths.is_empty());
1038 let longest = paths
1039 .iter()
1040 .max_by_key(|p| p.len())
1041 .expect("at least one path");
1042
1043 assert_eq!(longest.len(), 3, "critical chain should span all 3 gates");
1046 for window in longest.windows(2) {
1047 let (from, to) = (window[0], window[1]);
1048 assert!(
1049 graph.edges.contains_key(&(from, to)),
1050 "path step {from}->{to} must be a real edge"
1051 );
1052 let from_depth = graph.nodes[&from].depth;
1053 let to_depth = graph.nodes[&to].depth;
1054 assert_eq!(from_depth + 1, to_depth);
1055 }
1056 }
1057
1058 #[test]
1059 fn test_motif_detection() {
1060 let analyzer = SciRS2CircuitAnalyzer::new();
1061
1062 let mut circuit = Circuit::<1>::new();
1063 circuit
1064 .add_gate(Hadamard { target: QubitId(0) })
1065 .expect("Failed to add first Hadamard gate");
1066 circuit
1067 .add_gate(Hadamard { target: QubitId(0) })
1068 .expect("Failed to add second Hadamard gate");
1069 circuit
1070 .add_gate(Hadamard { target: QubitId(0) })
1071 .expect("Failed to add third Hadamard gate");
1072
1073 let graph = analyzer
1074 .circuit_to_scirs2_graph(&circuit)
1075 .expect("Failed to convert circuit to SciRS2 graph");
1076 let motifs = analyzer
1077 .detect_motifs(&graph)
1078 .expect("Failed to detect motifs");
1079
1080 assert!(motifs.is_empty() || !motifs.is_empty());
1084 }
1085}