Skip to content

Commit

Permalink
Merge branch 'move-target' into move-target-nullable
Browse files Browse the repository at this point in the history
  • Loading branch information
raynelfss committed Jul 9, 2024
2 parents ab0ba29 + 9e1e111 commit a703647
Show file tree
Hide file tree
Showing 31 changed files with 1,434 additions and 504 deletions.
12 changes: 6 additions & 6 deletions .github/workflows/wheels.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ jobs:
with:
components: llvm-tools-preview
- name: Build wheels
uses: pypa/[email protected].1
uses: pypa/[email protected].2
env:
CIBW_BEFORE_BUILD: 'bash ./tools/build_pgo.sh /tmp/pgo-data/merged.profdata'
CIBW_BEFORE_BUILD_WINDOWS: 'bash ./tools/build_pgo.sh /tmp/pgo-data/merged.profdata && cp /tmp/pgo-data/merged.profdata ~/.'
Expand Down Expand Up @@ -58,7 +58,7 @@ jobs:
with:
components: llvm-tools-preview
- name: Build wheels
uses: pypa/[email protected].1
uses: pypa/[email protected].2
env:
CIBW_BEFORE_ALL: rustup target add aarch64-apple-darwin
CIBW_BUILD: cp38-macosx_universal2 cp38-macosx_arm64
Expand Down Expand Up @@ -87,7 +87,7 @@ jobs:
with:
components: llvm-tools-preview
- name: Build wheels
uses: pypa/[email protected].1
uses: pypa/[email protected].2
env:
CIBW_SKIP: 'pp* cp36-* cp37-* *musllinux* *amd64 *x86_64'
- uses: actions/upload-artifact@v4
Expand Down Expand Up @@ -133,7 +133,7 @@ jobs:
with:
platforms: all
- name: Build wheels
uses: pypa/[email protected].1
uses: pypa/[email protected].2
env:
CIBW_ARCHS_LINUX: s390x
CIBW_TEST_SKIP: "cp*"
Expand Down Expand Up @@ -167,7 +167,7 @@ jobs:
with:
platforms: all
- name: Build wheels
uses: pypa/[email protected].1
uses: pypa/[email protected].2
env:
CIBW_ARCHS_LINUX: ppc64le
CIBW_TEST_SKIP: "cp*"
Expand Down Expand Up @@ -201,7 +201,7 @@ jobs:
with:
platforms: all
- name: Build wheels
uses: pypa/[email protected].1
uses: pypa/[email protected].2
env:
CIBW_ARCHS_LINUX: aarch64
- uses: actions/upload-artifact@v4
Expand Down
104 changes: 96 additions & 8 deletions crates/accelerate/src/convert_2q_block_matrix.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@
// copyright notice, and modified files need to carry a notice indicating
// that they have been altered from the originals.

use pyo3::intern;
use pyo3::prelude::*;
use pyo3::types::PyDict;
use pyo3::wrap_pyfunction;
use pyo3::Python;

Expand All @@ -20,32 +22,84 @@ use numpy::ndarray::{aview2, Array2, ArrayView2};
use numpy::{IntoPyArray, PyArray2, PyReadonlyArray2};
use smallvec::SmallVec;

use qiskit_circuit::bit_data::BitData;
use qiskit_circuit::circuit_instruction::{operation_type_to_py, CircuitInstruction};
use qiskit_circuit::dag_node::DAGOpNode;
use qiskit_circuit::gate_matrix::ONE_QUBIT_IDENTITY;
use qiskit_circuit::imports::QI_OPERATOR;
use qiskit_circuit::operations::{Operation, OperationType};

use crate::QiskitError;

fn get_matrix_from_inst<'py>(
py: Python<'py>,
inst: &'py CircuitInstruction,
) -> PyResult<Array2<Complex64>> {
match inst.operation.matrix(&inst.params) {
Some(mat) => Ok(mat),
None => match inst.operation {
OperationType::Standard(_) => Err(QiskitError::new_err(
"Parameterized gates can't be consolidated",
)),
OperationType::Gate(_) => Ok(QI_OPERATOR
.get_bound(py)
.call1((operation_type_to_py(py, inst)?,))?
.getattr(intern!(py, "data"))?
.extract::<PyReadonlyArray2<Complex64>>()?
.as_array()
.to_owned()),
_ => unreachable!("Only called for unitary ops"),
},
}
}

/// Return the matrix Operator resulting from a block of Instructions.
#[pyfunction]
#[pyo3(text_signature = "(op_list, /")]
pub fn blocks_to_matrix(
py: Python,
op_list: Vec<(PyReadonlyArray2<Complex64>, SmallVec<[u8; 2]>)>,
op_list: Vec<PyRef<DAGOpNode>>,
block_index_map_dict: &Bound<PyDict>,
) -> PyResult<Py<PyArray2<Complex64>>> {
// Build a BitData in block_index_map_dict order. block_index_map_dict is a dict of bits to
// indices mapping the order of the qargs in the block. There should only be 2 entries since
// there are only 2 qargs here (e.g. `{Qubit(): 0, Qubit(): 1}`) so we need to ensure that
// we added the qubits to bit data in the correct index order.
let mut index_map: Vec<PyObject> = (0..block_index_map_dict.len()).map(|_| py.None()).collect();
for bit_tuple in block_index_map_dict.items() {
let (bit, index): (PyObject, usize) = bit_tuple.extract()?;
index_map[index] = bit;
}
let mut bit_map: BitData<u32> = BitData::new(py, "qargs".to_string());
for bit in index_map {
bit_map.add(py, bit.bind(py), true)?;
}
let identity = aview2(&ONE_QUBIT_IDENTITY);
let input_matrix = op_list[0].0.as_array();
let mut matrix: Array2<Complex64> = match op_list[0].1.as_slice() {
let first_node = &op_list[0];
let input_matrix = get_matrix_from_inst(py, &first_node.instruction)?;
let mut matrix: Array2<Complex64> = match bit_map
.map_bits(first_node.instruction.qubits.bind(py).iter())?
.collect::<Vec<_>>()
.as_slice()
{
[0] => kron(&identity, &input_matrix),
[1] => kron(&input_matrix, &identity),
[0, 1] => input_matrix.to_owned(),
[1, 0] => change_basis(input_matrix),
[0, 1] => input_matrix,
[1, 0] => change_basis(input_matrix.view()),
[] => Array2::eye(4),
_ => unreachable!(),
};
for (op_matrix, q_list) in op_list.into_iter().skip(1) {
let op_matrix = op_matrix.as_array();
for node in op_list.into_iter().skip(1) {
let op_matrix = get_matrix_from_inst(py, &node.instruction)?;
let q_list = bit_map
.map_bits(node.instruction.qubits.bind(py).iter())?
.map(|x| x as u8)
.collect::<SmallVec<[u8; 2]>>();

let result = match q_list.as_slice() {
[0] => Some(kron(&identity, &op_matrix)),
[1] => Some(kron(&op_matrix, &identity)),
[1, 0] => Some(change_basis(op_matrix)),
[1, 0] => Some(change_basis(op_matrix.view())),
[] => Some(Array2::eye(4)),
_ => None,
};
Expand All @@ -71,8 +125,42 @@ pub fn change_basis(matrix: ArrayView2<Complex64>) -> Array2<Complex64> {
trans_matrix
}

#[pyfunction]
pub fn collect_2q_blocks_filter(node: &Bound<PyAny>) -> Option<bool> {
match node.downcast::<DAGOpNode>() {
Ok(bound_node) => {
let node = bound_node.borrow();
match &node.instruction.operation {
OperationType::Standard(gate) => Some(
gate.num_qubits() <= 2
&& node
.instruction
.extra_attrs
.as_ref()
.and_then(|attrs| attrs.condition.as_ref())
.is_none()
&& !node.is_parameterized(),
),
OperationType::Gate(gate) => Some(
gate.num_qubits() <= 2
&& node
.instruction
.extra_attrs
.as_ref()
.and_then(|attrs| attrs.condition.as_ref())
.is_none()
&& !node.is_parameterized(),
),
_ => Some(false),
}
}
Err(_) => None,
}
}

#[pymodule]
pub fn convert_2q_block_matrix(m: &Bound<PyModule>) -> PyResult<()> {
m.add_wrapped(wrap_pyfunction!(blocks_to_matrix))?;
m.add_wrapped(wrap_pyfunction!(collect_2q_blocks_filter))?;
Ok(())
}
19 changes: 5 additions & 14 deletions crates/accelerate/src/synthesis/permutation/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ use pyo3::exceptions::PyValueError;
use pyo3::prelude::*;
use std::vec::Vec;

use qiskit_circuit::slice::{PySequenceIndex, PySequenceIndexError, SequenceIndex};
use qiskit_circuit::slice::PySequenceIndex;

pub fn validate_permutation(pattern: &ArrayView1<i64>) -> PyResult<()> {
let n = pattern.len();
Expand Down Expand Up @@ -120,11 +120,8 @@ pub fn pattern_to_cycles(pattern: &ArrayView1<usize>) -> Vec<Vec<usize>> {
/// Periodic (or Python-like) access to a vector.
/// Util used below in ``decompose_cycles``.
#[inline]
fn pget(vec: &[usize], index: isize) -> Result<usize, PySequenceIndexError> {
let SequenceIndex::Int(wrapped) = PySequenceIndex::Int(index).with_len(vec.len())? else {
unreachable!()
};
Ok(vec[wrapped])
fn pget(vec: &[usize], index: isize) -> usize {
vec[PySequenceIndex::convert_idx(index, vec.len()).unwrap()]
}

/// Given a disjoint cycle decomposition of a permutation pattern (see the function
Expand All @@ -138,16 +135,10 @@ pub fn decompose_cycles(cycles: &Vec<Vec<usize>>) -> Vec<(usize, usize)> {
let length = cycle.len() as isize;

for idx in 0..(length - 1) / 2 {
swaps.push((
pget(cycle, idx - 1).unwrap(),
pget(cycle, length - 3 - idx).unwrap(),
));
swaps.push((pget(cycle, idx - 1), pget(cycle, length - 3 - idx)));
}
for idx in 0..length / 2 {
swaps.push((
pget(cycle, idx - 1).unwrap(),
pget(cycle, length - 2 - idx).unwrap(),
));
swaps.push((pget(cycle, idx - 1), pget(cycle, length - 2 - idx)));
}
}

Expand Down
8 changes: 6 additions & 2 deletions crates/circuit/src/bit_data.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ impl PartialEq for BitAsKey {
impl Eq for BitAsKey {}

#[derive(Clone, Debug)]
pub(crate) struct BitData<T> {
pub struct BitData<T> {
/// The public field name (i.e. `qubits` or `clbits`).
description: String,
/// Registered Python bits.
Expand All @@ -81,7 +81,7 @@ pub(crate) struct BitData<T> {
cached: Py<PyList>,
}

pub(crate) struct BitNotFoundError<'py>(pub(crate) Bound<'py, PyAny>);
pub struct BitNotFoundError<'py>(pub(crate) Bound<'py, PyAny>);

impl<'py> From<BitNotFoundError<'py>> for PyErr {
fn from(error: BitNotFoundError) -> Self {
Expand Down Expand Up @@ -111,6 +111,10 @@ where
self.bits.len()
}

pub fn is_empty(&self) -> bool {
self.bits.is_empty()
}

/// Gets a reference to the underlying vector of Python bits.
#[inline]
pub fn bits(&self) -> &Vec<PyObject> {
Expand Down
37 changes: 31 additions & 6 deletions crates/circuit/src/circuit_instruction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ use pyo3::{intern, IntoPy, PyObject, PyResult};
use smallvec::{smallvec, SmallVec};

use crate::imports::{
get_std_gate_class, populate_std_gate_map, GATE, INSTRUCTION, OPERATION,
get_std_gate_class, populate_std_gate_map, CONTROLLED_GATE, GATE, INSTRUCTION, OPERATION,
SINGLETON_CONTROLLED_GATE, SINGLETON_GATE, WARNINGS_WARN,
};
use crate::interner::Index;
Expand Down Expand Up @@ -884,24 +884,49 @@ pub fn convert_py_to_operation_type(
Ok(stdgate) => stdgate.extract().ok().unwrap_or_default(),
Err(_) => None,
};
// If the input instruction is a standard gate and a singleton instance
// If the input instruction is a standard gate and a singleton instance,
// we should check for mutable state. A mutable instance should be treated
// as a custom gate not a standard gate because it has custom properties.
//
// In the futuer we can revisit this when we've dropped `duration`, `unit`,
// Controlled gates with non-default control states are also considered
// custom gates even if a standard representation exists for the default
// control state.

// In the future we can revisit this when we've dropped `duration`, `unit`,
// and `condition` from the api as we should own the label in the
// `CircuitInstruction`. The other piece here is for controlled gates there
// is the control state, so for `SingletonControlledGates` we'll still need
// this check.
if standard.is_some() {
let mutable: bool = py_op.getattr(py, intern!(py, "mutable"))?.extract(py)?;
if mutable
// The default ctrl_states are the all 1 state and None.
// These are the only cases where controlled gates can be standard.
let is_default_ctrl_state = || -> PyResult<bool> {
match py_op.getattr(py, intern!(py, "ctrl_state")) {
Ok(c_state) => match c_state.extract::<Option<i32>>(py) {
Ok(c_state_int) => match c_state_int {
Some(c_int) => {
let qubits: u32 =
py_op.getattr(py, intern!(py, "num_qubits"))?.extract(py)?;
Ok(c_int == (2_i32.pow(qubits - 1) - 1))
}
None => Ok(true),
},
Err(_) => Ok(false),
},
Err(_) => Ok(false),
}
};

if (mutable
&& (py_op_bound.is_instance(SINGLETON_GATE.get_bound(py))?
|| py_op_bound.is_instance(SINGLETON_CONTROLLED_GATE.get_bound(py))?)
|| py_op_bound.is_instance(SINGLETON_CONTROLLED_GATE.get_bound(py))?))
|| (py_op_bound.is_instance(CONTROLLED_GATE.get_bound(py))?
&& !is_default_ctrl_state()?)
{
standard = None;
}
}

if let Some(op) = standard {
let base_class = op_type.to_object(py);
populate_std_gate_map(py, op, base_class);
Expand Down
Loading

0 comments on commit a703647

Please sign in to comment.