Repository: rigetticomputing/pyquil (python)

Issue:
Initialize PauliTerms with more than one Pauli op
In order to produce an PauliTerm  such as X0Y1Z5 one must specify each PauliTerm atomic element individually and multiply them together.  It would be easier to specify the tensor product in one go by passing a tuple of tuples representing the (operator, index) pairs.

Proposed mode of operation
```
pterm = PauliTerm([("X, 0), ("Y", 1), ("Z", 5)])
```
would be equivalent to the current way of constructing X0Y1Z5

```
pterm = PauliTerm("X", 0)*PauliTerm("Y", 1)*PauliTerm("Z", 5)
```

Pull request description:
fixes #120 allowing PauliTerms to be allocated from a list

Patch:
diff --git a/pyquil/paulis.py b/pyquil/paulis.py
index f94a4e8..ee994cf 100644
--- a/pyquil/paulis.py
+++ b/pyquil/paulis.py
@@ -262,6 +262,35 @@ class PauliTerm(object):
         out = "%s*%s" % (self.coefficient, '*'.join(term_strs))
         return out
 
+    @classmethod
+    def from_list(cls, terms_list, coefficient=1.0):
+        """
+        Allocates a Pauli Term from a list of operators and indices. This is more efficient than
+        multiplying together individual terms.
+
+        :param list terms_list: A list of tuples, e.g. [("X", 0), ("Y", 1)]
+        :return: PauliTerm
+        """
+        pterm = PauliTerm("I", 0)
+        assert all([op[0] in PAULI_OPS for op in terms_list])
+
+        indices = [op[1] for op in terms_list]
+        assert all([isinstance(index, integer_types) and index >= 0 for index in indices])
+
+        # this is because from_list doesn't call simplify in order to be more efficient.
+        if len(set(indices)) != len(indices):
+            raise ValueError("Elements of PauliTerm that are allocated using from_list must " \
+                              "be on disjoint qubits. Use PauliTerm multiplication to simplify " \
+                              "terms instead.")
+
+        for op, index in terms_list:
+            if op != "I":
+                pterm._ops[index] = op
+        if not isinstance(coefficient, Number):
+            raise ValueError("coefficient of PauliTerm must be a Number.")
+        pterm.coefficient = complex(coefficient)
+        return pterm
+
 
 # For convenience, a shorthand for several operators.
 ID = PauliTerm("I", 0)


Test patch:
diff --git a/pyquil/tests/test_paulis.py b/pyquil/tests/test_paulis.py
index 8e8c443..6a59f75 100644
--- a/pyquil/tests/test_paulis.py
+++ b/pyquil/tests/test_paulis.py
@@ -547,3 +547,15 @@ def test_sum_equality():
     assert pauli_sum == pauli_sum * 1.0
     with pytest.raises(TypeError):
         assert pauli_sum != 0
+
+
+def test_from_list():
+    terms_list = [("X", 0), ("Y", 1), ("Z", 5)]
+    term = reduce(lambda x, y: x * y, [PauliTerm(*x) for x in terms_list])
+
+    pterm = PauliTerm.from_list(terms_list)
+    assert pterm == term
+
+    with pytest.raises(ValueError):
+        # terms are not on disjoint qubits
+        pterm = PauliTerm.from_list([("X", 0), ("Y", 0)])