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

Move Bit and Register to live in Rust space #13860

Open
wants to merge 29 commits into
base: main
Choose a base branch
from

Conversation

raynelfss
Copy link
Contributor

@raynelfss raynelfss commented Feb 17, 2025

Summary

The following commits finally bring Bits and Registers entirely to Rust by performing a couple of tasks:

  • Create native representations of Bit (a.k.a. ShareableQubit and ShareableClbit) and Register (a.k.a. QuantumRegister and ClassicalRegister) and move their python counterparts to be managed by rust PyO3.
  • Leverage the usage of native bits and registers in DAGCircuit and CircuitData.
  • Add helper structs RegisterData and BitLocator to represent the layout of bits/registers in the circuit.

All these changes supersede #13686.

Details and comments

As we move more of our core data model to Rust, the main drawback that we've still had is needing to use Python space to create bits and registers. Although simple on the outside, the Bit and Register structures in Python can be quite challenging due to cross referencing that can happen between each type to the other (Bit._register <-> Register[i]] which is not as easy to replicate in Rust space.

With the following commits we aim to move the representation of Bits and Registers to Rust in which the behavior is more similar to that of Python and allows us to share instances between circuits, rather than having the circuit have complete ownership of the bits it contains (which is the reason for preferring this over #13686).

Additions

Bit representation:

To represent a Qubit or Clbit in Rust, we have a couple of identifiers:

  • Within the circuit, Qubit or Clbit which is an index that the circuit can locally refer to when using a bit.
  • Globally, we would use one of two types of Bit:
    • ShareableQubit which represents a global bit.
    • ShareableClbit the classical counterpart to the ShareableQubit.

The base structure for a Bit in Rust, called BitInfo describes a global bit as being one of two things:

  • Owned where the bit is owned by a register.
  • Anonymous where the bit is independent, containing its own unique ID.

Each of these Bits also has an extra property called BitExtraInfo that acts as an identifier, and contains any extra information a Bit of a specific type should have. In the case of ShareableQubit whether the instance is an ancilla or not.

Python:
  • The python counterparts( PyBit, PyQubit, PyClbit and PyAncillaQubit ) have similar behavior and inheritance models as the originals, but should not be subclassed any further as it is no longer supported. See Deprecate subclassing of Register and Bit #13841.
  • The python counterparts may no longer be comparable via is() checks due to conversions in PyO3 not taking on the same addresses.
  • Each of the Rust native bits implements IntoPyObject<'_> so that they can be successfully converted and extracted from their Python counterparts.

Register representation:

To represent a QuantumRegister or ClassicalRegister in Rust, we can use the rust native counterparts of the same name:

  • A rust register is a wrapper to a RegisterInfo which classifies them into two categories:
    • Owning: which owns its bits, can generate them as needed. All of its bits are Owned.
    • Alias: A register that acts as a collection of bits independent from their type.
  • Registers within a circuit should be stored within a RegisterData struct.
Python:
  • Just as with bits, these structs maintain the same inheritance model and behavior as the originals, but should not be subclassed further. See Deprecate subclassing of Register and Bit #13841. They may also not be compared using is(), and the native implementations can be converted to and from Python using the `IntoPyObject<'_> trait.

CircuitData

CircuitData has been upgraded to contain registers, this is how it was achieved.

  • A helper struct called RegisterData was created just to save all of the registers stored in the CircuitData instance.
    • This struct contains a mapping between the Register names and instances.
    • Contains a cached PyDict to allow for a faster visit path from Python.
  • A mapping for qubit instances and its locations has been added in the shape of BitLocator it is essentially a wrapper around an IndexMap and emulates the behavior of QuantumCircuit._{cl, qu}bit_indices.
    • This mapping helps us leverage native types and avoid using Python to fetch the location of a bit.
    • Like RegisterData, it also contains a cached PyDict to enable faster access from python.
  • When processing bit identifiers from Python, we use rust versions of {q,c}bit_argument_conversions methods in QuantumCircuit which perform mappings with native bit types.

DAGCircuit

The DAGCircuit now uses RegisterData and BitLocator in the same way they are used for CircuitData, getting rid of the many PyDict objects being stored within. Said additions removed the need of most py tokens previously needed to modify the circuit's bits and registers.

Questions and Comments

Feel free to leave a review/comment addressing any questions you may have. 🚀

Co-authored-by: Jake Lishman [email protected]

@coveralls
Copy link

coveralls commented Feb 17, 2025

Pull Request Test Coverage Report for Build 13547192049

Warning: This coverage report may be inaccurate.

This pull request's base commit is no longer the HEAD commit of its target branch. This means it includes changes from outside the original pull request, including, potentially, unrelated coverage changes.

Details

  • 1898 of 2242 (84.66%) changed or added relevant lines in 24 files are covered.
  • 362 unchanged lines in 25 files lost coverage.
  • Overall coverage decreased (-0.2%) to 87.777%

Changes Missing Coverage Covered Lines Changed/Added Lines %
qiskit/circuit/library/blueprintcircuit.py 7 9 77.78%
crates/circuit/src/bit_data.rs 46 49 93.88%
crates/circuit/src/bit_locator.rs 55 58 94.83%
qiskit/circuit/quantumcircuit.py 28 33 84.85%
crates/circuit/src/dot_utils.rs 0 7 0.0%
crates/circuit/src/bit.rs 267 295 90.51%
crates/circuit/src/register_data.rs 109 149 73.15%
crates/circuit/src/circuit_data.rs 323 369 87.53%
crates/circuit/src/dag_circuit.rs 518 608 85.2%
crates/circuit/src/register.rs 475 595 79.83%
Files with Coverage Reduction New Missed Lines %
crates/qasm2/src/expr.rs 1 94.23%
qiskit/circuit/library/blueprintcircuit.py 1 94.21%
qiskit/pulse/builder.py 1 85.31%
qiskit/pulse/instructions/reference.py 1 90.32%
crates/accelerate/src/unitary_synthesis.rs 2 94.29%
qiskit/circuit/library/arithmetic/integer_comparator.py 4 94.67%
qiskit/circuit/library/arithmetic/piecewise_linear_pauli_rotations.py 5 92.09%
qiskit/qpy/binary_io/value.py 5 86.26%
qiskit/circuit/library/arithmetic/linear_pauli_rotations.py 6 89.04%
crates/qasm2/src/lex.rs 7 91.48%
Totals Coverage Status
Change from base Build 13512719904: -0.2%
Covered Lines: 78594
Relevant Lines: 89538

💛 - Coveralls

@raynelfss raynelfss added this to the 2.0.0 milestone Feb 17, 2025
- Rebalance currently available api to allow for superclasses `Bit` and `Register` to also live in Rust.
- Remove: `ShareableBit` triat and structs that implement it. Replace them with new structs.
- Add prefix class attribute for python `Register` instances.
- Add `BitExtraInfo` as a soft identifier for `BitInfo`, can be null to identify a `Bit`.
- Add `RegisterInfo::get` method to retrieve the information of a `Bit`.
- Have the rust registers own their counters instead of having them be Python exclusive.
- Make subclassing of `Regster` and `Bit` a bit more effective by helping the subclasses inherit most methods.
raynelfss and others added 7 commits February 20, 2025 15:02
…GCircuit`.

- Modify `BitData` to accept an extra generic value specifying a "sharable" object type that can also be turned into a `PyObject` to allow for compatibility with `Var`.
- Rename `BitAsKey` to `VarAsKey` as it is currently only used for `Var` instances.
- Modify methods in `CommutationChecker` and `GateDirection` passes to use the newer methods.
- Other tweaks and fixes.
…bit`.

- Remove imports of `QUBIT` and `CLBIT` from `DAGCircuit` and `CircuitData`.
- Discarded old equality and hashing methods for `PyBit` and `PyRegister`.
- Fix `replace_bits` to accept any iterator over `Qubits` and `Clbits` from Python.
- Add `is_empty` methods to Register data, wherever `len` is available.
- Add additional parsing during creation of `Register` to more closely match previous behavior.
- Modify `Bit` tests due to mocking no longer operating correctly.
- Add method `register` to `BitData` to better represent a register reference in an owned bit.
- Add missing quotation marks in `Register` repr().
- Bits and registers that live in Rust are not guaranteed to pass the `is` equality check in Python.
- Restore type checking in `DAGCircuit` when adding bits.
- `Bits` should retain their hash value upon deserialization to avoid incorrect retrievals via hash or indexing. Fixed by implementing correct `__reduce__` method in `PyBit`, `PyClbit`, and `PyQubit`.
- Replace `__getnnewargs__` methods with `__reduce__` for more versatile serialization.
- Extend `SliceOrVec` to include a method that allows a vec with negative indices to correctly iterate based on a provided size.
- Modify `FullAncillaAllocation` to not replace the `QuantumRegister` prefix.
- Add `instances_count` getter attribute to all registers.
- `is` comparisons are no longer guaranteed to work between bits and registers. So some tests have been modified to reflect that.
- When `apply_operation` in the `DAGCircuit` receives a clbit in place of a qubit and viceversa, it will throw a type error, not a Key error. This is handled by PyO3 directly during extraction.
- Create `RegisterData` struct which, similarly to how `BitData` works, stores the registers and allows to access them by key, in this case by name or index.
- Tweak `CircuitData` and `QuantumCircuit` api to allow for access of registers from within `CircuitData`.
- Add `qubit_indices` and `clbit_indices` structs to keep track of the locations of bits and registers within the circuit.
- Modify `circuit_to_dag` to obtain the registers directly from `CircuitData`.
- Modify `BlueprintCircuit` to rely on `CircuitData` to access `QuantumRegister` instances.
- Add setters and getters for registers.
- Modify `circuit_to_instruction` to bring over the registers into the definition of the `Instruction`.
- Add `contains` method to `BitData`.
- Add rust native `BitLocations` that can be converted to a `Python` version if needed.
- Implemented `RegisterData` within the `DAGCircuit`.
- Modified methods of the `DAGCircuit` to utilize new native structures wherever possible.
- Modify `RegisterData` to not have a `description` attribute, and use an `IndexMap` to preserve insertion order of the registers.
- Use native initializers for Registers throughout the crate.
- Use native bits in initializer methods for `QuantumCircuit` and `ClassicalCircuit` instead of `BitData`.
- Modify additional `is` checks from tests where not needed.
- Modify `test_gate_definitions` to create owning registers when testing for definitions from the `EquivalenceLibrary`.
- Fix `remove` method to work more efficiently and add `remove_registers` for multiple removals.
- Store index mapping between register name and `RegisterIndex<u32>` which is copyable.
@raynelfss raynelfss added priority: high Rust This PR or issue is related to Rust code in the repository mod: circuit Related to the core of the `QuantumCircuit` class or the circuit library labels Feb 25, 2025
@raynelfss raynelfss changed the title [WIP] Bits and Registers Move Bit and Registers to live in Rust space Feb 25, 2025
@raynelfss raynelfss changed the title Move Bit and Registers to live in Rust space Move Bit and Register to live in Rust space Feb 25, 2025
@raynelfss raynelfss marked this pull request as ready for review February 25, 2025 18:52
@raynelfss raynelfss requested a review from a team as a code owner February 25, 2025 18:52
@qiskit-bot
Copy link
Collaborator

One or more of the following people are relevant to this code:

  • @Cryoris
  • @Qiskit/terra-core
  • @ajavadia
  • @mtreinish
  • @nkanazawa1989

Copy link
Member

@mtreinish mtreinish left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I haven't looked at the code in any depth, but I had some quick questions on the release notes.

Comment on lines +19 to +20
other:
- |
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be an upgrade note because it's an api change that users will need to potentially update their usage for on upgrading.

Suggested change
other:
- |
- |

Comment on lines +12 to +18
:class:`.DAGCircuit` and :class:`.CircuitData` will now store :class:`.Qubit`,
:class:`.Clbit`, as well as its register counterparts, in Rust space.
- The :attr:`.QuantumCircuit._qubit_indices` and :attr:`.QuantumCircuit._clbit_indices`
attributes of the :class:`.QuantumCircuit` class have been moved to live in
rust space and store rust native data.
- The :meth:`.QuantumCircuit._qbit_argument_conversion` and :meth:`.QuantumCircuit._cbit_argument_conversion`
have been moved to Rust to operate with native data.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What are the user implications for this? An upgrade note is a potential call for action from end users saying you may need to change something when you upgrade. Just operating in rust space isn't really a user facing change. Looking at the test changes it's the note below about the is checks, maybe the TypeError instead of DAGCircuitError.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, that's right, these functions weren't exposed to the public either way, so I could remove those parts.

Comment on lines +4 to +9
Python classes :class:`.Bit` and :class:`.Register` have been ported to Rust
along with all their respective subclasses including:
* :class:`.Qubit` and :class:`.QuantumRegister`.
* :class:`.Clbit` and :class:`.ClassicalRegister`.
* :class:`.AncillaQubit` and :class:`.AncillaRegister`.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the user impact for the feature here? Is it faster, etc? The rust refactor is not really a user facing change because the interface the users consume hasn't changed. We normally reserve feature notes for user facing features.

Copy link
Contributor Author

@raynelfss raynelfss Feb 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No user impact whatsoever, so then should I just remove that section and just mention the upgrade parts?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, if there isn't a user impact I would just remove this from the release notes

- Add dispose method to `BitLocator` and `RegisterData`.
- Make `BitInfo` private to the `circuit` crate.
- Add `cached_raw` getter to `BitLocator`.
Copy link
Member

@mtreinish mtreinish left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was mid-review and saw you pushed new commits. So here are the comments I have so far. I'll update the branch and continue reviewing, but I wanted to leave the comments in case they got lost in the github webui.

def __setstate__(self, state):
self._name, self._size, self._hash, self._repr, self._bits = state
self._bit_indices = None
Register = circuit.Register
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not just do: from qiskit._accelerate.circuit import Register ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was following what we do for importing CircuitInstruction, no reason other than that

# Prefix to use for auto naming.
prefix = "a"
bit_type = AncillaQubit
Qubit = circuit.Qubit
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is kind of a weird pattern, normally you'd only do this if you needed to rename the object. You can just import the objects. If lint complains I would add an __all__ to say explicitly the module exports the classes.

Comment on lines +1408 to +1409
qregs=_copy.deepcopy(self._data.qregs, memo),
cregs=_copy.deepcopy(self._data.cregs, memo),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not for this PR necessarily, but I feel like this should all be handled by _data.copy(deepcopy=True) now. With everything living in rust now, it should be easier to just bake this into the CircuitData.copy() implementation (the whole replace_bits() call not just the register piece added here).

@@ -87,7 +97,7 @@ def qregs(self, qregs):
return
self._qregs = []
self._ancillas = []
self._qubit_indices = {}
# self._qubit_indices = {}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
# self._qubit_indices = {}

@@ -39,7 +39,7 @@ def __init__(self, *regs, name: str | None = None) -> None:
super().__init__(*regs, name=name)
self._qregs: list[QuantumRegister] = []
self._cregs: list[ClassicalRegister] = []
self._qubit_indices = {}
# self._qubit_indices = {}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
# self._qubit_indices = {}

Comment on lines +238 to +239
items: PyList::type_object(py)
.call1((qreg.clone(),))?
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can't you just use the pyo3 constructor for a PyList here? Something like:

items: PyList::new(py, qreg.clone())?.unbind(),

@raynelfss raynelfss linked an issue Feb 26, 2025 that may be closed by this pull request
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
mod: circuit Related to the core of the `QuantumCircuit` class or the circuit library priority: high Rust This PR or issue is related to Rust code in the repository
Projects
None yet
5 participants