quantrs2_ml/torchquantum/gates/two_qubit/
special.rs

1//! Special Two-Qubit Gates
2//!
3//! This module provides special two-qubit gates including:
4//! - iSWAP, SSWAP (sqrt-SWAP)
5//! - ECR (Echoed Cross-Resonance)
6//! - CY (Controlled-Y), CH (Controlled-H)
7//! - DCX (Double CNOT)
8//! - XXMinusYY, XXPlusYY (parameterized)
9//! - CPhase, CU1
10
11use crate::error::{MLError, Result};
12use crate::torchquantum::{
13    CType, NParamsEnum, OpHistoryEntry, TQDevice, TQModule, TQOperator, TQParameter, WiresEnum,
14};
15use scirs2_core::ndarray::{Array2, ArrayD, IxDyn};
16
17/// iSWAP gate - swaps qubits and applies i phase
18#[derive(Debug, Clone)]
19pub struct TQiSWAP {
20    inverse: bool,
21    static_mode: bool,
22}
23
24impl TQiSWAP {
25    pub fn new() -> Self {
26        Self {
27            inverse: false,
28            static_mode: false,
29        }
30    }
31}
32
33impl Default for TQiSWAP {
34    fn default() -> Self {
35        Self::new()
36    }
37}
38
39impl TQModule for TQiSWAP {
40    fn forward(&mut self, _qdev: &mut TQDevice) -> Result<()> {
41        Err(MLError::InvalidConfiguration(
42            "Use apply() instead of forward() for operators".to_string(),
43        ))
44    }
45
46    fn parameters(&self) -> Vec<TQParameter> {
47        Vec::new()
48    }
49
50    fn n_wires(&self) -> Option<usize> {
51        Some(2)
52    }
53
54    fn set_n_wires(&mut self, _n_wires: usize) {}
55
56    fn is_static_mode(&self) -> bool {
57        self.static_mode
58    }
59
60    fn static_on(&mut self) {
61        self.static_mode = true;
62    }
63
64    fn static_off(&mut self) {
65        self.static_mode = false;
66    }
67
68    fn name(&self) -> &str {
69        "iSWAP"
70    }
71}
72
73impl TQOperator for TQiSWAP {
74    fn num_wires(&self) -> WiresEnum {
75        WiresEnum::Fixed(2)
76    }
77
78    fn num_params(&self) -> NParamsEnum {
79        NParamsEnum::Fixed(0)
80    }
81
82    fn get_matrix(&self, _params: Option<&[f64]>) -> Array2<CType> {
83        let i_val = if self.inverse {
84            CType::new(0.0, -1.0)
85        } else {
86            CType::new(0.0, 1.0)
87        };
88
89        Array2::from_shape_vec(
90            (4, 4),
91            vec![
92                CType::new(1.0, 0.0),
93                CType::new(0.0, 0.0),
94                CType::new(0.0, 0.0),
95                CType::new(0.0, 0.0),
96                CType::new(0.0, 0.0),
97                CType::new(0.0, 0.0),
98                i_val,
99                CType::new(0.0, 0.0),
100                CType::new(0.0, 0.0),
101                i_val,
102                CType::new(0.0, 0.0),
103                CType::new(0.0, 0.0),
104                CType::new(0.0, 0.0),
105                CType::new(0.0, 0.0),
106                CType::new(0.0, 0.0),
107                CType::new(1.0, 0.0),
108            ],
109        )
110        .unwrap_or_else(|_| Array2::eye(4).mapv(|x| CType::new(x, 0.0)))
111    }
112
113    fn apply(&mut self, qdev: &mut TQDevice, wires: &[usize]) -> Result<()> {
114        self.apply_with_params(qdev, wires, None)
115    }
116
117    fn apply_with_params(
118        &mut self,
119        qdev: &mut TQDevice,
120        wires: &[usize],
121        _params: Option<&[f64]>,
122    ) -> Result<()> {
123        if wires.len() < 2 {
124            return Err(MLError::InvalidConfiguration(
125                "iSWAP gate requires exactly 2 wires".to_string(),
126            ));
127        }
128        let matrix = self.get_matrix(None);
129        qdev.apply_two_qubit_gate(wires[0], wires[1], &matrix)?;
130
131        if qdev.record_op {
132            qdev.record_operation(OpHistoryEntry {
133                name: "iswap".to_string(),
134                wires: wires.to_vec(),
135                params: None,
136                inverse: self.inverse,
137                trainable: false,
138            });
139        }
140
141        Ok(())
142    }
143
144    fn has_params(&self) -> bool {
145        false
146    }
147
148    fn trainable(&self) -> bool {
149        false
150    }
151
152    fn inverse(&self) -> bool {
153        self.inverse
154    }
155
156    fn set_inverse(&mut self, inverse: bool) {
157        self.inverse = inverse;
158    }
159}
160
161/// ECR gate - Echoed Cross-Resonance gate
162#[derive(Debug, Clone)]
163pub struct TQECR {
164    inverse: bool,
165    static_mode: bool,
166}
167
168impl TQECR {
169    pub fn new() -> Self {
170        Self {
171            inverse: false,
172            static_mode: false,
173        }
174    }
175}
176
177impl Default for TQECR {
178    fn default() -> Self {
179        Self::new()
180    }
181}
182
183impl TQModule for TQECR {
184    fn forward(&mut self, _qdev: &mut TQDevice) -> Result<()> {
185        Err(MLError::InvalidConfiguration(
186            "Use apply() instead of forward() for operators".to_string(),
187        ))
188    }
189
190    fn parameters(&self) -> Vec<TQParameter> {
191        Vec::new()
192    }
193
194    fn n_wires(&self) -> Option<usize> {
195        Some(2)
196    }
197
198    fn set_n_wires(&mut self, _n_wires: usize) {}
199
200    fn is_static_mode(&self) -> bool {
201        self.static_mode
202    }
203
204    fn static_on(&mut self) {
205        self.static_mode = true;
206    }
207
208    fn static_off(&mut self) {
209        self.static_mode = false;
210    }
211
212    fn name(&self) -> &str {
213        "ECR"
214    }
215}
216
217impl TQOperator for TQECR {
218    fn num_wires(&self) -> WiresEnum {
219        WiresEnum::Fixed(2)
220    }
221
222    fn num_params(&self) -> NParamsEnum {
223        NParamsEnum::Fixed(0)
224    }
225
226    fn get_matrix(&self, _params: Option<&[f64]>) -> Array2<CType> {
227        let scale = 1.0 / std::f64::consts::SQRT_2;
228        let (sign_i, sign_neg_i) = if self.inverse {
229            (CType::new(0.0, -scale), CType::new(0.0, scale))
230        } else {
231            (CType::new(0.0, scale), CType::new(0.0, -scale))
232        };
233
234        Array2::from_shape_vec(
235            (4, 4),
236            vec![
237                CType::new(0.0, 0.0),
238                CType::new(0.0, 0.0),
239                CType::new(scale, 0.0),
240                sign_i,
241                CType::new(0.0, 0.0),
242                CType::new(0.0, 0.0),
243                sign_i,
244                CType::new(scale, 0.0),
245                CType::new(scale, 0.0),
246                sign_neg_i,
247                CType::new(0.0, 0.0),
248                CType::new(0.0, 0.0),
249                sign_neg_i,
250                CType::new(scale, 0.0),
251                CType::new(0.0, 0.0),
252                CType::new(0.0, 0.0),
253            ],
254        )
255        .unwrap_or_else(|_| Array2::eye(4).mapv(|x| CType::new(x, 0.0)))
256    }
257
258    fn apply(&mut self, qdev: &mut TQDevice, wires: &[usize]) -> Result<()> {
259        self.apply_with_params(qdev, wires, None)
260    }
261
262    fn apply_with_params(
263        &mut self,
264        qdev: &mut TQDevice,
265        wires: &[usize],
266        _params: Option<&[f64]>,
267    ) -> Result<()> {
268        if wires.len() < 2 {
269            return Err(MLError::InvalidConfiguration(
270                "ECR gate requires exactly 2 wires".to_string(),
271            ));
272        }
273        let matrix = self.get_matrix(None);
274        qdev.apply_two_qubit_gate(wires[0], wires[1], &matrix)?;
275
276        if qdev.record_op {
277            qdev.record_operation(OpHistoryEntry {
278                name: "ecr".to_string(),
279                wires: wires.to_vec(),
280                params: None,
281                inverse: self.inverse,
282                trainable: false,
283            });
284        }
285
286        Ok(())
287    }
288
289    fn has_params(&self) -> bool {
290        false
291    }
292
293    fn trainable(&self) -> bool {
294        false
295    }
296
297    fn inverse(&self) -> bool {
298        self.inverse
299    }
300
301    fn set_inverse(&mut self, inverse: bool) {
302        self.inverse = inverse;
303    }
304}
305
306/// CY gate - Controlled Y gate
307#[derive(Debug, Clone)]
308pub struct TQCY {
309    inverse: bool,
310    static_mode: bool,
311}
312
313impl TQCY {
314    pub fn new() -> Self {
315        Self {
316            inverse: false,
317            static_mode: false,
318        }
319    }
320}
321
322impl Default for TQCY {
323    fn default() -> Self {
324        Self::new()
325    }
326}
327
328impl TQModule for TQCY {
329    fn forward(&mut self, _qdev: &mut TQDevice) -> Result<()> {
330        Err(MLError::InvalidConfiguration(
331            "Use apply() instead of forward() for operators".to_string(),
332        ))
333    }
334
335    fn parameters(&self) -> Vec<TQParameter> {
336        Vec::new()
337    }
338
339    fn n_wires(&self) -> Option<usize> {
340        Some(2)
341    }
342
343    fn set_n_wires(&mut self, _n_wires: usize) {}
344
345    fn is_static_mode(&self) -> bool {
346        self.static_mode
347    }
348
349    fn static_on(&mut self) {
350        self.static_mode = true;
351    }
352
353    fn static_off(&mut self) {
354        self.static_mode = false;
355    }
356
357    fn name(&self) -> &str {
358        "CY"
359    }
360}
361
362impl TQOperator for TQCY {
363    fn num_wires(&self) -> WiresEnum {
364        WiresEnum::Fixed(2)
365    }
366
367    fn num_params(&self) -> NParamsEnum {
368        NParamsEnum::Fixed(0)
369    }
370
371    fn get_matrix(&self, _params: Option<&[f64]>) -> Array2<CType> {
372        let (i_val, neg_i_val) = if self.inverse {
373            (CType::new(0.0, -1.0), CType::new(0.0, 1.0))
374        } else {
375            (CType::new(0.0, 1.0), CType::new(0.0, -1.0))
376        };
377
378        Array2::from_shape_vec(
379            (4, 4),
380            vec![
381                CType::new(1.0, 0.0),
382                CType::new(0.0, 0.0),
383                CType::new(0.0, 0.0),
384                CType::new(0.0, 0.0),
385                CType::new(0.0, 0.0),
386                CType::new(1.0, 0.0),
387                CType::new(0.0, 0.0),
388                CType::new(0.0, 0.0),
389                CType::new(0.0, 0.0),
390                CType::new(0.0, 0.0),
391                CType::new(0.0, 0.0),
392                neg_i_val,
393                CType::new(0.0, 0.0),
394                CType::new(0.0, 0.0),
395                i_val,
396                CType::new(0.0, 0.0),
397            ],
398        )
399        .unwrap_or_else(|_| Array2::eye(4).mapv(|x| CType::new(x, 0.0)))
400    }
401
402    fn apply(&mut self, qdev: &mut TQDevice, wires: &[usize]) -> Result<()> {
403        self.apply_with_params(qdev, wires, None)
404    }
405
406    fn apply_with_params(
407        &mut self,
408        qdev: &mut TQDevice,
409        wires: &[usize],
410        _params: Option<&[f64]>,
411    ) -> Result<()> {
412        if wires.len() < 2 {
413            return Err(MLError::InvalidConfiguration(
414                "CY gate requires exactly 2 wires".to_string(),
415            ));
416        }
417        let matrix = self.get_matrix(None);
418        qdev.apply_two_qubit_gate(wires[0], wires[1], &matrix)?;
419
420        if qdev.record_op {
421            qdev.record_operation(OpHistoryEntry {
422                name: "cy".to_string(),
423                wires: wires.to_vec(),
424                params: None,
425                inverse: self.inverse,
426                trainable: false,
427            });
428        }
429
430        Ok(())
431    }
432
433    fn has_params(&self) -> bool {
434        false
435    }
436
437    fn trainable(&self) -> bool {
438        false
439    }
440
441    fn inverse(&self) -> bool {
442        self.inverse
443    }
444
445    fn set_inverse(&mut self, inverse: bool) {
446        self.inverse = inverse;
447    }
448}
449
450/// SSWAP gate - Square root SWAP gate
451#[derive(Debug, Clone)]
452pub struct TQSSWAP {
453    inverse: bool,
454    static_mode: bool,
455}
456
457impl TQSSWAP {
458    pub fn new() -> Self {
459        Self {
460            inverse: false,
461            static_mode: false,
462        }
463    }
464}
465
466impl Default for TQSSWAP {
467    fn default() -> Self {
468        Self::new()
469    }
470}
471
472impl TQModule for TQSSWAP {
473    fn forward(&mut self, _qdev: &mut TQDevice) -> Result<()> {
474        Err(MLError::InvalidConfiguration(
475            "Use apply() instead of forward() for operators".to_string(),
476        ))
477    }
478
479    fn parameters(&self) -> Vec<TQParameter> {
480        Vec::new()
481    }
482
483    fn n_wires(&self) -> Option<usize> {
484        Some(2)
485    }
486
487    fn set_n_wires(&mut self, _n_wires: usize) {}
488
489    fn is_static_mode(&self) -> bool {
490        self.static_mode
491    }
492
493    fn static_on(&mut self) {
494        self.static_mode = true;
495    }
496
497    fn static_off(&mut self) {
498        self.static_mode = false;
499    }
500
501    fn name(&self) -> &str {
502        "SSWAP"
503    }
504}
505
506impl TQOperator for TQSSWAP {
507    fn num_wires(&self) -> WiresEnum {
508        WiresEnum::Fixed(2)
509    }
510
511    fn num_params(&self) -> NParamsEnum {
512        NParamsEnum::Fixed(0)
513    }
514
515    fn get_matrix(&self, _params: Option<&[f64]>) -> Array2<CType> {
516        let (a, b) = if self.inverse {
517            (CType::new(0.5, -0.5), CType::new(0.5, 0.5))
518        } else {
519            (CType::new(0.5, 0.5), CType::new(0.5, -0.5))
520        };
521
522        Array2::from_shape_vec(
523            (4, 4),
524            vec![
525                CType::new(1.0, 0.0),
526                CType::new(0.0, 0.0),
527                CType::new(0.0, 0.0),
528                CType::new(0.0, 0.0),
529                CType::new(0.0, 0.0),
530                a,
531                b,
532                CType::new(0.0, 0.0),
533                CType::new(0.0, 0.0),
534                b,
535                a,
536                CType::new(0.0, 0.0),
537                CType::new(0.0, 0.0),
538                CType::new(0.0, 0.0),
539                CType::new(0.0, 0.0),
540                CType::new(1.0, 0.0),
541            ],
542        )
543        .unwrap_or_else(|_| Array2::eye(4).mapv(|x| CType::new(x, 0.0)))
544    }
545
546    fn apply(&mut self, qdev: &mut TQDevice, wires: &[usize]) -> Result<()> {
547        self.apply_with_params(qdev, wires, None)
548    }
549
550    fn apply_with_params(
551        &mut self,
552        qdev: &mut TQDevice,
553        wires: &[usize],
554        _params: Option<&[f64]>,
555    ) -> Result<()> {
556        if wires.len() < 2 {
557            return Err(MLError::InvalidConfiguration(
558                "SSWAP gate requires exactly 2 wires".to_string(),
559            ));
560        }
561        let matrix = self.get_matrix(None);
562        qdev.apply_two_qubit_gate(wires[0], wires[1], &matrix)?;
563
564        if qdev.record_op {
565            qdev.record_operation(OpHistoryEntry {
566                name: "sswap".to_string(),
567                wires: wires.to_vec(),
568                params: None,
569                inverse: self.inverse,
570                trainable: false,
571            });
572        }
573
574        Ok(())
575    }
576
577    fn has_params(&self) -> bool {
578        false
579    }
580
581    fn trainable(&self) -> bool {
582        false
583    }
584
585    fn inverse(&self) -> bool {
586        self.inverse
587    }
588
589    fn set_inverse(&mut self, inverse: bool) {
590        self.inverse = inverse;
591    }
592}
593
594/// DCX gate - Double CNOT gate
595#[derive(Debug, Clone)]
596pub struct TQDCX {
597    inverse: bool,
598    static_mode: bool,
599}
600
601impl TQDCX {
602    pub fn new() -> Self {
603        Self {
604            inverse: false,
605            static_mode: false,
606        }
607    }
608}
609
610impl Default for TQDCX {
611    fn default() -> Self {
612        Self::new()
613    }
614}
615
616impl TQModule for TQDCX {
617    fn forward(&mut self, _qdev: &mut TQDevice) -> Result<()> {
618        Err(MLError::InvalidConfiguration(
619            "Use apply() instead of forward() for operators".to_string(),
620        ))
621    }
622
623    fn parameters(&self) -> Vec<TQParameter> {
624        Vec::new()
625    }
626
627    fn n_wires(&self) -> Option<usize> {
628        Some(2)
629    }
630
631    fn set_n_wires(&mut self, _n_wires: usize) {}
632
633    fn is_static_mode(&self) -> bool {
634        self.static_mode
635    }
636
637    fn static_on(&mut self) {
638        self.static_mode = true;
639    }
640
641    fn static_off(&mut self) {
642        self.static_mode = false;
643    }
644
645    fn name(&self) -> &str {
646        "DCX"
647    }
648}
649
650impl TQOperator for TQDCX {
651    fn num_wires(&self) -> WiresEnum {
652        WiresEnum::Fixed(2)
653    }
654
655    fn num_params(&self) -> NParamsEnum {
656        NParamsEnum::Fixed(0)
657    }
658
659    fn get_matrix(&self, _params: Option<&[f64]>) -> Array2<CType> {
660        Array2::from_shape_vec(
661            (4, 4),
662            vec![
663                CType::new(1.0, 0.0),
664                CType::new(0.0, 0.0),
665                CType::new(0.0, 0.0),
666                CType::new(0.0, 0.0),
667                CType::new(0.0, 0.0),
668                CType::new(0.0, 0.0),
669                CType::new(0.0, 0.0),
670                CType::new(1.0, 0.0),
671                CType::new(0.0, 0.0),
672                CType::new(1.0, 0.0),
673                CType::new(0.0, 0.0),
674                CType::new(0.0, 0.0),
675                CType::new(0.0, 0.0),
676                CType::new(0.0, 0.0),
677                CType::new(1.0, 0.0),
678                CType::new(0.0, 0.0),
679            ],
680        )
681        .unwrap_or_else(|_| Array2::eye(4).mapv(|x| CType::new(x, 0.0)))
682    }
683
684    fn apply(&mut self, qdev: &mut TQDevice, wires: &[usize]) -> Result<()> {
685        self.apply_with_params(qdev, wires, None)
686    }
687
688    fn apply_with_params(
689        &mut self,
690        qdev: &mut TQDevice,
691        wires: &[usize],
692        _params: Option<&[f64]>,
693    ) -> Result<()> {
694        if wires.len() < 2 {
695            return Err(MLError::InvalidConfiguration(
696                "DCX gate requires exactly 2 wires".to_string(),
697            ));
698        }
699        let matrix = self.get_matrix(None);
700        qdev.apply_two_qubit_gate(wires[0], wires[1], &matrix)?;
701
702        if qdev.record_op {
703            qdev.record_operation(OpHistoryEntry {
704                name: "dcx".to_string(),
705                wires: wires.to_vec(),
706                params: None,
707                inverse: self.inverse,
708                trainable: false,
709            });
710        }
711
712        Ok(())
713    }
714
715    fn has_params(&self) -> bool {
716        false
717    }
718
719    fn trainable(&self) -> bool {
720        false
721    }
722
723    fn inverse(&self) -> bool {
724        self.inverse
725    }
726
727    fn set_inverse(&mut self, inverse: bool) {
728        self.inverse = inverse;
729    }
730}
731
732/// XXMinusYY gate - parameterized (XX - YY) interaction
733#[derive(Debug, Clone)]
734pub struct TQXXMinusYY {
735    params: Option<TQParameter>,
736    has_params: bool,
737    trainable: bool,
738    inverse: bool,
739    static_mode: bool,
740}
741
742impl TQXXMinusYY {
743    pub fn new(has_params: bool, trainable: bool) -> Self {
744        let params = if has_params {
745            Some(TQParameter::new(
746                ArrayD::zeros(IxDyn(&[1, 2])),
747                "xxmyy_params",
748            ))
749        } else {
750            None
751        };
752
753        Self {
754            params,
755            has_params,
756            trainable,
757            inverse: false,
758            static_mode: false,
759        }
760    }
761
762    pub fn with_init_params(mut self, theta: f64, beta: f64) -> Self {
763        if let Some(ref mut p) = self.params {
764            p.data[[0, 0]] = theta;
765            p.data[[0, 1]] = beta;
766        }
767        self
768    }
769}
770
771impl Default for TQXXMinusYY {
772    fn default() -> Self {
773        Self::new(true, true)
774    }
775}
776
777impl TQModule for TQXXMinusYY {
778    fn forward(&mut self, _qdev: &mut TQDevice) -> Result<()> {
779        Err(MLError::InvalidConfiguration(
780            "Use apply() instead of forward() for operators".to_string(),
781        ))
782    }
783
784    fn parameters(&self) -> Vec<TQParameter> {
785        self.params.iter().cloned().collect()
786    }
787
788    fn n_wires(&self) -> Option<usize> {
789        Some(2)
790    }
791
792    fn set_n_wires(&mut self, _n_wires: usize) {}
793
794    fn is_static_mode(&self) -> bool {
795        self.static_mode
796    }
797
798    fn static_on(&mut self) {
799        self.static_mode = true;
800    }
801
802    fn static_off(&mut self) {
803        self.static_mode = false;
804    }
805
806    fn name(&self) -> &str {
807        "XXMinusYY"
808    }
809
810    fn zero_grad(&mut self) {
811        if let Some(ref mut p) = self.params {
812            p.zero_grad();
813        }
814    }
815}
816
817impl TQOperator for TQXXMinusYY {
818    fn num_wires(&self) -> WiresEnum {
819        WiresEnum::Fixed(2)
820    }
821
822    fn num_params(&self) -> NParamsEnum {
823        NParamsEnum::Fixed(2)
824    }
825
826    fn get_matrix(&self, params: Option<&[f64]>) -> Array2<CType> {
827        let theta = params
828            .and_then(|p| p.first().copied())
829            .or_else(|| self.params.as_ref().map(|p| p.data[[0, 0]]))
830            .unwrap_or(0.0);
831        let beta = params
832            .and_then(|p| p.get(1).copied())
833            .or_else(|| self.params.as_ref().map(|p| p.data[[0, 1]]))
834            .unwrap_or(0.0);
835
836        let theta = if self.inverse { -theta } else { theta };
837        let half_theta = theta / 2.0;
838        let c = half_theta.cos();
839        let s = half_theta.sin();
840
841        let exp_pos = CType::from_polar(1.0, beta);
842        let exp_neg = CType::from_polar(1.0, -beta);
843
844        Array2::from_shape_vec(
845            (4, 4),
846            vec![
847                CType::new(c, 0.0),
848                CType::new(0.0, 0.0),
849                CType::new(0.0, 0.0),
850                CType::new(0.0, -s) * exp_neg,
851                CType::new(0.0, 0.0),
852                CType::new(1.0, 0.0),
853                CType::new(0.0, 0.0),
854                CType::new(0.0, 0.0),
855                CType::new(0.0, 0.0),
856                CType::new(0.0, 0.0),
857                CType::new(1.0, 0.0),
858                CType::new(0.0, 0.0),
859                CType::new(0.0, -s) * exp_pos,
860                CType::new(0.0, 0.0),
861                CType::new(0.0, 0.0),
862                CType::new(c, 0.0),
863            ],
864        )
865        .unwrap_or_else(|_| Array2::eye(4).mapv(|x| CType::new(x, 0.0)))
866    }
867
868    fn apply(&mut self, qdev: &mut TQDevice, wires: &[usize]) -> Result<()> {
869        self.apply_with_params(qdev, wires, None)
870    }
871
872    fn apply_with_params(
873        &mut self,
874        qdev: &mut TQDevice,
875        wires: &[usize],
876        params: Option<&[f64]>,
877    ) -> Result<()> {
878        if wires.len() < 2 {
879            return Err(MLError::InvalidConfiguration(
880                "XXMinusYY gate requires exactly 2 wires".to_string(),
881            ));
882        }
883        let matrix = self.get_matrix(params);
884        qdev.apply_two_qubit_gate(wires[0], wires[1], &matrix)?;
885
886        if qdev.record_op {
887            qdev.record_operation(OpHistoryEntry {
888                name: "xxmyy".to_string(),
889                wires: wires.to_vec(),
890                params: params.map(|p| p.to_vec()),
891                inverse: self.inverse,
892                trainable: self.trainable,
893            });
894        }
895
896        Ok(())
897    }
898
899    fn has_params(&self) -> bool {
900        self.has_params
901    }
902
903    fn trainable(&self) -> bool {
904        self.trainable
905    }
906
907    fn inverse(&self) -> bool {
908        self.inverse
909    }
910
911    fn set_inverse(&mut self, inverse: bool) {
912        self.inverse = inverse;
913    }
914}
915
916/// XXPlusYY gate - parameterized (XX + YY) interaction
917#[derive(Debug, Clone)]
918pub struct TQXXPlusYY {
919    params: Option<TQParameter>,
920    has_params: bool,
921    trainable: bool,
922    inverse: bool,
923    static_mode: bool,
924}
925
926impl TQXXPlusYY {
927    pub fn new(has_params: bool, trainable: bool) -> Self {
928        let params = if has_params {
929            Some(TQParameter::new(
930                ArrayD::zeros(IxDyn(&[1, 2])),
931                "xxpyy_params",
932            ))
933        } else {
934            None
935        };
936
937        Self {
938            params,
939            has_params,
940            trainable,
941            inverse: false,
942            static_mode: false,
943        }
944    }
945
946    pub fn with_init_params(mut self, theta: f64, beta: f64) -> Self {
947        if let Some(ref mut p) = self.params {
948            p.data[[0, 0]] = theta;
949            p.data[[0, 1]] = beta;
950        }
951        self
952    }
953}
954
955impl Default for TQXXPlusYY {
956    fn default() -> Self {
957        Self::new(true, true)
958    }
959}
960
961impl TQModule for TQXXPlusYY {
962    fn forward(&mut self, _qdev: &mut TQDevice) -> Result<()> {
963        Err(MLError::InvalidConfiguration(
964            "Use apply() instead of forward() for operators".to_string(),
965        ))
966    }
967
968    fn parameters(&self) -> Vec<TQParameter> {
969        self.params.iter().cloned().collect()
970    }
971
972    fn n_wires(&self) -> Option<usize> {
973        Some(2)
974    }
975
976    fn set_n_wires(&mut self, _n_wires: usize) {}
977
978    fn is_static_mode(&self) -> bool {
979        self.static_mode
980    }
981
982    fn static_on(&mut self) {
983        self.static_mode = true;
984    }
985
986    fn static_off(&mut self) {
987        self.static_mode = false;
988    }
989
990    fn name(&self) -> &str {
991        "XXPlusYY"
992    }
993
994    fn zero_grad(&mut self) {
995        if let Some(ref mut p) = self.params {
996            p.zero_grad();
997        }
998    }
999}
1000
1001impl TQOperator for TQXXPlusYY {
1002    fn num_wires(&self) -> WiresEnum {
1003        WiresEnum::Fixed(2)
1004    }
1005
1006    fn num_params(&self) -> NParamsEnum {
1007        NParamsEnum::Fixed(2)
1008    }
1009
1010    fn get_matrix(&self, params: Option<&[f64]>) -> Array2<CType> {
1011        let theta = params
1012            .and_then(|p| p.first().copied())
1013            .or_else(|| self.params.as_ref().map(|p| p.data[[0, 0]]))
1014            .unwrap_or(0.0);
1015        let beta = params
1016            .and_then(|p| p.get(1).copied())
1017            .or_else(|| self.params.as_ref().map(|p| p.data[[0, 1]]))
1018            .unwrap_or(0.0);
1019
1020        let theta = if self.inverse { -theta } else { theta };
1021        let half_theta = theta / 2.0;
1022        let c = half_theta.cos();
1023        let s = half_theta.sin();
1024
1025        let exp_pos = CType::from_polar(1.0, beta);
1026        let exp_neg = CType::from_polar(1.0, -beta);
1027
1028        Array2::from_shape_vec(
1029            (4, 4),
1030            vec![
1031                CType::new(1.0, 0.0),
1032                CType::new(0.0, 0.0),
1033                CType::new(0.0, 0.0),
1034                CType::new(0.0, 0.0),
1035                CType::new(0.0, 0.0),
1036                CType::new(c, 0.0),
1037                CType::new(0.0, -s) * exp_neg,
1038                CType::new(0.0, 0.0),
1039                CType::new(0.0, 0.0),
1040                CType::new(0.0, -s) * exp_pos,
1041                CType::new(c, 0.0),
1042                CType::new(0.0, 0.0),
1043                CType::new(0.0, 0.0),
1044                CType::new(0.0, 0.0),
1045                CType::new(0.0, 0.0),
1046                CType::new(1.0, 0.0),
1047            ],
1048        )
1049        .unwrap_or_else(|_| Array2::eye(4).mapv(|x| CType::new(x, 0.0)))
1050    }
1051
1052    fn apply(&mut self, qdev: &mut TQDevice, wires: &[usize]) -> Result<()> {
1053        self.apply_with_params(qdev, wires, None)
1054    }
1055
1056    fn apply_with_params(
1057        &mut self,
1058        qdev: &mut TQDevice,
1059        wires: &[usize],
1060        params: Option<&[f64]>,
1061    ) -> Result<()> {
1062        if wires.len() < 2 {
1063            return Err(MLError::InvalidConfiguration(
1064                "XXPlusYY gate requires exactly 2 wires".to_string(),
1065            ));
1066        }
1067        let matrix = self.get_matrix(params);
1068        qdev.apply_two_qubit_gate(wires[0], wires[1], &matrix)?;
1069
1070        if qdev.record_op {
1071            qdev.record_operation(OpHistoryEntry {
1072                name: "xxpyy".to_string(),
1073                wires: wires.to_vec(),
1074                params: params.map(|p| p.to_vec()),
1075                inverse: self.inverse,
1076                trainable: self.trainable,
1077            });
1078        }
1079
1080        Ok(())
1081    }
1082
1083    fn has_params(&self) -> bool {
1084        self.has_params
1085    }
1086
1087    fn trainable(&self) -> bool {
1088        self.trainable
1089    }
1090
1091    fn inverse(&self) -> bool {
1092        self.inverse
1093    }
1094
1095    fn set_inverse(&mut self, inverse: bool) {
1096        self.inverse = inverse;
1097    }
1098}
1099
1100/// CH gate - Controlled Hadamard gate
1101#[derive(Debug, Clone)]
1102pub struct TQCH {
1103    inverse: bool,
1104    static_mode: bool,
1105}
1106
1107impl TQCH {
1108    pub fn new() -> Self {
1109        Self {
1110            inverse: false,
1111            static_mode: false,
1112        }
1113    }
1114}
1115
1116impl Default for TQCH {
1117    fn default() -> Self {
1118        Self::new()
1119    }
1120}
1121
1122impl TQModule for TQCH {
1123    fn forward(&mut self, _qdev: &mut TQDevice) -> Result<()> {
1124        Err(MLError::InvalidConfiguration(
1125            "Use apply() instead of forward() for operators".to_string(),
1126        ))
1127    }
1128
1129    fn parameters(&self) -> Vec<TQParameter> {
1130        Vec::new()
1131    }
1132
1133    fn n_wires(&self) -> Option<usize> {
1134        Some(2)
1135    }
1136
1137    fn set_n_wires(&mut self, _n_wires: usize) {}
1138
1139    fn is_static_mode(&self) -> bool {
1140        self.static_mode
1141    }
1142
1143    fn static_on(&mut self) {
1144        self.static_mode = true;
1145    }
1146
1147    fn static_off(&mut self) {
1148        self.static_mode = false;
1149    }
1150
1151    fn name(&self) -> &str {
1152        "CH"
1153    }
1154}
1155
1156impl TQOperator for TQCH {
1157    fn num_wires(&self) -> WiresEnum {
1158        WiresEnum::Fixed(2)
1159    }
1160
1161    fn num_params(&self) -> NParamsEnum {
1162        NParamsEnum::Fixed(0)
1163    }
1164
1165    fn get_matrix(&self, _params: Option<&[f64]>) -> Array2<CType> {
1166        let scale = 1.0 / std::f64::consts::SQRT_2;
1167
1168        Array2::from_shape_vec(
1169            (4, 4),
1170            vec![
1171                CType::new(1.0, 0.0),
1172                CType::new(0.0, 0.0),
1173                CType::new(0.0, 0.0),
1174                CType::new(0.0, 0.0),
1175                CType::new(0.0, 0.0),
1176                CType::new(1.0, 0.0),
1177                CType::new(0.0, 0.0),
1178                CType::new(0.0, 0.0),
1179                CType::new(0.0, 0.0),
1180                CType::new(0.0, 0.0),
1181                CType::new(scale, 0.0),
1182                CType::new(scale, 0.0),
1183                CType::new(0.0, 0.0),
1184                CType::new(0.0, 0.0),
1185                CType::new(scale, 0.0),
1186                CType::new(-scale, 0.0),
1187            ],
1188        )
1189        .unwrap_or_else(|_| Array2::eye(4).mapv(|x| CType::new(x, 0.0)))
1190    }
1191
1192    fn apply(&mut self, qdev: &mut TQDevice, wires: &[usize]) -> Result<()> {
1193        self.apply_with_params(qdev, wires, None)
1194    }
1195
1196    fn apply_with_params(
1197        &mut self,
1198        qdev: &mut TQDevice,
1199        wires: &[usize],
1200        _params: Option<&[f64]>,
1201    ) -> Result<()> {
1202        if wires.len() < 2 {
1203            return Err(MLError::InvalidConfiguration(
1204                "CH gate requires exactly 2 wires".to_string(),
1205            ));
1206        }
1207        let matrix = self.get_matrix(None);
1208        qdev.apply_two_qubit_gate(wires[0], wires[1], &matrix)?;
1209
1210        if qdev.record_op {
1211            qdev.record_operation(OpHistoryEntry {
1212                name: "ch".to_string(),
1213                wires: wires.to_vec(),
1214                params: None,
1215                inverse: self.inverse,
1216                trainable: false,
1217            });
1218        }
1219
1220        Ok(())
1221    }
1222
1223    fn has_params(&self) -> bool {
1224        false
1225    }
1226
1227    fn trainable(&self) -> bool {
1228        false
1229    }
1230
1231    fn inverse(&self) -> bool {
1232        self.inverse
1233    }
1234
1235    fn set_inverse(&mut self, inverse: bool) {
1236        self.inverse = inverse;
1237    }
1238}
1239
1240/// CPhase gate - Controlled phase gate (also known as CU1)
1241#[derive(Debug, Clone)]
1242pub struct TQCPhase {
1243    params: Option<TQParameter>,
1244    has_params: bool,
1245    trainable: bool,
1246    inverse: bool,
1247    static_mode: bool,
1248}
1249
1250impl TQCPhase {
1251    pub fn new(has_params: bool, trainable: bool) -> Self {
1252        let params = if has_params {
1253            Some(TQParameter::new(
1254                ArrayD::zeros(IxDyn(&[1, 1])),
1255                "cphase_theta",
1256            ))
1257        } else {
1258            None
1259        };
1260
1261        Self {
1262            params,
1263            has_params,
1264            trainable,
1265            inverse: false,
1266            static_mode: false,
1267        }
1268    }
1269
1270    pub fn with_init_params(mut self, theta: f64) -> Self {
1271        if let Some(ref mut p) = self.params {
1272            p.data[[0, 0]] = theta;
1273        }
1274        self
1275    }
1276}
1277
1278impl Default for TQCPhase {
1279    fn default() -> Self {
1280        Self::new(true, true)
1281    }
1282}
1283
1284impl TQModule for TQCPhase {
1285    fn forward(&mut self, _qdev: &mut TQDevice) -> Result<()> {
1286        Err(MLError::InvalidConfiguration(
1287            "Use apply() instead of forward() for operators".to_string(),
1288        ))
1289    }
1290
1291    fn parameters(&self) -> Vec<TQParameter> {
1292        self.params.iter().cloned().collect()
1293    }
1294
1295    fn n_wires(&self) -> Option<usize> {
1296        Some(2)
1297    }
1298
1299    fn set_n_wires(&mut self, _n_wires: usize) {}
1300
1301    fn is_static_mode(&self) -> bool {
1302        self.static_mode
1303    }
1304
1305    fn static_on(&mut self) {
1306        self.static_mode = true;
1307    }
1308
1309    fn static_off(&mut self) {
1310        self.static_mode = false;
1311    }
1312
1313    fn name(&self) -> &str {
1314        "CPhase"
1315    }
1316
1317    fn zero_grad(&mut self) {
1318        if let Some(ref mut p) = self.params {
1319            p.zero_grad();
1320        }
1321    }
1322}
1323
1324impl TQOperator for TQCPhase {
1325    fn num_wires(&self) -> WiresEnum {
1326        WiresEnum::Fixed(2)
1327    }
1328
1329    fn num_params(&self) -> NParamsEnum {
1330        NParamsEnum::Fixed(1)
1331    }
1332
1333    fn get_matrix(&self, params: Option<&[f64]>) -> Array2<CType> {
1334        let theta = params
1335            .and_then(|p| p.first().copied())
1336            .or_else(|| self.params.as_ref().map(|p| p.data[[0, 0]]))
1337            .unwrap_or(0.0);
1338
1339        let theta = if self.inverse { -theta } else { theta };
1340        let exp_i = CType::from_polar(1.0, theta);
1341
1342        Array2::from_shape_vec(
1343            (4, 4),
1344            vec![
1345                CType::new(1.0, 0.0),
1346                CType::new(0.0, 0.0),
1347                CType::new(0.0, 0.0),
1348                CType::new(0.0, 0.0),
1349                CType::new(0.0, 0.0),
1350                CType::new(1.0, 0.0),
1351                CType::new(0.0, 0.0),
1352                CType::new(0.0, 0.0),
1353                CType::new(0.0, 0.0),
1354                CType::new(0.0, 0.0),
1355                CType::new(1.0, 0.0),
1356                CType::new(0.0, 0.0),
1357                CType::new(0.0, 0.0),
1358                CType::new(0.0, 0.0),
1359                CType::new(0.0, 0.0),
1360                exp_i,
1361            ],
1362        )
1363        .unwrap_or_else(|_| Array2::eye(4).mapv(|x| CType::new(x, 0.0)))
1364    }
1365
1366    fn apply(&mut self, qdev: &mut TQDevice, wires: &[usize]) -> Result<()> {
1367        self.apply_with_params(qdev, wires, None)
1368    }
1369
1370    fn apply_with_params(
1371        &mut self,
1372        qdev: &mut TQDevice,
1373        wires: &[usize],
1374        params: Option<&[f64]>,
1375    ) -> Result<()> {
1376        if wires.len() < 2 {
1377            return Err(MLError::InvalidConfiguration(
1378                "CPhase gate requires exactly 2 wires".to_string(),
1379            ));
1380        }
1381        let matrix = self.get_matrix(params);
1382        qdev.apply_two_qubit_gate(wires[0], wires[1], &matrix)?;
1383
1384        if qdev.record_op {
1385            qdev.record_operation(OpHistoryEntry {
1386                name: "cphase".to_string(),
1387                wires: wires.to_vec(),
1388                params: params.map(|p| p.to_vec()),
1389                inverse: self.inverse,
1390                trainable: self.trainable,
1391            });
1392        }
1393
1394        Ok(())
1395    }
1396
1397    fn has_params(&self) -> bool {
1398        self.has_params
1399    }
1400
1401    fn trainable(&self) -> bool {
1402        self.trainable
1403    }
1404
1405    fn inverse(&self) -> bool {
1406        self.inverse
1407    }
1408
1409    fn set_inverse(&mut self, inverse: bool) {
1410        self.inverse = inverse;
1411    }
1412}
1413
1414/// Type alias for CU1 gate (same as CPhase)
1415/// CU1 is the IBM/Qiskit name for controlled phase gate
1416pub type TQCU1 = TQCPhase;
1417
1418/// fSim gate - Google's fermionic simulation gate
1419///
1420/// This gate is used in quantum chemistry simulations and is native
1421/// to Google's Sycamore processor. It combines an iSWAP-like interaction
1422/// with a controlled phase.
1423///
1424/// Matrix representation:
1425/// ```text
1426/// [[1,           0,                0,           0        ],
1427///  [0,           cos(θ),           -i·sin(θ),   0        ],
1428///  [0,           -i·sin(θ),        cos(θ),      0        ],
1429///  [0,           0,                0,           e^{-iφ}  ]]
1430/// ```
1431#[derive(Debug, Clone)]
1432pub struct TQFSimGate {
1433    params: Option<TQParameter>,
1434    has_params: bool,
1435    trainable: bool,
1436    inverse: bool,
1437    static_mode: bool,
1438}
1439
1440impl TQFSimGate {
1441    pub fn new(has_params: bool, trainable: bool) -> Self {
1442        let params = if has_params {
1443            Some(TQParameter::new(
1444                ArrayD::zeros(IxDyn(&[1, 2])),
1445                "fsim_params",
1446            ))
1447        } else {
1448            None
1449        };
1450
1451        Self {
1452            params,
1453            has_params,
1454            trainable,
1455            inverse: false,
1456            static_mode: false,
1457        }
1458    }
1459
1460    /// Create with initial parameters (theta, phi)
1461    pub fn with_init_params(mut self, theta: f64, phi: f64) -> Self {
1462        if let Some(ref mut p) = self.params {
1463            p.data[[0, 0]] = theta;
1464            p.data[[0, 1]] = phi;
1465        }
1466        self
1467    }
1468
1469    /// Create a full iSWAP (theta=π/2, phi=0)
1470    pub fn iswap_like() -> Self {
1471        Self::new(true, false).with_init_params(std::f64::consts::FRAC_PI_2, 0.0)
1472    }
1473
1474    /// Create a sqrt-iSWAP (theta=π/4, phi=0)
1475    pub fn sqrt_iswap() -> Self {
1476        Self::new(true, false).with_init_params(std::f64::consts::FRAC_PI_4, 0.0)
1477    }
1478
1479    /// Create Sycamore gate (theta≈π/2, phi≈π/6) - Google's native gate
1480    pub fn sycamore() -> Self {
1481        Self::new(true, false)
1482            .with_init_params(std::f64::consts::FRAC_PI_2, std::f64::consts::FRAC_PI_6)
1483    }
1484}
1485
1486impl Default for TQFSimGate {
1487    fn default() -> Self {
1488        Self::new(true, true)
1489    }
1490}
1491
1492impl TQModule for TQFSimGate {
1493    fn forward(&mut self, _qdev: &mut TQDevice) -> Result<()> {
1494        Err(MLError::InvalidConfiguration(
1495            "Use apply() instead of forward() for operators".to_string(),
1496        ))
1497    }
1498
1499    fn parameters(&self) -> Vec<TQParameter> {
1500        self.params.iter().cloned().collect()
1501    }
1502
1503    fn n_wires(&self) -> Option<usize> {
1504        Some(2)
1505    }
1506
1507    fn set_n_wires(&mut self, _n_wires: usize) {}
1508
1509    fn is_static_mode(&self) -> bool {
1510        self.static_mode
1511    }
1512
1513    fn static_on(&mut self) {
1514        self.static_mode = true;
1515    }
1516
1517    fn static_off(&mut self) {
1518        self.static_mode = false;
1519    }
1520
1521    fn name(&self) -> &str {
1522        "fSim"
1523    }
1524
1525    fn zero_grad(&mut self) {
1526        if let Some(ref mut p) = self.params {
1527            p.zero_grad();
1528        }
1529    }
1530}
1531
1532impl TQOperator for TQFSimGate {
1533    fn num_wires(&self) -> WiresEnum {
1534        WiresEnum::Fixed(2)
1535    }
1536
1537    fn num_params(&self) -> NParamsEnum {
1538        NParamsEnum::Fixed(2)
1539    }
1540
1541    fn get_matrix(&self, params: Option<&[f64]>) -> Array2<CType> {
1542        let theta = params
1543            .and_then(|p| p.first().copied())
1544            .or_else(|| self.params.as_ref().map(|p| p.data[[0, 0]]))
1545            .unwrap_or(0.0);
1546        let phi = params
1547            .and_then(|p| p.get(1).copied())
1548            .or_else(|| self.params.as_ref().map(|p| p.data[[0, 1]]))
1549            .unwrap_or(0.0);
1550
1551        let theta = if self.inverse { -theta } else { theta };
1552        let phi = if self.inverse { -phi } else { phi };
1553
1554        let c = theta.cos();
1555        let s = theta.sin();
1556        let exp_neg_i_phi = CType::from_polar(1.0, -phi);
1557
1558        Array2::from_shape_vec(
1559            (4, 4),
1560            vec![
1561                CType::new(1.0, 0.0),
1562                CType::new(0.0, 0.0),
1563                CType::new(0.0, 0.0),
1564                CType::new(0.0, 0.0),
1565                CType::new(0.0, 0.0),
1566                CType::new(c, 0.0),
1567                CType::new(0.0, -s),
1568                CType::new(0.0, 0.0),
1569                CType::new(0.0, 0.0),
1570                CType::new(0.0, -s),
1571                CType::new(c, 0.0),
1572                CType::new(0.0, 0.0),
1573                CType::new(0.0, 0.0),
1574                CType::new(0.0, 0.0),
1575                CType::new(0.0, 0.0),
1576                exp_neg_i_phi,
1577            ],
1578        )
1579        .unwrap_or_else(|_| Array2::eye(4).mapv(|x| CType::new(x, 0.0)))
1580    }
1581
1582    fn apply(&mut self, qdev: &mut TQDevice, wires: &[usize]) -> Result<()> {
1583        self.apply_with_params(qdev, wires, None)
1584    }
1585
1586    fn apply_with_params(
1587        &mut self,
1588        qdev: &mut TQDevice,
1589        wires: &[usize],
1590        params: Option<&[f64]>,
1591    ) -> Result<()> {
1592        if wires.len() < 2 {
1593            return Err(MLError::InvalidConfiguration(
1594                "fSim gate requires exactly 2 wires".to_string(),
1595            ));
1596        }
1597        let matrix = self.get_matrix(params);
1598        qdev.apply_two_qubit_gate(wires[0], wires[1], &matrix)?;
1599
1600        if qdev.record_op {
1601            qdev.record_operation(OpHistoryEntry {
1602                name: "fsim".to_string(),
1603                wires: wires.to_vec(),
1604                params: params.map(|p| p.to_vec()),
1605                inverse: self.inverse,
1606                trainable: self.trainable,
1607            });
1608        }
1609
1610        Ok(())
1611    }
1612
1613    fn has_params(&self) -> bool {
1614        self.has_params
1615    }
1616
1617    fn trainable(&self) -> bool {
1618        self.trainable
1619    }
1620
1621    fn inverse(&self) -> bool {
1622        self.inverse
1623    }
1624
1625    fn set_inverse(&mut self, inverse: bool) {
1626        self.inverse = inverse;
1627    }
1628}
1629
1630/// Givens rotation gate - fundamental for quantum chemistry
1631///
1632/// The Givens rotation performs a rotation in the (i,j) subspace of the Hilbert space.
1633/// It is widely used in molecular orbital transformations and VQE circuits for chemistry.
1634///
1635/// G(θ, φ) = exp(-i·θ/2·(e^{iφ}·|01⟩⟨10| + e^{-iφ}·|10⟩⟨01|))
1636///
1637/// Matrix representation:
1638/// ```text
1639/// [[1,   0,                   0,                   0],
1640///  [0,   cos(θ/2),            -e^{iφ}·sin(θ/2),   0],
1641///  [0,   e^{-iφ}·sin(θ/2),    cos(θ/2),           0],
1642///  [0,   0,                   0,                   1]]
1643/// ```
1644#[derive(Debug, Clone)]
1645pub struct TQGivensRotation {
1646    params: Option<TQParameter>,
1647    has_params: bool,
1648    trainable: bool,
1649    inverse: bool,
1650    static_mode: bool,
1651}
1652
1653impl TQGivensRotation {
1654    pub fn new(has_params: bool, trainable: bool) -> Self {
1655        let params = if has_params {
1656            Some(TQParameter::new(
1657                ArrayD::zeros(IxDyn(&[1, 2])),
1658                "givens_params",
1659            ))
1660        } else {
1661            None
1662        };
1663
1664        Self {
1665            params,
1666            has_params,
1667            trainable,
1668            inverse: false,
1669            static_mode: false,
1670        }
1671    }
1672
1673    /// Create with initial parameters (theta, phi)
1674    pub fn with_init_params(mut self, theta: f64, phi: f64) -> Self {
1675        if let Some(ref mut p) = self.params {
1676            p.data[[0, 0]] = theta;
1677            p.data[[0, 1]] = phi;
1678        }
1679        self
1680    }
1681
1682    /// Create a real Givens rotation (phi=0)
1683    pub fn real(theta: f64) -> Self {
1684        Self::new(true, false).with_init_params(theta, 0.0)
1685    }
1686
1687    /// Create a complex Givens rotation
1688    pub fn complex(theta: f64, phi: f64) -> Self {
1689        Self::new(true, false).with_init_params(theta, phi)
1690    }
1691}
1692
1693impl Default for TQGivensRotation {
1694    fn default() -> Self {
1695        Self::new(true, true)
1696    }
1697}
1698
1699impl TQModule for TQGivensRotation {
1700    fn forward(&mut self, _qdev: &mut TQDevice) -> Result<()> {
1701        Err(MLError::InvalidConfiguration(
1702            "Use apply() instead of forward() for operators".to_string(),
1703        ))
1704    }
1705
1706    fn parameters(&self) -> Vec<TQParameter> {
1707        self.params.iter().cloned().collect()
1708    }
1709
1710    fn n_wires(&self) -> Option<usize> {
1711        Some(2)
1712    }
1713
1714    fn set_n_wires(&mut self, _n_wires: usize) {}
1715
1716    fn is_static_mode(&self) -> bool {
1717        self.static_mode
1718    }
1719
1720    fn static_on(&mut self) {
1721        self.static_mode = true;
1722    }
1723
1724    fn static_off(&mut self) {
1725        self.static_mode = false;
1726    }
1727
1728    fn name(&self) -> &str {
1729        "GivensRotation"
1730    }
1731
1732    fn zero_grad(&mut self) {
1733        if let Some(ref mut p) = self.params {
1734            p.zero_grad();
1735        }
1736    }
1737}
1738
1739impl TQOperator for TQGivensRotation {
1740    fn num_wires(&self) -> WiresEnum {
1741        WiresEnum::Fixed(2)
1742    }
1743
1744    fn num_params(&self) -> NParamsEnum {
1745        NParamsEnum::Fixed(2)
1746    }
1747
1748    fn get_matrix(&self, params: Option<&[f64]>) -> Array2<CType> {
1749        let theta = params
1750            .and_then(|p| p.first().copied())
1751            .or_else(|| self.params.as_ref().map(|p| p.data[[0, 0]]))
1752            .unwrap_or(0.0);
1753        let phi = params
1754            .and_then(|p| p.get(1).copied())
1755            .or_else(|| self.params.as_ref().map(|p| p.data[[0, 1]]))
1756            .unwrap_or(0.0);
1757
1758        let theta = if self.inverse { -theta } else { theta };
1759        let phi = if self.inverse { -phi } else { phi };
1760
1761        let half_theta = theta / 2.0;
1762        let c = half_theta.cos();
1763        let s = half_theta.sin();
1764
1765        let exp_i_phi = CType::from_polar(1.0, phi);
1766        let exp_neg_i_phi = CType::from_polar(1.0, -phi);
1767
1768        Array2::from_shape_vec(
1769            (4, 4),
1770            vec![
1771                CType::new(1.0, 0.0),
1772                CType::new(0.0, 0.0),
1773                CType::new(0.0, 0.0),
1774                CType::new(0.0, 0.0),
1775                CType::new(0.0, 0.0),
1776                CType::new(c, 0.0),
1777                -exp_i_phi * s,
1778                CType::new(0.0, 0.0),
1779                CType::new(0.0, 0.0),
1780                exp_neg_i_phi * s,
1781                CType::new(c, 0.0),
1782                CType::new(0.0, 0.0),
1783                CType::new(0.0, 0.0),
1784                CType::new(0.0, 0.0),
1785                CType::new(0.0, 0.0),
1786                CType::new(1.0, 0.0),
1787            ],
1788        )
1789        .unwrap_or_else(|_| Array2::eye(4).mapv(|x| CType::new(x, 0.0)))
1790    }
1791
1792    fn apply(&mut self, qdev: &mut TQDevice, wires: &[usize]) -> Result<()> {
1793        self.apply_with_params(qdev, wires, None)
1794    }
1795
1796    fn apply_with_params(
1797        &mut self,
1798        qdev: &mut TQDevice,
1799        wires: &[usize],
1800        params: Option<&[f64]>,
1801    ) -> Result<()> {
1802        if wires.len() < 2 {
1803            return Err(MLError::InvalidConfiguration(
1804                "GivensRotation gate requires exactly 2 wires".to_string(),
1805            ));
1806        }
1807        let matrix = self.get_matrix(params);
1808        qdev.apply_two_qubit_gate(wires[0], wires[1], &matrix)?;
1809
1810        if qdev.record_op {
1811            qdev.record_operation(OpHistoryEntry {
1812                name: "givens".to_string(),
1813                wires: wires.to_vec(),
1814                params: params.map(|p| p.to_vec()),
1815                inverse: self.inverse,
1816                trainable: self.trainable,
1817            });
1818        }
1819
1820        Ok(())
1821    }
1822
1823    fn has_params(&self) -> bool {
1824        self.has_params
1825    }
1826
1827    fn trainable(&self) -> bool {
1828        self.trainable
1829    }
1830
1831    fn inverse(&self) -> bool {
1832        self.inverse
1833    }
1834
1835    fn set_inverse(&mut self, inverse: bool) {
1836        self.inverse = inverse;
1837    }
1838}
1839
1840/// General controlled rotation gate
1841///
1842/// Applies a controlled rotation around an arbitrary axis.
1843/// CRot(theta, phi, omega) = CR_z(omega) @ CR_y(phi) @ CR_z(theta)
1844///
1845/// This is the controlled version of the general U3 rotation.
1846#[derive(Debug, Clone)]
1847pub struct TQControlledRot {
1848    params: Option<TQParameter>,
1849    has_params: bool,
1850    trainable: bool,
1851    inverse: bool,
1852    static_mode: bool,
1853}
1854
1855impl TQControlledRot {
1856    pub fn new(has_params: bool, trainable: bool) -> Self {
1857        let params = if has_params {
1858            Some(TQParameter::new(
1859                ArrayD::zeros(IxDyn(&[1, 3])),
1860                "crot_params",
1861            ))
1862        } else {
1863            None
1864        };
1865
1866        Self {
1867            params,
1868            has_params,
1869            trainable,
1870            inverse: false,
1871            static_mode: false,
1872        }
1873    }
1874
1875    /// Create with initial parameters (theta, phi, omega)
1876    pub fn with_init_params(mut self, theta: f64, phi: f64, omega: f64) -> Self {
1877        if let Some(ref mut p) = self.params {
1878            p.data[[0, 0]] = theta;
1879            p.data[[0, 1]] = phi;
1880            p.data[[0, 2]] = omega;
1881        }
1882        self
1883    }
1884}
1885
1886impl Default for TQControlledRot {
1887    fn default() -> Self {
1888        Self::new(true, true)
1889    }
1890}
1891
1892impl TQModule for TQControlledRot {
1893    fn forward(&mut self, _qdev: &mut TQDevice) -> Result<()> {
1894        Err(MLError::InvalidConfiguration(
1895            "Use apply() instead of forward() for operators".to_string(),
1896        ))
1897    }
1898
1899    fn parameters(&self) -> Vec<TQParameter> {
1900        self.params.iter().cloned().collect()
1901    }
1902
1903    fn n_wires(&self) -> Option<usize> {
1904        Some(2)
1905    }
1906
1907    fn set_n_wires(&mut self, _n_wires: usize) {}
1908
1909    fn is_static_mode(&self) -> bool {
1910        self.static_mode
1911    }
1912
1913    fn static_on(&mut self) {
1914        self.static_mode = true;
1915    }
1916
1917    fn static_off(&mut self) {
1918        self.static_mode = false;
1919    }
1920
1921    fn name(&self) -> &str {
1922        "CRot"
1923    }
1924
1925    fn zero_grad(&mut self) {
1926        if let Some(ref mut p) = self.params {
1927            p.zero_grad();
1928        }
1929    }
1930}
1931
1932impl TQOperator for TQControlledRot {
1933    fn num_wires(&self) -> WiresEnum {
1934        WiresEnum::Fixed(2)
1935    }
1936
1937    fn num_params(&self) -> NParamsEnum {
1938        NParamsEnum::Fixed(3)
1939    }
1940
1941    fn get_matrix(&self, params: Option<&[f64]>) -> Array2<CType> {
1942        let theta = params
1943            .and_then(|p| p.first().copied())
1944            .or_else(|| self.params.as_ref().map(|p| p.data[[0, 0]]))
1945            .unwrap_or(0.0);
1946        let phi = params
1947            .and_then(|p| p.get(1).copied())
1948            .or_else(|| self.params.as_ref().map(|p| p.data[[0, 1]]))
1949            .unwrap_or(0.0);
1950        let omega = params
1951            .and_then(|p| p.get(2).copied())
1952            .or_else(|| self.params.as_ref().map(|p| p.data[[0, 2]]))
1953            .unwrap_or(0.0);
1954
1955        let (theta, phi, omega) = if self.inverse {
1956            (-omega, -phi, -theta)
1957        } else {
1958            (theta, phi, omega)
1959        };
1960
1961        // CRot = CRz(omega) @ CRy(phi) @ CRz(theta)
1962        // For the target qubit: U = Rz(omega) @ Ry(phi) @ Rz(theta)
1963        let half_theta = theta / 2.0;
1964        let half_phi = phi / 2.0;
1965        let half_omega = omega / 2.0;
1966
1967        // Compute U = Rz(omega) @ Ry(phi) @ Rz(theta)
1968        // U_00 = cos(phi/2) * e^{-i(theta+omega)/2}
1969        // U_01 = -sin(phi/2) * e^{-i(theta-omega)/2}
1970        // U_10 = sin(phi/2) * e^{i(theta-omega)/2}
1971        // U_11 = cos(phi/2) * e^{i(theta+omega)/2}
1972        let cos_phi = half_phi.cos();
1973        let sin_phi = half_phi.sin();
1974
1975        let u00 = CType::from_polar(cos_phi, -(half_theta + half_omega));
1976        let u01 = CType::from_polar(-sin_phi, -(half_theta - half_omega));
1977        let u10 = CType::from_polar(sin_phi, half_theta - half_omega);
1978        let u11 = CType::from_polar(cos_phi, half_theta + half_omega);
1979
1980        Array2::from_shape_vec(
1981            (4, 4),
1982            vec![
1983                CType::new(1.0, 0.0),
1984                CType::new(0.0, 0.0),
1985                CType::new(0.0, 0.0),
1986                CType::new(0.0, 0.0),
1987                CType::new(0.0, 0.0),
1988                CType::new(1.0, 0.0),
1989                CType::new(0.0, 0.0),
1990                CType::new(0.0, 0.0),
1991                CType::new(0.0, 0.0),
1992                CType::new(0.0, 0.0),
1993                u00,
1994                u01,
1995                CType::new(0.0, 0.0),
1996                CType::new(0.0, 0.0),
1997                u10,
1998                u11,
1999            ],
2000        )
2001        .unwrap_or_else(|_| Array2::eye(4).mapv(|x| CType::new(x, 0.0)))
2002    }
2003
2004    fn apply(&mut self, qdev: &mut TQDevice, wires: &[usize]) -> Result<()> {
2005        self.apply_with_params(qdev, wires, None)
2006    }
2007
2008    fn apply_with_params(
2009        &mut self,
2010        qdev: &mut TQDevice,
2011        wires: &[usize],
2012        params: Option<&[f64]>,
2013    ) -> Result<()> {
2014        if wires.len() < 2 {
2015            return Err(MLError::InvalidConfiguration(
2016                "CRot gate requires exactly 2 wires".to_string(),
2017            ));
2018        }
2019        let matrix = self.get_matrix(params);
2020        qdev.apply_two_qubit_gate(wires[0], wires[1], &matrix)?;
2021
2022        if qdev.record_op {
2023            qdev.record_operation(OpHistoryEntry {
2024                name: "crot".to_string(),
2025                wires: wires.to_vec(),
2026                params: params.map(|p| p.to_vec()),
2027                inverse: self.inverse,
2028                trainable: self.trainable,
2029            });
2030        }
2031
2032        Ok(())
2033    }
2034
2035    fn has_params(&self) -> bool {
2036        self.has_params
2037    }
2038
2039    fn trainable(&self) -> bool {
2040        self.trainable
2041    }
2042
2043    fn inverse(&self) -> bool {
2044        self.inverse
2045    }
2046
2047    fn set_inverse(&mut self, inverse: bool) {
2048        self.inverse = inverse;
2049    }
2050}
2051
2052/// Phase shift gate (also known as P gate)
2053///
2054/// This is a parameterized version of the phase gate that applies
2055/// a phase shift to the |1⟩ state.
2056///
2057/// Matrix representation:
2058/// ```text
2059/// [[1,   0,        0,           0],
2060///  [0,   1,        0,           0],
2061///  [0,   0,        1,           0],
2062///  [0,   0,        0,   e^{iφ}]]
2063/// ```
2064///
2065/// Note: This is the two-qubit controlled version. For single-qubit
2066/// phase shift, use the P gate in single_qubit module.
2067#[derive(Debug, Clone)]
2068pub struct TQPhaseShift2 {
2069    params: Option<TQParameter>,
2070    has_params: bool,
2071    trainable: bool,
2072    inverse: bool,
2073    static_mode: bool,
2074}
2075
2076impl TQPhaseShift2 {
2077    pub fn new(has_params: bool, trainable: bool) -> Self {
2078        let params = if has_params {
2079            Some(TQParameter::new(
2080                ArrayD::zeros(IxDyn(&[1, 1])),
2081                "phaseshift2_phi",
2082            ))
2083        } else {
2084            None
2085        };
2086
2087        Self {
2088            params,
2089            has_params,
2090            trainable,
2091            inverse: false,
2092            static_mode: false,
2093        }
2094    }
2095
2096    /// Create with initial phase parameter
2097    pub fn with_init_params(mut self, phi: f64) -> Self {
2098        if let Some(ref mut p) = self.params {
2099            p.data[[0, 0]] = phi;
2100        }
2101        self
2102    }
2103}
2104
2105impl Default for TQPhaseShift2 {
2106    fn default() -> Self {
2107        Self::new(true, true)
2108    }
2109}
2110
2111impl TQModule for TQPhaseShift2 {
2112    fn forward(&mut self, _qdev: &mut TQDevice) -> Result<()> {
2113        Err(MLError::InvalidConfiguration(
2114            "Use apply() instead of forward() for operators".to_string(),
2115        ))
2116    }
2117
2118    fn parameters(&self) -> Vec<TQParameter> {
2119        self.params.iter().cloned().collect()
2120    }
2121
2122    fn n_wires(&self) -> Option<usize> {
2123        Some(2)
2124    }
2125
2126    fn set_n_wires(&mut self, _n_wires: usize) {}
2127
2128    fn is_static_mode(&self) -> bool {
2129        self.static_mode
2130    }
2131
2132    fn static_on(&mut self) {
2133        self.static_mode = true;
2134    }
2135
2136    fn static_off(&mut self) {
2137        self.static_mode = false;
2138    }
2139
2140    fn name(&self) -> &str {
2141        "PhaseShift2"
2142    }
2143
2144    fn zero_grad(&mut self) {
2145        if let Some(ref mut p) = self.params {
2146            p.zero_grad();
2147        }
2148    }
2149}
2150
2151impl TQOperator for TQPhaseShift2 {
2152    fn num_wires(&self) -> WiresEnum {
2153        WiresEnum::Fixed(2)
2154    }
2155
2156    fn num_params(&self) -> NParamsEnum {
2157        NParamsEnum::Fixed(1)
2158    }
2159
2160    fn get_matrix(&self, params: Option<&[f64]>) -> Array2<CType> {
2161        let phi = params
2162            .and_then(|p| p.first().copied())
2163            .or_else(|| self.params.as_ref().map(|p| p.data[[0, 0]]))
2164            .unwrap_or(0.0);
2165
2166        let phi = if self.inverse { -phi } else { phi };
2167        let exp_i_phi = CType::from_polar(1.0, phi);
2168
2169        Array2::from_shape_vec(
2170            (4, 4),
2171            vec![
2172                CType::new(1.0, 0.0),
2173                CType::new(0.0, 0.0),
2174                CType::new(0.0, 0.0),
2175                CType::new(0.0, 0.0),
2176                CType::new(0.0, 0.0),
2177                CType::new(1.0, 0.0),
2178                CType::new(0.0, 0.0),
2179                CType::new(0.0, 0.0),
2180                CType::new(0.0, 0.0),
2181                CType::new(0.0, 0.0),
2182                CType::new(1.0, 0.0),
2183                CType::new(0.0, 0.0),
2184                CType::new(0.0, 0.0),
2185                CType::new(0.0, 0.0),
2186                CType::new(0.0, 0.0),
2187                exp_i_phi,
2188            ],
2189        )
2190        .unwrap_or_else(|_| Array2::eye(4).mapv(|x| CType::new(x, 0.0)))
2191    }
2192
2193    fn apply(&mut self, qdev: &mut TQDevice, wires: &[usize]) -> Result<()> {
2194        self.apply_with_params(qdev, wires, None)
2195    }
2196
2197    fn apply_with_params(
2198        &mut self,
2199        qdev: &mut TQDevice,
2200        wires: &[usize],
2201        params: Option<&[f64]>,
2202    ) -> Result<()> {
2203        if wires.len() < 2 {
2204            return Err(MLError::InvalidConfiguration(
2205                "PhaseShift2 gate requires exactly 2 wires".to_string(),
2206            ));
2207        }
2208        let matrix = self.get_matrix(params);
2209        qdev.apply_two_qubit_gate(wires[0], wires[1], &matrix)?;
2210
2211        if qdev.record_op {
2212            qdev.record_operation(OpHistoryEntry {
2213                name: "phaseshift2".to_string(),
2214                wires: wires.to_vec(),
2215                params: params.map(|p| p.to_vec()),
2216                inverse: self.inverse,
2217                trainable: self.trainable,
2218            });
2219        }
2220
2221        Ok(())
2222    }
2223
2224    fn has_params(&self) -> bool {
2225        self.has_params
2226    }
2227
2228    fn trainable(&self) -> bool {
2229        self.trainable
2230    }
2231
2232    fn inverse(&self) -> bool {
2233        self.inverse
2234    }
2235
2236    fn set_inverse(&mut self, inverse: bool) {
2237        self.inverse = inverse;
2238    }
2239}