1# Copyright 2018 The Cirq Developers
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7#     https://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
15"""A simulator that uses numpy's einsum for sparse matrix operations."""
16
17from typing import (
18    Any,
19    Dict,
20    Iterator,
21    List,
22    Type,
23    TYPE_CHECKING,
24    Union,
25    Sequence,
26    Optional,
27)
28
29import numpy as np
30
31from cirq import ops, protocols, qis
32from cirq.sim import (
33    simulator,
34    state_vector,
35    state_vector_simulator,
36    act_on_state_vector_args,
37)
38
39if TYPE_CHECKING:
40    import cirq
41    from numpy.typing import DTypeLike
42
43
44class Simulator(
45    state_vector_simulator.SimulatesIntermediateStateVector['SparseSimulatorStep'],
46    simulator.SimulatesExpectationValues,
47):
48    """A sparse matrix state vector simulator that uses numpy.
49
50    This simulator can be applied on circuits that are made up of operations
51    that have a `_unitary_` method, or `_has_unitary_` and
52    `_apply_unitary_`, `_mixture_` methods, are measurements, or support a
53    `_decompose_` method that returns operations satisfying these same
54    conditions. That is to say, the operations should follow the
55    `cirq.SupportsConsistentApplyUnitary` protocol, the `cirq.SupportsUnitary`
56    protocol, the `cirq.SupportsMixture` protocol, or the
57    `cirq.CompositeOperation` protocol. It is also permitted for the circuit
58    to contain measurements which are operations that support
59    `cirq.SupportsKraus` and `cirq.SupportsMeasurementKey`
60
61    This simulator supports four types of simulation.
62
63    Run simulations which mimic running on actual quantum hardware. These
64    simulations do not give access to the state vector (like actual hardware).
65    There are two variations of run methods, one which takes in a single
66    (optional) way to resolve parameterized circuits, and a second which
67    takes in a list or sweep of parameter resolver:
68
69        run(circuit, param_resolver, repetitions)
70
71        run_sweep(circuit, params, repetitions)
72
73    The simulation performs optimizations if the number of repetitions is
74    greater than one and all measurements in the circuit are terminal (at the
75    end of the circuit). These methods return `Result`s which contain both
76    the measurement results, but also the parameters used for the parameterized
77    circuit operations. The initial state of a run is always the all 0s state
78    in the computational basis.
79
80    By contrast the simulate methods of the simulator give access to the
81    state vector of the simulation at the end of the simulation of the circuit.
82    These methods take in two parameters that the run methods do not: a
83    qubit order and an initial state. The qubit order is necessary because an
84    ordering must be chosen for the kronecker product (see
85    `SparseSimulationTrialResult` for details of this ordering). The initial
86    state can be either the full state vector, or an integer which represents
87    the initial state of being in a computational basis state for the binary
88    representation of that integer. Similar to run methods, there are two
89    simulate methods that run for single runs or for sweeps across different
90    parameters:
91
92        simulate(circuit, param_resolver, qubit_order, initial_state)
93
94        simulate_sweep(circuit, params, qubit_order, initial_state)
95
96    The simulate methods in contrast to the run methods do not perform
97    repetitions. The result of these simulations is a
98    `SparseSimulationTrialResult` which contains, in addition to measurement
99    results and information about the parameters that were used in the
100    simulation,access to the state via the `state` method and `StateVectorMixin`
101    methods.
102
103    If one wishes to perform simulations that have access to the
104    state vector as one steps through running the circuit there is a generator
105    which can be iterated over and each step is an object that gives access
106    to the state vector.  This stepping through a `Circuit` is done on a
107    `Moment` by `Moment` manner.
108
109        simulate_moment_steps(circuit, param_resolver, qubit_order,
110                              initial_state)
111
112    One can iterate over the moments via
113
114        for step_result in simulate_moments(circuit):
115           # do something with the state vector via step_result.state_vector
116
117    Note also that simulations can be stochastic, i.e. return different results
118    for different runs.  The first version of this occurs for measurements,
119    where the results of the measurement are recorded.  This can also
120    occur when the circuit has mixtures of unitaries.
121
122    If only the expectation values for some observables on the final state are
123    required, there are methods for that as well. These methods take a mapping
124    of names to observables, and return a map (or list of maps) of those names
125    to the corresponding expectation values.
126
127        simulate_expectation_values(circuit, observables, param_resolver,
128                                    qubit_order, initial_state,
129                                    permit_terminal_measurements)
130
131        simulate_expectation_values_sweep(circuit, observables, params,
132                                          qubit_order, initial_state,
133                                          permit_terminal_measurements)
134
135    Expectation values generated by these methods are exact (up to precision of
136    the floating-point type used); the closest analogy on hardware requires
137    estimating the expectation values from several samples.
138
139    See `Simulator` for the definitions of the supported methods.
140    """
141
142    # TODO(#3388) Add documentation for Raises.
143    # pylint: disable=missing-raises-doc
144    def __init__(
145        self,
146        *,
147        dtype: Type[np.number] = np.complex64,
148        noise: 'cirq.NOISE_MODEL_LIKE' = None,
149        seed: 'cirq.RANDOM_STATE_OR_SEED_LIKE' = None,
150        split_untangled_states: bool = True,
151    ):
152        """A sparse matrix simulator.
153
154        Args:
155            dtype: The `numpy.dtype` used by the simulation. One of
156                `numpy.complex64` or `numpy.complex128`.
157            noise: A noise model to apply while simulating.
158            seed: The random seed to use for this simulator.
159            split_untangled_states: If True, optimizes simulation by running
160                unentangled qubit sets independently and merging those states
161                at the end.
162        """
163        if np.dtype(dtype).kind != 'c':
164            raise ValueError(f'dtype must be a complex type but was {dtype}')
165        super().__init__(
166            dtype=dtype,
167            noise=noise,
168            seed=seed,
169            split_untangled_states=split_untangled_states,
170        )
171
172    # pylint: enable=missing-raises-doc
173    # TODO(#3388) Add documentation for Args.
174    # pylint: disable=missing-param-doc
175    def _create_partial_act_on_args(
176        self,
177        initial_state: Union['cirq.STATE_VECTOR_LIKE', 'cirq.ActOnStateVectorArgs'],
178        qubits: Sequence['cirq.Qid'],
179        logs: Dict[str, Any],
180    ):
181        """Creates the ActOnStateVectorArgs for a circuit.
182
183        Args:
184            initial_state: The initial state for the simulation in the
185                computational basis.
186            qubits: Determines the canonical ordering of the qubits. This
187                is often used in specifying the initial state, i.e. the
188                ordering of the computational basis states.
189
190        Returns:
191            ActOnStateVectorArgs for the circuit.
192        """
193        if isinstance(initial_state, act_on_state_vector_args.ActOnStateVectorArgs):
194            return initial_state
195
196        qid_shape = protocols.qid_shape(qubits)
197        state = qis.to_valid_state_vector(
198            initial_state, len(qubits), qid_shape=qid_shape, dtype=self._dtype
199        )
200
201        return act_on_state_vector_args.ActOnStateVectorArgs(
202            target_tensor=np.reshape(state, qid_shape),
203            available_buffer=np.empty(qid_shape, dtype=self._dtype),
204            qubits=qubits,
205            prng=self._prng,
206            log_of_measurement_results=logs,
207        )
208
209    # pylint: enable=missing-param-doc
210    def _create_step_result(
211        self,
212        sim_state: 'cirq.OperationTarget[cirq.ActOnStateVectorArgs]',
213    ):
214        return SparseSimulatorStep(
215            sim_state=sim_state,
216            simulator=self,
217            dtype=self._dtype,
218        )
219
220    def simulate_expectation_values_sweep_iter(
221        self,
222        program: 'cirq.AbstractCircuit',
223        observables: Union['cirq.PauliSumLike', List['cirq.PauliSumLike']],
224        params: 'cirq.Sweepable',
225        qubit_order: ops.QubitOrderOrList = ops.QubitOrder.DEFAULT,
226        initial_state: Any = None,
227        permit_terminal_measurements: bool = False,
228    ) -> Iterator[List[float]]:
229        if not permit_terminal_measurements and program.are_any_measurements_terminal():
230            raise ValueError(
231                'Provided circuit has terminal measurements, which may '
232                'skew expectation values. If this is intentional, set '
233                'permit_terminal_measurements=True.'
234            )
235        qubit_order = ops.QubitOrder.as_qubit_order(qubit_order)
236        qmap = {q: i for i, q in enumerate(qubit_order.order_for(program.all_qubits()))}
237        if not isinstance(observables, List):
238            observables = [observables]
239        pslist = [ops.PauliSum.wrap(pslike) for pslike in observables]
240        yield from (
241            [obs.expectation_from_state_vector(result.final_state_vector, qmap) for obs in pslist]
242            for result in self.simulate_sweep_iter(
243                program, params, qubit_order=qubit_order, initial_state=initial_state
244            )
245        )
246
247
248class SparseSimulatorStep(
249    state_vector.StateVectorMixin,
250    state_vector_simulator.StateVectorStepResult,
251):
252    """A `StepResult` that includes `StateVectorMixin` methods."""
253
254    def __init__(
255        self,
256        sim_state: 'cirq.OperationTarget[cirq.ActOnStateVectorArgs]',
257        simulator: Simulator,
258        dtype: 'DTypeLike' = np.complex64,
259    ):
260        """Results of a step of the simulator.
261
262        Args:
263            sim_state: The qubit:ActOnArgs lookup for this step.
264            simulator: The simulator used to create this.
265            dtype: The `numpy.dtype` used by the simulation. One of
266                `numpy.complex64` or `numpy.complex128`.
267        """
268        qubit_map = {q: i for i, q in enumerate(sim_state.qubits)}
269        super().__init__(sim_state=sim_state, qubit_map=qubit_map)
270        self._dtype = dtype
271        self._state_vector: Optional[np.ndarray] = None
272        self._simulator = simulator
273
274    def _simulator_state(self) -> state_vector_simulator.StateVectorSimulatorState:
275        return state_vector_simulator.StateVectorSimulatorState(
276            qubit_map=self.qubit_map, state_vector=self.state_vector(copy=False)
277        )
278
279    def state_vector(self, copy: bool = True):
280        """Return the state vector at this point in the computation.
281
282        The state is returned in the computational basis with these basis
283        states defined by the qubit_map. In particular the value in the
284        qubit_map is the index of the qubit, and these are translated into
285        binary vectors where the last qubit is the 1s bit of the index, the
286        second-to-last is the 2s bit of the index, and so forth (i.e. big
287        endian ordering).
288
289        Example:
290             qubit_map: {QubitA: 0, QubitB: 1, QubitC: 2}
291             Then the returned vector will have indices mapped to qubit basis
292             states like the following table
293
294                |     | QubitA | QubitB | QubitC |
295                | :-: | :----: | :----: | :----: |
296                |  0  |   0    |   0    |   0    |
297                |  1  |   0    |   0    |   1    |
298                |  2  |   0    |   1    |   0    |
299                |  3  |   0    |   1    |   1    |
300                |  4  |   1    |   0    |   0    |
301                |  5  |   1    |   0    |   1    |
302                |  6  |   1    |   1    |   0    |
303                |  7  |   1    |   1    |   1    |
304
305        Args:
306            copy: If True, then the returned state is a copy of the state
307                vector. If False, then the state vector is not copied,
308                potentially saving memory. If one only needs to read derived
309                parameters from the state vector and store then using False
310                can speed up simulation by eliminating a memory copy.
311        """
312        if self._state_vector is None:
313            self._state_vector = np.array([1])
314            state = self._merged_sim_state
315            if state is not None:
316                vector = state.target_tensor
317                size = np.prod(vector.shape, dtype=np.int64)
318                self._state_vector = np.reshape(vector, size)
319        return self._state_vector.copy() if copy else self._state_vector
320
321    def set_state_vector(self, state: 'cirq.STATE_VECTOR_LIKE'):
322        """Set the state vector.
323
324        One can pass a valid full state to this method by passing a numpy
325        array. Or, alternatively, one can pass an integer, and then the state
326        will be set to lie entirely in the computation basis state for the
327        binary expansion of the passed integer.
328
329        Args:
330            state: If an int, the state vector set is the state vector
331                corresponding to a computational basis state. If a numpy
332                array this is the full state vector.
333        """
334        self._sim_state = self._simulator._create_act_on_args(state, self._qubits)
335