Repository: google/TensorNetwork

Issue:
measure_two_body_correlator causing UnboundLocal Error
See below for a MWE. From inspecting the source code, I think the issue is due to the fact that the variable ``ls`` is only created if there are valid ``left_sites``, but it is implicitly assumed to exist later on in the function body.

The docstring is a big ambiguous, so I hope I'm passing the arguments as intended.
```
import tensornetwork as tn
import numpy as np

tensor = np.array([1.0, 0]).reshape([1,2,1])
nodes = [tn.Node(tensor) for _ in range(3)]
mps = tn.matrixproductstates.finite_mps.FiniteMPS(nodes)
op = np.diag([0.5, -0.5])

mps.measure_two_body_correlator(op, [op], 0, [1])
```


Discussion:
@mganahl can you take a look into this?
thanks @co9olguy for posting the issue. I'll have a look!

Resolving patch:
diff --git a/tensornetwork/matrixproductstates/base_mps.py b/tensornetwork/matrixproductstates/base_mps.py
--- a/tensornetwork/matrixproductstates/base_mps.py
+++ b/tensornetwork/matrixproductstates/base_mps.py
@@ -273,15 +273,14 @@ def measure_two_body_correlator(self, op1: Union[BaseNode, Tensor],
     """
     Compute the correlator 
     :math:`\\langle` `op1[site1], op2[s]`:math:`\\rangle`
-    between `site1` and all sites `s` in `sites2`. if `s==site1`, 
-    `op2[s]` will be applied first
+    between `site1` and all sites `s` in `sites2`. If `s == site1`, 
+    `op2[s]` will be applied first.
 
     Args:
-      op1: Tensor of rank 2; the local operator at `site1`
-      op2: List of tensors of rank 2; the local operators 
-        at `sites2`.
+      op1: Tensor of rank 2; the local operator at `site1`.
+      op2: Tensor of rank 2; the local operator at `sites2`.
       site1: The site where `op1`  acts
-      sites2: Sites where operators `op2` act.
+      sites2: Sites where operator `op2` acts.
     Returns:
       List: Correlator :math:`\\langle` `op1[site1], op2[s]`:math:`\\rangle`
         for `s` :math:`\\in` `sites2`.
@@ -297,26 +296,25 @@ def measure_two_body_correlator(self, op1: Union[BaseNode, Tensor],
 
     # we break the computation into two parts:
     # first we get all correlators <op2(site2) op1(site1)> with site2 < site1
-    # then all correlators <op1(site1) op2(site2)> with site1 >= site1
+    # then all correlators <op1(site1) op2(site2)> with site2 >= site1
 
     # get all sites smaller than site1
-    left_sites = sorted(sites2[sites2 < site1])
+    left_sites = np.sort(sites2[sites2 < site1])
     # get all sites larger than site1
-    right_sites = sorted(sites2[sites2 > site1])
+    right_sites = np.sort(sites2[sites2 > site1])
 
     # compute all neccessary right reduced
     # density matrices in one go. This is
     # more efficient than calling right_envs
     # for each site individually
-    if right_sites:
-      right_sites_mod = list({n % N for n in right_sites})
-      rs = self.right_envs([site1] + right_sites_mod)
-    c = []
-    if left_sites:
+    rs = self.right_envs(
+        np.append(site1, np.mod(right_sites, N)).astype(np.int64))
+    ls = self.left_envs(
+        np.append(np.mod(left_sites, N), site1).astype(np.int64))
 
-      left_sites_mod = list({n % N for n in left_sites})
+    c = []
+    if len(left_sites) > 0:
 
-      ls = self.left_envs(left_sites_mod + [site1])
       A = Node(self.nodes[site1], backend=self.backend)
       O1 = Node(op1, backend=self.backend)
       conj_A = conj(A)
@@ -382,8 +380,7 @@ def measure_two_body_correlator(self, op1: Union[BaseNode, Tensor],
       c.append(res.tensor)
 
     # compute <op1(site1) op2(site2)> for site1 < site2
-    right_sites = sorted(sites2[sites2 > site1])
-    if right_sites:
+    if len(right_sites) > 0:
       A = Node(self.nodes[site1], backend=self.backend)
       conj_A = conj(A)
       L = ls[site1]
@@ -407,7 +404,6 @@ def measure_two_body_correlator(self, op1: Union[BaseNode, Tensor],
       # |  op1(site1)    op2(site2)|
       # |   |             |        |
       #  ---A--........-- A*-------
-
       for n in range(site1 + 1, n2 + 1):
         if n in right_sites:
           R = rs[n % N]
diff --git a/tensornetwork/matrixproductstates/finite_mps.py b/tensornetwork/matrixproductstates/finite_mps.py
--- a/tensornetwork/matrixproductstates/finite_mps.py
+++ b/tensornetwork/matrixproductstates/finite_mps.py
@@ -160,11 +160,11 @@ def left_envs(self, sites: Sequence[int]) -> Dict:
       `dict` mapping `int` to `Tensor`: The left-reduced density matrices
         at each  site in `sites`.
     """
-    if not sites:
+    sites = np.array(sites)  #enable logical indexing
+    if len(sites) == 0:
       return {}
 
-    n2 = max(sites)
-    sites = np.array(sites)  #enable logical indexing
+    n2 = np.max(sites)
 
     #check if all elements of `sites` are within allowed range
     if not np.all(sites <= len(self)):
@@ -227,16 +227,16 @@ def right_envs(self, sites: Sequence[int]) -> Dict:
       `dict` mapping `int` to `Tensor`: The right-reduced density matrices
         at each  site in `sites`.
     """
-    if not sites:
+    sites = np.array(sites)
+    if len(sites) == 0:
       return {}
 
-    n1 = min(sites)
-    sites = np.array(sites)
+    n1 = np.min(sites)
     #check if all elements of `sites` are within allowed range
-    if not np.all(np.array(sites) < len(self)):
+    if not np.all(sites < len(self)):
       raise ValueError('all elements of `sites` have to be < N = {}'.format(
           len(self)))
-    if not np.all(np.array(sites) >= -1):
+    if not np.all(sites >= -1):
       raise ValueError('all elements of `sites` have to be >= -1')
 
     # right-reduced density matrices to the right of `center_position`


Test patch:
diff --git a/tensornetwork/matrixproductstates/finite_mps_test.py b/tensornetwork/matrixproductstates/finite_mps_test.py
--- a/tensornetwork/matrixproductstates/finite_mps_test.py
+++ b/tensornetwork/matrixproductstates/finite_mps_test.py
@@ -92,7 +92,8 @@ def test_local_measurement_finite_mps(backend_dtype_values):
   np.testing.assert_allclose(result_2, np.ones(N) * 0.5)
 
 
-def test_correlation_measurement_finite_mps(backend_dtype_values):
+@pytest.mark.parametrize("N1", [0, 5, 9])
+def test_correlation_measurement_finite_mps(backend_dtype_values, N1):
   backend = backend_dtype_values[0]
   dtype = backend_dtype_values[1]
 
@@ -113,12 +114,10 @@ def test_correlation_measurement_finite_mps(backend_dtype_values):
   mps_2.position(0)
 
   sz = np.diag([0.5, -0.5]).astype(dtype)
-  result_1 = np.array(
-      mps_1.measure_two_body_correlator(sz, sz, N // 2, range(N)))
-  result_2 = np.array(
-      mps_2.measure_two_body_correlator(sz, sz, N // 2, range(N)))
+  result_1 = np.array(mps_1.measure_two_body_correlator(sz, sz, N1, range(N)))
+  result_2 = np.array(mps_2.measure_two_body_correlator(sz, sz, N1, range(N)))
   actual = np.zeros(N)
-  actual[N // 2] = 0.25
+  actual[N1] = 0.25
   np.testing.assert_almost_equal(result_1, actual)
   np.testing.assert_allclose(result_2, np.ones(N) * 0.25)