Skip to content

Commit

Permalink
Merge branch 'Qiskit:main' into rust-compose-trans
Browse files Browse the repository at this point in the history
  • Loading branch information
raynelfss authored Sep 18, 2024
2 parents c9082c7 + f091cf2 commit 3d1a6d4
Show file tree
Hide file tree
Showing 20 changed files with 558 additions and 300 deletions.
4 changes: 2 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion crates/accelerate/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ itertools.workspace = true
qiskit-circuit.workspace = true
thiserror.workspace = true
ndarray_einsum_beta = "0.7"
once_cell = "1.19.0"
once_cell = "1.20.0"

[dependencies.smallvec]
workspace = true
Expand Down
4 changes: 2 additions & 2 deletions crates/accelerate/src/commutation_analysis.rs
Original file line number Diff line number Diff line change
Expand Up @@ -95,12 +95,12 @@ pub(crate) fn analyze_commutations_inner(
py,
&op1,
params1,
packed_inst0.extra_attrs.as_deref(),
&packed_inst0.extra_attrs,
qargs1,
cargs1,
&op2,
params2,
packed_inst1.extra_attrs.as_deref(),
&packed_inst1.extra_attrs,
qargs2,
cargs2,
MAX_NUM_QUBITS,
Expand Down
20 changes: 10 additions & 10 deletions crates/accelerate/src/commutation_checker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -121,12 +121,12 @@ impl CommutationChecker {
py,
&op1.instruction.operation.view(),
&op1.instruction.params,
op1.instruction.extra_attrs.as_deref(),
&op1.instruction.extra_attrs,
&qargs1,
&cargs1,
&op2.instruction.operation.view(),
&op2.instruction.params,
op2.instruction.extra_attrs.as_deref(),
&op2.instruction.extra_attrs,
&qargs2,
&cargs2,
max_num_qubits,
Expand Down Expand Up @@ -162,12 +162,12 @@ impl CommutationChecker {
py,
&op1.operation.view(),
&op1.params,
op1.extra_attrs.as_deref(),
&op1.extra_attrs,
&qargs1,
&cargs1,
&op2.operation.view(),
&op2.params,
op2.extra_attrs.as_deref(),
&op2.extra_attrs,
&qargs2,
&cargs2,
max_num_qubits,
Expand Down Expand Up @@ -232,12 +232,12 @@ impl CommutationChecker {
py: Python,
op1: &OperationRef,
params1: &[Param],
attrs1: Option<&ExtraInstructionAttributes>,
attrs1: &ExtraInstructionAttributes,
qargs1: &[Qubit],
cargs1: &[Clbit],
op2: &OperationRef,
params2: &[Param],
attrs2: Option<&ExtraInstructionAttributes>,
attrs2: &ExtraInstructionAttributes,
qargs2: &[Qubit],
cargs2: &[Clbit],
max_num_qubits: u32,
Expand Down Expand Up @@ -494,20 +494,20 @@ impl CommutationChecker {
fn commutation_precheck(
op1: &OperationRef,
params1: &[Param],
attrs1: Option<&ExtraInstructionAttributes>,
attrs1: &ExtraInstructionAttributes,
qargs1: &[Qubit],
cargs1: &[Clbit],
op2: &OperationRef,
params2: &[Param],
attrs2: Option<&ExtraInstructionAttributes>,
attrs2: &ExtraInstructionAttributes,
qargs2: &[Qubit],
cargs2: &[Clbit],
max_num_qubits: u32,
) -> Option<bool> {
if op1.control_flow()
|| op2.control_flow()
|| attrs1.is_some_and(|attr| attr.condition.is_some())
|| attrs2.is_some_and(|attr| attr.condition.is_some())
|| attrs1.condition().is_some()
|| attrs2.condition().is_some()
{
return Some(false);
}
Expand Down
73 changes: 72 additions & 1 deletion crates/accelerate/src/two_qubit_decompose.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ use ndarray::linalg::kron;
use ndarray::prelude::*;
use ndarray::Zip;
use numpy::{IntoPyArray, ToPyArray};
use numpy::{PyReadonlyArray1, PyReadonlyArray2};
use numpy::{PyArray2, PyArrayLike2, PyReadonlyArray1, PyReadonlyArray2};

use pyo3::exceptions::PyValueError;
use pyo3::prelude::*;
Expand Down Expand Up @@ -154,6 +154,15 @@ impl TraceToFidelity for c64 {
}
}

#[pyfunction]
#[pyo3(name = "trace_to_fid")]
/// Average gate fidelity is :math:`Fbar = (d + |Tr (Utarget \\cdot U^dag)|^2) / d(d+1)`
/// M. Horodecki, P. Horodecki and R. Horodecki, PRA 60, 1888 (1999)
fn py_trace_to_fid(trace: Complex64) -> PyResult<f64> {
let fid = trace.trace_to_fid();
Ok(fid)
}

fn decompose_two_qubit_product_gate(
special_unitary: ArrayView2<Complex64>,
) -> PyResult<(Array2<Complex64>, Array2<Complex64>, f64)> {
Expand Down Expand Up @@ -182,9 +191,31 @@ fn decompose_two_qubit_product_gate(
}
l.mapv_inplace(|x| x / det_l.sqrt());
let phase = det_l.arg() / 2.;

Ok((l, r, phase))
}

#[pyfunction]
#[pyo3(name = "decompose_two_qubit_product_gate")]
/// Decompose :math:`U = U_l \otimes U_r` where :math:`U \in SU(4)`,
/// and :math:`U_l,~U_r \in SU(2)`.
/// Args:
/// special_unitary_matrix: special unitary matrix to decompose
/// Raises:
/// QiskitError: if decomposition isn't possible.
fn py_decompose_two_qubit_product_gate(
py: Python,
special_unitary: PyArrayLike2<Complex64>,
) -> PyResult<(PyObject, PyObject, f64)> {
let view = special_unitary.as_array();
let (l, r, phase) = decompose_two_qubit_product_gate(view)?;
Ok((
l.into_pyarray_bound(py).unbind().into(),
r.into_pyarray_bound(py).unbind().into(),
phase,
))
}

fn __weyl_coordinates(unitary: MatRef<c64>) -> [f64; 3] {
let uscaled = scale(C1 / unitary.determinant().powf(0.25)) * unitary;
let uup = transform_from_magic_basis(uscaled);
Expand Down Expand Up @@ -301,6 +332,43 @@ fn rz_matrix(theta: f64) -> Array2<Complex64> {
array![[(-ilam2).exp(), C_ZERO], [C_ZERO, ilam2.exp()]]
}

/// Generates the array :math:`e^{(i a XX + i b YY + i c ZZ)}`
fn ud(a: f64, b: f64, c: f64) -> Array2<Complex64> {
array![
[
(IM * c).exp() * (a - b).cos(),
C_ZERO,
C_ZERO,
IM * (IM * c).exp() * (a - b).sin()
],
[
C_ZERO,
(M_IM * c).exp() * (a + b).cos(),
IM * (M_IM * c).exp() * (a + b).sin(),
C_ZERO
],
[
C_ZERO,
IM * (M_IM * c).exp() * (a + b).sin(),
(M_IM * c).exp() * (a + b).cos(),
C_ZERO
],
[
IM * (IM * c).exp() * (a - b).sin(),
C_ZERO,
C_ZERO,
(IM * c).exp() * (a - b).cos()
]
]
}

#[pyfunction]
#[pyo3(name = "Ud")]
fn py_ud(py: Python, a: f64, b: f64, c: f64) -> Py<PyArray2<Complex64>> {
let ud_mat = ud(a, b, c);
ud_mat.into_pyarray_bound(py).unbind()
}

fn compute_unitary(sequence: &TwoQubitSequenceVec, global_phase: f64) -> Array2<Complex64> {
let identity = aview2(&ONE_QUBIT_IDENTITY);
let phase = c64(0., global_phase).exp();
Expand Down Expand Up @@ -2278,9 +2346,12 @@ pub fn local_equivalence(weyl: PyReadonlyArray1<f64>) -> PyResult<[f64; 3]> {

pub fn two_qubit_decompose(m: &Bound<PyModule>) -> PyResult<()> {
m.add_wrapped(wrap_pyfunction!(_num_basis_gates))?;
m.add_wrapped(wrap_pyfunction!(py_decompose_two_qubit_product_gate))?;
m.add_wrapped(wrap_pyfunction!(two_qubit_decompose_up_to_diagonal))?;
m.add_wrapped(wrap_pyfunction!(two_qubit_local_invariants))?;
m.add_wrapped(wrap_pyfunction!(local_equivalence))?;
m.add_wrapped(wrap_pyfunction!(py_trace_to_fid))?;
m.add_wrapped(wrap_pyfunction!(py_ud))?;
m.add_class::<TwoQubitGateSequence>()?;
m.add_class::<TwoQubitWeylDecomposition>()?;
m.add_class::<Specialization>()?;
Expand Down
74 changes: 64 additions & 10 deletions crates/circuit/src/circuit_data.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@
use std::cell::OnceCell;

use crate::bit_data::BitData;
use crate::circuit_instruction::{CircuitInstruction, OperationFromPython};
use crate::circuit_instruction::{
CircuitInstruction, ExtraInstructionAttributes, OperationFromPython,
};
use crate::imports::{ANNOTATED_OPERATION, CLBIT, QUANTUM_CIRCUIT, QUBIT};
use crate::interner::{Interned, Interner};
use crate::operations::{Operation, OperationRef, Param, StandardGate};
Expand Down Expand Up @@ -157,7 +159,7 @@ impl CircuitData {
qubits,
clbits,
params,
extra_attrs: None,
extra_attrs: ExtraInstructionAttributes::default(),
#[cfg(feature = "cache_pygates")]
py_op: OnceCell::new(),
});
Expand All @@ -166,6 +168,63 @@ impl CircuitData {
Ok(res)
}

/// A constructor for CircuitData from an iterator of PackedInstruction objects
///
/// This is tpically useful when iterating over a CircuitData or DAGCircuit
/// to construct a new CircuitData from the iterator of PackedInstructions. As
/// such it requires that you have `BitData` and `Interner` objects to run. If
/// you just wish to build a circuit data from an iterator of instructions
/// the `from_packed_operations` or `from_standard_gates` constructor methods
/// are a better choice
///
/// # Args
///
/// * py: A GIL handle this is needed to instantiate Qubits in Python space
/// * qubits: The BitData to use for the new circuit's qubits
/// * clbits: The BitData to use for the new circuit's clbits
/// * qargs_interner: The interner for Qubit objects in the circuit. This must
/// contain all the Interned<Qubit> indices stored in the
/// PackedInstructions from `instructions`
/// * cargs_interner: The interner for Clbit objects in the circuit. This must
/// contain all the Interned<Clbit> indices stored in the
/// PackedInstructions from `instructions`
/// * Instructions: An iterator with items of type: `PyResult<PackedInstruction>`
/// that contais the instructions to insert in iterator order to the new
/// CircuitData. This returns a `PyResult` to facilitate the case where
/// you need to make a python copy (such as with `PackedOperation::py_deepcopy()`)
/// of the operation while iterating for constructing the new `CircuitData`. An
/// example of this use case is in `qiskit_circuit::converters::dag_to_circuit`.
/// * global_phase: The global phase value to use for the new circuit.
pub fn from_packed_instructions<I>(
py: Python,
qubits: BitData<Qubit>,
clbits: BitData<Clbit>,
qargs_interner: Interner<[Qubit]>,
cargs_interner: Interner<[Clbit]>,
instructions: I,
global_phase: Param,
) -> PyResult<Self>
where
I: IntoIterator<Item = PyResult<PackedInstruction>>,
{
let instruction_iter = instructions.into_iter();
let mut res = CircuitData {
data: Vec::with_capacity(instruction_iter.size_hint().0),
qargs_interner,
cargs_interner,
qubits,
clbits,
param_table: ParameterTable::new(),
global_phase,
};

for inst in instruction_iter {
res.data.push(inst?);
res.track_instruction_parameters(py, res.data.len() - 1)?;
}
Ok(res)
}

/// An alternate constructor to build a new `CircuitData` from an iterator
/// of standard gates. This can be used to build a circuit from a sequence
/// of standard gates, such as for a `StandardGate` definition or circuit
Expand Down Expand Up @@ -209,7 +268,7 @@ impl CircuitData {
qubits,
clbits: no_clbit_index,
params,
extra_attrs: None,
extra_attrs: ExtraInstructionAttributes::default(),
#[cfg(feature = "cache_pygates")]
py_op: OnceCell::new(),
});
Expand Down Expand Up @@ -267,7 +326,7 @@ impl CircuitData {
qubits,
clbits: no_clbit_index,
params,
extra_attrs: None,
extra_attrs: ExtraInstructionAttributes::default(),
#[cfg(feature = "cache_pygates")]
py_op: OnceCell::new(),
});
Expand Down Expand Up @@ -626,12 +685,7 @@ impl CircuitData {
#[pyo3(signature = (func))]
pub fn map_nonstandard_ops(&mut self, py: Python<'_>, func: &Bound<PyAny>) -> PyResult<()> {
for inst in self.data.iter_mut() {
if inst.op.try_standard_gate().is_some()
&& !inst
.extra_attrs
.as_ref()
.is_some_and(|attrs| attrs.condition.is_some())
{
if inst.op.try_standard_gate().is_some() && inst.extra_attrs.condition().is_none() {
continue;
}
let py_op = func.call1((inst.unpack_py_op(py)?,))?;
Expand Down
Loading

0 comments on commit 3d1a6d4

Please sign in to comment.