Repository: quantumlib/OpenFermion
Pull request #625 (2020-07-27 17:00:51)

QubitOperator to PauliSum translator
This PR contains the functions to translate from QubitOperator to PauliSum.

From the re-organization of the repository it was not clear to me where this functionality should go. I created a new folder `openfermion.circuits.translator` for it, but feel free to suggest a more suitable place.

Previously, this PR was approved in https://github.com/quantumlib/OpenFermion-Cirq/pull/396

xabomon: ATM Cirq is not found when running pytest, should I do anything from my side about this?
ncrubin: @xabomon testing setup happens by installing requirements.txt.  cirq isn't in there yet and your PR doesn't add it.  I think adding a cirq dependency to the requirements.txt will solve this issue.
xabomon: Hi @ncrubin ,

correct me if I'm wrong: adding Cirq to the requirements.txt file implies that Cirq is then needed to install OF.

I recall some discussion in the OF sync if making OF dependent of Cirq was a good idea. Unfortunately I had to miss a couple of these meetings and I'm not sure what's the status of this. Unless someone disagrees I will add Cirq to requirements.

Best,
ncrubin: @xabomon Adding cirq to requirements is fine.  We are moving towards having OF depend on Cirq.  
obriente: Thanks for putting this PR together Xavi.

I'm not sure the extra folder is needed, as I can't think of anything else that would go in there. Perhaps either openfermion.circuits, or openfermion.transforms.opconversions - @ncrubin , any suggestions?
xabomon: Hi Tom,

I'm totally open to suggestions on where to place this module.
obriente: Yeah, this is pretty much my thought process as well. I think opconversions is probably the most appropriate place to put it to be honest, if we say 'everything that depends on cirq should go in circuits', why not ensure that scipy.sparse.linalg is only found in linalg? Shift it there, and I'll do a once-over.

Patch:
diff --git a/src/openfermion/transforms/opconversions/__init__.py b/src/openfermion/transforms/opconversions/__init__.py
index ee50b00..7a9ee41 100644
--- a/src/openfermion/transforms/opconversions/__init__.py
+++ b/src/openfermion/transforms/opconversions/__init__.py
@@ -20,6 +20,8 @@ from .fenwick_tree import FenwickNode, FenwickTree
 
 from .jordan_wigner import jordan_wigner
 
+from .qubitoperator_to_paulisum import qubit_operator_to_pauli_sum
+
 from .reverse_jordan_wigner import reverse_jordan_wigner
 
 from .remove_symmetry_qubits import symmetry_conserving_bravyi_kitaev
diff --git a/src/openfermion/transforms/opconversions/qubitoperator_to_paulisum.py b/src/openfermion/transforms/opconversions/qubitoperator_to_paulisum.py
new file mode 100644
index 0000000..dae0d3f
--- /dev/null
+++ b/src/openfermion/transforms/opconversions/qubitoperator_to_paulisum.py
@@ -0,0 +1,63 @@
+#   Licensed under the Apache License, Version 2.0 (the "License");
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an "AS IS" BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+from typing import Optional, Sequence
+import cirq
+from openfermion.ops.operators import QubitOperator
+from openfermion.utils import count_qubits
+
+
+def _qubit_operator_term_to_pauli_string(term: dict, qubits: Sequence[cirq.Qid]
+                                        ) -> cirq.PauliString:
+    """
+    Convert term of QubitOperator to a PauliString.
+
+    Args:
+        term (dict): QubitOperator term.
+        qubits (list): List of qubit names.
+    Returns:
+        pauli_string (PauliString): cirq PauliString object.
+    """
+    ind_ops, coeff = term
+
+    return cirq.PauliString(dict((qubits[ind], op) for ind, op in ind_ops),
+                            coeff)
+
+
+def qubit_operator_to_pauli_sum(operator: QubitOperator,
+                                qubits: Optional[Sequence[cirq.Qid]] = None
+                               ) -> cirq.PauliSum:
+    """
+    Convert QubitOperator to a sum of PauliString.
+
+    Args:
+        operator (QubitOperator): operator to convert.
+        qubits (List): Optional list of qubit names.
+            If `None` a list of `cirq.LineQubit` of length number of qubits
+            in operator is created.
+
+    Returns:
+        pauli_sum (PauliSum): cirq PauliSum object.
+
+    Raises:
+        TypeError: if qubit_op is not a QubitOpertor.
+    """
+    if not isinstance(operator, QubitOperator):
+        raise TypeError('Input must be a QubitOperator.')
+
+    if qubits is None:
+        qubits = cirq.LineQubit.range(count_qubits(operator))
+
+    pauli_sum = cirq.PauliSum()
+    for pauli in operator.terms.items():
+        pauli_sum += _qubit_operator_term_to_pauli_string(pauli, qubits)
+
+    return pauli_sum


Test patch:
diff --git a/src/openfermion/transforms/opconversions/qubitoperator_to_paulisum_test.py b/src/openfermion/transforms/opconversions/qubitoperator_to_paulisum_test.py
new file mode 100644
index 0000000..421ce75
--- /dev/null
+++ b/src/openfermion/transforms/opconversions/qubitoperator_to_paulisum_test.py
@@ -0,0 +1,90 @@
+#   Licensed under the Apache License, Version 2.0 (the "License");
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an "AS IS" BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+import pytest
+import numpy
+import openfermion
+import cirq
+from openfermion.ops.operators import QubitOperator
+
+from openfermion.transforms.opconversions import (qubit_operator_to_pauli_sum)
+from openfermion.transforms.opconversions.qubitoperator_to_paulisum import (
+    _qubit_operator_term_to_pauli_string)
+
+
+def test_function_raises():
+    """Test function raises."""
+    operator = QubitOperator('X0 X1 X2 X3', 1.0)
+    with pytest.raises(TypeError):
+        _qubit_operator_term_to_pauli_string(list(operator.terms.items())[0],
+                                             qubits=0.0)
+    with pytest.raises(TypeError):
+        qubit_operator_to_pauli_sum([5.0])
+
+
+def test_pauli_sum_qubits():
+    """Test PauliSum creates qubits."""
+    operator = QubitOperator('X0 X1 X2 X3', 1.0)
+    pauli_sum = qubit_operator_to_pauli_sum(operator=operator)
+
+    assert list(pauli_sum.qubits) == (cirq.LineQubit.range(4))
+
+
+def test_identity():
+    """Test correct hanlding of Identity."""
+    identity_op = QubitOperator(' ', -0.5)
+    pau_from_qop = _qubit_operator_term_to_pauli_string(
+        term=list(identity_op.terms.items())[0], qubits=cirq.LineQubit.range(2))
+    pauli_str = cirq.PauliString() * (-0.5)
+
+    assert pauli_str == pau_from_qop
+
+
+@pytest.mark.parametrize('qubitop, state_binary',
+                         [(QubitOperator('Z0 Z1', -1.0), '00'),
+                          (QubitOperator('X0 Y1', 1.0), '10')])
+def test_expectation_values(qubitop, state_binary):
+    """Test PauliSum and QubitOperator expectation value."""
+    n_qubits = openfermion.count_qubits(qubitop)
+    state = numpy.zeros(2**n_qubits, dtype='complex64')
+    state[int(state_binary, 2)] = 1.0
+    qubit_map = {cirq.LineQubit(i): i for i in range(n_qubits)}
+
+    pauli_str = _qubit_operator_term_to_pauli_string(
+        term=list(qubitop.terms.items())[0],
+        qubits=cirq.LineQubit.range(n_qubits))
+    op_mat = openfermion.get_sparse_operator(qubitop, n_qubits)
+
+    expct_qop = openfermion.expectation(op_mat, state)
+    expct_pauli = pauli_str.expectation_from_wavefunction(state, qubit_map)
+
+    numpy.testing.assert_allclose(expct_qop, expct_pauli)
+
+
+@pytest.mark.parametrize(
+    'qubitop, state_binary',
+    [(QubitOperator('Z0 Z1 Z2 Z3', -1.0) + QubitOperator('X0 Y1 Y2 X3', 1.0),
+      '1100'),
+     (QubitOperator('X0 X3', -1.0) + QubitOperator('Y1 Y2', 1.0), '0000')])
+def test_expectation_values_paulisum(qubitop, state_binary):
+    """Test PauliSum and QubitOperator expectation value."""
+    n_qubits = openfermion.count_qubits(qubitop)
+    state = numpy.zeros(2**n_qubits, dtype='complex64')
+    state[int(state_binary, 2)] = 1.0
+    qubit_map = {cirq.LineQubit(i): i for i in range(n_qubits)}
+
+    pauli_str = qubit_operator_to_pauli_sum(qubitop, list(qubit_map.keys()))
+    op_mat = openfermion.get_sparse_operator(qubitop, n_qubits)
+
+    expct_qop = openfermion.expectation(op_mat, state)
+    expct_pauli = pauli_str.expectation_from_wavefunction(state, qubit_map)
+
+    numpy.testing.assert_allclose(expct_qop, expct_pauli)