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

Add unitary gate representation to rust #13759

Merged
merged 6 commits into from
Feb 5, 2025

Conversation

mtreinish
Copy link
Member

@mtreinish mtreinish commented Jan 30, 2025

Summary

This commit expands the rust circuit data model to include a definition
of UnitaryGate. This is a more abstract operation than what we've
defined so far in Rust, a gate represented solely by it's unitary matrix
but during normal transpilation we create and interact with these
operations for optimization purposes so having a native representation
in rust is necessary to avoid calling to Python when working with them.

This introduces a new UnitaryGate struct which represents the unpacked
operation. It has 3 internal storage variants based on either an ndarray
arbitrary sized array, a 2x2 nalgebra fixed sized array, or a 4x4
nalgebra fixed sized array. From the python perspective these all look
the same, but being able to internally work with all 3 variants lets us
optimize the code paths for 1q and 2q unitary gates (which are by far
more common) and avoid one layer of pointer indirection.

When stored in a circuit the packed representation is just a pointer to
the actual UnitaryGate which we put in a Box. This is necessary because
the struct size is too large to fit in our compact representation of
operations. Even the ndarray which is a heap allocated type requires more
than our allotted space in a PackedOperation so we need to reduce it's
size by putting it in a Box.

The one major difference here from the previous python based
representation the unitary matrix was stored as an object type parameter
in the PackedInstruction.params field, which now it is stored internally
in the operation itself. There is arguably a behavior change around
this because it's no longer possible to mutate the array of a
UnitaryGate in place once it's inserted into the circuit. While doing
this was horribly unsound, because there was no guardrails for doing it
a release note is added because there is a small use case where it would
have worked and it wasn't explicitly documented.

Details and comments

Closes #13272

A follow-up PR to this one will start to use UnitaryGate directly in rust instead of using the Python version.

This is currently based on top of #13486 and will need to be rebased when that merges. To view the contents of just this PR without #13486 you can look at the HEAD commit: 9bfe14b

@mtreinish mtreinish added on hold Can not fix yet Changelog: API Change Include in the "Changed" section of the changelog 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 Jan 30, 2025
@mtreinish mtreinish added this to the 2.0.0 milestone Jan 30, 2025
@qiskit-bot
Copy link
Collaborator

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

  • @Qiskit/terra-core
  • @levbishop

@coveralls
Copy link

coveralls commented Jan 30, 2025

Pull Request Test Coverage Report for Build 13166968930

Details

  • 117 of 177 (66.1%) changed or added relevant lines in 6 files are covered.
  • 14 unchanged lines in 4 files lost coverage.
  • Overall coverage decreased (-0.04%) to 88.608%

Changes Missing Coverage Covered Lines Changed/Added Lines %
crates/circuit/src/circuit_instruction.rs 33 34 97.06%
crates/accelerate/src/target_transpiler/mod.rs 0 3 0.0%
crates/circuit/src/operations.rs 60 83 72.29%
crates/circuit/src/dag_circuit.rs 8 41 19.51%
Files with Coverage Reduction New Missed Lines %
crates/circuit/src/dag_circuit.rs 1 88.84%
crates/circuit/src/circuit_instruction.rs 2 78.96%
crates/qasm2/src/lex.rs 5 91.98%
crates/qasm2/src/parse.rs 6 97.61%
Totals Coverage Status
Change from base Build 13165103262: -0.04%
Covered Lines: 79344
Relevant Lines: 89545

💛 - Coveralls

mtreinish added a commit to mtreinish/qiskit-core that referenced this pull request Jan 30, 2025
This commit builds off of the native rust representation of a
UnitaryGate added in Qiskit#13759 and uses the native representation
everywhere we were using UnitaryGate in rust via python previously:
the quantum_volume() function and consolidate blocks.

One future item is consolidate blocks can be updated to use nalgebra
types internally instead of ndarray as for the 1 and 2q cases we know
the fixed size of the array ahead of time. However the block
consolidation code is built using ndarray currently and later synthesis
code also works in ndarray so there isn't any real benefit yet, and we'd
just add unecessary conversions and allocations. However, once Qiskit#13649
merges this will change and it would make more sense to add the unitary
gate with a Matrix4. But this can be handled separately after this
merges.
mtreinish added a commit to mtreinish/qiskit-core that referenced this pull request Jan 30, 2025
This commit builds off of the native rust representation of a
UnitaryGate added in Qiskit#13759 and uses the native representation
everywhere we were using UnitaryGate in rust via python previously:
the quantum_volume() function and consolidate blocks.

One future item is consolidate blocks can be updated to use nalgebra
types internally instead of ndarray as for the 1 and 2q cases we know
the fixed size of the array ahead of time. However the block
consolidation code is built using ndarray currently and later synthesis
code also works in ndarray so there isn't any real benefit yet, and we'd
just add unecessary conversions and allocations. However, once Qiskit#13649
merges this will change and it would make more sense to add the unitary
gate with a Matrix4. But this can be handled separately after this
merges.
This commit expands the rust circuit data model to include a definition
of UnitaryGate. This is a more abstract operation than what we've
defined so far in Rust, a gate represented solely by it's unitary matrix
but during normal transpilation we create and interact with these
operations for optimization purposes so having a native representation
in rust is necessary to avoid calling to Python when working with them.

This introduces a new UnitaryGate struct which represents the unpacked
operation. It has 3 internal storage variants based on either an ndarray
arbitrary sized array, a 2x2 nalgebra fixed sized array, or a 4x4
nalgebra fixed sized array. From the python perspective these all look
the same, but being able to internally work with all 3 variants lets us
optimize the code paths for 1q and 2q unitary gates (which are by far
more common) and avoid one layer of pointer indirection.

When stored in a circuit the packed representation is just a pointer to
the actual UnitaryGate which we put in a Box. This is necessary because
the struct size is too large to fit in our compact representation of
operations. Even the ndarray which is a heap allocated type requires more
than our allotted space in a PackedOperation so we need to reduce it's
size by putting it in a Box.

The one major difference here from the previous python based
representation the unitary matrix was stored as an object type parameter
in the PackedInstruction.params field, which now it is stored internally
in the operation itself. There is arguably a behavior change around
this because it's no longer possible to mutate the array of a
UnitaryGate in place once it's inserted into the circuit. While doing
this was horribly unsound, because there was no guardrails for doing it
a release note is added because there is a small use case where it would
have worked and it wasn't explicitly documented.

Closes Qiskit#13272
@mtreinish mtreinish removed the on hold Can not fix yet label Feb 5, 2025
@mtreinish
Copy link
Member Author

I've rebased this now that #13486 has merged and it shouldn't be blocked anymore.

One other small breaking change with this move in the data model is that
UnitaryGate in rust doesn't store it's matrix as a parameter because it
doesn't fit logically in the rust data model for circuits. This wasn't a
problem before recent changes to BasicSimulator, but recent changes
started assuming this. This commit updates this in the basic simulator
and then adds a release note to document the change.
@mtreinish mtreinish requested a review from jyu00 as a code owner February 5, 2025 19:45
Copy link
Contributor

@kevinhartman kevinhartman left a comment

Choose a reason for hiding this comment

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

This is so beautiful (lol). It's nice to see how easily it fits in with the rest of our PackedOperation data model.

Perhaps worth noting, the impl_packable_pointer! macro also implements From<UnitaryGate> for PackedOperation, and that will automatically move the unitary into a Box and pack it into PackedOperation. But, I think what you're doing in FromPyObject for OperationFromPython is more likely to be optimized to elide a copy from stack to heap, so no changes are requested.

Just a few small comments, otherwise!

[OperationRef::Unitary(op_a), OperationRef::Unitary(op_b)] => {
match [&op_a.array, &op_b.array] {
[ArrayType::NDArray(a), ArrayType::NDArray(b)] => {
Ok(relative_eq!(a, b, max_relative = 1e-5, epsilon = 1e-8))
Copy link
Contributor

Choose a reason for hiding this comment

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

Less of a comment on this PR and more of a general question, but what is max_relative vs epsilon, exactly?

I spent some time looking at the docs for the approx crate, but I could quite grok it.

Copy link
Member Author

Choose a reason for hiding this comment

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

Hah, what docs? It's very sparse from what I remember.

The relative_eq! macro is basically checking something like: abs(a - b) <= epsilon + max_relative * abs(b). The equivalent in python land would be numpy.is_close() where epsilon is the atol argument and max_relative is rtol. You can look at the impl for the RelativeEq trait for f64 to get a feeling for how it works.

crates/circuit/src/dag_circuit.rs Outdated Show resolved Hide resolved
crates/circuit/src/operations.rs Outdated Show resolved Hide resolved
crates/circuit/src/circuit_instruction.rs Outdated Show resolved Hide resolved
@mtreinish mtreinish added this pull request to the merge queue Feb 5, 2025
Merged via the queue into Qiskit:main with commit 936ec8e Feb 5, 2025
17 checks passed
@mtreinish mtreinish deleted the unitary-gate-rs branch February 5, 2025 23:16
mtreinish added a commit to mtreinish/qiskit-core that referenced this pull request Feb 5, 2025
This commit builds off of the native rust representation of a
UnitaryGate added in Qiskit#13759 and uses the native representation
everywhere we were using UnitaryGate in rust via python previously:
the quantum_volume() function, consolidate blocks, split2qunitaries,
and unitary synthesis.

One future item is consolidate blocks can be updated to use nalgebra
types internally instead of ndarray as for the 1 and 2q cases we know
the fixed size of the array ahead of time. However the block
consolidation code is built using ndarray currently and later synthesis
code also works in ndarray so there isn't any real benefit yet, and we'd
just add unecessary conversions and allocations. However, once Qiskit#13649
merges this will change and it would make more sense to add the unitary
gate with a Matrix4. But this can be handled separately after this
merges.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Changelog: API Change Include in the "Changed" section of the changelog mod: circuit Related to the core of the `QuantumCircuit` class or the circuit library Rust This PR or issue is related to Rust code in the repository
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Add rust representation of the UnitaryGate class
4 participants