From e3a7fbcb33fb0429079b0a164dd0270fc16c44bb Mon Sep 17 00:00:00 2001 From: Pengfei Chen Date: Wed, 25 Oct 2023 12:48:28 -0700 Subject: [PATCH] up --- unitary/alpha/quantum_world.py | 13 ++++++++--- .../examples/quantum_chinese_chess/board.py | 12 ++++++++++ .../examples/quantum_chinese_chess/chess.py | 7 +++--- .../examples/quantum_chinese_chess/move.py | 6 ++--- .../quantum_chinese_chess/move_test.py | 15 ++++++++----- .../quantum_chinese_chess/test_utils.py | 22 ++++--------------- 6 files changed, 43 insertions(+), 32 deletions(-) diff --git a/unitary/alpha/quantum_world.py b/unitary/alpha/quantum_world.py index f5f74bb3..f72b1c6e 100644 --- a/unitary/alpha/quantum_world.py +++ b/unitary/alpha/quantum_world.py @@ -306,10 +306,17 @@ def _interpret_result(self, result: Union[int, Iterable[int]]) -> int: return result def unhook(self, object: QuantumObject) -> None: - """Replaces all usages of the given object in the circuit with a new ancilla with value=0.""" - # Creates a new ancilla. + """Replace all usages of the given `object` in the circuit with a new ancilla, + so that + - all former operations on `object` will be applied on the new ancilla; + - future operations on `object` start with its new reset value. + + Note that we don't do force measurement on it, since we don't care about its + current value but just want to reset it. + """ + # Create a new ancilla. new_ancilla = self._add_ancilla(object.name) - # Replace operations using the qubit of the given object with the new ancilla. + # Replace operations of the given `object` with the new ancilla. qubit_remapping_dict = { object.qubit: new_ancilla.qubit, new_ancilla.qubit: object.qubit, diff --git a/unitary/examples/quantum_chinese_chess/board.py b/unitary/examples/quantum_chinese_chess/board.py index efd3fbf4..4c824c55 100644 --- a/unitary/examples/quantum_chinese_chess/board.py +++ b/unitary/examples/quantum_chinese_chess/board.py @@ -184,3 +184,15 @@ def flying_general_check(self) -> bool: # If there are no pieces between two KINGs, the check successes. Game ends. return True # TODO(): add check when there are quantum pieces in between. + + def sample(self, repetitions: int) -> List[int]: + """Sample the current board by the given `repetitions`. + Returns a list of 90-bit bitstring, each corresponding to one sample. + """ + samples = self.board.peek(count=repetitions, convert_to_enum=False) + # Convert peek results (in List[List[int]]) into List[int]. + samples = [ + int("0b" + "".join([str(i) for i in sample[::-1]]), base=2) + for sample in samples + ] + return samples diff --git a/unitary/examples/quantum_chinese_chess/chess.py b/unitary/examples/quantum_chinese_chess/chess.py index 27aa8bc6..fad477e5 100644 --- a/unitary/examples/quantum_chinese_chess/chess.py +++ b/unitary/examples/quantum_chinese_chess/chess.py @@ -386,15 +386,16 @@ def apply_move(self, str_to_parse: str) -> None: quantum_pieces_1, ) - print(move_type, " ", move_variant) + if self.debug_level > 1: + print(move_type, " ", move_variant) # Apply the move accoding to its type. if move_type == MoveType.CLASSICAL: if source_0.type_ == Type.KING: # Update the locations of KING. self.board.king_locations[self.current_player] = targets[0] - # TODO(): only make such prints for a certain debug level. - print(f"Updated king locations: {self.board.king_locations}.") + if self.debug_level > 1: + print(f"Updated king locations: {self.board.king_locations}.") if target_0.type_ == Type.KING: # King is captured, then the game is over. self.game_state = GameState(self.current_player) diff --git a/unitary/examples/quantum_chinese_chess/move.py b/unitary/examples/quantum_chinese_chess/move.py index 6f8bddce..1b2cdd59 100644 --- a/unitary/examples/quantum_chinese_chess/move.py +++ b/unitary/examples/quantum_chinese_chess/move.py @@ -165,9 +165,9 @@ def effect(self, *objects) -> Iterator[cirq.Operation]: target_0.reset() elif self.move_variant == MoveVariant.CLASSICAL: if target_0.type_ != Type.EMPTY: - # For classical moves with target_0 occupied, we replace the qubit of target_0 with - # a new ancilla, and set its classical properties to be EMPTY. - world.unhook(target_0) + # For classical moves with target_0 occupied, we flip target_0 to be empty, + # and set its classical properties to be EMPTY. + alpha.Flip()(target_0) target_0.reset() # Make the jump move. diff --git a/unitary/examples/quantum_chinese_chess/move_test.py b/unitary/examples/quantum_chinese_chess/move_test.py index 0c8e156f..d6ae9a6d 100644 --- a/unitary/examples/quantum_chinese_chess/move_test.py +++ b/unitary/examples/quantum_chinese_chess/move_test.py @@ -31,7 +31,6 @@ assert_this_or_that, assert_prob_about, assert_fifty_fifty, - sample_board, get_board_probability_distribution, print_samples, set_board, @@ -156,7 +155,7 @@ def test_jump_classical(): assert_samples_in(board, [locations_to_bitboard(["b1"])]) -def test_jump_capture(): +def test_jump_capture_quantum_source(): # Source is in quantum state. board = set_board(["a1", "b1"]) world = board.board @@ -166,7 +165,7 @@ def test_jump_capture(): assert_fifty_fifty(board_probabilities, locations_to_bitboard(["a2", "b1"])) assert_fifty_fifty(board_probabilities, locations_to_bitboard(["a3", "b1"])) Jump(MoveVariant.CAPTURE)(world["a2"], world["b1"]) - # pop() will break the supersition and only one of the following two states are possible. + # pop() will break the superposition and only one of the following two states are possible. # We check the ancilla to learn if the jump was applied or not. source_is_occupied = world.post_selection[world["ancilla_a2_0"]] if source_is_occupied: @@ -174,6 +173,8 @@ def test_jump_capture(): else: assert_samples_in(board, [locations_to_bitboard(["a3", "b1"])]) + +def test_jump_capture_quantum_target(): # Target is in quantum state. board = set_board(["a1", "b1"]) world = board.board @@ -184,6 +185,8 @@ def test_jump_capture(): assert_fifty_fifty(board_probabilities, locations_to_bitboard(["b2"])) assert_fifty_fifty(board_probabilities, locations_to_bitboard(["b2", "b3"])) + +def test_jump_capture_quantum_source_and_target(): # Both source and target are in quantum state. board = set_board(["a1", "b1"]) world = board.board @@ -212,13 +215,13 @@ def test_jump_capture(): assert_fifty_fifty(board_probabilities, locations_to_bitboard(["a3", "b3"])) -def test_jump_excluded(): +def test_jump_excluded_quantum_target(): # Target is in quantum state. board = set_board(["a1", "b1"]) world = board.board alpha.PhasedSplit()(world["b1"], world["b2"], world["b3"]) Jump(MoveVariant.EXCLUDED)(world["a1"], world["b2"]) - # pop() will break the supersition and only one of the following two states are possible. + # pop() will break the superposition and only one of the following two states are possible. # We check the ancilla to learn if the jump was applied or not. target_is_occupied = world.post_selection[world["ancilla_b2_0"]] print(target_is_occupied) @@ -227,6 +230,8 @@ def test_jump_excluded(): else: assert_samples_in(board, [locations_to_bitboard(["b2", "b3"])]) + +def test_jump_excluded_quantum_source_and_target(): # Both source and target are in quantum state. board = set_board(["a1", "b1"]) world = board.board diff --git a/unitary/examples/quantum_chinese_chess/test_utils.py b/unitary/examples/quantum_chinese_chess/test_utils.py index 1c467a0b..8f4c6f24 100644 --- a/unitary/examples/quantum_chinese_chess/test_utils.py +++ b/unitary/examples/quantum_chinese_chess/test_utils.py @@ -78,19 +78,6 @@ def bitboard_to_locations(bitboard: int) -> List[str]: return locations -def sample_board(board: Board, repetitions: int) -> List[int]: - """Sample the given `board` by the given `repetitions`. - Returns a list of 90-bit bitstring, each corresponding to one sample. - """ - samples = board.board.peek(count=repetitions, convert_to_enum=False) - # Convert peek results (in List[List[int]]) into List[int]. - samples = [ - int("0b" + "".join([str(i) for i in sample[::-1]]), base=2) - for sample in samples - ] - return samples - - def print_samples(samples: List[int]) -> None: """Aggregate all the samples and print the dictionary of {locations: count}.""" sample_dict = {} @@ -111,7 +98,7 @@ def get_board_probability_distribution( """ board_probabilities: Dict[int, float] = {} - samples = sample_board(board, repetitions) + samples = board.sample(repetitions) for sample in samples: if sample not in board_probabilities: board_probabilities[sample] = 0.0 @@ -128,8 +115,7 @@ def assert_samples_in(board: Board, probabilities: Dict[int, float]) -> None: the given `probabilities` (i.e. a map from bitstring into its possibility), and that each possibility is represented at least once in the samples. """ - samples = sample_board(board, 500) - assert len(samples) == 500 + samples = board.sample(500) all_in = all(sample in probabilities for sample in samples) assert all_in, print_samples(samples) # Make sure each possibility is represented at least once. @@ -148,7 +134,7 @@ def assert_sample_distribution( """ n_samples = 500 assert abs(sum(probabilities.values()) - 1) < 1e-9 - samples = sample_board(board, n_samples) + samples = board.sample(n_samples) counts = defaultdict(int) for sample in samples: assert sample in probabilities, bitboard_to_locations(sample) @@ -176,7 +162,7 @@ def assert_this_or_that(samples: List[int], this: int, that: int) -> None: def assert_prob_about( - probabilities: Dict[int, float], that: int, expected: float, atol: float = 0.05 + probabilities: Dict[int, float], that: int, expected: float, atol: float = 0.06 ) -> None: """Checks that the probability of `that` is within `atol` of the value of `expected`.""" assert that in probabilities, print_samples(list(probabilities.keys()))