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

Support qubit operator in from_openfermion #5881

Merged
merged 17 commits into from
Jun 21, 2024
109 changes: 40 additions & 69 deletions pennylane/qchem/convert_openfermion.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,96 +45,66 @@ def _import_of():
return openfermion


def _from_openfermion_qubit(of_op, tol=1.0e-16, **kwargs):
r"""Convert OpenFermion ``QubitOperator`` to a :class:`~.LinearCombination` object in PennyLane representing a linear combination of qubit operators.

Args:
of_op (QubitOperator): fermionic-to-qubit transformed operator in terms of
Pauli matrices
wires (Wires, list, tuple, dict): Custom wire mapping used to convert the qubit operator
to 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 int-keyed dict (for qubit-to-wire conversion) is accepted.
If None, will use identity map (e.g. 0->0, 1->1, ...).
tol (float): tolerance value to decide whether the imaginary part of the coefficients is retained
return_sum (bool): flag indicating whether a ``Sum`` object is returned

Returns:
(pennylane.ops.Sum, pennylane.ops.LinearCombination): a linear combination of Pauli words

**Example**

>>> q_op = QubitOperator('X0', 1.2) + QubitOperator('Z1', 2.4)
>>> from_openfermion_qubit(q_op)
1.2 * X(0) + 2.4 * Z(1)

>>> from openfermion import FermionOperator
>>> of_op = 0.5 * FermionOperator('0^ 2') + FermionOperator('0 2^')
>>> pl_op = from_openfermion_qubit(of_op)
>>> print(pl_op)
0.5 * a⁺(0) a(2)
+ 1.0 * a(0) a⁺(2)
"""
coeffs, pl_ops = _openfermion_to_pennylane(of_op, tol=tol)
pl_term = qml.ops.LinearCombination(coeffs, pl_ops)

if "format" in kwargs:
if kwargs["format"] == "Sum":
return qml.dot(*pl_term.terms())
if kwargs["format"] != "LinearCombination":
f = kwargs["format"]
raise ValueError(f"format must be a Sum or LinearCombination, got: {f}.")

return pl_term


def from_openfermion(openfermion_op, tol=1e-16):
soranjh marked this conversation as resolved.
Show resolved Hide resolved
r"""Convert OpenFermion
`FermionOperator <https://quantumai.google/reference/python/openfermion/ops/FermionOperator>`__
object to PennyLane :class:`~.fermi.FermiWord` or :class:`~.fermi.FermiSentence` objects.
soranjh marked this conversation as resolved.
Show resolved Hide resolved
and `QubitOperator <https://quantumai.google/reference/python/openfermion/ops/QubitOperator>`__
objects to PennyLane :class:`~.fermi.FermiWord` or :class:`~.fermi.FermiSentence` or
:class:`~.LinearCombination` objects.

Args:
openfermion_op (FermionOperator): OpenFermion fermionic operator
openfermion_op (FermionOperator, QubitOperator): OpenFermion operator
wires (.Wires, list, tuple, dict): Custom wire mapping used to convert the external qubit
soranjh marked this conversation as resolved.
Show resolved Hide resolved
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.
soranjh marked this conversation as resolved.
Show resolved Hide resolved
If ``None``, the identity map (e.g., ``0->0, 1->1, ...``) will be used.
tol (float): tolerance for discarding negligible coefficients

Returns:
Union[FermiWord, FermiSentence]: the fermionic operator object
Union[FermiWord, FermiSentence, LinearCombination]: PennyLane operator

**Example**

>>> from openfermion import FermionOperator
>>> openfermion_op = 0.5 * FermionOperator('0^ 2') + FermionOperator('0 2^')
>>> pl_op = from_openfermion(openfermion_op)
>>> from openfermion import FermionOperator, QubitOperator
>>> of_op = 0.5 * FermionOperator('0^ 2') + FermionOperator('0 2^')
>>> pl_op = from_openfermion(of_op)
>>> print(pl_op)
0.5 * a⁺(0) a(2)
+ 1.0 * a(0) a⁺(2)

>>> of_op = QubitOperator('X0', 1.2) + QubitOperator('Z1', 2.4)
>>> pl_op = from_openfermion(of_op)
>>> print(pl_op)
1.2 * X(0) + 2.4 * Z(1)
"""
try:
import openfermion
except ImportError as Error:
raise ImportError(
"This feature requires openfermion. "
"It can be installed with: pip install openfermion"
) from Error
openfermion = _import_of()

typemap = {0: "-", 1: "+"}
if isinstance(openfermion_op, openfermion.FermionOperator):
typemap = {0: "-", 1: "+"}
soranjh marked this conversation as resolved.
Show resolved Hide resolved

fermi_words = []
fermi_coeffs = []
fermi_words = []
fermi_coeffs = []

for ops, val in openfermion_op.terms.items():
fw_dict = {(i, op[0]): typemap[op[1]] for i, op in enumerate(ops)}
fermi_words.append(FermiWord(fw_dict))
fermi_coeffs.append(val)
for ops, val in openfermion_op.terms.items():
fw_dict = {(i, op[0]): typemap[op[1]] for i, op in enumerate(ops)}
fermi_words.append(FermiWord(fw_dict))
fermi_coeffs.append(val)

if len(fermi_words) == 1 and fermi_coeffs[0] == 1.0:
return fermi_words[0]
if len(fermi_words) == 1 and fermi_coeffs[0] == 1.0:
return fermi_words[0]

pl_op = FermiSentence(dict(zip(fermi_words, fermi_coeffs)))
pl_op.simplify(tol=tol)
pl_op = FermiSentence(dict(zip(fermi_words, fermi_coeffs)))
pl_op.simplify(tol=tol)

return pl_op
return pl_op

coeffs, pl_ops = _openfermion_to_pennylane(openfermion_op, tol=tol)

pennylane_op = qml.ops.LinearCombination(coeffs, pl_ops)

return pennylane_op


def to_openfermion(
Expand Down Expand Up @@ -166,6 +136,7 @@ def to_openfermion(
1.2 [0^ 1] +
3.1 [1^ 2]
"""

return _to_openfermion_dispatch(pennylane_op, wires=wires, tol=tol)


Expand Down
25 changes: 5 additions & 20 deletions tests/qchem/openfermion_pyscf_tests/test_convert_openfermion.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@
import pennylane as qml
from pennylane import fermi
from pennylane import numpy as np
from pennylane.qchem.convert_openfermion import _from_openfermion_qubit

openfermion = pytest.importorskip("openfermion")

Expand Down Expand Up @@ -72,7 +71,7 @@ class TestFromOpenFermion:
@pytest.mark.parametrize("of_op, pl_op", OPS)
def test_convert_qubit(self, of_op, pl_op):
"""Test conversion from ``QubitOperator`` to PennyLane."""
converted_pl_op = _from_openfermion_qubit(of_op)
converted_pl_op = qml.from_openfermion(of_op)
assert converted_pl_op.compare(pl_op)

def test_tol_qubit(self):
Expand All @@ -81,31 +80,17 @@ def test_tol_qubit(self):
"Z1", complex(1.3, 1e-8)
)

pl_op = _from_openfermion_qubit(q_op, tol=1e-6)
pl_op = qml.from_openfermion(q_op, tol=1e-6)
assert not np.any(pl_op.coeffs.imag)

pl_op = _from_openfermion_qubit(q_op, tol=1e-10)
pl_op = qml.from_openfermion(q_op, tol=1e-10)
assert np.any(pl_op.coeffs.imag)

def test_sum_qubit(self):
"""Test that the from_openfermion_qubit method yields a :class:`~.Sum` object if requested."""
"""Test that from_openfermion yields a :class:`~.Sum` object."""
q_op = openfermion.QubitOperator("X0 X1", 0.25) + openfermion.QubitOperator("Z1 Z0", 0.75)

assert isinstance(_from_openfermion_qubit(q_op), qml.ops.LinearCombination)
assert isinstance(
_from_openfermion_qubit(q_op, format="LinearCombination"), qml.ops.LinearCombination
)
assert isinstance(_from_openfermion_qubit(q_op, format="Sum"), qml.ops.Sum)

def test_invalid_format_qubit(self):
ddhawan11 marked this conversation as resolved.
Show resolved Hide resolved
"""Test if error is raised if format is invalid."""
q_op = openfermion.QubitOperator("X0")

with pytest.raises(
ValueError,
match="format must be a Sum or LinearCombination, got: invalid_format",
):
_from_openfermion_qubit(q_op, format="invalid_format")
assert isinstance(qml.from_openfermion(q_op), qml.ops.LinearCombination)

# PennyLane operators were obtained from openfermion operators manually
@pytest.mark.parametrize(
Expand Down
Loading