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