Skip to content

Commit 6fef204

Browse files
committed
WIP: Gates in rust
This compiles but has circular import errors because we need to use the standard gate classes in Python to map from python to rust. Fixes: Qiskit#12205
1 parent c062dd6 commit 6fef204

File tree

12 files changed

+1304
-49
lines changed

12 files changed

+1304
-49
lines changed

Cargo.lock

+4
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

+5
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,11 @@ license = "Apache-2.0"
1616
[workspace.dependencies]
1717
indexmap.version = "2.2.6"
1818
hashbrown.version = "0.14.0"
19+
num-complex = "0.4"
20+
ndarray = "^0.15.6"
21+
numpy = "0.21.0"
22+
smallvec = "1.13"
23+
1924
# Most of the crates don't need the feature `extension-module`, since only `qiskit-pyext` builds an
2025
# actual C extension (the feature disables linking in `libpython`, which is forbidden in Python
2126
# distributions). We only activate that feature when building the C extension module; we still need

crates/accelerate/Cargo.toml

+4-4
Original file line numberDiff line numberDiff line change
@@ -11,29 +11,29 @@ doctest = false
1111

1212
[dependencies]
1313
rayon = "1.10"
14-
numpy = "0.21.0"
14+
numpy.workspace = true
1515
rand = "0.8"
1616
rand_pcg = "0.3"
1717
rand_distr = "0.4.3"
1818
ahash = "0.8.11"
1919
num-traits = "0.2"
20-
num-complex = "0.4"
20+
num-complex.workspace = true
2121
num-bigint = "0.4"
2222
rustworkx-core = "0.14"
2323
faer = "0.18.2"
2424
itertools = "0.12.1"
2525
qiskit-circuit.workspace = true
2626

2727
[dependencies.smallvec]
28-
version = "1.13"
28+
workspace = true
2929
features = ["union"]
3030

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

3535
[dependencies.ndarray]
36-
version = "^0.15.6"
36+
workspace = true
3737
features = ["rayon", "approx-0_5"]
3838

3939
[dependencies.approx]

crates/circuit/Cargo.toml

+11-1
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,14 @@ doctest = false
1111

1212
[dependencies]
1313
hashbrown.workspace = true
14-
pyo3.workspace = true
14+
num-complex.workspace = true
15+
ndarray.workspace = true
16+
numpy.workspace = true
17+
18+
[dependencies.pyo3]
19+
workspace = true
20+
features = ["hashbrown", "indexmap", "num-complex", "num-bigint", "smallvec"]
21+
22+
[dependencies.smallvec]
23+
workspace = true
24+
features = ["union"]

crates/circuit/src/circuit_data.rs

+150-8
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@
1313
use crate::circuit_instruction::CircuitInstruction;
1414
use crate::intern_context::{BitType, IndexType, InternContext};
1515
use crate::SliceOrInt;
16+
use smallvec::SmallVec;
17+
18+
use crate::operations::{OperationType, Param};
1619

1720
use hashbrown::HashMap;
1821
use pyo3::exceptions::{PyIndexError, PyKeyError, PyRuntimeError, PyValueError};
@@ -25,11 +28,16 @@ use std::hash::{Hash, Hasher};
2528
#[derive(Clone, Debug)]
2629
struct PackedInstruction {
2730
/// The Python-side operation instance.
28-
op: PyObject,
31+
op: OperationType,
2932
/// The index under which the interner has stored `qubits`.
3033
qubits_id: IndexType,
3134
/// The index under which the interner has stored `clbits`.
3235
clbits_id: IndexType,
36+
params: Option<SmallVec<[Param; 3]>>,
37+
label: Option<String>,
38+
duration: Option<PyObject>,
39+
unit: Option<String>,
40+
condition: Option<PyObject>,
3341
}
3442

3543
/// Private wrapper for Python-side Bit instances that implements
@@ -154,6 +162,71 @@ pub struct CircuitData {
154162
clbits: Py<PyList>,
155163
}
156164

165+
impl CircuitData {
166+
/// A helper method to build a new CircuitData from an owned definition
167+
/// as a slice of OperationType, parameters, and qubits.
168+
pub fn build_new_from(
169+
py: Python,
170+
num_qubits: usize,
171+
num_clbits: usize,
172+
instructions: &[(OperationType, &[Param], &[u32])],
173+
) -> PyResult<Self> {
174+
let mut res = CircuitData {
175+
data: Vec::with_capacity(instructions.len()),
176+
intern_context: InternContext::new(),
177+
qubits_native: Vec::with_capacity(num_qubits),
178+
clbits_native: Vec::with_capacity(num_clbits),
179+
qubit_indices_native: HashMap::with_capacity(num_qubits),
180+
clbit_indices_native: HashMap::with_capacity(num_clbits),
181+
qubits: PyList::empty_bound(py).unbind(),
182+
clbits: PyList::empty_bound(py).unbind(),
183+
};
184+
if num_qubits > 0 {
185+
let qubit_mod = py.import_bound("qiskit.circuit.quantumregister")?;
186+
let qubit_cls = qubit_mod.getattr("Qubit")?;
187+
for _i in 0..num_qubits {
188+
let bit = qubit_cls.call0()?;
189+
res.add_qubit(py, &bit, true)?;
190+
}
191+
}
192+
if num_clbits > 0 {
193+
let clbit_mod = py.import_bound("qiskit.circuit.classicalregister")?;
194+
let clbit_cls = clbit_mod.getattr("Clbit")?;
195+
for _i in 0..num_clbits {
196+
let bit = clbit_cls.call0()?;
197+
res.add_clbit(py, &bit, true)?;
198+
}
199+
}
200+
for (operation, params, qargs) in instructions {
201+
let qubits = PyTuple::new_bound(
202+
py,
203+
qargs
204+
.iter()
205+
.map(|x| res.qubits_native[*x as usize].clone_ref(py))
206+
.collect::<Vec<PyObject>>(),
207+
)
208+
.unbind();
209+
let empty: [u8; 0] = [];
210+
let clbits = PyTuple::new_bound(py, empty);
211+
let inst = res.pack_owned(
212+
py,
213+
&CircuitInstruction {
214+
operation: operation.clone(),
215+
qubits,
216+
clbits: clbits.into(),
217+
params: Some(params.iter().cloned().collect()),
218+
label: None,
219+
duration: None,
220+
unit: None,
221+
condition: None,
222+
},
223+
)?;
224+
res.data.push(inst);
225+
}
226+
Ok(res)
227+
}
228+
}
229+
157230
#[pymethods]
158231
impl CircuitData {
159232
#[new]
@@ -366,7 +439,15 @@ impl CircuitData {
366439
#[pyo3(signature = (func))]
367440
pub fn foreach_op(&self, py: Python<'_>, func: &Bound<PyAny>) -> PyResult<()> {
368441
for inst in self.data.iter() {
369-
func.call1((inst.op.bind(py),))?;
442+
match &inst.op {
443+
OperationType::Standard(op) => {
444+
let op = op.into_py(py);
445+
func.call1((op,))
446+
}
447+
OperationType::Instruction(op) => func.call1((op.instruction.clone_ref(py),)),
448+
OperationType::Gate(op) => func.call1((op.gate.clone_ref(py),)),
449+
OperationType::Operation(op) => func.call1((op.operation.clone_ref(py),)),
450+
}?;
370451
}
371452
Ok(())
372453
}
@@ -380,7 +461,15 @@ impl CircuitData {
380461
#[pyo3(signature = (func))]
381462
pub fn foreach_op_indexed(&self, py: Python<'_>, func: &Bound<PyAny>) -> PyResult<()> {
382463
for (index, inst) in self.data.iter().enumerate() {
383-
func.call1((index, inst.op.bind(py)))?;
464+
match &inst.op {
465+
OperationType::Standard(op) => {
466+
let op = op.into_py(py);
467+
func.call1((index, op))
468+
}
469+
OperationType::Instruction(op) => func.call1((index, op.instruction.clone_ref(py))),
470+
OperationType::Gate(op) => func.call1((index, op.gate.clone_ref(py))),
471+
OperationType::Operation(op) => func.call1((index, op.operation.clone_ref(py))),
472+
}?;
384473
}
385474
Ok(())
386475
}
@@ -395,7 +484,16 @@ impl CircuitData {
395484
#[pyo3(signature = (func))]
396485
pub fn map_ops(&mut self, py: Python<'_>, func: &Bound<PyAny>) -> PyResult<()> {
397486
for inst in self.data.iter_mut() {
398-
inst.op = func.call1((inst.op.bind(py),))?.into_py(py);
487+
inst.op = match &inst.op {
488+
OperationType::Standard(op) => {
489+
let op = op.into_py(py);
490+
func.call1((op,))
491+
}
492+
OperationType::Instruction(op) => func.call1((op.instruction.clone_ref(py),)),
493+
OperationType::Gate(op) => func.call1((op.gate.clone_ref(py),)),
494+
OperationType::Operation(op) => func.call1((op.operation.clone_ref(py),)),
495+
}?
496+
.extract()?;
399497
}
400498
Ok(())
401499
}
@@ -666,9 +764,14 @@ impl CircuitData {
666764
.collect::<PyResult<Vec<BitType>>>()?;
667765

668766
self.data.push(PackedInstruction {
669-
op: inst.op.clone_ref(py),
767+
op: inst.op.clone(),
670768
qubits_id: self.intern_context.intern(qubits)?,
671769
clbits_id: self.intern_context.intern(clbits)?,
770+
params: inst.params.clone(),
771+
label: inst.label.clone(),
772+
duration: inst.duration.clone(),
773+
unit: inst.unit.clone(),
774+
condition: inst.condition.clone(),
672775
});
673776
}
674777
return Ok(());
@@ -720,7 +823,7 @@ impl CircuitData {
720823

721824
fn __traverse__(&self, visit: PyVisit<'_>) -> Result<(), PyTraverseError> {
722825
for packed in self.data.iter() {
723-
visit.call(&packed.op)?;
826+
visit.call(&packed.duration)?;
724827
}
725828
for bit in self.qubits_native.iter().chain(self.clbits_native.iter()) {
726829
visit.call(bit)?;
@@ -820,17 +923,51 @@ impl CircuitData {
820923
self.intern_context.intern(args)
821924
};
822925
Ok(PackedInstruction {
823-
op: inst.operation.clone_ref(py),
926+
op: inst.operation.clone(),
927+
qubits_id: interned_bits(&self.qubit_indices_native, inst.qubits.bind(py))?,
928+
clbits_id: interned_bits(&self.clbit_indices_native, inst.clbits.bind(py))?,
929+
params: inst.params.clone(),
930+
label: inst.label.clone(),
931+
duration: inst.duration.clone(),
932+
unit: inst.unit.clone(),
933+
condition: inst.condition.clone(),
934+
})
935+
}
936+
937+
fn pack_owned(&mut self, py: Python, inst: &CircuitInstruction) -> PyResult<PackedInstruction> {
938+
let mut interned_bits =
939+
|indices: &HashMap<BitAsKey, BitType>, bits: &Bound<PyTuple>| -> PyResult<IndexType> {
940+
let args = bits
941+
.into_iter()
942+
.map(|b| {
943+
let key = BitAsKey::new(&b)?;
944+
indices.get(&key).copied().ok_or_else(|| {
945+
PyKeyError::new_err(format!(
946+
"Bit {:?} has not been added to this circuit.",
947+
b
948+
))
949+
})
950+
})
951+
.collect::<PyResult<Vec<BitType>>>()?;
952+
self.intern_context.intern(args)
953+
};
954+
Ok(PackedInstruction {
955+
op: inst.operation.clone(),
824956
qubits_id: interned_bits(&self.qubit_indices_native, inst.qubits.bind(py))?,
825957
clbits_id: interned_bits(&self.clbit_indices_native, inst.clbits.bind(py))?,
958+
params: inst.params.clone(),
959+
label: inst.label.clone(),
960+
duration: inst.duration.clone(),
961+
unit: inst.unit.clone(),
962+
condition: inst.condition.clone(),
826963
})
827964
}
828965

829966
fn unpack(&self, py: Python<'_>, inst: &PackedInstruction) -> PyResult<Py<CircuitInstruction>> {
830967
Py::new(
831968
py,
832969
CircuitInstruction {
833-
operation: inst.op.clone_ref(py),
970+
operation: inst.op.clone(),
834971
qubits: PyTuple::new_bound(
835972
py,
836973
self.intern_context
@@ -849,6 +986,11 @@ impl CircuitData {
849986
.collect::<Vec<_>>(),
850987
)
851988
.unbind(),
989+
params: inst.params.clone(),
990+
label: inst.label.clone(),
991+
duration: inst.duration.clone(),
992+
unit: inst.unit.clone(),
993+
condition: inst.condition.clone(),
852994
},
853995
)
854996
}

0 commit comments

Comments
 (0)