qiskit icon indicating copy to clipboard operation
qiskit copied to clipboard

Prepare `PauliEvolutionGate` for Rustiq & port it to Rust

Open Cryoris opened this issue 1 year ago • 2 comments

Summary

Port the PauliEvolutionGate synthesis to Rust, plus, expose the Pauli network and allow other plugins to synthesize the gate. Also adds the plugin structure for the gate for #12789.

Details and comments

The larger the Pauli network to synthesis, the better the speedup from the port to Rust. Here I measured a Heisenberg Hamiltonian (XX+YY+ZZ on interacting qubits, plus 1-qubit Z on each qubit) on a square lattice for different settings:

1 timestep for first order Trotter: speedup @ 100 qubits is 2.8
N=25: 7.0
main: 0.007 +- 0.006
this: 0.001 +- 0.000

N=100: 2.8
main: 0.042 +- 0.000
this: 0.015 +- 0.000

N=255: 2.8
main: 0.198 +- 0.040
this: 0.071 +- 0.003

N=400: 2.3
main: 0.607 +- 0.001
this: 0.260 +- 0.000
10 timestep for 4th order Trotter: speedup @ 100 qubits is 9.4
N=25: 6.7
main: 0.040 +- 0.003
this: 0.006 +- 0.000

N=100: 9.4
main: 0.330 +- 0.037
this: 0.035 +- 0.001

N=255: 10.8
main: 1.291 +- 0.066
this: 0.119 +- 0.007

N=400: 11.5
main: 4.052 +- 0.051
this: 0.353 +- 0.009
1 timestep for first order Trotter but with wrap=True: speedup @ 100 qubits is 3.6
N=25: 3.4
main: 0.034 +- 0.012
this: 0.010 +- 0.004

N=100: 3.6
main: 0.187 +- 0.017
this: 0.052 +- 0.010

N=255: 3.1
main: 0.551 +- 0.048
this: 0.178 +- 0.019

N=400: 3.0
main: 1.367 +- 0.068
this: 0.458 +- 0.027

Side effects of this PR:

  • add SparsePauliOp.to_sparse_list to construct the sparse list format (i.e. op == SparsePauliOp.from_sparse_list(op.to_sparse_list()))
  • refactor LieTrotter to just be an alias of SuzukiTrotter and reduce code duplication
  • fix the docstrings to use actually non-commuting Paulis (XX and ZZ commute and would have no expansion error)

Cryoris avatar Oct 08 '24 12:10 Cryoris

Pull Request Test Coverage Report for Build 11667384085

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

  • 313 of 332 (94.28%) changed or added relevant lines in 12 files are covered.
  • 587 unchanged lines in 28 files lost coverage.
  • Overall coverage increased (+0.03%) to 88.756%

Changes Missing Coverage Covered Lines Changed/Added Lines %
crates/circuit/src/operations.rs 1 2 50.0%
qiskit/synthesis/evolution/suzuki_trotter.py 18 20 90.0%
qiskit/synthesis/evolution/product_formula.py 41 45 91.11%
qiskit/synthesis/evolution/qdrift.py 14 18 77.78%
crates/accelerate/src/circuit_library/pauli_evolution.rs 204 212 96.23%
<!-- Total: 313 332
Files with Coverage Reduction New Missed Lines %
qiskit/transpiler/target.py 1 93.84%
qiskit/circuit/library/boolean_logic/quantum_or.py 1 98.08%
crates/accelerate/src/basis/basis_translator/basis_search.rs 1 99.31%
qiskit/circuit/library/boolean_logic/quantum_and.py 1 97.96%
qiskit/circuit/library/boolean_logic/inner_product.py 1 96.0%
qiskit/circuit/store.py 1 97.06%
qiskit/qasm2/export.py 1 98.48%
crates/accelerate/src/split_2q_unitaries.rs 2 96.43%
qiskit/transpiler/passmanager_config.py 3 96.1%
crates/accelerate/src/basis/basis_translator/compose_transforms.rs 3 97.48%
<!-- Total: 587
Totals Coverage Status
Change from base Build 11601286937: 0.03%
Covered Lines: 76579
Relevant Lines: 86280

💛 - Coveralls

coveralls avatar Oct 08 '24 12:10 coveralls

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

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

qiskit-bot avatar Oct 15 '24 11:10 qiskit-bot

Hmm, after switching SX and SXdg in 2b6f4eb, some of the tests in pauli_feature_map now fail. I believe the tests were incorrect before 2b6f4eb, so it's good that they would be fixed, yet it's scary how difficult it is to distinguish between the correct and incorrect behaviors (I was actually looking at these before and they looked perfectly fine to me). Can we add more Operator-based asserts? I.e. in addition to checking that the ref circuit looks as it's supposed to, also that Operator(encoding) == Operator(ref)?

Other than that, I am very happy with this PR, thanks for the great work!

alexanderivrii avatar Oct 31 '24 15:10 alexanderivrii

For the record, I am no longer sure what was our conclusion with @raynelfss (Ray is so ahead of me when discussing Rust types). I know that @Cryoris used boxing to be able to return both the usual and the double-ended iterators (and maybe something else too), yet if I remember correctly the double-ended iterators are a bit heavier than the usual ones. Does it make sense for us to return the double-ended iterators everywhere? Another possibility is maybe we should return the CircuitData objects (instead of Vec<Instruction> or iterator over that).

alexanderivrii avatar Oct 31 '24 16:10 alexanderivrii

In hindsight @Cryoris, we might not be able to use a simple impl Trait I believe I can now see why you used so many Box pointers.

This is because this function returns different types of Iterator depending on the input (e.g. Chain<Map<...>> vs Empty). The dynamic type was the only way I got to work, but I'm happy to change it if there's a better way 🙂

Even though both Once iterators and an example iterator such as Chain<Map<Iterator>> should technically both count as Iterator the types are different enough to require a dynamic block, and so you'd need to use the Box there. I can't think of another way to do this right now, so you can keep the Box here.

raynelfss avatar Oct 31 '24 18:10 raynelfss