Skip to content

Commit

Permalink
Add an option to build a PauliSum from a Sympy Boolean expression (#4282
Browse files Browse the repository at this point in the history
)

As part of the the review #3989 we could use the ability to build a PauliSum from a Boolean expression. This PR makes available that function (smaller change).
  • Loading branch information
tonybruguier committed Jul 9, 2021
1 parent b1a35bf commit ada1b09
Show file tree
Hide file tree
Showing 3 changed files with 115 additions and 1 deletion.
54 changes: 54 additions & 0 deletions cirq-core/cirq/ops/linear_combinations.py
Expand Up @@ -14,6 +14,7 @@
from collections import defaultdict
from typing import (
AbstractSet,
Dict,
Iterable,
Mapping,
Optional,
Expand All @@ -27,6 +28,9 @@
import numbers

import numpy as np
from sympy.logic.boolalg import And, Not, Or, Xor
from sympy.core.expr import Expr
from sympy.core.symbol import Symbol

from cirq import linalg, protocols, qis, value
from cirq._doc import document
Expand Down Expand Up @@ -398,6 +402,56 @@ def from_pauli_strings(cls, terms: Union[PauliString, List[PauliString]]) -> 'Pa
termdict[key] += pstring.coefficient
return cls(linear_dict=value.LinearDict(termdict))

@classmethod
def from_boolean_expression(
cls, boolean_expr: Expr, qubit_map: Dict[str, 'cirq.Qid']
) -> 'PauliSum':
"""Builds the Hamiltonian representation of a Boolean expression.
This is based on "On the representation of Boolean and real functions as Hamiltonians for
quantum computing" by Stuart Hadfield, https://arxiv.org/abs/1804.09130
Args:
boolean_expr: A Sympy expression containing symbols and Boolean operations
qubit_map: map of string (boolean variable name) to qubit.
Return:
The PauliString that represents the Boolean expression.
"""
if isinstance(boolean_expr, Symbol):
# In table 1, the entry for 'x' is '1/2.I - 1/2.Z'
return cls.from_pauli_strings(
[
PauliString({}, 0.5),
PauliString({qubit_map[boolean_expr.name]: pauli_gates.Z}, -0.5),
]
)

if isinstance(boolean_expr, (And, Not, Or, Xor)):
sub_pauli_sums = [
cls.from_boolean_expression(sub_boolean_expr, qubit_map)
for sub_boolean_expr in boolean_expr.args
]
# We apply the equalities of theorem 1.
if isinstance(boolean_expr, And):
pauli_sum = cls.from_pauli_strings(PauliString({}, 1.0))
for sub_pauli_sum in sub_pauli_sums:
pauli_sum = pauli_sum * sub_pauli_sum
elif isinstance(boolean_expr, Not):
assert len(sub_pauli_sums) == 1
pauli_sum = cls.from_pauli_strings(PauliString({}, 1.0)) - sub_pauli_sums[0]
elif isinstance(boolean_expr, Or):
pauli_sum = cls.from_pauli_strings(PauliString({}, 0.0))
for sub_pauli_sum in sub_pauli_sums:
pauli_sum = pauli_sum + sub_pauli_sum - pauli_sum * sub_pauli_sum
elif isinstance(boolean_expr, Xor):
pauli_sum = cls.from_pauli_strings(PauliString({}, 0.0))
for sub_pauli_sum in sub_pauli_sums:
pauli_sum = pauli_sum + sub_pauli_sum - 2.0 * pauli_sum * sub_pauli_sum
return pauli_sum

raise ValueError(f'Unsupported type: {type(boolean_expr)}')

@property
def qubits(self) -> Tuple[raw_types.Qid, ...]:
qs = {q for k in self._linear_dict.keys() for q, _ in k}
Expand Down
37 changes: 37 additions & 0 deletions cirq-core/cirq/ops/linear_combinations_test.py
Expand Up @@ -18,6 +18,7 @@
import numpy as np
import pytest
import sympy
import sympy.parsing.sympy_parser as sympy_parser

import cirq
import cirq.testing
Expand Down Expand Up @@ -1352,6 +1353,42 @@ def test_pauli_sum_pow():
assert cirq.approx_eq(psum ** 0, identity)


# Using the entries of table 1 of https://arxiv.org/abs/1804.09130 as golden values.
@pytest.mark.parametrize(
'boolean_expr,expected_pauli_sum',
[
('x', ['(-0.5+0j)*Z(x)', '(0.5+0j)*I']),
('~x', ['(0.5+0j)*I', '(0.5+0j)*Z(x)']),
('x0 ^ x1', ['(-0.5+0j)*Z(x0)*Z(x1)', '(0.5+0j)*I']),
(
'x0 & x1',
['(-0.25+0j)*Z(x0)', '(-0.25+0j)*Z(x1)', '(0.25+0j)*I', '(0.25+0j)*Z(x0)*Z(x1)'],
),
(
'x0 | x1',
['(-0.25+0j)*Z(x0)', '(-0.25+0j)*Z(x0)*Z(x1)', '(-0.25+0j)*Z(x1)', '(0.75+0j)*I'],
),
('x0 ^ x1 ^ x2', ['(-0.5+0j)*Z(x0)*Z(x1)*Z(x2)', '(0.5+0j)*I']),
],
)
def test_from_boolean_expression(boolean_expr, expected_pauli_sum):
boolean = sympy_parser.parse_expr(boolean_expr)
qubit_map = {name: cirq.NamedQubit(name) for name in sorted(cirq.parameter_names(boolean))}
actual = cirq.PauliSum.from_boolean_expression(boolean, qubit_map)
# Instead of calling str() directly, first make sure that the items are sorted. This is to make
# the unit test more robut in case Sympy would result in a different parsing order. By sorting
# the individual items, we would have a canonical representation.
actual_items = list(sorted(str(pauli_string) for pauli_string in actual))
assert expected_pauli_sum == actual_items


def test_unsupported_op():
not_a_boolean = sympy_parser.parse_expr('x * x')
qubit_map = {name: cirq.NamedQubit(name) for name in cirq.parameter_names(not_a_boolean)}
with pytest.raises(ValueError, match='Unsupported type'):
cirq.PauliSum.from_boolean_expression(not_a_boolean, qubit_map)


def test_imul_aliasing():
q0, q1, q2 = cirq.LineQubit.range(3)
psum1 = cirq.X(q0) + cirq.Y(q1)
Expand Down
25 changes: 24 additions & 1 deletion docs/operators_and_observables.ipynb
Expand Up @@ -78,7 +78,8 @@
" print(\"installed cirq.\")\n",
" import cirq\n",
" \n",
"import numpy as np"
"import numpy as np\n",
"import sympy.parsing.sympy_parser as sympy_parser"
]
},
{
Expand Down Expand Up @@ -740,6 +741,28 @@
"psum.matrix()"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "ba00a12b9fbd"
},
"source": [
"A Pauli sum can also be constructed from a `Sympy` Boolean expression. This is based on \"On the representation of Boolean and real functions as Hamiltonians for quantum computing\" by Stuart Hadfield (https://arxiv.org/abs/1804.09130)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "8da2814b60f3"
},
"outputs": [],
"source": [
"psum = cirq.PauliSum.from_boolean_expression(\n",
" sympy_parser.parse_expr('x0 ^ x1'),\n",
" {'x0': cirq.NamedQubit('q0'), 'x1': cirq.NamedQubit('q1')})"
]
},
{
"cell_type": "markdown",
"metadata": {
Expand Down

0 comments on commit ada1b09

Please sign in to comment.