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")
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")
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 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 test_bit_to_qubit(): assert u.bit_to_qubit(2) == cirq.NamedQubit("2") assert u.bit_to_qubit(0) == cirq.NamedQubit("0")
def test_bit_to_qubit(): assert u.bit_to_qubit(2) == cirq.NamedQubit("c1") assert u.bit_to_qubit(63) == cirq.NamedQubit("h8")
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')