Skip to content

New atomic_evolution signature in ProductFormula subclasses #13918

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

Merged
merged 2 commits into from
Feb 28, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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: 1 addition & 6 deletions qiskit/synthesis/evolution/lie_trotter.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,9 +55,7 @@ def __init__(
insert_barriers: bool = False,
cx_structure: str = "chain",
atomic_evolution: (
Callable[[Pauli | SparsePauliOp, float], QuantumCircuit]
| Callable[[QuantumCircuit, Pauli | SparsePauliOp, float], None]
| None
Callable[[QuantumCircuit, Pauli | SparsePauliOp, float], None] | None
) = None,
wrap: bool = False,
preserve_order: bool = True,
Expand All @@ -75,9 +73,6 @@ def __init__(
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``.
preserve_order: If ``False``, allows reordering the terms of the operator to
Expand Down
23 changes: 1 addition & 22 deletions qiskit/synthesis/evolution/product_formula.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@
from qiskit.circuit.parameterexpression import ParameterExpression
from qiskit.circuit.quantumcircuit import QuantumCircuit, ParameterValueType
from qiskit.quantum_info import SparsePauliOp, Pauli
from qiskit.utils.deprecation import deprecate_arg
from qiskit._accelerate.circuit_library import pauli_evolution

from .evolution_synthesis import EvolutionSynthesis
Expand All @@ -42,31 +41,14 @@ class ProductFormula(EvolutionSynthesis):
:obj:`.LieTrotter` and :obj:`.SuzukiTrotter` inherit from this class.
"""

@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,
order: int,
reps: int = 1,
insert_barriers: bool = False,
cx_structure: str = "chain",
atomic_evolution: (
Callable[[Pauli | SparsePauliOp, float], QuantumCircuit]
| Callable[[QuantumCircuit, Pauli | SparsePauliOp, float], None]
| None
Callable[[QuantumCircuit, Pauli | SparsePauliOp, float], None] | None
) = None,
wrap: bool = False,
preserve_order: bool = True,
Expand All @@ -85,9 +67,6 @@ def __init__(
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. Note that setting
this to ``True`` is slower than ``False``. This only takes effect when
``atomic_evolution is None``.
Expand Down
24 changes: 1 addition & 23 deletions qiskit/synthesis/evolution/qdrift.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,13 @@

from __future__ import annotations

import inspect
import math
import typing
from itertools import chain
from collections.abc import Callable
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 qiskit.exceptions import QiskitError

from .product_formula import ProductFormula, reorder_paulis
Expand All @@ -41,30 +39,13 @@ class QDrift(ProductFormula):
`arXiv:quant-ph/1811.08017 <https://arxiv.org/abs/1811.08017>`_
"""

@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: (
Callable[[Pauli | SparsePauliOp, float], QuantumCircuit]
| Callable[[QuantumCircuit, Pauli | SparsePauliOp, float], None]
| None
Callable[[QuantumCircuit, Pauli | SparsePauliOp, float], None] | None
) = None,
seed: int | None = None,
wrap: bool = False,
Expand All @@ -83,9 +64,6 @@ def __init__(
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.
seed: An optional seed for reproducibility of the random sampling process.
wrap: Whether to wrap the atomic evolutions into custom gate objects. This only takes
effect when ``atomic_evolution is None``.
Expand Down
24 changes: 1 addition & 23 deletions qiskit/synthesis/evolution/suzuki_trotter.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@

from __future__ import annotations

import inspect
import typing
from collections.abc import Callable
from itertools import chain
Expand All @@ -23,7 +22,6 @@
from qiskit.circuit.parameterexpression import ParameterExpression
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, reorder_paulis

Expand Down Expand Up @@ -60,31 +58,14 @@ class SuzukiTrotter(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,
order: int = 2,
reps: int = 1,
insert_barriers: bool = False,
cx_structure: str = "chain",
atomic_evolution: (
Callable[[Pauli | SparsePauliOp, float], QuantumCircuit]
| Callable[[QuantumCircuit, Pauli | SparsePauliOp, float], None]
| None
Callable[[QuantumCircuit, Pauli | SparsePauliOp, float], None] | None
) = None,
wrap: bool = False,
preserve_order: bool = True,
Expand All @@ -102,9 +83,6 @@ def __init__(
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``.
preserve_order: If ``False``, allows reordering the terms of the operator to
Expand Down
10 changes: 10 additions & 0 deletions releasenotes/notes/fixes_13858-2dacfc0431c1a6ea.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
upgrade_synthesis:
- |
The ``atomic_evolution`` argument to :class:`.ProductFormula` (and its
Copy link
Member

Choose a reason for hiding this comment

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

If you want to be exhaustive, you should also include LieTrotter as a subclass here.

subclasses :class:`.QDrift`, :class:`.LieTrotter` and :class:`SuzukiTrotter` )
has a new function signature. Rather than taking some Pauli
operator and time coefficient and returning the evolution circuit, the new
function takes in an existing circuit and should append the evolution of the
provided Pauli and given time to this circuit. This new implementation
benefits from significantly better performance.
26 changes: 11 additions & 15 deletions test/python/circuit/library/test_evolution_gate.py
Original file line number Diff line number Diff line change
Expand Up @@ -442,7 +442,7 @@ def test_labels_and_name(self):
def test_atomic_evolution(self):
"""Test a custom atomic_evolution."""

def atomic_evolution(pauli, time):
def atomic_evolution(circuit, pauli, time):
if isinstance(pauli, SparsePauliOp):
if len(pauli.paulis) != 1:
raise ValueError("Unsupported input.")
Expand All @@ -458,24 +458,20 @@ def atomic_evolution(pauli, time):
target = i
break

definition = QuantumCircuit(pauli.num_qubits)
definition.compose(cliff, inplace=True)
definition.compose(chain, inplace=True)
definition.rz(2 * time, target)
definition.compose(chain.inverse(), inplace=True)
definition.compose(cliff.inverse(), inplace=True)

return definition
circuit.compose(cliff, inplace=True)
circuit.compose(chain, inplace=True)
circuit.rz(2 * time, target)
circuit.compose(chain.inverse(), inplace=True)
circuit.compose(cliff.inverse(), inplace=True)

op = (X ^ X ^ X) + (Y ^ Y ^ Y) + (Z ^ Z ^ Z)
time = 0.123
reps = 4
with self.assertWarns(PendingDeprecationWarning):
evo_gate = PauliEvolutionGate(
op,
time,
synthesis=LieTrotter(reps=reps, atomic_evolution=atomic_evolution),
)
evo_gate = PauliEvolutionGate(
op,
time,
synthesis=LieTrotter(reps=reps, atomic_evolution=atomic_evolution),
)
decomposed = evo_gate.definition.decompose()
self.assertEqual(decomposed.count_ops()["cx"], reps * 3 * 4)

Expand Down