Exemple #1
0
 def _apply_unitary_(
         self, args: protocols.ApplyUnitaryArgs) -> Optional[np.ndarray]:
     if self._name == 'X':
         return protocols.apply_unitary(common_gates.X, args)
     elif self._name == 'Y':
         return protocols.apply_unitary(common_gates.Y, args)
     else:
         return protocols.apply_unitary(common_gates.Z, args)
Exemple #2
0
    def _apply_unitary_(self, args: protocols.ApplyUnitaryArgs) -> np.ndarray:
        n = len(self.controls)
        control_axes = args.axes[:n]
        sub_axes = args.axes[n:]
        active = linalg.slice_for_qubits_equal_to(control_axes, -1)
        view_axes = _positions_after_removals_at(initial_positions=sub_axes,
                                                 removals=control_axes)
        target_view = args.target_tensor[active]
        buffer_view = args.available_buffer[active]
        result = protocols.apply_unitary(self.sub_operation,
                                         protocols.ApplyUnitaryArgs(
                                             target_view, buffer_view,
                                             view_axes),
                                         default=NotImplemented)

        if result is NotImplemented:
            return NotImplemented

        if result is target_view:
            return args.target_tensor

        # HACK: assume they didn't somehow escape the slice view and edit the
        # rest of target_tensor.
        args.target_tensor[active] = result
        return args.target_tensor
Exemple #3
0
 def _apply_unitary_(self,
                     args: 'protocols.ApplyUnitaryArgs') -> np.ndarray:
     return protocols.apply_unitary(
         controlled_gate.ControlledGate(swap_gates.SWAP),
         protocols.ApplyUnitaryArgs(args.target_tensor,
                                    args.available_buffer, args.axes),
         default=NotImplemented)
Exemple #4
0
    def _apply_unitary_(self,
                        args: 'protocols.ApplyUnitaryArgs') -> np.ndarray:
        n = len(self.controls)
        sub_n = len(args.axes) - n
        sub_axes = args.axes[n:]
        for control_vals in itertools.product(*self.control_values):
            active = (..., *(slice(v, v + 1)
                             for v in control_vals), *(slice(None), ) * sub_n)
            target_view = args.target_tensor[active]
            buffer_view = args.available_buffer[active]
            result = protocols.apply_unitary(
                self.sub_operation,
                protocols.ApplyUnitaryArgs(target_view, buffer_view, sub_axes),
                default=NotImplemented,
            )

            if result is NotImplemented:
                return NotImplemented

            if result is not target_view:
                # HACK: assume they didn't somehow escape the slice view and
                # edit the rest of target_tensor.
                target_view[...] = result

        return args.target_tensor
Exemple #5
0
    def _expectation_from_wavefunction_no_validation(
            self, state: np.ndarray, qubit_map: Mapping[raw_types.Qid,
                                                        int]) -> float:
        """Evaluate the expectation of this PauliString given a wavefunction.

        This method does not provide input validation. See
        `PauliString.expectation_from_wavefunction` for function description.

        Args:
            state: An array representing a valid wavefunction.
            qubit_map: A map from all qubits used in this PauliString to the
            indices of the qubits that `state` is defined over.

        Returns:
            The expectation value of the input state.
        """
        if len(state.shape) == 1:
            num_qubits = state.shape[0].bit_length() - 1
            state = np.reshape(state, (2, ) * num_qubits)

        ket = np.copy(state)
        for qubit, pauli in self.items():
            buffer = np.empty(ket.shape, dtype=state.dtype)
            args = protocols.ApplyUnitaryArgs(target_tensor=ket,
                                              available_buffer=buffer,
                                              axes=(qubit_map[qubit], ))
            ket = protocols.apply_unitary(pauli, args)

        return self.coefficient * (np.tensordot(
            state.conj(), ket, axes=len(ket.shape)).item())
Exemple #6
0
    def _expectation_from_density_matrix_no_validation(
            self, state: np.ndarray, qubit_map: Mapping[raw_types.Qid,
                                                        int]) -> float:
        """Evaluate the expectation of this PauliString given a density matrix.

        This method does not provide input validation. See
        `PauliString.expectation_from_density_matrix` for function description.

        Args:
            state: An array representing a valid  density matrix.
            qubit_map: A map from all qubits used in this PauliString to the
            indices of the qubits that `state` is defined over.

        Returns:
            The expectation value of the input state.
        """
        result = np.copy(state)
        if len(state.shape) == 2:
            num_qubits = state.shape[0].bit_length() - 1
            result = np.reshape(result, (2, ) * num_qubits * 2)

        for qubit, pauli in self.items():
            buffer = np.empty(result.shape, dtype=state.dtype)
            args = protocols.ApplyUnitaryArgs(target_tensor=result,
                                              available_buffer=buffer,
                                              axes=(qubit_map[qubit], ))
            result = protocols.apply_unitary(pauli, args)

        while any(result.shape):
            result = np.trace(result, axis1=0, axis2=len(result.shape) // 2)
        return result * self.coefficient
Exemple #7
0
def assert_has_consistent_apply_unitary(val: Any,
                                        *,
                                        qubit_count: Optional[int] = None,
                                        atol: float = 1e-8) -> None:
    """Tests whether a value's _apply_unitary_ is correct.

    Contrasts the effects of the value's `_apply_unitary_` with the
    matrix returned by the value's `_unitary_` method.

    Args:
        val: The value under test. Should have a `__pow__` method.
        qubit_count: Usually inferred. The number of qubits the value acts on.
            This argument isn't needed if the gate has a unitary matrix or
            implements `cirq.SingleQubitGate`/`cirq.TwoQubitGate`/
            `cirq.ThreeQubitGate`.
        atol: Absolute error tolerance.
    """

    expected = protocols.unitary(val, default=None)

    qubit_counts = [
        qubit_count,
        # Only fall back to using the unitary size if num_qubits or qid_shape
        # protocols are not defined.
        protocols.num_qubits(val,
                             default=expected.shape[0].bit_length() -
                             1 if expected is not None else None),
        _infer_qubit_count(val)
    ]
    qubit_counts = [e for e in qubit_counts if e is not None]
    if not qubit_counts:
        raise NotImplementedError(
            'Failed to infer qubit count of <{!r}>. Specify it.'.format(val))
    assert len(set(qubit_counts)) == 1, (
        'Inconsistent qubit counts from different methods: {}'.format(
            qubit_counts))
    n = cast(int, qubit_counts[0])
    qid_shape = protocols.qid_shape(val, default=(2, ) * n)

    eye = linalg.eye_tensor((2, ) + qid_shape, dtype=np.complex128)
    actual = protocols.apply_unitary(
        unitary_value=val,
        args=protocols.ApplyUnitaryArgs(target_tensor=eye,
                                        available_buffer=np.ones_like(eye) *
                                        float('nan'),
                                        axes=list(range(1, n + 1))),
        default=None)

    # If you don't have a unitary, you shouldn't be able to apply a unitary.
    if expected is None:
        assert actual is None
    else:
        expected = np.kron(np.eye(2), expected)

    # If you applied a unitary, it should match the one you say you have.
    if actual is not None:
        np.testing.assert_allclose(actual.reshape((np.prod(
            (2, ) + qid_shape, dtype=int), ) * 2),
                                   expected,
                                   atol=atol)
Exemple #8
0
 def _apply_unitary_(self,
                     args: 'protocols.ApplyUnitaryArgs') -> np.ndarray:
     qubits = cirq.LineQid.for_gate(self)
     op = self.sub_gate.on(*qubits[self.num_controls():])
     c_op = cop.ControlledOperation(qubits[:self.num_controls()], op,
                                    self.control_values)
     return protocols.apply_unitary(c_op, args, default=NotImplemented)
Exemple #9
0
    def _apply_unitary_(self, args: protocols.ApplyUnitaryArgs) -> np.ndarray:
        control = args.axes[0]
        rest = args.axes[1:]
        active = linalg.slice_for_qubits_equal_to([control], 1)
        sub_axes = [r - int(r > control) for r in rest]
        target_view = args.target_tensor[active]
        buffer_view = args.available_buffer[active]
        result = protocols.apply_unitary(self.sub_gate,
                                         protocols.ApplyUnitaryArgs(
                                             target_view, buffer_view,
                                             sub_axes),
                                         default=NotImplemented)

        if result is NotImplemented:
            return NotImplemented

        if result is target_view:
            return args.target_tensor

        if result is buffer_view:
            inactive = linalg.slice_for_qubits_equal_to([control], 0)
            args.available_buffer[inactive] = args.target_tensor[inactive]
            return args.available_buffer

        # HACK: assume they didn't somehow escape the slice view and edit the
        # rest of target_tensor.
        args.target_tensor[active] = result
        return args.target_tensor
Exemple #10
0
    def _apply_unitary_(self, args: protocols.ApplyUnitaryArgs) -> np.ndarray:
        qubits = cirq.LineQubit.range(self.num_controls() +
                                      self.sub_gate.num_qubits())
        op = self.sub_gate.on(*qubits[self.num_controls():])
        c_op = cop.ControlledOperation(qubits[:self.num_controls()], op)

        return protocols.apply_unitary(c_op, args, default=NotImplemented)
Exemple #11
0
    def _base_iterator(
        self,
        circuit: circuits.Circuit,
        qubit_order: ops.QubitOrderOrList,
        initial_state: Union[int, np.ndarray],
        perform_measurements: bool = True,
    ) -> Iterator[simulator.StepResult]:
        qubits = ops.QubitOrder.as_qubit_order(qubit_order).order_for(
            circuit.all_qubits())
        num_qubits = len(qubits)
        qubit_map = {q: i for i, q in enumerate(qubits)}
        state = wave_function.to_valid_state_vector(initial_state, num_qubits,
                                                    self._dtype)

        def on_stuck(bad_op: ops.Operation):
            return TypeError(
                "Can't simulate unknown operations that don't specify a "
                "_unitary_ method, a _decompose_ method, or "
                "(_has_unitary_ + _apply_unitary_) methods"
                ": {!r}".format(bad_op))

        def keep(potential_op: ops.Operation) -> bool:
            return (protocols.has_unitary(potential_op)
                    or ops.MeasurementGate.is_measurement(potential_op))

        state = np.reshape(state, (2, ) * num_qubits)
        buffer = np.empty((2, ) * num_qubits, dtype=self._dtype)
        for moment in circuit:
            measurements = collections.defaultdict(
                list)  # type: Dict[str, List[bool]]

            unitary_ops_and_measurements = protocols.decompose(
                moment.operations, keep=keep, on_stuck_raise=on_stuck)

            for op in unitary_ops_and_measurements:
                indices = [qubit_map[qubit] for qubit in op.qubits]
                if ops.MeasurementGate.is_measurement(op):
                    gate = cast(ops.MeasurementGate,
                                cast(ops.GateOperation, op).gate)
                    if perform_measurements:
                        invert_mask = gate.invert_mask or num_qubits * (
                            False, )
                        # Measure updates inline.
                        bits, _ = wave_function.measure_state_vector(
                            state, indices, state)
                        corrected = [
                            bit ^ mask for bit, mask in zip(bits, invert_mask)
                        ]
                        measurements[cast(str, gate.key)].extend(corrected)
                else:
                    result = protocols.apply_unitary(
                        op,
                        args=protocols.ApplyUnitaryArgs(
                            state, buffer, indices))
                    if result is buffer:
                        buffer = state
                    state = result
            yield SimulatorStep(state, measurements, qubit_map, self._dtype)
Exemple #12
0
 def _simulate_unitary(self, op: ops.Operation, data: _StateAndBuffer,
                       indices: List[int]) -> None:
     """Simulate an op that has a unitary."""
     result = protocols.apply_unitary(op,
                                      args=protocols.ApplyUnitaryArgs(
                                          data.state, data.buffer, indices))
     if result is data.buffer:
         data.buffer = data.state
     data.state = result
Exemple #13
0
 def _apply_unitary_(self, args: protocols.ApplyUnitaryArgs) -> np.ndarray:
     if protocols.is_parameterized(self):
         return NotImplemented
     p = 1j**(2 * self._exponent * self._global_shift)
     if p != 1:
         args.target_tensor *= p
     return protocols.apply_unitary(controlled_gate.ControlledGate(
         controlled_gate.ControlledGate(pauli_gates.X**self.exponent)),
                                    protocols.ApplyUnitaryArgs(
                                        args.target_tensor,
                                        args.available_buffer, args.axes),
                                    default=NotImplemented)
Exemple #14
0
 def value_derived_from_wavefunction(
         self, state: np.ndarray, qubit_map: Dict[raw_types.Qid,
                                                  int]) -> float:
     num_qubits = state.shape[0].bit_length() - 1
     ket = np.reshape(np.copy(state), (2, ) * num_qubits)
     for qubit, pauli in self._pauli_string.items():
         buffer = np.empty(ket.shape, dtype=state.dtype)
         args = protocols.ApplyUnitaryArgs(target_tensor=ket,
                                           available_buffer=buffer,
                                           axes=(qubit_map[qubit], ))
         ket = protocols.apply_unitary(pauli, args)
     ket = np.reshape(ket, state.shape)
     return np.dot(state.conj(), ket) * self._pauli_string.coefficient
Exemple #15
0
 def value_derived_from_density_matrix(
         self, state: np.ndarray, qubit_map: Dict[raw_types.Qid,
                                                  int]) -> float:
     num_qubits = state.shape[0].bit_length() - 1
     result = np.reshape(np.copy(state), (2, ) * num_qubits * 2)
     for qubit, pauli in self._pauli_string.items():
         buffer = np.empty(result.shape, dtype=state.dtype)
         args = protocols.ApplyUnitaryArgs(target_tensor=result,
                                           available_buffer=buffer,
                                           axes=(qubit_map[qubit], ))
         result = protocols.apply_unitary(pauli, args)
     result = np.reshape(result, state.shape)
     return np.trace(result) * self._pauli_string.coefficient
def _strat_act_on_state_vector_from_apply_unitary(
        unitary_value: Any,
        args: 'cirq.ActOnStateVectorArgs',
) -> bool:
    new_target_tensor = protocols.apply_unitary(
        unitary_value,
        protocols.ApplyUnitaryArgs(
            target_tensor=args.target_tensor,
            available_buffer=args.available_buffer,
            axes=args.axes,
        ),
        allow_decompose=False,
        default=NotImplemented)
    if new_target_tensor is NotImplemented:
        return NotImplemented
    args.swap_target_tensor_for(new_target_tensor)
    return True
Exemple #17
0
def assert_has_consistent_apply_unitary(val: Any,
                                        *,
                                        atol: float = 1e-8) -> None:
    """Tests whether a value's _apply_unitary_ is correct.

    Contrasts the effects of the value's `_apply_unitary_` with the
    matrix returned by the value's `_unitary_` method.

    Args:
        val: The value under test. Should have a `__pow__` method.
        atol: Absolute error tolerance.
    """
    # pylint: disable=unused-variable
    __tracebackhide__ = True
    # pylint: enable=unused-variable

    _assert_apply_unitary_works_when_axes_transposed(val, atol=atol)

    expected = protocols.unitary(val, default=None)

    qid_shape = protocols.qid_shape(val)

    eye = qis.eye_tensor((2, ) + qid_shape, dtype=np.complex128)
    actual = protocols.apply_unitary(
        unitary_value=val,
        args=protocols.ApplyUnitaryArgs(
            target_tensor=eye,
            available_buffer=np.ones_like(eye) * float('nan'),
            axes=list(range(1,
                            len(qid_shape) + 1)),
        ),
        default=None,
    )

    # If you don't have a unitary, you shouldn't be able to apply a unitary.
    if expected is None:
        assert actual is None
    else:
        expected = np.kron(np.eye(2), expected)

    # If you applied a unitary, it should match the one you say you have.
    if actual is not None:
        np.testing.assert_allclose(actual.reshape((np.prod(
            (2, ) + qid_shape, dtype=int), ) * 2),
                                   expected,
                                   atol=atol)
Exemple #18
0
def assert_has_consistent_apply_unitary(val: Any,
                                        *,
                                        qubit_count: Optional[int] = None,
                                        atol: float = 1e-8) -> None:
    """Tests whether a value's _apply_unitary_ is correct.

    Contrasts the effects of the value's `_apply_unitary_` with the
    matrix returned by the value's `_unitary_` method.

    Args:
        val: The value under test. Should have a `__pow__` method.
        qubit_count: Usually inferred. The number of qubits the value acts on.
            This argument isn't needed if the gate has a unitary matrix or
            implements `cirq.SingleQubitGate`/`cirq.TwoQubitGate`/
            `cirq.ThreeQubitGate`.
    """

    expected = protocols.unitary(val, default=None)
    if qubit_count is not None:
        n = qubit_count
    elif expected is not None:
        n = expected.shape[0].bit_length() - 1
    else:
        n = _infer_qubit_count(val)

    eye = np.eye(2 << n, dtype=np.complex128).reshape((2, ) * (2 * n + 2))
    actual = protocols.apply_unitary(
        unitary_value=val,
        args=protocols.ApplyUnitaryArgs(target_tensor=eye,
                                        available_buffer=np.ones_like(eye) *
                                        float('nan'),
                                        axes=list(range(1, n + 1))),
        default=None)

    # If you don't have a unitary, you shouldn't be able to apply a unitary.
    if expected is None:
        assert actual is None
    else:
        expected = np.kron(np.eye(2), expected)

    # If you applied a unitary, it should match the one you say you have.
    if actual is not None:
        np.testing.assert_allclose(actual.reshape(2 << n, 2 << n),
                                   expected,
                                   atol=atol)
Exemple #19
0
    def matrix(self) -> np.ndarray:
        """Reconstructs matrix of self using unitaries of underlying operations.

        Raises:
            TypeError: if any of the gates in self does not provide a unitary.
        """
        num_qubits = len(self.qubits)
        num_dim = 2**num_qubits
        qubit_to_axis = {q: i for i, q in enumerate(self.qubits)}
        result = np.zeros((2, ) * (2 * num_qubits), dtype=np.complex128)
        for op, coefficient in self.items():
            identity = np.eye(num_dim,
                              dtype=np.complex128).reshape(result.shape)
            workspace = np.empty_like(identity)
            axes = tuple(qubit_to_axis[q] for q in op.qubits)
            u = protocols.apply_unitary(
                op, protocols.ApplyUnitaryArgs(identity, workspace, axes))
            result += coefficient * u
        return result.reshape((num_dim, num_dim))
Exemple #20
0
    def _apply_unitary_(
        self, args: protocols.ApplyUnitaryArgs
    ) -> Union[np.ndarray, None, NotImplementedType]:
        """Replicates the logic the simulators use to apply the equivalent
           sequence of GateOperations
        """
        state = args.target_tensor
        buffer = args.available_buffer
        for axis in args.axes:
            result = protocols.apply_unitary(self.gate,
                                             protocols.ApplyUnitaryArgs(
                                                 state, buffer, (axis, )),
                                             default=NotImplemented)

            if result is buffer:
                buffer = state

            state = result

        return state
    def apply_unitary(self, action: Any, axes: Sequence[int]) -> bool:
        """Apply unitary to state.

        Args:
            action: The value with a unitary to apply.
            axes: The axes on which to apply the unitary.
        Returns:
            True if the operation succeeded.
        """
        new_target_tensor = protocols.apply_unitary(
            action,
            protocols.ApplyUnitaryArgs(target_tensor=self._state_vector,
                                       available_buffer=self._buffer,
                                       axes=axes),
            allow_decompose=False,
            default=NotImplemented,
        )
        if new_target_tensor is NotImplemented:
            return False
        self._swap_target_tensor_for(new_target_tensor)
        return True
Exemple #22
0
 def _apply_unitary_(
     self, args: 'protocols.ApplyUnitaryArgs'
 ) -> Union[np.ndarray, None, NotImplementedType]:
     return protocols.apply_unitary(self.sub_operation, args, default=None)
Exemple #23
0
def _assert_apply_unitary_works_when_axes_transposed(val: Any,
                                                     *,
                                                     atol: float = 1e-8
                                                     ) -> None:
    """Tests whether a value's _apply_unitary_ handles out-of-order axes.

    A common mistake to make when implementing `_apply_unitary_` is to assume
    that the incoming axes will be contiguous, or ascending, or that they can be
    flattened, or that other axes have a length of two, etc, etc ,etc. This
    method checks that `_apply_unitary_` does the same thing to out-of-order
    axes that it does to contiguous in-order axes.

    Args:
        val: The operation, gate, or other unitary object to test.
        atol: Absolute error tolerance.
    """

    # Only test custom apply unitary methods.
    if not hasattr(val, '_apply_unitary_') or not protocols.has_unitary(val):
        return

    # Pick sizes and shapes.
    shape = protocols.qid_shape(val)
    n = len(shape)
    padded_shape = shape + (1, 2, 2, 3)
    padded_n = len(padded_shape)
    size = np.product(padded_shape).item()

    # Shuffle the axes.
    permutation = list(range(padded_n))
    random.shuffle(permutation)
    transposed_shape = [0] * padded_n
    for i in range(padded_n):
        transposed_shape[permutation[i]] = padded_shape[i]

    # Prepare input states.
    in_order_input = lin_alg_utils.random_superposition(size).reshape(
        padded_shape)
    out_of_order_input = np.empty(shape=transposed_shape, dtype=np.complex128)
    out_of_order_input.transpose(permutation)[...] = in_order_input

    # Apply to in-order and out-of-order axes.
    in_order_output = protocols.apply_unitary(
        val,
        protocols.ApplyUnitaryArgs(
            target_tensor=in_order_input,
            available_buffer=np.empty_like(in_order_input),
            axes=range(n),
        ),
    )
    out_of_order_output = protocols.apply_unitary(
        val,
        protocols.ApplyUnitaryArgs(
            target_tensor=out_of_order_input,
            available_buffer=np.empty_like(out_of_order_input),
            axes=permutation[:n],
        ),
    )

    # Put the out of order output back into order, to enable comparison.
    reordered_output = out_of_order_output.transpose(permutation)

    # The results should be identical.
    if not np.allclose(in_order_output, reordered_output, atol=atol):
        raise AssertionError(
            f'The _apply_unitary_ method of {repr(val)} acted differently on '
            f'out-of-order axes than on in-order axes.\n'
            f'\n'
            f'The failing axis order: {repr(permutation[:n])}')
Exemple #24
0
 def _apply_unitary_(self, args):
     return protocols.apply_unitary(self.to_circuit(), args)
Exemple #25
0
 def _apply_unitary_(
     self, args: protocols.ApplyUnitaryArgs
 ) -> Union[np.ndarray, None, NotImplementedType]:
     return protocols.apply_unitary(self.gate, args, default=NotImplemented)
Exemple #26
0
    def _base_iterator(
        self,
        circuit: circuits.Circuit,
        qubit_order: ops.QubitOrderOrList,
        initial_state: Union[int, np.ndarray],
        perform_measurements: bool = True,
    ) -> Iterator:
        qubits = ops.QubitOrder.as_qubit_order(qubit_order).order_for(
            circuit.all_qubits())
        num_qubits = len(qubits)
        qubit_map = {q: i for i, q in enumerate(qubits)}
        state = wave_function.to_valid_state_vector(initial_state, num_qubits,
                                                    self._dtype)
        if len(circuit) == 0:
            yield SparseSimulatorStep(state, {}, qubit_map, self._dtype)

        def on_stuck(bad_op: ops.Operation):
            return TypeError(
                "Can't simulate unknown operations that don't specify a "
                "_unitary_ method, a _decompose_ method, or "
                "(_has_unitary_ + _apply_unitary_) methods"
                ": {!r}".format(bad_op))

        def keep(potential_op: ops.Operation) -> bool:
            return (protocols.has_unitary(potential_op)
                    or protocols.is_measurement(potential_op))

        state = np.reshape(state, (2, ) * num_qubits)
        buffer = np.empty((2, ) * num_qubits, dtype=self._dtype)
        for moment in circuit:
            measurements = collections.defaultdict(
                list)  # type: Dict[str, List[bool]]

            non_display_ops = (op for op in moment
                               if not isinstance(op, (
                                   ops.SamplesDisplay, ops.WaveFunctionDisplay,
                                   ops.DensityMatrixDisplay)))
            unitary_ops_and_measurements = protocols.decompose(
                non_display_ops, keep=keep, on_stuck_raise=on_stuck)

            for op in unitary_ops_and_measurements:
                indices = [qubit_map[qubit] for qubit in op.qubits]
                # TODO: Support measurements outside of computational basis.
                # TODO: Support mixtures.
                meas = ops.op_gate_of_type(op, ops.MeasurementGate)
                if meas:
                    if perform_measurements:
                        invert_mask = meas.invert_mask or num_qubits * (
                            False, )
                        # Measure updates inline.
                        bits, _ = wave_function.measure_state_vector(
                            state, indices, state)
                        corrected = [
                            bit ^ mask for bit, mask in zip(bits, invert_mask)
                        ]
                        key = protocols.measurement_key(meas)
                        measurements[key].extend(corrected)
                else:
                    result = protocols.apply_unitary(
                        op,
                        args=protocols.ApplyUnitaryArgs(
                            state, buffer, indices))
                    if result is buffer:
                        buffer = state
                    state = result
            yield SparseSimulatorStep(state_vector=state,
                                      measurements=measurements,
                                      qubit_map=qubit_map,
                                      dtype=self._dtype)