Skip to content

Commit d2ae1e4

Browse files
authored
BooleanHamiltonianGate implementation (#4705)
* BooleanHamiltonianGate implementation * Replace dict with sequence for order preservation * Remove logic for qudits, add check that only qubits are accepted. * Deprecate boolham op
1 parent 8d65806 commit d2ae1e4

File tree

8 files changed

+155
-14
lines changed

8 files changed

+155
-14
lines changed

cirq-core/cirq/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,7 @@
196196
bit_flip,
197197
BitFlipChannel,
198198
BooleanHamiltonian,
199+
BooleanHamiltonianGate,
199200
CCX,
200201
CCXPowGate,
201202
CCZ,

cirq-core/cirq/json_resolver_cache.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ def _parallel_gate_op(gate, qubits):
5959
'BitFlipChannel': cirq.BitFlipChannel,
6060
'BitstringAccumulator': cirq.work.BitstringAccumulator,
6161
'BooleanHamiltonian': cirq.BooleanHamiltonian,
62+
'BooleanHamiltonianGate': cirq.BooleanHamiltonianGate,
6263
'CCNotPowGate': cirq.CCNotPowGate,
6364
'CCXPowGate': cirq.CCXPowGate,
6465
'CCZPowGate': cirq.CCZPowGate,

cirq-core/cirq/ops/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232

3333
from cirq.ops.boolean_hamiltonian import (
3434
BooleanHamiltonian,
35+
BooleanHamiltonianGate,
3536
)
3637

3738
from cirq.ops.common_channels import (

cirq-core/cirq/ops/boolean_hamiltonian.py

Lines changed: 93 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,19 +22,20 @@
2222
[4] Efficient Quantum Circuits for Diagonal Unitaries Without Ancillas by Jonathan Welch, Daniel
2323
Greenbaum, Sarah Mostame, and Alán Aspuru-Guzik, https://arxiv.org/abs/1306.3991
2424
"""
25-
import itertools
2625
import functools
27-
26+
import itertools
2827
from typing import Any, Dict, Generator, List, Sequence, Tuple
2928

3029
import sympy.parsing.sympy_parser as sympy_parser
3130

3231
import cirq
3332
from cirq import value
33+
from cirq._compat import deprecated_class
3434
from cirq.ops import raw_types
3535
from cirq.ops.linear_combinations import PauliSum, PauliString
3636

3737

38+
@deprecated_class(deadline='v0.15', fix='Use cirq.BooleanHamiltonianGate')
3839
@value.value_equality
3940
class BooleanHamiltonian(raw_types.Operation):
4041
"""An operation that represents a Hamiltonian from a set of Boolean functions."""
@@ -64,7 +65,12 @@ def __init__(
6465
boolean_strs: The list of Sympy-parsable Boolean expressions.
6566
qubit_map: map of string (boolean variable name) to qubit.
6667
theta: The evolution time (angle) for the Hamiltonian
68+
69+
Raises:
70+
ValueError: If the any qubits are not 2D.
6771
"""
72+
if any(q.dimension != 2 for q in qubit_map.values()):
73+
raise ValueError('All qubits must be 2-dimensional.')
6874
self._qubit_map: Dict[str, 'cirq.Qid'] = qubit_map
6975
self._boolean_strs: Sequence[str] = boolean_strs
7076
self._theta: float = theta
@@ -114,6 +120,91 @@ def _decompose_(self):
114120
hamiltonian_polynomial_list, self._qubit_map, self._theta
115121
)
116122

123+
def _has_unitary_(self):
124+
return True
125+
126+
@property
127+
def gate(self) -> 'cirq.Gate':
128+
return BooleanHamiltonianGate(
129+
tuple(self._qubit_map.keys()), self._boolean_strs, self._theta
130+
)
131+
132+
133+
@value.value_equality
134+
class BooleanHamiltonianGate(raw_types.Gate):
135+
"""A gate that represents a Hamiltonian from a set of Boolean functions."""
136+
137+
def __init__(
138+
self,
139+
parameter_names: Sequence[str],
140+
boolean_strs: Sequence[str],
141+
theta: float,
142+
):
143+
"""Builds a BooleanHamiltonianGate.
144+
145+
For each element of a sequence of Boolean expressions, the code first transforms it into a
146+
polynomial of Pauli Zs that represent that particular expression. Then, we sum all the
147+
polynomials, thus making a function that goes from a series to Boolean inputs to an integer
148+
that is the number of Boolean expressions that are true.
149+
150+
For example, if we were using this gate for the unweighted max-cut problem that is typically
151+
used to demonstrate the QAOA algorithm, there would be one Boolean expression per edge. Each
152+
Boolean expression would be true iff the vertices on that are in different cuts (i.e. it's)
153+
an XOR.
154+
155+
Then, we compute exp(-j * theta * polynomial), which is unitary because the polynomial is
156+
Hermitian.
157+
158+
Args:
159+
parameter_names: The names of the inputs to the expressions.
160+
boolean_strs: The list of Sympy-parsable Boolean expressions.
161+
theta: The evolution time (angle) for the Hamiltonian
162+
"""
163+
self._parameter_names: Sequence[str] = parameter_names
164+
self._boolean_strs: Sequence[str] = boolean_strs
165+
self._theta: float = theta
166+
167+
def _qid_shape_(self) -> Tuple[int, ...]:
168+
return (2,) * len(self._parameter_names)
169+
170+
def _value_equality_values_(self) -> Any:
171+
return self._parameter_names, self._boolean_strs, self._theta
172+
173+
def _json_dict_(self) -> Dict[str, Any]:
174+
return {
175+
'cirq_type': self.__class__.__name__,
176+
'parameter_names': self._parameter_names,
177+
'boolean_strs': self._boolean_strs,
178+
'theta': self._theta,
179+
}
180+
181+
@classmethod
182+
def _from_json_dict_(
183+
cls, parameter_names, boolean_strs, theta, **kwargs
184+
) -> 'cirq.BooleanHamiltonianGate':
185+
return cls(parameter_names, boolean_strs, theta)
186+
187+
def _decompose_(self, qubits: Sequence['cirq.Qid']) -> 'cirq.OP_TREE':
188+
qubit_map = dict(zip(self._parameter_names, qubits))
189+
boolean_exprs = [sympy_parser.parse_expr(boolean_str) for boolean_str in self._boolean_strs]
190+
hamiltonian_polynomial_list = [
191+
PauliSum.from_boolean_expression(boolean_expr, qubit_map)
192+
for boolean_expr in boolean_exprs
193+
]
194+
195+
return _get_gates_from_hamiltonians(hamiltonian_polynomial_list, qubit_map, self._theta)
196+
197+
def _has_unitary_(self) -> bool:
198+
return True
199+
200+
def __repr__(self) -> str:
201+
return (
202+
f'cirq.BooleanHamiltonianGate('
203+
f'parameter_names={self._parameter_names!r}, '
204+
f'boolean_strs={self._boolean_strs!r}, '
205+
f'theta={self._theta!r})'
206+
)
207+
117208

118209
def _gray_code_comparator(k1: Tuple[int, ...], k2: Tuple[int, ...], flip: bool = False) -> int:
119210
"""Compares two Gray-encoded binary numbers.

cirq-core/cirq/ops/boolean_hamiltonian_test.py

Lines changed: 49 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,8 @@
5353
'(x2 | x1) ^ x0',
5454
],
5555
)
56-
def test_circuit(boolean_str):
56+
@pytest.mark.parametrize('transform', [lambda op: op, lambda op: op.gate.on(*op.qubits)])
57+
def test_circuit(boolean_str, transform):
5758
boolean_expr = sympy_parser.parse_expr(boolean_str)
5859
var_names = cirq.parameter_names(boolean_expr)
5960
qubits = [cirq.NamedQubit(name) for name in var_names]
@@ -72,13 +73,14 @@ def test_circuit(boolean_str):
7273
circuit = cirq.Circuit()
7374
circuit.append(cirq.H.on_each(*qubits))
7475

75-
hamiltonian_gate = cirq.BooleanHamiltonian(
76-
{q.name: q for q in qubits}, [boolean_str], 0.1 * math.pi
77-
)
76+
with cirq.testing.assert_deprecated('Use cirq.BooleanHamiltonianGate', deadline='v0.15'):
77+
hamiltonian_gate = cirq.BooleanHamiltonian(
78+
{q.name: q for q in qubits}, [boolean_str], 0.1 * math.pi
79+
)
7880

7981
assert hamiltonian_gate.num_qubits() == n
8082

81-
circuit.append(hamiltonian_gate)
83+
circuit.append(transform(hamiltonian_gate))
8284

8385
phi = cirq.Simulator().simulate(circuit, qubit_order=qubits, initial_state=0).state_vector()
8486
actual = np.arctan2(phi.real, phi.imag) - math.pi / 2.0 > 0.0
@@ -89,18 +91,53 @@ def test_circuit(boolean_str):
8991

9092
def test_with_custom_names():
9193
q0, q1, q2, q3 = cirq.LineQubit.range(4)
92-
original_op = cirq.BooleanHamiltonian(
93-
{'a': q0, 'b': q1},
94+
with cirq.testing.assert_deprecated(
95+
'Use cirq.BooleanHamiltonianGate', deadline='v0.15', count=3
96+
):
97+
original_op = cirq.BooleanHamiltonian(
98+
{'a': q0, 'b': q1},
99+
['a'],
100+
0.1,
101+
)
102+
assert cirq.decompose(original_op) == [cirq.Rz(rads=-0.05).on(q0)]
103+
104+
renamed_op = original_op.with_qubits(q2, q3)
105+
assert cirq.decompose(renamed_op) == [cirq.Rz(rads=-0.05).on(q2)]
106+
107+
with pytest.raises(ValueError, match='Length of replacement qubits must be the same'):
108+
original_op.with_qubits(q2)
109+
110+
with pytest.raises(ValueError, match='All qubits must be 2-dimensional'):
111+
original_op.with_qubits(q0, cirq.LineQid(1, 3))
112+
113+
114+
def test_gate_with_custom_names():
115+
q0, q1, q2, q3 = cirq.LineQubit.range(4)
116+
gate = cirq.BooleanHamiltonianGate(
117+
['a', 'b'],
94118
['a'],
95119
0.1,
96120
)
97-
assert cirq.decompose(original_op) == [cirq.Rz(rads=-0.05).on(q0)]
121+
assert cirq.decompose(gate.on(q0, q1)) == [cirq.Rz(rads=-0.05).on(q0)]
122+
assert cirq.decompose_once_with_qubits(gate, (q0, q1)) == [cirq.Rz(rads=-0.05).on(q0)]
123+
assert cirq.decompose(gate.on(q2, q3)) == [cirq.Rz(rads=-0.05).on(q2)]
124+
assert cirq.decompose_once_with_qubits(gate, (q2, q3)) == [cirq.Rz(rads=-0.05).on(q2)]
125+
126+
with pytest.raises(ValueError, match='Wrong number of qubits'):
127+
gate.on(q2)
128+
with pytest.raises(ValueError, match='Wrong shape of qids'):
129+
gate.on(q0, cirq.LineQid(1, 3))
98130

99-
renamed_op = original_op.with_qubits(q2, q3)
100-
assert cirq.decompose(renamed_op) == [cirq.Rz(rads=-0.05).on(q2)]
101131

102-
with pytest.raises(ValueError, match='Length of replacement qubits must be the same'):
103-
original_op.with_qubits(q2)
132+
def test_gate_consistent():
133+
gate = cirq.BooleanHamiltonianGate(
134+
['a', 'b'],
135+
['a'],
136+
0.1,
137+
)
138+
op = gate.on(*cirq.LineQubit.range(2))
139+
cirq.testing.assert_implements_consistent_protocols(gate)
140+
cirq.testing.assert_implements_consistent_protocols(op)
104141

105142

106143
@pytest.mark.parametrize(
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
[
2+
{
3+
"cirq_type": "BooleanHamiltonianGate",
4+
"parameter_names": ["q0", "q1"],
5+
"boolean_strs": ["q0"],
6+
"theta": 0.20160913
7+
}
8+
]
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
[cirq.BooleanHamiltonianGate(parameter_names=['q0', 'q1'], boolean_strs=['q0'], theta=0.20160913)]

cirq-core/cirq/protocols/json_test_data/spec.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,7 @@
190190
],
191191
deprecated={
192192
'GlobalPhaseOperation': 'v0.16',
193+
'BooleanHamiltonian': 'v0.15',
193194
'SymmetricalQidPair': 'v0.15',
194195
},
195196
tested_elsewhere=[

0 commit comments

Comments
 (0)