Skip to content

Commit

Permalink
Improve the performance of the ProductFormula synthesizers (Qiskit#…
Browse files Browse the repository at this point in the history
…12724)

* [WIP] adds the output argument to the internal atomic evolution

* meta: modernize type hints

* refactor: change callable structure of atomic evolution

This changes the structure of the `atomic_evolution` callable in the
`ProductFormula` synthesis class. This is motivated by the significant
performance improvements that can be obtained by appending to the
existing circuit directly rather than building out individual evolution
circuits and iteratively composing them.

* refactor: deprecate the legacy atomic_evolution signature

* refactor: add the wrap argument to ProductFormula

This can be used to recover the previous behavior in which the single
individually evolved Pauli terms get wrapped into gate objects.

* fix: insert the missing barriers between LieTrotter repetitions

* refactor: align SuzukiTrotter and LieTrotter

* Propoagate deprecation notice

* fix: the labels of wrapped Pauli evolutions

* fix: respect the insert_barriers setting

* docs: add a release note

* Apply suggestions from code review

Co-authored-by: Julien Gacon <gaconju@gmail.com>

* fix: missing `wrap` forward in SuzukiTrotter

* docs: improve documentation of the `atomic_evolution` argument

Co-authored-by: Julien Gacon <gaconju@gmail.com>

* docs: also document the deprecated form of `atomic_evolution`

* docs: call out ProductFormula docs in release note

* refactor: change to PendingDeprecationWarning

* refactor: explicitly convert to Gate when wrapping

This is slightly faster than the `.compose`-based operation done
previously as it performs fewer checks. Thanks to @jakelishman for the
suggestion offline.

* Update qiskit/synthesis/evolution/lie_trotter.py

Co-authored-by: Julien Gacon <gaconju@gmail.com>

* docs: update after pending deprecation

---------

Co-authored-by: Julien Gacon <gaconju@gmail.com>
  • Loading branch information
mrossinek and Cryoris authored Jul 8, 2024
1 parent e5533fd commit 008dde3
Show file tree
Hide file tree
Showing 8 changed files with 315 additions and 125 deletions.
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]'."
),
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

0 comments on commit 008dde3

Please sign in to comment.