1use itertools::{chain, Itertools};
17use serde::{Deserialize, Serialize};
18use std::{
19 collections::BTreeSet,
20 f64::consts::{FRAC_1_SQRT_2, FRAC_PI_2, FRAC_PI_4, FRAC_PI_8, PI},
21};
22
23use crate::{
24 decompose::{self, x::CXMode, Algorithm, AuxMode, DepthMode},
25 error::KetError,
26 ir::gate::QuantumGate::{PauliX, RotationZ},
27 matrix::{Cf64, Matrix},
28};
29
30#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
37pub enum Param {
38 Value(f64),
40 Ref {
46 index: usize,
48 multiplier: f64,
50 value: f64,
52 },
53}
54
55impl Param {
56 #[must_use]
61 pub fn inverse(&self) -> Self {
62 match self {
63 Self::Value(v) => Self::Value(-v),
64 Self::Ref {
65 index,
66 multiplier,
67 value,
68 } => Self::Ref {
69 index: *index,
70 multiplier: -multiplier,
71 value: *value,
72 },
73 }
74 }
75
76 #[must_use]
81 pub fn value(&self) -> f64 {
82 match self {
83 Self::Value(value) => *value,
84 Self::Ref {
85 multiplier, value, ..
86 } => *value * *multiplier,
87 }
88 }
89
90 #[must_use]
95 pub fn set_parameter(&self, parameters: &[f64]) -> Self {
96 match self {
97 Self::Value(_) => *self,
98 Self::Ref {
99 multiplier,
100 value: _,
101 index,
102 } => Self::Value(*multiplier * parameters[*index]),
103 }
104 }
105
106 #[must_use]
113 pub fn add(&self, other: &Self) -> Option<Self> {
114 let (Self::Value(a), Self::Value(b)) = (self, other) else {
115 return None;
116 };
117 Some(Self::Value(a + b))
118 }
119
120 #[must_use]
126 pub fn is_near_zero(&self, epsilon: f64) -> bool {
127 match self {
128 Self::Value(v) => v.abs() <= epsilon,
129 Self::Ref { multiplier, .. } => multiplier.abs() <= epsilon,
130 }
131 }
132}
133
134#[derive(Debug, Clone, Copy, PartialEq, Serialize, Default, Eq, PartialOrd, Ord)]
139pub enum GatePropriety {
140 #[default]
142 Identity,
143 Diagonal,
145 Permutation,
147 Unitary,
149}
150
151impl GatePropriety {
152 pub fn restrict(&mut self, propriety: GatePropriety) {
153 *self = match (*self, propriety) {
154 (GatePropriety::Identity, other) | (other, GatePropriety::Identity) => other,
155 (GatePropriety::Diagonal, GatePropriety::Diagonal) => GatePropriety::Diagonal,
156 (_, GatePropriety::Unitary) | (GatePropriety::Unitary, _) => GatePropriety::Unitary,
157 _ => GatePropriety::Permutation,
158 };
159 }
160
161 pub fn broaden(&mut self, propriety: GatePropriety) {
162 *self = match (*self, propriety) {
163 (GatePropriety::Identity, _) | (_, GatePropriety::Identity) => GatePropriety::Identity,
164 (
165 GatePropriety::Diagonal | GatePropriety::Permutation | GatePropriety::Unitary,
166 GatePropriety::Diagonal,
167 )
168 | (GatePropriety::Diagonal, GatePropriety::Permutation) => GatePropriety::Diagonal,
169 (GatePropriety::Permutation | GatePropriety::Unitary, GatePropriety::Permutation) => {
170 GatePropriety::Permutation
171 }
172 (other, GatePropriety::Unitary) => other,
173 }
174 }
175}
176
177#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
182pub enum QuantumGate {
183 PauliX,
185 PauliY,
187 PauliZ,
189 RotationX(Param),
191 RotationY(Param),
193 RotationZ(Param),
195 Phase(Param),
197 Hadamard,
199}
200
201impl QuantumGate {
202 #[must_use]
205 pub fn is_permutation(&self) -> bool {
206 match self {
207 Self::RotationX(param) | Self::RotationY(param) => {
208 if let Param::Value(value) = param {
209 (value.abs() - PI).abs() <= 1e-10
210 || 2.0f64.mul_add(-PI, value.abs()).abs() <= 1e-10
211 } else {
212 false
213 }
214 }
215 Self::Hadamard => false,
216 _ => true,
217 }
218 }
219
220 #[must_use]
231 pub fn param_index(&self) -> Option<(usize, f64)> {
232 match self {
233 Self::RotationX(Param::Ref {
234 index, multiplier, ..
235 })
236 | Self::RotationY(Param::Ref {
237 index, multiplier, ..
238 })
239 | Self::RotationZ(Param::Ref {
240 index, multiplier, ..
241 })
242 | Self::Phase(Param::Ref {
243 index, multiplier, ..
244 }) => Some((*index, *multiplier)),
245 _ => None,
246 }
247 }
248
249 #[must_use]
251 pub const fn is_diagonal(&self) -> bool {
252 matches!(self, Self::PauliZ | Self::RotationZ(_) | Self::Phase(_))
253 }
254
255 #[must_use]
257 pub fn inverse(&self) -> Self {
258 match self {
259 Self::PauliX => Self::PauliX,
260 Self::PauliY => Self::PauliY,
261 Self::PauliZ => Self::PauliZ,
262 Self::RotationX(p) => Self::RotationX(p.inverse()),
263 Self::RotationY(p) => Self::RotationY(p.inverse()),
264 Self::RotationZ(p) => Self::RotationZ(p.inverse()),
265 Self::Phase(p) => Self::Phase(p.inverse()),
266 Self::Hadamard => Self::Hadamard,
267 }
268 }
269
270 #[must_use]
272 pub fn propriety(&self) -> GatePropriety {
273 match self {
274 Self::PauliX | Self::PauliY => GatePropriety::Permutation,
275 Self::RotationX(_) | Self::RotationY(_) => {
276 if self.is_permutation() {
277 GatePropriety::Permutation
278 } else {
279 GatePropriety::Unitary
280 }
281 }
282 Self::PauliZ | Self::RotationZ(_) | Self::Phase(_) => GatePropriety::Diagonal,
283 Self::Hadamard => GatePropriety::Unitary,
284 }
285 }
286
287 #[must_use]
289 pub const fn s() -> Self {
290 Self::Phase(Param::Value(FRAC_PI_2))
291 }
292
293 #[must_use]
295 pub fn sd() -> Self {
296 Self::Phase(Param::Value(-FRAC_PI_2))
297 }
298
299 #[must_use]
301 pub const fn t() -> Self {
302 Self::Phase(Param::Value(FRAC_PI_4))
303 }
304
305 #[must_use]
307 pub fn td() -> Self {
308 Self::Phase(Param::Value(-FRAC_PI_4))
309 }
310
311 #[must_use]
313 pub const fn sqrt_t() -> Self {
314 Self::Phase(Param::Value(FRAC_PI_8))
315 }
316
317 #[must_use]
319 pub fn sqrt_td() -> Self {
320 Self::Phase(Param::Value(-FRAC_PI_8))
321 }
322
323 pub(crate) fn matrix(&self) -> Matrix {
325 match self {
326 Self::PauliX => [[0.0.into(), 1.0.into()], [1.0.into(), 0.0.into()]],
327 Self::PauliY => [[0.0.into(), -Cf64::i()], [Cf64::i(), 0.0.into()]],
328 Self::PauliZ => [[1.0.into(), 0.0.into()], [0.0.into(), (-1.0).into()]],
329 Self::RotationX(angle) => {
330 let angle = angle.value();
331 [
332 [(angle / 2.0).cos().into(), -Cf64::i() * (angle / 2.0).sin()],
333 [-Cf64::i() * (angle / 2.0).sin(), (angle / 2.0).cos().into()],
334 ]
335 }
336 Self::RotationY(angle) => {
337 let angle = angle.value();
338 [
339 [(angle / 2.0).cos().into(), (-(angle / 2.0).sin()).into()],
340 [(angle / 2.0).sin().into(), (angle / 2.0).cos().into()],
341 ]
342 }
343 Self::RotationZ(angle) => {
344 let angle = angle.value();
345 [
346 [(-Cf64::i() * (angle / 2.0)).exp(), 0.0.into()],
347 [0.0.into(), (Cf64::i() * (angle / 2.0)).exp()],
348 ]
349 }
350 Self::Phase(angle) => [
351 [1.0.into(), 0.0.into()],
352 [0.0.into(), (Cf64::i() * angle.value()).exp()],
353 ],
354 Self::Hadamard => [
355 [(1.0 / 2.0f64.sqrt()).into(), (1.0 / 2.0f64.sqrt()).into()],
356 [(1.0 / 2.0f64.sqrt()).into(), (-1.0 / 2.0f64.sqrt()).into()],
357 ],
358 }
359 }
360
361 pub(crate) fn su2_matrix(&self) -> Matrix {
367 match self {
368 Self::PauliX => Self::RotationX(Param::Value(PI)).matrix(),
369 Self::PauliY => Self::RotationY(Param::Value(PI)).matrix(),
370 Self::PauliZ => Self::RotationZ(Param::Value(PI)).matrix(),
371 Self::Phase(angle) => Self::RotationZ(*angle).matrix(),
372 Self::Hadamard => [
373 [-Cf64::i() * FRAC_1_SQRT_2, -Cf64::i() * FRAC_1_SQRT_2],
374 [-Cf64::i() * FRAC_1_SQRT_2, Cf64::i() * FRAC_1_SQRT_2],
375 ],
376 _ => self.matrix(),
377 }
378 }
379
380 pub fn merge(&self, other: &Self) -> Option<Self> {
385 match (self, other) {
386 (Self::RotationX(a), Self::RotationX(b)) => a.add(b).map(QuantumGate::RotationX),
387 (Self::RotationY(a), Self::RotationY(b)) => a.add(b).map(QuantumGate::RotationY),
388 (Self::RotationZ(a), Self::RotationZ(b)) => a.add(b).map(QuantumGate::RotationZ),
389 (Self::Phase(a), Self::Phase(b)) => a.add(b).map(QuantumGate::Phase),
390 _ => None,
391 }
392 }
393
394 #[must_use]
400 pub fn is_near_zero(&self, epsilon: f64) -> bool {
401 match self {
402 Self::RotationX(p) | Self::RotationY(p) | Self::RotationZ(p) | Self::Phase(p) => {
403 p.is_near_zero(epsilon)
404 }
405 _ => false,
406 }
407 }
408
409 #[must_use]
415 pub fn set_parameter(&self, parameters: &[f64]) -> Self {
416 match self {
417 Self::PauliX | Self::PauliY | Self::PauliZ | Self::Hadamard => *self,
418 Self::RotationX(param) => Self::RotationX(param.set_parameter(parameters)),
419 Self::RotationY(param) => Self::RotationY(param.set_parameter(parameters)),
420 Self::RotationZ(param) => Self::RotationZ(param.set_parameter(parameters)),
421 Self::Phase(param) => Self::Phase(param.set_parameter(parameters)),
422 }
423 }
424}
425
426#[derive(Debug, Clone, Copy, PartialEq, Serialize)]
431pub enum DecomposedGate {
432 U(QuantumGate, usize),
434 CNOT(usize, usize),
436}
437
438impl DecomposedGate {
439 #[must_use]
440 pub fn inverse(&self) -> Self {
441 match self {
442 DecomposedGate::U(gate, t) => DecomposedGate::U(gate.inverse(), *t),
443 DecomposedGate::CNOT(c, t) => DecomposedGate::CNOT(*c, *t),
444 }
445 }
446}
447
448impl From<(QuantumGate, usize)> for DecomposedGate {
449 fn from(value: (QuantumGate, usize)) -> Self {
450 let (gate, target) = value;
451 Self::U(gate, target)
452 }
453}
454
455#[derive(Debug, Clone, PartialEq, Serialize)]
463pub struct GateInstruction {
464 pub gate: QuantumGate,
466 pub target: usize,
468 pub control: BTreeSet<usize>,
470 pub control_locked: bool,
472 pub is_approximated: bool,
475 pub decomposed: Option<Vec<DecomposedGate>>,
477}
478
479impl From<&DecomposedGate> for GateInstruction {
480 fn from(value: &DecomposedGate) -> Self {
481 match value {
482 DecomposedGate::U(gate, target) => Self {
483 gate: *gate,
484 target: *target,
485 control: BTreeSet::new(),
486 control_locked: false,
487 is_approximated: false,
488 decomposed: None,
489 },
490 DecomposedGate::CNOT(control, target) => Self {
491 gate: PauliX,
492 target: *target,
493 control: BTreeSet::from([*control]),
494 control_locked: false,
495 is_approximated: false,
496 decomposed: None,
497 },
498 }
499 }
500}
501
502impl GateInstruction {
503 #[must_use]
505 pub const fn new(gate: QuantumGate, target: usize) -> Self {
506 Self {
507 gate,
508 target,
509 control: BTreeSet::new(),
510 control_locked: false,
511 is_approximated: false,
512 decomposed: None,
513 }
514 }
515
516 #[must_use]
522 pub fn inverse(&self) -> Self {
523 Self {
524 gate: self.gate.inverse(),
525 target: self.target,
526 control: self.control.clone(),
527 control_locked: self.control_locked,
528 is_approximated: self.is_approximated,
529 decomposed: self
530 .decomposed
531 .as_ref()
532 .map(|gates| gates.iter().rev().map(DecomposedGate::inverse).collect()),
533 }
534 }
535
536 pub fn control(&self, control_qubits: &[usize]) -> Result<Self, KetError> {
547 if self.control_locked {
548 return Ok(self.clone());
549 }
550
551 let mut control = self.control.clone();
552 control.extend(control_qubits.iter());
553
554 if self.gate.param_index().is_some() {
555 Err(KetError::ControlParameterizedGate)
556 } else if control.len() != (control_qubits.len() + self.control.len()) {
557 Err(KetError::DuplicateControlQubit)
558 } else if control.contains(&self.target) {
559 Err(KetError::ControlTargetConflict)
560 } else {
561 Ok(Self {
562 gate: self.gate,
563 target: self.target,
564 control,
565 control_locked: false,
566 is_approximated: false,
567 decomposed: None,
568 })
569 }
570 }
571
572 pub fn enable_approximated_decomposition(&mut self) {
577 self.is_approximated = true;
578 }
579
580 pub fn lock_control(&mut self) {
585 self.control_locked = true;
586 }
587
588 #[must_use]
593 pub fn commutes_with(&self, other: &Self) -> bool {
594 self.gate.is_diagonal() && other.gate.is_diagonal()
595 || (self.gate == other.gate && self.target == other.target)
596 }
597
598 pub fn decompose(&mut self, clean_auxiliary_range: (usize, usize)) {
607 let control = self.control.iter().copied().collect_vec();
608 if !self.control.is_empty() && self.decomposed.is_none() {
609 self.decomposed = Some(match self.gate {
610 QuantumGate::PauliX => Self::decompose_x(
611 self.target,
612 &control,
613 self.is_approximated,
614 clean_auxiliary_range,
615 ),
616 QuantumGate::PauliY => Self::decompose_y(
617 self.target,
618 &control,
619 self.is_approximated,
620 clean_auxiliary_range,
621 ),
622 QuantumGate::PauliZ => Self::decompose_z(
623 self.target,
624 &control,
625 self.is_approximated,
626 clean_auxiliary_range,
627 ),
628 QuantumGate::RotationX(_)
629 | QuantumGate::RotationY(_)
630 | QuantumGate::RotationZ(_) => Self::decompose_r(
631 self.gate,
632 self.target,
633 &control,
634 self.is_approximated,
635 clean_auxiliary_range,
636 ),
637 QuantumGate::Phase(angle) => Self::decompose_phase(
638 angle.value(),
639 self.target,
640 &control,
641 self.is_approximated,
642 clean_auxiliary_range,
643 ),
644 QuantumGate::Hadamard => Self::decompose_hadamard(
645 self.target,
646 &control,
647 self.is_approximated,
648 clean_auxiliary_range,
649 ),
650 });
651 }
652 }
653
654 fn decompose_r(
656 gate: QuantumGate,
657 target: usize,
658 control: &[usize],
659 approximated: bool,
660 clean_axillary_range: (usize, usize),
661 ) -> Vec<DecomposedGate> {
662 if control.len() == 1 {
663 return decompose::u2::cu2(gate.matrix(), control[0], target);
664 }
665
666 let num_clean_aux = clean_axillary_range.1 - clean_axillary_range.0;
667 let network_num_aux = Algorithm::NetworkU2(CXMode::C3X).aux_needed(control.len());
668
669 if network_num_aux <= num_clean_aux {
670 let aux_qubits: Vec<_> =
671 (clean_axillary_range.0..clean_axillary_range.0 + network_num_aux).collect();
672
673 decompose::network::network(
674 gate,
675 control,
676 &aux_qubits,
677 target,
678 CXMode::C3X,
679 approximated,
680 )
681 } else {
682 decompose::su2::decompose(gate, control, target, DepthMode::Linear, approximated)
683 }
684 }
685
686 fn decompose_x(
688 target: usize,
689 control: &[usize],
690 approximated: bool,
691 clean_axillary_range: (usize, usize),
692 ) -> Vec<DecomposedGate> {
693 if control.len() <= 4 {
694 return decompose::x::c2to4x::c1to4x(control, target, approximated);
695 }
696
697 let num_clean_aux = clean_axillary_range.1 - clean_axillary_range.0;
698
699 let network_c2x_num_aux = Algorithm::NetworkPauli(CXMode::C2X).aux_needed(control.len());
700
701 if num_clean_aux >= network_c2x_num_aux {
702 let aux_qubits: Vec<_> =
703 (clean_axillary_range.0..clean_axillary_range.0 + network_c2x_num_aux).collect();
704
705 return decompose::network::network(
706 QuantumGate::PauliX,
707 control,
708 &aux_qubits,
709 target,
710 CXMode::C2X,
711 approximated,
712 );
713 }
714
715 let network_c3x_num_aux = Algorithm::NetworkPauli(CXMode::C3X).aux_needed(control.len());
716
717 if num_clean_aux >= network_c3x_num_aux {
718 let aux_qubits: Vec<_> =
719 (clean_axillary_range.0..clean_axillary_range.0 + network_c3x_num_aux).collect();
720
721 return decompose::network::network(
722 QuantumGate::PauliX,
723 control,
724 &aux_qubits,
725 target,
726 CXMode::C3X,
727 approximated,
728 );
729 }
730 let v_chain_c3x_clean_num_aux =
731 Algorithm::VChain(CXMode::C3X, AuxMode::Clean).aux_needed(control.len());
732
733 if num_clean_aux >= v_chain_c3x_clean_num_aux {
734 let aux_qubits: Vec<_> = (clean_axillary_range.0
735 ..clean_axillary_range.0 + v_chain_c3x_clean_num_aux)
736 .collect();
737
738 return decompose::x::v_chain::v_chain(
739 control,
740 &aux_qubits,
741 target,
742 AuxMode::Clean,
743 CXMode::C3X,
744 approximated,
745 );
746 }
747
748 let num_dirty_aux = clean_axillary_range.1 - 1 - control.len();
749
750 let v_chain_c2x_dirty_num_aux =
751 Algorithm::VChain(CXMode::C2X, AuxMode::Dirty).aux_needed(control.len());
752
753 if num_dirty_aux >= v_chain_c2x_dirty_num_aux {
754 let all_qubits = control.iter().copied().chain([target]).collect_vec();
755
756 let aux_qubits: Vec<_> = (0..v_chain_c3x_clean_num_aux + 1 + control.len())
757 .filter(|qubit| !all_qubits.contains(qubit))
758 .collect();
759
760 decompose::x::v_chain::v_chain(
761 control,
762 &aux_qubits,
763 target,
764 AuxMode::Dirty,
765 CXMode::C2X,
766 approximated,
767 )
768 } else if num_clean_aux >= 1 {
769 decompose::x::single_aux::decompose(
770 control,
771 clean_axillary_range.0,
772 target,
773 AuxMode::Clean,
774 DepthMode::Linear,
775 approximated,
776 )
777 } else {
778 decompose::u2::linear_depth(QuantumGate::PauliX, control, target)
779 }
780 }
781
782 fn decompose_y(
786 target: usize,
787 control: &[usize],
788 approximated: bool,
789 clean_axillary_range: (usize, usize),
790 ) -> Vec<DecomposedGate> {
791 chain![
792 [(QuantumGate::sd(), target).into()],
793 Self::decompose_x(target, control, approximated, clean_axillary_range),
794 [(QuantumGate::s(), target).into()]
795 ]
796 .collect()
797 }
798
799 fn decompose_z(
803 target: usize,
804 control: &[usize],
805 approximated: bool,
806 clean_axillary_range: (usize, usize),
807 ) -> Vec<DecomposedGate> {
808 chain![
809 [(QuantumGate::Hadamard, target).into()],
810 Self::decompose_x(target, control, approximated, clean_axillary_range),
811 [(QuantumGate::Hadamard, target).into()]
812 ]
813 .collect()
814 }
815
816 fn decompose_phase(
818 angle: f64,
819 target: usize,
820 control: &[usize],
821 approximated: bool,
822 clean_axillary_range: (usize, usize),
823 ) -> Vec<DecomposedGate> {
824 let num_clean_aux = clean_axillary_range.1 - clean_axillary_range.0;
825 let network_c3x_num_aux = Algorithm::NetworkU2(CXMode::C3X).aux_needed(control.len());
826
827 if control.len() == 1 {
828 decompose::u2::cu2(
829 QuantumGate::Phase(Param::Value(angle)).matrix(),
830 control[0],
831 target,
832 )
833 } else if num_clean_aux >= network_c3x_num_aux {
834 let aux_qubits: Vec<_> =
835 (clean_axillary_range.0..clean_axillary_range.0 + network_c3x_num_aux).collect();
836
837 decompose::network::network(
838 QuantumGate::Phase(Param::Value(angle)),
839 control,
840 &aux_qubits,
841 target,
842 CXMode::C3X,
843 approximated,
844 )
845 } else if num_clean_aux >= 1 {
846 let control = control.iter().copied().chain([target]).collect_vec();
847 decompose::su2::decompose(
848 RotationZ(Param::Value(-2.0 * angle)),
849 &control,
850 clean_axillary_range.0,
851 DepthMode::Linear,
852 approximated,
853 )
854 } else {
855 decompose::u2::linear_depth(QuantumGate::Phase(Param::Value(angle)), control, target)
856 }
857 }
858
859 fn decompose_hadamard(
861 target: usize,
862 control: &[usize],
863 approximated: bool,
864 clean_axillary_range: (usize, usize),
865 ) -> Vec<DecomposedGate> {
866 let num_clean_aux = clean_axillary_range.1 - clean_axillary_range.0;
867 let network_c3x_num_aux = Algorithm::NetworkU2(CXMode::C3X).aux_needed(control.len());
868
869 if control.len() == 1 {
870 decompose::u2::cu2(QuantumGate::Hadamard.matrix(), control[0], target)
871 } else if num_clean_aux >= network_c3x_num_aux {
872 let aux_qubits: Vec<_> =
873 (clean_axillary_range.0..clean_axillary_range.0 + network_c3x_num_aux).collect();
874
875 decompose::network::network(
876 QuantumGate::Hadamard,
877 control,
878 &aux_qubits,
879 target,
880 CXMode::C3X,
881 approximated,
882 )
883 } else if num_clean_aux >= 1 {
884 decompose::u2::su2_rewrite_hadamard(control, clean_axillary_range.0, target)
885 } else {
886 decompose::u2::linear_depth(QuantumGate::Hadamard, control, target)
887 }
888 }
889}