1use crate::builder::Circuit;
8use crate::dag::{circuit_to_dag, CircuitDag, DagNode};
9use quantrs2_core::{
10 error::{QuantRS2Error, QuantRS2Result},
11 gate::GateOp,
12 qubit::QubitId,
13};
14use serde::{Deserialize, Serialize};
15use std::collections::{HashMap, HashSet, VecDeque};
16use std::f64::consts::PI;
17use std::sync::Arc;
18
19#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
21pub enum ZXNode {
22 ZSpider {
24 id: usize,
25 phase: f64,
26 arity: usize,
28 },
29 XSpider {
31 id: usize,
32 phase: f64,
33 arity: usize,
34 },
35 Hadamard {
37 id: usize,
38 },
39 Input {
41 id: usize,
42 qubit: u32,
43 },
44 Output {
45 id: usize,
46 qubit: u32,
47 },
48}
49
50impl ZXNode {
51 #[must_use]
52 pub const fn id(&self) -> usize {
53 match self {
54 Self::ZSpider { id, .. } => *id,
55 Self::XSpider { id, .. } => *id,
56 Self::Hadamard { id } => *id,
57 Self::Input { id, .. } => *id,
58 Self::Output { id, .. } => *id,
59 }
60 }
61
62 #[must_use]
63 pub const fn phase(&self) -> f64 {
64 match self {
65 Self::ZSpider { phase, .. } | Self::XSpider { phase, .. } => *phase,
66 _ => 0.0,
67 }
68 }
69
70 pub const fn set_phase(&mut self, new_phase: f64) {
71 match self {
72 Self::ZSpider { phase, .. } | Self::XSpider { phase, .. } => *phase = new_phase,
73 _ => {}
74 }
75 }
76}
77
78#[derive(Debug, Clone, PartialEq, Eq)]
80pub struct ZXEdge {
81 pub source: usize,
82 pub target: usize,
83 pub is_hadamard: bool,
85}
86
87#[derive(Debug, Clone)]
89pub struct ZXDiagram {
90 pub nodes: HashMap<usize, ZXNode>,
92 pub edges: Vec<ZXEdge>,
94 pub adjacency: HashMap<usize, Vec<usize>>,
96 pub inputs: HashMap<u32, usize>,
98 pub outputs: HashMap<u32, usize>,
100 next_id: usize,
102}
103
104impl Default for ZXDiagram {
105 fn default() -> Self {
106 Self::new()
107 }
108}
109
110impl ZXDiagram {
111 #[must_use]
113 pub fn new() -> Self {
114 Self {
115 nodes: HashMap::new(),
116 edges: Vec::new(),
117 adjacency: HashMap::new(),
118 inputs: HashMap::new(),
119 outputs: HashMap::new(),
120 next_id: 0,
121 }
122 }
123
124 pub fn add_node(&mut self, node: ZXNode) -> usize {
126 let id = self.next_id;
127 self.next_id += 1;
128
129 let node_with_id = match node {
130 ZXNode::ZSpider { phase, arity, .. } => ZXNode::ZSpider { id, phase, arity },
131 ZXNode::XSpider { phase, arity, .. } => ZXNode::XSpider { id, phase, arity },
132 ZXNode::Hadamard { .. } => ZXNode::Hadamard { id },
133 ZXNode::Input { qubit, .. } => ZXNode::Input { id, qubit },
134 ZXNode::Output { qubit, .. } => ZXNode::Output { id, qubit },
135 };
136
137 self.nodes.insert(id, node_with_id);
138 self.adjacency.insert(id, Vec::new());
139 id
140 }
141
142 pub fn add_edge(&mut self, source: usize, target: usize, is_hadamard: bool) {
144 let edge = ZXEdge {
145 source,
146 target,
147 is_hadamard,
148 };
149 self.edges.push(edge);
150
151 self.adjacency.entry(source).or_default().push(target);
153 self.adjacency.entry(target).or_default().push(source);
154 }
155
156 pub fn initialize_boundaries(&mut self, num_qubits: usize) {
158 for i in 0..num_qubits {
159 let qubit = i as u32;
160
161 let input_id = self.add_node(ZXNode::Input { id: 0, qubit });
162 let output_id = self.add_node(ZXNode::Output { id: 0, qubit });
163
164 self.inputs.insert(qubit, input_id);
165 self.outputs.insert(qubit, output_id);
166 }
167 }
168
169 #[must_use]
171 pub fn neighbors(&self, node_id: usize) -> &[usize] {
172 self.adjacency
173 .get(&node_id)
174 .map_or(&[], std::vec::Vec::as_slice)
175 }
176
177 pub fn spider_fusion(&mut self) -> bool {
180 let mut changed = false;
181 let mut to_remove = Vec::new();
182 let mut to_update = Vec::new();
183
184 for edge in &self.edges {
185 if !edge.is_hadamard {
186 if let (Some(node1), Some(node2)) =
187 (self.nodes.get(&edge.source), self.nodes.get(&edge.target))
188 {
189 match (node1, node2) {
191 (
192 ZXNode::ZSpider {
193 id: id1,
194 phase: phase1,
195 ..
196 },
197 ZXNode::ZSpider {
198 id: id2,
199 phase: phase2,
200 ..
201 },
202 )
203 | (
204 ZXNode::XSpider {
205 id: id1,
206 phase: phase1,
207 ..
208 },
209 ZXNode::XSpider {
210 id: id2,
211 phase: phase2,
212 ..
213 },
214 ) => {
215 let new_phase = (phase1 + phase2) % (2.0 * PI);
217 to_update.push((*id1, new_phase));
218 to_remove.push(*id2);
219 changed = true;
220 }
221 _ => {}
222 }
223 }
224 }
225 }
226
227 for (id, new_phase) in to_update {
229 if let Some(node) = self.nodes.get_mut(&id) {
230 node.set_phase(new_phase);
231 }
232 }
233
234 for id in to_remove {
236 self.remove_node(id);
237 }
238
239 changed
240 }
241
242 pub fn identity_removal(&mut self) -> bool {
245 let mut changed = false;
246 let mut to_remove = Vec::new();
247
248 for (id, node) in &self.nodes {
249 match node {
250 ZXNode::ZSpider { phase, arity, .. } | ZXNode::XSpider { phase, arity, .. }
251 if *arity == 2 && phase.abs() < 1e-10 =>
252 {
253 to_remove.push(*id);
254 }
255 _ => {}
256 }
257 }
258
259 for id in to_remove {
260 let neighbors: Vec<_> = self.neighbors(id).to_vec();
262 if neighbors.len() == 2 {
263 self.add_edge(neighbors[0], neighbors[1], false);
264 changed = true;
265 }
266 self.remove_node(id);
267 }
268
269 changed
270 }
271
272 pub const fn pi_commutation(&self) -> bool {
291 false
292 }
293
294 pub fn hadamard_cancellation(&mut self) -> bool {
297 let mut changed = false;
298 let mut to_remove = Vec::new();
299
300 for edge in &self.edges {
302 if let (Some(ZXNode::Hadamard { id: id1 }), Some(ZXNode::Hadamard { id: id2 })) =
303 (self.nodes.get(&edge.source), self.nodes.get(&edge.target))
304 {
305 to_remove.push(*id1);
307 to_remove.push(*id2);
308 changed = true;
309 }
310 }
311
312 for id in to_remove {
313 self.remove_node(id);
314 }
315
316 changed
317 }
318
319 fn remove_node(&mut self, node_id: usize) {
321 self.nodes.remove(&node_id);
323
324 self.adjacency.remove(&node_id);
326
327 for adj_list in self.adjacency.values_mut() {
329 adj_list.retain(|&id| id != node_id);
330 }
331
332 self.edges
334 .retain(|edge| edge.source != node_id && edge.target != node_id);
335 }
336
337 #[must_use]
339 pub fn t_count(&self) -> usize {
340 self.nodes
341 .values()
342 .filter(|node| {
343 let phase = node.phase();
344 (phase - PI / 4.0).abs() < 1e-10
345 || (phase - 3.0 * PI / 4.0).abs() < 1e-10
346 || (phase - 5.0 * PI / 4.0).abs() < 1e-10
347 || (phase - 7.0 * PI / 4.0).abs() < 1e-10
348 })
349 .count()
350 }
351
352 pub fn optimize(&mut self) -> ZXOptimizationResult {
354 let initial_node_count = self.nodes.len();
355 let initial_t_count = self.t_count();
356
357 let mut iterations = 0;
358 let max_iterations = 100;
359
360 while iterations < max_iterations {
361 let mut changed = false;
362
363 changed |= self.spider_fusion();
365 changed |= self.identity_removal();
366 changed |= self.hadamard_cancellation();
367 changed |= self.pi_commutation();
368
369 if !changed {
370 break;
371 }
372 iterations += 1;
373 }
374
375 let final_node_count = self.nodes.len();
376 let final_t_count = self.t_count();
377
378 ZXOptimizationResult {
379 iterations,
380 initial_node_count,
381 final_node_count,
382 initial_t_count,
383 final_t_count,
384 converged: iterations < max_iterations,
385 }
386 }
387}
388
389#[derive(Debug, Clone)]
391pub struct ZXOptimizationResult {
392 pub iterations: usize,
393 pub initial_node_count: usize,
394 pub final_node_count: usize,
395 pub initial_t_count: usize,
396 pub final_t_count: usize,
397 pub converged: bool,
398}
399
400pub struct ZXOptimizer {
402 pub max_iterations: usize,
404 pub enable_spider_fusion: bool,
406 pub enable_identity_removal: bool,
407 pub enable_pi_commutation: bool,
408 pub enable_hadamard_cancellation: bool,
409}
410
411impl Default for ZXOptimizer {
412 fn default() -> Self {
413 Self {
414 max_iterations: 100,
415 enable_spider_fusion: true,
416 enable_identity_removal: true,
417 enable_pi_commutation: true,
418 enable_hadamard_cancellation: true,
419 }
420 }
421}
422
423impl ZXOptimizer {
424 #[must_use]
426 pub fn new() -> Self {
427 Self::default()
428 }
429
430 pub fn circuit_to_zx<const N: usize>(&self, circuit: &Circuit<N>) -> QuantRS2Result<ZXDiagram> {
432 let mut diagram = ZXDiagram::new();
433 diagram.initialize_boundaries(N);
434
435 let mut qubit_wires = HashMap::new();
437 for i in 0..N {
438 let qubit = i as u32;
439 if let Some(&input_id) = diagram.inputs.get(&qubit) {
440 qubit_wires.insert(qubit, input_id);
441 }
442 }
443
444 for gate in circuit.gates() {
446 self.gate_to_zx(gate.as_ref(), &mut diagram, &mut qubit_wires)?;
447 }
448
449 for i in 0..N {
451 let qubit = i as u32;
452 if let (Some(&last_node), Some(&output_id)) =
453 (qubit_wires.get(&qubit), diagram.outputs.get(&qubit))
454 {
455 diagram.add_edge(last_node, output_id, false);
456 }
457 }
458
459 Ok(diagram)
460 }
461
462 fn gate_to_zx(
464 &self,
465 gate: &dyn GateOp,
466 diagram: &mut ZXDiagram,
467 qubit_wires: &mut HashMap<u32, usize>,
468 ) -> QuantRS2Result<()> {
469 let gate_name = gate.name();
470 let qubits = gate.qubits();
471
472 match gate_name {
473 "H" => {
474 let qubit = qubits[0].id();
476 let h_node = diagram.add_node(ZXNode::Hadamard { id: 0 });
477
478 if let Some(&prev_node) = qubit_wires.get(&qubit) {
479 diagram.add_edge(prev_node, h_node, false);
480 }
481 qubit_wires.insert(qubit, h_node);
482 }
483 "X" => {
484 let qubit = qubits[0].id();
486 let x_node = diagram.add_node(ZXNode::ZSpider {
487 id: 0,
488 phase: PI,
489 arity: 2,
490 });
491
492 if let Some(&prev_node) = qubit_wires.get(&qubit) {
493 diagram.add_edge(prev_node, x_node, false);
494 }
495 qubit_wires.insert(qubit, x_node);
496 }
497 "Y" => {
498 let qubit = qubits[0].id();
500 let y_node = diagram.add_node(ZXNode::ZSpider {
501 id: 0,
502 phase: PI,
503 arity: 2,
504 });
505
506 if let Some(&prev_node) = qubit_wires.get(&qubit) {
507 diagram.add_edge(prev_node, y_node, false);
508 }
509 qubit_wires.insert(qubit, y_node);
510 }
511 "Z" => {
512 let qubit = qubits[0].id();
514 let z_node = diagram.add_node(ZXNode::ZSpider {
515 id: 0,
516 phase: PI,
517 arity: 2,
518 });
519
520 if let Some(&prev_node) = qubit_wires.get(&qubit) {
521 diagram.add_edge(prev_node, z_node, false);
522 }
523 qubit_wires.insert(qubit, z_node);
524 }
525 "RZ" => {
526 let qubit = qubits[0].id();
528
529 let angle = self.extract_rotation_angle(gate);
531 let rz_node = diagram.add_node(ZXNode::ZSpider {
532 id: 0,
533 phase: angle,
534 arity: 2,
535 });
536
537 if let Some(&prev_node) = qubit_wires.get(&qubit) {
538 diagram.add_edge(prev_node, rz_node, false);
539 }
540 qubit_wires.insert(qubit, rz_node);
541 }
542 "CNOT" => {
543 let control_qubit = qubits[0].id();
545 let target_qubit = qubits[1].id();
546
547 let control_spider = diagram.add_node(ZXNode::ZSpider {
548 id: 0,
549 phase: 0.0,
550 arity: 3,
551 });
552 let target_spider = diagram.add_node(ZXNode::XSpider {
553 id: 0,
554 phase: 0.0,
555 arity: 3,
556 });
557
558 if let Some(&prev_control) = qubit_wires.get(&control_qubit) {
560 diagram.add_edge(prev_control, control_spider, false);
561 }
562
563 if let Some(&prev_target) = qubit_wires.get(&target_qubit) {
565 diagram.add_edge(prev_target, target_spider, false);
566 }
567
568 diagram.add_edge(control_spider, target_spider, false);
570
571 qubit_wires.insert(control_qubit, control_spider);
572 qubit_wires.insert(target_qubit, target_spider);
573 }
574 _ => {
575 for qubit_id in qubits {
577 let qubit = qubit_id.id();
578 let identity_node = diagram.add_node(ZXNode::ZSpider {
579 id: 0,
580 phase: 0.0,
581 arity: 2,
582 });
583
584 if let Some(&prev_node) = qubit_wires.get(&qubit) {
585 diagram.add_edge(prev_node, identity_node, false);
586 }
587 qubit_wires.insert(qubit, identity_node);
588 }
589 }
590 }
591
592 Ok(())
593 }
594
595 fn extract_rotation_angle(&self, gate: &dyn GateOp) -> f64 {
602 use quantrs2_core::gate::single::{Phase, RotationX, RotationY, RotationZ};
603
604 let any = gate.as_any();
605 if let Some(g) = any.downcast_ref::<RotationZ>() {
606 g.theta
607 } else if let Some(g) = any.downcast_ref::<RotationX>() {
608 g.theta
609 } else if let Some(g) = any.downcast_ref::<RotationY>() {
610 g.theta
611 } else if any.downcast_ref::<Phase>().is_some() {
612 PI / 2.0
614 } else {
615 0.0
616 }
617 }
618
619 pub fn optimize_circuit<const N: usize>(
630 &self,
631 circuit: &Circuit<N>,
632 ) -> QuantRS2Result<OptimizedZXResult<N>> {
633 let mut diagram = self.circuit_to_zx(circuit)?;
635
636 let optimization_result = diagram.optimize();
638
639 let optimized_circuit = self.zx_to_circuit(&diagram)?;
642
643 Ok(OptimizedZXResult {
644 original_circuit: circuit.clone(),
645 optimized_circuit,
646 diagram,
647 optimization_stats: optimization_result,
648 })
649 }
650
651 fn zx_to_circuit<const N: usize>(&self, diagram: &ZXDiagram) -> QuantRS2Result<Circuit<N>> {
667 let mut circuit = Circuit::<N>::new();
668
669 for qubit in 0..N as u32 {
670 let Some(&input_id) = diagram.inputs.get(&qubit) else {
671 continue;
672 };
673 let Some(&output_id) = diagram.outputs.get(&qubit) else {
674 continue;
675 };
676
677 let mut prev = input_id;
679 let mut current_neighbors = diagram.neighbors(input_id).to_vec();
680 let mut current = match current_neighbors.as_slice() {
682 [next] => *next,
683 [] => continue, _ => {
685 return Err(QuantRS2Error::UnsupportedOperation(format!(
686 "ZX extraction: input boundary for qubit {qubit} has degree \
687 {} (expected 1); entangled diagrams are not supported",
688 current_neighbors.len()
689 )))
690 }
691 };
692
693 let mut guard = 0usize;
694 let node_budget = diagram.nodes.len() + 1;
695 while current != output_id {
696 guard += 1;
697 if guard > node_budget {
698 return Err(QuantRS2Error::ComputationError(
699 "ZX extraction: wire traversal did not terminate (cycle in diagram)"
700 .to_string(),
701 ));
702 }
703
704 let node = diagram.nodes.get(¤t).ok_or_else(|| {
705 QuantRS2Error::ComputationError(format!(
706 "ZX extraction: dangling node reference {current}"
707 ))
708 })?;
709 current_neighbors = diagram.neighbors(current).to_vec();
710
711 if current_neighbors.len() != 2 {
714 return Err(QuantRS2Error::UnsupportedOperation(format!(
715 "ZX extraction: node {current} on qubit {qubit} has degree {} \
716 (expected 2); entangling structure cannot be extracted by the \
717 linear-wire extractor",
718 current_neighbors.len()
719 )));
720 }
721
722 let target = QubitId(qubit);
724 match node {
725 ZXNode::ZSpider { phase, .. } => {
726 emit_phase_gate(&mut circuit, target, *phase, true)?;
727 }
728 ZXNode::XSpider { phase, .. } => {
729 emit_phase_gate(&mut circuit, target, *phase, false)?;
730 }
731 ZXNode::Hadamard { .. } => {
732 circuit.h(target)?;
733 }
734 ZXNode::Input { .. } | ZXNode::Output { .. } => {
735 return Err(QuantRS2Error::ComputationError(format!(
736 "ZX extraction: unexpected boundary node {current} in wire interior"
737 )));
738 }
739 }
740
741 let next = if current_neighbors[0] == prev {
743 current_neighbors[1]
744 } else {
745 current_neighbors[0]
746 };
747 prev = current;
748 current = next;
749 }
750 }
751
752 Ok(circuit)
753 }
754}
755
756fn emit_phase_gate<const N: usize>(
762 circuit: &mut Circuit<N>,
763 target: QubitId,
764 phase: f64,
765 is_z: bool,
766) -> QuantRS2Result<()> {
767 let two_pi = 2.0 * PI;
768 let phase = phase.rem_euclid(two_pi);
770 if phase.abs() < 1e-10 || (phase - two_pi).abs() < 1e-10 {
771 return Ok(()); }
773
774 if (phase - PI).abs() < 1e-10 {
775 if is_z {
777 circuit.z(target)?;
778 } else {
779 circuit.x(target)?;
780 }
781 } else if is_z {
782 circuit.rz(target, phase)?;
783 } else {
784 circuit.rx(target, phase)?;
785 }
786 Ok(())
787}
788
789#[derive(Debug)]
791pub struct OptimizedZXResult<const N: usize> {
792 pub original_circuit: Circuit<N>,
793 pub optimized_circuit: Circuit<N>,
794 pub diagram: ZXDiagram,
795 pub optimization_stats: ZXOptimizationResult,
796}
797
798#[cfg(test)]
799mod tests {
800 use super::*;
801 use quantrs2_core::gate::multi::CNOT;
802 use quantrs2_core::gate::single::Hadamard;
803
804 #[test]
805 fn test_zx_diagram_creation() {
806 let mut diagram = ZXDiagram::new();
807 diagram.initialize_boundaries(2);
808
809 assert_eq!(diagram.inputs.len(), 2);
810 assert_eq!(diagram.outputs.len(), 2);
811 }
812
813 #[test]
814 fn test_spider_fusion() {
815 let mut diagram = ZXDiagram::new();
816
817 let spider1 = diagram.add_node(ZXNode::ZSpider {
819 id: 0,
820 phase: PI / 4.0,
821 arity: 2,
822 });
823 let spider2 = diagram.add_node(ZXNode::ZSpider {
824 id: 0,
825 phase: PI / 8.0,
826 arity: 2,
827 });
828
829 diagram.add_edge(spider1, spider2, false);
831
832 let changed = diagram.spider_fusion();
834 assert!(changed);
835
836 assert_eq!(diagram.nodes.len(), 1);
838
839 let remaining_node = diagram
841 .nodes
842 .values()
843 .next()
844 .expect("Expected at least one remaining node after fusion");
845 assert!((remaining_node.phase() - (PI / 4.0 + PI / 8.0)).abs() < 1e-10);
846 }
847
848 #[test]
849 fn test_identity_removal() {
850 let mut diagram = ZXDiagram::new();
851
852 let identity = diagram.add_node(ZXNode::ZSpider {
854 id: 0,
855 phase: 0.0,
856 arity: 2,
857 });
858
859 let node1 = diagram.add_node(ZXNode::ZSpider {
861 id: 0,
862 phase: PI / 4.0,
863 arity: 2,
864 });
865 let node2 = diagram.add_node(ZXNode::ZSpider {
866 id: 0,
867 phase: PI / 2.0,
868 arity: 2,
869 });
870
871 diagram.add_edge(node1, identity, false);
873 diagram.add_edge(identity, node2, false);
874
875 let initial_count = diagram.nodes.len();
876 let changed = diagram.identity_removal();
877
878 assert!(changed);
879 assert_eq!(diagram.nodes.len(), initial_count - 1);
880 }
881
882 #[test]
883 fn test_circuit_to_zx_conversion() {
884 let optimizer = ZXOptimizer::new();
885
886 let mut circuit = Circuit::<2>::new();
887 circuit
888 .add_gate(Hadamard { target: QubitId(0) })
889 .expect("Failed to add Hadamard gate");
890 circuit
891 .add_gate(CNOT {
892 control: QubitId(0),
893 target: QubitId(1),
894 })
895 .expect("Failed to add CNOT gate");
896
897 let diagram = optimizer
898 .circuit_to_zx(&circuit)
899 .expect("Failed to convert circuit to ZX diagram");
900
901 assert!(diagram.nodes.len() >= 4); assert!(!diagram.edges.is_empty());
904 }
905
906 #[test]
907 fn test_zx_optimization() {
908 let optimizer = ZXOptimizer::new();
909
910 let mut circuit = Circuit::<1>::new();
911 circuit
912 .add_gate(Hadamard { target: QubitId(0) })
913 .expect("Failed to add first Hadamard gate");
914 circuit
915 .add_gate(Hadamard { target: QubitId(0) })
916 .expect("Failed to add second Hadamard gate"); let result = optimizer
919 .optimize_circuit(&circuit)
920 .expect("Failed to optimize circuit");
921
922 assert!(
923 result.optimization_stats.final_node_count
924 <= result.optimization_stats.initial_node_count
925 );
926 }
927
928 #[test]
931 fn test_extract_rotation_angle_reads_real_theta() {
932 use quantrs2_core::gate::single::{RotationX, RotationY, RotationZ};
933 let optimizer = ZXOptimizer::new();
934
935 let rz = RotationZ {
936 target: QubitId(0),
937 theta: 0.123,
938 };
939 assert!((optimizer.extract_rotation_angle(&rz) - 0.123).abs() < 1e-12);
940
941 let rx = RotationX {
942 target: QubitId(0),
943 theta: 1.75,
944 };
945 assert!((optimizer.extract_rotation_angle(&rx) - 1.75).abs() < 1e-12);
946
947 let ry = RotationY {
948 target: QubitId(0),
949 theta: -0.6,
950 };
951 assert!((optimizer.extract_rotation_angle(&ry) + 0.6).abs() < 1e-12);
952
953 let h = Hadamard { target: QubitId(0) };
955 assert!(optimizer.extract_rotation_angle(&h).abs() < 1e-12);
956 }
957
958 #[test]
966 fn test_zx_to_circuit_extracts_single_qubit_chain() {
967 use quantrs2_core::gate::single::{PauliZ, RotationZ};
968 let optimizer = ZXOptimizer::new();
969
970 let mut circuit = Circuit::<1>::new();
971 circuit
972 .add_gate(Hadamard { target: QubitId(0) })
973 .expect("h");
974 circuit
975 .add_gate(RotationZ {
976 target: QubitId(0),
977 theta: 0.4,
978 })
979 .expect("rz");
980 circuit.add_gate(PauliZ { target: QubitId(0) }).expect("z");
981
982 let diagram = optimizer.circuit_to_zx(&circuit).expect("to zx");
983 let extracted: Circuit<1> = optimizer.zx_to_circuit(&diagram).expect("extract");
984
985 let names: Vec<&str> = extracted.gates().iter().map(|g| g.name()).collect();
986 assert_eq!(names, vec!["H", "RZ", "Z"], "got {names:?}");
988
989 let rz = extracted
992 .gates()
993 .iter()
994 .find(|g| g.name() == "RZ")
995 .expect("rz present");
996 let rz_concrete = rz
997 .as_any()
998 .downcast_ref::<RotationZ>()
999 .expect("downcast RZ");
1000 assert!(
1001 (rz_concrete.theta - 0.4).abs() < 1e-10,
1002 "RZ angle {}",
1003 rz_concrete.theta
1004 );
1005 }
1006
1007 #[test]
1011 fn test_zx_to_circuit_errors_on_entangling_diagram() {
1012 let optimizer = ZXOptimizer::new();
1013
1014 let mut circuit = Circuit::<2>::new();
1015 circuit
1016 .add_gate(CNOT {
1017 control: QubitId(0),
1018 target: QubitId(1),
1019 })
1020 .expect("cnot");
1021
1022 let result = optimizer.optimize_circuit(&circuit);
1023 assert!(
1024 result.is_err(),
1025 "entangling diagram extraction must error, not fabricate an empty circuit"
1026 );
1027 }
1028
1029 #[test]
1032 fn test_zx_to_circuit_identity_is_empty() {
1033 let optimizer = ZXOptimizer::new();
1034
1035 let mut circuit = Circuit::<1>::new();
1036 circuit
1037 .add_gate(Hadamard { target: QubitId(0) })
1038 .expect("h1");
1039 circuit
1040 .add_gate(Hadamard { target: QubitId(0) })
1041 .expect("h2");
1042
1043 let result = optimizer
1044 .optimize_circuit(&circuit)
1045 .expect("optimize identity");
1046 assert_eq!(result.optimized_circuit.gates().len(), 0);
1047 }
1048}