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

Use native bits and registers for CircuitData. #13686

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

Conversation

raynelfss
Copy link
Contributor

@raynelfss raynelfss commented Jan 17, 2025

Summary

The following commits add native bits and register usage to CircuitData. This is the first public attempt at implementing these structures and is open to feedback.

Details and comments

As we continue to move towards having more rust-native components as part of our core data model, we need to continue to promote more functionality to be able to work without needing intervention from Python. #13269 describes the cost paid whenever an instance of CircuitData and DAGCircuit needs to be constructed from Rust. There is a constant need for calling back to Python to create instances of Qubit and Clbit as needed.

An important point made towards this is to transfer the ownership of Bits and Registers to the circuit, but also to keep the current functionality of this API. Meaning that, instead of removing the dependency completely, we had to shift the source of truth to rust. The following commits achieve this by performing the following:

Adding representations of QuantumRegister and ClassicalRegister in Rust.

A simple representation using an IndexSet, and attributes such as name and size.

Modifying BitData currently NewBitData to also store register information and dynamically initialize the Python counter parts as requested:

  • Registers are now kept in a Vec<_> and can be accessed by index or by key (RegisterKey).
  • Bits are still referred to as either Qubit(u32) and Clbit(u32) and there exists a mapping between Python Bits and the specified index type.
    • The mapping has to be inside of an RWLock due to the dynamic initialization of Python Bits.
  • A new attribute: BitInfo keeps a list, organized by the bit's index, which keeps track of:
    • Whether a bit was added by a register into the circuit or not.
    • Which registers and which index within said register(s) does that bit belong in.
  • Separate python specific and rust methods:
    • Python methods will perform dynamic initialization of bits and registers in Python when needed.
  • Both Bits and Registers from Python are stored as a OnceLock which gets dynamically initialized upon request based on the information stored here.

Changes to CircuitData

A new Python-free constructor CircuitData::new() was created to allow initialization of Rust instances without any need for interaction with Python unless otherwise requested.

  • Modifications were also added to the CircuitData::with_capacity constructor to also add the bits dynamically without accessing Python.
    • This constructor will still need a py token due to assignments to the ParameterTable.

Known issues and drawbacks

  • copy() can take up to twice as long to perform a copy of the circuit compared to previous iterations. This most likely due re-mapping of the registers.
  • Risks with using OnceLock and RWLock as these can panic under the wrong circumstances.
  • Python Bits are fully opaque to the Rust ones and there's no validation other than the mapping and information stored in Rust.
  • DAGCircuit does not use any of the new additions.
    • This is mostly due to the robust nature of the changes and a follow-up PR will add these changes to the DAGCircuit.
  • Testing in Rust does not allow us to check if conversion works as needed into a QuantumCircuit.

Tasks:

  • Implement a new BitData struct to store registers.
  • Implement the new BitData in CircuitData.
  • Fix all the broken tests.
  • Add rust-native tests to check if this is working properly.
  • Implement partial python cross testing.

Questions or comments?

Feel free to leave a review with any questions or concerns you might have.

@raynelfss raynelfss linked an issue Jan 17, 2025 that may be closed by this pull request
raynelfss and others added 6 commits January 21, 2025 15:21
- Some functions in `CircuitData` had become mutable unnecessarily due to erroneous assumptions on the `BitData` `OnceCell` usage.
- Fix incorrect serialization to now include registers in `CircuitData::__reduce__`.
- Also add `CircuitData::__setstate__` to deal with the new register state.
- Fix bug with `BlueprintCircuit` that discarded most instances of `QuantumRegister`.
- Rename `RegisterAsKey::index()` to size, since it was erroneously named.
Copy link
Contributor

@to24toro to24toro left a comment

Choose a reason for hiding this comment

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

Thank you for sharing this pull request. I understand that this is currently marked as a WIP and a draft, so I was initially hesitant to leave comments. However, I do have a few questions and suggestions that I hope will be helpful as the work progresses.

crates/circuit/src/bit.rs Outdated Show resolved Hide resolved
Comment on lines +8 to +11
pub struct BitInfo {
added_by_reg: bool,
registers: Vec<BitLocation>,
}
Copy link
Contributor

Choose a reason for hiding this comment

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

The BitInfo struct and its registers field may be somewhat ambiguous in conveying their true purpose.It appears that BitInfo primarily tracks how bits are associated with registers.

You might consider renaming this BitInfo to something more indicative of its focus on bit-register relationships or placements.

However, if you assume how to use it in another way, it is not necessary to rename this field.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

You got the intended use correctly. I'm open to renaming this, since it is WiP you can leave any suggestions.

@@ -230,3 +235,602 @@ where
self.bits.clear();
}
}

#[derive(Clone, Debug)]
pub struct NewBitData<T: From<BitType>, R: Register + Hash + Eq> {
Copy link
Contributor

Choose a reason for hiding this comment

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

[Q] Both BitData<T> and NewBitData<T, R>, which both manage and track bit-related information. However, it’s not entirely clear how they are intended to coexist. Are they meant to be used in parallel for different scenarios(is BitData<T> for dag-circuit?), or is there a plan to eventually replace BitData<T> with NewBitData<T, R>? Any clarification on whether BitData<T> is deprecated or if both are needed for distinct reasons would be greatly appreciated.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

NewBitData here is meant to replace BitData, the only reason why I'm keeping the old one around still is because the new replacement methods will need a bigger rewrite of DAGCircuit's methods. Which I plan on doing on a separate PR due to how big it is.

let res = py_reg.unwrap().bind(py).get_item(bit_info.index())?;
self.bits[index_as_usize]
.set(res.into())
.map_err(|_| PyRuntimeError::new_err("Could not set the OnceCell correctly"))?;
Copy link
Contributor

Choose a reason for hiding this comment

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

Instead of simply saying "Could not set the OnceCell correctly", you might include any relevant internal state or the specific condition that led to the failure. This added detail will greatly aid in diagnosing issues.

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 agree with this.

raynelfss and others added 7 commits January 27, 2025 15:32
- Fix `BitLocation` updates when a bit or a register is added to use data directly from `CircuitData`.
- Fix incorrect register re-assignment in `QuantumCircuit._from_circuit_data`.
- Re-organize `BitData::py_get_bit_location` to behave more closely to python.
Co-authored-by: Kento Ueda <[email protected]>
- Add additional checks to `find_bit()`, to make sure bit exists in the `CircuitData` instance.
- Separate python `add_register` from native method due to conflicting implementation.
- Fix register setter in `NewBitData` to discard old `BitLocations`.
- Update `BitLocations` when setting a new register in `QuantumCircuit`.
- Modify test to include registers when comparing to an equivalency from the `EquivalenceLibrary`.
- Add one test case to test initialization.
- Modify the getter methods for the cached lists to correct the cache if it becomes necessary. This to avoid the uninitialization of bits when rust bits are added after requesting access from Python. These methods are now fallible, so a PyResult will always be returned.
- Adapt `CircuitData` and `DAGCircuit` to these changes.
@coveralls
Copy link

coveralls commented Jan 29, 2025

Pull Request Test Coverage Report for Build 13185407140

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

  • 583 of 987 (59.07%) changed or added relevant lines in 11 files are covered.
  • 812 unchanged lines in 13 files lost coverage.
  • Overall coverage decreased (-0.4%) to 88.254%

Changes Missing Coverage Covered Lines Changed/Added Lines %
qiskit/circuit/library/blueprintcircuit.py 7 9 77.78%
qiskit/circuit/quantumcircuit.py 40 42 95.24%
crates/circuit/src/lib.rs 3 6 50.0%
crates/circuit/src/operations.rs 0 3 0.0%
crates/circuit/src/register.rs 38 104 36.54%
crates/circuit/src/circuit_data.rs 145 255 56.86%
crates/circuit/src/bit_data.rs 299 517 57.83%
Files with Coverage Reduction New Missed Lines %
crates/accelerate/src/euler_one_qubit_decomposer.rs 1 91.5%
qiskit/circuit/library/blueprintcircuit.py 1 94.31%
crates/qasm2/src/lex.rs 5 92.48%
crates/circuit/src/bit_data.rs 8 63.99%
crates/qasm2/src/parse.rs 12 97.15%
qiskit/transpiler/preset_passmanagers/builtin_plugins.py 13 95.74%
crates/accelerate/src/commutation_checker.rs 16 97.35%
qiskit/transpiler/preset_passmanagers/common.py 20 85.94%
crates/circuit/src/packed_instruction.rs 22 94.24%
crates/accelerate/src/target_transpiler/mod.rs 39 81.11%
Totals Coverage Status
Change from base Build 13165103262: -0.4%
Covered Lines: 79800
Relevant Lines: 90421

💛 - Coveralls

raynelfss and others added 10 commits January 30, 2025 11:59
- Remove stale methods `contains` and `get_interned` from `Interner` in favor of the changes introduced by Qiskit#13752.
- Temporarily fix incorrect null assignment of `Bit` object to indices. A different solution can be achieved if more time is spent.
- Incorrect python circuit creation in Python due to misusage of the bit's `BitInfo` property inside of `BitData`.
- Fix incorrect register and bit creation from `CircuitData::new()`
- Add provisional `qubits_mut()` and `clbits_mut()` methods while we address the issue with `indices`.
- Fix error where rust bits wouldn't be initialized in the cache.
- Provisionally fix error in which rust bits wouldn't have valid mapping to python bits. Bits need to be generated before being able to map them to a rust native index. The current solution implements said changes during the `find_bit` function, in which there is an extra validation step. However, this is certain to consume more resources. The ideal solution would be to generate said mapping when obtaining the Python bit with something similar to a `RefCell`. However, `RefCells` are not thread safe and could cause several mutable references to coexist.
- Re-purpose `NewBitData.indices` to be modifiable even when if a mutable references is not granted by using `RWLock`. This is done to stay consistent with the behavior of `OnceLock` which allows us to initialize bits upon request. We need to make sure to map a bit once it has been initialized. Otherwise, the circuit will have to regenerate this mapping multiple times during runtime.
- Optimize pre-caching of Python bits and registers by using the cache size. Bits and registers are guaranteed to be initialized in ther order they were added/last accessed, so in the case a cache is initialized prematurely, add the missing bits or registers to the back of the cache while following the same index order.
…es of the inner `CircuitData`.

- Fix docstrings in `CircuitData` and `BitData`.
- Remove unnecessary mutable reference in `find_bit()`.
- Register constructors will now accept `Cow<'_, [T]>` to prevent ownership issues.
- `CircuitData::{qregs, cregs}` will now return a slice of registers, instead of an iterator.
- Add duplicate tests for when registers are added to any circuit from rust.
- Add two more rust native tests for circuit construction with registers present.
- Add the ability of turning a float into a `Param` by calling `Into::into`.
@1ucian0 1ucian0 added this to the 2.0.0 milestone Feb 6, 2025
@raynelfss raynelfss marked this pull request as ready for review February 6, 2025 19:01
@raynelfss raynelfss requested a review from a team as a code owner February 6, 2025 19:01
@qiskit-bot
Copy link
Collaborator

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

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

@raynelfss raynelfss changed the title [WIP] Use native bits and registers for CircuitData. Use native bits and registers for CircuitData. Feb 6, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Make rust Qubit and Clbit the source of truth in a circuit
5 participants