1use crate::{
13 error::{QuantRS2Error, QuantRS2Result},
14 gate::{single::*, GateOp},
15 qubit::QubitId,
16};
17use rustc_hash::FxHashMap;
18use std::collections::{HashSet, VecDeque};
19use std::f64::consts::PI;
20
21use std::fmt::Write;
22#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
24pub enum SpiderType {
25 Z,
27 X,
29 Boundary,
31}
32
33#[derive(Debug, Clone)]
35pub struct Spider {
36 pub id: usize,
38 pub spider_type: SpiderType,
40 pub phase: f64,
42 pub qubit: Option<QubitId>,
44}
45
46impl Spider {
47 pub const fn new(id: usize, spider_type: SpiderType, phase: f64) -> Self {
49 Self {
50 id,
51 spider_type,
52 phase,
53 qubit: None,
54 }
55 }
56
57 pub const fn boundary(id: usize, qubit: QubitId) -> Self {
59 Self {
60 id,
61 spider_type: SpiderType::Boundary,
62 phase: 0.0,
63 qubit: Some(qubit),
64 }
65 }
66
67 pub fn is_clifford(&self, tolerance: f64) -> bool {
69 let normalized_phase = self.phase % (2.0 * PI);
70 let multiples_of_pi_2 = [0.0, PI / 2.0, PI, 3.0 * PI / 2.0];
71
72 multiples_of_pi_2.iter().any(|&p| {
73 (normalized_phase - p).abs() < tolerance
74 || 2.0f64.mul_add(PI, normalized_phase - p).abs() < tolerance
75 })
76 }
77
78 pub fn is_pauli(&self, tolerance: f64) -> bool {
80 let normalized_phase = self.phase % (2.0 * PI);
81 normalized_phase.abs() < tolerance
82 || (normalized_phase - PI).abs() < tolerance
83 || 2.0f64.mul_add(-PI, normalized_phase).abs() < tolerance
84 }
85}
86
87#[derive(Debug, Clone, Copy, PartialEq, Eq)]
89pub enum EdgeType {
90 Regular,
92 Hadamard,
94}
95
96#[derive(Debug, Clone)]
98pub struct Edge {
99 pub source: usize,
101 pub target: usize,
103 pub edge_type: EdgeType,
105}
106
107#[derive(Debug, Clone)]
109pub struct ZXDiagram {
110 pub spiders: FxHashMap<usize, Spider>,
112 pub adjacency: FxHashMap<usize, Vec<(usize, EdgeType)>>,
114 pub inputs: Vec<usize>,
116 pub outputs: Vec<usize>,
118 next_id: usize,
120}
121
122impl ZXDiagram {
123 pub fn new() -> Self {
125 Self {
126 spiders: FxHashMap::default(),
127 adjacency: FxHashMap::default(),
128 inputs: Vec::new(),
129 outputs: Vec::new(),
130 next_id: 0,
131 }
132 }
133
134 pub fn add_spider(&mut self, spider_type: SpiderType, phase: f64) -> usize {
136 let id = self.next_id;
137 self.next_id += 1;
138
139 let spider = Spider::new(id, spider_type, phase);
140 self.spiders.insert(id, spider);
141 self.adjacency.insert(id, Vec::new());
142
143 id
144 }
145
146 pub fn add_boundary(&mut self, qubit: QubitId, is_input: bool) -> usize {
148 let id = self.next_id;
149 self.next_id += 1;
150
151 let spider = Spider::boundary(id, qubit);
152 self.spiders.insert(id, spider);
153 self.adjacency.insert(id, Vec::new());
154
155 if is_input {
156 self.inputs.push(id);
157 } else {
158 self.outputs.push(id);
159 }
160
161 id
162 }
163
164 pub fn add_edge(&mut self, source: usize, target: usize, edge_type: EdgeType) {
166 if let Some(adj) = self.adjacency.get_mut(&source) {
167 adj.push((target, edge_type));
168 }
169 if let Some(adj) = self.adjacency.get_mut(&target) {
170 adj.push((source, edge_type));
171 }
172 }
173
174 pub fn remove_edge(&mut self, source: usize, target: usize) {
176 if let Some(adj) = self.adjacency.get_mut(&source) {
177 adj.retain(|(t, _)| *t != target);
178 }
179 if let Some(adj) = self.adjacency.get_mut(&target) {
180 adj.retain(|(s, _)| *s != source);
181 }
182 }
183
184 pub fn neighbors(&self, spider_id: usize) -> Vec<(usize, EdgeType)> {
186 self.adjacency.get(&spider_id).cloned().unwrap_or_default()
187 }
188
189 pub fn remove_spider(&mut self, spider_id: usize) {
191 self.spiders.remove(&spider_id);
193
194 let neighbors = self.neighbors(spider_id);
196 for (neighbor, _) in neighbors {
197 self.remove_edge(spider_id, neighbor);
198 }
199
200 self.adjacency.remove(&spider_id);
202
203 self.inputs.retain(|&id| id != spider_id);
205 self.outputs.retain(|&id| id != spider_id);
206 }
207
208 pub fn degree(&self, spider_id: usize) -> usize {
210 self.adjacency.get(&spider_id).map_or(0, |adj| adj.len())
211 }
212
213 pub fn spider_fusion(&mut self, spider1: usize, spider2: usize) -> QuantRS2Result<()> {
215 let s1 = self
216 .spiders
217 .get(&spider1)
218 .ok_or_else(|| QuantRS2Error::InvalidInput("Spider 1 not found".to_string()))?;
219 let s2 = self
220 .spiders
221 .get(&spider2)
222 .ok_or_else(|| QuantRS2Error::InvalidInput("Spider 2 not found".to_string()))?;
223
224 if s1.spider_type != s2.spider_type || s1.spider_type == SpiderType::Boundary {
226 return Err(QuantRS2Error::InvalidInput(
227 "Can only fuse spiders of the same non-boundary type".to_string(),
228 ));
229 }
230
231 let connected = self.neighbors(spider1).iter().any(|(id, _)| *id == spider2);
233
234 if !connected {
235 return Err(QuantRS2Error::InvalidInput(
236 "Spiders must be connected".to_string(),
237 ));
238 }
239
240 let new_phase = s1.phase + s2.phase;
242
243 if let Some(s1_mut) = self.spiders.get_mut(&spider1) {
245 s1_mut.phase = new_phase;
246 }
247
248 let spider2_neighbors = self.neighbors(spider2);
250 for (neighbor, edge_type) in spider2_neighbors {
251 if neighbor != spider1 {
252 self.remove_edge(spider2, neighbor);
253 self.add_edge(spider1, neighbor, edge_type);
254 }
255 }
256
257 self.remove_spider(spider2);
259
260 Ok(())
261 }
262
263 pub fn remove_identities(&mut self) -> usize {
265 let mut removed = 0;
266 let mut to_remove = Vec::new();
267
268 for (&id, spider) in &self.spiders {
269 if spider.spider_type != SpiderType::Boundary
270 && spider.phase.abs() < 1e-10
271 && self.degree(id) == 2
272 {
273 to_remove.push(id);
274 }
275 }
276
277 for id in to_remove {
278 let neighbors = self.neighbors(id);
279 if neighbors.len() == 2 {
280 let (n1, e1) = neighbors[0];
281 let (n2, e2) = neighbors[1];
282
283 let new_edge_type = match (e1, e2) {
285 (EdgeType::Regular, EdgeType::Regular)
286 | (EdgeType::Hadamard, EdgeType::Hadamard) => EdgeType::Regular,
287 _ => EdgeType::Hadamard,
288 };
289
290 self.remove_spider(id);
291 self.add_edge(n1, n2, new_edge_type);
292 removed += 1;
293 }
294 }
295
296 removed
297 }
298
299 pub fn color_change(&mut self, spider_id: usize) -> QuantRS2Result<()> {
301 let spider = self
302 .spiders
303 .get(&spider_id)
304 .ok_or_else(|| QuantRS2Error::InvalidInput("Spider not found".to_string()))?
305 .clone();
306
307 if spider.spider_type == SpiderType::Boundary {
308 return Err(QuantRS2Error::InvalidInput(
309 "Cannot change color of boundary spider".to_string(),
310 ));
311 }
312
313 let new_type = match spider.spider_type {
315 SpiderType::Z => SpiderType::X,
316 SpiderType::X => SpiderType::Z,
317 SpiderType::Boundary => return Ok(()),
318 };
319
320 if let Some(s) = self.spiders.get_mut(&spider_id) {
321 s.spider_type = new_type;
322 }
323
324 let neighbors = self.neighbors(spider_id);
326 for (neighbor, edge_type) in neighbors {
327 self.remove_edge(spider_id, neighbor);
328 let new_edge_type = match edge_type {
329 EdgeType::Regular => EdgeType::Hadamard,
330 EdgeType::Hadamard => EdgeType::Regular,
331 };
332 self.add_edge(spider_id, neighbor, new_edge_type);
333 }
334
335 Ok(())
336 }
337
338 pub fn pi_copy(&mut self, spider_id: usize) -> QuantRS2Result<Vec<usize>> {
340 let spider = self
341 .spiders
342 .get(&spider_id)
343 .ok_or_else(|| QuantRS2Error::InvalidInput("Spider not found".to_string()))?
344 .clone();
345
346 if !spider.is_pauli(1e-10) {
347 return Err(QuantRS2Error::InvalidInput(
348 "Spider must be Pauli (phase 0 or π)".to_string(),
349 ));
350 }
351
352 let neighbors = self.neighbors(spider_id);
353 let mut new_spiders = Vec::new();
354
355 for (neighbor, edge_type) in &neighbors[1..] {
357 let new_id = self.add_spider(spider.spider_type, spider.phase);
358 new_spiders.push(new_id);
359
360 self.remove_edge(spider_id, *neighbor);
362 self.add_edge(new_id, *neighbor, *edge_type);
363
364 if let Some((first_neighbor, first_edge_type)) = neighbors.first() {
366 self.add_edge(new_id, *first_neighbor, *first_edge_type);
367 }
368 }
369
370 Ok(new_spiders)
371 }
372
373 pub fn bialgebra(&mut self, z_spider: usize, x_spider: usize) -> QuantRS2Result<()> {
375 let z = self
376 .spiders
377 .get(&z_spider)
378 .ok_or_else(|| QuantRS2Error::InvalidInput("Z-spider not found".to_string()))?;
379 let x = self
380 .spiders
381 .get(&x_spider)
382 .ok_or_else(|| QuantRS2Error::InvalidInput("X-spider not found".to_string()))?;
383
384 if z.spider_type != SpiderType::Z || x.spider_type != SpiderType::X {
386 return Err(QuantRS2Error::InvalidInput(
387 "Need one Z-spider and one X-spider".to_string(),
388 ));
389 }
390
391 let connected = self
393 .neighbors(z_spider)
394 .iter()
395 .any(|(id, _)| *id == x_spider);
396
397 if !connected {
398 return Err(QuantRS2Error::InvalidInput(
399 "Spiders must be connected".to_string(),
400 ));
401 }
402
403 let z_neighbors: Vec<_> = self
405 .neighbors(z_spider)
406 .into_iter()
407 .filter(|(id, _)| *id != x_spider)
408 .collect();
409 let x_neighbors: Vec<_> = self
410 .neighbors(x_spider)
411 .into_iter()
412 .filter(|(id, _)| *id != z_spider)
413 .collect();
414
415 self.remove_spider(z_spider);
417 self.remove_spider(x_spider);
418
419 for (z_n, z_edge) in &z_neighbors {
421 for (x_n, x_edge) in &x_neighbors {
422 let edge_type = match (z_edge, x_edge) {
424 (EdgeType::Regular, EdgeType::Regular)
425 | (EdgeType::Hadamard, EdgeType::Hadamard) => EdgeType::Regular,
426 _ => EdgeType::Hadamard,
427 };
428 self.add_edge(*z_n, *x_n, edge_type);
429 }
430 }
431
432 Ok(())
433 }
434
435 pub fn simplify(&mut self, max_iterations: usize) -> usize {
437 let mut total_rewrites = 0;
438
439 for _ in 0..max_iterations {
440 let mut rewrites = 0;
441
442 rewrites += self.remove_identities();
444
445 rewrites += self.apply_spider_fusion();
447
448 rewrites += self.apply_pivot_rules();
450 rewrites += self.apply_local_complementation();
451 rewrites += self.apply_stabilizer_decomposition();
452
453 rewrites += self.optimize_clifford_subcircuits();
455
456 total_rewrites += rewrites;
457 if rewrites == 0 {
458 break;
459 }
460 }
461
462 total_rewrites
463 }
464
465 fn apply_spider_fusion(&mut self) -> usize {
467 let mut rewrites = 0;
468 let spider_ids: Vec<_> = self.spiders.keys().copied().collect();
469
470 for i in 0..spider_ids.len() {
471 for j in i + 1..spider_ids.len() {
472 let id1 = spider_ids[i];
473 let id2 = spider_ids[j];
474
475 if self.spiders.contains_key(&id1)
477 && self.spiders.contains_key(&id2)
478 && self.spider_fusion(id1, id2) == Ok(())
479 {
480 rewrites += 1;
481 break;
482 }
483 }
484 if rewrites > 0 {
485 break;
486 }
487 }
488
489 rewrites
490 }
491
492 fn apply_pivot_rules(&mut self) -> usize {
494 let mut rewrites = 0;
495 let spider_ids: Vec<_> = self.spiders.keys().copied().collect();
496
497 for &spider_id in &spider_ids {
498 if !self.spiders.contains_key(&spider_id) {
499 continue;
500 }
501
502 let spider = self.spiders[&spider_id].clone();
503
504 if spider.spider_type == SpiderType::Z
506 && self.is_even_multiple_of_pi(spider.phase, 1e-10)
507 && self.degree(spider_id) >= 2
508 && self.pivot_around_spider(spider_id) == Ok(())
509 {
510 rewrites += 1;
511 break;
512 }
513 }
514
515 rewrites
516 }
517
518 fn apply_local_complementation(&mut self) -> usize {
520 let mut rewrites = 0;
521 let spider_ids: Vec<_> = self.spiders.keys().copied().collect();
522
523 for &spider_id in &spider_ids {
524 if !self.spiders.contains_key(&spider_id) {
525 continue;
526 }
527
528 let spider = self.spiders[&spider_id].clone();
529
530 if spider.spider_type == SpiderType::X
532 && self.is_odd_multiple_of_pi(spider.phase, 1e-10)
533 && self.degree(spider_id) >= 3
534 && self.local_complement_around_spider(spider_id) == Ok(())
535 {
536 rewrites += 1;
537 break;
538 }
539 }
540
541 rewrites
542 }
543
544 fn apply_stabilizer_decomposition(&mut self) -> usize {
546 let mut rewrites = 0;
547
548 let clifford_components = self.find_clifford_components();
550
551 for component in clifford_components {
552 if component.len() > 2 && self.decompose_clifford_component(&component) == Ok(()) {
553 rewrites += 1;
554 }
555 }
556
557 rewrites
558 }
559
560 fn optimize_clifford_subcircuits(&self) -> usize {
562 let mut rewrites = 0;
563
564 let clifford_regions = self.identify_clifford_regions();
566
567 for region in clifford_regions {
568 let optimized_size = self.optimize_clifford_region(®ion);
569 if optimized_size < region.len() {
570 rewrites += region.len() - optimized_size;
571 }
572 }
573
574 rewrites
575 }
576
577 fn is_even_multiple_of_pi(&self, phase: f64, tolerance: f64) -> bool {
579 let normalized = (phase / PI) % 2.0;
580 normalized.abs() < tolerance || (normalized - 2.0).abs() < tolerance
581 }
582
583 fn is_odd_multiple_of_pi(&self, phase: f64, tolerance: f64) -> bool {
585 let normalized = (phase / PI) % 2.0;
586 (normalized - 1.0).abs() < tolerance
587 }
588
589 fn pivot_around_spider(&mut self, spider_id: usize) -> QuantRS2Result<()> {
591 let neighbors = self.neighbors(spider_id);
592
593 for i in 0..neighbors.len() {
595 for j in i + 1..neighbors.len() {
596 let (n1, _) = neighbors[i];
597 let (n2, _) = neighbors[j];
598
599 let existing_edge = self.neighbors(n1).iter().any(|(id, _)| *id == n2);
601
602 if existing_edge {
603 self.remove_edge(n1, n2);
605 } else {
606 self.add_edge(n1, n2, EdgeType::Regular);
608 }
609 }
610 }
611
612 self.remove_spider(spider_id);
614
615 Ok(())
616 }
617
618 fn local_complement_around_spider(&mut self, spider_id: usize) -> QuantRS2Result<()> {
620 let neighbors = self.neighbors(spider_id);
621
622 for i in 0..neighbors.len() {
624 for j in i + 1..neighbors.len() {
625 let (n1, _) = neighbors[i];
626 let (n2, _) = neighbors[j];
627
628 let existing_edge = self
629 .neighbors(n1)
630 .iter()
631 .find(|(id, _edge_type)| *id == n2)
632 .map(|(_, edge_type)| *edge_type);
633
634 match existing_edge {
635 Some(EdgeType::Regular) => {
636 self.remove_edge(n1, n2);
637 self.add_edge(n1, n2, EdgeType::Hadamard);
638 }
639 Some(EdgeType::Hadamard) => {
640 self.remove_edge(n1, n2);
641 self.add_edge(n1, n2, EdgeType::Regular);
642 }
643 None => {
644 self.add_edge(n1, n2, EdgeType::Regular);
645 }
646 }
647 }
648 }
649
650 if let Some(spider) = self.spiders.get_mut(&spider_id) {
652 spider.phase += PI;
653 }
654
655 Ok(())
656 }
657
658 fn find_clifford_components(&self) -> Vec<Vec<usize>> {
660 let mut visited = HashSet::new();
661 let mut components = Vec::new();
662
663 for &spider_id in self.spiders.keys() {
664 if !visited.contains(&spider_id) {
665 let spider = &self.spiders[&spider_id];
666
667 if spider.spider_type != SpiderType::Boundary && spider.is_clifford(1e-10) {
668 let component = self.dfs_clifford_component(spider_id, &mut visited);
669 if component.len() > 1 {
670 components.push(component);
671 }
672 }
673 }
674 }
675
676 components
677 }
678
679 fn dfs_clifford_component(&self, start: usize, visited: &mut HashSet<usize>) -> Vec<usize> {
681 let mut component = Vec::new();
682 let mut stack = vec![start];
683
684 while let Some(spider_id) = stack.pop() {
685 if visited.contains(&spider_id) {
686 continue;
687 }
688
689 visited.insert(spider_id);
690
691 if let Some(spider) = self.spiders.get(&spider_id) {
692 if spider.spider_type != SpiderType::Boundary && spider.is_clifford(1e-10) {
693 component.push(spider_id);
694
695 for (neighbor, _) in self.neighbors(spider_id) {
697 if !visited.contains(&neighbor) {
698 if let Some(neighbor_spider) = self.spiders.get(&neighbor) {
699 if neighbor_spider.spider_type != SpiderType::Boundary
700 && neighbor_spider.is_clifford(1e-10)
701 {
702 stack.push(neighbor);
703 }
704 }
705 }
706 }
707 }
708 }
709 }
710
711 component
712 }
713
714 fn decompose_clifford_component(&mut self, component: &[usize]) -> QuantRS2Result<()> {
716 for &spider_id in component {
725 if let Some(spider) = self.spiders.get(&spider_id) {
726 if spider.phase.abs() < 1e-10 && self.degree(spider_id) == 2 {
727 let neighbors = self.neighbors(spider_id);
728 if neighbors.len() == 2 {
729 let (n1, e1) = neighbors[0];
730 let (n2, e2) = neighbors[1];
731
732 let new_edge_type = match (e1, e2) {
733 (EdgeType::Regular, EdgeType::Regular)
734 | (EdgeType::Hadamard, EdgeType::Hadamard) => EdgeType::Regular,
735 _ => EdgeType::Hadamard,
736 };
737
738 self.remove_spider(spider_id);
739 self.add_edge(n1, n2, new_edge_type);
740 return Ok(());
741 }
742 }
743 }
744 }
745
746 Ok(())
747 }
748
749 fn identify_clifford_regions(&self) -> Vec<Vec<usize>> {
751 let mut regions = Vec::new();
752 let mut visited = HashSet::new();
753
754 for &spider_id in self.spiders.keys() {
755 if !visited.contains(&spider_id) {
756 if let Some(spider) = self.spiders.get(&spider_id) {
757 if spider.spider_type != SpiderType::Boundary && spider.is_clifford(1e-10) {
758 let region = self.expand_clifford_region(spider_id, &mut visited);
759 if region.len() >= 2 {
760 regions.push(region);
761 }
762 }
763 }
764 }
765 }
766
767 regions
768 }
769
770 fn expand_clifford_region(&self, start: usize, visited: &mut HashSet<usize>) -> Vec<usize> {
772 let mut region = Vec::new();
773 let mut queue = VecDeque::new();
774
775 queue.push_back(start);
776 visited.insert(start);
777
778 while let Some(spider_id) = queue.pop_front() {
779 if let Some(spider) = self.spiders.get(&spider_id) {
780 if spider.spider_type != SpiderType::Boundary && spider.is_clifford(1e-10) {
781 region.push(spider_id);
782
783 for (neighbor, _) in self.neighbors(spider_id) {
785 if let Some(neighbor_spider) = self.spiders.get(&neighbor) {
786 if neighbor_spider.spider_type != SpiderType::Boundary
787 && neighbor_spider.is_clifford(1e-10)
788 && visited.insert(neighbor)
789 {
790 queue.push_back(neighbor);
791 }
792 }
793 }
794 }
795 }
796 }
797
798 region
799 }
800
801 const fn optimize_clifford_region(&self, region: &[usize]) -> usize {
803 region.len()
812 }
813
814 pub fn to_dot(&self) -> String {
816 let mut dot = String::from("graph ZX {\n");
817 dot.push_str(" rankdir=LR;\n");
818
819 for (id, spider) in &self.spiders {
821 let color = match spider.spider_type {
822 SpiderType::Z => "green",
823 SpiderType::X => "red",
824 SpiderType::Boundary => "black",
825 };
826 let shape = match spider.spider_type {
827 SpiderType::Boundary => "square",
828 _ => "circle",
829 };
830 let label = match spider.spider_type {
831 SpiderType::Boundary => {
832 if let Some(qubit) = spider.qubit {
833 format!("q{}", qubit.0)
834 } else {
835 String::new()
836 }
837 }
838 _ => {
839 if spider.phase.abs() < 1e-10 {
840 String::new()
841 } else {
842 format!("{:.2}", spider.phase / PI)
843 }
844 }
845 };
846
847 let _ = writeln!(
848 dot,
849 " {id} [label=\"{label}\", color={color}, shape={shape}];"
850 );
851 }
852
853 let mut processed = HashSet::new();
855 for (&source, neighbors) in &self.adjacency {
856 for &(target, edge_type) in neighbors {
857 let key = if source < target {
858 (source, target)
859 } else {
860 (target, source)
861 };
862
863 if processed.insert(key) {
864 let style = match edge_type {
865 EdgeType::Regular => "solid",
866 EdgeType::Hadamard => "dashed",
867 };
868
869 let _ = writeln!(dot, " {source} -- {target} [style={style}];");
870 }
871 }
872 }
873
874 dot.push_str("}\n");
875 dot
876 }
877}
878
879impl Default for ZXDiagram {
880 fn default() -> Self {
881 Self::new()
882 }
883}
884
885#[derive(Debug, Clone)]
887pub struct ZXOptimizer {
888 max_iterations: usize,
890 enable_advanced: bool,
892 verbose: bool,
894 tolerance: f64,
896}
897
898impl Default for ZXOptimizer {
899 fn default() -> Self {
900 Self {
901 max_iterations: 100,
902 enable_advanced: true,
903 verbose: false,
904 tolerance: 1e-10,
905 }
906 }
907}
908
909impl ZXOptimizer {
910 pub const fn new() -> Self {
912 Self {
913 max_iterations: 100,
914 enable_advanced: true,
915 verbose: false,
916 tolerance: 1e-10,
917 }
918 }
919
920 #[must_use]
922 pub const fn with_max_iterations(mut self, max_iterations: usize) -> Self {
923 self.max_iterations = max_iterations;
924 self
925 }
926
927 #[must_use]
929 pub const fn with_advanced(mut self, enable_advanced: bool) -> Self {
930 self.enable_advanced = enable_advanced;
931 self
932 }
933
934 #[must_use]
936 pub const fn with_verbose(mut self, verbose: bool) -> Self {
937 self.verbose = verbose;
938 self
939 }
940
941 #[must_use]
943 pub const fn with_tolerance(mut self, tolerance: f64) -> Self {
944 self.tolerance = tolerance;
945 self
946 }
947
948 pub fn optimize(&self, diagram: &mut ZXDiagram) -> usize {
950 let initial_size = diagram.spiders.len();
951
952 if self.verbose {
953 println!("ZX Optimizer: Starting with {initial_size} spiders");
954 }
955
956 let mut total_rewrites = 0;
957 let mut iteration = 0;
958
959 while iteration < self.max_iterations {
960 let iteration_rewrites = if self.enable_advanced {
961 self.apply_advanced_optimization(diagram)
962 } else {
963 diagram.simplify(1)
964 };
965
966 total_rewrites += iteration_rewrites;
967 iteration += 1;
968
969 if iteration_rewrites == 0 {
970 if self.verbose {
971 println!("ZX Optimizer: Converged after {iteration} iterations");
972 }
973 break;
974 }
975
976 if self.verbose && iteration % 10 == 0 {
977 println!(
978 "ZX Optimizer: Iteration {}, {} spiders remaining",
979 iteration,
980 diagram.spiders.len()
981 );
982 }
983 }
984
985 let final_size = diagram.spiders.len();
986 let reduction = if initial_size > 0 {
987 ((initial_size - final_size) * 100) / initial_size
988 } else {
989 0
990 };
991
992 if self.verbose {
993 println!(
994 "ZX Optimizer: Reduced from {initial_size} to {final_size} spiders ({reduction}% reduction, {total_rewrites} total rewrites)"
995 );
996 }
997
998 total_rewrites
999 }
1000
1001 fn apply_advanced_optimization(&self, diagram: &mut ZXDiagram) -> usize {
1003 let mut rewrites = 0;
1004
1005 rewrites += diagram.remove_identities();
1007 rewrites += diagram.apply_spider_fusion();
1008
1009 rewrites += self.apply_graph_state_optimization(diagram);
1011
1012 rewrites += self.apply_clifford_optimization(diagram);
1014
1015 rewrites += self.apply_phase_polynomial_optimization(diagram);
1017
1018 rewrites += self.apply_routing_optimization(diagram);
1020
1021 rewrites
1022 }
1023
1024 fn apply_graph_state_optimization(&self, diagram: &mut ZXDiagram) -> usize {
1026 let mut rewrites = 0;
1027
1028 rewrites += diagram.apply_pivot_rules();
1030
1031 rewrites += diagram.apply_local_complementation();
1033
1034 rewrites += self.apply_graph_state_decomposition(diagram);
1036
1037 rewrites
1038 }
1039
1040 fn apply_clifford_optimization(&self, diagram: &mut ZXDiagram) -> usize {
1042 let mut rewrites = 0;
1043
1044 rewrites += diagram.apply_stabilizer_decomposition();
1046
1047 rewrites += diagram.optimize_clifford_subcircuits();
1049
1050 rewrites += self.apply_tableau_reduction(diagram);
1052
1053 rewrites
1054 }
1055
1056 fn apply_phase_polynomial_optimization(&self, diagram: &mut ZXDiagram) -> usize {
1058 let mut rewrites = 0;
1059
1060 let phase_polynomials = self.extract_phase_polynomials(diagram);
1062
1063 for polynomial in phase_polynomials {
1065 rewrites += self.optimize_phase_polynomial(diagram, &polynomial);
1066 }
1067
1068 rewrites
1069 }
1070
1071 const fn apply_routing_optimization(&self, diagram: &mut ZXDiagram) -> usize {
1073 let mut rewrites = 0;
1074
1075 rewrites += self.optimize_cnot_routing(diagram);
1077
1078 rewrites += self.apply_commutation_optimization(diagram);
1080
1081 rewrites
1082 }
1083
1084 const fn apply_graph_state_decomposition(&self, _diagram: &mut ZXDiagram) -> usize {
1086 0
1090 }
1091
1092 const fn apply_tableau_reduction(&self, _diagram: &mut ZXDiagram) -> usize {
1094 0
1100 }
1101
1102 fn extract_phase_polynomials(&self, diagram: &ZXDiagram) -> Vec<Vec<usize>> {
1104 let mut polynomials = Vec::new();
1105 let mut visited = HashSet::new();
1106
1107 for &spider_id in diagram.spiders.keys() {
1108 if !visited.contains(&spider_id) {
1109 if let Some(spider) = diagram.spiders.get(&spider_id) {
1110 if spider.spider_type != SpiderType::Boundary
1111 && !spider.is_clifford(self.tolerance)
1112 {
1113 let polynomial =
1114 self.extract_connected_polynomial(diagram, spider_id, &mut visited);
1115 if polynomial.len() > 1 {
1116 polynomials.push(polynomial);
1117 }
1118 }
1119 }
1120 }
1121 }
1122
1123 polynomials
1124 }
1125
1126 fn extract_connected_polynomial(
1128 &self,
1129 diagram: &ZXDiagram,
1130 start: usize,
1131 visited: &mut HashSet<usize>,
1132 ) -> Vec<usize> {
1133 let mut polynomial = Vec::new();
1134 let mut queue = VecDeque::new();
1135
1136 queue.push_back(start);
1137 visited.insert(start);
1138
1139 while let Some(spider_id) = queue.pop_front() {
1140 if let Some(spider) = diagram.spiders.get(&spider_id) {
1141 if spider.spider_type != SpiderType::Boundary {
1142 polynomial.push(spider_id);
1143
1144 for (neighbor, _) in diagram.neighbors(spider_id) {
1146 if let Some(neighbor_spider) = diagram.spiders.get(&neighbor) {
1147 if neighbor_spider.spider_type != SpiderType::Boundary
1148 && !neighbor_spider.is_clifford(self.tolerance)
1149 && visited.insert(neighbor)
1150 {
1151 queue.push_back(neighbor);
1152 }
1153 }
1154 }
1155 }
1156 }
1157 }
1158
1159 polynomial
1160 }
1161
1162 const fn optimize_phase_polynomial(
1164 &self,
1165 _diagram: &mut ZXDiagram,
1166 _polynomial: &[usize],
1167 ) -> usize {
1168 0
1174 }
1175
1176 const fn optimize_cnot_routing(&self, _diagram: &mut ZXDiagram) -> usize {
1178 0
1181 }
1182
1183 const fn apply_commutation_optimization(&self, _diagram: &mut ZXDiagram) -> usize {
1185 0
1188 }
1189
1190 pub fn count_t_gates(&self, diagram: &ZXDiagram) -> usize {
1192 diagram
1193 .spiders
1194 .values()
1195 .filter(|spider| {
1196 spider.spider_type != SpiderType::Boundary && !spider.is_clifford(self.tolerance)
1197 })
1198 .count()
1199 }
1200
1201 pub fn estimate_depth(&self, diagram: &ZXDiagram) -> usize {
1203 let inputs = &diagram.inputs;
1205 let outputs = &diagram.outputs;
1206
1207 if inputs.is_empty() || outputs.is_empty() {
1208 return 0;
1209 }
1210
1211 let mut max_depth = 0;
1213
1214 for &input in inputs {
1215 for &output in outputs {
1216 let depth = self.shortest_path_length(diagram, input, output);
1217 max_depth = max_depth.max(depth);
1218 }
1219 }
1220
1221 max_depth
1222 }
1223
1224 fn shortest_path_length(&self, diagram: &ZXDiagram, start: usize, end: usize) -> usize {
1226 if start == end {
1227 return 0;
1228 }
1229
1230 let mut visited = HashSet::new();
1231 let mut queue = VecDeque::new();
1232
1233 queue.push_back((start, 0));
1234 visited.insert(start);
1235
1236 while let Some((current, depth)) = queue.pop_front() {
1237 if current == end {
1238 return depth;
1239 }
1240
1241 for (neighbor, _) in diagram.neighbors(current) {
1242 if visited.insert(neighbor) {
1243 queue.push_back((neighbor, depth + 1));
1244 }
1245 }
1246 }
1247
1248 usize::MAX }
1250
1251 pub fn optimize_circuit(
1253 &self,
1254 gates: &[Box<dyn GateOp>],
1255 ) -> QuantRS2Result<Vec<Box<dyn GateOp>>> {
1256 let num_qubits = gates
1258 .iter()
1259 .flat_map(|g| g.qubits())
1260 .map(|q| q.0 + 1)
1261 .max()
1262 .unwrap_or(0);
1263
1264 let mut converter = CircuitToZX::new(num_qubits as usize);
1266 for gate in gates {
1267 converter.add_gate(gate.as_ref())?;
1268 }
1269
1270 let mut diagram = converter.into_diagram();
1271
1272 self.optimize(&mut diagram);
1274
1275 Ok(gates.to_vec())
1278 }
1279}
1280
1281pub struct CircuitToZX {
1283 diagram: ZXDiagram,
1284 qubit_wires: FxHashMap<QubitId, (usize, usize)>, }
1286
1287impl CircuitToZX {
1288 pub fn new(num_qubits: usize) -> Self {
1290 let mut diagram = ZXDiagram::new();
1291 let mut qubit_wires = FxHashMap::default();
1292
1293 for i in 0..num_qubits {
1295 let qubit = QubitId(i as u32);
1296 let input = diagram.add_boundary(qubit, true);
1297 let output = diagram.add_boundary(qubit, false);
1298 qubit_wires.insert(qubit, (input, output));
1299 }
1300
1301 Self {
1302 diagram,
1303 qubit_wires,
1304 }
1305 }
1306
1307 pub fn add_gate(&mut self, gate: &dyn GateOp) -> QuantRS2Result<()> {
1309 let qubits = gate.qubits();
1310
1311 match gate.name() {
1312 "H" => self.add_hadamard(qubits[0]),
1313 "X" => self.add_pauli_x(qubits[0]),
1314 "Y" => self.add_pauli_y(qubits[0]),
1315 "Z" => self.add_pauli_z(qubits[0]),
1316 "S" => self.add_phase(qubits[0], PI / 2.0),
1317 "T" => self.add_phase(qubits[0], PI / 4.0),
1318 "RX" => {
1319 if let Some(rx) = gate.as_any().downcast_ref::<RotationX>() {
1321 self.add_rotation_x(qubits[0], rx.theta)
1322 } else {
1323 Err(QuantRS2Error::InvalidInput("Invalid RX gate".to_string()))
1324 }
1325 }
1326 "RY" => {
1327 if let Some(ry) = gate.as_any().downcast_ref::<RotationY>() {
1328 self.add_rotation_y(qubits[0], ry.theta)
1329 } else {
1330 Err(QuantRS2Error::InvalidInput("Invalid RY gate".to_string()))
1331 }
1332 }
1333 "RZ" => {
1334 if let Some(rz) = gate.as_any().downcast_ref::<RotationZ>() {
1335 self.add_rotation_z(qubits[0], rz.theta)
1336 } else {
1337 Err(QuantRS2Error::InvalidInput("Invalid RZ gate".to_string()))
1338 }
1339 }
1340 "CNOT" => self.add_cnot(qubits[0], qubits[1]),
1341 "CZ" => self.add_cz(qubits[0], qubits[1]),
1342 _ => Err(QuantRS2Error::UnsupportedOperation(format!(
1343 "Gate {} not yet supported in ZX-calculus",
1344 gate.name()
1345 ))),
1346 }
1347 }
1348
1349 fn add_hadamard(&mut self, qubit: QubitId) -> QuantRS2Result<()> {
1351 let (start, end) = *self
1352 .qubit_wires
1353 .get(&qubit)
1354 .ok_or_else(|| QuantRS2Error::InvalidInput("Qubit not found".to_string()))?;
1355
1356 self.diagram.remove_edge(start, end);
1358 self.diagram.add_edge(start, end, EdgeType::Hadamard);
1359
1360 Ok(())
1361 }
1362
1363 fn add_pauli_x(&mut self, qubit: QubitId) -> QuantRS2Result<()> {
1365 self.add_x_spider(qubit, PI)
1366 }
1367
1368 fn add_pauli_y(&mut self, qubit: QubitId) -> QuantRS2Result<()> {
1370 self.add_x_spider(qubit, PI)?;
1372 self.add_z_spider(qubit, PI)
1373 }
1374
1375 fn add_pauli_z(&mut self, qubit: QubitId) -> QuantRS2Result<()> {
1377 self.add_z_spider(qubit, PI)
1378 }
1379
1380 fn add_phase(&mut self, qubit: QubitId, angle: f64) -> QuantRS2Result<()> {
1382 self.add_z_spider(qubit, angle)
1383 }
1384
1385 fn add_rotation_x(&mut self, qubit: QubitId, angle: f64) -> QuantRS2Result<()> {
1387 self.add_x_spider(qubit, angle)
1388 }
1389
1390 fn add_rotation_y(&mut self, qubit: QubitId, angle: f64) -> QuantRS2Result<()> {
1392 self.add_z_spider(qubit, -PI / 2.0)?;
1394 self.add_x_spider(qubit, angle)?;
1395 self.add_z_spider(qubit, PI / 2.0)
1396 }
1397
1398 fn add_rotation_z(&mut self, qubit: QubitId, angle: f64) -> QuantRS2Result<()> {
1400 self.add_z_spider(qubit, angle)
1401 }
1402
1403 fn add_cnot(&mut self, control: QubitId, target: QubitId) -> QuantRS2Result<()> {
1405 let (c_start, c_end) = *self
1406 .qubit_wires
1407 .get(&control)
1408 .ok_or_else(|| QuantRS2Error::InvalidInput("Control qubit not found".to_string()))?;
1409 let (t_start, t_end) = *self
1410 .qubit_wires
1411 .get(&target)
1412 .ok_or_else(|| QuantRS2Error::InvalidInput("Target qubit not found".to_string()))?;
1413
1414 let z_spider = self.diagram.add_spider(SpiderType::Z, 0.0);
1416 let x_spider = self.diagram.add_spider(SpiderType::X, 0.0);
1417
1418 self.diagram.remove_edge(c_start, c_end);
1420 self.diagram.remove_edge(t_start, t_end);
1421
1422 self.diagram.add_edge(c_start, z_spider, EdgeType::Regular);
1424 self.diagram.add_edge(z_spider, c_end, EdgeType::Regular);
1425
1426 self.diagram.add_edge(t_start, x_spider, EdgeType::Regular);
1428 self.diagram.add_edge(x_spider, t_end, EdgeType::Regular);
1429
1430 self.diagram.add_edge(z_spider, x_spider, EdgeType::Regular);
1432
1433 Ok(())
1434 }
1435
1436 fn add_cz(&mut self, qubit1: QubitId, qubit2: QubitId) -> QuantRS2Result<()> {
1438 let (q1_start, q1_end) = *self
1439 .qubit_wires
1440 .get(&qubit1)
1441 .ok_or_else(|| QuantRS2Error::InvalidInput("Qubit 1 not found".to_string()))?;
1442 let (q2_start, q2_end) = *self
1443 .qubit_wires
1444 .get(&qubit2)
1445 .ok_or_else(|| QuantRS2Error::InvalidInput("Qubit 2 not found".to_string()))?;
1446
1447 let z1_spider = self.diagram.add_spider(SpiderType::Z, 0.0);
1449 let z2_spider = self.diagram.add_spider(SpiderType::Z, 0.0);
1450
1451 self.diagram.remove_edge(q1_start, q1_end);
1453 self.diagram.remove_edge(q2_start, q2_end);
1454
1455 self.diagram
1457 .add_edge(q1_start, z1_spider, EdgeType::Regular);
1458 self.diagram.add_edge(z1_spider, q1_end, EdgeType::Regular);
1459
1460 self.diagram
1462 .add_edge(q2_start, z2_spider, EdgeType::Regular);
1463 self.diagram.add_edge(z2_spider, q2_end, EdgeType::Regular);
1464
1465 self.diagram
1467 .add_edge(z1_spider, z2_spider, EdgeType::Hadamard);
1468
1469 Ok(())
1470 }
1471
1472 fn add_z_spider(&mut self, qubit: QubitId, phase: f64) -> QuantRS2Result<()> {
1474 let (start, end) = *self
1475 .qubit_wires
1476 .get(&qubit)
1477 .ok_or_else(|| QuantRS2Error::InvalidInput("Qubit not found".to_string()))?;
1478
1479 let spider = self.diagram.add_spider(SpiderType::Z, phase);
1481
1482 self.diagram.remove_edge(start, end);
1484 self.diagram.add_edge(start, spider, EdgeType::Regular);
1485 self.diagram.add_edge(spider, end, EdgeType::Regular);
1486
1487 self.qubit_wires.insert(qubit, (start, end));
1489
1490 Ok(())
1491 }
1492
1493 fn add_x_spider(&mut self, qubit: QubitId, phase: f64) -> QuantRS2Result<()> {
1495 let (start, end) = *self
1496 .qubit_wires
1497 .get(&qubit)
1498 .ok_or_else(|| QuantRS2Error::InvalidInput("Qubit not found".to_string()))?;
1499
1500 let spider = self.diagram.add_spider(SpiderType::X, phase);
1502
1503 self.diagram.remove_edge(start, end);
1505 self.diagram.add_edge(start, spider, EdgeType::Regular);
1506 self.diagram.add_edge(spider, end, EdgeType::Regular);
1507
1508 self.qubit_wires.insert(qubit, (start, end));
1510
1511 Ok(())
1512 }
1513
1514 pub fn into_diagram(self) -> ZXDiagram {
1516 self.diagram
1517 }
1518}
1519
1520#[cfg(test)]
1521mod tests {
1522 use super::*;
1523 use crate::gate::multi::CNOT;
1524
1525 #[test]
1526 fn test_spider_creation() {
1527 let spider = Spider::new(0, SpiderType::Z, PI / 2.0);
1528 assert_eq!(spider.id, 0);
1529 assert_eq!(spider.spider_type, SpiderType::Z);
1530 assert!((spider.phase - PI / 2.0).abs() < 1e-10);
1531 assert!(spider.is_clifford(1e-10));
1532 assert!(!spider.is_pauli(1e-10));
1533 }
1534
1535 #[test]
1536 fn test_diagram_creation() {
1537 let mut diagram = ZXDiagram::new();
1538
1539 let z_id = diagram.add_spider(SpiderType::Z, 0.0);
1540 let x_id = diagram.add_spider(SpiderType::X, PI);
1541
1542 diagram.add_edge(z_id, x_id, EdgeType::Regular);
1543
1544 assert_eq!(diagram.degree(z_id), 1);
1545 assert_eq!(diagram.degree(x_id), 1);
1546 }
1547
1548 #[test]
1549 fn test_spider_fusion() {
1550 let mut diagram = ZXDiagram::new();
1551
1552 let z1 = diagram.add_spider(SpiderType::Z, PI / 4.0);
1553 let z2 = diagram.add_spider(SpiderType::Z, PI / 4.0);
1554 let boundary = diagram.add_boundary(QubitId(0), true);
1555
1556 diagram.add_edge(z1, z2, EdgeType::Regular);
1557 diagram.add_edge(z2, boundary, EdgeType::Regular);
1558
1559 assert!(diagram.spider_fusion(z1, z2).is_ok());
1560
1561 assert!(!diagram.spiders.contains_key(&z2));
1563 assert_eq!(diagram.spiders[&z1].phase, PI / 2.0);
1564 assert_eq!(diagram.degree(z1), 1); }
1566
1567 #[test]
1568 fn test_identity_removal() {
1569 let mut diagram = ZXDiagram::new();
1570
1571 let b1 = diagram.add_boundary(QubitId(0), true);
1572 let id_spider = diagram.add_spider(SpiderType::Z, 0.0);
1573 let b2 = diagram.add_boundary(QubitId(0), false);
1574
1575 diagram.add_edge(b1, id_spider, EdgeType::Regular);
1576 diagram.add_edge(id_spider, b2, EdgeType::Regular);
1577
1578 let removed = diagram.remove_identities();
1579 assert_eq!(removed, 1);
1580 assert!(!diagram.spiders.contains_key(&id_spider));
1581
1582 assert!(diagram.neighbors(b1).iter().any(|(id, _)| *id == b2));
1584 }
1585
1586 #[test]
1587 fn test_circuit_to_zx_hadamard() {
1588 let mut converter = CircuitToZX::new(1);
1589 let h_gate = Hadamard { target: QubitId(0) };
1590
1591 assert!(converter.add_gate(&h_gate).is_ok());
1592
1593 let diagram = converter.into_diagram();
1594
1595 let has_hadamard = diagram.adjacency.values().any(|neighbors| {
1597 neighbors
1598 .iter()
1599 .any(|(_, edge_type)| *edge_type == EdgeType::Hadamard)
1600 });
1601 assert!(has_hadamard);
1602 }
1603
1604 #[test]
1605 fn test_circuit_to_zx_cnot() {
1606 let mut converter = CircuitToZX::new(2);
1607 let cnot = CNOT {
1608 control: QubitId(0),
1609 target: QubitId(1),
1610 };
1611
1612 assert!(converter.add_gate(&cnot).is_ok());
1613
1614 let diagram = converter.into_diagram();
1615
1616 assert_eq!(diagram.spiders.len(), 6);
1618
1619 let z_count = diagram
1621 .spiders
1622 .values()
1623 .filter(|s| s.spider_type == SpiderType::Z && s.phase.abs() < 1e-10)
1624 .count();
1625 let x_count = diagram
1626 .spiders
1627 .values()
1628 .filter(|s| s.spider_type == SpiderType::X && s.phase.abs() < 1e-10)
1629 .count();
1630
1631 assert_eq!(z_count, 1);
1632 assert_eq!(x_count, 1);
1633 }
1634
1635 #[test]
1636 fn test_zx_optimizer() {
1637 let gates: Vec<Box<dyn GateOp>> = vec![
1638 Box::new(Hadamard { target: QubitId(0) }),
1639 Box::new(PauliZ { target: QubitId(0) }),
1640 Box::new(Hadamard { target: QubitId(0) }),
1641 ];
1642
1643 let optimizer = ZXOptimizer::new();
1644 let result = optimizer.optimize_circuit(&gates);
1645
1646 assert!(result.is_ok());
1647 }
1649
1650 #[test]
1651 fn test_dot_generation() {
1652 let mut diagram = ZXDiagram::new();
1653
1654 let input = diagram.add_boundary(QubitId(0), true);
1655 let z = diagram.add_spider(SpiderType::Z, PI / 2.0);
1656 let x = diagram.add_spider(SpiderType::X, 0.0);
1657 let output = diagram.add_boundary(QubitId(0), false);
1658
1659 diagram.add_edge(input, z, EdgeType::Regular);
1660 diagram.add_edge(z, x, EdgeType::Hadamard);
1661 diagram.add_edge(x, output, EdgeType::Regular);
1662
1663 let dot = diagram.to_dot();
1664 assert!(dot.contains("graph ZX"));
1665 assert!(dot.contains("color=green")); assert!(dot.contains("color=red")); assert!(dot.contains("style=dashed")); }
1669}