diff --git a/unitary/alpha/quantum_effect.py b/unitary/alpha/quantum_effect.py index d08a0735..d3fe058d 100644 --- a/unitary/alpha/quantum_effect.py +++ b/unitary/alpha/quantum_effect.py @@ -49,7 +49,7 @@ def _verify_objects(self, *objects): q.num_states != required_dimension ): raise ValueError( - f"Cannot apply effect to qids of dimension {required_dimension}." + f"Cannot apply effect to qids of dimension {q.num_states}." ) if q.world is None: raise ValueError( diff --git a/unitary/alpha/quantum_effect_test.py b/unitary/alpha/quantum_effect_test.py index db2b9d25..29198acf 100644 --- a/unitary/alpha/quantum_effect_test.py +++ b/unitary/alpha/quantum_effect_test.py @@ -23,9 +23,10 @@ Q1 = cirq.NamedQubit("q1") +@pytest.mark.parametrize("compile_to_qubits", [False, True]) @pytest.mark.parametrize("simulator", [cirq.Simulator, SparseSimulator]) -def test_quantum_if(simulator): - board = alpha.QuantumWorld(sampler=simulator()) +def test_quantum_if(simulator, compile_to_qubits): + board = alpha.QuantumWorld(sampler=simulator(), compile_to_qubits=compile_to_qubits) piece = alpha.QuantumObject("q0", 1) piece2 = alpha.QuantumObject("q1", 0) board.add_object(piece) @@ -44,9 +45,10 @@ def test_quantum_if(simulator): assert (result[1] == 1 for result in results) +@pytest.mark.parametrize("compile_to_qubits", [False, True]) @pytest.mark.parametrize("simulator", [cirq.Simulator, SparseSimulator]) -def test_anti_control(simulator): - board = alpha.QuantumWorld(sampler=simulator()) +def test_anti_control(simulator, compile_to_qubits): + board = alpha.QuantumWorld(sampler=simulator(), compile_to_qubits=compile_to_qubits) piece = alpha.QuantumObject("q0", 0) piece2 = alpha.QuantumObject("q1", 0) board.add_object(piece) @@ -72,8 +74,9 @@ def test_no_world(): alpha.Flip()(piece) -def test_bad_length(): - board = alpha.QuantumWorld() +@pytest.mark.parametrize("compile_to_qubits", [False, True]) +def test_bad_length(compile_to_qubits): + board = alpha.QuantumWorld(compile_to_qubits=compile_to_qubits) piece = alpha.QuantumObject("q0", 1) board.add_object(piece) with pytest.raises(ValueError, match="Not able to equate"): @@ -83,8 +86,9 @@ def test_bad_length(): alpha.Split()(piece) -def test_no_qutrits(): - board = alpha.QuantumWorld() +@pytest.mark.parametrize("compile_to_qubits", [False, True]) +def test_no_qutrits(compile_to_qubits): + board = alpha.QuantumWorld(compile_to_qubits=compile_to_qubits) piece = alpha.QuantumObject("q0", 2) board.add_object(piece) with pytest.raises(ValueError, match="Cannot apply effect to qids"): diff --git a/unitary/alpha/quantum_object_test.py b/unitary/alpha/quantum_object_test.py index e6b5f070..59375e70 100644 --- a/unitary/alpha/quantum_object_test.py +++ b/unitary/alpha/quantum_object_test.py @@ -21,10 +21,13 @@ from unitary.alpha.sparse_vector_simulator import SparseSimulator +@pytest.mark.parametrize("compile_to_qubits", [False, True]) @pytest.mark.parametrize("simulator", [cirq.Simulator, SparseSimulator]) -def test_negation(simulator): +def test_negation(simulator, compile_to_qubits): piece = alpha.QuantumObject("t", 0) - board = alpha.QuantumWorld(piece, sampler=simulator()) + board = alpha.QuantumWorld( + piece, sampler=simulator(), compile_to_qubits=compile_to_qubits + ) assert board.peek() == [[0]] -piece assert board.peek() == [[1]] @@ -34,17 +37,21 @@ def test_negation(simulator): assert board.peek() == [[1]] +@pytest.mark.parametrize("compile_to_qubits", [False, True]) @pytest.mark.parametrize("simulator", [cirq.Simulator, SparseSimulator]) -def test_add_world_after_state_change(simulator): +def test_add_world_after_state_change(simulator, compile_to_qubits): piece = alpha.QuantumObject("t", 0) piece += 1 - board = alpha.QuantumWorld(piece, sampler=simulator()) + board = alpha.QuantumWorld( + piece, sampler=simulator(), compile_to_qubits=compile_to_qubits + ) assert board.peek() == [[1]] -def test_qutrit(): +@pytest.mark.parametrize("compile_to_qubits", [False, True]) +def test_qutrit(compile_to_qubits): piece = alpha.QuantumObject("t", 2) - board = alpha.QuantumWorld(piece) + board = alpha.QuantumWorld(piece, compile_to_qubits=compile_to_qubits) assert board.peek() == [[2]] piece += 1 assert board.peek() == [[0]] @@ -58,14 +65,15 @@ def test_qutrit(): assert board.peek() == [[2]] -def test_enum(): +@pytest.mark.parametrize("compile_to_qubits", [False, True]) +def test_enum(compile_to_qubits): class Color(enum.Enum): RED = 0 YELLOW = 1 GREEN = 2 piece = alpha.QuantumObject("t", Color.YELLOW) - board = alpha.QuantumWorld(piece) + board = alpha.QuantumWorld(piece, compile_to_qubits=compile_to_qubits) assert board.peek() == [[Color.YELLOW]] piece += Color.YELLOW assert board.peek() == [[Color.GREEN]] diff --git a/unitary/alpha/quantum_world.py b/unitary/alpha/quantum_world.py index 9df1de04..d8b68b25 100644 --- a/unitary/alpha/quantum_world.py +++ b/unitary/alpha/quantum_world.py @@ -13,11 +13,12 @@ # limitations under the License. import copy import enum -from typing import Dict, List, Optional, Sequence, Union +from typing import Dict, Iterable, List, Optional, Sequence, Union import cirq from unitary.alpha.quantum_object import QuantumObject from unitary.alpha.sparse_vector_simulator import PostSelectOperation, SparseSimulator +from unitary.alpha.qudit_state_transform import qudit_to_qubit_unitary, num_bits class QuantumWorld: @@ -35,15 +36,21 @@ class QuantumWorld: This object should be initialized with a sampler that determines how to evaluate the quantum game state. If not specified, this defaults to the built-in cirq Simulator. - """ + Setting the `compile_to_qubits` option results in an internal state + representation of ancilla qubits for every qudit in the world. That + also results in the effects being applied to the corresponding qubits + instead of the original qudits. + """ def __init__(self, objects: Optional[List[QuantumObject]] = None, - sampler=cirq.Simulator()): + sampler=cirq.Simulator(), + compile_to_qubits: bool = False): self.clear() self.sampler = sampler self.use_sparse = isinstance(sampler, SparseSimulator) + self.compile_to_qubits = compile_to_qubits if isinstance(objects, QuantumObject): objects = [objects] @@ -60,6 +67,9 @@ def clear(self): self.effect_history = [] self.object_name_dict: Dict[str, QuantumObject] = {} self.ancilla_names = set() + # When `compile_to_qubits` is True, this tracks the mapping of the + # original qudits to the compiled qubits. + self.compiled_qubits: Dict[cirq.Qid, List[cirq.Qid]] = {} self.post_selection: Dict[QuantumObject, int] = {} def add_object(self, obj: QuantumObject): @@ -70,15 +80,33 @@ def add_object(self, obj: QuantumObject): already been added to the world. """ if obj.name in self.object_name_dict: - raise ValueError("QuantumObject {obj.name} already added to world.") + raise ValueError( + "QuantumObject {obj.name} already added to world.") self.object_name_dict[obj.name] = obj obj.world = self + if self.compile_to_qubits: + qudit_dim = obj.qubit.dimension + if qudit_dim == 2: + self.compiled_qubits[obj.qubit] = [obj.qubit] + else: + self.compiled_qubits[obj.qubit] = [] + for qubit_num in range(num_bits(qudit_dim)): + new_obj = self._add_ancilla(obj.qubit.name) + self.compiled_qubits[obj.qubit].append(new_obj.qubit) obj.initial_effect() @property def objects(self) -> List[QuantumObject]: return list(self.object_name_dict.values()) + @property + def public_objects(self) -> List[QuantumObject]: + """All non-ancilla objects in the world.""" + return [ + obj for obj in self.object_name_dict.values() + if obj.name not in self.ancilla_names + ] + def get_object_by_name(self, name: str) -> Optional[QuantumObject]: """Returns the object with the given name. @@ -106,6 +134,7 @@ def combine_with(self, other_world: "QuantumWorld"): "Cannot combine sparse simulator world with non-sparse") self.object_name_dict.update(other_world.object_name_dict) self.ancilla_names.update(other_world.ancilla_names) + self.compiled_qubits.update(other_world.compiled_qubits) self.post_selection.update(other_world.post_selection) self.circuit = self.circuit.zip(other_world.circuit) # Clear effect history, since undoing would undo the combined worlds @@ -113,22 +142,83 @@ def combine_with(self, other_world: "QuantumWorld"): # Clear the other world so that objects cannot be used from that world. other_world.clear() + def _add_ancilla(self, + namespace: str, + value: Union[enum.Enum, int] = 0) -> QuantumObject: + """Adds an ancilla qudit object with a unique name. + + Args: + namespace: Custom string to be added in the name + value: The value for the ancilla qudit + + Returns: + The added ancilla object. + """ + count = 0 + ancilla_name = f"ancilla_{namespace}_{count}" + while ancilla_name in self.object_name_dict: + count += 1 + ancilla_name = f"ancilla_{namespace}_{count}" + new_obj = QuantumObject(ancilla_name, value) + self.add_object(new_obj) + self.ancilla_names.add(ancilla_name) + return new_obj + def _append_op(self, op: cirq.Operation): """Add the operation in a way designed to speed execution. For the sparse simulator post-selections should be as early as possible to cut down the state size. Also X's since they don't increase the size. """ - if not self.use_sparse: - self.circuit.append(op) - return - if isinstance(op, PostSelectOperation) or op.gate is cirq.X: + if not self.use_sparse or isinstance( + op, PostSelectOperation) or op.gate is cirq.X: strategy = cirq.InsertStrategy.EARLIEST else: strategy = cirq.InsertStrategy.NEW + + if self.compile_to_qubits: + op = self._compile_op(op) + self.circuit.append(op, strategy=strategy) + def _compile_op(self, + op: cirq.Operation) -> Union[cirq.Operation, cirq.OP_TREE]: + """Compiles the operation down to qubits, if needed.""" + qid_shape = cirq.qid_shape(op) + if len(set(qid_shape)) > 1: + # TODO(#77): Add support for arbitrary Qid shapes to + # `qudit_state_transform`. + raise ValueError( + f"Found operation shape {qid_shape}. Compiling operations with" + " a mix of different dimensioned qudits is not supported yet.") + qudit_dim = qid_shape[0] + if qudit_dim == 2: + return op + num_qudits = len(qid_shape) + compiled_qubits = [] + for qudit in op.qubits: + compiled_qubits.extend(self.compiled_qubits[qudit]) + + if isinstance(op, PostSelectOperation): + # Spread the post-selected value across the compiled qubits using the + # big endian convention. + value_bits = cirq.big_endian_int_to_bits( + op.value, bit_count=len(compiled_qubits)) + return [ + PostSelectOperation(qubit, value) + for qubit, value in zip(compiled_qubits, value_bits) + ] + + # Compile the input unitary to a target qubit-based unitary. + compiled_unitary = qudit_to_qubit_unitary( + qudit_dimension=qudit_dim, + num_qudits=num_qudits, + qudit_unitary=cirq.unitary(op)) + return cirq.MatrixGate(matrix=compiled_unitary, + qid_shape=(2, ) * + len(compiled_qubits)).on(*compiled_qubits) + def add_effect(self, op_list: List[cirq.Operation]): """Adds an operation to the current circuit.""" self.effect_history.append( @@ -163,6 +253,28 @@ def _suggest_num_reps(self, sample_size: int) -> int: sample_size = 100 return sample_size + def _interpret_result(self, result: Union[int, Iterable[int]]) -> int: + """Canonicalize an entry from the measurement results array to int. + + When `compile_to_qubit` is set, the results are expected to be a + sequence of bits that are the binary representation of the measurement + of the original key. Returns the `int` represented by the bits. + + If the input is a single-element Iterable, returns the first element. + """ + if self.compile_to_qubits: + # For a compiled qudit, the results will be a bit array + # representing an integer outcome. + return cirq.big_endian_bits_to_int(result) + if isinstance(result, Iterable): + # If it is a single-element iterable, return the first element. + if len(result) != 1: + raise ValueError( + f"Cannot interpret a multivalued iterable {result} as a " + "single result for a non-compiled world.") + return result[0] + return result + def force_measurement(self, obj: QuantumObject, result: Union[enum.Enum, int]) -> str: """Measures a QuantumObject with a defined outcome. @@ -172,17 +284,24 @@ def force_measurement(self, obj: QuantumObject, result: Union[enum.Enum, to be a particular result. A new qubit set to the initial state of the result. """ - count = 0 - ancilla_name = f"ancilla_{obj.name}_{count}" - while ancilla_name in self.object_name_dict: - count += 1 - ancilla_name = f"ancilla_{obj.name}_{count}" - new_obj = QuantumObject(ancilla_name, result) - self.add_object(new_obj) - self.ancilla_names.add(ancilla_name) + new_obj = self._add_ancilla(namespace=obj.name, value=result) + # Swap the input and ancilla qubits using a remapping dict. + qubit_remapping_dict = { + obj.qubit: new_obj.qubit, + new_obj.qubit: obj.qubit + } + if self.compile_to_qubits: + # Swap the compiled qubits. + obj_qubits = self.compiled_qubits.get(obj.qubit, [obj.qubit]) + new_obj_qubits = self.compiled_qubits.get(new_obj.qubit, + [new_obj.qubit]) + qubit_remapping_dict.update({ + *zip(obj_qubits, new_obj_qubits), + *zip(new_obj_qubits, obj_qubits) + }) + self.circuit = self.circuit.transform_qubits( - lambda q: q if q != obj.qubit and q != new_obj.qubit else - (new_obj.qubit if q == obj.qubit else obj.qubit)) + lambda q: qubit_remapping_dict.get(q, q)) post_selection = result.value if isinstance(result, enum.Enum) else result self.post_selection[new_obj] = post_selection @@ -218,10 +337,12 @@ def peek( measure_circuit = self.circuit.copy() if objects is None: - objects = self.objects + objects = self.public_objects measure_set = set(objects + list(self.post_selection.keys())) - measure_circuit.append( - [cirq.measure(p.qubit, key=p.qubit.name) for p in measure_set]) + measure_circuit.append([ + cirq.measure(self.compiled_qubits.get(p.qubit, p.qubit), + key=p.qubit.name) for p in measure_set + ]) results = self.sampler.run(measure_circuit, repetitions=num_reps) # Perform post-selection @@ -229,15 +350,15 @@ def peek( for rep in range(num_reps): post_selected = True for obj in self.post_selection.keys(): - result = results.measurements[obj.name][rep][0] + result = self._interpret_result( + results.measurements[obj.name][rep]) if result != self.post_selection[obj]: post_selected = False break if post_selected: rtn_list.append([ - results.measurements[obj.name][rep] + self._interpret_result(results.measurements[obj.name][rep]) for obj in objects - if obj.name not in self.ancilla_names ]) if len(rtn_list) == count: break @@ -264,7 +385,7 @@ def pop( self.effect_history.append( (self.circuit.copy(), copy.copy(self.post_selection))) if objects is None: - objects = self.objects + objects = self.public_objects results = self.peek(objects, convert_to_enum=convert_to_enum) for idx, result in enumerate(results[0]): self.force_measurement(objects[idx], result) @@ -285,7 +406,7 @@ def get_histogram(self, counts for each state of the given object. """ if not objects: - objects = self.objects + objects = self.public_objects peek_results = self.peek(objects=objects, convert_to_enum=False, count=count) @@ -294,7 +415,7 @@ def get_histogram(self, histogram.append({state: 0 for state in range(obj.num_states)}) for result in peek_results: for idx in range(len(objects)): - histogram[idx][result[idx][0]] += 1 + histogram[idx][result[idx]] += 1 return histogram def get_probabilities(self, @@ -314,7 +435,8 @@ def get_probabilities(self, probabilities = [] for obj_hist in histogram: probabilities.append({ - state: obj_hist[state] / count for state in range(len(obj_hist)) + state: obj_hist[state] / count + for state in range(len(obj_hist)) }) return probabilities diff --git a/unitary/alpha/quantum_world_test.py b/unitary/alpha/quantum_world_test.py index d3795d12..b93b51e6 100644 --- a/unitary/alpha/quantum_world_test.py +++ b/unitary/alpha/quantum_world_test.py @@ -13,11 +13,15 @@ # limitations under the License. # import enum + import pytest +import numpy as np + import cirq import unitary.alpha as alpha +import unitary.alpha.qudit_gates as qudit_gates class Light(enum.Enum): @@ -31,39 +35,49 @@ class StopLight(enum.Enum): GREEN = 2 -def test_duplicate_objects(): +@pytest.mark.parametrize("compile_to_qubits", [False, True]) +def test_duplicate_objects(compile_to_qubits): light = alpha.QuantumObject("test", Light.GREEN) - board = alpha.QuantumWorld([light]) + board = alpha.QuantumWorld([light], compile_to_qubits=compile_to_qubits) with pytest.raises(ValueError, match="already added"): board.add_object(alpha.QuantumObject("test", Light.RED)) -def test_get_object_by_name(): + +@pytest.mark.parametrize("compile_to_qubits", [False, True]) +def test_get_object_by_name(compile_to_qubits): light = alpha.QuantumObject("test", Light.GREEN) light2 = alpha.QuantumObject("test2", Light.RED) - board = alpha.QuantumWorld([light, light2]) + board = alpha.QuantumWorld([light, light2], + compile_to_qubits=compile_to_qubits) assert board.get_object_by_name("test") == light assert board.get_object_by_name("test2") == light2 assert board.get_object_by_name("test3") == None +@pytest.mark.parametrize("compile_to_qubits", [False, True]) @pytest.mark.parametrize("simulator", [cirq.Simulator, alpha.SparseSimulator]) -def test_one_qubit(simulator): +def test_one_qubit(simulator, compile_to_qubits): light = alpha.QuantumObject("test", Light.GREEN) - board = alpha.QuantumWorld([light], sampler=simulator()) + board = alpha.QuantumWorld([light], + sampler=simulator(), + compile_to_qubits=compile_to_qubits) assert board.peek() == [[Light.GREEN]] assert board.peek([light], count=2) == [[Light.GREEN], [Light.GREEN]] light = alpha.QuantumObject("test", 1) - board = alpha.QuantumWorld([light]) + board = alpha.QuantumWorld([light], compile_to_qubits=compile_to_qubits) assert board.peek() == [[1]] assert board.peek([light], count=2) == [[1], [1]] assert board.pop() == [1] +@pytest.mark.parametrize("compile_to_qubits", [False, True]) @pytest.mark.parametrize("simulator", [cirq.Simulator, alpha.SparseSimulator]) -def test_two_qubits(simulator): +def test_two_qubits(simulator, compile_to_qubits): light = alpha.QuantumObject("green", Light.GREEN) light2 = alpha.QuantumObject("red", Light.RED) - board = alpha.QuantumWorld([light, light2], sampler=simulator()) + board = alpha.QuantumWorld([light, light2], + sampler=simulator(), + compile_to_qubits=compile_to_qubits) assert board.peek() == [[Light.GREEN, Light.RED]] assert board.peek(convert_to_enum=False) == [[1, 0]] assert board.peek([light], count=2) == [[Light.GREEN], [Light.GREEN]] @@ -75,29 +89,82 @@ def test_two_qubits(simulator): ] -def test_two_qutrits(): +@pytest.mark.parametrize("compile_to_qubits", [False, True]) +def test_two_qutrits(compile_to_qubits): light = alpha.QuantumObject("yellow", StopLight.YELLOW) light2 = alpha.QuantumObject("green", StopLight.GREEN) - board = alpha.QuantumWorld([light, light2]) + board = alpha.QuantumWorld([light, light2], + compile_to_qubits=compile_to_qubits) assert board.peek(convert_to_enum=False) == [[1, 2]] assert board.peek() == [[StopLight.YELLOW, StopLight.GREEN]] - assert board.peek([light], count=2) == [[StopLight.YELLOW], [StopLight.YELLOW]] - assert board.peek([light2], count=2) == [[StopLight.GREEN], [StopLight.GREEN]] + assert board.peek([light], count=2) == [[StopLight.YELLOW], + [StopLight.YELLOW]] + assert board.peek([light2], count=2) == [[StopLight.GREEN], + [StopLight.GREEN]] assert board.peek(count=3) == [ [StopLight.YELLOW, StopLight.GREEN], [StopLight.YELLOW, StopLight.GREEN], [StopLight.YELLOW, StopLight.GREEN], ] - expected = "green (d=3): ────X(0_2)───\n\nyellow (d=3): ───X(0_1)───" + if board.compile_to_qubits: + g0 = cirq.NamedQubit("ancilla_green_0") + g1 = cirq.NamedQubit("ancilla_green_1") + y0 = cirq.NamedQubit("ancilla_yellow_0") + y1 = cirq.NamedQubit("ancilla_yellow_1") + # Flip the 0 and 2 states from identity for Green. + g_x02 = cirq.MatrixGate( + np.array([[0, 0, 1, 0], [0, 1, 0, 0], [1, 0, 0, 0], [0, 0, 0, 1]], + dtype=float)).on(g0, g1) + # Flip the 0 and 1 states from identity for Yellow. + y_x01 = cirq.MatrixGate( + np.array([[0, 1, 0, 0], [1, 0, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]], + dtype=float)).on(y0, y1) + circuit = cirq.Circuit(g_x02, y_x01) + expected = str(circuit) + else: + expected = "green (d=3): ────X(0_2)───\n\nyellow (d=3): ───X(0_1)───" assert str(board.circuit) == expected + assert board.pop() == [StopLight.YELLOW, StopLight.GREEN] + + +@pytest.mark.parametrize( + ("simulator", "compile_to_qubits"), + [ + (cirq.Simulator, False), + (cirq.Simulator, True), + # Cannot use SparseSimulator without `compile_to_qubits` due to issue #78. + (alpha.SparseSimulator, True), + ]) +def test_qubit_and_qutrit(simulator, compile_to_qubits): + light = alpha.QuantumObject("yellow", Light.GREEN) + light2 = alpha.QuantumObject("green", StopLight.GREEN) + board = alpha.QuantumWorld([light, light2], + sampler=simulator(), + compile_to_qubits=compile_to_qubits) + assert board.peek(convert_to_enum=False) == [[1, 2]] + assert board.peek() == [[Light.GREEN, StopLight.GREEN]] + assert board.peek([light], count=2) == [[Light.GREEN], [Light.GREEN]] + assert board.peek([light2], count=2) == [[StopLight.GREEN], + [StopLight.GREEN]] + assert board.peek(count=3) == [ + [Light.GREEN, StopLight.GREEN], + [Light.GREEN, StopLight.GREEN], + [Light.GREEN, StopLight.GREEN], + ] + # Only the qutrit gets compiled to ancillas. + assert len(board.ancilla_names) == (2 if compile_to_qubits else 0) + assert board.pop() == [Light.GREEN, StopLight.GREEN] +@pytest.mark.parametrize("compile_to_qubits", [False, True]) @pytest.mark.parametrize("simulator", [cirq.Simulator, alpha.SparseSimulator]) -def test_pop(simulator): +def test_pop(simulator, compile_to_qubits): light = alpha.QuantumObject("l1", Light.GREEN) light2 = alpha.QuantumObject("l2", Light.RED) light3 = alpha.QuantumObject("l3", Light.RED) - board = alpha.QuantumWorld([light, light2, light3], sampler=simulator()) + board = alpha.QuantumWorld([light, light2, light3], + sampler=simulator(), + compile_to_qubits=compile_to_qubits) alpha.Split()(light, light2, light3) results = board.peek([light2, light3], count=200) assert all(result[0] != result[1] for result in results) @@ -110,11 +177,63 @@ def test_pop(simulator): assert all(result[1] != popped for result in results) +# TODO: Consider moving to qudit_effects.py if this can be broadly useful. +class QuditSwapEffect(alpha.QuantumEffect): + def __init__(self, dimension): + self.dimension = dimension + + def effect(self, object1, object2): + yield qudit_gates.QuditSwapPowGate(self.dimension)(object1.qubit, + object2.qubit) + + +# TODO: Consider moving to qudit_effects.py if this can be broadly useful. +class QuditSplitEffect(alpha.QuantumEffect): + def __init__(self, dimension): + self.dimension = dimension + + def effect(self, object1, object2, object3): + yield qudit_gates.QuditSwapPowGate(self.dimension, 0.5)(object1.qubit, + object2.qubit) + yield qudit_gates.QuditSwapPowGate(self.dimension, 1)(object1.qubit, + object3.qubit) + + +@pytest.mark.parametrize( + ("simulator", "compile_to_qubits"), + [ + (cirq.Simulator, False), + (cirq.Simulator, True), + # Cannot use SparseSimulator without `compile_to_qubits` due to issue #78. + (alpha.SparseSimulator, True), + ]) +def test_pop_qudit(simulator, compile_to_qubits): + light = alpha.QuantumObject("l1", StopLight.GREEN) + light2 = alpha.QuantumObject("l2", StopLight.RED) + light3 = alpha.QuantumObject("l3", StopLight.RED) + board = alpha.QuantumWorld([light, light2, light3], + sampler=simulator(), + compile_to_qubits=compile_to_qubits) + QuditSplitEffect(3)(light, light2, light3) + results = board.peek([light2, light3], count=200) + assert all(result[0] != result[1] for result in results) + assert not all(result[0] == 0 for result in results) + assert not all(result[0] == 1 for result in results) + popped = board.pop([light2])[0] + results = board.peek([light2, light3], count=200) + assert len(results) == 200 + assert all(result[0] == popped for result in results) + assert all(result[1] != popped for result in results) + + +@pytest.mark.parametrize("compile_to_qubits", [False, True]) @pytest.mark.parametrize("simulator", [cirq.Simulator, alpha.SparseSimulator]) -def test_pop_and_reuse(simulator): +def test_pop_and_reuse(simulator, compile_to_qubits): """Tests reusing a popped qubit.""" light = alpha.QuantumObject("l1", Light.GREEN) - board = alpha.QuantumWorld([light], sampler=simulator()) + board = alpha.QuantumWorld([light], + sampler=simulator(), + compile_to_qubits=compile_to_qubits) popped = board.pop([light])[0] assert popped == Light.GREEN alpha.Flip()(light) @@ -122,10 +241,34 @@ def test_pop_and_reuse(simulator): assert popped == Light.RED +@pytest.mark.parametrize( + ("simulator", "compile_to_qubits"), + [ + (cirq.Simulator, False), + (cirq.Simulator, True), + # Cannot use SparseSimulator without `compile_to_qubits` due to issue #78. + (alpha.SparseSimulator, True), + ]) +def test_pop_and_reuse_qudit(simulator, compile_to_qubits): + """Tests reusing a popped qudit.""" + light = alpha.QuantumObject("l1", StopLight.GREEN) + board = alpha.QuantumWorld([light], + sampler=simulator(), + compile_to_qubits=compile_to_qubits) + popped = board.pop([light])[0] + assert popped == StopLight.GREEN + alpha.QuditFlip(3, StopLight.RED.value, StopLight.GREEN.value)(light) + popped = board.pop([light])[0] + assert popped == StopLight.RED + + +@pytest.mark.parametrize("compile_to_qubits", [False, True]) @pytest.mark.parametrize("simulator", [cirq.Simulator, alpha.SparseSimulator]) -def test_undo(simulator): +def test_undo(simulator, compile_to_qubits): light = alpha.QuantumObject("l1", Light.GREEN) - board = alpha.QuantumWorld([light], sampler=simulator()) + board = alpha.QuantumWorld([light], + sampler=simulator(), + compile_to_qubits=compile_to_qubits) alpha.Flip()(light) results = board.peek([light], count=200) assert all(result[0] == Light.RED for result in results) @@ -134,18 +277,43 @@ def test_undo(simulator): assert all(result[0] == Light.GREEN for result in results) -def test_undo_no_effects(): - board = alpha.QuantumWorld([]) +@pytest.mark.parametrize( + ("simulator", "compile_to_qubits"), + [ + (cirq.Simulator, False), + (cirq.Simulator, True), + # Cannot use SparseSimulator without `compile_to_qubits` due to issue #78. + (alpha.SparseSimulator, True), + ]) +def test_undo_qudit(simulator, compile_to_qubits): + light = alpha.QuantumObject("l1", StopLight.GREEN) + board = alpha.QuantumWorld([light], + sampler=simulator(), + compile_to_qubits=compile_to_qubits) + alpha.QuditFlip(3, StopLight.RED.value, StopLight.GREEN.value)(light) + results = board.peek([light], count=200) + assert all(result[0] == StopLight.RED for result in results) + board.undo_last_effect() + results = board.peek([light], count=200) + assert all(result[0] == StopLight.GREEN for result in results) + + +@pytest.mark.parametrize("compile_to_qubits", [False, True]) +def test_undo_no_effects(compile_to_qubits): + board = alpha.QuantumWorld([], compile_to_qubits=compile_to_qubits) with pytest.raises(IndexError, match='No effects'): board.undo_last_effect() +@pytest.mark.parametrize("compile_to_qubits", [False, True]) @pytest.mark.parametrize("simulator", [cirq.Simulator, alpha.SparseSimulator]) -def test_undo_post_select(simulator): +def test_undo_post_select(simulator, compile_to_qubits): light = alpha.QuantumObject("l1", Light.GREEN) light2 = alpha.QuantumObject("l2", Light.RED) light3 = alpha.QuantumObject("l3", Light.RED) - board = alpha.QuantumWorld([light, light2, light3], sampler=simulator()) + board = alpha.QuantumWorld([light, light2, light3], + sampler=simulator(), + compile_to_qubits=compile_to_qubits) alpha.Split()(light, light2, light3) # After split, should be fifty-fifty @@ -170,14 +338,56 @@ def test_undo_post_select(simulator): assert not all(result[0] == Light.GREEN for result in results) +@pytest.mark.parametrize( + ("simulator", "compile_to_qubits"), + [ + (cirq.Simulator, False), + (cirq.Simulator, True), + # Cannot use SparseSimulator without `compile_to_qubits` due to issue #78. + (alpha.SparseSimulator, True), + ]) +def test_undo_post_select_qudits(simulator, compile_to_qubits): + light = alpha.QuantumObject("l1", StopLight.GREEN) + light2 = alpha.QuantumObject("l2", StopLight.RED) + light3 = alpha.QuantumObject("l3", StopLight.RED) + board = alpha.QuantumWorld([light, light2, light3], + sampler=simulator(), + compile_to_qubits=compile_to_qubits) + QuditSplitEffect(3)(light, light2, light3) + + # After split, should be fifty-fifty + results = board.peek([light2, light3], count=200) + assert all(result[0] != result[1] for result in results) + assert not all(result[0] == StopLight.RED for result in results) + assert not all(result[0] == StopLight.GREEN for result in results) + + # After pop, should be consistently one value + popped = board.pop([light2])[0] + results = board.peek([light2, light3], count=200) + assert len(results) == 200 + assert all(result[0] == popped for result in results) + assert all(result[1] != popped for result in results) + + # After undo, should be fifty-fifty + board.undo_last_effect() + results = board.peek([light2, light3], count=200) + assert len(results) == 200 + assert all(result[0] != result[1] for result in results) + assert not all(result[0] == StopLight.RED for result in results) + assert not all(result[0] == StopLight.GREEN for result in results) + + +@pytest.mark.parametrize("compile_to_qubits", [False, True]) @pytest.mark.parametrize("simulator", [cirq.Simulator, alpha.SparseSimulator]) -def test_pop_not_enough_reps(simulator): +def test_pop_not_enough_reps(simulator, compile_to_qubits): """Tests forcing a measurement of a rare outcome, so that peek needs to be called recursively to get more occurances. """ lights = [alpha.QuantumObject("l" + str(i), Light.RED) for i in range(15)] - board = alpha.QuantumWorld(lights, sampler=simulator()) + board = alpha.QuantumWorld(lights, + sampler=simulator(), + compile_to_qubits=compile_to_qubits) alpha.Flip()(lights[0]) alpha.Split()(lights[0], lights[1], lights[2]) alpha.Split()(lights[2], lights[3], lights[4]) @@ -197,15 +407,58 @@ def test_pop_not_enough_reps(simulator): assert all(result[0] == Light.GREEN for result in results) results = board.peek(count=200) assert all(len(result) == 15 for result in results) - assert all(result == [Light.RED] * 14 + [Light.GREEN] for result in results) + assert all(result == [Light.RED] * 14 + [Light.GREEN] + for result in results) + + +@pytest.mark.parametrize( + ("simulator", "compile_to_qubits"), + [ + (cirq.Simulator, False), + (cirq.Simulator, True), + # Cannot use SparseSimulator without `compile_to_qubits` due to issue #78. + (alpha.SparseSimulator, True), + ]) +def test_pop_not_enough_reps_qudits(simulator, compile_to_qubits): + """Tests forcing a measurement of a rare outcome, + so that peek needs to be called recursively to get more + occurances. + """ + lights = [ + alpha.QuantumObject("l" + str(i), StopLight.RED) for i in range(9) + ] + board = alpha.QuantumWorld(lights, + sampler=simulator(), + compile_to_qubits=compile_to_qubits) + alpha.QuditFlip(3, StopLight.RED.value, StopLight.GREEN.value)(lights[0]) + QuditSplitEffect(3)(lights[0], lights[1], lights[2]) + QuditSplitEffect(3)(lights[2], lights[3], lights[4]) + QuditSplitEffect(3)(lights[4], lights[5], lights[6]) + QuditSplitEffect(3)(lights[6], lights[7], lights[8]) + + results = board.peek([lights[8]], count=2000) + assert any(result[0] == StopLight.GREEN for result in results) + assert not all(result[0] == StopLight.GREEN for result in results) + board.force_measurement(lights[8], StopLight.GREEN) + + results = board.peek([lights[8]], count=200) + assert len(results) == 200 + assert all(result[0] == StopLight.GREEN for result in results) + results = board.peek(count=200) + assert all(len(result) == 9 for result in results) + assert all(result == [StopLight.RED] * 8 + [StopLight.GREEN] + for result in results) +@pytest.mark.parametrize("compile_to_qubits", [False, True]) @pytest.mark.parametrize("simulator", [cirq.Simulator, alpha.SparseSimulator]) -def test_pop_qubits_twice(simulator): +def test_pop_qubits_twice(simulator, compile_to_qubits): """Tests popping qubits twice, so that 2 ancillas are created for each qubit.""" lights = [alpha.QuantumObject("l" + str(i), Light.RED) for i in range(3)] - board = alpha.QuantumWorld(lights, sampler=simulator()) + board = alpha.QuantumWorld(lights, + sampler=simulator(), + compile_to_qubits=compile_to_qubits) alpha.Flip()(lights[0]) alpha.Split()(lights[0], lights[1], lights[2]) result = board.pop() @@ -222,40 +475,81 @@ def test_pop_qubits_twice(simulator): assert all(peek_result == result2 for peek_result in peek_results) -def test_combine_overlapping_worlds(): +@pytest.mark.parametrize( + ("simulator", "compile_to_qubits"), + [ + (cirq.Simulator, False), + (cirq.Simulator, True), + # Cannot use SparseSimulator without `compile_to_qubits` due to issue #78. + (alpha.SparseSimulator, True), + ]) +def test_pop_qudits_twice(simulator, compile_to_qubits): + """Tests popping qudits twice, so that 2 ancillas are created + for each qudit.""" + lights = [ + alpha.QuantumObject("l" + str(i), StopLight.RED) for i in range(3) + ] + board = alpha.QuantumWorld(lights, + sampler=simulator(), + compile_to_qubits=compile_to_qubits) + alpha.QuditFlip(3, StopLight.RED.value, StopLight.GREEN.value)(lights[0]) + QuditSplitEffect(3)(lights[0], lights[1], lights[2]) + result = board.pop() + green_on_1 = [StopLight.RED, StopLight.GREEN, StopLight.RED] + green_on_2 = [StopLight.RED, StopLight.RED, StopLight.GREEN] + assert (result == green_on_1) or (result == green_on_2) + QuditSwapEffect(3)(lights[1], lights[2]) + result2 = board.pop() + peek_results = board.peek(count=200) + if result == green_on_1: + assert result2 == green_on_2 + else: + assert result2 == green_on_1 + assert all(peek_result == result2 for peek_result in peek_results) + + +@pytest.mark.parametrize("compile_to_qubits", [False, True]) +def test_combine_overlapping_worlds(compile_to_qubits): l1 = alpha.QuantumObject("l1", Light.GREEN) - world1 = alpha.QuantumWorld([l1]) + world1 = alpha.QuantumWorld([l1], compile_to_qubits=compile_to_qubits) l2 = alpha.QuantumObject("l1", StopLight.YELLOW) - world2 = alpha.QuantumWorld([l2]) + world2 = alpha.QuantumWorld([l2], compile_to_qubits=compile_to_qubits) with pytest.raises(ValueError, match="overlapping"): world1.combine_with(world2) with pytest.raises(ValueError, match="overlapping"): world2.combine_with(world1) -def test_combine_incompatibly_sparse_worlds(): +@pytest.mark.parametrize("compile_to_qubits", [False, True]) +def test_combine_incompatibly_sparse_worlds(compile_to_qubits): l1 = alpha.QuantumObject("l1", Light.GREEN) - world1 = alpha.QuantumWorld([l1], sampler=cirq.Simulator()) + world1 = alpha.QuantumWorld([l1], + sampler=cirq.Simulator(), + compile_to_qubits=compile_to_qubits) l2 = alpha.QuantumObject("l2", StopLight.YELLOW) - world2 = alpha.QuantumWorld([l2], sampler=alpha.SparseSimulator()) + world2 = alpha.QuantumWorld([l2], + sampler=alpha.SparseSimulator(), + compile_to_qubits=compile_to_qubits) with pytest.raises(ValueError, match="sparse"): world1.combine_with(world2) with pytest.raises(ValueError, match="sparse"): world2.combine_with(world1) -def test_combine_worlds(): +@pytest.mark.parametrize("compile_to_qubits", [False, True]) +def test_combine_worlds(compile_to_qubits): l1 = alpha.QuantumObject("l1", Light.GREEN) l2 = alpha.QuantumObject("l2", Light.RED) l3 = alpha.QuantumObject("l3", Light.RED) - world1 = alpha.QuantumWorld([l1, l2, l3]) + world1 = alpha.QuantumWorld([l1, l2, l3], + compile_to_qubits=compile_to_qubits) # Split and pop to test post-selection after combining alpha.Split()(l1, l2, l3) result = world1.pop() l4 = alpha.QuantumObject("stop_light", StopLight.YELLOW) - world2 = alpha.QuantumWorld([l4]) + world2 = alpha.QuantumWorld([l4], compile_to_qubits=compile_to_qubits) world2.combine_with(world1) results = world2.peek(count=100) @@ -265,10 +559,14 @@ def test_combine_worlds(): assert all(actual == expected for actual in results) +@pytest.mark.parametrize("compile_to_qubits", [False, True]) @pytest.mark.parametrize("simulator", [cirq.Simulator, alpha.SparseSimulator]) -def test_get_histogram_and_get_probabilities_one_binary_qobject(simulator): +def test_get_histogram_and_get_probabilities_one_binary_qobject( + simulator, compile_to_qubits): l1 = alpha.QuantumObject("l1", Light.GREEN) - world = alpha.QuantumWorld([l1], sampler=simulator()) + world = alpha.QuantumWorld([l1], + sampler=simulator(), + compile_to_qubits=compile_to_qubits) histogram = world.get_histogram() assert histogram == [{0: 0, 1: 100}] probs = world.get_probabilities() @@ -297,9 +595,20 @@ def test_get_histogram_and_get_probabilities_one_binary_qobject(simulator): assert 0.1 <= bin_probs[0] <= 1.0 -def test_get_histogram_and_get_probabilities_one_trinary_qobject(): +@pytest.mark.parametrize( + ("simulator", "compile_to_qubits"), + [ + (cirq.Simulator, False), + (cirq.Simulator, True), + # Cannot use SparseSimulator without `compile_to_qubits` due to issue #78. + (alpha.SparseSimulator, True), + ]) +def test_get_histogram_and_get_probabilities_one_trinary_qobject( + simulator, compile_to_qubits): l1 = alpha.QuantumObject("l1", StopLight.YELLOW) - world = alpha.QuantumWorld([l1]) + world = alpha.QuantumWorld([l1], + sampler=simulator(), + compile_to_qubits=compile_to_qubits) histogram = world.get_histogram() assert histogram == [{0: 0, 1: 100, 2: 0}] probs = world.get_probabilities() @@ -308,10 +617,21 @@ def test_get_histogram_and_get_probabilities_one_trinary_qobject(): assert bin_probs == [1.0] -def test_get_histogram_and_get_probabilities_two_qobjects(): +@pytest.mark.parametrize( + ("simulator", "compile_to_qubits"), + [ + (cirq.Simulator, False), + (cirq.Simulator, True), + # Cannot use SparseSimulator without `compile_to_qubits` due to issue #78. + (alpha.SparseSimulator, True), + ]) +def test_get_histogram_and_get_probabilities_two_qobjects( + simulator, compile_to_qubits): l1 = alpha.QuantumObject("l1", Light.GREEN) l2 = alpha.QuantumObject("l2", StopLight.YELLOW) - world = alpha.QuantumWorld([l1, l2]) + world = alpha.QuantumWorld([l1, l2], + sampler=simulator(), + compile_to_qubits=compile_to_qubits) histogram = world.get_histogram() assert histogram == [{0: 0, 1: 100}, {0: 0, 1: 100, 2: 0}] probs = world.get_probabilities() diff --git a/unitary/alpha/qubit_effects_test.py b/unitary/alpha/qubit_effects_test.py index 1fb13fb3..6e2a33de 100644 --- a/unitary/alpha/qubit_effects_test.py +++ b/unitary/alpha/qubit_effects_test.py @@ -21,27 +21,30 @@ from unitary.alpha.sparse_vector_simulator import SparseSimulator +@pytest.mark.parametrize("compile_to_qubits", [False, True]) @pytest.mark.parametrize("simulator", [cirq.Simulator, SparseSimulator]) -def test_flip(simulator): - board = alpha.QuantumWorld(sampler=simulator()) +def test_flip(simulator, compile_to_qubits): + board = alpha.QuantumWorld(sampler=simulator(), compile_to_qubits=compile_to_qubits) piece = alpha.QuantumObject("t", 0) board.add_object(piece) alpha.Flip()(piece) assert board.circuit == cirq.Circuit(cirq.X(cirq.NamedQubit("t"))) +@pytest.mark.parametrize("compile_to_qubits", [False, True]) @pytest.mark.parametrize("simulator", [cirq.Simulator, SparseSimulator]) -def test_superposition(simulator): - board = alpha.QuantumWorld(sampler=simulator()) +def test_superposition(simulator, compile_to_qubits): + board = alpha.QuantumWorld(sampler=simulator(), compile_to_qubits=compile_to_qubits) piece = alpha.QuantumObject("t", 0) board.add_object(piece) alpha.Superposition()(piece) assert board.circuit == cirq.Circuit(cirq.H(cirq.NamedQubit("t"))) +@pytest.mark.parametrize("compile_to_qubits", [False, True]) @pytest.mark.parametrize("simulator", [cirq.Simulator, SparseSimulator]) -def test_move(simulator): - board = alpha.QuantumWorld(sampler=simulator()) +def test_move(simulator, compile_to_qubits): + board = alpha.QuantumWorld(sampler=simulator(), compile_to_qubits=compile_to_qubits) piece1 = alpha.QuantumObject("a", 1) piece2 = alpha.QuantumObject("b", 0) board.add_object(piece1) @@ -59,9 +62,10 @@ def test_move(simulator): assert all(result == [0, 1] for result in results) +@pytest.mark.parametrize("compile_to_qubits", [False, True]) @pytest.mark.parametrize("simulator", [cirq.Simulator, SparseSimulator]) -def test_phased_move(simulator): - board = alpha.QuantumWorld(sampler=simulator()) +def test_phased_move(simulator, compile_to_qubits): + board = alpha.QuantumWorld(sampler=simulator(), compile_to_qubits=compile_to_qubits) piece1 = alpha.QuantumObject("a", 1) piece2 = alpha.QuantumObject("b", 0) board.add_object(piece1) @@ -79,9 +83,10 @@ def test_phased_move(simulator): assert all(result == [0, 1] for result in results) +@pytest.mark.parametrize("compile_to_qubits", [False, True]) @pytest.mark.parametrize("simulator", [cirq.Simulator, SparseSimulator]) -def test_split(simulator): - board = alpha.QuantumWorld(sampler=simulator()) +def test_split(simulator, compile_to_qubits): + board = alpha.QuantumWorld(sampler=simulator(), compile_to_qubits=compile_to_qubits) piece1 = alpha.QuantumObject("a", 1) piece2 = alpha.QuantumObject("b", 0) piece3 = alpha.QuantumObject("c", 0) @@ -100,9 +105,10 @@ def test_split(simulator): assert board.circuit == expected_circuit +@pytest.mark.parametrize("compile_to_qubits", [False, True]) @pytest.mark.parametrize("simulator", [cirq.Simulator, SparseSimulator]) -def test_phased_split(simulator): - board = alpha.QuantumWorld(sampler=simulator()) +def test_phased_split(simulator, compile_to_qubits): + board = alpha.QuantumWorld(sampler=simulator(), compile_to_qubits=compile_to_qubits) piece1 = alpha.QuantumObject("a", 1) piece2 = alpha.QuantumObject("b", 0) piece3 = alpha.QuantumObject("c", 0) diff --git a/unitary/alpha/qudit_effects_test.py b/unitary/alpha/qudit_effects_test.py index 089ab0a2..2e5c7f34 100644 --- a/unitary/alpha/qudit_effects_test.py +++ b/unitary/alpha/qudit_effects_test.py @@ -13,8 +13,7 @@ # limitations under the License. # import enum - -import cirq +import pytest import unitary.alpha as alpha @@ -25,8 +24,9 @@ class StopLight(enum.Enum): GREEN = 2 -def test_qudit_cycle(): - board = alpha.QuantumWorld() +@pytest.mark.parametrize("compile_to_qubits", [False, True]) +def test_qudit_cycle(compile_to_qubits): + board = alpha.QuantumWorld(compile_to_qubits=compile_to_qubits) piece = alpha.QuantumObject("t", StopLight.GREEN) board.add_object(piece) alpha.QuditCycle(3)(piece) @@ -43,8 +43,9 @@ def test_qudit_cycle(): assert all(result == [StopLight.YELLOW] for result in results) -def test_qudit_flip(): - board = alpha.QuantumWorld() +@pytest.mark.parametrize("compile_to_qubits", [False, True]) +def test_qudit_flip(compile_to_qubits): + board = alpha.QuantumWorld(compile_to_qubits=compile_to_qubits) piece = alpha.QuantumObject("t", StopLight.GREEN) board.add_object(piece) alpha.QuditFlip(3, 0, 2)(piece) diff --git a/unitary/alpha/qudit_state_transform.py b/unitary/alpha/qudit_state_transform.py index f9a8d522..2fe553c1 100644 --- a/unitary/alpha/qudit_state_transform.py +++ b/unitary/alpha/qudit_state_transform.py @@ -17,6 +17,14 @@ import numpy as np +def num_bits(num: int) -> int: + """Returns the minimum number of bits needed to represent the input.""" + result = 1 + while num > 2**result: + result += 1 + return result + + def _nearest_power_of_two_ceiling(qudit_dim: int) -> int: """Returns the smallest power of two greater than or equal to qudit_dim.""" if qudit_dim == 0: diff --git a/unitary/alpha/sparse_vector_simulator_test.py b/unitary/alpha/sparse_vector_simulator_test.py index 3ab607cf..40e88729 100644 --- a/unitary/alpha/sparse_vector_simulator_test.py +++ b/unitary/alpha/sparse_vector_simulator_test.py @@ -2,6 +2,7 @@ import cirq from cirq.testing import random_circuit +from unitary.alpha import qudit_gates from unitary.alpha.sparse_vector_simulator import ( SparseSimulator, @@ -41,6 +42,17 @@ def test_simulation_fidelity(): assert abs(test_ones_fraction - validation_ones_fraction) < 0.1 +def test_simulation_fidelity_qudits_fails(): + """Check that SparseSimulator does not support Qudit operations yet. + + TODO(#78): Fix this. + """ + qudit = cirq.NamedQid("a", 3) + circuit = cirq.Circuit(qudit_gates.QuditXGate(3).on(qudit), cirq.measure(qudit)) + with pytest.raises(ValueError, match="size 2 is different from 3"): + _ = SparseSimulator().run(circuit).measurements + + def test_post_selection(): """Test a circuit with PostSelectOperation.""" sim = SparseSimulator() diff --git a/unitary/examples/tictactoe/tic_tac_split_test.py b/unitary/examples/tictactoe/tic_tac_split_test.py index 6f536168..775818a9 100644 --- a/unitary/examples/tictactoe/tic_tac_split_test.py +++ b/unitary/examples/tictactoe/tic_tac_split_test.py @@ -82,6 +82,7 @@ def test_diagram(): ) +@pytest.mark.parametrize("compile_to_qubits", [False, True]) @pytest.mark.parametrize( "mark, ruleset", [ @@ -91,10 +92,14 @@ def test_diagram(): (tictactoe.TicTacSquare.O, tictactoe.TicTacRules.QUANTUM_V3), ], ) -def test_tic_tac_split(mark: tictactoe.TicTacSquare, ruleset: tictactoe.TicTacRules): +def test_tic_tac_split( + mark: tictactoe.TicTacSquare, + ruleset: tictactoe.TicTacRules, + compile_to_qubits: bool, +): a = alpha.QuantumObject("a", tictactoe.TicTacSquare.EMPTY) b = alpha.QuantumObject("b", tictactoe.TicTacSquare.EMPTY) - board = alpha.QuantumWorld([a, b]) + board = alpha.QuantumWorld([a, b], compile_to_qubits=compile_to_qubits) tictactoe.TicTacSplit(mark, ruleset)(a, b) results = board.peek(count=1000) on_a = [mark, tictactoe.TicTacSquare.EMPTY] @@ -104,11 +109,12 @@ def test_tic_tac_split(mark: tictactoe.TicTacSquare, ruleset: tictactoe.TicTacRu assert all(r == on_a or r == on_b for r in results) -def test_tic_tac_split_entangled_v2(): +@pytest.mark.parametrize("compile_to_qubits", [False, True]) +def test_tic_tac_split_entangled_v2(compile_to_qubits): a = alpha.QuantumObject("a", tictactoe.TicTacSquare.EMPTY) b = alpha.QuantumObject("b", tictactoe.TicTacSquare.EMPTY) c = alpha.QuantumObject("c", tictactoe.TicTacSquare.EMPTY) - board = alpha.QuantumWorld([a, b, c]) + board = alpha.QuantumWorld([a, b, c], compile_to_qubits=compile_to_qubits) ruleset = tictactoe.TicTacRules.QUANTUM_V2 tictactoe.TicTacSplit(tictactoe.TicTacSquare.X, ruleset)(a, b) tictactoe.TicTacSplit(tictactoe.TicTacSquare.O, ruleset)(b, c) @@ -122,11 +128,12 @@ def test_tic_tac_split_entangled_v2(): assert all(r == on_ab or r == on_ac or r == on_b for r in results) -def test_tic_tac_split_entangled_v3(): +@pytest.mark.parametrize("compile_to_qubits", [False, True]) +def test_tic_tac_split_entangled_v3(compile_to_qubits): a = alpha.QuantumObject("a", tictactoe.TicTacSquare.EMPTY) b = alpha.QuantumObject("b", tictactoe.TicTacSquare.EMPTY) c = alpha.QuantumObject("c", tictactoe.TicTacSquare.EMPTY) - board = alpha.QuantumWorld([a, b, c]) + board = alpha.QuantumWorld([a, b, c], compile_to_qubits=compile_to_qubits) ruleset = tictactoe.TicTacRules.QUANTUM_V3 tictactoe.TicTacSplit(tictactoe.TicTacSquare.X, ruleset)(a, b) tictactoe.TicTacSplit(tictactoe.TicTacSquare.O, ruleset)(b, c) @@ -148,11 +155,12 @@ def test_tic_tac_split_entangled_v3(): ) -def test_tic_tac_split_entangled_v3_empty(): +@pytest.mark.parametrize("compile_to_qubits", [False, True]) +def test_tic_tac_split_entangled_v3_empty(compile_to_qubits): a = alpha.QuantumObject("a", tictactoe.TicTacSquare.EMPTY) b = alpha.QuantumObject("b", tictactoe.TicTacSquare.EMPTY) c = alpha.QuantumObject("c", tictactoe.TicTacSquare.EMPTY) - board = alpha.QuantumWorld([a, b, c]) + board = alpha.QuantumWorld([a, b, c], compile_to_qubits=compile_to_qubits) ruleset = tictactoe.TicTacRules.QUANTUM_V3 tictactoe.TicTacSplit(tictactoe.TicTacSquare.X, ruleset)(a, b) tictactoe.TicTacSplit(tictactoe.TicTacSquare.O, ruleset)(c, b) diff --git a/unitary/examples/tictactoe/tic_tac_toe.py b/unitary/examples/tictactoe/tic_tac_toe.py index db22fdfc..770413a0 100644 --- a/unitary/examples/tictactoe/tic_tac_toe.py +++ b/unitary/examples/tictactoe/tic_tac_toe.py @@ -18,7 +18,6 @@ from unitary.examples.tictactoe.enums import TicTacSquare, TicTacResult, TicTacRules from unitary.examples.tictactoe.tic_tac_split import TicTacSplit - _SQUARE_NAMES = "abcdefghi" _MARK_SYMBOLS = {TicTacSquare.EMPTY: ".", TicTacSquare.X: "X", TicTacSquare.O: "O"} @@ -99,13 +98,18 @@ class TicTacToe: Results can be viewed by using `print()` to see the board in a human-friendly text format or by using `sample()` to get one or more samples (measurements) of the board. + + Set `run_on_hardware` to compile the board to qubits for running on + actual hardware. """ - def __init__(self, rules: TicTacRules = TicTacRules.QUANTUM_V3): - self.clear() + def __init__( + self, rules: TicTacRules = TicTacRules.QUANTUM_V3, run_on_hardware: bool = False + ): + self.clear(run_on_hardware) self.rules = rules - def clear(self) -> None: + def clear(self, run_on_hardware: bool = False) -> None: """Clears the TicTacToe board. Sets all 9 squares to empty. @@ -116,7 +120,9 @@ def clear(self) -> None: for name in _SQUARE_NAMES: self.empty_squares.add(name) self.squares[name] = QuantumObject(name, TicTacSquare.EMPTY) - self.board = QuantumWorld(list(self.squares.values())) + self.board = QuantumWorld( + list(self.squares.values()), compile_to_qubits=run_on_hardware + ) def result(self) -> TicTacResult: """Returns the result of the TicTacToe game. diff --git a/unitary/examples/tictactoe/tic_tac_toe_test.py b/unitary/examples/tictactoe/tic_tac_toe_test.py index bb911c33..153f4704 100644 --- a/unitary/examples/tictactoe/tic_tac_toe_test.py +++ b/unitary/examples/tictactoe/tic_tac_toe_test.py @@ -53,8 +53,9 @@ def test_eval_board(result, expected): assert tictactoe.tic_tac_toe.eval_board(result) == expected -def test_measure(): - board = tictactoe.TicTacToe() +@pytest.mark.parametrize("run_on_hardware", [False, True]) +def test_measure(run_on_hardware): + board = tictactoe.TicTacToe(run_on_hardware=run_on_hardware) board.move("ab", tictactoe.TicTacSquare.X) board.move("cd", tictactoe.TicTacSquare.O) board.move("ef", tictactoe.TicTacSquare.X) @@ -95,8 +96,9 @@ def test_measure(): assert all(result == expected for result in results) -def test_sample(): - board = tictactoe.TicTacToe() +@pytest.mark.parametrize("run_on_hardware", [False, True]) +def test_sample(run_on_hardware): + board = tictactoe.TicTacToe(run_on_hardware=run_on_hardware) board.move("a", tictactoe.TicTacSquare.X) board.move("e", tictactoe.TicTacSquare.O) results = board.sample(count=100) @@ -109,8 +111,9 @@ def test_sample(): assert all(result == "X...O..OX" for result in results) -def test_split(): - board = tictactoe.TicTacToe() +@pytest.mark.parametrize("run_on_hardware", [False, True]) +def test_split(run_on_hardware): + board = tictactoe.TicTacToe(run_on_hardware=run_on_hardware) board.move("ab", tictactoe.TicTacSquare.X) results = board.sample(count=1000) assert len(results) == 1000 @@ -121,21 +124,27 @@ def test_split(): assert all(result == in_a or result == in_b for result in results) -def test_rulesets(): +@pytest.mark.parametrize("run_on_hardware", [False, True]) +def test_rulesets(run_on_hardware): # Try to make a quantum move with classical rules - board = tictactoe.TicTacToe(tictactoe.TicTacRules.CLASSICAL) + board = tictactoe.TicTacToe( + tictactoe.TicTacRules.CLASSICAL, run_on_hardware=run_on_hardware + ) with pytest.raises(ValueError): board.move("ab", tictactoe.TicTacSquare.X) # Try to make a split move on non-empty squares at minimal quantum - board = tictactoe.TicTacToe(tictactoe.TicTacRules.QUANTUM_V1) + board = tictactoe.TicTacToe( + tictactoe.TicTacRules.QUANTUM_V1, run_on_hardware=run_on_hardware + ) board.move("a", tictactoe.TicTacSquare.X) with pytest.raises(ValueError): board.move("ab", tictactoe.TicTacSquare.O) -def test_print(): - board = tictactoe.TicTacToe() +@pytest.mark.parametrize("run_on_hardware", [False, True]) +def test_print(run_on_hardware): + board = tictactoe.TicTacToe(run_on_hardware=run_on_hardware) board.move("a", tictactoe.TicTacSquare.X) board.move("e", tictactoe.TicTacSquare.O) output = board.print() diff --git a/unitary/quantum_chess/ascii_board.py b/unitary/quantum_chess/ascii_board.py index a0ffd721..31219177 100644 --- a/unitary/quantum_chess/ascii_board.py +++ b/unitary/quantum_chess/ascii_board.py @@ -17,7 +17,6 @@ class AsciiBoard: """ def __init__(self, size: int = 8, reps: int = 1000, board=None): - self.size = size self.reps = reps self.board = board or qb.CirqBoard(0) diff --git a/unitary/quantum_chess/circuit_transformer.py b/unitary/quantum_chess/circuit_transformer.py index ec3c85ed..300708ce 100644 --- a/unitary/quantum_chess/circuit_transformer.py +++ b/unitary/quantum_chess/circuit_transformer.py @@ -402,7 +402,6 @@ def __call__( def transform_controlled_iswap(op: cirq.Operation, index: int) -> cirq.OP_TREE: - if len(op.qubits) <= 3 and isinstance(op, cirq.ControlledOperation): if not all(v == 1 for values in op.control_values for v in values): return op