Skip to content

Oxidize the Solovay Kitaev algorithm #14203

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

Merged
merged 19 commits into from
Jun 3, 2025
Merged

Conversation

Cryoris
Copy link
Contributor

@Cryoris Cryoris commented Apr 11, 2025

Summary

This PR implements SolovayKitaevDecomposition in Rust, along with some algorithmic changes and fixes.

Details and comments

Main contributions:

  • faster (*see note below)
  • maintain a single R* tree to construct the set of basic approximations and query from it (previously it used a flat list in construction and re-built a Kd-tree upon every query)

Benchmark of the new code:
image

In comparison the old code (current main) is significantly slower (very roughly a factor 100):
image

(I didn't make a nice plot comparing the runtime data in the same figure, feel free to request, or just squint and look at both numbers separately)

@Cryoris Cryoris added synthesis Rust This PR or issue is related to Rust code in the repository labels Apr 11, 2025
@Cryoris Cryoris added this to the 2.1.0 milestone Apr 11, 2025
@Cryoris Cryoris requested a review from a team as a code owner April 11, 2025 16:18
@qiskit-bot
Copy link
Collaborator

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

  • @Qiskit/terra-core

@mtreinish mtreinish linked an issue Apr 11, 2025 that may be closed by this pull request
@mtreinish mtreinish added performance Changelog: New Feature Include in the "Added" section of the changelog Changelog: Bugfix Include in the "Fixed" section of the changelog labels Apr 11, 2025
Copy link
Member

@jakelishman jakelishman left a comment

Choose a reason for hiding this comment

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

Just bringing in a comment I made offline to Julien: I feel like this PR would be better if we only do the move to Rust space using f64 at first, and then look at methods to improve precision in follow-ups.

Personally, I'm concerned about the big-decimal implementation here. If nothing else, that project is abandoned. That more, my larger issue is that the error rates here suggest to me that we have catastrophic cancellation in floating-point addition/subtraction along the way in the algorithm, and that throwing a software-emulated higher-precision type at it is not a scalable way to go.

The BigFloat type used here is a software-emulated base-10 floating-point number, with just under 133 bits of precision, so about 2.5x greater precision than double-precision IEEE-754 floats. That 2.5 number is then seen again in the precision improvement. I haven't done the maths exactly to verify my expectations, but that feels to me to be indicative of catastrophic cancellations; if it were simply round-off error in matrix construction, we wouldn't expect to see much of an improvement, because the initial matrix constructions are done using f64 maths, so the round-off would have occurred before switching to the new type.

I would need to look into the algorithm and our implementation in a lot more detail, but overall:

  • I wonder if swapping it from an SO3-based decomposition to a direct SU2-based one would help phase issues
  • If using an SU2-based one, perhaps just a quick swap to quaternion-based representations could yield some performance and precision improvements
  • More properly, I feel like we ought to do more research into the floating-point characteristics of this, and understand more where the error terms are coming from. I doubt it's as easier as just wrapping one part of the algorithm in a Kahan sum, but that's the type of thing I'm thinking about.

Copy link
Member

@alexanderivrii alexanderivrii 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 a really impressive work - porting the Solovay-Kitaev algorithm to Rust and fixing a multitude of Python-code bugs we have recently discovered. I still have not actually experimented with the code or looked closely at the two key files math.rs and basic_approximations.rs (I will do this a bit later), but I have left some random comments as I was looking through the other files.

@alexanderivrii
Copy link
Member

One additional quick request: could you please provide a script to reproduce the experiments listed in the summary? And do I understand correctly that Python code is actually faster for depth=8?

@coveralls
Copy link

coveralls commented May 21, 2025

Pull Request Test Coverage Report for Build 15414389477

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

  • 765 of 857 (89.26%) changed or added relevant lines in 9 files are covered.
  • 571 unchanged lines in 17 files lost coverage.
  • Overall coverage increased (+0.05%) to 87.892%

Changes Missing Coverage Covered Lines Changed/Added Lines %
crates/synthesis/src/discrete_basis/basic_approximations.rs 300 310 96.77%
qiskit/synthesis/discrete_basis/solovay_kitaev.py 61 71 85.92%
crates/synthesis/src/discrete_basis/solovay_kitaev.rs 181 213 84.98%
crates/synthesis/src/discrete_basis/math.rs 202 242 83.47%
Files with Coverage Reduction New Missed Lines %
qiskit/circuit/library/pauli_evolution.py 2 96.49%
qiskit/result/sampled_expval.py 2 93.55%
crates/cext/src/exit_codes.rs 4 0.0%
qiskit/synthesis/evolution/suzuki_trotter.py 4 90.38%
qiskit/synthesis/multi_controlled/mcx_synthesis.py 4 98.55%
qiskit/synthesis/evolution/qdrift.py 5 86.49%
crates/qasm2/src/lex.rs 6 91.48%
crates/qasm2/src/parse.rs 6 97.15%
qiskit/synthesis/evolution/product_formula.py 7 89.9%
qiskit/transpiler/instruction_durations.py 7 86.44%
Totals Coverage Status
Change from base Build 15312889782: 0.05%
Covered Lines: 81241
Relevant Lines: 92433

💛 - Coveralls

@Cryoris
Copy link
Contributor Author

Cryoris commented May 23, 2025

One small thing, the following no longer worked for me:

from qiskit.synthesis.discrete_basis.solovay_kitaev import generate_basic_approximations

This is not the proper import path for generate_basic_approximations: this only happened to work because the function was locally imported in solovay_kitaev.py (no longer needed), but the official import path is

from qiskit.synthesis import generate_basic_approximations

Usually we only guarantee that this, API-documented path works.

Copy link
Member

@alexanderivrii alexanderivrii left a comment

Choose a reason for hiding this comment

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

I have looked at the rest of the Rust code (still not the Python code, but that part should be much more straightforward).

I think that using R* to store information is really clever, I like this a lot.

I had two sets of general questions/comments that ended up in many in-code comments.

First, I think that the function recurse should explicitly take an SO(3)-matrix and not a GateSequence. This allows to have GateSequence representing only a sequence of gates, and not also optionally a matrix. And this simplifies the code quite a bit (the changes are mostly automatic).

Second, I was not sure what was true when supporting BigFloats in the first iteration of the code vs. right now. Do we still get better precision when working with gates rather than their U(2)-matrices?

@alexanderivrii
Copy link
Member

One additional question, can you please describe the backwards compatibility issues and solutions to support the Python generate_basic_approximations?

Copy link
Member

@ShellyGarion ShellyGarion left a comment

Choose a reason for hiding this comment

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

Great work. I have a few comments and questions.

Cryoris added 5 commits May 27, 2025 18:11
- gates are not optional
- don't store u2 matrix
- adapting tolerances
- outer prod
- update docs/minors
- better docs
- SO(3) for any pauli
Copy link
Member

@alexanderivrii alexanderivrii left a comment

Choose a reason for hiding this comment

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

Thanks for doing all these changes! This is almost completely ready. I have left a few really minor nitpicks and questions. Other than that, there is a small CI docs issue and you need release notes.


I don't know if you have answered my question on backwards compatibility (I may have missed your reply with so many comments on the issue). Please tell me if I understand the following correctly. The flow of first generating basic approximations and then instantiating SolovayKitaevDecomposition with these approximations is still supported

approx = generate_basic_approximations(basis_gates=gates, depth=depth)
sk = SolovayKitaevDecomposition(approx)

and this uses the new SK code in Rust only.

Saving and loading approximations in the new bin fomat works.

Loading and working with approximations saved in legacy ".npy" format still works, however gives a warning message. Saving in ".npy" format is no longer supported.

Given the above, I don't think there is a breaking API change (you have done an amazing job of keeping all the different ways of using the code intact) unless someone exploits the fact that the newly saved approximations are stored in npy format. Actually I don't consider this an API change as the exact format in which the approximations are saved are not a part of the publicly exposed API, yet we might reflect this in the upgrade section of the release notes. You should also mention that the default values were changed. Also, you probably want to add something to the deprecations section of the release notes.

Comment on lines +125 to +128
/// Get the gate labels.
pub fn label(&self) -> String {
self.gates.iter().map(|gate| gate.name()).collect()
}
Copy link
Member

Choose a reason for hiding this comment

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

Not important, but do we use this function anywhere?

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've used it a fair amount in debugging, I can also remove it, if you prefer 🙂

Copy link
Member

Choose a reason for hiding this comment

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

It's fine to keep it. I am not asking for additional changes, but for debugging purposes I found it useful to print from the Python side the set of basic approximations (that is, gate sequences and corresponding matrices), and now it seems the most I can get is the list of objects of the form <builtins.GateSequence object at 0x1048a4530>. Thus, it might be nice to have a pymethod __str__() for GateSequence, or expose this function label (along with a function that returns the SO(3)-matrix) as pyfunction. But again this is not something I am asking for this PR.

@Cryoris
Copy link
Contributor Author

Cryoris commented Jun 2, 2025

I've actually missed the explanations on backwards compatibility, but what you describe above is exactly correct and there shouldn't be a breaking API change (which is also not allowed 😛). I'll reflect the change in format in the release notes, but note that there are not deprecations yet, only pending deprecations.

- add reno
- fix gate path + more tests
- use gate path per default
- more docs
- tolerances
- rm unused errors
@alexanderivrii alexanderivrii added this pull request to the merge queue Jun 3, 2025
Merged via the queue into Qiskit:main with commit 892b369 Jun 3, 2025
26 checks passed
@Cryoris Cryoris deleted the sk-rust branch June 3, 2025 11:59
mtreinish added a commit to mtreinish/qiskit-core that referenced this pull request Jun 3, 2025
In the recently merged Qiskit#14203 the bincode dependency was added to the
qiskit-synthesis crate to provide a binary serialization of the computed
basic approximations. However, under the covers this library is using
serde and it depends on having anything passed to it be serde
serializable. When building the qiskit-synthesis crate by itself the
hashbrown crate does not enable the serde feature meaning it doesn't
derive the necessary serialization traits for serde. This didn't show up
in a full build because other libraries pulled in as part of the build
cause serde to be enabled for hashbrown. This commit fixes this by
adding the serde feature to the Cargo.toml for hashbrown to ensure we're
building with the feature required in the crate using it.

This also takes the opportunity to fix some small clippy lint issues
identified by the latest stable version of clippy in the synthesis crate
which were introduced in Qiskit#14203 and Qiskit#13929.
mtreinish added a commit to mtreinish/qiskit-core that referenced this pull request Jun 3, 2025
In the recently merged Qiskit#14203 the bincode dependency was added to the
qiskit-synthesis crate to provide a binary serialization of the computed
basic approximations. However, under the covers this library is using
serde and it depends on having anything passed to it be serde
serializable. When building the qiskit-synthesis crate by itself the
hashbrown crate does not enable the serde feature meaning it doesn't
derive the necessary serialization traits for serde. This didn't show up
in a full build because other libraries pulled in as part of the build
cause serde to be enabled for hashbrown. This commit fixes this by
adding the serde feature to the Cargo.toml for hashbrown to ensure we're
building with the feature required in the crate using it.

This also takes the opportunity to fix some small clippy lint issues
identified by the latest stable version of clippy in the synthesis crate
which were introduced in Qiskit#14203 and Qiskit#13929.
github-merge-queue bot pushed a commit that referenced this pull request Jun 3, 2025
In the recently merged #14203 the bincode dependency was added to the
qiskit-synthesis crate to provide a binary serialization of the computed
basic approximations. However, under the covers this library is using
serde and it depends on having anything passed to it be serde
serializable. When building the qiskit-synthesis crate by itself the
hashbrown crate does not enable the serde feature meaning it doesn't
derive the necessary serialization traits for serde. This didn't show up
in a full build because other libraries pulled in as part of the build
cause serde to be enabled for hashbrown. This commit fixes this by
adding the serde feature to the Cargo.toml for hashbrown to ensure we're
building with the feature required in the crate using it.

This also takes the opportunity to fix some small clippy lint issues
identified by the latest stable version of clippy in the synthesis crate
which were introduced in #14203 and #13929.
rahaman-quantum pushed a commit to rahaman-quantum/qiskit that referenced this pull request Jun 20, 2025
* SolovayKitaev in Rust

* fix decorator order

* use `f64`

* fix tests and SO(3) formula

* merge artifacts

* mostly backward compat

* fix tests

* docs

* some initial comments

* simplify GateSequence

- gates are not optional
- don't store u2 matrix

* directly solve Eq. (10)

* more review comments

- adapting tolerances
- outer prod
- update docs/minors

* more review comments

- better docs
- SO(3) for any pauli

* further review comments

- add reno
- fix gate path + more tests
- use gate path per default
- more docs

* more review comments

- tolerances
- rm unused errors
rahaman-quantum pushed a commit to rahaman-quantum/qiskit that referenced this pull request Jun 20, 2025
In the recently merged Qiskit#14203 the bincode dependency was added to the
qiskit-synthesis crate to provide a binary serialization of the computed
basic approximations. However, under the covers this library is using
serde and it depends on having anything passed to it be serde
serializable. When building the qiskit-synthesis crate by itself the
hashbrown crate does not enable the serde feature meaning it doesn't
derive the necessary serialization traits for serde. This didn't show up
in a full build because other libraries pulled in as part of the build
cause serde to be enabled for hashbrown. This commit fixes this by
adding the serde feature to the Cargo.toml for hashbrown to ensure we're
building with the feature required in the crate using it.

This also takes the opportunity to fix some small clippy lint issues
identified by the latest stable version of clippy in the synthesis crate
which were introduced in Qiskit#14203 and Qiskit#13929.
@ShellyGarion ShellyGarion added the fault tolerance related to fault tolerance compilation label Jul 9, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Changelog: Bugfix Include in the "Fixed" section of the changelog Changelog: New Feature Include in the "Added" section of the changelog fault tolerance related to fault tolerance compilation performance Rust This PR or issue is related to Rust code in the repository synthesis
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Port SolovayKitaevDecomposition to Rust
7 participants