Skip to content

Commit

Permalink
Make BitData private to qiskit-circuit.
Browse files Browse the repository at this point in the history
The BitData type was only ever intended as an implementation detail
of CircuitData and DAGCircuit. As such, its visibility has been
downgraded from pub to pub(crate). By limiting visibility of BitData
to the circuit crate, we can fully encapsulate the storage type of
Qubit, Clbit, and dagcircuit::Var. We also encourage the accelerate
crate to avoid writing code which depends on conversion to and from
the Python bit representations (there were only two existing places
where BitData was being used in accelerate, and it'd be nice to
avoid any more.
  • Loading branch information
kevinhartman committed Oct 28, 2024
1 parent fbe75e8 commit 698c226
Show file tree
Hide file tree
Showing 8 changed files with 144 additions and 128 deletions.
5 changes: 4 additions & 1 deletion crates/accelerate/src/circuit_library/pauli_feature_map.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,10 @@ fn pauli_evolution(
// Get pairs of (pauli, qubit) that are active, i.e. that are not the identity. Note that
// the rest of the code also works if there are only identities, in which case we will
// effectively return an empty iterator.
let qubits = indices.iter().map(|i| Qubit::new(*i as usize)).collect_vec();
let qubits = indices
.iter()
.map(|i| Qubit::new(*i as usize))
.collect_vec();
let binding = pauli.to_lowercase(); // lowercase for convenience
let active_paulis = binding
.as_str()
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 @@ -153,7 +153,7 @@ pub(crate) fn analyze_commutations(
// we know all wires are of type Wire::Qubit, since in analyze_commutations_inner
// we only iterater over the qubits
let py_wire = match wire {
Wire::Qubit(q) => dag.qubits().get(q).unwrap().to_object(py),
Wire::Qubit(q) => dag.get_qubit(py, q).unwrap(),
_ => return Err(PyValueError::new_err("Unexpected wire type.")),
};

Expand All @@ -176,7 +176,7 @@ pub(crate) fn analyze_commutations(
// Then we add the {(node, wire): index} dictionary
for ((node_index, wire), index) in node_indices {
let py_wire = match wire {
Wire::Qubit(q) => dag.qubits().get(q).unwrap().to_object(py),
Wire::Qubit(q) => dag.get_qubit(py, q).unwrap(),
_ => return Err(PyValueError::new_err("Unexpected wire type.")),
};
out_dict.set_item((dag.get_node(py, node_index)?, py_wire), index)?;
Expand Down
152 changes: 73 additions & 79 deletions crates/accelerate/src/convert_2q_block_matrix.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,104 +10,98 @@
// 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;

use num_complex::Complex64;
use numpy::ndarray::linalg::kron;
use numpy::ndarray::{aview2, Array2, ArrayView2};
use numpy::{IntoPyArray, PyArray2, PyReadonlyArray2};
use smallvec::SmallVec;
use numpy::ndarray::{Array2, ArrayView2};
use numpy::PyArray2;
use pyo3::exceptions::PyNotImplementedError;

use qiskit_circuit::bit_data::BitData;
use qiskit_circuit::circuit_instruction::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;

use crate::QiskitError;

fn get_matrix_from_inst<'py>(
py: Python<'py>,
inst: &'py CircuitInstruction,
) -> PyResult<Array2<Complex64>> {
if let Some(mat) = inst.operation.matrix(&inst.params) {
Ok(mat)
} else if inst.operation.try_standard_gate().is_some() {
Err(QiskitError::new_err(
"Parameterized gates can't be consolidated",
))
} else {
Ok(QI_OPERATOR
.get_bound(py)
.call1((inst.get_operation(py)?,))?
.getattr(intern!(py, "data"))?
.extract::<PyReadonlyArray2<Complex64>>()?
.as_array()
.to_owned())
}
}
// fn get_matrix_from_inst<'py>(
// py: Python<'py>,
// inst: &'py CircuitInstruction,
// ) -> PyResult<Array2<Complex64>> {
// if let Some(mat) = inst.operation.matrix(&inst.params) {
// Ok(mat)
// } else if inst.operation.try_standard_gate().is_some() {
// Err(QiskitError::new_err(
// "Parameterized gates can't be consolidated",
// ))
// } else {
// Ok(QI_OPERATOR
// .get_bound(py)
// .call1((inst.get_operation(py)?,))?
// .getattr(intern!(py, "data"))?
// .extract::<PyReadonlyArray2<Complex64>>()?
// .as_array()
// .to_owned())
// }
// }

/// 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<PyRef<DAGOpNode>>,
block_index_map_dict: &Bound<PyDict>,
_py: Python,
_op_list: Vec<PyRef<DAGOpNode>>,
_block_index_map_dict: &Bound<PyDict>,
) -> PyResult<Py<PyArray2<Complex64>>> {
Err(PyNotImplementedError::new_err(
"needs rebase on https://github.com/Qiskit/qiskit/pull/13368",
))
// 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 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,
[1, 0] => change_basis(input_matrix.view()),
[] => Array2::eye(4),
_ => unreachable!(),
};
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.view())),
[] => Some(Array2::eye(4)),
_ => None,
};
matrix = match result {
Some(result) => result.dot(&matrix),
None => op_matrix.dot(&matrix),
};
}
Ok(matrix.into_pyarray_bound(py).unbind())
// 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 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,
// [1, 0] => change_basis(input_matrix.view()),
// [] => Array2::eye(4),
// _ => unreachable!(),
// };
// 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.view())),
// [] => Some(Array2::eye(4)),
// _ => None,
// };
// matrix = match result {
// Some(result) => result.dot(&matrix),
// None => op_matrix.dot(&matrix),
// };
// }
// Ok(matrix.into_pyarray_bound(py).unbind())
}

/// Switches the order of qubits in a two qubit operation.
Expand Down
2 changes: 1 addition & 1 deletion crates/circuit/src/bit_data.rs
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ impl PartialEq for BitAsKey {
impl Eq for BitAsKey {}

#[derive(Clone, Debug)]
pub struct BitData<T> {
pub(crate) struct BitData<T> {
/// The public field name (i.e. `qubits` or `clbits`).
description: String,
/// Registered Python bits.
Expand Down
48 changes: 44 additions & 4 deletions crates/circuit/src/circuit_data.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ use pyo3::pybacked::PyBackedStr;
use pyo3::types::{IntoPyDict, PyDict, PyList, PySet, PyTuple, PyType};
use pyo3::{import_exception, intern, PyTraverseError, PyVisit};

use crate::dag_circuit::DAGCircuit;
use hashbrown::{HashMap, HashSet};
use indexmap::IndexMap;
use smallvec::SmallVec;
Expand Down Expand Up @@ -929,9 +930,48 @@ impl CircuitData {
Ok(res)
}

pub fn from_dag(py: Python, dag: &DAGCircuit, copy_operations: bool) -> PyResult<Self> {
use crate::dag_circuit::NodeType;
Self::from_packed_instructions(
py,
dag.qubits().clone(),
dag.clbits().clone(),
dag.qargs_interner().clone(),
dag.cargs_interner().clone(),
dag.topological_op_nodes()?.map(|node_index| {
let NodeType::Operation(ref instr) = dag.dag()[node_index] else {
unreachable!(
"The received node from topological_op_nodes() is not an Operation node."
)
};
if copy_operations {
let op = instr.op.py_deepcopy(py, None)?;
Ok(PackedInstruction {
op,
qubits: instr.qubits,
clbits: instr.clbits,
params: Some(Box::new(
instr
.params_view()
.iter()
.map(|param| param.clone_ref(py))
.collect(),
)),
extra_attrs: instr.extra_attrs.clone(),
#[cfg(feature = "cache_pygates")]
py_op: OnceCell::new(),
})
} else {
Ok(instr.clone())
}
}),
dag.get_global_phase(),
)
}

/// A constructor for CircuitData from an iterator of PackedInstruction objects
///
/// This is tpically useful when iterating over a CircuitData or DAGCircuit
/// This is typically 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
Expand All @@ -956,7 +996,7 @@ impl CircuitData {
/// 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>(
pub(crate) fn from_packed_instructions<I>(
py: Python,
qubits: BitData<Qubit>,
clbits: BitData<Clbit>,
Expand Down Expand Up @@ -1254,12 +1294,12 @@ impl CircuitData {
}

/// Returns an immutable view of the Qubits registered in the circuit
pub fn qubits(&self) -> &BitData<Qubit> {
pub(crate) fn qubits(&self) -> &BitData<Qubit> {
&self.qubits
}

/// Returns an immutable view of the Classical bits registered in the circuit
pub fn clbits(&self) -> &BitData<Clbit> {
pub(crate) fn clbits(&self) -> &BitData<Clbit> {
&self.clbits
}

Expand Down
39 changes: 2 additions & 37 deletions crates/circuit/src/converters.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,7 @@ use pyo3::{
};

use crate::circuit_data::CircuitData;
use crate::dag_circuit::{DAGCircuit, NodeType};
use crate::packed_instruction::PackedInstruction;
use crate::dag_circuit::DAGCircuit;

/// An extractable representation of a QuantumCircuit reserved only for
/// conversion purposes.
Expand Down Expand Up @@ -96,41 +95,7 @@ pub fn dag_to_circuit(
dag: &DAGCircuit,
copy_operations: bool,
) -> PyResult<CircuitData> {
CircuitData::from_packed_instructions(
py,
dag.qubits().clone(),
dag.clbits().clone(),
dag.qargs_interner().clone(),
dag.cargs_interner().clone(),
dag.topological_op_nodes()?.map(|node_index| {
let NodeType::Operation(ref instr) = dag.dag()[node_index] else {
unreachable!(
"The received node from topological_op_nodes() is not an Operation node."
)
};
if copy_operations {
let op = instr.op.py_deepcopy(py, None)?;
Ok(PackedInstruction {
op,
qubits: instr.qubits,
clbits: instr.clbits,
params: Some(Box::new(
instr
.params_view()
.iter()
.map(|param| param.clone_ref(py))
.collect(),
)),
extra_attrs: instr.extra_attrs.clone(),
#[cfg(feature = "cache_pygates")]
py_op: OnceCell::new(),
})
} else {
Ok(instr.clone())
}
}),
dag.get_global_phase(),
)
CircuitData::from_dag(py, dag, copy_operations)
}

pub fn converters(m: &Bound<PyModule>) -> PyResult<()> {
Expand Down
20 changes: 17 additions & 3 deletions crates/circuit/src/dag_circuit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4975,19 +4975,19 @@ impl DAGCircuit {

/// Returns an immutable view of the Qubits registered in the circuit
#[inline(always)]
pub fn qubits(&self) -> &BitData<Qubit> {
pub(crate) fn qubits(&self) -> &BitData<Qubit> {
&self.qubits
}

/// Returns an immutable view of the Classical bits registered in the circuit
#[inline(always)]
pub fn clbits(&self) -> &BitData<Clbit> {
pub(crate) fn clbits(&self) -> &BitData<Clbit> {
&self.clbits
}

/// Returns an immutable view of the Variable wires registered in the circuit
#[inline(always)]
pub fn vars(&self) -> &BitData<Var> {
pub(crate) fn vars(&self) -> &BitData<Var> {
&self.vars
}

Expand Down Expand Up @@ -6192,6 +6192,20 @@ impl DAGCircuit {
Ok(out_map)
}

/// Retrieve a Python qubit given its [Qubit] within the DAG.
///
/// The provided [Qubit] must be from this [DAGCircuit].
pub fn get_qubit<'py>(&self, py: Python<'py>, qubit: Qubit) -> Option<Bound<'py, PyAny>> {
self.qubits.get(qubit).map(|v| v.bind(py).clone())
}

/// Retrieve a Python clbit given its [Clbit] within the DAG.
///
/// The provided [Clbit] must be from this [DAGCircuit].
pub fn get_clbit<'py>(&self, py: Python<'py>, clbit: Clbit) -> Option<Bound<'py, PyAny>> {
self.clbits.get(clbit).map(|v| v.bind(py).clone())
}

/// Retrieve a variable given its unique [Var] key within the DAG.
///
/// The provided [Var] must be from this [DAGCircuit].
Expand Down
Loading

0 comments on commit 698c226

Please sign in to comment.