Skip to content

Commit

Permalink
Construct multi-qubit Pauli terms and sums from strings (#984)
Browse files Browse the repository at this point in the history
  • Loading branch information
jlbosse authored and karalekas committed Sep 20, 2019
1 parent 984b0c5 commit 9bdbd71
Show file tree
Hide file tree
Showing 4 changed files with 89 additions and 10 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ Changelog
and a [Feature Request Template](.github/ISSUE_TEMPLATE/FEATURE_REQUEST.md),
which contain sections to fill out when filing a bug or suggesting an enhancement
(@karalekas, gh-985, gh-986).
- `PauliSum` objects can now be constructed from strings via
`from_compact_str()` and `PauliTerm.from_compact_str()` supports multi-qubit
strings (@jlbosse, gh-984).

### Improvements and Changes

Expand Down
1 change: 1 addition & 0 deletions docs/source/apidocs/pauli.rst
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ Classes
~PauliSum.get_qubits
~PauliSum.simplify
~PauliSum.get_programs
~PauliSum.from_compact_str


.. autoclass:: pyquil.paulis.PauliTerm
Expand Down
57 changes: 47 additions & 10 deletions pyquil/paulis.py
Original file line number Diff line number Diff line change
Expand Up @@ -358,23 +358,38 @@ def from_list(cls, terms_list, coefficient=1.0):
def from_compact_str(cls, str_pauli_term):
"""Construct a PauliTerm from the result of str(pauli_term)
"""
coef_str, str_pauli_term = str_pauli_term.split('*')
# split into str_coef, str_op at first '*'' outside parenthesis
try:
coef = int(coef_str)
str_coef, str_op = re.split(r'\*(?![^(]*\))', str_pauli_term,
maxsplit=1)
except ValueError:
raise ValueError("Could not separate the pauli string into "
f"coefficient and operator. {str_pauli_term} does"
" not match <coefficient>*<operator>")

# parse the coefficient into either a float or complex
str_coef = str_coef.replace(' ', '')
try:
coef = float(str_coef)
except ValueError:
try:
coef = float(coef_str)
coef = complex(str_coef)
except ValueError:
coef = complex(coef_str)
raise ValueError("Could not parse the coefficient "
f"{str_coef}")

op = sI() * coef
if str_pauli_term == 'I':
if str_op == 'I':
return op
ma = re.fullmatch(r'(([XYZ])(\d+))+', str_pauli_term)
if ma is None:
raise ValueError(f"Could not parse pauli string {str_pauli_term}")
for ma in re.finditer(r'([XYZ])(\d+)', str_pauli_term):
op *= cls(ma.group(1), int(ma.group(2)))

# parse the operator
str_op = re.sub(r'\*', '', str_op)
if not re.match(r'^(([XYZ])(\d+))+$', str_op):
raise ValueError(f"Could not parse operator string {str_op}. "
r"It should match ^(([XYZ])(\d+))+$")

for factor in re.finditer(r'([XYZ])(\d+)', str_op):
op *= cls(factor.group(1), int(factor.group(2)))

return op

Expand Down Expand Up @@ -678,6 +693,28 @@ def get_programs(self):
coefficients = np.array([term.coefficient for term in self.terms])
return programs, coefficients

def compact_str(self):
"""A string representation of the PauliSum that is more compact than ``str(pauli_sum)``
>>> pauli_sum = 2.0 * sX(1)* sZ(2) + 1.5 * sY(2)
>>> str(pauli_sum)
>>> '2.0*X1*X2 + 1.5*Y2'
>>> pauli_sum.compact_str()
>>> '2.0*X1X2+1.5*Y2'
"""
return "+".join([term.compact_str() for term in self.terms])

@classmethod
def from_compact_str(cls, str_pauli_sum):
"""Construct a PauliSum from the result of str(pauli_sum)
"""
# split str_pauli_sum only at "+" outside of parenthesis to allow
# e.g. "0.5*X0 + (0.5+0j)*Z2"
str_terms = re.split(r'\+(?![^(]*\))', str_pauli_sum)
str_terms = [s.strip() for s in str_terms]
terms = [PauliTerm.from_compact_str(term) for term in str_terms]
return cls(terms).simplify()


def simplify_pauli_sum(pauli_sum):
"""Simplify the sum of Pauli operators according to Pauli algebra rules."""
Expand Down
38 changes: 38 additions & 0 deletions pyquil/tests/test_paulis.py
Original file line number Diff line number Diff line change
Expand Up @@ -677,3 +677,41 @@ def test_identity_no_qubit():
def test_qubit_validation():
with pytest.raises(ValueError):
op = sX(None)


def test_pauli_term_from_str():
# tests that should _not_ fail are in test_pauli_sum_from_str
with pytest.raises(ValueError):
PauliTerm.from_compact_str("X0")
with pytest.raises(ValueError):
PauliTerm.from_compact_str("10")
with pytest.raises(ValueError):
PauliTerm.from_compact_str("1.0X0")
with pytest.raises(ValueError):
PauliTerm.from_compact_str("(1.0+9i)*X0")
with pytest.raises(ValueError):
PauliTerm.from_compact_str("(1.0+0j)*A0")


def test_pauli_sum_from_str():
# this also tests PauliTerm.from_compact_str() since it gets called
Sum = (1.5 + .5j) * sX(0) * sZ(2) + 0.7 * sZ(1)
another_str = "(1.5 + 0.5j)*X0*Z2+.7*Z1"
assert PauliSum.from_compact_str(str(Sum)) == Sum
assert PauliSum.from_compact_str(Sum.compact_str()) == Sum
assert PauliSum.from_compact_str(another_str) == Sum

# test sums of length one
Sum = PauliSum([1 * sY(0) * sY(1)])
the_str = "1*Y0*Y1"
assert PauliSum.from_compact_str(the_str) == Sum

# test sums containing the identity
Sum = (1.5 + .5j) * sX(0) * sZ(2) + 0.7 * sI(1)
the_str = "(1.5 + 0.5j)*X0*Z2+.7*I"
assert PauliSum.from_compact_str(the_str) == Sum

# test the simplification (both in sums and products)
Sum = PauliSum([2 * sY(1)])
the_str = "1*Y0*X0 + (0+1j)*Z0 + 2*Y1"
assert PauliSum.from_compact_str(the_str) == Sum

0 comments on commit 9bdbd71

Please sign in to comment.