Exemplo n.º 1
0
 def set_castle(self, sbit: int, rook_sbit: int, tbit: int,
                rook_tbit: int) -> None:
     """Adjusts classical bits for a castling operation."""
     self.state = set_nth_bit(sbit, self.state, False)
     self.state = set_nth_bit(rook_sbit, self.state, False)
     self.state = set_nth_bit(tbit, self.state, True)
     self.state = set_nth_bit(rook_tbit, self.state, True)
Exemplo n.º 2
0
 def _set_full_empty_squares_from_probability(self) -> None:
     self.full_squares = 0
     self.empty_squares = 0
     for i, p in enumerate(self.probabilities):
         if p == 1:
             self.full_squares = set_nth_bit(i, self.full_squares, True)
         if p == 0:
             self.empty_squares = set_nth_bit(i, self.empty_squares, True)
Exemplo n.º 3
0
    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
Exemplo n.º 4
0
 def _bit_board(self) -> int:
     rtn = 0
     for x in range(self.size):
         for y in range(self.size):
             s = to_square(x, y)
             if self._pieces[s] != c.EMPTY:
                 rtn = bu.set_nth_bit(bu.xy_to_bit(x, y), rtn, True)
     return rtn
Exemplo n.º 5
0
    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
Exemplo n.º 6
0
    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
Exemplo n.º 7
0
    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
Exemplo n.º 8
0
    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
Exemplo n.º 9
0
    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
Exemplo n.º 10
0
def test_set_nth_bit():
    assert u.set_nth_bit(0, 6, True) == 7
    assert u.set_nth_bit(0, 6, False) == 6
    assert u.set_nth_bit(2, 7, True) == 7
    assert u.set_nth_bit(2, 7, False) == 3
Exemplo n.º 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')
Exemplo n.º 12
0
    def sample_with_ancilla(
            self, num_samples: int) -> Tuple[List[int], List[Dict[str, int]]]:
        """Samples the board and returns square and ancilla measurements.

        Sends the current circuit to the sampler then retrieves the results.
        May return less samples than num_samples due to post-selection.

        Returns the results as a tuple.  The first entry is the list of
        measured squares, as represented by a 64-bit int bitboard.
        The second value is a list of ancilla values, as represented as a
        dictionary from ancilla name to value (0 or 1).
        """
        t0 = time.perf_counter()
        measure_circuit = self.circuit.copy()
        ancilla = []
        error_count = 0
        noise_count = 0
        post_count = 0
        if self.entangled_squares:
            qubits = sorted(self.entangled_squares)
            measure_moment = cirq.Moment(
                cirq.measure(q, key=q.name) for q in qubits)
            measure_circuit.append(measure_moment)

            # Try to guess the appropriate number of repetitions needed
            # Assume that each post_selection is about 50/50
            # Noise and error mitigation will discard reps, so increase
            # the total number of repetitions to compensate
            if len(self.post_selection) > 1:
                num_reps = num_samples * (2**(len(self.post_selection) + 1))
            else:
                num_reps = num_samples
            if self.error_mitigation == enums.ErrorMitigation.Correct:
                num_reps *= 2
            noise_threshold = self.noise_mitigation * num_samples
            if self.noise_mitigation > 0:
                num_reps *= 3
            if num_reps < 100:
                num_reps = 100

            self.debug_log += (f'Running circuit with {num_reps} reps '
                               f'to get {num_samples} samples:\n'
                               f'{str(measure_circuit)}\n')

            # Translate circuit to grid qubits and sqrtISWAP gates
            if self.device is not None:
                # Decompose 3-qubit operations
                ct.SycamoreDecomposer().optimize_circuit(measure_circuit)
                # Create NamedQubit to GridQubit mapping and transform
                measure_circuit = self.transformer.transform(measure_circuit)

                # For debug, ensure that the circuit correctly validates
                self.device.validate_circuit(measure_circuit)

            # Run the circuit using the provided sampler (simulator or hardware)
            results = self.sampler.run(measure_circuit, repetitions=num_reps)

            # Parse the results
            rtn = []
            noise_buffer = {}
            data = results.data
            for rep in range(num_reps):
                new_sample = self.state
                new_ancilla = {}

                # Go through the results and discard any results
                # that disagree with our pre-defined post-selection criteria
                post_selected = True
                for qubit in self.post_selection.keys():
                    key = qubit.name
                    if key in data.columns:
                        result = data.at[rep, key]
                        if result != self.post_selection[qubit]:
                            post_selected = False
                if not post_selected:
                    post_count += 1
                    continue

                # Translate qubit results into a 64-bit chess board
                for qubit in qubits:
                    key = qubit.name
                    result = data.at[rep, key]
                    # Ancilla bits should not be part of the chess board
                    if 'anc' not in key:
                        bit = qubit_to_bit(qubit)
                        new_sample = set_nth_bit(bit, new_sample, result)
                    else:
                        new_ancilla[key] = result

                # Perform Error Mitigation
                if self.error_mitigation != enums.ErrorMitigation.Nothing:
                    # Discard boards that have the wrong number of pieces
                    if num_ones(new_sample) not in self.allowed_pieces:
                        if self.error_mitigation == enums.ErrorMitigation.Error:
                            raise ValueError(
                                'Error detected, '
                                f'pieces allowed = {self.allowed_pieces}'
                                f'but got {num_ones(new_sample)}')
                        if self.error_mitigation == enums.ErrorMitigation.Correct:
                            error_count += 1
                            continue

                # Noise mitigation
                if self.noise_mitigation > 0.0:
                    # Ignore samples up to a threshold
                    if new_sample not in noise_buffer:
                        noise_buffer[new_sample] = 0
                    noise_buffer[new_sample] += 1
                    if noise_buffer[new_sample] < noise_threshold:
                        noise_count += 1
                        continue

                # This sample has passed noise and error mitigation
                # Record it as a proper sample
                rtn.append(new_sample)
                ancilla.append(new_ancilla)
                if len(rtn) >= num_samples:
                    self.debug_log += (
                        f'Discarded {error_count} from error mitigation '
                        f'{noise_count} from noise and '
                        f'{post_count} from post-selection\n')
                    self.record_time('sample_with_ancilla', t0)
                    return (rtn, ancilla)
        else:
            rtn = [self.state] * num_samples
            self.debug_log += (
                f'Discarded {error_count} from error mitigation '
                f'{noise_count} from noise and {post_count} from post-selection\n'
            )
        self.record_time("sample_with_ancilla", t0)
        return (rtn, ancilla)