Repository: quantumlib/OpenFermion
Pull request #538 (2019-09-06 16:43:07)

Enhancement of Project onto sector function.
Hi all,

After some discussion with the user @rugantio I decided to improve the existing functions project_onto_sector and projection_error.

The main enhancement consists on checking that the sectors are passed as 0 or 1 (which is clearly documented) instead of 1 or -1.
Additionally, I added some trivial assertions and update the test such that the assertions are tested.

Please, let me know if anything else is required.

PS: A previous PR didn't have the commits, sorry.

Best,
Xavi


Patch:
diff --git a/src/openfermion/transforms/_qubit_operator_transforms.py b/src/openfermion/transforms/_qubit_operator_transforms.py
index f0d064c..81f1966 100644
--- a/src/openfermion/transforms/_qubit_operator_transforms.py
+++ b/src/openfermion/transforms/_qubit_operator_transforms.py
@@ -19,7 +19,9 @@ from openfermion.ops import QubitOperator
 
 
 def project_onto_sector(operator, qubits, sectors):
-    '''
+    """
+    Remove qubit by projecting onto sector.
+
     Takes a QubitOperator, and projects out a list
     of qubits, into either the +1 or -1 sector.
     Note - this requires knowledge of which sector
@@ -37,21 +39,32 @@ def project_onto_sector(operator, qubits, sectors):
         projected_operator: the resultant operator
 
     Raises:
-        TypeError: operator must be a QubitOperator
-    '''
-    if type(operator) is not QubitOperator:
-        raise TypeError('''Input operator must be a QubitOperator''')
+        TypeError: operator must be a QubitOperator.
+        TypeError: qubits and sector must be an array-like.
+        ValueError: If qubits and sectors have different length.
+        ValueError: If sector are not specified as 0 or 1.
+    """
+    if not isinstance(operator, QubitOperator):
+        raise TypeError('Input operator must be a QubitOperator.')
+    if not isinstance(qubits, (list, numpy.ndarray)):
+        raise TypeError('Qubit input must be an array-like.')
+    if not isinstance(sectors, (list, numpy.ndarray)):
+        raise TypeError('Sector input must be an array-like.')
+    if len(qubits) != len(sectors):
+        raise ValueError('Number of qubits and sectors must be equal.')
+    for i in sectors:
+        if i not in [0, 1]:
+            raise ValueError('Sectors must be 0 or 1.')
 
     projected_operator = QubitOperator()
     for term, factor in operator.terms.items():
 
         # Any term containing X or Y on the removed
         # qubits has an expectation value of zero
-        if [t for t in term if t[0] in qubits
-                and t[1] in ['X', 'Y']]:
+        if [t for t in term if t[0] in qubits and t[1] in ['X', 'Y']]:
             continue
 
-        new_term = tuple((t[0]-len([q for q in qubits if q < t[0]]), t[1])
+        new_term = tuple((t[0] - len([q for q in qubits if q < t[0]]), t[1])
                          for t in term if t[0] not in qubits)
         new_factor =\
             factor * (-1)**(sum([sectors[qubits.index(t[0])]
@@ -62,8 +75,8 @@ def project_onto_sector(operator, qubits, sectors):
 
 
 def projection_error(operator, qubits, sectors):
-    '''
-    Calculates the error from the project_onto_sector function.
+    """
+    Calculate the error from the project_onto_sector function.
 
     Args:
         operator: the QubitOperator to work on
@@ -77,26 +90,39 @@ def projection_error(operator, qubits, sectors):
         error: the trace norm of the removed term.
 
     Raises:
-        TypeError: operator must be a QubitOperator
-    '''
-    if type(operator) is not QubitOperator:
-        raise TypeError('''Input operator must be a QubitOperator''')
+        TypeError: operator must be a QubitOperator.
+        TypeError: qubits and sector must be an array-like.
+        ValueError: If qubits and sectors have different length.
+        ValueError: If sector are not specified as 0 or 1.
+    """
+    if not isinstance(operator, QubitOperator):
+        raise TypeError('Input operator must be a QubitOperator.')
+    if not isinstance(qubits, (list, numpy.ndarray)):
+        raise TypeError('Qubit input must be an array-like.')
+    if not isinstance(sectors, (list, numpy.ndarray)):
+        raise TypeError('Sector input must be an array-like.')
+    if len(qubits) != len(sectors):
+        raise ValueError('Number of qubits and sectors must be equal.')
+    for i in sectors:
+        if i not in [0, 1]:
+            raise ValueError('Sectors must be 0 or 1.')
 
     error = 0
     for term, factor in operator.terms.items():
 
         # Any term containing X or Y on the removed
         # qubits contributes to the error
-        if [t for t in term if t[0] in qubits
-                and t[1] in ['X', 'Y']]:
+        if [t for t in term if t[0] in qubits and t[1] in ['X', 'Y']]:
             error += abs(factor)**2
 
     return numpy.sqrt(error)
 
 
 def rotate_qubit_by_pauli(qop, pauli, angle):
-    '''
-    Performs the rotation e^{-i \theta * P}Qe^{i \theta * P}
+    r"""
+    Rotate qubit operator by exponential of Pauli.
+
+    Perform the rotation e^{-i \theta * P}Qe^{i \theta * P}
     on a qubitoperator Q and a Pauli operator P.
 
     Args:
@@ -113,7 +139,7 @@ def rotate_qubit_by_pauli(qop, pauli, angle):
         TypeError: qop must be a QubitOperator
         TypeError: pauli must be a Pauli Operator (QubitOperator
             with single term and coefficient equal to 1).
-    '''
+    """
     pvals = list(pauli.terms.values())
     if type(qop) is not QubitOperator:
         raise TypeError('This can only rotate QubitOperators')
@@ -127,7 +153,7 @@ def rotate_qubit_by_pauli(qop, pauli, angle):
     even_terms = 0.5 * (qop + pqp)
     odd_terms = 0.5 * (qop - pqp)
 
-    rotated_op = even_terms + numpy.cos(2*angle) * odd_terms + \
-        1j * numpy.sin(2*angle) * odd_terms * pauli
+    rotated_op = even_terms + numpy.cos(2 * angle) * odd_terms + \
+        1j * numpy.sin(2 * angle) * odd_terms * pauli
 
     return rotated_op


Test patch:
diff --git a/src/openfermion/transforms/_qubit_operator_transforms_test.py b/src/openfermion/transforms/_qubit_operator_transforms_test.py
index 916598c..0fa8809 100644
--- a/src/openfermion/transforms/_qubit_operator_transforms_test.py
+++ b/src/openfermion/transforms/_qubit_operator_transforms_test.py
@@ -26,6 +26,44 @@ class ProjectionTest(unittest.TestCase):
     def setUp(self):
         pass
 
+    def test_function_errors(self):
+        """Test main function errors."""
+        operator = (QubitOperator('Z0 X1', 1.0) +
+                    QubitOperator('X1', 2.0))
+        sector1 = [0]
+        sector2 = [1]
+        qbt_list = [0]
+        with self.assertRaises(TypeError):
+            project_onto_sector(operator=1.0, qubits=qbt_list, sectors=sector1)
+        with self.assertRaises(TypeError):
+            projection_error(operator=1.0, qubits=qbt_list, sectors=sector1)
+        with self.assertRaises(TypeError):
+            project_onto_sector(operator=operator, qubits=0.0, sectors=sector2)
+        with self.assertRaises(TypeError):
+            projection_error(operator=operator, qubits=0.0, sectors=sector2)
+        with self.assertRaises(TypeError):
+            project_onto_sector(operator=operator,
+                                qubits=qbt_list, sectors=operator)
+        with self.assertRaises(TypeError):
+            projection_error(operator=operator,
+                             qubits=qbt_list, sectors=operator)
+        with self.assertRaises(ValueError):
+            project_onto_sector(operator=operator, qubits=[
+                                0, 1], sectors=sector1)
+        with self.assertRaises(ValueError):
+            projection_error(operator=operator, qubits=[0, 1], sectors=sector1)
+        with self.assertRaises(ValueError):
+            project_onto_sector(operator=operator,
+                                qubits=qbt_list, sectors=[0, 0])
+        with self.assertRaises(ValueError):
+            projection_error(operator=operator,
+                             qubits=qbt_list, sectors=[0, 0])
+        with self.assertRaises(ValueError):
+            project_onto_sector(operator=operator,
+                                qubits=qbt_list, sectors=[-1])
+        with self.assertRaises(ValueError):
+            projection_error(operator=operator, qubits=qbt_list, sectors=[-1])
+
     def test_projection(self):
         coefficient = 0.5
         opstring = ((0, 'X'), (1, 'X'), (2, 'Z'))
@@ -33,7 +71,7 @@ class ProjectionTest(unittest.TestCase):
         operator = QubitOperator(opstring, coefficient)
         operator += QubitOperator(opstring2, coefficient)
         new_operator = project_onto_sector(
-                operator, qubits=[2, 3], sectors=[0, 1])
+            operator, qubits=[2, 3], sectors=[0, 1])
         error = projection_error(operator, qubits=[2, 3], sectors=[0, 1])
         self.assertEqual(count_qubits(new_operator), 2)
         self.assertEqual(error, 0)
@@ -67,7 +105,7 @@ class UnitaryRotationsTest(unittest.TestCase):
         qop += QubitOperator('Z0 Z1', 1)
         rot_op = QubitOperator('Z1', 1)
 
-        rotated_qop = rotate_qubit_by_pauli(qop, rot_op, numpy.pi/4)
+        rotated_qop = rotate_qubit_by_pauli(qop, rot_op, numpy.pi / 4)
         comp_op = QubitOperator('Z0 Z1', 1)
         comp_op += QubitOperator('X0 Y1', 1)
         self.assertEqual(comp_op, rotated_qop)
@@ -80,6 +118,6 @@ class UnitaryRotationsTest(unittest.TestCase):
         rot_op2 = QubitOperator('Z1', 1)
         ferm_op = FermionOperator('1^ 2', 1)
         with self.assertRaises(TypeError):
-            rotate_qubit_by_pauli(qop, rot_op, numpy.pi/4)
+            rotate_qubit_by_pauli(qop, rot_op, numpy.pi / 4)
         with self.assertRaises(TypeError):
-            rotate_qubit_by_pauli(ferm_op, rot_op2, numpy.pi/4)
+            rotate_qubit_by_pauli(ferm_op, rot_op2, numpy.pi / 4)