def test_split_then_merge(device): transformer = ct.CircuitTransformer(device) c = cirq.Circuit(qm.split_move(a1, a2, b1), qm.split_move(a2, a3, b3), qm.split_move(b1, c1, c2), qm.normal_move(c1, d1), qm.normal_move(a3, a4), qm.merge_move(a4, d1, a1)) transformer.qubit_mapping(c) device.validate_circuit(transformer.optimize_circuit(c))
def test_move_around_square(transformer, device): c = cirq.Circuit( qm.normal_move(a1, a2), qm.normal_move(a2, b2), qm.normal_move(b2, b1), qm.normal_move(b1, a1), ) t = transformer(device) device.validate_circuit(t.transform(c))
def test_split_then_merge(transformer, device): c = cirq.Circuit( qm.split_move(a1, a2, b1), qm.split_move(a2, a3, b3), qm.split_move(b1, c1, c2), qm.normal_move(c1, d1), qm.normal_move(a3, a4), qm.merge_move(a4, d1, a1), ) t = transformer(device) device.validate_circuit(t.transform(c))
def test_pentagonal_split_and_merge(): grid_2x3 = cirq.GridQubit.rect(2, 3, 4, 4) logical_qubits = list( cirq.NamedQubit(f'{x}{i}') for x in ('a', 'b') for i in range(3)) initial_mapping = dict(zip(logical_qubits, grid_2x3)) a1, a2, a3, b1, b2, b3 = logical_qubits logical_circuit = cirq.Circuit(qm.normal_move(a1, b1), qm.normal_move(a2, a3), qm.merge_move(a3, b1, b3)) updater = SwapUpdater(logical_circuit, grid_2x3, initial_mapping) updated_circuit = cirq.Circuit(updater.add_swaps()) # Whereas the original circuit's initial mapping was not valid due to # adjacency constraints, the updated circuit is valid. device = cirq.google.Sycamore with pytest.raises(ValueError): device.validate_circuit( logical_circuit.transform_qubits(lambda q: initial_mapping.get(q))) device.validate_circuit(updated_circuit)
def test_move_around_square(device): transformer = ct.CircuitTransformer(device) c = cirq.Circuit(qm.normal_move(a1, a2), qm.normal_move(a2, b2), qm.normal_move(b2, b1), qm.normal_move(b1, a1)) transformer.qubit_mapping(c) device.validate_circuit(transformer.optimize_circuit(c))
def test_split_then_merge_trapezoid(device): c = cirq.Circuit(qm.split_move(a1, a2, b1), qm.normal_move(a2, a3), qm.merge_move(a3, b1, b3)) t = ct.DynamicLookAheadHeuristicCircuitTransformer(device) device.validate_circuit(t.transform(c))
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')
def test_move_around_square(device): transformer = ct.ConnectivityHeuristicCircuitTransformer(device) c = cirq.Circuit(qm.normal_move(a1, a2), qm.normal_move(a2, b2), qm.normal_move(b2, b1), qm.normal_move(b1, a1)) transformer.qubit_mapping(c) device.validate_circuit(transformer.transform(c))