-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Hamiltonian representation of binary functions
- Loading branch information
1 parent
e0365e4
commit 914f3d5
Showing
2 changed files
with
197 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,139 @@ | ||
import math | ||
from typing import Dict, Tuple | ||
|
||
from sympy.logic.boolalg import And, Not, Or, Xor | ||
from sympy.core.symbol import Symbol | ||
|
||
import cirq | ||
|
||
# References: | ||
# [1] On the representation of Boolean and real functions as Hamil tonians for quantum computing | ||
# by StuartHadfield | ||
# [2] https://www.youtube.com/watch?v=AOKM9BkweVU is a useful intro | ||
# [3] https://github.com/rsln-s/IEEE_QW_2020/blob/master/Slides.pdf | ||
|
||
# TODO(tonybruguier): Hook up this code with the QAOA example so that we can solve generic problems | ||
# instead of just the max-cut example. | ||
|
||
|
||
class HamiltonianList: | ||
"""A container class of Boolean function as equation (2) or [1]""" | ||
|
||
def __init__(self, hamiltonians: Dict[Tuple[int, ...], float]): | ||
self.hamiltonians = {h: w for h, w in hamiltonians.items() if math.fabs(w) > 1e-12} | ||
|
||
def __str__(self): | ||
# For run-to-run identicalness, we sort the keys lexicographically. | ||
return "; ".join( | ||
"%.2f.%s" % (self.hamiltonians[h], ".".join("Z_%d" % (d) for d in h) if h else 'I') | ||
for h in sorted(self.hamiltonians) | ||
) | ||
|
||
def __add__(self, other): | ||
return self._signed_add(other, 1.0) | ||
|
||
def __sub__(self, other): | ||
return self._signed_add(other, -1.0) | ||
|
||
def _signed_add(self, other, sign: float): | ||
hamiltonians = self.hamiltonians.copy() | ||
for h, w in other.hamiltonians.items(): | ||
if h not in hamiltonians: | ||
hamiltonians[h] = sign * w | ||
else: | ||
hamiltonians[h] += sign * w | ||
return HamiltonianList(hamiltonians) | ||
|
||
def __rmul__(self, other: float): | ||
return HamiltonianList({k: other * w for k, w in self.hamiltonians.items()}) | ||
|
||
def __mul__(self, other): | ||
hamiltonians = {} | ||
for h1, w1 in self.hamiltonians.items(): | ||
for h2, w2 in other.hamiltonians.items(): | ||
h = tuple(set(h1).symmetric_difference(h2)) | ||
w = w1 * w2 | ||
if h not in hamiltonians: | ||
hamiltonians[h] = w | ||
else: | ||
hamiltonians[h] += w | ||
return HamiltonianList(hamiltonians) | ||
|
||
@staticmethod | ||
def O(): | ||
return HamiltonianList({}) | ||
|
||
@staticmethod | ||
def I(): | ||
return HamiltonianList({(): 1.0}) | ||
|
||
@staticmethod | ||
def Z(i: int): | ||
return HamiltonianList({(i,): 1.0}) | ||
|
||
|
||
def build_hamiltonian_from_boolean(boolean_expr, name_to_id) -> HamiltonianList: | ||
"""Builds the Hamiltonian representation of Boolean expression as per [1]: | ||
Args: | ||
boolean_expr: A Sympy expression containing symbols and Boolean operations | ||
name_to_id: A dictionary from symbol name to an integer, typically built by calling | ||
get_name_to_id(). | ||
Return: | ||
The HamiltonianList that represents the Boolean expression. | ||
""" | ||
|
||
if isinstance(boolean_expr, (And, Not, Or, Xor)): | ||
sub_hamiltonians = [ | ||
build_hamiltonian_from_boolean(sub_boolean_expr, name_to_id) | ||
for sub_boolean_expr in boolean_expr.args | ||
] | ||
# We apply the equalities of theorem 1 of [1]. | ||
if isinstance(boolean_expr, And): | ||
hamiltonian = HamiltonianList.I() | ||
for sub_hamiltonian in sub_hamiltonians: | ||
hamiltonian = hamiltonian * sub_hamiltonian | ||
elif isinstance(boolean_expr, Not): | ||
assert len(sub_hamiltonians) == 1 | ||
hamiltonian = HamiltonianList.I() - sub_hamiltonians[0] | ||
elif isinstance(boolean_expr, Or): | ||
hamiltonian = HamiltonianList.O() | ||
for sub_hamiltonian in sub_hamiltonians: | ||
hamiltonian = hamiltonian + sub_hamiltonian - hamiltonian * sub_hamiltonian | ||
elif isinstance(boolean_expr, Xor): | ||
hamiltonian = HamiltonianList.O() | ||
for sub_hamiltonian in sub_hamiltonians: | ||
hamiltonian = hamiltonian + sub_hamiltonian - 2.0 * hamiltonian * sub_hamiltonian | ||
return hamiltonian | ||
elif isinstance(boolean_expr, Symbol): | ||
# Table 1 of [1], entry for 'x' is '1/2.I - 1/2.Z' | ||
i = name_to_id[boolean_expr.name] | ||
return 0.5 * HamiltonianList.I() - 0.5 * HamiltonianList.Z(i) | ||
else: | ||
raise ValueError(f'Unsupported type: {type(boolean_expr)}') | ||
|
||
|
||
def get_name_to_id(boolean_expr): | ||
# For run-to-run identicalness, we sort the symbol name lexicographically. | ||
symbol_names = sorted(symbol.name for symbol in boolean_expr.free_symbols) | ||
return {symbol_name: i for i, symbol_name in enumerate(symbol_names)} | ||
|
||
|
||
def build_circuit_from_hamiltonian(hamiltonian, name_to_id, theta): | ||
qubits = [cirq.NamedQubit(name) for name in name_to_id.keys()] | ||
circuit = cirq.Circuit() | ||
|
||
circuit.append(cirq.H.on_each(*qubits)) | ||
|
||
for h, w in hamiltonian.hamiltonians.items(): | ||
for i in range(1, len(h)): | ||
circuit.append(cirq.CNOT(qubits[h[i]], qubits[h[0]])) | ||
|
||
if len(h) >= 1: | ||
circuit.append(cirq.Rz(rads=(theta * w)).on(qubits[h[0]])) | ||
|
||
for i in range(1, len(h)): | ||
circuit.append(cirq.CNOT(qubits[h[i]], qubits[h[0]])) | ||
|
||
return circuit, qubits |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
import math | ||
|
||
import numpy as np | ||
import pytest | ||
from sympy.parsing.sympy_parser import parse_expr | ||
|
||
import cirq | ||
import examples.hamiltonian_representation as hr | ||
|
||
# These are some of the entries of table 1. | ||
@pytest.mark.parametrize( | ||
'boolean_expr,hamiltonian', | ||
[ | ||
('x', '0.50.I; -0.50.Z_0'), | ||
('~x', '0.50.I; 0.50.Z_0'), | ||
('x0 ^ x1', '0.50.I; -0.50.Z_0.Z_1'), | ||
('x0 & x1', '0.25.I; -0.25.Z_0; 0.25.Z_0.Z_1; -0.25.Z_1'), | ||
('x0 | x1', '0.75.I; -0.25.Z_0; -0.25.Z_0.Z_1; -0.25.Z_1'), | ||
('x0 ^ x1 ^ x2', '0.50.I; -0.50.Z_0.Z_1.Z_2'), | ||
], | ||
) | ||
def test_build_hamiltonian_from_boolean(boolean_expr, hamiltonian): | ||
boolean = parse_expr(boolean_expr) | ||
name_to_id = hr.get_name_to_id(boolean) | ||
actual = hr.build_hamiltonian_from_boolean(boolean, name_to_id) | ||
assert hamiltonian == str(actual) | ||
|
||
|
||
def test_unsupported_op(): | ||
not_a_boolean = parse_expr('x * x') | ||
name_to_id = hr.get_name_to_id(not_a_boolean) | ||
with pytest.raises(ValueError, match='Unsupported type'): | ||
hr.build_hamiltonian_from_boolean(not_a_boolean, name_to_id) | ||
|
||
|
||
@pytest.mark.parametrize( | ||
'boolean_expr, expected', | ||
[ | ||
('x', [False, True]), | ||
('~x', [True, False]), | ||
('x0 ^ x1', [False, True, True, False]), | ||
('x0 & x1', [False, False, False, True]), | ||
('x0 | x1', [False, True, True, True]), | ||
('x0 & ~x1 & x2', [False, False, False, False, False, True, False, False]), | ||
], | ||
) | ||
def test_circuit(boolean_expr, expected): | ||
boolean = parse_expr(boolean_expr) | ||
name_to_id = hr.get_name_to_id(boolean) | ||
hamiltonian = hr.build_hamiltonian_from_boolean(boolean, name_to_id) | ||
|
||
theta = 0.1 * math.pi | ||
circuit, qubits = hr.build_circuit_from_hamiltonian(hamiltonian, name_to_id, theta) | ||
|
||
phi = cirq.Simulator().simulate(circuit, qubit_order=qubits, initial_state=0).state_vector() | ||
actual = np.arctan2(phi.real, phi.imag) - math.pi / 2.0 > 0.0 | ||
|
||
np.testing.assert_array_equal(actual, expected) |