Repository: quantumlib/OpenFermion
Pull request #106 (2017-11-11 18:50:22)

Use property decorators in PolynomialTensor and subclasses
- Made `constant` a property of PolynomialTensor, with getter and setter
- Turned some methods of QuadraticHamiltonian into properties


Patch:
diff --git a/src/openfermion/ops/_interaction_operator.py b/src/openfermion/ops/_interaction_operator.py
index 9d08f18..d3ea31c 100644
--- a/src/openfermion/ops/_interaction_operator.py
+++ b/src/openfermion/ops/_interaction_operator.py
@@ -64,7 +64,6 @@ class InteractionOperator(PolynomialTensor):
                 {(): constant,
                  (1, 0): one_body_tensor,
                  (1, 1, 0, 0): two_body_tensor})
-        self.constant = self.n_body_tensors[()]
         self.one_body_tensor = self.n_body_tensors[1, 0]
         self.two_body_tensor = self.n_body_tensors[1, 1, 0, 0]
 
diff --git a/src/openfermion/ops/_polynomial_tensor.py b/src/openfermion/ops/_polynomial_tensor.py
index ffdac71..63797bf 100644
--- a/src/openfermion/ops/_polynomial_tensor.py
+++ b/src/openfermion/ops/_polynomial_tensor.py
@@ -126,6 +126,16 @@ class PolynomialTensor(object):
             key = next(key_iterator)
         self.n_qubits = n_body_tensors[key].shape[0]
 
+    @property
+    def constant(self):
+        """Get the value of the constant term."""
+        return self.n_body_tensors[()]
+
+    @constant.setter
+    def constant(self, value):
+        """Set the value of the constant term."""
+        self.n_body_tensors[()] = value
+
     def __getitem__(self, args):
         """Look up matrix element.
 
diff --git a/src/openfermion/ops/_quadratic_hamiltonian.py b/src/openfermion/ops/_quadratic_hamiltonian.py
index 3d8cf0b..24935fc 100644
--- a/src/openfermion/ops/_quadratic_hamiltonian.py
+++ b/src/openfermion/ops/_quadratic_hamiltonian.py
@@ -84,31 +84,34 @@ class QuadraticHamiltonian(PolynomialTensor):
                      (0, 0): -.5 * antisymmetric_part.conj()})
 
         # Add remaining attributes
-        self.constant = self.n_body_tensors[()]
-        self.chemical_potential = chemical_potential
+        if chemical_potential is None:
+            self.chemical_potential = 0.
+        else:
+            self.chemical_potential = chemical_potential
 
+    @property
     def combined_hermitian_part(self):
         """Return the Hermitian part including the chemical potential."""
-        return self.n_body_tensors[1, 0].copy()
-
-    def hermitian_part(self):
-        """Return the Hermitian part not including the chemical potential."""
-        hermitian_part = self.combined_hermitian_part()
-        if self.chemical_potential:
-            hermitian_part += (self.chemical_potential *
-                               numpy.eye(self.n_qubits))
-        return hermitian_part
+        return self.n_body_tensors[1, 0]
 
+    @property
     def antisymmetric_part(self):
         """Return the antisymmetric part."""
         if (1, 1) in self.n_body_tensors:
-            return 2. * self.n_body_tensors[1, 1].copy()
+            return 2. * self.n_body_tensors[1, 1]
         else:
             return numpy.zeros((self.n_qubits, self.n_qubits), complex)
 
+    @property
+    def hermitian_part(self):
+        """Return the Hermitian part not including the chemical potential."""
+        return (self.combined_hermitian_part +
+                self.chemical_potential * numpy.eye(self.n_qubits))
+
+    @property
     def conserves_particle_number(self):
         """Return whether this Hamiltonian conserves particle number."""
-        discrepancy = numpy.max(numpy.abs(self.antisymmetric_part()))
+        discrepancy = numpy.max(numpy.abs(self.antisymmetric_part))
         return discrepancy < EQ_TOLERANCE
 
     def majorana_form(self):
@@ -126,8 +129,8 @@ class QuadraticHamiltonian(PolynomialTensor):
         and A is a (2 * n_qubits) x (2 * n_qubits) real antisymmetric matrix.
         This function returns the matrix A and the constant.
         """
-        hermitian_part = self.combined_hermitian_part()
-        antisymmetric_part = self.antisymmetric_part()
+        hermitian_part = self.combined_hermitian_part
+        antisymmetric_part = self.antisymmetric_part
 
         # Compute the Majorana matrix using block matrix manipulations
         majorana_matrix = numpy.zeros((2 * self.n_qubits, 2 * self.n_qubits))
diff --git a/src/openfermion/utils/_slater_determinants.py b/src/openfermion/utils/_slater_determinants.py
index 0485665..9d0dd1a 100644
--- a/src/openfermion/utils/_slater_determinants.py
+++ b/src/openfermion/utils/_slater_determinants.py
@@ -51,10 +51,10 @@ def ground_state_preparation_circuit(quadratic_hamiltonian):
     if not isinstance(quadratic_hamiltonian, QuadraticHamiltonian):
         raise ValueError('Input must be an instance of QuadraticHamiltonian.')
 
-    if quadratic_hamiltonian.conserves_particle_number():
+    if quadratic_hamiltonian.conserves_particle_number:
         # The Hamiltonian conserves particle number, so we don't need
         # to use the most general procedure.
-        hermitian_matrix = quadratic_hamiltonian.combined_hermitian_part()
+        hermitian_matrix = quadratic_hamiltonian.combined_hermitian_part
         # Get the unitary rows which represent the ground state
         # Slater determinant
         energies, diagonalizing_unitary = numpy.linalg.eigh(
@@ -103,8 +103,8 @@ def jw_get_quadratic_hamiltonian_ground_state(quadratic_hamiltonian):
     n_qubits = quadratic_hamiltonian.n_qubits
 
     # Compute the ground energy
-    if quadratic_hamiltonian.conserves_particle_number():
-        hermitian_matrix = quadratic_hamiltonian.combined_hermitian_part()
+    if quadratic_hamiltonian.conserves_particle_number:
+        hermitian_matrix = quadratic_hamiltonian.combined_hermitian_part
         energies, diagonalizing_unitary = numpy.linalg.eigh(
                 hermitian_matrix)
         num_negative_energies = numpy.count_nonzero(


Test patch:
diff --git a/src/openfermion/ops/_quadratic_hamiltonian_test.py b/src/openfermion/ops/_quadratic_hamiltonian_test.py
index 321f925..f34c68a 100644
--- a/src/openfermion/ops/_quadratic_hamiltonian_test.py
+++ b/src/openfermion/ops/_quadratic_hamiltonian_test.py
@@ -56,41 +56,41 @@ class QuadraticHamiltoniansTest(unittest.TestCase):
 
     def test_combined_hermitian_part(self):
         """Test getting the combined Hermitian part."""
-        combined_hermitian_part = self.quad_ham_pc.combined_hermitian_part()
+        combined_hermitian_part = self.quad_ham_pc.combined_hermitian_part
         for i in numpy.ndindex(combined_hermitian_part.shape):
             self.assertAlmostEqual(self.hermitian_mat[i],
                                    combined_hermitian_part[i])
 
-        combined_hermitian_part = self.quad_ham_npc.combined_hermitian_part()
+        combined_hermitian_part = self.quad_ham_npc.combined_hermitian_part
         for i in numpy.ndindex(combined_hermitian_part.shape):
             self.assertAlmostEqual(self.combined_hermitian[i],
                                    combined_hermitian_part[i])
 
     def test_hermitian_part(self):
         """Test getting the Hermitian part."""
-        hermitian_part = self.quad_ham_pc.hermitian_part()
+        hermitian_part = self.quad_ham_pc.hermitian_part
         for i in numpy.ndindex(hermitian_part.shape):
             self.assertAlmostEqual(self.hermitian_mat[i], hermitian_part[i])
 
-        hermitian_part = self.quad_ham_npc.hermitian_part()
+        hermitian_part = self.quad_ham_npc.hermitian_part
         for i in numpy.ndindex(hermitian_part.shape):
             self.assertAlmostEqual(self.hermitian_mat[i], hermitian_part[i])
 
     def test_antisymmetric_part(self):
         """Test getting the antisymmetric part."""
-        antisymmetric_part = self.quad_ham_pc.antisymmetric_part()
+        antisymmetric_part = self.quad_ham_pc.antisymmetric_part
         for i in numpy.ndindex(antisymmetric_part.shape):
             self.assertAlmostEqual(0., antisymmetric_part[i])
 
-        antisymmetric_part = self.quad_ham_npc.antisymmetric_part()
+        antisymmetric_part = self.quad_ham_npc.antisymmetric_part
         for i in numpy.ndindex(antisymmetric_part.shape):
             self.assertAlmostEqual(self.antisymmetric_mat[i],
                                    antisymmetric_part[i])
 
     def test_conserves_particle_number(self):
         """Test checking whether Hamiltonian conserves particle number."""
-        self.assertTrue(self.quad_ham_pc.conserves_particle_number())
-        self.assertFalse(self.quad_ham_npc.conserves_particle_number())
+        self.assertTrue(self.quad_ham_pc.conserves_particle_number)
+        self.assertFalse(self.quad_ham_npc.conserves_particle_number)
 
     def test_majorana_form(self):
         """Test getting the Majorana form."""
diff --git a/src/openfermion/utils/_slater_determinants_test.py b/src/openfermion/utils/_slater_determinants_test.py
index d7f9444..29bfa25 100644
--- a/src/openfermion/utils/_slater_determinants_test.py
+++ b/src/openfermion/utils/_slater_determinants_test.py
@@ -389,8 +389,8 @@ class DiagonalizingFermionicUnitaryTest(unittest.TestCase):
     def test_diagonalizes_quadratic_hamiltonian(self):
         """Test that the unitary returned indeed diagonalizes a
         quadratic Hamiltonian."""
-        hermitian_part = self.quad_ham_npc.combined_hermitian_part()
-        antisymmetric_part = self.quad_ham_npc.antisymmetric_part()
+        hermitian_part = self.quad_ham_npc.combined_hermitian_part
+        antisymmetric_part = self.quad_ham_npc.antisymmetric_part
         block_matrix = numpy.zeros((2 * self.n_qubits, 2 * self.n_qubits),
                                    dtype=complex)
         block_matrix[:self.n_qubits, :self.n_qubits] = antisymmetric_part