Skip to content

Commit a415913

Browse files
Add C/Python interop for QuantumRegister and ClassicalRegister (#15768)
* Add C/Python interop for `QuantumRegister` and `ClassicalRegister` This is largely very straightforwards, since the types are clearly separated between Rust and Python space. The only additional trickery needed was to allow the internal `QuantumRegister` to be borrowed from the `PyQuantumRegister` instead of cloning an additional reference to it. While the runtime savings are incredibly minor, the important detail is that the register does not need to be freed by the C caller, which matches the assumptions of all the other Python-space interoperation methods. * Fix typos Co-authored-by: Max Rossmannek <rmax@ethz.ch> * Fix doubled backticks Co-authored-by: Max Rossmannek <21973473+mrossinek@users.noreply.github.com> * Fix typos from merge resolution Co-authored-by: Max Rossmannek <21973473+mrossinek@users.noreply.github.com> --------- Co-authored-by: Max Rossmannek <rmax@ethz.ch> Co-authored-by: Max Rossmannek <21973473+mrossinek@users.noreply.github.com>
1 parent 2be5b9f commit a415913

7 files changed

Lines changed: 338 additions & 19 deletions

File tree

crates/cext/src/circuit.rs

Lines changed: 227 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1374,6 +1374,9 @@ pub unsafe extern "C" fn qk_opcounts_clear(op_counts: *mut OpCounts) {
13741374
mod py {
13751375
use crate::circuit::mut_ptr_as_ref;
13761376
use pyo3::prelude::*;
1377+
use qiskit_circuit::bit::{
1378+
ClassicalRegister, PyClassicalRegister, PyQuantumRegister, QuantumRegister,
1379+
};
13771380
use qiskit_circuit::circuit_data::{CircuitData, PyCircuitData};
13781381

13791382
/// @ingroup QkCircuit
@@ -1479,7 +1482,7 @@ mod py {
14791482
// SAFETY: per documentation, we are attached to a Python interpreter, and `ob` points to a
14801483
// valid PyObject.
14811484
unsafe {
1482-
crate::py::borrow_map::<PyCircuitData, CircuitData>(
1485+
crate::py::borrow_map_mut::<PyCircuitData, CircuitData>(
14831486
Python::assume_attached(),
14841487
ob,
14851488
// If in the future we change `PyCircuitData` to store an `Arc<RwLock>`, look at
@@ -1490,16 +1493,16 @@ mod py {
14901493
}
14911494
}
14921495

1493-
/// @ingroup QkDag
1496+
/// @ingroup QkCircuit
14941497
/// Retrieve a `QkCircuit` pointer from a Python object.
14951498
///
14961499
/// Note that the input to this function should _not_ be `QuantumCircuit`, but the output of
14971500
/// `QuantumCircuit._data`. This is necessary to enforce correct reference-counting semantics.
14981501
///
14991502
/// This borrows a Python reference and extracts the `QkCircuit` pointer for it into
15001503
/// ``address``, if it is of the correct type. The returned pointer is borrowed from the
1501-
/// `object` pointer. If the ``PyObject`` is not the correct type, the return value is 1, the
1502-
/// exception state of the Python interpreter is set, and ``address`` is unchanged.
1504+
/// `object` pointer. If the `PyObject` is not the correct type, the return value is 0, the
1505+
/// exception state of the Python interpreter is set, and `address` is unchanged.
15031506
///
15041507
/// You must be attached to a Python interpreter to call this function.
15051508
///
@@ -1508,7 +1511,7 @@ mod py {
15081511
///
15091512
/// @param object A borrowed Python object.
15101513
/// @param address The location to write the output to.
1511-
/// @return 0 on success, 1 on failure.
1514+
/// @return 1 on success, 0 on failure.
15121515
///
15131516
/// # Safety
15141517
///
@@ -1524,14 +1527,232 @@ mod py {
15241527
// SAFETY: per documentation, we are attached to a Python interpreter, `object` is a valid
15251528
// pointer to a PyObject, and `address` points to enough space to write a pointer.
15261529
unsafe {
1527-
crate::py::convert_map::<PyCircuitData, CircuitData>(
1530+
crate::py::convert_map_mut::<PyCircuitData, CircuitData>(
15281531
Python::assume_attached(),
15291532
object,
15301533
address,
15311534
|_py, qc| Ok(&mut qc.inner),
15321535
)
15331536
}
15341537
}
1538+
1539+
/// @ingroup QkCircuit
1540+
/// Pass ownership of a `QkQuantumRegister` object to Python.
1541+
///
1542+
/// It is not safe to use the `QkQuantumRegister` pointer after calling this function. In
1543+
/// particular, you should not attempt to clear or free it. The caller must own the
1544+
/// `QkQuantumRegister`, not hold a borrowed reference (for example, a `QkQuantumRegister *`
1545+
/// retrieved from `qk_quantum_register_borrow_from_python` is not owned).
1546+
///
1547+
/// @param qr The owned object.
1548+
/// @return An owned Python reference to the object.
1549+
///
1550+
/// # Safety
1551+
///
1552+
/// The caller must be attached to a Python interpreter. Behavior is undefined if `qr` is not
1553+
/// a valid non-null pointer to an initialized and owned `QkQuantumRegister`.
1554+
#[unsafe(no_mangle)]
1555+
pub unsafe extern "C" fn qk_quantum_register_to_python(
1556+
qr: *mut QuantumRegister,
1557+
) -> *mut ::pyo3::ffi::PyObject {
1558+
// SAFETY: per documentation, we are attached to a Python interpreter.
1559+
let py = unsafe { Python::assume_attached() };
1560+
// SAFETY: per documentation, `dag` points to owned and valid data.
1561+
let qreg = unsafe { Box::from_raw(mut_ptr_as_ref(qr)) };
1562+
match qreg.into_pyobject(py) {
1563+
Ok(ob) => ob.into_ptr(),
1564+
Err(e) => {
1565+
e.restore(py);
1566+
::std::ptr::null_mut()
1567+
}
1568+
}
1569+
}
1570+
1571+
/// @ingroup QkCircuit
1572+
/// Retrieve a `QkQuantumRegister` pointer from a Python object.
1573+
///
1574+
/// This borrows a Python reference and extracts the `QkQuantumRegister` pointer for it, if it
1575+
/// is of the correct type. The returned pointer is borrowed from the `ob` pointer. If the
1576+
/// `PyObject` is not the correct type, the return value is `NULL` and the exception state of
1577+
/// the Python interpreter is set.
1578+
///
1579+
/// You must be attached to a Python interpreter to call this function.
1580+
///
1581+
/// You can also use `qk_quantum_register_convert_from_python`, which is logically the exact
1582+
/// same as this function, but can be directly used as a "converter" function for the
1583+
/// `PyArg_Parse*` family of Python converter functions.
1584+
///
1585+
/// @param ob A borrowed Python object.
1586+
/// @return A pointer to the native object, or `NULL` if the Python object is the wrong type.
1587+
///
1588+
/// # Safety
1589+
///
1590+
/// The caller must be attached to a Python interpreter. Behavior is undefined if `ob` is
1591+
/// not a valid non-null pointer to a Python object.
1592+
#[unsafe(no_mangle)]
1593+
#[cfg(feature = "python_binding")]
1594+
pub unsafe extern "C" fn qk_quantum_register_borrow_from_python(
1595+
ob: *mut pyo3::ffi::PyObject,
1596+
) -> *const QuantumRegister {
1597+
// SAFETY: per documentation, we are attached to a Python interpreter, and `ob` points to a
1598+
// valid PyObject.
1599+
unsafe {
1600+
crate::py::borrow_map::<PyQuantumRegister, QuantumRegister>(
1601+
Python::assume_attached(),
1602+
ob,
1603+
|_py, qr| Ok(qr),
1604+
)
1605+
}
1606+
}
1607+
1608+
/// @ingroup QkCircuit
1609+
/// Retrieve a `QkQuantumRegister` pointer from a Python object.
1610+
///
1611+
/// This borrows a Python reference and extracts the `QkQuantumRegister` pointer for it into
1612+
/// `address`, if it is of the correct type. The returned pointer is borrowed from the
1613+
/// `object` pointer. If the `PyObject` is not the correct type, the return value is 0, the
1614+
/// exception state of the Python interpreter is set, and `address` is unchanged.
1615+
///
1616+
/// You must be attached to a Python interpreter to call this function.
1617+
///
1618+
/// You can also use `qk_quantum_register_borrow_from_python`, which is logically the exact same
1619+
/// as this, but with a more natural signature for direct usage.
1620+
///
1621+
/// @param object A borrowed Python object.
1622+
/// @param address The location to write the output to.
1623+
/// @return 1 on success, 0 on failure.
1624+
///
1625+
/// # Safety
1626+
///
1627+
/// The caller must be attached to a Python interpreter. Behavior is undefined if `object`
1628+
/// is not a valid non-null pointer to a Python object, or if `address` is not a pointer to
1629+
/// writeable data of the correct type.
1630+
#[unsafe(no_mangle)]
1631+
#[cfg(feature = "python_binding")]
1632+
pub unsafe extern "C" fn qk_quantum_register_convert_from_python(
1633+
object: *mut ::pyo3::ffi::PyObject,
1634+
address: *mut ::std::ffi::c_void,
1635+
) -> ::std::ffi::c_int {
1636+
// SAFETY: per documentation, we are attached to a Python interpreter, `object` is a valid
1637+
// pointer to a PyObject, and `address` points to enough space to write a pointer.
1638+
unsafe {
1639+
crate::py::convert_map::<PyQuantumRegister, QuantumRegister>(
1640+
Python::assume_attached(),
1641+
object,
1642+
address,
1643+
|_py, qr| Ok(qr),
1644+
)
1645+
}
1646+
}
1647+
1648+
/// @ingroup QkCircuit
1649+
/// Pass ownership of a `QkClassicalRegister` object to Python.
1650+
///
1651+
/// It is not safe to use the `QkClassicalRegister` pointer after calling this function. In
1652+
/// particular, you should not attempt to clear or free it. The caller must own the
1653+
/// `QkClassicalRegister`, not hold a borrowed reference (for example, a `QkClassicalRegister *`
1654+
/// retrieved from `qk_classical_register_borrow_from_python` is not owned).
1655+
///
1656+
/// @param cr The owned object.
1657+
/// @return An owned Python reference to the object.
1658+
///
1659+
/// # Safety
1660+
///
1661+
/// The caller must be attached to a Python interpreter. Behavior is undefined if `cr` is not
1662+
/// a valid non-null pointer to an initialized and owned `QkClassicalRegister`.
1663+
#[unsafe(no_mangle)]
1664+
pub unsafe extern "C" fn qk_classical_register_to_python(
1665+
cr: *mut ClassicalRegister,
1666+
) -> *mut ::pyo3::ffi::PyObject {
1667+
// SAFETY: per documentation, we are attached to a Python interpreter.
1668+
let py = unsafe { Python::assume_attached() };
1669+
// SAFETY: per documentation, `dag` points to owned and valid data.
1670+
let cr = unsafe { Box::from_raw(mut_ptr_as_ref(cr)) };
1671+
match cr.into_pyobject(py) {
1672+
Ok(ob) => ob.into_ptr(),
1673+
Err(e) => {
1674+
e.restore(py);
1675+
::std::ptr::null_mut()
1676+
}
1677+
}
1678+
}
1679+
1680+
/// @ingroup QkCircuit
1681+
/// Retrieve a `QkClassicalRegister` pointer from a Python object.
1682+
///
1683+
/// This borrows a Python reference and extracts the `QkClassicalRegister` pointer for it, if it
1684+
/// is of the correct type. The returned pointer is borrowed from the `ob` pointer. If the
1685+
/// `PyObject` is not the correct type, the return value is `NULL` and the exception
1686+
/// state of the Python interpreter is set.
1687+
///
1688+
/// You must be attached to a Python interpreter to call this function.
1689+
///
1690+
/// You can also use `qk_classical_register_convert_from_python`, which is logically the exact
1691+
/// same as this function, but can be directly used as a "converter" function for the
1692+
/// `PyArg_Parse*` family of Python converter functions.
1693+
///
1694+
/// @param ob A borrowed Python object.
1695+
/// @return A pointer to the native object, or `NULL` if the Python object is the wrong type.
1696+
///
1697+
/// # Safety
1698+
///
1699+
/// The caller must be attached to a Python interpreter. Behavior is undefined if `ob` is
1700+
/// not a valid non-null pointer to a Python object.
1701+
#[unsafe(no_mangle)]
1702+
#[cfg(feature = "python_binding")]
1703+
pub unsafe extern "C" fn qk_classical_register_borrow_from_python(
1704+
ob: *mut pyo3::ffi::PyObject,
1705+
) -> *const ClassicalRegister {
1706+
// SAFETY: per documentation, we are attached to a Python interpreter, and `ob` points to a
1707+
// valid PyObject.
1708+
unsafe {
1709+
crate::py::borrow_map::<PyClassicalRegister, ClassicalRegister>(
1710+
Python::assume_attached(),
1711+
ob,
1712+
|_py, cr| Ok(cr),
1713+
)
1714+
}
1715+
}
1716+
1717+
/// @ingroup QkCircuit
1718+
/// Retrieve a `QkClassicalRegister` pointer from a Python object.
1719+
///
1720+
/// This borrows a Python reference and extracts the `QkClassicalRegister` pointer for it into
1721+
/// `address`, if it is of the correct type. The returned pointer is borrowed from the
1722+
/// `object` pointer. If the `PyObject` is not the correct type, the return value is 0, the
1723+
/// exception state of the Python interpreter is set, and `address` is unchanged.
1724+
///
1725+
/// You must be attached to a Python interpreter to call this function.
1726+
///
1727+
/// You can also use `qk_classical_register_borrow_from_python`, which is logically the exact same as this,
1728+
/// but with a more natural signature for direct usage.
1729+
///
1730+
/// @param object A borrowed Python object.
1731+
/// @param address The location to write the output to.
1732+
/// @return 1 on success, 0 on failure.
1733+
///
1734+
/// # Safety
1735+
///
1736+
/// The caller must be attached to a Python interpreter. Behavior is undefined if `object`
1737+
/// is not a valid non-null pointer to a Python object, or if `address` is not a pointer to
1738+
/// writeable data of the correct type.
1739+
#[unsafe(no_mangle)]
1740+
#[cfg(feature = "python_binding")]
1741+
pub unsafe extern "C" fn qk_classical_register_convert_from_python(
1742+
object: *mut ::pyo3::ffi::PyObject,
1743+
address: *mut ::std::ffi::c_void,
1744+
) -> ::std::ffi::c_int {
1745+
// SAFETY: per documentation, we are attached to a Python interpreter, `object` is a valid
1746+
// pointer to a PyObject, and `address` points to enough space to write a pointer.
1747+
unsafe {
1748+
crate::py::convert_map::<PyClassicalRegister, ClassicalRegister>(
1749+
Python::assume_attached(),
1750+
object,
1751+
address,
1752+
|_py, cr| Ok(cr),
1753+
)
1754+
}
1755+
}
15351756
}
15361757
#[cfg(feature = "python_binding")]
15371758
pub use py::*;

crates/cext/src/dag.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1830,7 +1830,7 @@ pub unsafe extern "C" fn qk_dag_borrow_from_python(
18301830
) -> *mut DAGCircuit {
18311831
// SAFETY: per documentation, we are attached to a Python interpreter, and `ob` points to a
18321832
// valid PyObject.
1833-
unsafe { crate::py::borrow(::pyo3::Python::assume_attached(), ob) }
1833+
unsafe { crate::py::borrow_mut(::pyo3::Python::assume_attached(), ob) }
18341834
}
18351835

18361836
/// @ingroup QkDag
@@ -1863,5 +1863,7 @@ pub unsafe extern "C" fn qk_dag_convert_from_python(
18631863
) -> ::std::ffi::c_int {
18641864
// SAFETY: per documentation, we are attached to a Python interpreter, `object` is a valid
18651865
// pointer to a PyObject, and `address` points to enough space to write a pointer.
1866-
unsafe { crate::py::convert::<DAGCircuit>(::pyo3::Python::assume_attached(), object, address) }
1866+
unsafe {
1867+
crate::py::convert_mut::<DAGCircuit>(::pyo3::Python::assume_attached(), object, address)
1868+
}
18671869
}

0 commit comments

Comments
 (0)