diff --git a/unitary/examples/quantum_chinese_chess/move.py b/unitary/examples/quantum_chinese_chess/move.py index 1b2cdd59..806d7566 100644 --- a/unitary/examples/quantum_chinese_chess/move.py +++ b/unitary/examples/quantum_chinese_chess/move.py @@ -114,6 +114,8 @@ class Jump(QuantumEffect): - CAPTURE - EXCLUDED - BASIC + + All types of pieces could make Jump move. """ def __init__( @@ -176,3 +178,62 @@ def effect(self, *objects) -> Iterator[cirq.Operation]: target_0.reset(source_0) source_0.reset() return iter(()) + + +class SplitJump(QuantumEffect): + """SplitJump from source_0 to target_0 and target_1. The only accepted (default) move_variant is + - BASIC. + + All types of pieces could make SplitJump move, except KING. This is implemented with a + PhasedSplit(). + """ + + def __init__( + self, + ): + return + + def num_dimension(self) -> Optional[int]: + return 2 + + def num_objects(self) -> Optional[int]: + return 3 + + def effect(self, *objects) -> Iterator[cirq.Operation]: + source_0, target_0, target_1 = objects + # Make the split jump. + source_0.is_entangled = True + alpha.PhasedSplit()(source_0, target_0, target_1) + # Pass the classical properties of the source piece to the target pieces. + target_0.reset(source_0) + target_1.reset(source_0) + return iter(()) + + +class MergeJump(QuantumEffect): + """MergeJump from source_0 to source_1 to target_0. The only accepted (default) move_variant is + - BASIC. + + All types of pieces could make MergeJump move, except KING. + """ + + def __init__( + self, + ): + return + + def num_dimension(self) -> Optional[int]: + return 2 + + def num_objects(self) -> Optional[int]: + return 3 + + def effect(self, *objects) -> Iterator[cirq.Operation]: + source_0, source_1, target_0 = objects + # Make the merge jump. + alpha.PhasedMove(-0.5)(source_0, target_0) + alpha.PhasedMove(-0.5)(source_0, target_0) + alpha.PhasedMove(-0.5)(source_1, target_0) + # Pass the classical properties of the source pieces to the target piece. + target_0.reset(source_0) + return iter(()) diff --git a/unitary/examples/quantum_chinese_chess/move_test.py b/unitary/examples/quantum_chinese_chess/move_test.py index d6ae9a6d..f3ba9a66 100644 --- a/unitary/examples/quantum_chinese_chess/move_test.py +++ b/unitary/examples/quantum_chinese_chess/move_test.py @@ -11,7 +11,7 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -from unitary.examples.quantum_chinese_chess.move import Move, Jump +from unitary.examples.quantum_chinese_chess.move import * from unitary.examples.quantum_chinese_chess.board import Board from unitary.examples.quantum_chinese_chess.piece import Piece import pytest @@ -143,7 +143,7 @@ def test_to_str(): def test_jump_classical(): - # Target is empty. + """Target is empty.""" board = set_board(["a1", "b1"]) world = board.board # TODO(): try move all varaibles declarations of a1 = world["a1"] into a function. @@ -156,7 +156,7 @@ def test_jump_classical(): def test_jump_capture_quantum_source(): - # Source is in quantum state. + """Source is in quantum state.""" board = set_board(["a1", "b1"]) world = board.board alpha.PhasedSplit()(world["a1"], world["a2"], world["a3"]) @@ -175,7 +175,7 @@ def test_jump_capture_quantum_source(): def test_jump_capture_quantum_target(): - # Target is in quantum state. + """Target is in quantum state.""" board = set_board(["a1", "b1"]) world = board.board alpha.PhasedSplit()(world["b1"], world["b2"], world["b3"]) @@ -187,7 +187,7 @@ def test_jump_capture_quantum_target(): def test_jump_capture_quantum_source_and_target(): - # Both source and target are in quantum state. + """Both source and target are in quantum state.""" board = set_board(["a1", "b1"]) world = board.board alpha.PhasedSplit()(world["a1"], world["a2"], world["a3"]) @@ -216,7 +216,7 @@ def test_jump_capture_quantum_source_and_target(): def test_jump_excluded_quantum_target(): - # Target is in quantum state. + """Target is in quantum state.""" board = set_board(["a1", "b1"]) world = board.board alpha.PhasedSplit()(world["b1"], world["b2"], world["b3"]) @@ -232,7 +232,7 @@ def test_jump_excluded_quantum_target(): def test_jump_excluded_quantum_source_and_target(): - # Both source and target are in quantum state. + """Both source and target are in quantum state.""" board = set_board(["a1", "b1"]) world = board.board alpha.PhasedSplit()(world["a1"], world["a2"], world["a3"]) @@ -252,7 +252,7 @@ def test_jump_excluded_quantum_source_and_target(): def test_jump_basic(): - # Source is in quantum state. + """Source is in quantum state.""" board = set_board(["a1"]) world = board.board alpha.PhasedSplit()(world["a1"], world["a2"], world["a3"]) @@ -261,3 +261,123 @@ def test_jump_basic(): assert len(board_probabilities) == 2 assert_fifty_fifty(board_probabilities, locations_to_bitboard(["d1"])) assert_fifty_fifty(board_probabilities, locations_to_bitboard(["a3"])) + + +def test_split_jump_classical_source(): + """Source is in classical state.""" + board = set_board(["a1"]) + world = board.board + SplitJump()(world["a1"], world["a2"], world["a3"]) + board_probabilities = get_board_probability_distribution(board, 1000) + assert len(board_probabilities) == 2 + assert_fifty_fifty(board_probabilities, locations_to_bitboard(["a2"])) + assert_fifty_fifty(board_probabilities, locations_to_bitboard(["a3"])) + assert world["a2"].type_ == Type.ROOK + assert world["a2"].color == Color.RED + assert world["a2"].is_entangled == True + assert world["a3"].type_ == Type.ROOK + assert world["a3"].color == Color.RED + assert world["a3"].is_entangled == True + + +def test_split_jump_quantum_source(): + """Source is in quantum state.""" + board = set_board(["a1"]) + world = board.board + alpha.PhasedSplit()(world["a1"], world["a2"], world["a3"]) + SplitJump()(world["a3"], world["a4"], world["a5"]) + assert_sample_distribution( + board, + { + locations_to_bitboard(["a2"]): 0.5, + locations_to_bitboard(["a4"]): 0.25, + locations_to_bitboard(["a5"]): 0.25, + }, + ) + assert world["a4"].is_entangled == True + assert world["a5"].is_entangled == True + + +def test_merge_jump_perfect_merge(): + """Two quantum pieces split from one source could be merge back to one.""" + board = set_board(["a1"]) + world = board.board + SplitJump()(world["a1"], world["a2"], world["a3"]) + MergeJump()(world["a2"], world["a3"], world["a1"]) + assert_samples_in(board, {locations_to_bitboard(["a1"]): 1.0}) + + +def test_merge_jump_imperfect_merge_scenario_1(): + """Imperfect merge scenario 1""" + board = set_board(["a1"]) + world = board.board + SplitJump()(world["a1"], world["a2"], world["a3"]) + SplitJump()(world["a3"], world["a4"], world["a5"]) + # a2 has prob. 0.5 to be occupied, while a4 has prob. 0.25 to be occupied + MergeJump()(world["a2"], world["a4"], world["a6"]) + # Accoding to matrix calculations, the ending coefficient of + # a5 to be occupied: -1/2; + # a6 to be occupied: 1/2 + i/2/sqrt(2) + # a4 to be occupied: -i/2 -1/2/sqrt(2) + # a2 to be occupied: 0 + assert_sample_distribution( + board, + { + locations_to_bitboard(["a5"]): 1.0 / 4, + locations_to_bitboard(["a6"]): 3.0 / 8, + locations_to_bitboard(["a4"]): 3.0 / 8, + }, + ) + + +def test_merge_jump_imperfect_merge_scenario_2(): + """Imperfect merge scenario 2 + Two quantum pieces split from two sources could not be merge into to one. + """ + board = set_board(["a1", "b1"]) + world = board.board + SplitJump()(world["a1"], world["a2"], world["a3"]) + SplitJump()(world["b1"], world["b2"], world["b3"]) + MergeJump()(world["a2"], world["b2"], world["c2"]) + # According to matrix calculations, the ending coefficient of + # [a3, b3]: 1/2 + # [a3, c2]: i/2/sqrt(2) + # [a3, b2]: -1/2/sqrt(2) + # [b3, c2]: i/2/sqrt(2) + # [b2, b3]: 1/2/sqrt(2) + # [b2, c2]: 1/2 + assert_sample_distribution( + board, + { + locations_to_bitboard(["a3", "b3"]): 1.0 / 4, + locations_to_bitboard(["a3", "c2"]): 1.0 / 8, + locations_to_bitboard(["a3", "b2"]): 1.0 / 8, + locations_to_bitboard(["b3", "c2"]): 1.0 / 8, + locations_to_bitboard(["b2", "b3"]): 1.0 / 8, + locations_to_bitboard(["b2", "c2"]): 1.0 / 4, + }, + ) + + +def test_merge_jump_imperfect_merge_scenario_3(): + """Imperfect merge scenario 3. + This is a simplied version of the scenario above, where we unhook a3 and b3. + """ + board = set_board(["a1", "b1"]) + world = board.board + SplitJump()(world["a1"], world["a2"], world["a3"]) + SplitJump()(world["b1"], world["b2"], world["b3"]) + board.board.unhook(world["a3"]) + board.board.unhook(world["b3"]) + # Now the only quantum pieces in the board are a2 and b2. + MergeJump()(world["a2"], world["b2"], world["c2"]) + # The expected distribution is same as above by summing over a3 and b3. + assert_sample_distribution( + board, + { + locations_to_bitboard([]): 1.0 / 4, + locations_to_bitboard(["c2"]): 1.0 / 4, + locations_to_bitboard(["b2"]): 1.0 / 4, + locations_to_bitboard(["b2", "c2"]): 1.0 / 4, + }, + )