"""
Module containing utils for converting Qiskit operators to Pennylane format.
Notes:
It representes a major extension of pennylane/qchem/convert.py module.
https://github.com/PennyLaneAI/pennylane/blob/master/pennylane/qchem/convert.py
The implemented functions may only be valid to fermionic-to-qubit
transformed hamiltonians as it accounts for the distinct alpha and beta
qubit (wire) distribution between VQE anzatses in Qiskit-Nature and
Pennylane.
"""
try:
import warnings
import pennylane as qml
from pennylane import numpy as np
from pennylane.operation import active_new_opmath, Tensor
from pennylane.ops import Hamiltonian, Prod, SProd, Sum
from pennylane.qchem.convert import _process_wires
from pennylane.qchem.convert import _openfermion_to_pennylane
from pennylane.wires import Wires
except ImportError as Error:
raise ImportError(
"This feature requires PennyLane. "
"It can be installed with: pip install pennylane."
) from Error
[docs]
def _qiskit_nature_to_pennylane(qubit_operator, wires=None):
"""Convert Qiskit SparsePauliOp to 2-tuple of coeffs and PennyLane Paulis.
This functions is usefull to convert fermionic-to-qubit transformed
qchem operators from Qiskit-Nature to Pennynale format.
Args:
qubit_operator (qiskit.quantum_info.SparsePauliOp): Qiskit operator
representing the qubit electronic Hamiltonian from Qiskit-Nature.
wires (Wires, list, tuple, dict): Custom wire mapping used to convert
the qubit operator to an observable terms measurable in PennyLane.
For types Wires/list/tuple, each item in the iterable represents a
wire label corresponding to the qubit number equal to its index.
For type dict, only int-keyed dict (for qubit-to-wire conversion)
is accepted. If None, will use identity map (0->0, 1->1, ...).
Returns:
tuple[array[float], Iterable[pennylane.operation.Operator]]: coeffs
and their corresponding PennyLane observables in the Pauli basis.
**Example**
>>> from qiskit.quantum_info import SparsePauliOp
>>> qubit_op = SparsePauliOp.from_list([("XIIZI", 1), ("IYIIY", 2)])
>>> qubit_op
SparsePauliOp(['XIIZI', 'IYIIY'],
coeffs=[1.+0.j, 2.+0.j])
>>> _qiskit_nature_to_pennylane(qubit_op,wires=['w0','w1','w2','w3','w4'])
(tensor([1., 2.], requires_grad=False),
[PauliX(wires=['w2']) @ PauliZ(wires=['w3']),
PauliY(wires=['w0']) @ PauliY(wires=['w4'])])
If the new op-math is active, the list of operators will be cast as
:class:`~.Prod` instances instead of :class:`~.Tensor` instances when
appropriate.
"""
n_wires = qubit_operator.num_qubits
wires = _process_wires(wires, n_wires=n_wires)
wire_map = {wires[x]: y for x, y in zip(range(n_wires), range(n_wires))}
if qubit_operator.coeffs.size == 0:
return np.array([0.0]), [qml.Identity(wires[0])]
def _get_op(term, wires):
"""A function to translate Qiskit to Pennylane Pauli terms."""
if len(term) == n_wires:
# the Pauli term '...XYZ' in Qiskit is equivalent to [Z0 Y1 X2 ..]
# in Pennylane. So, invert the string..
term = term[::-1]
# wires in Qiskit-Nature are grouped by separated alpha and beta
# blocks, e.g., for H2 the circuit is represented by:
# ┌───┐
# q_0: ┤ X ├
# └───┘
# q_1: ─────
# ┌───┐
# q_2: ┤ X ├
# └───┘
# q_3: ─────
#
# However, in Pennylane they are represented by alpha-beta
# sequences. So, organize the term accordingly...
n = len(term)//2 # => valid for closed shell systems only ?
term = ''.join([term[i::n] for i in range(n)])
# this could also be done by using the `_process_wires` function.
if active_new_opmath():
return qml.prod(
qml.pauli.string_to_pauli_word(term, wire_map=wire_map))
return Tensor(qml.pauli.string_to_pauli_word(
term, wire_map=wire_map
))
return qml.Identity(wires[0])
coeffs, ops = zip(
*[(coef, _get_op(term, wires))
for term, coef in qubit_operator.to_list()]
)
return np.array(coeffs).real, list(ops)
[docs]
def _pennylane_to_qiskit_nature(coeffs, ops, wires):
"""Convert Pennylane to Qiskit-Nature formats.
Convert a 2-tuple of complex coefficients and PennyLane operations to
Qiskit ``SparsePauliOp``.
Args:
coeffs (array[complex]):
coefficients for each observable, same length as ops
ops (Iterable[pennylane.operation.Operations]): list of PennyLane
operations that have a valid PauliSentence representation.
wires (Wires, list, tuple, dict): Full wire mapping used to convert
to qubit operator from an observable terms measurable in a
PennyLane ansatz. For types Wires/list/tuple, each item in the
iterable represents a wire label corresponding to the qubit number
equal to its index. For type dict, only consecutive-int-valued
dict (for wire-to-qubit conversion) is accepted.
Returns:
SparsePauliOp: an instance of Qiskit's ``SparsePauliOp``.
Notes:
Instead of having to provide the full list of wires,
e.g., wires=[0, 1, 2, 3, ..., n_wires],
this function could be alternativelly implemented using
``SparsePauliOp.from_sparse_list()`` in place of
``SparsePauliOp.from_list()``,
e.g.,
.. code-block:: python
op = SparsePauliOp.from_sparse_list(
[("ZX", [1, 4], 1), ("YY", [0, 3], 2)], num_qubits=5)
with the requirement that users provide the total number of qubits.
**Example**
>>> coeffs = np.array([0.1, 0.2, 0.3])
>>>> ops = [
... qml.operation.Tensor(qml.PauliX(wires=[0])),
... qml.operation.Tensor(qml.PauliY(wires=[0]), qml.PauliZ(wires=[2])),
... qml.prod(qml.PauliX(wires=[0]), qml.PauliZ(wires=[3]))
... ]
>>> _pennylane_to_qiskit_nature(coeffs, ops, wires=[0, 1, 2, 3])
SparsePauliOp(['IIIX', 'IZIY', 'ZIIX'],
coeffs=[0.1+0.j, 0.2+0.j, 0.3+0.j])
"""
try:
from qiskit.quantum_info import SparsePauliOp
except ImportError as Error:
raise ImportError(
"This feature requires qiskit. "
"It can be installed with: pip install qiskit"
) from Error
all_wires = Wires.all_wires([op.wires for op in ops], sort=True)
if wires is not None:
qubit_indexed_wires = _process_wires(
wires,
)
if not set(all_wires).issubset(set(qubit_indexed_wires)):
raise ValueError("Supplied `wires` does not cover all wires"
" defined in `ops`.")
else:
raise ValueError(
"Please, provide the full sequence of wires "
"so that a complete Pauli term is generated "
"for Qiskit.")
n_wires = len(qubit_indexed_wires)
wire_map = {qubit_indexed_wires[x]: y for x, y in zip(range(n_wires),
range(n_wires))}
q_op_list = []
for coeff, op in zip(coeffs, ops):
if isinstance(op, (Tensor, Prod, SProd, Hamiltonian)):
string = qml.pauli.pauli_word_to_string(op, wire_map=wire_map)
n = len(string)//2 # => valid for closed shell singlets only
string = ''.join([string[i::n] for i in range(n)])
string = string[::-1]
q_op_list.append((string, coeff))
if isinstance(op, Sum):
# print(coeff, op.simplify(), op._build_pauli_rep(), op.terms())
raise ValueError(
"Pauli operators representing :class:`.Sum` "
"not accepted in the current implementation.")
return SparsePauliOp.from_list(q_op_list)
[docs]
def _qiskit_nature_pennylane_equivalent(
qiskit_qubit_operator, pennylane_qubit_operator, wires=None
):
"""Check functionality of :func:`_pennylane_to_qiskit_nature`.
Check equivalence between Qiskit :class:`~.SparsePauliOp` and Pennylane
VQE ``Hamiltonian`` (Tensor product of Pauli matrices).
Equality is based on Qiskit :class:`~.SparsePauliOp`'s equality.
Args:
qiskit_qubit_operator (SparsePauliOp): Qiskit-Natuire qubit operator
represented as a Pauli summation
pennylane_qubit_operator (pennylane.Hamiltonian): PennyLane
Hamiltonian object
wires (Wires, list, tuple, dict): Full wire mapping used to convert
to qubit operator from an observable terms measurable in a
PennyLane ansatz. For types Wires/list/tuple, each item in the
iterable represents a wire label corresponding to the qubit number
equal to its index. For type dict, only consecutive-int-valued
dict (for wire-to-qubit conversion) is accepted.
Returns:
(bool): True if equivalent
"""
coeffs, ops = pennylane_qubit_operator.terms()
return qiskit_qubit_operator == _pennylane_to_qiskit_nature(
coeffs, ops, wires=wires)
[docs]
def import_operator(qubit_observable, format="openfermion",
wires=None, tol=1e010):
r"""Convert an external operator to a PennyLane operator.
The external format currently supported is openfermion and qiskit.
Args:
qubit_observable: external qubit operator that will be converted
format (str): the format of the operator object to convert from
wires (.Wires, list, tuple, dict): Custom wire mapping used to convert
the external qubit operator to a PennyLane operator.
For types ``Wires``/list/tuple, each item in the iterable
represents a wire label for the corresponding qubit index.
For type dict, only int-keyed dictionaries (for qubit-to-wire
conversion) are accepted. If ``None``, the identity map
(0->0, 1->1, ...) will be used.
tol (float): Tolerance in `machine epsilon
<https://numpy.org/doc/stable/reference/generated/numpy.real_if_close.html>`
for the imaginary part of the coefficients in ``qubit_observable``.
Coefficients with imaginary part less than 2.22e-16*tol are
considered to be real.
Returns:
(.Operator): PennyLane operator representing any operator expressed as
linear comb of Pauli words, e.g., :math:`\\sum_{k=0}^{N-1} c_k O_k`
**Example**
>>> from openfermion import QubitOperator
>>> h_of = QubitOperator('X0 X1 Y2 Y3', -0.0548)
+ QubitOperator('Z0 Z1', 0.14297)
>>> h_pl = import_operator(h_of, format='openfermion')
>>> print(h_pl)
(0.14297) [Z0 Z1]
+ (-0.0548) [X0 X1 Y2 Y3]
>>> from qiskit.quantum_info import SparsePauliOp
>>> h_qt = SparsePauliOp.from_list([("XXYY", -0.0548), ("ZZII", 0.14297)])
>>> h_pl = import_operator(h_qt, format='qiskit')
>>> print(h_pl)
(0.14297) [Z1 Z3]
+ (-0.0548) [Y0 X1 Y2 Y3]
If the new op-math is active, an arithmetic operator is returned instead.
>>> qml.operation.enable_new_opmath()
>>> h_pl = import_operator(h_of, format='openfermion')
>>> print(h_pl)
(-0.0548*(PauliX(wires=[0]) @ PauliX(wires=[1]) @ PauliY(wires=[2]) @
PauliY(wires=[3]))) + (0.14297*(PauliZ(wires=[0]) @ PauliZ(wires=[1])))
"""
if format not in ["openfermion", "qiskit"]:
raise TypeError(f"Converter does not exist for {format} format.")
if format == "openfermion":
# dealing with openfermion `QubitOperator`
coeffs = np.array([np.real_if_close(coef, tol=tol)
for coef in qubit_observable.terms.values()])
elif format == "qiskit":
# dealing with qiskit `SparsePauliOp`
coeffs = np.array([np.real_if_close(coef, tol=tol)
for coef in qubit_observable.coeffs])
if any(np.iscomplex(coeffs)):
warnings.warn(
f"The coefficients entering the QubitOperator"
f" or SparsePauliOp must be real;"
f" got complex coefficients in the operator"
f" {list(coeffs[np.iscomplex(coeffs)])}"
)
if format == "openfermion":
if active_new_opmath():
return qml.dot(*_openfermion_to_pennylane(
qubit_observable, wires=wires))
return qml.Hamiltonian(*_openfermion_to_pennylane(
qubit_observable, wires=wires))
if format == "qiskit":
if active_new_opmath():
return qml.dot(*_qiskit_nature_to_pennylane(
qubit_observable, wires=wires))
return qml.Hamiltonian(*_qiskit_nature_to_pennylane(
qubit_observable, wires=wires))