From 83f007675cdff4290dfa26dbf9ff578591beb612 Mon Sep 17 00:00:00 2001 From: daxfohl Date: Tue, 23 Nov 2021 14:24:00 -0800 Subject: [PATCH 01/10] BooleanHamiltonianGate implementation --- cirq-core/cirq/__init__.py | 1 + cirq-core/cirq/json_resolver_cache.py | 1 + cirq-core/cirq/ops/__init__.py | 1 + cirq-core/cirq/ops/boolean_hamiltonian.py | 85 +++++++++++++- .../cirq/ops/boolean_hamiltonian_test.py | 105 ++++++++++++++++++ .../BooleanHamiltonianGate.json | 12 ++ .../BooleanHamiltonianGate.repr | 1 + 7 files changed, 205 insertions(+), 1 deletion(-) create mode 100644 cirq-core/cirq/protocols/json_test_data/BooleanHamiltonianGate.json create mode 100644 cirq-core/cirq/protocols/json_test_data/BooleanHamiltonianGate.repr diff --git a/cirq-core/cirq/__init__.py b/cirq-core/cirq/__init__.py index 2923d10979f..49c0fe09d29 100644 --- a/cirq-core/cirq/__init__.py +++ b/cirq-core/cirq/__init__.py @@ -184,6 +184,7 @@ bit_flip, BitFlipChannel, BooleanHamiltonian, + BooleanHamiltonianGate, CCX, CCXPowGate, CCZ, diff --git a/cirq-core/cirq/json_resolver_cache.py b/cirq-core/cirq/json_resolver_cache.py index 1237716c7ef..6773e493281 100644 --- a/cirq-core/cirq/json_resolver_cache.py +++ b/cirq-core/cirq/json_resolver_cache.py @@ -56,6 +56,7 @@ def _parallel_gate_op(gate, qubits): 'BitFlipChannel': cirq.BitFlipChannel, 'BitstringAccumulator': cirq.work.BitstringAccumulator, 'BooleanHamiltonian': cirq.BooleanHamiltonian, + 'BooleanHamiltonianGate': cirq.BooleanHamiltonianGate, 'ProductState': cirq.ProductState, 'CCNotPowGate': cirq.CCNotPowGate, 'CCXPowGate': cirq.CCXPowGate, diff --git a/cirq-core/cirq/ops/__init__.py b/cirq-core/cirq/ops/__init__.py index 0b4041e5641..65fadfe8472 100644 --- a/cirq-core/cirq/ops/__init__.py +++ b/cirq-core/cirq/ops/__init__.py @@ -31,6 +31,7 @@ from cirq.ops.boolean_hamiltonian import ( BooleanHamiltonian, + BooleanHamiltonianGate, ) from cirq.ops.common_channels import ( diff --git a/cirq-core/cirq/ops/boolean_hamiltonian.py b/cirq-core/cirq/ops/boolean_hamiltonian.py index 0be4a2a7241..d19162118ed 100644 --- a/cirq-core/cirq/ops/boolean_hamiltonian.py +++ b/cirq-core/cirq/ops/boolean_hamiltonian.py @@ -31,8 +31,9 @@ import cirq from cirq import value -from cirq.ops import raw_types +from cirq.ops import gate_operation, raw_types from cirq.ops.linear_combinations import PauliSum, PauliString +from cirq import protocols @value.value_equality @@ -72,6 +73,8 @@ def __init__( def with_qubits(self, *new_qubits: 'cirq.Qid') -> 'BooleanHamiltonian': if len(self._qubit_map) != len(new_qubits): raise ValueError('Length of replacement qubits must be the same') + if protocols.qid_shape(tuple(self._qubit_map.values())) != protocols.qid_shape(new_qubits): + raise ValueError('Dimensions of replacement qubits must be the same') new_qubit_map = { variable_name: new_qubit for variable_name, new_qubit in zip(self._qubit_map, new_qubits) @@ -115,6 +118,86 @@ def _decompose_(self): hamiltonian_polynomial_list, self._qubit_map, self._theta ) + def _has_unitary_(self): + return True + + def __repr__(self): + return ( + f'cirq.BooleanHamiltonian({self._qubit_map!r}, {self._boolean_strs!r}, {self._theta})' + ) + + +@value.value_equality +class BooleanHamiltonianGate(raw_types.Gate): + """An operation that represents a Hamiltonian from a set of Boolean functions.""" + + def __init__( + self, + qubit_map: Dict[str, int], + boolean_strs: Sequence[str], + theta: float, + ): + """Builds a BooleanHamiltonianGate. + + For each element of a sequence of Boolean expressions, the code first transforms it into a + polynomial of Pauli Zs that represent that particular expression. Then, we sum all the + polynomials, thus making a function that goes from a series to Boolean inputs to an integer + that is the number of Boolean expressions that are true. + + For example, if we were using this gate for the unweighted max-cut problem that is typically + used to demonstrate the QAOA algorithm, there would be one Boolean expression per edge. Each + Boolean expression would be true iff the vertices on that are in different cuts (i.e. it's) + an XOR. + + Then, we compute exp(-j * theta * polynomial), which is unitary because the polynomial is + Hermitian. + + Args: + boolean_strs: The list of Sympy-parsable Boolean expressions. + qubit_map: map of string (boolean variable name) to qubit dimension. + theta: The evolution time (angle) for the Hamiltonian + """ + self._qubit_map: Dict[str, int] = qubit_map + self._boolean_strs: Sequence[str] = boolean_strs + self._theta: float = theta + + def _qid_shape_(self): + return tuple(self._qubit_map.values()) + + def on(self, *qubits) -> 'cirq.Operation': + return gate_operation.GateOperation(self, qubits) + + def _value_equality_values_(self): + return self._qubit_map, self._boolean_strs, self._theta + + def _json_dict_(self) -> Dict[str, Any]: + return { + 'cirq_type': self.__class__.__name__, + 'qubit_map': self._qubit_map, + 'boolean_strs': self._boolean_strs, + 'theta': self._theta, + } + + @classmethod + def _from_json_dict_(cls, qubit_map, boolean_strs, theta, **kwargs): + return cls(qubit_map, boolean_strs, theta) + + def _decompose_(self, qubits: Sequence['cirq.Qid']) -> 'cirq.OP_TREE': + qubit_map = dict(zip(self._qubit_map.keys(), qubits)) + boolean_exprs = [sympy_parser.parse_expr(boolean_str) for boolean_str in self._boolean_strs] + hamiltonian_polynomial_list = [ + PauliSum.from_boolean_expression(boolean_expr, qubit_map) + for boolean_expr in boolean_exprs + ] + + return _get_gates_from_hamiltonians(hamiltonian_polynomial_list, qubit_map, self._theta) + + def _has_unitary_(self): + return True + + def __repr__(self): + return f'cirq.BooleanHamiltonianGate({self._qubit_map!r}, {self._boolean_strs!r}, {self._theta})' + def _gray_code_comparator(k1: Tuple[int, ...], k2: Tuple[int, ...], flip: bool = False) -> int: """Compares two Gray-encoded binary numbers. diff --git a/cirq-core/cirq/ops/boolean_hamiltonian_test.py b/cirq-core/cirq/ops/boolean_hamiltonian_test.py index 9671101210e..a84d8486b6b 100644 --- a/cirq-core/cirq/ops/boolean_hamiltonian_test.py +++ b/cirq-core/cirq/ops/boolean_hamiltonian_test.py @@ -87,6 +87,69 @@ def test_circuit(boolean_str): np.testing.assert_array_equal(actual, expected) +@pytest.mark.parametrize( + 'boolean_str', + [ + 'x0', + '~x0', + 'x0 ^ x1', + 'x0 & x1', + 'x0 | x1', + 'x0 & x1 & x2', + 'x0 & x1 & ~x2', + 'x0 & ~x1 & x2', + 'x0 & ~x1 & ~x2', + '~x0 & x1 & x2', + '~x0 & x1 & ~x2', + '~x0 & ~x1 & x2', + '~x0 & ~x1 & ~x2', + 'x0 ^ x1 ^ x2', + 'x0 | (x1 & x2)', + 'x0 & (x1 | x2)', + '(x0 ^ x1 ^ x2) | (x2 ^ x3 ^ x4)', + '(x0 ^ x2 ^ x4) | (x1 ^ x2 ^ x3)', + 'x0 & x1 & (x2 | x3)', + 'x0 & ~x2', + '~x0 & x2', + 'x2 & ~x0', + '~x2 & x0', + '(x2 | x1) ^ x0', + ], +) +def test_gate_circuit(boolean_str): + boolean_expr = sympy_parser.parse_expr(boolean_str) + var_names = cirq.parameter_names(boolean_expr) + qubits = [cirq.NamedQubit(name) for name in var_names] + + # We use Sympy to evaluate the expression: + n = len(var_names) + + expected = [] + for binary_inputs in itertools.product([0, 1], repeat=n): + subed_expr = boolean_expr + for var_name, binary_input in zip(var_names, binary_inputs): + subed_expr = subed_expr.subs(var_name, binary_input) + expected.append(bool(subed_expr)) + + # We build a circuit and look at its output state vector: + circuit = cirq.Circuit() + circuit.append(cirq.H.on_each(*qubits)) + + hamiltonian_gate = cirq.BooleanHamiltonianGate( + {q.name: q.dimension for q in qubits}, [boolean_str], 0.1 * math.pi + ).on(*qubits) + + assert cirq.qid_shape(hamiltonian_gate) == cirq.qid_shape(qubits) + + circuit.append(hamiltonian_gate) + + 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 + + # Compare the two: + np.testing.assert_array_equal(actual, expected) + + def test_with_custom_names(): q0, q1, q2, q3 = cirq.LineQubit.range(4) original_op = cirq.BooleanHamiltonian( @@ -102,6 +165,48 @@ def test_with_custom_names(): with pytest.raises(ValueError, match='Length of replacement qubits must be the same'): original_op.with_qubits(q2) + with pytest.raises(ValueError, match='Dimensions of replacement qubits must be the same'): + original_op.with_qubits(q0, cirq.LineQid(1, 3)) + + +def test_gate_with_custom_names(): + q0, q1, q2, q3 = cirq.LineQubit.range(4) + gate = cirq.BooleanHamiltonianGate( + {'a': 2, 'b': 2}, + ['a'], + 0.1, + ) + assert cirq.decompose(gate.on(q0, q1)) == [cirq.Rz(rads=-0.05).on(q0)] + assert cirq.decompose_once_with_qubits(gate, (q0, q1)) == [cirq.Rz(rads=-0.05).on(q0)] + assert cirq.decompose(gate.on(q2, q3)) == [cirq.Rz(rads=-0.05).on(q2)] + assert cirq.decompose_once_with_qubits(gate, (q2, q3)) == [cirq.Rz(rads=-0.05).on(q2)] + + with pytest.raises(ValueError, match='Wrong number of qubits'): + gate.on(q2) + with pytest.raises(ValueError, match='Wrong shape of qids'): + gate.on(q0, cirq.LineQid(1, 3)) + + +def test_consistent(): + q0, q1 = cirq.LineQubit.range(2) + op = cirq.BooleanHamiltonian( + {'a': q0, 'b': q1}, + ['a'], + 0.1, + ) + cirq.testing.assert_implements_consistent_protocols(op) + + +def test_gate_consistent(): + gate = cirq.BooleanHamiltonianGate( + {'a': 2, 'b': 2}, + ['a'], + 0.1, + ) + op = gate.on(*cirq.LineQubit.range(2)) + cirq.testing.assert_implements_consistent_protocols(gate) + cirq.testing.assert_implements_consistent_protocols(op) + @pytest.mark.parametrize( 'n_bits,expected_hs', diff --git a/cirq-core/cirq/protocols/json_test_data/BooleanHamiltonianGate.json b/cirq-core/cirq/protocols/json_test_data/BooleanHamiltonianGate.json new file mode 100644 index 00000000000..f25b7b91ca1 --- /dev/null +++ b/cirq-core/cirq/protocols/json_test_data/BooleanHamiltonianGate.json @@ -0,0 +1,12 @@ +[ + { + "cirq_type": "BooleanHamiltonianGate", + "qubit_map": { + "q0": 2 + }, + "boolean_strs": [ + "q0" + ], + "theta": 0.20160913 + } +] \ No newline at end of file diff --git a/cirq-core/cirq/protocols/json_test_data/BooleanHamiltonianGate.repr b/cirq-core/cirq/protocols/json_test_data/BooleanHamiltonianGate.repr new file mode 100644 index 00000000000..598c6996684 --- /dev/null +++ b/cirq-core/cirq/protocols/json_test_data/BooleanHamiltonianGate.repr @@ -0,0 +1 @@ +[cirq.BooleanHamiltonianGate({'q0': 2}, ['q0'], 0.20160913)] \ No newline at end of file From 8fc16e3a5e9d1c21cd80b32d49bb0e8b88b8804d Mon Sep 17 00:00:00 2001 From: daxfohl Date: Tue, 23 Nov 2021 14:47:05 -0800 Subject: [PATCH 02/10] Replace dict with sequence for order preservation --- cirq-core/cirq/ops/boolean_hamiltonian.py | 10 +++++----- cirq-core/cirq/ops/boolean_hamiltonian_test.py | 6 +++--- .../json_test_data/BooleanHamiltonianGate.json | 4 +--- .../json_test_data/BooleanHamiltonianGate.repr | 2 +- 4 files changed, 10 insertions(+), 12 deletions(-) diff --git a/cirq-core/cirq/ops/boolean_hamiltonian.py b/cirq-core/cirq/ops/boolean_hamiltonian.py index d19162118ed..52bc541940b 100644 --- a/cirq-core/cirq/ops/boolean_hamiltonian.py +++ b/cirq-core/cirq/ops/boolean_hamiltonian.py @@ -129,11 +129,11 @@ def __repr__(self): @value.value_equality class BooleanHamiltonianGate(raw_types.Gate): - """An operation that represents a Hamiltonian from a set of Boolean functions.""" + """A gate that represents a Hamiltonian from a set of Boolean functions.""" def __init__( self, - qubit_map: Dict[str, int], + qubit_map: Sequence[Tuple[str, int]], boolean_strs: Sequence[str], theta: float, ): @@ -157,12 +157,12 @@ def __init__( qubit_map: map of string (boolean variable name) to qubit dimension. theta: The evolution time (angle) for the Hamiltonian """ - self._qubit_map: Dict[str, int] = qubit_map + self._qubit_map: Sequence[Tuple[str, int]] = qubit_map self._boolean_strs: Sequence[str] = boolean_strs self._theta: float = theta def _qid_shape_(self): - return tuple(self._qubit_map.values()) + return tuple(dim for _, dim in self._qubit_map) def on(self, *qubits) -> 'cirq.Operation': return gate_operation.GateOperation(self, qubits) @@ -183,7 +183,7 @@ def _from_json_dict_(cls, qubit_map, boolean_strs, theta, **kwargs): return cls(qubit_map, boolean_strs, theta) def _decompose_(self, qubits: Sequence['cirq.Qid']) -> 'cirq.OP_TREE': - qubit_map = dict(zip(self._qubit_map.keys(), qubits)) + qubit_map = dict(zip([name for name, _ in self._qubit_map], qubits)) boolean_exprs = [sympy_parser.parse_expr(boolean_str) for boolean_str in self._boolean_strs] hamiltonian_polynomial_list = [ PauliSum.from_boolean_expression(boolean_expr, qubit_map) diff --git a/cirq-core/cirq/ops/boolean_hamiltonian_test.py b/cirq-core/cirq/ops/boolean_hamiltonian_test.py index a84d8486b6b..6212a45338a 100644 --- a/cirq-core/cirq/ops/boolean_hamiltonian_test.py +++ b/cirq-core/cirq/ops/boolean_hamiltonian_test.py @@ -136,7 +136,7 @@ def test_gate_circuit(boolean_str): circuit.append(cirq.H.on_each(*qubits)) hamiltonian_gate = cirq.BooleanHamiltonianGate( - {q.name: q.dimension for q in qubits}, [boolean_str], 0.1 * math.pi + [(q.name, q.dimension) for q in qubits], [boolean_str], 0.1 * math.pi ).on(*qubits) assert cirq.qid_shape(hamiltonian_gate) == cirq.qid_shape(qubits) @@ -172,7 +172,7 @@ def test_with_custom_names(): def test_gate_with_custom_names(): q0, q1, q2, q3 = cirq.LineQubit.range(4) gate = cirq.BooleanHamiltonianGate( - {'a': 2, 'b': 2}, + [('a', 2), ('b', 2)], ['a'], 0.1, ) @@ -199,7 +199,7 @@ def test_consistent(): def test_gate_consistent(): gate = cirq.BooleanHamiltonianGate( - {'a': 2, 'b': 2}, + [('a', 2), ('b', 2)], ['a'], 0.1, ) diff --git a/cirq-core/cirq/protocols/json_test_data/BooleanHamiltonianGate.json b/cirq-core/cirq/protocols/json_test_data/BooleanHamiltonianGate.json index f25b7b91ca1..cb0e44988bc 100644 --- a/cirq-core/cirq/protocols/json_test_data/BooleanHamiltonianGate.json +++ b/cirq-core/cirq/protocols/json_test_data/BooleanHamiltonianGate.json @@ -1,9 +1,7 @@ [ { "cirq_type": "BooleanHamiltonianGate", - "qubit_map": { - "q0": 2 - }, + "qubit_map": [["q0", 2]], "boolean_strs": [ "q0" ], diff --git a/cirq-core/cirq/protocols/json_test_data/BooleanHamiltonianGate.repr b/cirq-core/cirq/protocols/json_test_data/BooleanHamiltonianGate.repr index 598c6996684..14983729193 100644 --- a/cirq-core/cirq/protocols/json_test_data/BooleanHamiltonianGate.repr +++ b/cirq-core/cirq/protocols/json_test_data/BooleanHamiltonianGate.repr @@ -1 +1 @@ -[cirq.BooleanHamiltonianGate({'q0': 2}, ['q0'], 0.20160913)] \ No newline at end of file +[cirq.BooleanHamiltonianGate([['q0', 2]], ['q0'], 0.20160913)] \ No newline at end of file From d6d280c31844c7e40f5f637ee1e9ab180ba932c5 Mon Sep 17 00:00:00 2001 From: daxfohl Date: Tue, 23 Nov 2021 15:03:46 -0800 Subject: [PATCH 03/10] format --- cirq-core/cirq/ops/boolean_hamiltonian.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/cirq-core/cirq/ops/boolean_hamiltonian.py b/cirq-core/cirq/ops/boolean_hamiltonian.py index 52bc541940b..1d58b548bb6 100644 --- a/cirq-core/cirq/ops/boolean_hamiltonian.py +++ b/cirq-core/cirq/ops/boolean_hamiltonian.py @@ -123,7 +123,10 @@ def _has_unitary_(self): def __repr__(self): return ( - f'cirq.BooleanHamiltonian({self._qubit_map!r}, {self._boolean_strs!r}, {self._theta})' + f'cirq.BooleanHamiltonian(' + f'qubit_map={self._qubit_map!r}, ' + f'boolean_strs={self._boolean_strs!r}, ' + f'theta={self._theta!r})' ) @@ -196,7 +199,12 @@ def _has_unitary_(self): return True def __repr__(self): - return f'cirq.BooleanHamiltonianGate({self._qubit_map!r}, {self._boolean_strs!r}, {self._theta})' + return ( + f'cirq.BooleanHamiltonianGate(' + f'qubit_map={self._qubit_map!r}, ' + f'boolean_strs={self._boolean_strs!r}, ' + f'theta={self._theta!r})' + ) def _gray_code_comparator(k1: Tuple[int, ...], k2: Tuple[int, ...], flip: bool = False) -> int: From 8d8f9a19b6f75ba49a0ebdcee930ac0be2d0efec Mon Sep 17 00:00:00 2001 From: daxfohl Date: Wed, 24 Nov 2021 09:48:19 -0800 Subject: [PATCH 04/10] Remove logic for qudits, add check that only qubits are accepted. --- cirq-core/cirq/ops/boolean_hamiltonian.py | 28 ++++++++++--------- .../cirq/ops/boolean_hamiltonian_test.py | 8 +++--- .../BooleanHamiltonianGate.json | 6 ++-- .../BooleanHamiltonianGate.repr | 2 +- 4 files changed, 22 insertions(+), 22 deletions(-) diff --git a/cirq-core/cirq/ops/boolean_hamiltonian.py b/cirq-core/cirq/ops/boolean_hamiltonian.py index 1d58b548bb6..5489b40bcbc 100644 --- a/cirq-core/cirq/ops/boolean_hamiltonian.py +++ b/cirq-core/cirq/ops/boolean_hamiltonian.py @@ -33,7 +33,6 @@ from cirq import value from cirq.ops import gate_operation, raw_types from cirq.ops.linear_combinations import PauliSum, PauliString -from cirq import protocols @value.value_equality @@ -65,7 +64,12 @@ def __init__( boolean_strs: The list of Sympy-parsable Boolean expressions. qubit_map: map of string (boolean variable name) to qubit. theta: The evolution time (angle) for the Hamiltonian + + Raises: + ValueError: If the any qubits are not 2D. """ + if any(q.dimension != 2 for q in qubit_map.values()): + raise ValueError('All qubits must be 2-dimensional.') self._qubit_map: Dict[str, 'cirq.Qid'] = qubit_map self._boolean_strs: Sequence[str] = boolean_strs self._theta: float = theta @@ -73,8 +77,6 @@ def __init__( def with_qubits(self, *new_qubits: 'cirq.Qid') -> 'BooleanHamiltonian': if len(self._qubit_map) != len(new_qubits): raise ValueError('Length of replacement qubits must be the same') - if protocols.qid_shape(tuple(self._qubit_map.values())) != protocols.qid_shape(new_qubits): - raise ValueError('Dimensions of replacement qubits must be the same') new_qubit_map = { variable_name: new_qubit for variable_name, new_qubit in zip(self._qubit_map, new_qubits) @@ -136,7 +138,7 @@ class BooleanHamiltonianGate(raw_types.Gate): def __init__( self, - qubit_map: Sequence[Tuple[str, int]], + parameter_names: Sequence[str], boolean_strs: Sequence[str], theta: float, ): @@ -156,37 +158,37 @@ def __init__( Hermitian. Args: + parameter_names: The names of the inputs to the expressions. boolean_strs: The list of Sympy-parsable Boolean expressions. - qubit_map: map of string (boolean variable name) to qubit dimension. theta: The evolution time (angle) for the Hamiltonian """ - self._qubit_map: Sequence[Tuple[str, int]] = qubit_map + self._parameter_names: Sequence[str] = parameter_names self._boolean_strs: Sequence[str] = boolean_strs self._theta: float = theta def _qid_shape_(self): - return tuple(dim for _, dim in self._qubit_map) + return (2,) * len(self._parameter_names) def on(self, *qubits) -> 'cirq.Operation': return gate_operation.GateOperation(self, qubits) def _value_equality_values_(self): - return self._qubit_map, self._boolean_strs, self._theta + return self._parameter_names, self._boolean_strs, self._theta def _json_dict_(self) -> Dict[str, Any]: return { 'cirq_type': self.__class__.__name__, - 'qubit_map': self._qubit_map, + 'parameter_names': self._parameter_names, 'boolean_strs': self._boolean_strs, 'theta': self._theta, } @classmethod - def _from_json_dict_(cls, qubit_map, boolean_strs, theta, **kwargs): - return cls(qubit_map, boolean_strs, theta) + def _from_json_dict_(cls, parameter_names, boolean_strs, theta, **kwargs): + return cls(parameter_names, boolean_strs, theta) def _decompose_(self, qubits: Sequence['cirq.Qid']) -> 'cirq.OP_TREE': - qubit_map = dict(zip([name for name, _ in self._qubit_map], qubits)) + qubit_map = dict(zip(self._parameter_names, qubits)) boolean_exprs = [sympy_parser.parse_expr(boolean_str) for boolean_str in self._boolean_strs] hamiltonian_polynomial_list = [ PauliSum.from_boolean_expression(boolean_expr, qubit_map) @@ -201,7 +203,7 @@ def _has_unitary_(self): def __repr__(self): return ( f'cirq.BooleanHamiltonianGate(' - f'qubit_map={self._qubit_map!r}, ' + f'parameter_names={self._parameter_names!r}, ' f'boolean_strs={self._boolean_strs!r}, ' f'theta={self._theta!r})' ) diff --git a/cirq-core/cirq/ops/boolean_hamiltonian_test.py b/cirq-core/cirq/ops/boolean_hamiltonian_test.py index 6212a45338a..87e7ad67410 100644 --- a/cirq-core/cirq/ops/boolean_hamiltonian_test.py +++ b/cirq-core/cirq/ops/boolean_hamiltonian_test.py @@ -136,7 +136,7 @@ def test_gate_circuit(boolean_str): circuit.append(cirq.H.on_each(*qubits)) hamiltonian_gate = cirq.BooleanHamiltonianGate( - [(q.name, q.dimension) for q in qubits], [boolean_str], 0.1 * math.pi + [q.name for q in qubits], [boolean_str], 0.1 * math.pi ).on(*qubits) assert cirq.qid_shape(hamiltonian_gate) == cirq.qid_shape(qubits) @@ -165,14 +165,14 @@ def test_with_custom_names(): with pytest.raises(ValueError, match='Length of replacement qubits must be the same'): original_op.with_qubits(q2) - with pytest.raises(ValueError, match='Dimensions of replacement qubits must be the same'): + with pytest.raises(ValueError, match='All qubits must be 2-dimensional'): original_op.with_qubits(q0, cirq.LineQid(1, 3)) def test_gate_with_custom_names(): q0, q1, q2, q3 = cirq.LineQubit.range(4) gate = cirq.BooleanHamiltonianGate( - [('a', 2), ('b', 2)], + ['a', 'b'], ['a'], 0.1, ) @@ -199,7 +199,7 @@ def test_consistent(): def test_gate_consistent(): gate = cirq.BooleanHamiltonianGate( - [('a', 2), ('b', 2)], + ['a', 'b'], ['a'], 0.1, ) diff --git a/cirq-core/cirq/protocols/json_test_data/BooleanHamiltonianGate.json b/cirq-core/cirq/protocols/json_test_data/BooleanHamiltonianGate.json index cb0e44988bc..23d6e7a221b 100644 --- a/cirq-core/cirq/protocols/json_test_data/BooleanHamiltonianGate.json +++ b/cirq-core/cirq/protocols/json_test_data/BooleanHamiltonianGate.json @@ -1,10 +1,8 @@ [ { "cirq_type": "BooleanHamiltonianGate", - "qubit_map": [["q0", 2]], - "boolean_strs": [ - "q0" - ], + "parameter_names": ["q0", "q1"], + "boolean_strs": ["q0"], "theta": 0.20160913 } ] \ No newline at end of file diff --git a/cirq-core/cirq/protocols/json_test_data/BooleanHamiltonianGate.repr b/cirq-core/cirq/protocols/json_test_data/BooleanHamiltonianGate.repr index 14983729193..0d1c4055ad9 100644 --- a/cirq-core/cirq/protocols/json_test_data/BooleanHamiltonianGate.repr +++ b/cirq-core/cirq/protocols/json_test_data/BooleanHamiltonianGate.repr @@ -1 +1 @@ -[cirq.BooleanHamiltonianGate([['q0', 2]], ['q0'], 0.20160913)] \ No newline at end of file +[cirq.BooleanHamiltonianGate(parameter_names=['q0', 'q1'], boolean_strs=['q0'], theta=0.20160913)] \ No newline at end of file From d993741afc6bd9df743e03b67efdbff29ef25e94 Mon Sep 17 00:00:00 2001 From: daxfohl Date: Sun, 26 Dec 2021 09:29:11 -0800 Subject: [PATCH 05/10] Deprecate boolham op --- cirq-core/cirq/ops/boolean_hamiltonian.py | 6 + .../cirq/ops/boolean_hamiltonian_test.py | 115 +++++------------- 2 files changed, 35 insertions(+), 86 deletions(-) diff --git a/cirq-core/cirq/ops/boolean_hamiltonian.py b/cirq-core/cirq/ops/boolean_hamiltonian.py index 74cbfb32450..bd8d309ca1a 100644 --- a/cirq-core/cirq/ops/boolean_hamiltonian.py +++ b/cirq-core/cirq/ops/boolean_hamiltonian.py @@ -31,10 +31,12 @@ import cirq from cirq import value +from cirq._compat import deprecated_class from cirq.ops import gate_operation, raw_types from cirq.ops.linear_combinations import PauliSum, PauliString +@deprecated_class(deadline='v0.15', fix='Use cirq.BooleanHamiltonianGate') @value.value_equality class BooleanHamiltonian(raw_types.Operation): """An operation that represents a Hamiltonian from a set of Boolean functions.""" @@ -130,6 +132,10 @@ def __repr__(self): f'theta={self._theta!r})' ) + @property + def gate(self) -> 'cirq.Gate': + return BooleanHamiltonianGate(self._qubit_map.keys(), self._boolean_strs, self._theta) + @value.value_equality class BooleanHamiltonianGate(raw_types.Gate): diff --git a/cirq-core/cirq/ops/boolean_hamiltonian_test.py b/cirq-core/cirq/ops/boolean_hamiltonian_test.py index 87e7ad67410..15951cd0596 100644 --- a/cirq-core/cirq/ops/boolean_hamiltonian_test.py +++ b/cirq-core/cirq/ops/boolean_hamiltonian_test.py @@ -53,7 +53,8 @@ '(x2 | x1) ^ x0', ], ) -def test_circuit(boolean_str): +@pytest.mark.parametrize('transform', [lambda op: op, lambda op: op.gate.on(*op.qubits)]) +def test_circuit(boolean_str, transform): boolean_expr = sympy_parser.parse_expr(boolean_str) var_names = cirq.parameter_names(boolean_expr) qubits = [cirq.NamedQubit(name) for name in var_names] @@ -72,76 +73,14 @@ def test_circuit(boolean_str): circuit = cirq.Circuit() circuit.append(cirq.H.on_each(*qubits)) - hamiltonian_gate = cirq.BooleanHamiltonian( - {q.name: q for q in qubits}, [boolean_str], 0.1 * math.pi - ) + with cirq.testing.assert_deprecated('Use cirq.BooleanHamiltonianGate', deadline='v0.15'): + hamiltonian_gate = cirq.BooleanHamiltonian( + {q.name: q for q in qubits}, [boolean_str], 0.1 * math.pi + ) assert hamiltonian_gate.num_qubits() == n - circuit.append(hamiltonian_gate) - - 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 - - # Compare the two: - np.testing.assert_array_equal(actual, expected) - - -@pytest.mark.parametrize( - 'boolean_str', - [ - 'x0', - '~x0', - 'x0 ^ x1', - 'x0 & x1', - 'x0 | x1', - 'x0 & x1 & x2', - 'x0 & x1 & ~x2', - 'x0 & ~x1 & x2', - 'x0 & ~x1 & ~x2', - '~x0 & x1 & x2', - '~x0 & x1 & ~x2', - '~x0 & ~x1 & x2', - '~x0 & ~x1 & ~x2', - 'x0 ^ x1 ^ x2', - 'x0 | (x1 & x2)', - 'x0 & (x1 | x2)', - '(x0 ^ x1 ^ x2) | (x2 ^ x3 ^ x4)', - '(x0 ^ x2 ^ x4) | (x1 ^ x2 ^ x3)', - 'x0 & x1 & (x2 | x3)', - 'x0 & ~x2', - '~x0 & x2', - 'x2 & ~x0', - '~x2 & x0', - '(x2 | x1) ^ x0', - ], -) -def test_gate_circuit(boolean_str): - boolean_expr = sympy_parser.parse_expr(boolean_str) - var_names = cirq.parameter_names(boolean_expr) - qubits = [cirq.NamedQubit(name) for name in var_names] - - # We use Sympy to evaluate the expression: - n = len(var_names) - - expected = [] - for binary_inputs in itertools.product([0, 1], repeat=n): - subed_expr = boolean_expr - for var_name, binary_input in zip(var_names, binary_inputs): - subed_expr = subed_expr.subs(var_name, binary_input) - expected.append(bool(subed_expr)) - - # We build a circuit and look at its output state vector: - circuit = cirq.Circuit() - circuit.append(cirq.H.on_each(*qubits)) - - hamiltonian_gate = cirq.BooleanHamiltonianGate( - [q.name for q in qubits], [boolean_str], 0.1 * math.pi - ).on(*qubits) - - assert cirq.qid_shape(hamiltonian_gate) == cirq.qid_shape(qubits) - - circuit.append(hamiltonian_gate) + circuit.append(transform(hamiltonian_gate)) 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 @@ -152,21 +91,24 @@ def test_gate_circuit(boolean_str): def test_with_custom_names(): q0, q1, q2, q3 = cirq.LineQubit.range(4) - original_op = cirq.BooleanHamiltonian( - {'a': q0, 'b': q1}, - ['a'], - 0.1, - ) - assert cirq.decompose(original_op) == [cirq.Rz(rads=-0.05).on(q0)] + with cirq.testing.assert_deprecated( + 'Use cirq.BooleanHamiltonianGate', deadline='v0.15', count=3 + ): + original_op = cirq.BooleanHamiltonian( + {'a': q0, 'b': q1}, + ['a'], + 0.1, + ) + assert cirq.decompose(original_op) == [cirq.Rz(rads=-0.05).on(q0)] - renamed_op = original_op.with_qubits(q2, q3) - assert cirq.decompose(renamed_op) == [cirq.Rz(rads=-0.05).on(q2)] + renamed_op = original_op.with_qubits(q2, q3) + assert cirq.decompose(renamed_op) == [cirq.Rz(rads=-0.05).on(q2)] - with pytest.raises(ValueError, match='Length of replacement qubits must be the same'): - original_op.with_qubits(q2) + with pytest.raises(ValueError, match='Length of replacement qubits must be the same'): + original_op.with_qubits(q2) - with pytest.raises(ValueError, match='All qubits must be 2-dimensional'): - original_op.with_qubits(q0, cirq.LineQid(1, 3)) + with pytest.raises(ValueError, match='All qubits must be 2-dimensional'): + original_op.with_qubits(q0, cirq.LineQid(1, 3)) def test_gate_with_custom_names(): @@ -189,12 +131,13 @@ def test_gate_with_custom_names(): def test_consistent(): q0, q1 = cirq.LineQubit.range(2) - op = cirq.BooleanHamiltonian( - {'a': q0, 'b': q1}, - ['a'], - 0.1, - ) - cirq.testing.assert_implements_consistent_protocols(op) + with cirq.testing.assert_deprecated('Use cirq.BooleanHamiltonianGate', deadline='v0.15', count=6): + op = cirq.BooleanHamiltonian( + {'a': q0, 'b': q1}, + ['a'], + 0.1, + ) + cirq.testing.assert_implements_consistent_protocols(op) def test_gate_consistent(): From 2a7604576a264b8818e96134af266caa7abfa9a5 Mon Sep 17 00:00:00 2001 From: daxfohl Date: Sun, 26 Dec 2021 09:37:16 -0800 Subject: [PATCH 06/10] format --- cirq-core/cirq/ops/boolean_hamiltonian_test.py | 4 +++- cirq-core/cirq/protocols/json_test_data/spec.py | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/cirq-core/cirq/ops/boolean_hamiltonian_test.py b/cirq-core/cirq/ops/boolean_hamiltonian_test.py index 15951cd0596..dc5c908fc08 100644 --- a/cirq-core/cirq/ops/boolean_hamiltonian_test.py +++ b/cirq-core/cirq/ops/boolean_hamiltonian_test.py @@ -131,7 +131,9 @@ def test_gate_with_custom_names(): def test_consistent(): q0, q1 = cirq.LineQubit.range(2) - with cirq.testing.assert_deprecated('Use cirq.BooleanHamiltonianGate', deadline='v0.15', count=6): + with cirq.testing.assert_deprecated( + 'Use cirq.BooleanHamiltonianGate', deadline='v0.15', count=6 + ): op = cirq.BooleanHamiltonian( {'a': q0, 'b': q1}, ['a'], diff --git a/cirq-core/cirq/protocols/json_test_data/spec.py b/cirq-core/cirq/protocols/json_test_data/spec.py index e2ce8086e53..3621d9c69a2 100644 --- a/cirq-core/cirq/protocols/json_test_data/spec.py +++ b/cirq-core/cirq/protocols/json_test_data/spec.py @@ -179,7 +179,7 @@ 'Unique', 'DEFAULT_RESOLVERS', ], - deprecated={'GlobalPhaseOperation': 'v0.16'}, + deprecated={'GlobalPhaseOperation': 'v0.16', 'BooleanHamiltonian': 'v0.15'}, tested_elsewhere=[ # SerializableByKey does not follow common serialization rules. # It is tested separately in test_context_serialization. From 44b0e53a2b9dc78e08d8c8cdccc60a95c8385798 Mon Sep 17 00:00:00 2001 From: daxfohl Date: Sun, 26 Dec 2021 09:41:54 -0800 Subject: [PATCH 07/10] mypy --- cirq-core/cirq/ops/boolean_hamiltonian.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/cirq-core/cirq/ops/boolean_hamiltonian.py b/cirq-core/cirq/ops/boolean_hamiltonian.py index bd8d309ca1a..58097fe6725 100644 --- a/cirq-core/cirq/ops/boolean_hamiltonian.py +++ b/cirq-core/cirq/ops/boolean_hamiltonian.py @@ -134,7 +134,9 @@ def __repr__(self): @property def gate(self) -> 'cirq.Gate': - return BooleanHamiltonianGate(self._qubit_map.keys(), self._boolean_strs, self._theta) + return BooleanHamiltonianGate( + tuple(self._qubit_map.keys()), self._boolean_strs, self._theta + ) @value.value_equality From 6e1428d6a70ef568146d62c1e685a845c8b7b863 Mon Sep 17 00:00:00 2001 From: daxfohl Date: Sun, 26 Dec 2021 09:55:41 -0800 Subject: [PATCH 08/10] remove flaky test --- cirq-core/cirq/ops/boolean_hamiltonian_test.py | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/cirq-core/cirq/ops/boolean_hamiltonian_test.py b/cirq-core/cirq/ops/boolean_hamiltonian_test.py index dc5c908fc08..95911165d40 100644 --- a/cirq-core/cirq/ops/boolean_hamiltonian_test.py +++ b/cirq-core/cirq/ops/boolean_hamiltonian_test.py @@ -129,19 +129,6 @@ def test_gate_with_custom_names(): gate.on(q0, cirq.LineQid(1, 3)) -def test_consistent(): - q0, q1 = cirq.LineQubit.range(2) - with cirq.testing.assert_deprecated( - 'Use cirq.BooleanHamiltonianGate', deadline='v0.15', count=6 - ): - op = cirq.BooleanHamiltonian( - {'a': q0, 'b': q1}, - ['a'], - 0.1, - ) - cirq.testing.assert_implements_consistent_protocols(op) - - def test_gate_consistent(): gate = cirq.BooleanHamiltonianGate( ['a', 'b'], From cd7038df00c84b644038a536e935da6d82677083 Mon Sep 17 00:00:00 2001 From: daxfohl Date: Sun, 26 Dec 2021 10:27:16 -0800 Subject: [PATCH 09/10] cover --- cirq-core/cirq/ops/boolean_hamiltonian.py | 8 -------- 1 file changed, 8 deletions(-) diff --git a/cirq-core/cirq/ops/boolean_hamiltonian.py b/cirq-core/cirq/ops/boolean_hamiltonian.py index 58097fe6725..cd0f9ef0561 100644 --- a/cirq-core/cirq/ops/boolean_hamiltonian.py +++ b/cirq-core/cirq/ops/boolean_hamiltonian.py @@ -124,14 +124,6 @@ def _decompose_(self): def _has_unitary_(self): return True - def __repr__(self): - return ( - f'cirq.BooleanHamiltonian(' - f'qubit_map={self._qubit_map!r}, ' - f'boolean_strs={self._boolean_strs!r}, ' - f'theta={self._theta!r})' - ) - @property def gate(self) -> 'cirq.Gate': return BooleanHamiltonianGate( From 0204b7567de98c45a8d2192614eed4904d11bc36 Mon Sep 17 00:00:00 2001 From: daxfohl Date: Mon, 28 Feb 2022 12:01:00 -0800 Subject: [PATCH 10/10] fix nits --- cirq-core/cirq/json_resolver_cache.py | 1 - cirq-core/cirq/ops/boolean_hamiltonian.py | 20 +++++++++----------- 2 files changed, 9 insertions(+), 12 deletions(-) diff --git a/cirq-core/cirq/json_resolver_cache.py b/cirq-core/cirq/json_resolver_cache.py index dfa16c91191..f15252c3256 100644 --- a/cirq-core/cirq/json_resolver_cache.py +++ b/cirq-core/cirq/json_resolver_cache.py @@ -60,7 +60,6 @@ def _parallel_gate_op(gate, qubits): 'BitstringAccumulator': cirq.work.BitstringAccumulator, 'BooleanHamiltonian': cirq.BooleanHamiltonian, 'BooleanHamiltonianGate': cirq.BooleanHamiltonianGate, - 'ProductState': cirq.ProductState, 'CCNotPowGate': cirq.CCNotPowGate, 'CCXPowGate': cirq.CCXPowGate, 'CCZPowGate': cirq.CCZPowGate, diff --git a/cirq-core/cirq/ops/boolean_hamiltonian.py b/cirq-core/cirq/ops/boolean_hamiltonian.py index f03a94d3289..f4fbf019d6c 100644 --- a/cirq-core/cirq/ops/boolean_hamiltonian.py +++ b/cirq-core/cirq/ops/boolean_hamiltonian.py @@ -22,9 +22,8 @@ [4] Efficient Quantum Circuits for Diagonal Unitaries Without Ancillas by Jonathan Welch, Daniel Greenbaum, Sarah Mostame, and Alán Aspuru-Guzik, https://arxiv.org/abs/1306.3991 """ -import itertools import functools - +import itertools from typing import Any, Dict, Generator, List, Sequence, Tuple import sympy.parsing.sympy_parser as sympy_parser @@ -32,7 +31,7 @@ import cirq from cirq import value from cirq._compat import deprecated_class -from cirq.ops import gate_operation, raw_types +from cirq.ops import raw_types from cirq.ops.linear_combinations import PauliSum, PauliString @@ -165,13 +164,10 @@ def __init__( self._boolean_strs: Sequence[str] = boolean_strs self._theta: float = theta - def _qid_shape_(self): + def _qid_shape_(self) -> Tuple[int, ...]: return (2,) * len(self._parameter_names) - def on(self, *qubits) -> 'cirq.Operation': - return gate_operation.GateOperation(self, qubits) - - def _value_equality_values_(self): + def _value_equality_values_(self) -> Any: return self._parameter_names, self._boolean_strs, self._theta def _json_dict_(self) -> Dict[str, Any]: @@ -183,7 +179,9 @@ def _json_dict_(self) -> Dict[str, Any]: } @classmethod - def _from_json_dict_(cls, parameter_names, boolean_strs, theta, **kwargs): + def _from_json_dict_( + cls, parameter_names, boolean_strs, theta, **kwargs + ) -> 'cirq.BooleanHamiltonianGate': return cls(parameter_names, boolean_strs, theta) def _decompose_(self, qubits: Sequence['cirq.Qid']) -> 'cirq.OP_TREE': @@ -196,10 +194,10 @@ def _decompose_(self, qubits: Sequence['cirq.Qid']) -> 'cirq.OP_TREE': return _get_gates_from_hamiltonians(hamiltonian_polynomial_list, qubit_map, self._theta) - def _has_unitary_(self): + def _has_unitary_(self) -> bool: return True - def __repr__(self): + def __repr__(self) -> str: return ( f'cirq.BooleanHamiltonianGate(' f'parameter_names={self._parameter_names!r}, '