Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[DAGCircuit Oxidation] Port DAGCircuit to Rust #12550

Merged
merged 70 commits into from
Aug 23, 2024
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
70 commits
Select commit Hold shift + click to select a range
c911205
Port DAGCircuit to Rust
kevinhartman Aug 12, 2024
1e4e6f3
Update visual mpl circuit drawer references
mtreinish Aug 12, 2024
463b86a
Ensure DAGNode.sort_key is always a string
mtreinish Aug 12, 2024
01c06e5
Merge branch 'main' into oxidize-dag
mtreinish Aug 12, 2024
f6b27ff
Make Python argument first in Param::eq and Param::is_close
mtreinish Aug 12, 2024
0e62ad0
Merge branch 'main' into oxidize-dag
mtreinish Aug 12, 2024
aaf38b9
Fix merge conflict with #12943
mtreinish Aug 12, 2024
28f6de1
Add release notes and test for invalid args on apply methods
mtreinish Aug 12, 2024
11c52ab
Restore `inplace` argument functionality for substitute_node()
mtreinish Aug 12, 2024
d68743f
Revert needless dict() cast on metadata in dag_to_circuit()
mtreinish Aug 12, 2024
e07f3d5
Add code comment for DAGOpNode.__eq__ parameter checking
mtreinish Aug 12, 2024
30a4a1a
Raise a ValueError on DAGNode creation with invalid index
mtreinish Aug 12, 2024
6dec768
Use macro argument to set python getter/setter name
mtreinish Aug 12, 2024
1de3290
Remove Ord and PartialOrd derives from interner::Index
mtreinish Aug 12, 2024
5c6f006
Fix missing nodes in matplotlib drawer.
kevinhartman Aug 12, 2024
b90b660
Revert "Update visual mpl circuit drawer references"
mtreinish Aug 13, 2024
9a6f953
Update visual mpl circuit drawer references for control flow circuits
mtreinish Aug 13, 2024
701c980
Fix edge cases in DAGOpNode.__eq__
mtreinish Aug 13, 2024
464e87b
Remove Param::add() for global phase addition
mtreinish Aug 13, 2024
7f1b451
More complete fix for matplotlib drawer.
kevinhartman Aug 13, 2024
6aab4a6
Revert "Update visual mpl circuit drawer references for control flow …
kevinhartman Aug 13, 2024
e5e57c6
Unify rayon versions in workspace
mtreinish Aug 13, 2024
584ee9b
Remove unused _GLOBAL_NID.
kevinhartman Aug 13, 2024
bdeb5f6
Merge branch 'main' into oxidize-dag
mtreinish Aug 13, 2024
1d249ab
Use global monotonic ID counter for ids in drawer
mtreinish Aug 14, 2024
0c66934
Re-add missing documentation
jakelishman Aug 14, 2024
c6ff31b
Merge remote-tracking branch 'ibm/main' into oxidize-dag
jakelishman Aug 15, 2024
1960ede
Remove unused BitData iterator stuff.
kevinhartman Aug 14, 2024
1d71204
Make types, dag, and bit count methods public
mtreinish Aug 16, 2024
90b92f8
Make Wire pickle serialization explicit
mtreinish Aug 16, 2024
dcef2a4
Make py token usage explicit in _VarIndexMap
mtreinish Aug 16, 2024
c4d6bd2
Make all pub(crate) visibility pub
mtreinish Aug 16, 2024
396dcaa
Remove unused method
mtreinish Aug 16, 2024
adf0c91
Reorganize code structure around PyVariableMapper and BitLocations
mtreinish Aug 16, 2024
a670439
Add missing var wires to .get_wires() method
mtreinish Aug 16, 2024
555f254
Raise TypeError not ValueError for invalid input to set_global_phase
mtreinish Aug 16, 2024
38b0cd4
De-duplicate check logic for op node adding methods
mtreinish Aug 16, 2024
7e43071
Improve collect_1q_runs() filter function
mtreinish Aug 16, 2024
4611d15
Merge branch 'main' into oxidize-dag
mtreinish Aug 16, 2024
7ff00ab
Use swap_remove instead of shift_remove
mtreinish Aug 16, 2024
22d0d3e
Combine input and output maps into single mapping
mtreinish Aug 16, 2024
6e29375
Merge remote-tracking branch 'origin/main' into oxidize-dag
mtreinish Aug 16, 2024
d8a6d78
Ensure we account for clbits in depth() short circuit check
mtreinish Aug 16, 2024
cda5f1d
Also account for Vars in DAGCircuit.width()
mtreinish Aug 16, 2024
b13fe3a
Remove duplicated _get_node() method
mtreinish Aug 16, 2024
6d3104f
Handle Var wires in classical_predecessors
mtreinish Aug 16, 2024
2abe084
Remove stray comment
mtreinish Aug 16, 2024
73661a9
Use Operation::control_flow() instead of isinstance checking
mtreinish Aug 16, 2024
694d78e
Use &str for increment_op and decrement_op
mtreinish Aug 16, 2024
a8a10db
Also include vars in depth short circuit
mtreinish Aug 16, 2024
6a7d950
Fix typing for controlflow name lookup in count_ops
mtreinish Aug 19, 2024
8a107fe
Fix .properties() method to include operations field
mtreinish Aug 19, 2024
2769831
Add missing Var wire handling to py_nodes_on_wire
mtreinish Aug 19, 2024
8e6a209
Add back optimization to avoid isinstance in op_nodes
mtreinish Aug 19, 2024
cd43af5
Simplify/deduplicate __eq__ method
mtreinish Aug 19, 2024
99d016a
Merge branch 'main' into oxidize-dag
mtreinish Aug 19, 2024
f48b378
Invalidate cached py op when needed in substitute_node_with_dag
mtreinish Aug 19, 2024
d3677b3
Copy-editing suggestions for release notes
mtreinish Aug 19, 2024
e89e715
Merge branch 'main' into oxidize-dag
mtreinish Aug 19, 2024
39064c2
Fix and simplify separable_circuits()
mtreinish Aug 19, 2024
41f0735
Merge branch 'main' into oxidize-dag
mtreinish Aug 19, 2024
70e98b6
Add clbit removal test
mtreinish Aug 20, 2024
818681a
Move to using a Vec<[NodeIndex; 2]> for io maps
mtreinish Aug 22, 2024
32f5762
Merge remote-tracking branch 'origin/main' into oxidize-dag
mtreinish Aug 22, 2024
415e9b7
Make add_clbits() signature the same as add_qubits()
mtreinish Aug 22, 2024
333172f
Add attribution comment to num_tensor_factors() method
mtreinish Aug 22, 2024
54a534d
Add py argument to add_declared_var()
mtreinish Aug 22, 2024
cc175c5
Merge remote-tracking branch 'ibm/main' into oxidize-dag
jakelishman Aug 23, 2024
4d68875
Remove unnecessarily Python-space check
jakelishman Aug 23, 2024
1ad514c
Correct typo in `to_pickle` method
jakelishman Aug 23, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions Cargo.lock

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

5 changes: 4 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,16 @@ license = "Apache-2.0"
[workspace.dependencies]
bytemuck = "1.16"
indexmap.version = "2.3.0"
hashbrown.version = "0.14.0"
hashbrown.version = "0.14.5"
num-bigint = "0.4"
num-complex = "0.4"
ndarray = "^0.15.6"
numpy = "0.21.0"
smallvec = "1.13"
thiserror = "1.0"
rustworkx-core = "0.15"
approx = "0.5"
itertools = "0.13.0"
ahash = "0.8.11"

# Most of the crates don't need the feature `extension-module`, since only `qiskit-pyext` builds an
Expand Down
6 changes: 3 additions & 3 deletions crates/accelerate/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,10 @@ rand_distr = "0.4.3"
ahash.workspace = true
num-traits = "0.2"
num-complex.workspace = true
rustworkx-core.workspace = true
num-bigint.workspace = true
rustworkx-core = "0.15"
faer = "0.19.1"
itertools = "0.13.0"
itertools.workspace = true
qiskit-circuit.workspace = true
thiserror.workspace = true

Expand All @@ -38,7 +38,7 @@ workspace = true
features = ["rayon", "approx-0_5"]

[dependencies.approx]
version = "0.5"
workspace = true
features = ["num-complex"]

[dependencies.hashbrown]
Expand Down
26 changes: 2 additions & 24 deletions crates/accelerate/src/convert_2q_block_matrix.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,15 +27,15 @@ 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, OperationRef};
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.op().matrix(&inst.params) {
if let Some(mat) = inst.operation.matrix(&inst.params) {
Ok(mat)
} else if inst.operation.try_standard_gate().is_some() {
Err(QiskitError::new_err(
Expand Down Expand Up @@ -124,29 +124,7 @@ pub fn change_basis(matrix: ArrayView2<Complex64>) -> Array2<Complex64> {
trans_matrix
}

#[pyfunction]
pub fn collect_2q_blocks_filter(node: &Bound<PyAny>) -> Option<bool> {
let Ok(node) = node.downcast::<DAGOpNode>() else {
return None;
};
let node = node.borrow();
match node.instruction.op() {
gate @ (OperationRef::Standard(_) | OperationRef::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),
}
}

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(())
}
24 changes: 4 additions & 20 deletions crates/accelerate/src/euler_one_qubit_decomposer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -743,7 +743,7 @@ pub fn compute_error_list(
.iter()
.map(|node| {
(
node.instruction.op().name().to_string(),
node.instruction.operation.name().to_string(),
smallvec![], // Params not needed in this path
)
})
Expand Down Expand Up @@ -988,10 +988,11 @@ pub fn optimize_1q_gates_decomposition(
.iter()
.map(|node| {
if let Some(err_map) = error_map {
error *= compute_error_term(node.instruction.op().name(), err_map, qubit)
error *=
compute_error_term(node.instruction.operation.name(), err_map, qubit)
}
node.instruction
.op()
.operation
.matrix(&node.instruction.params)
.expect("No matrix defined for operation")
})
Expand Down Expand Up @@ -1043,22 +1044,6 @@ fn matmul_1q(operator: &mut [[Complex64; 2]; 2], other: Array2<Complex64>) {
];
}

#[pyfunction]
pub fn collect_1q_runs_filter(node: &Bound<PyAny>) -> bool {
let Ok(node) = node.downcast::<DAGOpNode>() else {
return false;
};
let node = node.borrow();
let op = node.instruction.op();
op.num_qubits() == 1
&& op.num_clbits() == 0
&& op.matrix(&node.instruction.params).is_some()
&& match &node.instruction.extra_attrs {
None => true,
Some(attrs) => attrs.condition.is_none(),
}
}

pub fn euler_one_qubit_decomposer(m: &Bound<PyModule>) -> PyResult<()> {
m.add_wrapped(wrap_pyfunction!(params_zyz))?;
m.add_wrapped(wrap_pyfunction!(params_xyx))?;
Expand All @@ -1072,7 +1057,6 @@ pub fn euler_one_qubit_decomposer(m: &Bound<PyModule>) -> PyResult<()> {
m.add_wrapped(wrap_pyfunction!(compute_error_one_qubit_sequence))?;
m.add_wrapped(wrap_pyfunction!(compute_error_list))?;
m.add_wrapped(wrap_pyfunction!(optimize_1q_gates_decomposition))?;
m.add_wrapped(wrap_pyfunction!(collect_1q_runs_filter))?;
m.add_class::<OneQubitGateSequence>()?;
m.add_class::<OneQubitGateErrorMap>()?;
m.add_class::<EulerBasis>()?;
Expand Down
14 changes: 13 additions & 1 deletion crates/circuit/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,29 @@ name = "qiskit_circuit"
doctest = false

[dependencies]
rayon = "1.10"
ahash.workspace = true
rustworkx-core.workspace = true
bytemuck.workspace = true
hashbrown.workspace = true
num-complex.workspace = true
ndarray.workspace = true
numpy.workspace = true
thiserror.workspace = true
approx.workspace = true
itertools.workspace = true

[dependencies.pyo3]
workspace = true
features = ["hashbrown", "indexmap", "num-complex", "num-bigint", "smallvec"]

[dependencies.hashbrown]
workspace = true
features = ["rayon"]

[dependencies.indexmap]
workspace = true
features = ["rayon"]

[dependencies.smallvec]
workspace = true
features = ["union"]
Expand Down
80 changes: 66 additions & 14 deletions crates/circuit/src/bit_data.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ use pyo3::prelude::*;
use pyo3::types::PyList;
use std::fmt::Debug;
use std::hash::{Hash, Hasher};
use std::mem::swap;

/// Private wrapper for Python-side Bit instances that implements
/// [Hash] and [Eq], allowing them to be used in Rust hash-based
Expand Down Expand Up @@ -81,17 +82,6 @@ pub struct BitData<T> {
cached: Py<PyList>,
}

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

impl<'py> From<BitNotFoundError<'py>> for PyErr {
fn from(error: BitNotFoundError) -> Self {
PyKeyError::new_err(format!(
"Bit {:?} has not been added to this circuit.",
error.0
))
}
}

impl<T> BitData<T>
where
T: From<BitType> + Copy,
Expand Down Expand Up @@ -139,14 +129,19 @@ where
pub fn map_bits<'py>(
&self,
bits: impl IntoIterator<Item = Bound<'py, PyAny>>,
) -> Result<impl Iterator<Item = T>, BitNotFoundError<'py>> {
) -> PyResult<impl Iterator<Item = T>> {
let v: Result<Vec<_>, _> = bits
.into_iter()
.map(|b| {
self.indices
.get(&BitAsKey::new(&b))
.copied()
.ok_or_else(|| BitNotFoundError(b))
.ok_or_else(|| {
PyKeyError::new_err(format!(
"Bit {:?} has not been added to this circuit.",
b
))
})
})
.collect();
v.map(|x| x.into_iter())
Expand All @@ -168,7 +163,7 @@ where
}

/// Adds a new Python bit.
pub fn add(&mut self, py: Python, bit: &Bound<PyAny>, strict: bool) -> PyResult<()> {
pub fn add(&mut self, py: Python, bit: &Bound<PyAny>, strict: bool) -> PyResult<T> {
if self.bits.len() != self.cached.bind(bit.py()).len() {
return Err(PyRuntimeError::new_err(
format!("This circuit's {} list has become out of sync with the circuit data. Did something modify it?", self.description)
Expand All @@ -193,6 +188,29 @@ where
bit
)));
}
Ok(idx.into())
}

pub fn remove_indices<I>(&mut self, py: Python, indices: I) -> PyResult<()>
where
I: IntoIterator<Item = T>,
{
let mut indices_sorted: Vec<usize> = indices
.into_iter()
.map(|i| <BitType as From<T>>::from(i) as usize)
.collect();
indices_sorted.sort();

for index in indices_sorted.into_iter().rev() {
self.cached.bind(py).del_item(index)?;
let bit = self.bits.remove(index);
self.indices.remove(&BitAsKey::new(bit.bind(py)));
}
// Update indices.
for (i, bit) in self.bits.iter().enumerate() {
self.indices
.insert(BitAsKey::new(bit.bind(py)), (i as BitType).into());
}
Ok(())
}

Expand All @@ -203,3 +221,37 @@ where
self.bits.clear();
}
}

pub struct Iter<'a, T> {
_data: &'a BitData<T>,
index: usize,
}

impl<'a, T> Iterator for Iter<'a, T>
where
T: From<BitType>,
{
type Item = T;

fn next(&mut self) -> Option<Self::Item> {
let mut index = self.index + 1;
swap(&mut self.index, &mut index);
let index: Option<BitType> = index.try_into().ok();
index.map(|i| From::from(i))
}
}

impl<'a, T> IntoIterator for &'a BitData<T>
where
T: From<BitType>,
{
type Item = T;
type IntoIter = Iter<'a, T>;

fn into_iter(self) -> Self::IntoIter {
Iter {
_data: self,
index: 0,
}
}
}
mtreinish marked this conversation as resolved.
Show resolved Hide resolved
Loading
Loading