Example #1
0
def test_path_qubits():
    """Source and target should be in the same line, otherwise ValueError should be returned."""
    b = qb.CirqBoard(
        u.squares_to_bitboard(['a1', 'b3', 'c4', 'd5', 'e6', 'f7']))
    assert b.path_qubits("b3", "f7") == [bit_to_qubit(square_to_bit('c4')), \
                                         bit_to_qubit(square_to_bit('d5')), \
                                         bit_to_qubit(square_to_bit('e6'))]
    with pytest.raises(ValueError):
        b.path_qubits("a1", "b3")
    with pytest.raises(ValueError):
        b.path_qubits("c4", "a1")
Example #2
0
    def apply(self, move: Move):
        """Applies a move to the board."""

        s = self._pieces[move.source]

        # Defaults to a BASIC JUMP move if not specified
        if not move.move_type:
            move.move_type = enums.MoveType.JUMP
        if not move.move_variant:
            move.move_variant = enums.MoveVariant.BASIC

        # Call the quantum board to apply the move
        meas = self.board.do_move(move)

        # Cache the probability distribution
        self._probs = self.board.get_probability_distribution(self.reps)

        # Set the turn to be the next player
        self.white_moves = not self.white_moves

        # If the move was not successful, return
        # Otherwise, update classical bits
        if not meas:
            return meas

        # Assume that squares with low probability are empty
        if self._probs[bu.square_to_bit(move.source)] < 0.01:
            self._pieces[move.source] = c.EMPTY

        # Update classical bits
        self._pieces[move.target] = s
        if move.target2:
            self._pieces[move.target2] = s
        if move.source2 and self._probs[bu.square_to_bit(move.source2)] < 0.01:
            self._pieces[move.source2] = c.EMPTY

        # Update en passant and castling flags
        self.ep_flag = self._ep_flag(move, s)
        if s == c.KING:
            self.castling_flags["O-O"] = False
            self.castling_flags["O-O-O"] = False
        if s == -c.KING:
            self.castling_flags["o-o"] = False
            self.castling_flags["o-o-o"] = False
        if move.source == "a1" or move.target == "a1":
            self.castling_flags["O-O-O"] = False
        if move.source == "a8" or move.target == "a8":
            self.castling_flags["o-o-o"] = False
        if move.source == "h1" or move.target == "h1":
            self.castling_flags["O-O"] = False
        if move.source == "h8" or move.target == "h8":
            self.castling_flags["o-o"] = False

        return meas
Example #3
0
def test_path_qubits():
    """Source and target should be in the same line, otherwise ValueError should be returned."""
    b = qb.CirqBoard(
        u.squares_to_bitboard(["a1", "b3", "c4", "d5", "e6", "f7"]))
    assert b.path_qubits("b3", "f7") == [
        bit_to_qubit(square_to_bit("c4")),
        bit_to_qubit(square_to_bit("d5")),
        bit_to_qubit(square_to_bit("e6")),
    ]
    with pytest.raises(ValueError):
        b.path_qubits("a1", "b3")
    with pytest.raises(ValueError):
        b.path_qubits("c4", "a1")
Example #4
0
def test_get_probability_distribution_split_jump_first_move_pre_cached(board):
    b = board(u.squares_to_bitboard(["a1", "b1"]))
    # Cache a split jump in advance.
    cache_key = CacheKey(enums.MoveType.SPLIT_JUMP, 100)
    b.cache_results(cache_key)
    m1 = move.Move(
        "b1",
        "c1",
        target2="d1",
        move_type=enums.MoveType.SPLIT_JUMP,
        move_variant=enums.MoveVariant.BASIC,
    )
    b.do_move(m1)
    b.clear_debug_log()
    # Expected probability with the cache applied
    expected_probs = [0] * 64
    expected_probs[square_to_bit("a1")] = 1
    expected_probs[square_to_bit("b1")] = 0
    expected_probs[square_to_bit("c1")] = b.cache[cache_key]["target"]
    expected_probs[square_to_bit("d1")] = b.cache[cache_key]["target2"]

    # Get probability distribution should apply the cache without rerunning _generate_accumulations.
    probs = b.get_probability_distribution(100, use_cache=True)
    full_squares = b.get_full_squares_bitboard(100, use_cache=True)
    empty_squares = b.get_empty_squares_bitboard(100, use_cache=True)

    assert probs == expected_probs
    # Check that the second run and getting full and empty bitboards did not trigger any new logs.
    assert len(b.debug_log) == 0
    # Check bitboard updated correctly
    assert not nth_bit_of(square_to_bit("b1"), full_squares)
    assert not nth_bit_of(square_to_bit("c1"), full_squares)
    assert not nth_bit_of(square_to_bit("d1"), full_squares)
    assert nth_bit_of(square_to_bit("b1"), empty_squares)
Example #5
0
def test_superposition_slide_move2(board):
    """Tests a basic slide through a superposition of two pieces.

    Splits b3 and c3 to b2/b1 and c2/c1 then slides a1 to d1.
    """
    b = board.with_state(u.squares_to_bitboard(['a1', 'b3', 'c3']))
    assert b.perform_moves(
        'b3b2b1:SPLIT_JUMP:BASIC',
        'c3c2c1:SPLIT_JUMP:BASIC',
        'a1e1:SLIDE:BASIC',
    )
    possibilities = [
        u.squares_to_bitboard(['a1', 'b1', 'c1']),
        u.squares_to_bitboard(['a1', 'b1', 'c2']),
        u.squares_to_bitboard(['a1', 'b2', 'c1']),
        u.squares_to_bitboard(['e1', 'b2', 'c2'])
    ]
    samples = b.sample(100)
    assert (all(sample in possibilities for sample in samples))
    probs = b.get_probability_distribution(10000)
    assert_fifty_fifty(probs, u.square_to_bit('b2'))
    assert_fifty_fifty(probs, u.square_to_bit('b1'))
    assert_fifty_fifty(probs, u.square_to_bit('c2'))
    assert_fifty_fifty(probs, u.square_to_bit('c1'))
    assert_prob_about(probs, u.square_to_bit('a1'), 0.75)
    assert_prob_about(probs, u.square_to_bit('e1'), 0.25)
Example #6
0
def test_superposition_slide_move2():
    """Tests a basic slide through a superposition of two pieces.

    Splits b3 and c3 to b2/b1 and c2/c1 then slides a1 to d1.
    """
    b = simulator(u.squares_to_bitboard(["a1", "b3", "c3"]))
    assert b.perform_moves(
        "b3^b2b1:SPLIT_JUMP:BASIC",
        "c3^c2c1:SPLIT_JUMP:BASIC",
        "a1e1:SLIDE:BASIC",
    )
    possibilities = [
        u.squares_to_bitboard(["a1", "b1", "c1"]),
        u.squares_to_bitboard(["a1", "b1", "c2"]),
        u.squares_to_bitboard(["a1", "b2", "c1"]),
        u.squares_to_bitboard(["e1", "b2", "c2"]),
    ]
    samples = b.sample(100)
    assert all(sample in possibilities for sample in samples)
    probs = b.get_probability_distribution(10000)
    assert_fifty_fifty(probs, u.square_to_bit("b2"))
    assert_fifty_fifty(probs, u.square_to_bit("b1"))
    assert_fifty_fifty(probs, u.square_to_bit("c2"))
    assert_fifty_fifty(probs, u.square_to_bit("c1"))
    assert_prob_about(probs, u.square_to_bit("a1"), 0.75)
    assert_prob_about(probs, u.square_to_bit("e1"), 0.25)
    board_probs = b.get_board_probability_distribution(10000)
    assert len(board_probs) == len(possibilities)
    for possibility in possibilities:
        assert_prob_about(board_probs, possibility, 0.25)
Example #7
0
def test_split_move(move_type, board):
    b = board(u.squares_to_bitboard(["a1"]))
    b.do_move(
        move.Move(
            "a1",
            "a3",
            target2="c1",
            move_type=move_type,
            move_variant=enums.MoveVariant.BASIC,
        ))
    samples = b.sample(100)
    assert_this_or_that(samples, u.squares_to_bitboard(["a3"]),
                        u.squares_to_bitboard(["c1"]))
    probs = b.get_probability_distribution(5000)
    assert_fifty_fifty(probs, qb.square_to_bit("a3"))
    assert_fifty_fifty(probs, qb.square_to_bit("c1"))
    board_probs = b.get_board_probability_distribution(5000)
    assert len(board_probs) == 2
    assert_fifty_fifty(board_probs, u.squares_to_bitboard(["a3"]))
    assert_fifty_fifty(board_probs, u.squares_to_bitboard(["c1"]))

    # Test doing a jump after a split move
    m = move.Move("c1",
                  "d1",
                  move_type=enums.MoveType.JUMP,
                  move_variant=enums.MoveVariant.BASIC)
    assert b.do_move(m)
    samples = b.sample(100)
    assert_this_or_that(samples, u.squares_to_bitboard(["a3"]),
                        u.squares_to_bitboard(["d1"]))
    probs = b.get_probability_distribution(5000)
    assert_fifty_fifty(probs, u.square_to_bit("a3"))
    assert_fifty_fifty(probs, u.square_to_bit("d1"))
    board_probs = b.get_board_probability_distribution(5000)
    assert len(board_probs) == 2
    assert_fifty_fifty(board_probs, u.squares_to_bitboard(["a3"]))
    assert_fifty_fifty(board_probs, u.squares_to_bitboard(["d1"]))
Example #8
0
def test_get_probability_distribution_split_jump_pre_cached(board):
    b = board(u.squares_to_bitboard(['a1', 'b1']))
    # Cache a split jump in advance.
    cache_key = CacheKey(enums.MoveType.SPLIT_JUMP, 100)
    b.cache_results(cache_key)

    m1 = move.Move('a1',
                   'a2',
                   move_type=enums.MoveType.JUMP,
                   move_variant=enums.MoveVariant.BASIC)
    m2 = move.Move('b1',
                   'c1',
                   target2='d1',
                   move_type=enums.MoveType.SPLIT_JUMP,
                   move_variant=enums.MoveVariant.BASIC)
    b.do_move(m1)
    probs = b.get_probability_distribution(100)
    b.do_move(m2)
    b.clear_debug_log()
    # Expected probability with the cache applied
    probs[square_to_bit('b1')] = 0
    probs[square_to_bit('c1')] = b.cache[cache_key]["target"]
    probs[square_to_bit('d1')] = b.cache[cache_key]["target2"]

    # Get probability distribution should apply the cache without rerunning _generate_accumulations.
    probs2 = b.get_probability_distribution(100, use_cache=True)
    full_squares = b.get_full_squares_bitboard(100, use_cache=True)
    empty_squares = b.get_empty_squares_bitboard(100, use_cache=True)

    assert probs == probs2
    # Check that the second run and getting full and empty bitboards did not trigger any new logs.
    assert len(b.debug_log) == 0
    # Check bitboard updated correctly
    assert not nth_bit_of(square_to_bit('b1'), full_squares)
    assert not nth_bit_of(square_to_bit('c1'), full_squares)
    assert not nth_bit_of(square_to_bit('d1'), full_squares)
    assert nth_bit_of(square_to_bit('b1'), empty_squares)
Example #9
0
def test_square_to_bit():
    assert u.square_to_bit('a1') == 0
    assert u.square_to_bit('a2') == 8
    assert u.square_to_bit('b2') == 9
    assert u.square_to_bit('b1') == 1
    assert u.square_to_bit('h8') == 63
Example #10
0
def test_square_to_bit():
    assert u.square_to_bit("a1") == 0
    assert u.square_to_bit("a2") == 8
    assert u.square_to_bit("b2") == 9
    assert u.square_to_bit("b1") == 1
    assert u.square_to_bit("h8") == 63
Example #11
0
    def do_move(self, m: move.Move) -> int:
        """Performs a move on the quantum board.

        Based on the type and variant of the move requested,
        this function augments the circuit, classical registers,
        and post-selection criteria to perform the board.

        Returns:  The measurement that was performed, or 1 if
            no measurement was required.
        """
        if not m.move_type:
            raise ValueError('No Move defined')
        if m.move_type == enums.MoveType.NULL_TYPE:
            raise ValueError('Move has null type')
        if m.move_type == enums.MoveType.UNSPECIFIED_STANDARD:
            raise ValueError('Move type is unspecified')

        # Reset accumulations here because function has conditional return branches
        self.accumulations_repetitions = None

        # Add move to the move move_history
        self.move_history.append(m)

        sbit = square_to_bit(m.source)
        tbit = square_to_bit(m.target)
        squbit = bit_to_qubit(sbit)
        tqubit = bit_to_qubit(tbit)

        if (m.move_variant == enums.MoveVariant.CAPTURE
                or m.move_type == enums.MoveType.PAWN_EP
                or m.move_type == enums.MoveType.PAWN_CAPTURE):
            # TODO: figure out if it is a deterministic capture.
            for val in list(self.allowed_pieces):
                self.allowed_pieces.add(val - 1)

        if m.move_type == enums.MoveType.PAWN_EP:
            # For en passant, first determine the square of the pawn being
            # captured, which should be next to the target.
            if m.target[1] == '6':
                epbit = square_to_bit(m.target[0] + '5')
            elif m.target[1] == '2':
                epbit = square_to_bit(m.target[0] + '4')
            else:
                raise ValueError(f'Invalid en passant target {m.target}')
            epqubit = bit_to_qubit(epbit)

            # For the classical version, set the bits appropriately
            if (epqubit not in self.entangled_squares
                    and squbit not in self.entangled_squares
                    and tqubit not in self.entangled_squares):
                if (not nth_bit_of(epbit, self.state)
                        or not nth_bit_of(sbit, self.state)
                        or nth_bit_of(tbit, self.state)):
                    raise ValueError('Invalid classical e.p. move')

                self.state = set_nth_bit(epbit, self.state, False)
                self.state = set_nth_bit(sbit, self.state, False)
                self.state = set_nth_bit(tbit, self.state, True)
                return 1

            # If any squares are quantum, it's a quantum move
            self.add_entangled(squbit, tqubit, epqubit)

            # Capture e.p. post-select on the source
            if m.move_variant == enums.MoveVariant.CAPTURE:
                is_there = self.post_select_on(squbit)
                if not is_there:
                    return 0
                self.add_entangled(squbit)
                path_ancilla = self.new_ancilla()
                captured_ancilla = self.new_ancilla()
                captured_ancilla2 = self.new_ancilla()
                # capture e.p. has a special circuit
                self.circuit.append(
                    qm.capture_ep(squbit, tqubit, epqubit, self.new_ancilla(),
                                  self.new_ancilla(), self.new_ancilla()))
                return 1

            # Blocked/excluded e.p. post-select on the target
            if m.move_variant == enums.MoveVariant.EXCLUDED:
                is_there = self.post_select_on(tqubit)
                if is_there:
                    return 0
                self.add_entangled(tqubit)
            self.circuit.append(
                qm.en_passant(squbit, tqubit, epqubit, self.new_ancilla(),
                              self.new_ancilla()))
            return 1

        if m.move_type == enums.MoveType.PAWN_CAPTURE:
            # For pawn capture, first measure source.
            is_there = self.post_select_on(squbit)
            if not is_there:
                return 0
            if tqubit in self.entangled_squares:
                old_tqubit = self.unhook(tqubit)
                self.add_entangled(squbit, tqubit)

                self.circuit.append(
                    qm.controlled_operation(cirq.ISWAP, [squbit, tqubit],
                                            [old_tqubit], []))
            else:
                # Classical case
                self.state = set_nth_bit(sbit, self.state, False)
                self.state = set_nth_bit(tbit, self.state, True)
            return 1

        if m.move_type == enums.MoveType.SPLIT_SLIDE:
            tbit2 = square_to_bit(m.target2)
            tqubit2 = bit_to_qubit(tbit2)

            # Find all the squares on both paths
            path_qubits = self.path_qubits(m.source, m.target)
            path_qubits2 = self.path_qubits(m.source, m.target2)

            if len(path_qubits) == 0 and len(path_qubits2) == 0:
                # No interposing squares, just jump.
                m.move_type = enums.MoveType.SPLIT_JUMP
            else:
                self.add_entangled(squbit, tqubit, tqubit2)
                path1 = self.create_path_ancilla(path_qubits)
                path2 = self.create_path_ancilla(path_qubits2)
                ancilla = self.new_ancilla()
                self.circuit.append(
                    qm.split_slide(squbit, tqubit, tqubit2, path1, path2,
                                   ancilla))
                return 1

        if m.move_type == enums.MoveType.MERGE_SLIDE:
            sbit2 = square_to_bit(m.source2)
            squbit2 = bit_to_qubit(sbit2)
            self.add_entangled(squbit, squbit2, tqubit)

            # Find all the squares on both paths
            path_qubits = self.path_qubits(m.source, m.target)
            path_qubits2 = self.path_qubits(m.source2, m.target)
            if len(path_qubits) == 0 and len(path_qubits2) == 0:
                # No interposing squares, just jump.
                m.move_type = enums.MoveType.MERGE_JUMP
            else:
                path1 = self.create_path_ancilla(path_qubits)
                path2 = self.create_path_ancilla(path_qubits2)
                ancilla = self.new_ancilla()
                self.circuit.append(
                    qm.merge_slide(squbit, tqubit, squbit2, path1, path2,
                                   ancilla))
                return 1

        if (m.move_type == enums.MoveType.SLIDE
                or m.move_type == enums.MoveType.PAWN_TWO_STEP):
            path_qubits = self.path_qubits(m.source, m.target)
            if len(path_qubits) == 0:
                # No path, change to jump
                m.move_type = enums.MoveType.JUMP

        if (m.move_type == enums.MoveType.SLIDE
                or m.move_type == enums.MoveType.PAWN_TWO_STEP):
            for p in path_qubits:
                if (p not in self.entangled_squares
                        and nth_bit_of(qubit_to_bit(p), self.state)):
                    # Classical piece in the way
                    return 0

            # For excluded case, measure target
            if m.move_variant == enums.MoveVariant.EXCLUDED:
                is_there = self.post_select_on(tqubit)
                if is_there:
                    return 0

            self.add_entangled(squbit, tqubit)
            if m.move_variant == enums.MoveVariant.CAPTURE:
                capture_ancilla = self.new_ancilla()
                self.circuit.append(
                    qm.controlled_operation(cirq.X, [capture_ancilla],
                                            [squbit], path_qubits))

                # We need to add the captured_ancilla to entangled squares
                # So that we measure it
                self.entangled_squares.add(capture_ancilla)
                capture_allowed = self.post_select_on(capture_ancilla)

                if not capture_allowed:
                    return 0
                else:
                    # Perform the captured slide
                    self.add_entangled(squbit)
                    # Remove the target from the board into an ancilla
                    # and set bit to zero
                    self.unhook(tqubit)
                    self.state = set_nth_bit(tbit, self.state, False)

                    # Re-add target since we need to swap into the square
                    self.add_entangled(tqubit)

                    # Perform the actual move
                    self.circuit.append(qm.normal_move(squbit, tqubit))

                    # Set source to empty
                    self.unhook(squbit)
                    self.state = set_nth_bit(sbit, self.state, False)

                    # Now set the whole path to empty
                    for p in path_qubits:
                        self.state = set_nth_bit(qubit_to_bit(p), self.state,
                                                 False)
                        self.unhook(p)
                    return 1
            # Basic slide (or successful excluded slide)

            # Add all involved squares into entanglement
            self.add_entangled(squbit, tqubit, *path_qubits)

            if len(path_qubits) == 1:
                # For path of one, no ancilla needed
                self.circuit.append(qm.slide_move(squbit, tqubit, path_qubits))
                return 1
            # Longer paths require a path ancilla
            ancilla = self.new_ancilla()
            self.circuit.append(
                qm.slide_move(squbit, tqubit, path_qubits, ancilla))
            return 1

        if (m.move_type == enums.MoveType.JUMP
                or m.move_type == enums.MoveType.PAWN_STEP):
            if (squbit not in self.entangled_squares
                    and tqubit not in self.entangled_squares):
                # Classical version
                self.state = set_nth_bit(sbit, self.state, False)
                self.state = set_nth_bit(tbit, self.state, True)
                return 1

            # Measure source for capture
            if m.move_variant == enums.MoveVariant.CAPTURE:
                is_there = self.post_select_on(squbit)
                if not is_there:
                    return 0
                self.unhook(tqubit)

            # Measure target for excluded
            if m.move_variant == enums.MoveVariant.EXCLUDED:
                is_there = self.post_select_on(tqubit)
                if is_there:
                    return 0

            # Only convert source qubit to ancilla if target
            # is empty
            unhook = tqubit not in self.entangled_squares
            self.add_entangled(squbit, tqubit)

            # Execute jump
            self.circuit.append(qm.normal_move(squbit, tqubit))

            if unhook or m.move_variant != enums.MoveVariant.BASIC:
                # The source is empty.
                # Change source qubit to be an ancilla
                # and set classical bit to zero
                self.state = set_nth_bit(sbit, self.state, False)
                self.unhook(squbit)

            return 1

        if m.move_type == enums.MoveType.SPLIT_JUMP:
            tbit2 = square_to_bit(m.target2)
            tqubit2 = bit_to_qubit(tbit2)
            self.add_entangled(squbit, tqubit, tqubit2)
            self.circuit.append(qm.split_move(squbit, tqubit, tqubit2))
            self.state = set_nth_bit(sbit, self.state, False)
            self.unhook(squbit)
            return 1

        if m.move_type == enums.MoveType.MERGE_JUMP:
            sbit2 = square_to_bit(m.source2)
            squbit2 = bit_to_qubit(sbit2)
            self.add_entangled(squbit, squbit2, tqubit)
            self.circuit.append(qm.merge_move(squbit, squbit2, tqubit))
            # TODO: should the source qubit be 'unhooked'?
            return 1

        if m.move_type == enums.MoveType.KS_CASTLE:
            # Figure out the rook squares
            if sbit == square_to_bit('e1') and tbit == square_to_bit('g1'):
                rook_sbit = square_to_bit('h1')
                rook_tbit = square_to_bit('f1')
            elif sbit == square_to_bit('e8') and tbit == square_to_bit('g8'):
                rook_sbit = square_to_bit('h8')
                rook_tbit = square_to_bit('f8')
            else:
                raise ValueError(f'Invalid kingside castling move')
            rook_squbit = bit_to_qubit(rook_sbit)
            rook_tqubit = bit_to_qubit(rook_tbit)

            # Piece in non-superposition in the way, not legal
            if (nth_bit_of(rook_tbit, self.state)
                    and rook_tqubit not in self.entangled_squares):
                return 0
            if (nth_bit_of(tbit, self.state)
                    and tqubit not in self.entangled_squares):
                return 0

            # Not in superposition, just castle
            if (rook_tqubit not in self.entangled_squares
                    and tqubit not in self.entangled_squares):
                self.set_castle(sbit, rook_sbit, tbit, rook_tbit)
                return 1

            # Both intervening squares in superposition
            if (rook_tqubit in self.entangled_squares
                    and tqubit in self.entangled_squares):
                castle_ancilla = self.create_path_ancilla(
                    [rook_tqubit, tqubit])
                self.entangled_squares.add(castle_ancilla)
                castle_allowed = self.post_select_on(castle_ancilla)
                if castle_allowed:
                    self.unhook(rook_tqubit)
                    self.unhook(tqubit)
                    self.set_castle(sbit, rook_sbit, tbit, rook_tbit)
                    return 1
                else:
                    self.post_selection[castle_ancilla] = castle_allowed
                    return 0

            # One intervening square in superposition
            if rook_tqubit in self.entangled_squares:
                measure_qubit = rook_tqubit
                measure_bit = rook_tbit
            else:
                measure_qubit = tqubit
                measure_bit = tbit
            is_there = self.post_select_on(measure_qubit)
            if is_there:
                return 0
            self.set_castle(sbit, rook_sbit, tbit, rook_tbit)
            return 1

        if m.move_type == enums.MoveType.QS_CASTLE:

            # Figure out the rook squares and the b-file square involved
            if sbit == square_to_bit('e1') and tbit == square_to_bit('c1'):
                rook_sbit = square_to_bit('a1')
                rook_tbit = square_to_bit('d1')
                b_bit = square_to_bit('b1')
            elif sbit == square_to_bit('e8') and tbit == square_to_bit('c8'):
                rook_sbit = square_to_bit('a8')
                rook_tbit = square_to_bit('d8')
                b_bit = square_to_bit('b8')
            else:
                raise ValueError(f'Invalid queenside castling move')
            rook_squbit = bit_to_qubit(rook_sbit)
            rook_tqubit = bit_to_qubit(rook_tbit)
            b_qubit = bit_to_qubit(b_bit)

            # Piece in non-superposition in the way, not legal
            if (nth_bit_of(rook_tbit, self.state)
                    and rook_tqubit not in self.entangled_squares):
                return 0
            if (nth_bit_of(tbit, self.state)
                    and tqubit not in self.entangled_squares):
                return 0
            if (b_bit is not None and nth_bit_of(b_bit, self.state)
                    and b_qubit not in self.entangled_squares):
                return 0

            # Not in superposition, just castle
            if (rook_tqubit not in self.entangled_squares
                    and tqubit not in self.entangled_squares
                    and b_qubit not in self.entangled_squares):
                self.set_castle(sbit, rook_sbit, tbit, rook_tbit)
                return 1

            # Neither intervening squares in superposition
            if (rook_tqubit not in self.entangled_squares
                    and tqubit not in self.entangled_squares):
                if b_qubit not in self.entangled_squares:
                    self.set_castle(sbit, rook_sbit, tbit, rook_tbit)
                else:
                    self.queenside_castle(squbit, rook_squbit, tqubit,
                                          rook_tqubit, b_qubit)
                return 1

            # Both intervening squares in superposition
            if (rook_tqubit in self.entangled_squares
                    and tqubit in self.entangled_squares):
                castle_ancilla = self.create_path_ancilla(
                    [rook_tqubit, tqubit])
                self.entangled_squares.add(castle_ancilla)
                castle_allowed = self.post_select_on(castle_ancilla)
                if castle_allowed:
                    self.unhook(rook_tqubit)
                    self.unhook(tqubit)
                    if b_qubit not in self.entangled_squares:
                        self.set_castle(sbit, rook_sbit, tbit, rook_tbit)
                    else:
                        self.queenside_castle(squbit, rook_squbit, tqubit,
                                              rook_tqubit, b_qubit)
                    return 1
                else:
                    self.post_selection[castle_ancilla] = castle_allowed
                    return 0

            # One intervening square in superposition
            if rook_tqubit in self.entangled_squares:
                measure_qubit = rook_tqubit
                measure_bit = rook_tbit
            else:
                measure_qubit = tqubit
                measure_bit = tbit
            is_there = self.post_select_on(measure_qubit)
            if is_there:
                return 0
            if b_qubit not in self.entangled_squares:
                self.set_castle(sbit, rook_sbit, tbit, rook_tbit)
            else:
                self.queenside_castle(squbit, rook_squbit, tqubit, rook_tqubit,
                                      b_qubit)
            return 1

        raise ValueError(f'Move type {m.move_type} not supported')
Example #12
0
 def _apply_cache(probability, m, cache_value):
     new_probability = probability.copy()
     for k, v in cache_value.items():
         square = getattr(m, k)
         new_probability[square_to_bit(square)] = v
     return new_probability