Skip to content

Commit ef5a9e9

Browse files
committed
Merge upstream/main and finalize PPR matrix fixes
2 parents b20e4f0 + 50cb9b1 commit ef5a9e9

67 files changed

Lines changed: 1838 additions & 366 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/circuit/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,4 +64,4 @@ features = ["nalgebra"]
6464
cache_pygates = []
6565

6666
[dev-dependencies]
67-
pyo3 = { workspace = true, features = ["auto-initialize"] }
67+
pyo3 = { workspace = true, features = ["auto-initialize"] }

crates/circuit/src/circuit_drawer.rs

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1216,6 +1216,8 @@ mod tests {
12161216
};
12171217
use crate::parameter::parameter_expression::ParameterExpression;
12181218
use crate::parameter::symbol_expr::Symbol;
1219+
#[cfg(feature = "cache_pygates")]
1220+
use std::sync::OnceLock;
12191221

12201222
fn basic_circuit() -> CircuitData {
12211223
let qreg = QuantumRegister::new_owning("q", 2);
@@ -1407,6 +1409,8 @@ q_1: ┤ H ├┤1 ├┤1 ├┤ my_ch ├
14071409
clbits: circuit.cargs_interner().get_default(),
14081410
params: None,
14091411
label: None,
1412+
#[cfg(feature = "cache_pygates")]
1413+
py_op: OnceLock::new(),
14101414
};
14111415
circuit.push(inst).unwrap();
14121416
let inst = PackedInstruction {
@@ -1418,6 +1422,8 @@ q_1: ┤ H ├┤1 ├┤1 ├┤ my_ch ├
14181422
clbits: circuit.cargs_interner().get_default(),
14191423
params: None,
14201424
label: Some(Box::new("my little identity".to_string())),
1425+
#[cfg(feature = "cache_pygates")]
1426+
py_op: OnceLock::new(),
14211427
};
14221428
circuit.push(inst).unwrap();
14231429
let inst = PackedInstruction {
@@ -1429,6 +1435,8 @@ q_1: ┤ H ├┤1 ├┤1 ├┤ my_ch ├
14291435
clbits: circuit.cargs_interner().get_default(),
14301436
params: None,
14311437
label: None,
1438+
#[cfg(feature = "cache_pygates")]
1439+
py_op: OnceLock::new(),
14321440
};
14331441
circuit.push(inst).unwrap();
14341442
let inst = PackedInstruction {
@@ -1440,6 +1448,8 @@ q_1: ┤ H ├┤1 ├┤1 ├┤ my_ch ├
14401448
clbits: circuit.cargs_interner().get_default(),
14411449
params: None,
14421450
label: Some(Box::new("my small identity".to_string())),
1451+
#[cfg(feature = "cache_pygates")]
1452+
py_op: OnceLock::new(),
14431453
};
14441454
circuit.push(inst).unwrap();
14451455
let inst = PackedInstruction {
@@ -1451,6 +1461,8 @@ q_1: ┤ H ├┤1 ├┤1 ├┤ my_ch ├
14511461
clbits: circuit.cargs_interner().get_default(),
14521462
params: None,
14531463
label: None,
1464+
#[cfg(feature = "cache_pygates")]
1465+
py_op: OnceLock::new(),
14541466
};
14551467
circuit.push(inst).unwrap();
14561468
let inst = PackedInstruction {
@@ -1462,6 +1474,8 @@ q_1: ┤ H ├┤1 ├┤1 ├┤ my_ch ├
14621474
clbits: circuit.cargs_interner().get_default(),
14631475
params: None,
14641476
label: Some(Box::new("my medium identity".to_string())),
1477+
#[cfg(feature = "cache_pygates")]
1478+
py_op: OnceLock::new(),
14651479
};
14661480
circuit.push(inst).unwrap();
14671481
let inst = PackedInstruction {
@@ -1473,6 +1487,8 @@ q_1: ┤ H ├┤1 ├┤1 ├┤ my_ch ├
14731487
clbits: circuit.cargs_interner().get_default(),
14741488
params: None,
14751489
label: None,
1490+
#[cfg(feature = "cache_pygates")]
1491+
py_op: OnceLock::new(),
14761492
};
14771493
circuit.push(inst).unwrap();
14781494
let inst = PackedInstruction {
@@ -1484,6 +1500,8 @@ q_1: ┤ H ├┤1 ├┤1 ├┤ my_ch ├
14841500
clbits: circuit.cargs_interner().get_default(),
14851501
params: None,
14861502
label: Some(Box::new("my bigger identity".to_string())),
1503+
#[cfg(feature = "cache_pygates")]
1504+
py_op: OnceLock::new(),
14871505
};
14881506
circuit.push(inst).unwrap();
14891507
let result = draw_circuit(&circuit, false, false, Some(80)).unwrap();
@@ -1535,7 +1553,7 @@ q_3: ──────────────────────┤1
15351553
.map(|x| Qubit(x + (i as u32 % (num_wires - num_qubits))))
15361554
.collect::<Vec<_>>();
15371555
let params = (0..num_params)
1538-
.map(|_x| 3.141.into())
1556+
.map(|_x| std::f64::consts::PI.into())
15391557
.collect::<Vec<Param>>();
15401558
circuit.push_standard_gate(op, &params, &qubits).unwrap();
15411559
}
@@ -1624,7 +1642,9 @@ q_4: ─────────────────────────
16241642
#[test]
16251643
fn test_global_phase() {
16261644
let mut circuit = basic_circuit();
1627-
circuit.set_global_phase_param(3.14.into()).unwrap();
1645+
circuit
1646+
.set_global_phase_param(std::f64::consts::PI.into())
1647+
.unwrap();
16281648
let result = draw_circuit(&circuit, true, false, None).unwrap();
16291649

16301650
let expected = "
@@ -1729,6 +1749,8 @@ q_1: ┤1 ├┤1 ├┤1 ├
17291749
clbits: circuit.cargs_interner().get_default(),
17301750
params: None,
17311751
label: None,
1752+
#[cfg(feature = "cache_pygates")]
1753+
py_op: OnceLock::new(),
17321754
};
17331755
circuit.push(inst).unwrap();
17341756
}
@@ -1747,18 +1769,22 @@ q_1: ┤1 ├┤1 ├┤1 ├
17471769
clbits: circuit.cargs_interner().get_default(),
17481770
params: Some(Box::new(Parameters::Params(smallvec![param]))),
17491771
label: None,
1772+
#[cfg(feature = "cache_pygates")]
1773+
py_op: OnceLock::new(),
17501774
};
17511775
circuit.push(inst).unwrap();
17521776
}
17531777
}
17541778
for i in [1, 2, 3, 4] {
1755-
let qubits = (0..i).map(|x| Qubit::new(x)).collect::<Vec<_>>();
1779+
let qubits = (0..i).map(Qubit::new).collect::<Vec<_>>();
17561780
let inst = PackedInstruction {
17571781
op: StandardInstruction::Barrier(i as u32).into(),
17581782
qubits: circuit.add_qargs(&qubits),
17591783
clbits: circuit.cargs_interner().get_default(),
17601784
params: None,
17611785
label: None,
1786+
#[cfg(feature = "cache_pygates")]
1787+
py_op: OnceLock::new(),
17621788
};
17631789
circuit.push(inst).unwrap();
17641790
}
@@ -1769,6 +1795,8 @@ q_1: ┤1 ├┤1 ├┤1 ├
17691795
clbits: circuit.add_cargs(&[Clbit::new(i)]),
17701796
params: None,
17711797
label: None,
1798+
#[cfg(feature = "cache_pygates")]
1799+
py_op: OnceLock::new(),
17721800
};
17731801
circuit.push(inst).unwrap();
17741802
}

crates/circuit/src/gate_matrix.rs

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
// copyright notice, and modified files need to carry a notice indicating
1111
// that they have been altered from the originals.
1212

13+
use ndarray::{Array2, array};
1314
use num_complex::Complex64;
1415
use std::f64::consts::FRAC_1_SQRT_2;
1516

@@ -516,3 +517,56 @@ pub fn xx_plus_yy_gate(theta: f64, beta: f64) -> GateArray2Q {
516517
[C_ZERO, C_ZERO, C_ZERO, C_ONE],
517518
]
518519
}
520+
521+
pub fn pauli_zx_to_matrix(z: &[bool], x: &[bool]) -> Array2<Complex64> {
522+
debug_assert_eq!(z.len(), x.len());
523+
524+
let i = array![
525+
[Complex64::new(1.0, 0.0), Complex64::new(0.0, 0.0)],
526+
[Complex64::new(0.0, 0.0), Complex64::new(1.0, 0.0)],
527+
];
528+
let x_mat = array![
529+
[Complex64::new(0.0, 0.0), Complex64::new(1.0, 0.0)],
530+
[Complex64::new(1.0, 0.0), Complex64::new(0.0, 0.0)],
531+
];
532+
let z_mat = array![
533+
[Complex64::new(1.0, 0.0), Complex64::new(0.0, 0.0)],
534+
[Complex64::new(0.0, 0.0), Complex64::new(-1.0, 0.0)],
535+
];
536+
let y_mat = array![
537+
[Complex64::new(0.0, 0.0), Complex64::new(0.0, -1.0)],
538+
[Complex64::new(0.0, 1.0), Complex64::new(0.0, 0.0)],
539+
];
540+
541+
fn kron(a: &Array2<Complex64>, b: &Array2<Complex64>) -> Array2<Complex64> {
542+
let (ar, ac) = a.dim();
543+
let (br, bc) = b.dim();
544+
let mut out = Array2::<Complex64>::zeros((ar * br, ac * bc));
545+
546+
for i in 0..ar {
547+
for j in 0..ac {
548+
let coeff = a[(i, j)];
549+
for k in 0..br {
550+
for l in 0..bc {
551+
out[(i * br + k, j * bc + l)] = coeff * b[(k, l)];
552+
}
553+
}
554+
}
555+
}
556+
out
557+
}
558+
559+
let mut result = array![[Complex64::new(1.0, 0.0)]];
560+
561+
for (&z_bit, &x_bit) in z.iter().zip(x.iter()) {
562+
let factor = match (z_bit, x_bit) {
563+
(false, false) => &i,
564+
(false, true) => &x_mat,
565+
(true, false) => &z_mat,
566+
(true, true) => &y_mat,
567+
};
568+
result = kron(&result, factor);
569+
}
570+
571+
result
572+
}

crates/circuit/src/operations.rs

Lines changed: 50 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3531,7 +3531,7 @@ impl UnitaryGate {
35313531
}
35323532
}
35333533

3534-
/// A Pauli-based gate model, consisting of PauliProductRotation and PauliProductMeasurement ops.
3534+
/// A Pauli-based gate model, consisting of [PauliProductRotation] and [PauliProductMeasurement] ops.
35353535
#[derive(Clone, Debug, PartialEq, Eq)]
35363536
pub enum PauliBased {
35373537
PauliProductRotation(PauliProductRotation),
@@ -3568,20 +3568,63 @@ impl PauliProductRotation {
35683568
)?;
35693569
Ok(gate.unbind())
35703570
}
3571+
35713572
pub fn matrix(&self) -> Option<Array2<Complex64>> {
35723573
let angle = match self.angle {
35733574
Param::Float(f) => f,
35743575
_ => return None,
35753576
};
3576-
let pauli_mat = gate_matrix::pauli_zx_to_dense_matrix(&self.z, &self.x);
3577+
3578+
let pauli_mat = gate_matrix::pauli_zx_to_matrix(&self.z, &self.x);
35773579
let cos_val = (angle / 2.0).cos();
35783580
let sin_val = (angle / 2.0).sin();
3581+
35793582
let dim = pauli_mat.shape()[0];
3580-
let identity = Array2::<Complex64>::eye(dim);
3581-
Some(
3582-
identity.mapv(|v| Complex64::new(v.re * cos_val, 0.))
3583-
+ pauli_mat.mapv(|v| Complex64::new(0., -sin_val) * v),
3584-
)
3583+
let mut result = pauli_mat.mapv(|v| Complex64::new(0.0, -sin_val) * v);
3584+
3585+
for i in 0..dim {
3586+
result[[i, i]] += Complex64::new(cos_val, 0.0);
3587+
}
3588+
3589+
Some(result)
3590+
}
3591+
3592+
/// Attempts to merge `self` and `other`.
3593+
/// If successful, returns the merged [PauliProductRotation].
3594+
/// If not successful, returns `None`.
3595+
pub fn merge_with(&self, other: &Self) -> Option<Self> {
3596+
if self.x == other.x && self.z == other.z {
3597+
Some(PauliProductRotation {
3598+
z: self.z.clone(),
3599+
x: self.x.clone(),
3600+
angle: radd_param(self.angle.clone(), other.angle.clone()),
3601+
})
3602+
} else {
3603+
None
3604+
}
3605+
}
3606+
3607+
/// For a [PauliProductRotation] gate with a floating-point angle return a tuple `(Tr(gate) / dim, dim)`.
3608+
/// Return `None` if the angle is parameterized.
3609+
pub fn rotation_trace_and_dim(&self) -> Option<(Complex64, f64)> {
3610+
let Param::Float(angle) = self.angle else {
3611+
return None;
3612+
};
3613+
3614+
let num_qubits = self
3615+
.z
3616+
.iter()
3617+
.zip(self.x.iter())
3618+
.filter(|(z, x)| **z || **x)
3619+
.count();
3620+
let dim = 2u32.pow(num_qubits as u32);
3621+
let tr_over_dim = if num_qubits == 0 {
3622+
(Complex64::new(0.0, -angle / 2.)).exp()
3623+
} else {
3624+
Complex64::new((angle / 2.).cos(), 0.)
3625+
};
3626+
3627+
Some((tr_over_dim, dim as f64))
35853628
}
35863629
}
35873630

crates/circuit/src/util.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,3 +47,12 @@ pub const C_ONE: Complex64 = c64(1., 0.);
4747
pub const C_M_ONE: Complex64 = c64(-1., 0.);
4848
pub const IM: Complex64 = c64(0., 1.);
4949
pub const M_IM: Complex64 = c64(0., -1.);
50+
51+
use std::f64::consts::{FRAC_PI_2, FRAC_PI_4, FRAC_PI_8, SQRT_2};
52+
pub const C_FRAC_PI_2: Complex64 = c64(FRAC_PI_2, 0.0);
53+
pub const C_FRAC_PI_4: Complex64 = c64(FRAC_PI_4, 0.0);
54+
pub const C_FRAC_PI_8: Complex64 = c64(FRAC_PI_8, 0.0);
55+
pub const C_FRAC_PI_2_SQRT_2: Complex64 = c64(FRAC_PI_2 / SQRT_2, 0.0);
56+
pub const C_M_FRAC_PI_4: Complex64 = c64(-FRAC_PI_4, 0.0);
57+
pub const C_M_FRAC_PI_8: Complex64 = c64(-FRAC_PI_8, 0.0);
58+
pub const C_M_FRAC_PI_2_SQRT_2: Complex64 = c64(-FRAC_PI_2 / SQRT_2, 0.0);

crates/pyext/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ fn _accelerate(m: &Bound<PyModule>) -> PyResult<()> {
7272
add_submodule(m, ::qiskit_transpiler::passes::sabre::sabre, "sabre")?;
7373
add_submodule(m, ::qiskit_accelerate::sampled_exp_val::sampled_exp_val, "sampled_exp_val")?;
7474
add_submodule(m, ::qiskit_quantum_info::sparse_observable::sparse_observable, "sparse_observable")?;
75+
add_submodule(m, ::qiskit_quantum_info::sparse_observable::standard_generators::standard_generators, "standard_generators")?;
7576
add_submodule(m, ::qiskit_quantum_info::sparse_pauli_op::sparse_pauli_op, "sparse_pauli_op")?;
7677
add_submodule(m, ::qiskit_transpiler::passes::scheduling_mod, "scheduling")?;
7778
add_submodule(m, ::qiskit_quantum_info::unitary_sim::unitary_sim, "unitary_sim")?;

crates/qpy/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ qiskit-circuit.workspace = true
2121
qiskit-quantum-info.workspace = true
2222
num-bigint.workspace = true
2323
num-complex.workspace = true
24+
num-traits.workspace = true
2425
bytemuck.workspace = true
2526
binrw.workspace = true
2627
uuid = {version = "1", features = ["v4"]}

0 commit comments

Comments
 (0)