Skip to content

Conversation

alexanderivrii
Copy link
Member

@alexanderivrii alexanderivrii commented Jul 3, 2024

Summary

Closes #12234.

This ports the function synth_clifford_bm to Rust. Recall that this function optimally synthesizes Clifford operators on 1, 2 or 3 qubits with respect to the number of CX-gates.

Here are the performance comparisons on my laptop between Python and Rust versions for 1000 random n-qubit Cliffords, with n=1, 2, 3.

1000 random 1-qubit cliffords:
Python version: 0.05
Rust version: 0.02
Improvement: 2.5x

1000 random 2-qubit cliffords:
Python version: 14.68
Rust version: 0.03
Improvement: 400x.

1000 random 3-qubit cliffords:
Python version: 30.95
Rust version: 0.38
Improvement: 80x

Details and comments

The circuits produced by the Python and Rust versions should be identical (up to register naming).

This is an almost line-by-line port of the Python file. The only minor difference is that the function reduce_cost returns the list of inverted gates, so that the gates in the final circuit only needs to be reversed and not both reversed and inverted.

There are a few places where I think the Rust code could be improved, but I did not find a good solution. I will highlight this as separate comments and ask for feedback/suggestions.

@alexanderivrii alexanderivrii requested review from ShellyGarion and a team as code owners July 3, 2024 07:05
@qiskit-bot
Copy link
Collaborator

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

  • @Qiskit/terra-core
  • @kevinhartman
  • @mtreinish

Comment on lines +149 to +151
for qubit0 in 0..cliff.num_qubits {
for qubit1 in qubit0 + 1..cliff.num_qubits {
for (n0, n1) in iproduct!(0..3, 0..3) {
Copy link
Member Author

Choose a reason for hiding this comment

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

Feedback wanted! I very much like the iproduct! macro as it allows to write concisely nested for loops. Would it be possible to put for loops for qubit0 and qubit1 inside the same macro? Note that qubit1 iterates from qubit0+1.

Copy link
Contributor

Choose a reason for hiding this comment

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

With iproduct probably not as qubit0 and qubit1 are not independent... unless you'd want to do something like iproduct!(0..n, 0..n, 0..3, 0..3).filter(|(q0, q1, n0, n1)| q1 > q0) -- but it seems to me being explicit like you have it is benevolent for people reading the code in the future 😉

Comment on lines 79 to 84
let xs = clifford.tableau.slice(s![q1, 0..6]).to_owned();
let zs = clifford.tableau.slice(s![q1 + 3, 0..6]).to_owned();
let ys = xs.clone() ^ zs.clone();
let is_loc_x = (xs.clone() & mask.clone()) == xs;
let is_loc_z = (zs.clone() & mask.clone()) == zs;
let is_loc_y = (ys.clone() & mask.clone()) == ys;
Copy link
Member Author

Choose a reason for hiding this comment

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

Feedback wanted!. This is almost a 1-1 rewrite of the Python code. I had to use to_owned and clone to make this compile. This is probably not a huge deal since all the arrays have only 6 elements, but I would like to get rid of this extra copying if possible. I also do not understand why let ys = xs ^ zx; does not compile (it seems that it needs to move both xs or ys). Of course, checking that x & mask == x is equivalent to checking that x has no nonzero-elements on non-masked qubits, leading to an alternative implementation, but the current form is probably a bit clearer.

Copy link
Contributor

Choose a reason for hiding this comment

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

I think you can get away with passing most of these by reference, even your views of xs and zs. Try this:

Suggested change
let xs = clifford.tableau.slice(s![q1, 0..6]).to_owned();
let zs = clifford.tableau.slice(s![q1 + 3, 0..6]).to_owned();
let ys = xs.clone() ^ zs.clone();
let is_loc_x = (xs.clone() & mask.clone()) == xs;
let is_loc_z = (zs.clone() & mask.clone()) == zs;
let is_loc_y = (ys.clone() & mask.clone()) == ys;
let xs = clifford.tableau.slice(s![q1, 0..6]);
let zs = clifford.tableau.slice(s![q1 + 3, 0..6]);
let ys = &xs ^ &zs;
let is_loc_x = (&xs & &mask) == xs;
let is_loc_z = (&zs & &mask) == zs;
let is_loc_y = (&ys & &mask) == ys;

Copy link
Member Author

@alexanderivrii alexanderivrii Jul 19, 2024

Choose a reason for hiding this comment

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

Thank you, @raynelfss! This solves the problem. Done in a89ed9e.

@coveralls
Copy link

coveralls commented Jul 3, 2024

Pull Request Test Coverage Report for Build 9773370551

Details

  • 713 of 764 (93.32%) changed or added relevant lines in 10 files are covered.
  • 8 unchanged lines in 3 files lost coverage.
  • Overall coverage decreased (-0.003%) to 89.868%

Changes Missing Coverage Covered Lines Changed/Added Lines %
crates/accelerate/src/synthesis/clifford/bm_synthesis.rs 273 275 99.27%
crates/accelerate/src/synthesis/clifford/greedy_synthesis.rs 256 263 97.34%
crates/accelerate/src/synthesis/clifford/utils.rs 149 191 78.01%
Files with Coverage Reduction New Missed Lines %
qiskit/circuit/random/utils.py 1 94.83%
qiskit/quantum_info/operators/symplectic/clifford.py 1 91.87%
crates/qasm2/src/lex.rs 6 92.37%
Totals Coverage Status
Change from base Build 9772085193: -0.003%
Covered Lines: 65207
Relevant Lines: 72559

💛 - Coveralls

@alexanderivrii alexanderivrii added performance Rust This PR or issue is related to Rust code in the repository labels Jul 3, 2024
@alexanderivrii alexanderivrii added this to the 1.2.0 milestone Jul 3, 2024
@coveralls
Copy link

coveralls commented Jul 3, 2024

Pull Request Test Coverage Report for Build 9775748915

Details

  • 299 of 301 (99.34%) changed or added relevant lines in 6 files are covered.
  • 7 unchanged lines in 2 files lost coverage.
  • Overall coverage increased (+0.02%) to 89.854%

Changes Missing Coverage Covered Lines Changed/Added Lines %
crates/accelerate/src/synthesis/clifford/bm_synthesis.rs 273 275 99.27%
Files with Coverage Reduction New Missed Lines %
qiskit/circuit/random/utils.py 1 94.83%
crates/qasm2/src/lex.rs 6 91.86%
Totals Coverage Status
Change from base Build 9775174998: 0.02%
Covered Lines: 65228
Relevant Lines: 72593

💛 - Coveralls

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.

LGTM. The functionality looks ok to me. thanks!

@coveralls
Copy link

Pull Request Test Coverage Report for Build 10004179783

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

  • 299 of 301 (99.34%) changed or added relevant lines in 6 files are covered.
  • 14 unchanged lines in 5 files lost coverage.
  • Overall coverage increased (+0.03%) to 89.943%

Changes Missing Coverage Covered Lines Changed/Added Lines %
crates/accelerate/src/synthesis/clifford/bm_synthesis.rs 273 275 99.27%
Files with Coverage Reduction New Missed Lines %
crates/qasm2/src/expr.rs 1 94.02%
qiskit/circuit/random/utils.py 1 94.87%
qiskit/primitives/backend_estimator_v2.py 3 98.29%
crates/qasm2/src/lex.rs 3 93.13%
crates/qasm2/src/parse.rs 6 97.61%
Totals Coverage Status
Change from base Build 9976077291: 0.03%
Covered Lines: 65973
Relevant Lines: 73350

💛 - Coveralls

Copy link
Contributor

@Cryoris Cryoris left a comment

Choose a reason for hiding this comment

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

LGTM as well!

/// for a 3-qubit Clifford.
fn cx_cost3(clifford: &Clifford) -> usize {
// create information transfer matrices r1, r2
let mut r1 = arr2(&[[0, 0, 0], [0, 0, 0], [0, 0, 0]]);
Copy link
Contributor

Choose a reason for hiding this comment

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

You could also use the designated Array::zero function here if you want to type less 0s, as

Suggested change
let mut r1 = arr2(&[[0, 0, 0], [0, 0, 0], [0, 0, 0]]);
let mut r1 = Array2::<u32>::zeros((3, 3));

🙂

Comment on lines +149 to +151
for qubit0 in 0..cliff.num_qubits {
for qubit1 in qubit0 + 1..cliff.num_qubits {
for (n0, n1) in iproduct!(0..3, 0..3) {
Copy link
Contributor

Choose a reason for hiding this comment

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

With iproduct probably not as qubit0 and qubit1 are not independent... unless you'd want to do something like iproduct!(0..n, 0..n, 0..3, 0..3).filter(|(q0, q1, n0, n1)| q1 > q0) -- but it seems to me being explicit like you have it is benevolent for people reading the code in the future 😉

@ShellyGarion ShellyGarion added this pull request to the merge queue Jul 21, 2024
Merged via the queue into Qiskit:main with commit e5ee1a8 Jul 21, 2024
15 checks passed
@ShellyGarion ShellyGarion added the Changelog: New Feature Include in the "Added" section of the changelog label Jul 21, 2024
ElePT pushed a commit to mtreinish/qiskit-core that referenced this pull request Jul 24, 2024
* initial commit

* release notes

* fixing synthesis plugin options

* finalize merge conflicts

* fixing default option values for qft plugins'

* additional tests for qft plugins

* starting to experiment

* porting code

* messy code porting

* printing statements to enable debugging

* fixes

* fixing phase

* removing some of the printing statements

* fixing inaccuracy for cost computation

* Moving some of the functionality to SymplecticMatrix class

* reducing the number of warnings

* formatting

* replacing expensive adjoint and compose operations for symplectic matrices by significantly cheaper in-place prepend operations

* resolving merge conflicts

* cleanup

* code cleanup

* cleanup

* cleanup

* cleanup

* cleanup

* cleanup

* cleanup

* cleanup

* using fast lookup

* cleanup

* clippy

* including params in gate_seq to avoid mapping

* removing unnecessary inner function

* cleanup

* renaming

* changes on the python side

* reno

* adding error handling

* improved error handling

* removing redundant Ok(Some(...))

* using random_clifford in tests

* reorganizing clifford code

* fixes

* formatting

* improved error handling

* do not panic

* formatting

* Applying refactoring suggestions d/utils.rs from code review

* release notes update

* starting to port code

* continuing to port code

* porting + fixing

* polishing

* changes on the python side; modifying tests to include missing testcases and to use random_clifford instead of random_clifford_circuit since the former provides better randomness guarantees

* release notes (last but not least)

* Correct reported speedups

* applying suggestion from code review

* resolving even more merge conflicts
Procatv pushed a commit to Procatv/qiskit-terra-catherines that referenced this pull request Aug 1, 2024
* initial commit

* release notes

* fixing synthesis plugin options

* finalize merge conflicts

* fixing default option values for qft plugins'

* additional tests for qft plugins

* starting to experiment

* porting code

* messy code porting

* printing statements to enable debugging

* fixes

* fixing phase

* removing some of the printing statements

* fixing inaccuracy for cost computation

* Moving some of the functionality to SymplecticMatrix class

* reducing the number of warnings

* formatting

* replacing expensive adjoint and compose operations for symplectic matrices by significantly cheaper in-place prepend operations

* resolving merge conflicts

* cleanup

* code cleanup

* cleanup

* cleanup

* cleanup

* cleanup

* cleanup

* cleanup

* cleanup

* using fast lookup

* cleanup

* clippy

* including params in gate_seq to avoid mapping

* removing unnecessary inner function

* cleanup

* renaming

* changes on the python side

* reno

* adding error handling

* improved error handling

* removing redundant Ok(Some(...))

* using random_clifford in tests

* reorganizing clifford code

* fixes

* formatting

* improved error handling

* do not panic

* formatting

* Applying refactoring suggestions d/utils.rs from code review

* release notes update

* starting to port code

* continuing to port code

* porting + fixing

* polishing

* changes on the python side; modifying tests to include missing testcases and to use random_clifford instead of random_clifford_circuit since the former provides better randomness guarantees

* release notes (last but not least)

* Correct reported speedups

* applying suggestion from code review

* resolving even more merge conflicts
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Changelog: New Feature Include in the "Added" section of the changelog 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 synth_clifford_bm to Rust
6 participants