Source code for qiskit.circuit.library.pauli_evolution

# This code is part of Qiskit.
# (C) Copyright IBM 2021.
# This code is licensed under the Apache License, Version 2.0. You may
# obtain a copy of this license in the LICENSE.txt file in the root directory
# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
# Any modifications or derivative works of this code must retain this
# copyright notice, and modified files need to carry a notice indicating
# that they have been altered from the originals.

"""A gate to implement time-evolution of operators."""

from typing import Union, Optional
import numpy as np

from qiskit.circuit.gate import Gate
from qiskit.circuit.parameterexpression import ParameterExpression
from qiskit.synthesis import EvolutionSynthesis, LieTrotter
from qiskit.quantum_info import Pauli, SparsePauliOp

[docs]class PauliEvolutionGate(Gate): r"""Time-evolution of an operator consisting of Paulis. For an operator :math:`H` consisting of Pauli terms and (real) evolution time :math:`t` this gate implements .. math:: U(t) = e^{-itH}. This gate serves as a high-level definition of the evolution and can be synthesized into a circuit using different algorithms. The evolution gates are related to the Pauli rotation gates by a factor of 2. For example the time evolution of the Pauli :math:`X` operator is connected to the Pauli :math:`X` rotation :math:`R_X` by .. math:: U(t) = e^{-itX} = R_X(2t). **Examples:** .. code-block:: python from qiskit.circuit import QuantumCircuit from qiskit.circuit.library import PauliEvolutionGate from qiskit.opflow import I, Z, X # build the evolution gate operator = (Z ^ Z) - 0.1 * (X ^ I) evo = PauliEvolutionGate(operator, time=0.2) # plug it into a circuit circuit = QuantumCircuit(2) circuit.append(evo, range(2)) print(circuit.draw()) The above will print (note that the ``-0.1`` coefficient is not printed!):: ┌──────────────────────────┐ q_0: ┤0 ├ │ exp(-it (ZZ + XI))(0.2) │ q_1: ┤1 ├ └──────────────────────────┘ **References:** [1] G. Li et al. Paulihedral: A Generalized Block-Wise Compiler Optimization Framework For Quantum Simulation Kernels (2021). [`arXiv:2109.03371 <https://arxiv.org/abs/2109.03371>`_] """ def __init__( self, operator, time: Union[int, float, ParameterExpression] = 1.0, label: Optional[str] = None, synthesis: Optional[EvolutionSynthesis] = None, ) -> None: """ Args: operator (Pauli | PauliOp | SparsePauliOp | PauliSumOp | list): The operator to evolve. Can also be provided as list of non-commuting operators where the elements are sums of commuting operators. For example: ``[XY + YX, ZZ + ZI + IZ, YY]``. time: The evolution time. label: A label for the gate to display in visualizations. Per default, the label is set to ``exp(-it <operators>)`` where ``<operators>`` is the sum of the Paulis. Note that the label does not include any coefficients of the Paulis. See the class docstring for an example. synthesis: A synthesis strategy. If None, the default synthesis is the Lie-Trotter product formula with a single repetition. """ if isinstance(operator, list): operator = [_to_sparse_pauli_op(op) for op in operator] else: operator = _to_sparse_pauli_op(operator) if synthesis is None: synthesis = LieTrotter() if label is None: label = _get_default_label(operator) num_qubits = operator[0].num_qubits if isinstance(operator, list) else operator.num_qubits super().__init__(name="PauliEvolution", num_qubits=num_qubits, params=[time], label=label) self.operator = operator self.synthesis = synthesis @property def time(self) -> Union[float, ParameterExpression]: """Return the evolution time as stored in the gate parameters. Returns: The evolution time. """ return self.params[0] @time.setter def time(self, time: Union[float, ParameterExpression]) -> None: """Set the evolution time. Args: time: The evolution time. """ self.params = [time] def _define(self): """Unroll, where the default synthesis is matrix based.""" self.definition = self.synthesis.synthesize(self)
[docs] def validate_parameter( self, parameter: Union[int, float, ParameterExpression] ) -> Union[float, ParameterExpression]: """Gate parameters should be int, float, or ParameterExpression""" if isinstance(parameter, int): parameter = float(parameter) return super().validate_parameter(parameter)
def _to_sparse_pauli_op(operator): """Cast the operator to a SparsePauliOp. For Opflow objects, return a global coefficient that must be multiplied to the evolution time. Since this coefficient might contain unbound parameters it cannot be absorbed into the coefficients of the SparsePauliOp. """ # pylint: disable=cyclic-import from qiskit.opflow import PauliSumOp, PauliOp if isinstance(operator, PauliSumOp): sparse_pauli = operator.primitive sparse_pauli._coeffs *= operator.coeff elif isinstance(operator, PauliOp): sparse_pauli = SparsePauliOp(operator.primitive) sparse_pauli._coeffs *= operator.coeff elif isinstance(operator, Pauli): sparse_pauli = SparsePauliOp(operator) elif isinstance(operator, SparsePauliOp): sparse_pauli = operator else: raise ValueError(f"Unsupported operator type for evolution: {type(operator)}.") if any(np.iscomplex(sparse_pauli.coeffs)): raise ValueError("Operator contains complex coefficients, which are not supported.") return sparse_pauli def _get_default_label(operator): if isinstance(operator, list): label = f"exp(-it ({[' + '.join(op.paulis.to_labels()) for op in operator]}))" else: if len(operator.paulis) == 1: label = f"exp(-it {operator.paulis.to_labels()[0]})" # for just a single Pauli don't add brackets around the sum else: label = f"exp(-it ({' + '.join(operator.paulis.to_labels())}))" return label