Skip to content
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

Improve the performance of the ProductFormula synthesizers #12724

Merged
merged 22 commits into from
Jul 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
dada3a6
[WIP] adds the output argument to the internal atomic evolution
mrossinek Jul 4, 2024
bc00ecb
meta: modernize type hints
mrossinek Jul 4, 2024
849a768
refactor: change callable structure of atomic evolution
mrossinek Jul 4, 2024
2137f1e
refactor: deprecate the legacy atomic_evolution signature
mrossinek Jul 4, 2024
db601b7
refactor: add the wrap argument to ProductFormula
mrossinek Jul 4, 2024
94b8eea
fix: insert the missing barriers between LieTrotter repetitions
mrossinek Jul 4, 2024
5d04a78
refactor: align SuzukiTrotter and LieTrotter
mrossinek Jul 4, 2024
e8c1f9e
Propoagate deprecation notice
mrossinek Jul 4, 2024
80b4fb7
fix: the labels of wrapped Pauli evolutions
mrossinek Jul 4, 2024
f8a7c95
fix: respect the insert_barriers setting
mrossinek Jul 4, 2024
e6e425e
docs: add a release note
mrossinek Jul 4, 2024
84f5a5f
Apply suggestions from code review
mrossinek Jul 5, 2024
bb825c7
fix: missing `wrap` forward in SuzukiTrotter
mrossinek Jul 5, 2024
375170f
docs: improve documentation of the `atomic_evolution` argument
mrossinek Jul 5, 2024
07de8c1
docs: also document the deprecated form of `atomic_evolution`
mrossinek Jul 5, 2024
01cad96
docs: call out ProductFormula docs in release note
mrossinek Jul 5, 2024
4de223d
Merge branch 'main' into local_atomic_evolution
mrossinek Jul 5, 2024
ffa0a2b
Merge branch 'main' into local_atomic_evolution
mrossinek Jul 8, 2024
6f1cd7e
refactor: change to PendingDeprecationWarning
mrossinek Jul 8, 2024
5485bec
refactor: explicitly convert to Gate when wrapping
mrossinek Jul 8, 2024
c128667
Update qiskit/synthesis/evolution/lie_trotter.py
mrossinek Jul 8, 2024
e261535
docs: update after pending deprecation
mrossinek Jul 8, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions qiskit/circuit/quantumcircuit.py
Original file line number Diff line number Diff line change
Expand Up @@ -1596,11 +1596,12 @@ def inverse(self, annotated: bool = False) -> "QuantumCircuit":
)
return inverse_circ

def repeat(self, reps: int) -> "QuantumCircuit":
def repeat(self, reps: int, *, insert_barriers: bool = False) -> "QuantumCircuit":
"""Repeat this circuit ``reps`` times.

Args:
reps (int): How often this circuit should be repeated.
insert_barriers (bool): Whether to include barriers between circuit repetitions.

Returns:
QuantumCircuit: A circuit containing ``reps`` repetitions of this circuit.
Expand All @@ -1616,8 +1617,10 @@ def repeat(self, reps: int) -> "QuantumCircuit":
inst: Instruction = self.to_gate()
except QiskitError:
inst = self.to_instruction()
for _ in range(reps):
for i in range(reps):
repeated_circ._append(inst, self.qubits, self.clbits)
if insert_barriers and i != reps - 1:
repeated_circ.barrier()

return repeated_circ

Expand Down
6 changes: 4 additions & 2 deletions qiskit/synthesis/evolution/evolution_synthesis.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,10 @@

"""Evolution synthesis."""

from __future__ import annotations

from abc import ABC, abstractmethod
from typing import Any, Dict
from typing import Any


class EvolutionSynthesis(ABC):
Expand All @@ -32,7 +34,7 @@ def synthesize(self, evolution):
raise NotImplementedError

@property
def settings(self) -> Dict[str, Any]:
def settings(self) -> dict[str, Any]:
"""Return the settings in a dictionary, which can be used to reconstruct the object.

Returns:
Expand Down
65 changes: 46 additions & 19 deletions qiskit/synthesis/evolution/lie_trotter.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,15 @@

"""The Lie-Trotter product formula."""

from typing import Callable, Optional, Union, Dict, Any
from __future__ import annotations

import inspect
from collections.abc import Callable
from typing import Any
import numpy as np
from qiskit.circuit.quantumcircuit import QuantumCircuit
from qiskit.quantum_info.operators import SparsePauliOp, Pauli
from qiskit.utils.deprecation import deprecate_arg

from .product_formula import ProductFormula

Expand Down Expand Up @@ -47,55 +52,76 @@ class LieTrotter(ProductFormula):
`arXiv:math-ph/0506007 <https://arxiv.org/pdf/math-ph/0506007.pdf>`_
"""

@deprecate_arg(
name="atomic_evolution",
since="1.2",
predicate=lambda callable: callable is not None
and len(inspect.signature(callable).parameters) == 2,
deprecation_description=(
"The 'Callable[[Pauli | SparsePauliOp, float], QuantumCircuit]' signature of the "
"'atomic_evolution' argument"
),
additional_msg=(
"Instead you should update your 'atomic_evolution' function to be of the following "
"type: 'Callable[[QuantumCircuit, Pauli | SparsePauliOp, float], None]'."
mrossinek marked this conversation as resolved.
Show resolved Hide resolved
),
pending=True,
)
def __init__(
self,
reps: int = 1,
insert_barriers: bool = False,
cx_structure: str = "chain",
atomic_evolution: Optional[
Callable[[Union[Pauli, SparsePauliOp], float], QuantumCircuit]
] = None,
atomic_evolution: (
Callable[[Pauli | SparsePauliOp, float], QuantumCircuit]
| Callable[[QuantumCircuit, Pauli | SparsePauliOp, float], None]
| None
) = None,
wrap: bool = False,
) -> None:
"""
Args:
reps: The number of time steps.
insert_barriers: Whether to insert barriers between the atomic evolutions.
cx_structure: How to arrange the CX gates for the Pauli evolutions, can be
``"chain"``, where next neighbor connections are used, or ``"fountain"``,
where all qubits are connected to one.
atomic_evolution: A function to construct the circuit for the evolution of single
Pauli string. Per default, a single Pauli evolution is decomposed in a CX chain
and a single qubit Z rotation.
where all qubits are connected to one. This only takes effect when
``atomic_evolution is None``.
atomic_evolution: A function to apply the evolution of a single :class:`.Pauli`, or
:class:`.SparsePauliOp` of only commuting terms, to a circuit. The function takes in
three arguments: the circuit to append the evolution to, the Pauli operator to
evolve, and the evolution time. By default, a single Pauli evolution is decomposed
into a chain of ``CX`` gates and a single ``RZ`` gate.
Alternatively, the function can also take Pauli operator and evolution time as
inputs and returns the circuit that will be appended to the overall circuit being
built.
wrap: Whether to wrap the atomic evolutions into custom gate objects. This only takes
effect when ``atomic_evolution is None``.
"""
super().__init__(1, reps, insert_barriers, cx_structure, atomic_evolution)
super().__init__(1, reps, insert_barriers, cx_structure, atomic_evolution, wrap)

def synthesize(self, evolution):
# get operators and time to evolve
operators = evolution.operator
time = evolution.time

# construct the evolution circuit
evolution_circuit = QuantumCircuit(operators[0].num_qubits)
single_rep = QuantumCircuit(operators[0].num_qubits)

if not isinstance(operators, list):
pauli_list = [(Pauli(op), np.real(coeff)) for op, coeff in operators.to_list()]
else:
pauli_list = [(op, 1) for op in operators]

# if we only evolve a single Pauli we don't need to additionally wrap it
wrap = not (len(pauli_list) == 1 and self.reps == 1)

for i, (op, coeff) in enumerate(pauli_list):
evolution_circuit.compose(
self.atomic_evolution(op, coeff * time / self.reps), wrap=wrap, inplace=True
)
self.atomic_evolution(single_rep, op, coeff * time / self.reps)
if self.insert_barriers and i != len(pauli_list) - 1:
evolution_circuit.barrier()
single_rep.barrier()

return evolution_circuit.repeat(self.reps).decompose()
return single_rep.repeat(self.reps, insert_barriers=self.insert_barriers).decompose()

@property
def settings(self) -> Dict[str, Any]:
def settings(self) -> dict[str, Any]:
"""Return the settings in a dictionary, which can be used to reconstruct the object.

Returns:
Expand All @@ -113,4 +139,5 @@ def settings(self) -> Dict[str, Any]:
"reps": self.reps,
"insert_barriers": self.insert_barriers,
"cx_structure": self._cx_structure,
"wrap": self._wrap,
}
Loading
Loading