Skip to content

Commit 7d3f417

Browse files
committed
feat(circuit/rust): add matrix methods for PauliProductRotation
1 parent 572045f commit 7d3f417

3 files changed

Lines changed: 140 additions & 16 deletions

File tree

crates/circuit/src/gate_matrix.rs

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,10 @@
1313
use num_complex::Complex64;
1414
use std::f64::consts::FRAC_1_SQRT_2;
1515

16+
// Ensure necessary imports are present
17+
use ndarray::Array2;
18+
19+
1620
use crate::util::{
1721
C_M_ONE, C_ONE, C_ZERO, GateArray0Q, GateArray1Q, GateArray2Q, GateArray3Q, GateArray4Q, IM,
1822
M_IM, c64,
@@ -516,3 +520,91 @@ pub fn xx_plus_yy_gate(theta: f64, beta: f64) -> GateArray2Q {
516520
[C_ZERO, C_ZERO, C_ZERO, C_ONE],
517521
]
518522
}
523+
/// Compute the unitary matrix for a PauliProductRotation gate.
524+
///
525+
/// The gate implements: exp(-i * theta/2 * P) = cos(theta/2)*I - i*sin(theta/2)*P
526+
/// where P is the tensor product of Pauli operators given by `pauli_str`.
527+
///
528+
/// # Arguments
529+
/// * `theta` - Rotation angle in radians (f64)
530+
/// * `pauli_str` - Pauli string label e.g. "XYZ", "ZZ", "X" (rightmost = qubit 0)
531+
///
532+
/// # Returns
533+
/// Dense 2^n × 2^n complex unitary matrix as `Array2<Complex64>`
534+
pub fn pauli_product_rotation_matrix(
535+
angle: f64,
536+
z: &[bool],
537+
x: &[bool],
538+
) -> Array2<Complex64> {
539+
540+
let n = z.len();
541+
let dim = 1usize << n;
542+
543+
let pauli_i = Array2::from_shape_vec(
544+
(2, 2),
545+
vec![c64(1.,0.), c64(0.,0.), c64(0.,0.), c64(1.,0.)],
546+
).unwrap();
547+
let pauli_x = Array2::from_shape_vec(
548+
(2, 2),
549+
vec![c64(0.,0.), c64(1.,0.), c64(1.,0.), c64(0.,0.)],
550+
).unwrap();
551+
let pauli_y = Array2::from_shape_vec(
552+
(2, 2),
553+
vec![c64(0.,0.), c64(0.,-1.), c64(0.,1.), c64(0.,0.)],
554+
).unwrap();
555+
let pauli_z = Array2::from_shape_vec(
556+
(2, 2),
557+
vec![c64(1.,0.), c64(0.,0.), c64(0.,0.), c64(-1.,0.)],
558+
).unwrap();
559+
560+
// Build full tensor product using z/x symplectic representation:
561+
// z=false x=false → I
562+
// z=false x=true → X
563+
// z=true x=true → Y
564+
// z=true x=false → Z
565+
let first = match (z[0], x[0]) {
566+
(false, false) => pauli_i.clone(),
567+
(false, true) => pauli_x.clone(),
568+
(true, true) => pauli_y.clone(),
569+
(true, false) => pauli_z.clone(),
570+
};
571+
572+
let full_pauli = (1..n).fold(first, |acc, i| {
573+
let single = match (z[i], x[i]) {
574+
(false, false) => pauli_i.clone(),
575+
(false, true) => pauli_x.clone(),
576+
(true, true) => pauli_y.clone(),
577+
(true, false) => pauli_z.clone(),
578+
};
579+
kron(&single, &acc)
580+
});
581+
582+
let cos_val = (angle / 2.0).cos();
583+
let sin_val = (angle / 2.0).sin();
584+
585+
let identity: Array2<Complex64> = Array2::eye(dim)
586+
.mapv(|x: f64| c64(x, 0.));
587+
588+
identity.mapv(|v| v * c64(cos_val, 0.))
589+
+ full_pauli.mapv(|v| v * c64(0., -sin_val))
590+
591+
}
592+
593+
/// Kronecker (tensor) product of two complex matrices.
594+
fn kron(a: &Array2<Complex64>, b: &Array2<Complex64>) -> Array2<Complex64> {
595+
let (ra, ca) = a.dim();
596+
let (rb, cb) = b.dim();
597+
let mut out = Array2::zeros((ra * rb, ca * cb));
598+
for i in 0..ra {
599+
for j in 0..ca {
600+
let aij = a[[i, j]];
601+
for p in 0..rb {
602+
for q in 0..cb {
603+
out[[i * rb + p, j * cb + q]] = aij * b[[p, q]];
604+
}
605+
}
606+
}
607+
}
608+
out
609+
}
610+

crates/circuit/src/operations.rs

Lines changed: 47 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -3335,22 +3335,18 @@ pub struct PauliProductRotation {
33353335
pub angle: Param,
33363336
}
33373337

3338-
impl Operation for PauliProductRotation {
3339-
fn name(&self) -> &str {
3340-
"pauli_product_rotation"
3341-
}
3342-
fn num_qubits(&self) -> u32 {
3343-
self.z.len() as u32
3344-
}
3345-
fn num_clbits(&self) -> u32 {
3346-
0
3347-
}
3348-
fn num_params(&self) -> u32 {
3349-
1
3350-
}
3351-
fn directive(&self) -> bool {
3352-
false
3353-
}
3338+
3339+
// Helper function to convert x/z bool vectors into a Pauli string in "IXYZ" notation.
3340+
fn pauli_product_to_string(zs: &[bool], xs: &[bool]) -> String {
3341+
zs.iter()
3342+
.zip(xs.iter())
3343+
.map(|(&z, &x)| match (z, x) {
3344+
(false, false) => 'I',
3345+
(false, true) => 'X',
3346+
(true, false) => 'Z',
3347+
(true, true) => 'Y',
3348+
})
3349+
.collect()
33543350
}
33553351

33563352
impl PauliProductRotation {
@@ -3372,8 +3368,43 @@ impl PauliProductRotation {
33723368
)?;
33733369
Ok(gate.unbind())
33743370
}
3371+
3372+
pub fn matrix(&self) -> Option<Array2<Complex64>> {
3373+
let angle = match self.angle {
3374+
Param::Float(f) => f,
3375+
_ => return None,
3376+
};
3377+
Some(gate_matrix::pauli_product_rotation_matrix(
3378+
angle,
3379+
&self.z,
3380+
&self.x,
3381+
))
3382+
}
33753383
}
33763384

3385+
impl Operation for PauliProductRotation {
3386+
fn name(&self) -> &str {
3387+
"pauli_product_rotation"
3388+
}
3389+
3390+
fn num_qubits(&self) -> u32 {
3391+
self.z.len() as u32
3392+
}
3393+
3394+
fn num_clbits(&self) -> u32 {
3395+
0
3396+
}
3397+
3398+
fn num_params(&self) -> u32 {
3399+
1
3400+
}
3401+
3402+
fn directive(&self) -> bool {
3403+
false
3404+
}
3405+
}
3406+
3407+
33773408
impl PartialEq for PauliProductRotation {
33783409
fn eq(&self, other: &Self) -> bool {
33793410
self.x == other.x

crates/circuit/src/packed_instruction.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ use crate::operations::{
2323
PyOperationTypes, PythonOperation, StandardGate, StandardInstruction, UnitaryGate,
2424
};
2525
use crate::{Block, Clbit, Qubit};
26+
2627
use hashbrown::HashMap;
2728
use nalgebra::Matrix2;
2829
use ndarray::{Array2, CowArray, Ix2};

0 commit comments

Comments
 (0)