-
Notifications
You must be signed in to change notification settings - Fork 2.5k
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
Reorder Pauli terms before Trotterization #12925
Merged
Merged
Changes from all commits
Commits
Show all changes
24 commits
Select commit
Hold shift + click to select a range
047fa2e
Added option to reorder Paulis before Trotterization
altaris 5c61160
Added unit tests for Pauli reordering
altaris 758caff
reorder_paulis acts as the identity if the operator has only one term
altaris 4c53dbc
Reorder Pauli terms by default
altaris f0a6b3f
reorder_paulis is deterministic and order-invariant
altaris d98c019
Fixed SuzukiTrotter unit tests
altaris 098dd29
No Pauli reordering by default
altaris 6fc1b49
Added Pauli reordering option in QDrift
altaris 1748ae9
Typset math correctly
altaris 84f937e
Reorder ops individually
altaris 50e7d50
Option to change graph coloring heuristic
altaris 750b56a
Merge branch 'main' into reorder-paulis
Cryoris 9ccb34b
add plugin
Cryoris 22fc36b
consistent with rustiq
Cryoris 71cff0f
rm print, fix sorting
Cryoris a588ec3
lint & docs
Cryoris d7c5f67
Fix QDrift reordering
Cryoris 56e619c
review comments
Cryoris 7df41d1
Merge branch 'reorder-paulis' of github.com:altaris/qiskit into reord…
Cryoris 7089729
Merge branch 'main' into reorder-paulis
Cryoris c192d87
rm dead code
Cryoris 80c4022
fix default value
Cryoris 365f238
missed one
Cryoris 5d57a61
Merge branch 'main' into reorder-paulis
mtreinish File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -15,9 +15,13 @@ | |
from __future__ import annotations | ||
|
||
import inspect | ||
from collections.abc import Callable | ||
import itertools | ||
from collections.abc import Callable, Sequence | ||
from collections import defaultdict | ||
from itertools import combinations | ||
import typing | ||
import numpy as np | ||
import rustworkx as rx | ||
from qiskit.circuit.parameterexpression import ParameterExpression | ||
from qiskit.circuit.quantumcircuit import QuantumCircuit, ParameterValueType | ||
from qiskit.quantum_info import SparsePauliOp, Pauli | ||
|
@@ -29,6 +33,8 @@ | |
if typing.TYPE_CHECKING: | ||
from qiskit.circuit.library import PauliEvolutionGate | ||
|
||
SparsePauliLabel = typing.Tuple[str, list[int], ParameterValueType] | ||
|
||
|
||
class ProductFormula(EvolutionSynthesis): | ||
"""Product formula base class for the decomposition of non-commuting operator exponentials. | ||
|
@@ -63,6 +69,7 @@ def __init__( | |
| None | ||
) = None, | ||
wrap: bool = False, | ||
preserve_order: bool = True, | ||
) -> None: | ||
""" | ||
Args: | ||
|
@@ -84,11 +91,15 @@ def __init__( | |
wrap: Whether to wrap the atomic evolutions into custom gate objects. Note that setting | ||
this to ``True`` is slower than ``False``. This only takes effect when | ||
``atomic_evolution is None``. | ||
preserve_order: If ``False``, allows reordering the terms of the operator to | ||
potentially yield a shallower evolution circuit. Not relevant | ||
when synthesizing operator with a single term. | ||
""" | ||
super().__init__() | ||
self.order = order | ||
self.reps = reps | ||
self.insert_barriers = insert_barriers | ||
self.preserve_order = preserve_order | ||
|
||
# user-provided atomic evolution, stored for serialization | ||
self._atomic_evolution = atomic_evolution | ||
|
@@ -177,6 +188,7 @@ def settings(self) -> dict[str, typing.Any]: | |
"insert_barriers": self.insert_barriers, | ||
"cx_structure": self._cx_structure, | ||
"wrap": self._wrap, | ||
"preserve_order": self.preserve_order, | ||
} | ||
|
||
def _normalize_coefficients( | ||
|
@@ -239,3 +251,61 @@ def real_or_fail(value, tol=100): | |
return np.real(value) | ||
|
||
raise ValueError(f"Encountered complex value {value}, but expected real.") | ||
|
||
|
||
def reorder_paulis( | ||
paulis: Sequence[SparsePauliLabel], | ||
strategy: rx.ColoringStrategy = rx.ColoringStrategy.Saturation, | ||
) -> list[SparsePauliLabel]: | ||
r""" | ||
Creates an equivalent operator by reordering terms in order to yield a | ||
shallower circuit after evolution synthesis. The original operator remains | ||
unchanged. | ||
|
||
This method works in three steps. First, a graph is constructed, where the | ||
nodes are the terms of the operator and where two nodes are connected if | ||
their terms act on the same qubit (for example, the terms :math:`IXX` and | ||
:math:`IYI` would be connected, but not :math:`IXX` and :math:`YII`). Then, | ||
the graph is colored. Two terms with the same color thus do not act on the | ||
same qubit, and in particular, their evolution subcircuits can be run in | ||
parallel in the greater evolution circuit of ``paulis``. | ||
|
||
This method is deterministic and invariant under permutation of the Pauli | ||
term in ``paulis``. | ||
|
||
Args: | ||
paulis: The operator whose terms to reorder. | ||
strategy: The coloring heuristic to use, see ``ColoringStrategy`` [#]. | ||
Default is ``ColoringStrategy.Saturation``. | ||
|
||
.. [#] https://www.rustworkx.org/apiref/rustworkx.ColoringStrategy.html#coloringstrategy | ||
|
||
""" | ||
|
||
def _term_sort_key(term: SparsePauliLabel) -> typing.Any: | ||
# sort by index, then by pauli | ||
return (term[1], term[0]) | ||
|
||
# Do nothing in trivial cases | ||
if len(paulis) <= 1: | ||
return paulis | ||
|
||
terms = sorted(paulis, key=_term_sort_key) | ||
graph = rx.PyGraph() | ||
graph.add_nodes_from(terms) | ||
indexed_nodes = list(enumerate(graph.nodes())) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think you don't actually need to cast this is as a list, it should all work fine without it and save a copy. |
||
for (idx1, (_, ind1, _)), (idx2, (_, ind2, _)) in combinations(indexed_nodes, 2): | ||
# Add an edge between two terms if they touch the same qubit | ||
if len(set(ind1).intersection(ind2)) > 0: | ||
graph.add_edge(idx1, idx2, None) | ||
|
||
# rx.graph_greedy_color is supposed to be deterministic | ||
coloring = rx.graph_greedy_color(graph, strategy=strategy) | ||
terms_by_color = defaultdict(list) | ||
|
||
for term_idx, color in sorted(coloring.items()): | ||
term = graph.nodes()[term_idx] | ||
terms_by_color[color].append(term) | ||
|
||
terms = list(itertools.chain(*terms_by_color.values())) | ||
return terms |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
40 changes: 40 additions & 0 deletions
40
releasenotes/notes/reorder-trotter-terms-c8a6eb3cdb831f77.yaml
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
--- | ||
features_synthesis: | ||
- | | ||
Added a new argument ``preserve_order`` to :class:`.ProductFormula`, which allows | ||
re-ordering the Pauli terms in the Hamiltonian before the product formula expansion, | ||
to compress the final circuit depth. By setting this to ``False``, a term of form | ||
|
||
.. math:: | ||
|
||
Z_0 Z_1 + X_1 X_2 + Y_2 Y_3 | ||
|
||
will be re-ordered to | ||
|
||
.. math:: | ||
|
||
Z_0 Z_1 + Y_2 Y_3 + X_1 X_2 | ||
|
||
which will lead to the ``RZZ`` and ``RYY`` rotations being applied in parallel, instead | ||
of three sequential rotations in the first part. | ||
|
||
This option can be set via the plugin interface:: | ||
|
||
from qiskit import QuantumCircuit, transpile | ||
from qiskit.circuit.library import PauliEvolutionGate | ||
from qiskit.quantum_info import SparsePauliOp | ||
from qiskit.synthesis.evolution import SuzukiTrotter | ||
from qiskit.transpiler.passes import HLSConfig | ||
|
||
op = SparsePauliOp(["XXII", "IYYI", "IIZZ"]) | ||
time, reps = 0.1, 1 | ||
|
||
synthesis = SuzukiTrotter(order=2, reps=reps) | ||
hls_config = HLSConfig(PauliEvolution=[("default", {"preserve_order": False})]) | ||
|
||
circuit = QuantumCircuit(op.num_qubits) | ||
circuit.append(PauliEvolutionGate(op, time), circuit.qubits) | ||
|
||
tqc = transpile(circuit, basis_gates=["u", "cx"], hls_config=hls_config) | ||
print(tqc.draw()) | ||
|
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think from a docs perspective it would be good to explain the tradeoffs here, especially around runtime performance.