Skip to content

Conversation

gadial
Copy link
Contributor

@gadial gadial commented Jan 13, 2025

Summary

Ports the optimize_cx_circ_depth_5n_line method to Rust.

Closes #12228

Details and comments

This is synthesis pass for constructing general linear reversible circuits given by an invertible boolean matrix. The code generating the instruction list was ported to Rust in a straightforward manner; construction of the circuit itself is done in Python.

A quick benchmarking was performed by using random_invertible_binary_matrix(n, seed=seed) and timing the application of synth_cnot_depth_line_kms on the result, showing consistent improvement for the rust version.

╒═════════════════════╤═════════════╤═════════════╤═══════════════════════╕
│ Test Case           │        rust │      python │   Ratio (python/rust) │
╞═════════════════════╪═════════════╪═════════════╪═══════════════════════╡
│ 10x10 (seed 42)     │ 8.58307e-05 │ 0.00221419  │              25.7972  │
├─────────────────────┼─────────────┼─────────────┼───────────────────────┤
│ 5x5 (seed 42)       │ 9.48906e-05 │ 0.000352144 │               3.71106 │
├─────────────────────┼─────────────┼─────────────┼───────────────────────┤
│ 20x20 (seed 55)     │ 0.000299549 │ 0.00747766  │              24.9631  │
├─────────────────────┼─────────────┼─────────────┼───────────────────────┤
│ 50x50 (seed 13)     │ 0.00281119  │ 0.0665068   │              23.6579  │
├─────────────────────┼─────────────┼─────────────┼───────────────────────┤
│ 60x60 (seed 55)     │ 0.00581484  │ 0.119858    │              20.6124  │
├─────────────────────┼─────────────┼─────────────┼───────────────────────┤
│ 70x70 (seed 13)     │ 0.00826478  │ 0.168697    │              20.4115  │
├─────────────────────┼─────────────┼─────────────┼───────────────────────┤
│ 100x100 (seed 1089) │ 0.0262463   │ 0.439383    │              16.7407  │
├─────────────────────┼─────────────┼─────────────┼───────────────────────┤
│ 120x120 (seed 17)   │ 0.0428403   │ 0.675542    │              15.7688  │
├─────────────────────┼─────────────┼─────────────┼───────────────────────┤
│ 150x150 (seed 99)   │ 0.0835741   │ 1.47674     │              17.6699  │
╘═════════════════════╧═════════════╧═════════════╧═══════════════════════╛

@qiskit-bot
Copy link
Collaborator

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

  • @Qiskit/terra-core

@coveralls
Copy link

coveralls commented Jan 13, 2025

Pull Request Test Coverage Report for Build 12986663347

Details

  • 244 of 250 (97.6%) changed or added relevant lines in 5 files are covered.
  • 31 unchanged lines in 5 files lost coverage.
  • Overall coverage decreased (-0.01%) to 88.93%

Changes Missing Coverage Covered Lines Changed/Added Lines %
crates/accelerate/src/synthesis/linear/lnn.rs 226 227 99.56%
crates/accelerate/src/synthesis/linear/utils.rs 10 15 66.67%
Files with Coverage Reduction New Missed Lines %
crates/accelerate/src/unitary_synthesis.rs 1 92.97%
crates/qasm2/src/expr.rs 1 94.23%
crates/qasm2/src/lex.rs 7 91.98%
crates/accelerate/src/synthesis/linear/mod.rs 10 82.8%
crates/qasm2/src/parse.rs 12 97.15%
Totals Coverage Status
Change from base Build 12951053301: -0.01%
Covered Lines: 79549
Relevant Lines: 89451

💛 - Coveralls

@ShellyGarion ShellyGarion added synthesis Rust This PR or issue is related to Rust code in the repository labels Jan 13, 2025
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.

Thanks @gadial for your contribution! I only have some minor comments and questions.

@ShellyGarion
Copy link
Member

I also think we can add some release notes saying that there was a performance improvement in synth_cnot_depth_line_kms since it's implementation was ported to rust

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 Gadi, the PR looks very nice and is quite close to the original Python code. I have left a few comments in-place. The main one is probably to rethink which functions we want to expose from the rust side. You probably want to add release notes as well.

Comment on lines 294 to 299
#[pyfunction]
#[pyo3(signature = (mat))]
pub fn optimize_cx_circ_depth_5n_line(
_py: Python,
mat: PyReadonlyArray2<bool>,
) -> PyResult<(InstructionList, InstructionList)> {
Copy link
Member

Choose a reason for hiding this comment

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

For the rust function to be entirely self-contained and for consistency with other synthesis functions, it would be nice to return PyResult<CircuitData>.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done in 4902815

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 looks great! I have left a few minor in-place suggestions, but I don't feel strongly about any of these, so feel free to ignore, I am completely happy to approve this PR just as it is right now. @ShellyGarion, please take a look to see if you have any more comments. I am not sure if we need release notes: on the one hand, we can skip release notes if there is no API change, but on the other hand it might be nice to highlight that a certain algorithm was ported to Rust, giving 20x performance speedup.

// Find the last "1" in row i, use COL operations to the left in order to
// zero out all other "1"s in that row.
let cols_to_update: Vec<usize> = (0..n).rev().filter(|&j| mat[[i, j]]).collect();
let (first_j, cols_to_update) = cols_to_update.split_first().unwrap();
Copy link
Member

Choose a reason for hiding this comment

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

oh nice! I didn't know about split_first before :)

let cols_to_update: Vec<usize> = (0..n).rev().filter(|&j| mat[[i, j]]).collect();
let (first_j, cols_to_update) = cols_to_update.split_first().unwrap();
cols_to_update.iter().for_each(|j| {
_col_op_inv(mat.view_mut(), *j, *first_j);
Copy link
Member

Choose a reason for hiding this comment

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

Would it make sense to replace _col_op_inv by _col_op and swap the order of the arguments? (I know this is how it was done in the Python code, and badly named at that).

#[pyfunction]
#[pyo3(signature = (mat))]
pub fn optimize_cx_circ_depth_5n_line(
_py: Python,
Copy link
Member

Choose a reason for hiding this comment

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

We can probably skip passing _py to the function.

Copy link
Member

Choose a reason for hiding this comment

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

The function name is not particularly representative of what the function is doing (in particular, it does not optimize anything). Maybe something like synth_cnot_depth_line_pair (but please find a better name than that). Also it seems that our new agreed on convention is to prefix python interface functions with py, i.e. let's change the names to py_synth_cnot_depth_line_pair and to py_synth_cnot_depth_line_kms. We don't follow this convention anywhere else in the synthesis code yet, but I believe someone is working on that already.

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.

Generally it looks good. I only have a minor comment on the docs.

/// `arXiv:quant-ph/0701194 <https://arxiv.org/abs/quant-ph/0701194>`_.
#[pyfunction]
#[pyo3(signature = (mat))]
pub fn py_synth_cnot_depth_line_kms(
Copy link
Member

@ShellyGarion ShellyGarion Jan 26, 2025

Choose a reason for hiding this comment

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

this is a minor comment: both these pyfunctions have exactly the same docstring. could you add an explanation? it seems that the difference is in the args+returns. Could you please add them too (at least to the pyfunctions)?

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, thanks @gadial !

@ShellyGarion ShellyGarion added this pull request to the merge queue Jan 27, 2025
Merged via the queue into Qiskit:main with commit e4743a0 Jan 27, 2025
17 checks passed
@gadial gadial deleted the rust_linear_depth_lnn branch January 27, 2025 12:05
emilkovacev pushed a commit to emilkovacev/qiskit that referenced this pull request Feb 7, 2025
* Port linear_depth_lnn to rust

* Remove unnecessary release note

* Changes according to code review

* More corrections based on the review

* Changes according to code review

* Small fix

* Docstring change according to PR review

* Cargo fmt for version 1.79
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
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_cnot_depth_line_kms to Rust
5 participants