Skip to content

Commit 9669df9

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 7882eb1 commit 9669df9

13 files changed

+1263
-51
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/accelerate/src/sparse_pauli_op.rs

+3-1
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,9 @@ impl ZXPaulis {
141141
phases: &Bound<PyArray1<u8>>,
142142
coeffs: &Bound<PyArray1<Complex64>>,
143143
) -> PyResult<Self> {
144-
let &[num_ops, num_qubits] = x.shape() else { unreachable!("PyArray2 must be 2D") };
144+
let &[num_ops, num_qubits] = x.shape() else {
145+
unreachable!("PyArray2 must be 2D")
146+
};
145147
if z.shape() != [num_ops, num_qubits] {
146148
return Err(PyValueError::new_err(format!(
147149
"'x' and 'z' have different shapes: {:?} and {:?}",

crates/circuit/Cargo.toml

+8-1
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,11 @@ 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+
smallvec.workspace = true
18+
19+
[dependencies.pyo3]
20+
workspace = true
21+
features = ["hashbrown", "indexmap", "num-complex", "num-bigint", "smallvec"]

crates/circuit/src/circuit_data.rs

+145-9
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,15 @@ 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>,
3340
}
3441

3542
/// Private wrapper for Python-side Bit instances that implements
@@ -154,6 +161,70 @@ pub struct CircuitData {
154161
clbits: Py<PyList>,
155162
}
156163

164+
impl CircuitData {
165+
/// A helper method to build a new CircuitData from an owned definition
166+
/// as a slice of OperationType, parameters, and qubits.
167+
pub fn build_new_from(
168+
py: Python,
169+
num_qubits: usize,
170+
num_clbits: usize,
171+
instructions: &[(OperationType, &[Param], &[u32])],
172+
) -> PyResult<Self> {
173+
let mut res = CircuitData {
174+
data: Vec::with_capacity(instructions.len()),
175+
intern_context: InternContext::new(),
176+
qubits_native: Vec::with_capacity(num_qubits),
177+
clbits_native: Vec::with_capacity(num_clbits),
178+
qubit_indices_native: HashMap::with_capacity(num_qubits),
179+
clbit_indices_native: HashMap::with_capacity(num_clbits),
180+
qubits: PyList::empty_bound(py).unbind(),
181+
clbits: PyList::empty_bound(py).unbind(),
182+
};
183+
if num_qubits > 0 {
184+
let qubit_mod = py.import_bound("qiskit.circuit.quantumregister")?;
185+
let qubit_cls = qubit_mod.getattr("Qubit")?;
186+
for _i in 0..num_qubits {
187+
let bit = qubit_cls.call0()?;
188+
res.add_qubit(py, &bit, true)?;
189+
}
190+
}
191+
if num_clbits > 0 {
192+
let clbit_mod = py.import_bound("qiskit.circuit.classicalregister")?;
193+
let clbit_cls = clbit_mod.getattr("Clbit")?;
194+
for _i in 0..num_clbits {
195+
let bit = clbit_cls.call0()?;
196+
res.add_clbit(py, &bit, true)?;
197+
}
198+
}
199+
for (operation, params, qargs) in instructions {
200+
let qubits = PyTuple::new_bound(
201+
py,
202+
qargs
203+
.iter()
204+
.map(|x| res.qubits_native[*x as usize].clone_ref(py))
205+
.collect::<Vec<PyObject>>(),
206+
)
207+
.unbind();
208+
let empty: [u8; 0] = [];
209+
let clbits = PyTuple::new_bound(py, empty);
210+
let inst = res.pack_owned(
211+
py,
212+
&CircuitInstruction {
213+
operation: operation.clone(),
214+
qubits,
215+
clbits: clbits.into(),
216+
params: Some(params.iter().cloned().collect()),
217+
label: None,
218+
duration: None,
219+
unit: None,
220+
},
221+
)?;
222+
res.data.push(inst);
223+
}
224+
Ok(res)
225+
}
226+
}
227+
157228
#[pymethods]
158229
impl CircuitData {
159230
#[new]
@@ -324,7 +395,7 @@ impl CircuitData {
324395
0,
325396
)?;
326397
res.intern_context = self.intern_context.clone();
327-
res.data = self.data.clone();
398+
res.data.clone_from(&self.data);
328399
Ok(res)
329400
}
330401

@@ -366,7 +437,15 @@ impl CircuitData {
366437
#[pyo3(signature = (func))]
367438
pub fn foreach_op(&self, py: Python<'_>, func: &Bound<PyAny>) -> PyResult<()> {
368439
for inst in self.data.iter() {
369-
func.call1((inst.op.bind(py),))?;
440+
match &inst.op {
441+
OperationType::Standard(op) => {
442+
let op = op.into_py(py);
443+
func.call1((op,))
444+
}
445+
OperationType::Instruction(op) => func.call1((op.instruction.clone_ref(py),)),
446+
OperationType::Gate(op) => func.call1((op.gate.clone_ref(py),)),
447+
OperationType::Operation(op) => func.call1((op.operation.clone_ref(py),)),
448+
}?;
370449
}
371450
Ok(())
372451
}
@@ -380,7 +459,15 @@ impl CircuitData {
380459
#[pyo3(signature = (func))]
381460
pub fn foreach_op_indexed(&self, py: Python<'_>, func: &Bound<PyAny>) -> PyResult<()> {
382461
for (index, inst) in self.data.iter().enumerate() {
383-
func.call1((index, inst.op.bind(py)))?;
462+
match &inst.op {
463+
OperationType::Standard(op) => {
464+
let op = op.into_py(py);
465+
func.call1((index, op))
466+
}
467+
OperationType::Instruction(op) => func.call1((index, op.instruction.clone_ref(py))),
468+
OperationType::Gate(op) => func.call1((index, op.gate.clone_ref(py))),
469+
OperationType::Operation(op) => func.call1((index, op.operation.clone_ref(py))),
470+
}?;
384471
}
385472
Ok(())
386473
}
@@ -395,7 +482,16 @@ impl CircuitData {
395482
#[pyo3(signature = (func))]
396483
pub fn map_ops(&mut self, py: Python<'_>, func: &Bound<PyAny>) -> PyResult<()> {
397484
for inst in self.data.iter_mut() {
398-
inst.op = func.call1((inst.op.bind(py),))?.into_py(py);
485+
inst.op = match &inst.op {
486+
OperationType::Standard(op) => {
487+
let op = op.into_py(py);
488+
func.call1((op,))
489+
}
490+
OperationType::Instruction(op) => func.call1((op.instruction.clone_ref(py),)),
491+
OperationType::Gate(op) => func.call1((op.gate.clone_ref(py),)),
492+
OperationType::Operation(op) => func.call1((op.operation.clone_ref(py),)),
493+
}?
494+
.extract()?;
399495
}
400496
Ok(())
401497
}
@@ -666,9 +762,13 @@ impl CircuitData {
666762
.collect::<PyResult<Vec<BitType>>>()?;
667763

668764
self.data.push(PackedInstruction {
669-
op: inst.op.clone_ref(py),
765+
op: inst.op.clone(),
670766
qubits_id: self.intern_context.intern(qubits)?,
671767
clbits_id: self.intern_context.intern(clbits)?,
768+
params: inst.params.clone(),
769+
label: inst.label.clone(),
770+
duration: inst.duration.clone(),
771+
unit: inst.unit.clone(),
672772
});
673773
}
674774
return Ok(());
@@ -720,7 +820,7 @@ impl CircuitData {
720820

721821
fn __traverse__(&self, visit: PyVisit<'_>) -> Result<(), PyTraverseError> {
722822
for packed in self.data.iter() {
723-
visit.call(&packed.op)?;
823+
visit.call(&packed.duration)?;
724824
}
725825
for bit in self.qubits_native.iter().chain(self.clbits_native.iter()) {
726826
visit.call(bit)?;
@@ -820,17 +920,49 @@ impl CircuitData {
820920
self.intern_context.intern(args)
821921
};
822922
Ok(PackedInstruction {
823-
op: inst.operation.clone_ref(py),
923+
op: inst.operation.clone(),
924+
qubits_id: interned_bits(&self.qubit_indices_native, inst.qubits.bind(py))?,
925+
clbits_id: interned_bits(&self.clbit_indices_native, inst.clbits.bind(py))?,
926+
params: inst.params.clone(),
927+
label: inst.label.clone(),
928+
duration: inst.duration.clone(),
929+
unit: inst.unit.clone(),
930+
})
931+
}
932+
933+
fn pack_owned(&mut self, py: Python, inst: &CircuitInstruction) -> PyResult<PackedInstruction> {
934+
let mut interned_bits =
935+
|indices: &HashMap<BitAsKey, BitType>, bits: &Bound<PyTuple>| -> PyResult<IndexType> {
936+
let args = bits
937+
.into_iter()
938+
.map(|b| {
939+
let key = BitAsKey::new(&b)?;
940+
indices.get(&key).copied().ok_or_else(|| {
941+
PyKeyError::new_err(format!(
942+
"Bit {:?} has not been added to this circuit.",
943+
b
944+
))
945+
})
946+
})
947+
.collect::<PyResult<Vec<BitType>>>()?;
948+
self.intern_context.intern(args)
949+
};
950+
Ok(PackedInstruction {
951+
op: inst.operation.clone(),
824952
qubits_id: interned_bits(&self.qubit_indices_native, inst.qubits.bind(py))?,
825953
clbits_id: interned_bits(&self.clbit_indices_native, inst.clbits.bind(py))?,
954+
params: inst.params.clone(),
955+
label: inst.label.clone(),
956+
duration: inst.duration.clone(),
957+
unit: inst.unit.clone(),
826958
})
827959
}
828960

829961
fn unpack(&self, py: Python<'_>, inst: &PackedInstruction) -> PyResult<Py<CircuitInstruction>> {
830962
Py::new(
831963
py,
832964
CircuitInstruction {
833-
operation: inst.op.clone_ref(py),
965+
operation: inst.op.clone(),
834966
qubits: PyTuple::new_bound(
835967
py,
836968
self.intern_context
@@ -849,6 +981,10 @@ impl CircuitData {
849981
.collect::<Vec<_>>(),
850982
)
851983
.unbind(),
984+
params: inst.params.clone(),
985+
label: inst.label.clone(),
986+
duration: inst.duration.clone(),
987+
unit: inst.unit.clone(),
852988
},
853989
)
854990
}

0 commit comments

Comments
 (0)