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)
def _caching_supported(self, m: move.Move): """Checks if caching is supported for this move.""" # Caching is supported for a split jump from one full square to two empty squares. if (m.move_type == enums.MoveType.SPLIT_JUMP and nth_bit_of(square_to_bit(m.source), self.full_squares) and nth_bit_of(square_to_bit(m.target), self.empty_squares) and nth_bit_of(square_to_bit(m.target2), self.empty_squares)): return True return False
def path_qubits(self, source: str, target: str) -> List[cirq.Qid]: """Returns all entangled qubits (or classical pieces) between source and target. Source and target should be specified in algebraic notation, such as 'f4'. """ rtn = [] xs = move.x_of(source) ys = move.y_of(source) xt = move.x_of(target) yt = move.y_of(target) if xt > xs: dx = 1 elif xt < xs: dx = -1 else: dx = 0 if yt > ys: dy = 1 elif yt < ys: dy = -1 else: dy = 0 max_slide = max(abs(xs - xt), abs(ys - yt)) if max_slide > 1: for t in range(1, max_slide): path_bit = xy_to_bit(xs + dx * t, ys + dy * t) path_qubit = bit_to_qubit(path_bit) if (path_qubit in self.entangled_squares or nth_bit_of(path_bit, self.state)): rtn.append(path_qubit) return rtn
def with_state(self, basis_state: int) -> "CirqBoard": """Resets the board with a specific classical state.""" self.accumulations_repetitions = None self.board_accumulations_repetitions = None self.state = basis_state self.allowed_pieces = set() self.allowed_pieces.add(num_ones(self.state)) self.entangled_squares = set() self.post_selection = {} self.circuit = cirq.Circuit() self.ancilla_count = 0 self.move_history = [] self.full_squares = basis_state self.empty_squares = 0 for i in range(64): self.empty_squares = set_nth_bit( i, self.empty_squares, not nth_bit_of(i, self.full_squares)) # Each entry is a 2-tuple of (repetitions, probabilities) corresponding to the probabilities after each move. self.move_history_probabilities_cache = [] # Store the initial basis state so that we can use it for replaying # the move-history when undoing moves self.init_basis_state = basis_state self.clear_debug_log() self.timing_stats = defaultdict(list) return self
def test_nth_bit_of(): assert u.nth_bit_of(0, 7) assert u.nth_bit_of(1, 7) assert u.nth_bit_of(2, 7) assert not u.nth_bit_of(3, 7) assert not u.nth_bit_of(0, 6) assert u.nth_bit_of(1, 6) assert u.nth_bit_of(2, 6) assert not u.nth_bit_of(3, 6)
def add_entangled(self, *qubits): """Adds squares as entangled. This enables measuring of the square by the quantum circuit and also adds a piece in the square to the circuit if the classical register is currently set to one. """ for qubit in qubits: if qubit not in self.entangled_squares: self.entangled_squares.add(qubit) if nth_bit_of(qubit_to_bit(qubit), self.state): self.circuit.append(qm.place_piece(qubit))
def _generate_accumulations(self, repetitions: int = 1000, use_cache: bool = False) -> None: """Samples the state and generates the accumulated probabilities of each square, empty_squares, and full_squares. """ if use_cache and self.move_history: last_move = self.move_history[-1] if self.move_history_probabilities_cache[-1][0] >= repetitions: self.probabilities = self.move_history_probabilities_cache[-1][ 1].copy() self._set_full_empty_squares_from_probability() return previous_move_in_cache = ( len(self.move_history) > 1 and self.move_history_probabilities_cache[-2][0] >= repetitions) is_first_move = len(self.move_history) == 1 cache_key = cache_key_from_move(last_move, repetitions) if ((previous_move_in_cache or is_first_move) and self._caching_supported(last_move) and cache_key in self.cache): if previous_move_in_cache: previous_probability = self.move_history_probabilities_cache[ len(self.move_history) - 2][1] else: previous_probability = [0] * 64 for i in range(64): # Assume initial state is classical previous_probability[i] = nth_bit_of(i, self.state) probs = self._apply_cache(previous_probability, last_move, self.cache[cache_key]) self.probabilities = probs self._set_full_empty_squares_from_probability() self.move_history_probabilities_cache.append( (repetitions, probs.copy())) # Remove entry from cached since it has been consumed. del self.cache[cache_key] return self.probabilities = [0] * 64 samples = self.sample(repetitions) for sample in samples: for bit in bit_ones(sample): self.probabilities[bit] += 1 for bit in range(64): self.probabilities[bit] = float( self.probabilities[bit]) / float(repetitions) self._set_full_empty_squares_from_probability() self.accumulations_repetitions = repetitions
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)
def get_probability_distribution(self, repetitions: int = 1000) -> List[float]: """Returns the probability of a piece being in each square. The values are returned as a list in the same ordering as a bitboard. """ samples = self.sample(repetitions) counts = [0] * 64 for sample in samples: for bit in range(64): if nth_bit_of(bit, sample): counts[bit] += 1 for bit in range(64): counts[bit] = float(counts[bit]) / float(repetitions) return counts
def get_empty_squares_bitboard(self, repetitions: int = 1000) -> int: """Retrieves which squares are marked as full. This information is created using a representative set of samples (defined by the repetitions argument) to determine which squares are empty on all boards. Returns a bitboard. """ samples = self.sample(repetitions) empty_squares = (1 << 64) - 1 for bit in range(64): for sample in samples: if nth_bit_of(bit, sample): empty_squares = set_nth_bit(bit, empty_squares, 0) break return empty_squares
def get_full_squares_bitboard(self, repetitions: int = 1000) -> int: """Retrieves which squares are marked as full. This information is created using a representative set of samples (defined by the repetitions argument) to determine which squares are occupied on at least one board. Returns a bitboard. """ samples = self.sample(repetitions) full_squares = 0 for bit in range(64): for sample in samples: if nth_bit_of(bit, sample): full_squares = set_nth_bit(bit, full_squares, 1) break return full_squares
def post_select_on( self, qubit: cirq.Qid, measurement_outcome: Optional[int] = None, invert: Optional[bool] = False, ) -> bool: """Adds a post-selection requirement to the circuit. If no measurement_outcome is provided, performs a single sample of the qubit to get a value for the post-selection condition. Adjusts the post-selection requirements dictionary to this value. If this qubit is a square qubit, it adjusts the classical register to match the sample result. Args: qubit: the qubit to post-select on measurement_outcome: the optional measurement outcome. If present, post-selection is conditioned on the qubit having the given outcome. If absent, a single measurement is performed instead. invert: If True and measurement_outcome is set, this will invert the measurement to post-select on the opposite value. Returns: the measurement outcome or sample result as 1 or 0. """ result = measurement_outcome if invert and measurement_outcome is not None: result = 1 - result sample_size = 100 if self.noise_mitigation else 1 if "anc" in qubit.name: if result is None: ancilla_result = [] while len(ancilla_result) == 0: _, ancilla_result = self.sample_with_ancilla(sample_size) result = ancilla_result[0][qubit.name] self.post_selection[qubit] = result else: bit = qubit_to_bit(qubit) if result is None: result = nth_bit_of(bit, self.sample(sample_size)[0]) if qubit in self.entangled_squares: ancillary = self.unhook(qubit) self.post_selection[ancillary] = result self.state = set_nth_bit(bit, self.state, result) return result
def path_qubits(self, source: str, target: str) -> List[cirq.Qid]: """Returns all entangled qubits (or classical pieces) between source and target. Source and target should be in the same line, i.e. same row, same column, or same diagonal. Source and target should be specified in algebraic notation, such as 'f4'. """ rtn = [] xs = move.x_of(source) ys = move.y_of(source) xt = move.x_of(target) yt = move.y_of(target) if xt > xs: dx = 1 elif xt < xs: dx = -1 else: dx = 0 if yt > ys: dy = 1 elif yt < ys: dy = -1 else: dy = 0 x_slide = abs(xt - xs) y_slide = abs(yt - ys) # Souce and target should always be in the same line. if x_slide != y_slide and x_slide * y_slide: raise ValueError( 'Wrong inputs for path_qubits: source and target are not in the same line.' ) max_slide = max(x_slide, y_slide) # Only calculates path when max_slide > 1. for t in range(1, max_slide): path_bit = xy_to_bit(xs + dx * t, ys + dy * t) path_qubit = bit_to_qubit(path_bit) if (path_qubit in self.entangled_squares or nth_bit_of(path_bit, self.state)): rtn.append(path_qubit) return rtn
def _generate_accumulations(self, repetitions: int = 1000) -> None: """ Samples the state and generates the accumulated probabilities, empty_squares, and full_squares """ self.probabilities = [0] * 64 self.full_squares = (1 << 64) - 1 self.empty_squares = (1 << 64) - 1 samples = self.sample(repetitions) for sample in samples: for bit in range(64): if nth_bit_of(bit, sample): self.probabilities[bit] += 1 self.empty_squares = set_nth_bit(bit, self.empty_squares, 0) else: self.full_squares = set_nth_bit(bit, self.full_squares, 0) for bit in range(64): self.probabilities[bit] = float( self.probabilities[bit]) / float(repetitions) self.accumulations_valid = True
def post_select_on(self, qubit: cirq.Qid) -> int: """Adds a post-selection requirement to the circuit, Performs a single sample of the qubit to get a value. Adjusts the post-selection requirements dictionary to this value. If this qubit is a square qubit, it adjusts the classical register to match the sample result. Returns: the sample result as 1 or 0. """ if 'anc' in qubit.name: ancilla_result = [] while len(ancilla_result) == 0: _, ancilla_result = self.sample_with_ancilla(10) result = ancilla_result[0][qubit.name] self.post_selection[qubit] = result else: bit = qubit_to_bit(qubit) result = nth_bit_of(bit, self.sample(1)[0]) if qubit in self.entangled_squares: ancillary = self.unhook(qubit) self.post_selection[ancillary] = result self.state = set_nth_bit(bit, self.state, result) return result
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')