Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add an option to build a PauliSum from a Sympy Boolean expression #4282

Merged
merged 16 commits into from
Jul 9, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
54 changes: 54 additions & 0 deletions cirq-core/cirq/ops/linear_combinations.py
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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