Skip to content

Commit feb4dbb

Browse files
committed
Initial: Add circuit_to_dag in rust.
- Add new method `DAGCircuit::from_quantum_circuit` which uses the data from a `QuantumCircuit` instance to build a dag_circuit. - Expose the method through a `Python` interface with `circuit_to_dag` which goes by the alias of `core_circuit_to_dag` and is called by the original method. - Add an arbitrary structure `QuantumCircuitData` that successfully extract attributes from the python `QuantumCircuit` instance and makes it easier to access in rust. - This structure is for attribute extraction only and is not clonable/copyable. - Expose a new module `converters` which should store all of the rust-side converters whenever they get brought into rust. - Other small tweaks and fixes.
1 parent baea69e commit feb4dbb

File tree

5 files changed

+318
-40
lines changed

5 files changed

+318
-40
lines changed

crates/circuit/src/converters.rs

+69
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
// This code is part of Qiskit.
2+
//
3+
// (C) Copyright IBM 2023, 2024
4+
//
5+
// This code is licensed under the Apache License, Version 2.0. You may
6+
// obtain a copy of this license in the LICENSE.txt file in the root directory
7+
// of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
8+
//
9+
// Any modifications or derivative works of this code must retain this
10+
// copyright notice, and modified files need to carry a notice indicating
11+
// that they have been altered from the originals.
12+
13+
use ::pyo3::prelude::*;
14+
use hashbrown::HashMap;
15+
use pyo3::types::{PyDict, PyList};
16+
17+
use crate::{circuit_data::CircuitData, dag_circuit::DAGCircuit};
18+
19+
/// An extractable representation of a QuantumCircuit reserved only for
20+
/// conversion purposes.
21+
///
22+
/// ## Notes:
23+
/// This structure does not implement `Clone`, this is the intended behavior as
24+
/// it contains callbacks to Python and should not be stored anywhere.
25+
#[derive(Debug)]
26+
pub(crate) struct QuantumCircuitData<'py> {
27+
pub data: PyRef<'py, CircuitData>,
28+
pub name: Option<Bound<'py, PyAny>>,
29+
pub calibrations: HashMap<String, Py<PyDict>>,
30+
pub metadata: Option<Bound<'py, PyAny>>,
31+
pub qregs: Option<Bound<'py, PyList>>,
32+
pub cregs: Option<Bound<'py, PyList>>,
33+
pub input_vars: Option<Bound<'py, PyAny>>,
34+
pub captured_vars: Option<Bound<'py, PyAny>>,
35+
pub declared_vars: Option<Bound<'py, PyAny>>,
36+
}
37+
38+
impl<'py> FromPyObject<'py> for QuantumCircuitData<'py> {
39+
fn extract_bound(ob: &Bound<'py, PyAny>) -> PyResult<Self> {
40+
let circuit_data = ob.getattr("_data")?;
41+
let data_borrowed = circuit_data.downcast::<CircuitData>()?.borrow();
42+
Ok(QuantumCircuitData {
43+
data: data_borrowed,
44+
name: ob.getattr("name").ok(),
45+
calibrations: ob.getattr("calibrations")?.extract()?,
46+
metadata: ob.getattr("metadata").ok(),
47+
qregs: ob.getattr("qregs").map(|ob| ob.downcast_into())?.ok(),
48+
cregs: ob.getattr("cregs").map(|ob| ob.downcast_into())?.ok(),
49+
input_vars: ob.call_method0("iter_input_vars").ok(),
50+
captured_vars: ob.call_method0("iter_captured_vars").ok(),
51+
declared_vars: ob.call_method0("iter_declared_vars").ok(),
52+
})
53+
}
54+
}
55+
56+
#[pyfunction]
57+
fn circuit_to_dag(
58+
py: Python,
59+
quantum_circuit: QuantumCircuitData,
60+
qubit_order: Option<Vec<PyObject>>,
61+
clbit_order: Option<Vec<PyObject>>,
62+
) -> PyResult<DAGCircuit> {
63+
DAGCircuit::from_quantum_circuit(py, quantum_circuit, qubit_order, clbit_order)
64+
}
65+
66+
pub fn converters(m: &Bound<PyModule>) -> PyResult<()> {
67+
m.add_function(wrap_pyfunction!(circuit_to_dag, m)?)?;
68+
Ok(())
69+
}

crates/circuit/src/dag_circuit.rs

+233
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ use crate::bit_data::BitData;
1818
use crate::circuit_instruction::{
1919
CircuitInstruction, ExtraInstructionAttributes, OperationFromPython,
2020
};
21+
use crate::converters::QuantumCircuitData;
2122
use crate::dag_node::{DAGInNode, DAGNode, DAGOpNode, DAGOutNode};
2223
use crate::dot_utils::build_dot;
2324
use crate::error::DAGCircuitError;
@@ -6368,6 +6369,238 @@ impl DAGCircuit {
63686369

63696370
Ok(new_nodes)
63706371
}
6372+
6373+
/// Alternative constructor to build an instance of [DAGCircuit] from a `QuantumCircuit`.
6374+
pub(crate) fn from_quantum_circuit(
6375+
py: Python,
6376+
qc: QuantumCircuitData,
6377+
qubit_order: Option<Vec<PyObject>>,
6378+
clbit_order: Option<Vec<PyObject>>,
6379+
) -> PyResult<DAGCircuit> {
6380+
// Extract necessary attributes
6381+
let qc_data = qc.data;
6382+
let num_qubits = qc_data.num_qubits();
6383+
let num_clbits = qc_data.num_clbits();
6384+
let num_ops = qc_data.__len__();
6385+
let mut num_vars = 0;
6386+
if let Some(vars) = &qc.input_vars {
6387+
num_vars += vars.len()?;
6388+
}
6389+
if let Some(vars) = &qc.captured_vars {
6390+
num_vars += vars.len()?;
6391+
}
6392+
if let Some(vars) = &qc.declared_vars {
6393+
num_vars += vars.len()?;
6394+
}
6395+
let mut num_edges = 0;
6396+
6397+
// Take ownership of the interners.
6398+
let mut qubit_interner = if qubit_order.is_some() {
6399+
IndexedInterner::new()
6400+
} else {
6401+
qc_data.qargs_interner().clone()
6402+
};
6403+
6404+
let mut clbit_interner = if clbit_order.is_some() {
6405+
IndexedInterner::new()
6406+
} else {
6407+
qc_data.cargs_interner().clone()
6408+
};
6409+
6410+
// Take ownership of the bit_data
6411+
let mut qubit_data = if qubit_order.is_some() {
6412+
BitData::with_capacity(py, "qubits".to_string(), num_qubits)
6413+
} else {
6414+
qc_data.qubits().clone()
6415+
};
6416+
6417+
let mut clbit_data = if clbit_order.is_some() {
6418+
BitData::with_capacity(py, "clbits".to_string(), num_clbits)
6419+
} else {
6420+
qc_data.clbits().clone()
6421+
};
6422+
6423+
// Pre-process the instructions
6424+
let qubit_set: Vec<Qubit> = if let Some(qubit_ordering) = &qubit_order {
6425+
if qubit_ordering.len() != num_qubits {
6426+
return Err(PyValueError::new_err(
6427+
"'qubit_order' does not contain exactly the same qubits as the circuit",
6428+
));
6429+
};
6430+
let mut qubits = vec![];
6431+
for qubit in qubit_ordering {
6432+
let bound = qubit.bind(py);
6433+
if qubit_data.find(bound).is_some() {
6434+
return Err(DAGCircuitError::new_err(format!(
6435+
"duplicate qubits {}",
6436+
bound.repr()?
6437+
)));
6438+
}
6439+
// Add bit to its respective BitData and add index to qubit_set
6440+
qubit_data.add(py, bound, false)?;
6441+
if let Some(qubit) = qc_data.qubits().find(bound) {
6442+
qubits.push(qubit);
6443+
}
6444+
}
6445+
qubits
6446+
} else {
6447+
(0..num_qubits as u32).map(Qubit).collect()
6448+
};
6449+
let clbit_set: Vec<Clbit> = if let Some(clbit_ordering) = &clbit_order {
6450+
if clbit_ordering.len() != num_clbits {
6451+
return Err(PyValueError::new_err(
6452+
"'clbit_order' does not contain exactly the same clbits as the circuit",
6453+
));
6454+
};
6455+
let mut clbits = vec![];
6456+
for clbit in clbit_ordering {
6457+
let bound = clbit.bind(py);
6458+
if clbit_data.find(bound).is_some() {
6459+
return Err(DAGCircuitError::new_err(format!(
6460+
"duplicate clbits {}",
6461+
bound.repr()?
6462+
)));
6463+
}
6464+
// Add bit to its respective BitData and add index to clbit_set
6465+
clbit_data.add(py, bound, false)?;
6466+
if let Some(clbit) = qc_data.clbits().find(bound) {
6467+
clbits.push(clbit);
6468+
}
6469+
}
6470+
clbits
6471+
} else {
6472+
(0..num_clbits as u32).map(Clbit).collect()
6473+
};
6474+
6475+
// Count all input nodes in each instruction
6476+
let instructions: Vec<PackedInstruction> = qc_data
6477+
.iter()
6478+
.cloned()
6479+
.map(|instr| -> PyResult<PackedInstruction> {
6480+
// Re-map the qubits
6481+
let qargs: Vec<Qubit> = qc_data.get_qargs(instr.qubits).to_vec();
6482+
if qubit_order.is_some() {
6483+
let ordered_qargs = qargs
6484+
.iter()
6485+
.map(|index| qubit_set[index.0 as usize])
6486+
.collect();
6487+
Interner::intern(&mut qubit_interner, ordered_qargs)?;
6488+
}
6489+
// Remap the clbits
6490+
let cargs: Vec<Clbit> = qc_data.get_cargs(instr.clbits).to_vec();
6491+
if clbit_order.is_some() {
6492+
let ordered_cargs = cargs
6493+
.iter()
6494+
.map(|index| clbit_set[index.0 as usize])
6495+
.collect();
6496+
Interner::intern(&mut clbit_interner, ordered_cargs)?;
6497+
}
6498+
6499+
num_edges += qargs.len() + cargs.len();
6500+
6501+
Ok(instr)
6502+
})
6503+
.collect::<PyResult<Vec<_>>>()?;
6504+
6505+
// Build DAGCircuit with capacity
6506+
let mut new_dag = DAGCircuit::with_capacity(
6507+
py,
6508+
num_qubits,
6509+
num_clbits,
6510+
Some(num_ops),
6511+
Some(num_vars),
6512+
Some(num_edges),
6513+
)?;
6514+
6515+
// Assign other necessary data
6516+
new_dag.name = qc.name.map(|ob| ob.unbind());
6517+
6518+
// Avoid manually acquiring the GIL.
6519+
new_dag.global_phase = match qc_data.global_phase() {
6520+
Param::ParameterExpression(exp) => Param::ParameterExpression(exp.clone_ref(py)),
6521+
Param::Float(float) => Param::Float(*float),
6522+
_ => unreachable!("Incorrect parameter assigned for global phase"),
6523+
};
6524+
6525+
new_dag.calibrations = qc.calibrations;
6526+
new_dag.metadata = qc.metadata.map(|meta| meta.unbind());
6527+
6528+
// TODO: Use Qubit ordering to remap the interners and qubit
6529+
6530+
// Copy over all interners and registers
6531+
new_dag.qargs_cache = qubit_interner;
6532+
new_dag.cargs_cache = clbit_interner;
6533+
6534+
new_dag.qubits = qubit_data;
6535+
new_dag.clbits = clbit_data;
6536+
6537+
// Re-map and add all of the qubits
6538+
for qubit in (0..num_qubits as u32).map(Qubit) {
6539+
if let Some(bit) = new_dag.qubits.get(qubit) {
6540+
new_dag.qubit_locations.bind(py).set_item(
6541+
bit,
6542+
BitLocations {
6543+
index: qubit.0 as usize,
6544+
registers: PyList::empty_bound(py).unbind(),
6545+
}
6546+
.into_py(py),
6547+
)?;
6548+
new_dag.add_wire(py, Wire::Qubit(qubit))?;
6549+
}
6550+
}
6551+
6552+
// Re-map and add all of the qubits
6553+
for clbit in (0..num_clbits as u32).map(Clbit) {
6554+
if let Some(bit) = new_dag.clbits.get(clbit) {
6555+
new_dag.clbit_locations.bind(py).set_item(
6556+
bit,
6557+
BitLocations {
6558+
index: clbit.0 as usize,
6559+
registers: PyList::empty_bound(py).unbind(),
6560+
}
6561+
.into_py(py),
6562+
)?;
6563+
new_dag.add_wire(py, Wire::Clbit(clbit))?;
6564+
}
6565+
}
6566+
6567+
if let Some(vars) = qc.declared_vars {
6568+
for var in vars.iter()? {
6569+
new_dag.add_var(py, &var?, DAGVarType::Declare)?;
6570+
}
6571+
}
6572+
6573+
if let Some(vars) = qc.input_vars {
6574+
for var in vars.iter()? {
6575+
new_dag.add_var(py, &var?, DAGVarType::Input)?;
6576+
}
6577+
}
6578+
6579+
if let Some(vars) = qc.captured_vars {
6580+
for var in vars.iter()? {
6581+
new_dag.add_var(py, &var?, DAGVarType::Capture)?;
6582+
}
6583+
}
6584+
6585+
// Add all the registers
6586+
6587+
if let Some(qregs) = qc.qregs {
6588+
for qreg in qregs.iter() {
6589+
new_dag.add_qreg(py, &qreg)?;
6590+
}
6591+
}
6592+
6593+
if let Some(cregs) = qc.cregs {
6594+
for creg in cregs.iter() {
6595+
new_dag.add_creg(py, &creg)?;
6596+
}
6597+
}
6598+
6599+
// Finally add all the instructions back
6600+
new_dag.add_from_iter(py, instructions)?;
6601+
6602+
Ok(new_dag)
6603+
}
63716604
}
63726605

63736606
/// Add to global phase. Global phase can only be Float or ParameterExpression so this

crates/circuit/src/lib.rs

+13
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
pub mod bit_data;
1414
pub mod circuit_data;
1515
pub mod circuit_instruction;
16+
pub mod converters;
1617
pub mod dag_circuit;
1718
pub mod dag_node;
1819
mod dot_utils;
@@ -80,6 +81,17 @@ impl From<Clbit> for BitType {
8081
}
8182
}
8283

84+
#[inline(always)]
85+
#[doc(hidden)]
86+
fn add_submodule<F>(m: &Bound<PyModule>, constructor: F, name: &str) -> PyResult<()>
87+
where
88+
F: FnOnce(&Bound<PyModule>) -> PyResult<()>,
89+
{
90+
let new_mod = PyModule::new_bound(m.py(), name)?;
91+
constructor(&new_mod)?;
92+
m.add_submodule(&new_mod)
93+
}
94+
8395
pub fn circuit(m: &Bound<PyModule>) -> PyResult<()> {
8496
m.add_class::<circuit_data::CircuitData>()?;
8597
m.add_class::<circuit_instruction::CircuitInstruction>()?;
@@ -89,5 +101,6 @@ pub fn circuit(m: &Bound<PyModule>) -> PyResult<()> {
89101
m.add_class::<dag_node::DAGOutNode>()?;
90102
m.add_class::<dag_node::DAGOpNode>()?;
91103
m.add_class::<operations::StandardGate>()?;
104+
add_submodule(m, converters::converters, "converters")?;
92105
Ok(())
93106
}

qiskit/__init__.py

+1
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@
6161
# and not have to rely on attribute access. No action needed for top-level extension packages.
6262
sys.modules["qiskit._accelerate.circuit"] = _accelerate.circuit
6363
sys.modules["qiskit._accelerate.circuit_library"] = _accelerate.circuit_library
64+
sys.modules["qiskit._accelerate.circuit.converters"] = _accelerate.circuit.converters
6465
sys.modules["qiskit._accelerate.convert_2q_block_matrix"] = _accelerate.convert_2q_block_matrix
6566
sys.modules["qiskit._accelerate.dense_layout"] = _accelerate.dense_layout
6667
sys.modules["qiskit._accelerate.error_map"] = _accelerate.error_map

0 commit comments

Comments
 (0)