From 0d72d53ee243dc4c2496bd275e2064c9a1174bea Mon Sep 17 00:00:00 2001 From: soranjh Date: Wed, 19 Jun 2024 20:00:31 -0400 Subject: [PATCH 01/15] use dispatch --- pennylane/qchem/convert_openfermion.py | 94 +++++++------------ .../test_convert_openfermion.py | 35 +++---- 2 files changed, 51 insertions(+), 78 deletions(-) diff --git a/pennylane/qchem/convert_openfermion.py b/pennylane/qchem/convert_openfermion.py index dad1d5dcec2..59804df16c3 100644 --- a/pennylane/qchem/convert_openfermion.py +++ b/pennylane/qchem/convert_openfermion.py @@ -45,78 +45,52 @@ 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): +def from_openfermion(openfermion_op, wires=None, tol=1e-16): r"""Convert OpenFermion `FermionOperator `__ - object to PennyLane :class:`~.fermi.FermiWord` or :class:`~.fermi.FermiSentence` objects. + and `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 + 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 (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 + return _from_openfermion_dispatch(openfermion_op, wires=None, tol=tol) + +@singledispatch +def _from_openfermion_dispatch(of_op, wires=None, tol=1.0e-16): + """Dispatches to appropriate function if of_op is a ``FermionOperator`` or ``QubitOperator``.""" + raise ValueError( + f"The input operator must be a FermionOperator or QubitOperator, got: {type(of_op)}." + ) + +openfermion = _import_of() +@_from_openfermion_dispatch.register +def _(openfermion_op: openfermion.FermionOperator, wires=None, tol=1.0e-16): typemap = {0: "-", 1: "+"} @@ -136,6 +110,10 @@ def from_openfermion(openfermion_op, tol=1e-16): return pl_op +@_from_openfermion_dispatch.register +def _(openfermion_op: openfermion.QubitOperator, wires=None, tol=1.0e-16): + + return qml.dot(*_openfermion_to_pennylane(openfermion_op, wires=wires)) def to_openfermion( pennylane_op: Union[Sum, LinearCombination, FermiWord, FermiSentence], wires=None, tol=1.0e-16 diff --git a/tests/qchem/openfermion_pyscf_tests/test_convert_openfermion.py b/tests/qchem/openfermion_pyscf_tests/test_convert_openfermion.py index 3d565bb74b8..2a35fb9978d 100644 --- a/tests/qchem/openfermion_pyscf_tests/test_convert_openfermion.py +++ b/tests/qchem/openfermion_pyscf_tests/test_convert_openfermion.py @@ -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") @@ -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): @@ -81,31 +80,27 @@ 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): - """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.Sum) + + # def test_invalid_format_qubit(self): + # """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") # PennyLane operators were obtained from openfermion operators manually @pytest.mark.parametrize( From 2c79d9b7fc744fd1ffde2cc4650216c152a25e38 Mon Sep 17 00:00:00 2001 From: soranjh Date: Wed, 19 Jun 2024 20:09:37 -0400 Subject: [PATCH 02/15] run black --- pennylane/qchem/convert_openfermion.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/pennylane/qchem/convert_openfermion.py b/pennylane/qchem/convert_openfermion.py index 59804df16c3..796c7c3a710 100644 --- a/pennylane/qchem/convert_openfermion.py +++ b/pennylane/qchem/convert_openfermion.py @@ -79,8 +79,10 @@ def from_openfermion(openfermion_op, wires=None, tol=1e-16): >>> print(pl_op) 1.2 * X(0) + 2.4 * Z(1) """ + _ = _import_of() return _from_openfermion_dispatch(openfermion_op, wires=None, tol=tol) + @singledispatch def _from_openfermion_dispatch(of_op, wires=None, tol=1.0e-16): """Dispatches to appropriate function if of_op is a ``FermionOperator`` or ``QubitOperator``.""" @@ -88,7 +90,10 @@ def _from_openfermion_dispatch(of_op, wires=None, tol=1.0e-16): f"The input operator must be a FermionOperator or QubitOperator, got: {type(of_op)}." ) + openfermion = _import_of() + + @_from_openfermion_dispatch.register def _(openfermion_op: openfermion.FermionOperator, wires=None, tol=1.0e-16): @@ -110,10 +115,16 @@ def _(openfermion_op: openfermion.FermionOperator, wires=None, tol=1.0e-16): return pl_op + @_from_openfermion_dispatch.register def _(openfermion_op: openfermion.QubitOperator, wires=None, tol=1.0e-16): - return qml.dot(*_openfermion_to_pennylane(openfermion_op, wires=wires)) + coeffs, pl_ops = _openfermion_to_pennylane(openfermion_op, tol=tol) + + pennylane_op = qml.ops.LinearCombination(coeffs, pl_ops) + + return pennylane_op + def to_openfermion( pennylane_op: Union[Sum, LinearCombination, FermiWord, FermiSentence], wires=None, tol=1.0e-16 @@ -144,6 +155,7 @@ def to_openfermion( 1.2 [0^ 1] + 3.1 [1^ 2] """ + _ = _import_of() return _to_openfermion_dispatch(pennylane_op, wires=wires, tol=tol) From c0cf6cd2484e89ad5919d0985f4e973a0c7d36f7 Mon Sep 17 00:00:00 2001 From: soranjh Date: Wed, 19 Jun 2024 20:13:50 -0400 Subject: [PATCH 03/15] run black --- tests/qchem/openfermion_pyscf_tests/test_convert_openfermion.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/qchem/openfermion_pyscf_tests/test_convert_openfermion.py b/tests/qchem/openfermion_pyscf_tests/test_convert_openfermion.py index 2a35fb9978d..fc9e41fe829 100644 --- a/tests/qchem/openfermion_pyscf_tests/test_convert_openfermion.py +++ b/tests/qchem/openfermion_pyscf_tests/test_convert_openfermion.py @@ -90,7 +90,7 @@ def test_sum_qubit(self): """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(qml.from_openfermion(q_op), qml.ops.Sum) + assert isinstance(qml.from_openfermion(q_op), qml.ops.LinearCombination) # def test_invalid_format_qubit(self): # """Test if error is raised if format is invalid.""" From e0608c4bbe6e2a4fa23bb5199e5c987acb9cc02a Mon Sep 17 00:00:00 2001 From: soranjh Date: Wed, 19 Jun 2024 21:56:44 -0400 Subject: [PATCH 04/15] merge functions --- pennylane/qchem/convert_openfermion.py | 58 ++++++++++---------------- 1 file changed, 23 insertions(+), 35 deletions(-) diff --git a/pennylane/qchem/convert_openfermion.py b/pennylane/qchem/convert_openfermion.py index 796c7c3a710..64fb45bdfcd 100644 --- a/pennylane/qchem/convert_openfermion.py +++ b/pennylane/qchem/convert_openfermion.py @@ -79,51 +79,39 @@ def from_openfermion(openfermion_op, wires=None, tol=1e-16): >>> print(pl_op) 1.2 * X(0) + 2.4 * Z(1) """ - _ = _import_of() - return _from_openfermion_dispatch(openfermion_op, wires=None, tol=tol) - - -@singledispatch -def _from_openfermion_dispatch(of_op, wires=None, tol=1.0e-16): - """Dispatches to appropriate function if of_op is a ``FermionOperator`` or ``QubitOperator``.""" - raise ValueError( - f"The input operator must be a FermionOperator or QubitOperator, got: {type(of_op)}." - ) - - -openfermion = _import_of() - - -@_from_openfermion_dispatch.register -def _(openfermion_op: openfermion.FermionOperator, wires=None, tol=1.0e-16): + openfermion = _import_of() - typemap = {0: "-", 1: "+"} + if isinstance(openfermion_op, openfermion.FermionOperator): + typemap = {0: "-", 1: "+"} - 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 + elif isinstance(openfermion_op, openfermion.QubitOperator): -@_from_openfermion_dispatch.register -def _(openfermion_op: openfermion.QubitOperator, wires=None, tol=1.0e-16): + coeffs, pl_ops = _openfermion_to_pennylane(openfermion_op, tol=tol) - coeffs, pl_ops = _openfermion_to_pennylane(openfermion_op, tol=tol) + pennylane_op = qml.ops.LinearCombination(coeffs, pl_ops) - pennylane_op = qml.ops.LinearCombination(coeffs, pl_ops) + return pennylane_op - return pennylane_op + else: + raise ValueError( + f"The input operator must be a QubitOperator or FermionOperator, got: {type(openfermion_op)}." + ) def to_openfermion( @@ -155,7 +143,7 @@ def to_openfermion( 1.2 [0^ 1] + 3.1 [1^ 2] """ - _ = _import_of() + return _to_openfermion_dispatch(pennylane_op, wires=wires, tol=tol) From cc55ff01e7db7c70ea6fdd05ab0c5144cf98fc05 Mon Sep 17 00:00:00 2001 From: soranjh Date: Wed, 19 Jun 2024 23:27:29 -0400 Subject: [PATCH 05/15] fix pylint --- pennylane/qchem/convert_openfermion.py | 15 ++++----------- .../test_convert_openfermion.py | 10 ---------- 2 files changed, 4 insertions(+), 21 deletions(-) diff --git a/pennylane/qchem/convert_openfermion.py b/pennylane/qchem/convert_openfermion.py index 64fb45bdfcd..1ab35dcca5c 100644 --- a/pennylane/qchem/convert_openfermion.py +++ b/pennylane/qchem/convert_openfermion.py @@ -45,7 +45,7 @@ def _import_of(): return openfermion -def from_openfermion(openfermion_op, wires=None, tol=1e-16): +def from_openfermion(openfermion_op, tol=1e-16): r"""Convert OpenFermion `FermionOperator `__ and `QubitOperator `__ @@ -100,18 +100,11 @@ def from_openfermion(openfermion_op, wires=None, tol=1e-16): return pl_op - elif isinstance(openfermion_op, openfermion.QubitOperator): + coeffs, pl_ops = _openfermion_to_pennylane(openfermion_op, tol=tol) - coeffs, pl_ops = _openfermion_to_pennylane(openfermion_op, tol=tol) + pennylane_op = qml.ops.LinearCombination(coeffs, pl_ops) - pennylane_op = qml.ops.LinearCombination(coeffs, pl_ops) - - return pennylane_op - - else: - raise ValueError( - f"The input operator must be a QubitOperator or FermionOperator, got: {type(openfermion_op)}." - ) + return pennylane_op def to_openfermion( diff --git a/tests/qchem/openfermion_pyscf_tests/test_convert_openfermion.py b/tests/qchem/openfermion_pyscf_tests/test_convert_openfermion.py index fc9e41fe829..de157d7a0a2 100644 --- a/tests/qchem/openfermion_pyscf_tests/test_convert_openfermion.py +++ b/tests/qchem/openfermion_pyscf_tests/test_convert_openfermion.py @@ -92,16 +92,6 @@ def test_sum_qubit(self): assert isinstance(qml.from_openfermion(q_op), qml.ops.LinearCombination) - # def test_invalid_format_qubit(self): - # """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") - # PennyLane operators were obtained from openfermion operators manually @pytest.mark.parametrize( ("of_op", "pl_op"), From 7a7e5e134f2010ebf88d445549c1ff703119df2b Mon Sep 17 00:00:00 2001 From: soranjh Date: Thu, 20 Jun 2024 12:14:11 -0400 Subject: [PATCH 06/15] fix wires --- pennylane/qchem/convert_openfermion.py | 20 ++---- .../test_convert_openfermion.py | 67 ++++++------------- 2 files changed, 23 insertions(+), 64 deletions(-) diff --git a/pennylane/qchem/convert_openfermion.py b/pennylane/qchem/convert_openfermion.py index 1ab35dcca5c..1968939c491 100644 --- a/pennylane/qchem/convert_openfermion.py +++ b/pennylane/qchem/convert_openfermion.py @@ -45,7 +45,7 @@ def _import_of(): return openfermion -def from_openfermion(openfermion_op, tol=1e-16): +def from_openfermion(openfermion_op, wires=None, tol=1e-16): r"""Convert OpenFermion `FermionOperator `__ and `QubitOperator `__ @@ -100,7 +100,7 @@ def from_openfermion(openfermion_op, tol=1e-16): return pl_op - coeffs, pl_ops = _openfermion_to_pennylane(openfermion_op, tol=tol) + coeffs, pl_ops = _openfermion_to_pennylane(openfermion_op, wires=wires, tol=tol) pennylane_op = qml.ops.LinearCombination(coeffs, pl_ops) @@ -159,18 +159,6 @@ def _(pl_op: Sum, wires=None, tol=1.0e-16): def _(ops: FermiWord, wires=None, tol=1.0e-16): openfermion = _import_of() - if wires: - all_wires = Wires.all_wires(ops.wires, sort=True) - mapped_wires = _process_wires(wires) - if not set(all_wires).issubset(set(mapped_wires)): - raise ValueError("Supplied `wires` does not cover all wires defined in `ops`.") - - pl_op_mapped = {} - for loc, orbital in ops.keys(): - pl_op_mapped[(loc, mapped_wires.index(orbital))] = ops[(loc, orbital)] - - ops = FermiWord(pl_op_mapped) - return openfermion.ops.FermionOperator(qml.fermi.fermionic._to_string(ops, of=True)) @@ -181,8 +169,8 @@ def _(pl_op: FermiSentence, wires=None, tol=1.0e-16): fermion_op = openfermion.ops.FermionOperator() for fermi_word in pl_op: if np.abs(pl_op[fermi_word].imag) < tol: - fermion_op += pl_op[fermi_word].real * to_openfermion(fermi_word, wires=wires) + fermion_op += pl_op[fermi_word].real * to_openfermion(fermi_word) else: - fermion_op += pl_op[fermi_word] * to_openfermion(fermi_word, wires=wires) + fermion_op += pl_op[fermi_word] * to_openfermion(fermi_word) return fermion_op diff --git a/tests/qchem/openfermion_pyscf_tests/test_convert_openfermion.py b/tests/qchem/openfermion_pyscf_tests/test_convert_openfermion.py index de157d7a0a2..846a6ec4329 100644 --- a/tests/qchem/openfermion_pyscf_tests/test_convert_openfermion.py +++ b/tests/qchem/openfermion_pyscf_tests/test_convert_openfermion.py @@ -74,6 +74,20 @@ def test_convert_qubit(self, of_op, pl_op): converted_pl_op = qml.from_openfermion(of_op) assert converted_pl_op.compare(pl_op) + OPSWIRES = ( + ( + (openfermion.QubitOperator("X0", 1.2) + openfermion.QubitOperator("Z1", 2.4)), + ({0: "a", 1: 2}), + (1.2 * qml.X("a") + 2.4 * qml.Z(2)), + ), + ) + + @pytest.mark.parametrize("of_op, wires, pl_op", OPSWIRES) + def test_wires_qubit(self, of_op, wires, pl_op): + """Test conversion from ``QubitOperator`` to PennyLane with wire map.""" + converted_pl_op = qml.from_openfermion(of_op, wires=wires) + assert converted_pl_op.compare(pl_op) + def test_tol_qubit(self): """Test with complex coefficients.""" q_op = openfermion.QubitOperator("X0", complex(1.0, 1e-8)) + openfermion.QubitOperator( @@ -86,8 +100,8 @@ def test_tol_qubit(self): pl_op = qml.from_openfermion(q_op, tol=1e-10) assert np.any(pl_op.coeffs.imag) - def test_sum_qubit(self): - """Test that from_openfermion yields a :class:`~.Sum` object.""" + def test_type_qubit(self): + """Test that from_openfermion yields a ``LinearCombination`` object.""" q_op = openfermion.QubitOperator("X0 X1", 0.25) + openfermion.QubitOperator("Z1 Z0", 0.75) assert isinstance(qml.from_openfermion(q_op), qml.ops.LinearCombination) @@ -266,26 +280,9 @@ def test_tol(self, pl_op): MAPPED_OPS = ( ( - (qml.fermi.FermiWord({(0, 0): "+", (1, 1): "-"})), - (openfermion.FermionOperator("1^ 0")), - ({0: 1, 1: 0}), - ), - ( - ( - qml.fermi.FermiSentence( - { - qml.fermi.FermiWord( - {(0, 0): "+", (1, 1): "-", (2, 3): "+", (3, 2): "-"} - ): 0.25, - qml.fermi.FermiWord({(1, 0): "+", (0, 1): "-"}): 0.1, - } - ) - ), - ( - 0.1 * openfermion.FermionOperator("1 0^") - + 0.25 * openfermion.FermionOperator("0^ 1 2^ 3") - ), - ({0: 0, 1: 1, 2: 3, 3: 2}), + (1.2 * qml.X("a") + 2.4 * qml.Z(2)), + (openfermion.QubitOperator("X0", 1.2) + openfermion.QubitOperator("Z1", 2.4)), + ({"a": 0, 2: 1}), ), ) @@ -312,32 +309,6 @@ def test_not_xyz(self, op): with pytest.raises(ValueError, match=_match): qml.to_openfermion(qml.to_openfermion(pl_op)) - INVALID_OPS_WIRES = ( - ( - qml.ops.LinearCombination( - np.array([0.1, 0.2]), - [ - qml.operation.Tensor(qml.PauliX(wires=["w0"])), - qml.operation.Tensor(qml.PauliY(wires=["w0"]), qml.PauliZ(wires=["w1"])), - ], - ) - ), - ((qml.fermi.FermiWord({(0, 0): "+", (1, 1): "-"}))), - ) - - @pytest.mark.parametrize("pl_op", INVALID_OPS_WIRES) - def test_wires_not_covered(self, pl_op): - r"""Test if the conversion complains about supplied wires not covering ops wires.""" - - with pytest.raises( - ValueError, - match="Supplied `wires` does not cover all wires defined in `ops`.", - ): - qml.to_openfermion( - pl_op, - wires=qml.wires.Wires(["w0", "w2"]), - ) - def test_invalid_op(self): r"""Test if to_openfermion throws an error if the wrong type of operator is given.""" pl_op = "Wrong type." From cb3b37ba0e1a722d6e93eb993aa990c08ab678c5 Mon Sep 17 00:00:00 2001 From: soranjh Date: Thu, 20 Jun 2024 12:50:29 -0400 Subject: [PATCH 07/15] add review comments --- pennylane/qchem/convert_openfermion.py | 21 ++++++++------------- 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/pennylane/qchem/convert_openfermion.py b/pennylane/qchem/convert_openfermion.py index 1968939c491..3c59aa9064a 100644 --- a/pennylane/qchem/convert_openfermion.py +++ b/pennylane/qchem/convert_openfermion.py @@ -48,17 +48,15 @@ def _import_of(): def from_openfermion(openfermion_op, wires=None, tol=1e-16): r"""Convert OpenFermion `FermionOperator `__ - and `QubitOperator `__ - objects to PennyLane :class:`~.fermi.FermiWord` or :class:`~.fermi.FermiSentence` or - :class:`~.LinearCombination` objects. + to PennyLane :class:`~.fermi.FermiWord` or :class:`~.fermi.FermiSentence` and + OpenFermion `QubitOperator `__ + to PennyLane :class:`~.LinearCombination`. Args: openfermion_op (FermionOperator, QubitOperator): OpenFermion operator - wires (.Wires, list, tuple, dict): Custom wire mapping used to convert the external qubit + wires (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. + Only int-keyed dictionaries (for qubit-to-wire conversion) are accepted. If ``None``, the identity map (e.g., ``0->0, 1->1, ...``) will be used. tol (float): tolerance for discarding negligible coefficients @@ -115,12 +113,9 @@ def to_openfermion( Args: pennylane_op (~ops.op_math.Sum, ~ops.op_math.LinearCombination, FermiWord, FermiSentence): linear combination of operators - 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. + wires (dict): Custom wire mapping used to convert a PennyLane qubit operator + to the external operator. + Only int-keyed dict (for qubit-to-wire conversion) is accepted. If None, will use identity map (e.g. 0->0, 1->1, ...). Returns: From 64905079791094ed3202a599e73e23920eca8e39 Mon Sep 17 00:00:00 2001 From: soranjh Date: Thu, 20 Jun 2024 12:51:53 -0400 Subject: [PATCH 08/15] update changelog --- doc/releases/changelog-dev.md | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/releases/changelog-dev.md b/doc/releases/changelog-dev.md index eb52d55607d..9f93d6790d6 100644 --- a/doc/releases/changelog-dev.md +++ b/doc/releases/changelog-dev.md @@ -97,6 +97,7 @@ OpenFermion and PennyLane objects. [(#5773)](https://github.com/PennyLaneAI/pennylane/pull/5773) [(#5808)](https://github.com/PennyLaneAI/pennylane/pull/5808) + [(#5881)](https://github.com/PennyLaneAI/pennylane/pull/5881) ```python of_op = openfermion.FermionOperator('0^ 2') From 5c81beff24e7302e6ae7d9dde8222cf6281d4d6b Mon Sep 17 00:00:00 2001 From: soranjh Date: Thu, 20 Jun 2024 16:27:25 -0400 Subject: [PATCH 09/15] add review comments --- pennylane/qchem/convert_openfermion.py | 4 ++-- .../qchem/openfermion_pyscf_tests/test_convert_openfermion.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/pennylane/qchem/convert_openfermion.py b/pennylane/qchem/convert_openfermion.py index 3c59aa9064a..a24ee62a45c 100644 --- a/pennylane/qchem/convert_openfermion.py +++ b/pennylane/qchem/convert_openfermion.py @@ -56,7 +56,7 @@ def from_openfermion(openfermion_op, wires=None, tol=1e-16): openfermion_op (FermionOperator, QubitOperator): OpenFermion operator wires (dict): Custom wire mapping used to convert the external qubit operator to a PennyLane operator. - Only int-keyed dictionaries (for qubit-to-wire conversion) are accepted. + Only dictionaries with integer keys (for qubit-to-wire conversion) are accepted. If ``None``, the identity map (e.g., ``0->0, 1->1, ...``) will be used. tol (float): tolerance for discarding negligible coefficients @@ -115,7 +115,7 @@ def to_openfermion( linear combination of operators wires (dict): Custom wire mapping used to convert a PennyLane qubit operator to the external operator. - Only int-keyed dict (for qubit-to-wire conversion) is accepted. + Only dictionaries with integer keys (for qubit-to-wire conversion) are accepted. If None, will use identity map (e.g. 0->0, 1->1, ...). Returns: diff --git a/tests/qchem/openfermion_pyscf_tests/test_convert_openfermion.py b/tests/qchem/openfermion_pyscf_tests/test_convert_openfermion.py index 846a6ec4329..1365a9a7964 100644 --- a/tests/qchem/openfermion_pyscf_tests/test_convert_openfermion.py +++ b/tests/qchem/openfermion_pyscf_tests/test_convert_openfermion.py @@ -74,7 +74,7 @@ def test_convert_qubit(self, of_op, pl_op): converted_pl_op = qml.from_openfermion(of_op) assert converted_pl_op.compare(pl_op) - OPSWIRES = ( + OPS_WIRES = ( ( (openfermion.QubitOperator("X0", 1.2) + openfermion.QubitOperator("Z1", 2.4)), ({0: "a", 1: 2}), @@ -82,7 +82,7 @@ def test_convert_qubit(self, of_op, pl_op): ), ) - @pytest.mark.parametrize("of_op, wires, pl_op", OPSWIRES) + @pytest.mark.parametrize("of_op, wires, pl_op", OPS_WIRES) def test_wires_qubit(self, of_op, wires, pl_op): """Test conversion from ``QubitOperator`` to PennyLane with wire map.""" converted_pl_op = qml.from_openfermion(of_op, wires=wires) From 0e33f0e17b446ef3ec6edc4faee483ae54e75427 Mon Sep 17 00:00:00 2001 From: soranjh Date: Thu, 20 Jun 2024 16:31:42 -0400 Subject: [PATCH 10/15] raise error for fermionic wiremap --- pennylane/qchem/convert_openfermion.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/pennylane/qchem/convert_openfermion.py b/pennylane/qchem/convert_openfermion.py index a24ee62a45c..8b254f9162a 100644 --- a/pennylane/qchem/convert_openfermion.py +++ b/pennylane/qchem/convert_openfermion.py @@ -80,6 +80,10 @@ def from_openfermion(openfermion_op, wires=None, tol=1e-16): openfermion = _import_of() if isinstance(openfermion_op, openfermion.FermionOperator): + + if wires: + raise ValueError(f"Custom wire mapping is not supported for fermionic operators.") + typemap = {0: "-", 1: "+"} fermi_words = [] @@ -154,6 +158,9 @@ def _(pl_op: Sum, wires=None, tol=1.0e-16): def _(ops: FermiWord, wires=None, tol=1.0e-16): openfermion = _import_of() + if wires: + raise ValueError(f"Custom wire mapping is not supported for fermionic operators.") + return openfermion.ops.FermionOperator(qml.fermi.fermionic._to_string(ops, of=True)) @@ -161,6 +168,9 @@ def _(ops: FermiWord, wires=None, tol=1.0e-16): def _(pl_op: FermiSentence, wires=None, tol=1.0e-16): openfermion = _import_of() + if wires: + raise ValueError(f"Custom wire mapping is not supported for fermionic operators.") + fermion_op = openfermion.ops.FermionOperator() for fermi_word in pl_op: if np.abs(pl_op[fermi_word].imag) < tol: From 4e9e5ab6838652887ed9d2e58182fa0b1a920b07 Mon Sep 17 00:00:00 2001 From: soranjh Date: Thu, 20 Jun 2024 16:58:36 -0400 Subject: [PATCH 11/15] add wire tests --- .../test_convert_openfermion.py | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/tests/qchem/openfermion_pyscf_tests/test_convert_openfermion.py b/tests/qchem/openfermion_pyscf_tests/test_convert_openfermion.py index 1365a9a7964..70feeea9579 100644 --- a/tests/qchem/openfermion_pyscf_tests/test_convert_openfermion.py +++ b/tests/qchem/openfermion_pyscf_tests/test_convert_openfermion.py @@ -88,6 +88,23 @@ def test_wires_qubit(self, of_op, wires, pl_op): converted_pl_op = qml.from_openfermion(of_op, wires=wires) assert converted_pl_op.compare(pl_op) + OPS_FERMI = ( + ((openfermion.FermionOperator("0^ 1")), ({0: "a", 1: 2})), + ( + (openfermion.FermionOperator("0^ 1") + openfermion.FermionOperator("3^ 4^")), + ({0: "a", 3: 2}), + ), + ) + + @pytest.mark.parametrize("of_op, wires", OPS_FERMI) + def test_wires_fermionic(self, of_op, wires): + """Test that an error is raised for mapping wires in fermionic operators.""" + with pytest.raises( + ValueError, + match=f"Custom wire mapping is not supported for fermionic operators.", + ): + qml.from_openfermion(of_op, wires=wires) + def test_tol_qubit(self): """Test with complex coefficients.""" q_op = openfermion.QubitOperator("X0", complex(1.0, 1e-8)) + openfermion.QubitOperator( @@ -320,3 +337,20 @@ def test_invalid_op(self): qml.to_openfermion( pl_op, ) + + OPS_FERMI_WIRE = ( + ((qml.fermi.FermiWord({(0, 0): "+", (1, 1): "-"})), ({0: "a", 1: 2})), + ( + (qml.fermi.FermiSentence({qml.fermi.FermiWord({(0, 0): "+", (1, 1): "-"}): 1.2})), + ({0: "a", 1: 2}), + ), + ) + + @pytest.mark.parametrize("pl_op, wires", OPS_FERMI_WIRE) + def test_wires_fermionic_error(self, pl_op, wires): + """Test that an error is raised for mapping wires in fermionic operators.""" + with pytest.raises( + ValueError, + match=f"Custom wire mapping is not supported for fermionic operators.", + ): + qml.to_openfermion(pl_op, wires=wires) From f1c883dc3b8d42189565cb351ffab85a17c67f3b Mon Sep 17 00:00:00 2001 From: soranjh Date: Thu, 20 Jun 2024 17:00:54 -0400 Subject: [PATCH 12/15] fix f string --- pennylane/qchem/convert_openfermion.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pennylane/qchem/convert_openfermion.py b/pennylane/qchem/convert_openfermion.py index 8b254f9162a..71b34939a57 100644 --- a/pennylane/qchem/convert_openfermion.py +++ b/pennylane/qchem/convert_openfermion.py @@ -82,7 +82,7 @@ def from_openfermion(openfermion_op, wires=None, tol=1e-16): if isinstance(openfermion_op, openfermion.FermionOperator): if wires: - raise ValueError(f"Custom wire mapping is not supported for fermionic operators.") + raise ValueError("Custom wire mapping is not supported for fermionic operators.") typemap = {0: "-", 1: "+"} @@ -159,7 +159,7 @@ def _(ops: FermiWord, wires=None, tol=1.0e-16): openfermion = _import_of() if wires: - raise ValueError(f"Custom wire mapping is not supported for fermionic operators.") + raise ValueError("Custom wire mapping is not supported for fermionic operators.") return openfermion.ops.FermionOperator(qml.fermi.fermionic._to_string(ops, of=True)) @@ -169,7 +169,7 @@ def _(pl_op: FermiSentence, wires=None, tol=1.0e-16): openfermion = _import_of() if wires: - raise ValueError(f"Custom wire mapping is not supported for fermionic operators.") + raise ValueError("Custom wire mapping is not supported for fermionic operators.") fermion_op = openfermion.ops.FermionOperator() for fermi_word in pl_op: From 4f60e66908642c53d89287686c2cba0bcfc35453 Mon Sep 17 00:00:00 2001 From: soranjh Date: Thu, 20 Jun 2024 22:55:33 -0400 Subject: [PATCH 13/15] remove f string --- .../qchem/openfermion_pyscf_tests/test_convert_openfermion.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/qchem/openfermion_pyscf_tests/test_convert_openfermion.py b/tests/qchem/openfermion_pyscf_tests/test_convert_openfermion.py index 70feeea9579..2938ca07a29 100644 --- a/tests/qchem/openfermion_pyscf_tests/test_convert_openfermion.py +++ b/tests/qchem/openfermion_pyscf_tests/test_convert_openfermion.py @@ -101,7 +101,7 @@ def test_wires_fermionic(self, of_op, wires): """Test that an error is raised for mapping wires in fermionic operators.""" with pytest.raises( ValueError, - match=f"Custom wire mapping is not supported for fermionic operators.", + match="Custom wire mapping is not supported for fermionic operators.", ): qml.from_openfermion(of_op, wires=wires) @@ -351,6 +351,6 @@ def test_wires_fermionic_error(self, pl_op, wires): """Test that an error is raised for mapping wires in fermionic operators.""" with pytest.raises( ValueError, - match=f"Custom wire mapping is not supported for fermionic operators.", + match="Custom wire mapping is not supported for fermionic operators.", ): qml.to_openfermion(pl_op, wires=wires) From 0ed104aeae474d6535e0a059b482db5e34668e80 Mon Sep 17 00:00:00 2001 From: soranjh Date: Thu, 20 Jun 2024 23:29:36 -0400 Subject: [PATCH 14/15] update docstring --- pennylane/qchem/convert_openfermion.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/pennylane/qchem/convert_openfermion.py b/pennylane/qchem/convert_openfermion.py index 71b34939a57..5bc25556f9f 100644 --- a/pennylane/qchem/convert_openfermion.py +++ b/pennylane/qchem/convert_openfermion.py @@ -69,8 +69,8 @@ def from_openfermion(openfermion_op, wires=None, tol=1e-16): >>> 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) + 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) @@ -112,18 +112,20 @@ def from_openfermion(openfermion_op, wires=None, tol=1e-16): def to_openfermion( pennylane_op: Union[Sum, LinearCombination, FermiWord, FermiSentence], wires=None, tol=1.0e-16 ): - r"""Convert a PennyLane operator to a OpenFermion ``QubitOperator`` or ``FermionOperator``. + r"""Convert a PennyLane operator to OpenFermion + `QubitOperator `__ or + `FermionOperator `__. Args: pennylane_op (~ops.op_math.Sum, ~ops.op_math.LinearCombination, FermiWord, FermiSentence): - linear combination of operators + PennyLane operator wires (dict): Custom wire mapping used to convert a PennyLane qubit operator to the external operator. Only dictionaries with integer keys (for qubit-to-wire conversion) are accepted. If None, will use identity map (e.g. 0->0, 1->1, ...). Returns: - (QubitOperator, FermionOperator): an OpenFermion operator + (QubitOperator, FermionOperator): OpenFermion operator **Example** From 7d371739336997425549c674c1734ecd08d8d245 Mon Sep 17 00:00:00 2001 From: soranjh Date: Thu, 20 Jun 2024 23:30:58 -0400 Subject: [PATCH 15/15] update docstring --- pennylane/qchem/convert_openfermion.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pennylane/qchem/convert_openfermion.py b/pennylane/qchem/convert_openfermion.py index 5bc25556f9f..ce7f3123100 100644 --- a/pennylane/qchem/convert_openfermion.py +++ b/pennylane/qchem/convert_openfermion.py @@ -122,7 +122,7 @@ def to_openfermion( wires (dict): Custom wire mapping used to convert a PennyLane qubit operator to the external operator. Only dictionaries with integer keys (for qubit-to-wire conversion) are accepted. - If None, will use identity map (e.g. 0->0, 1->1, ...). + If ``None``, the identity map (e.g., ``0->0, 1->1, ...``) will be used. Returns: (QubitOperator, FermionOperator): OpenFermion operator