# Source code for cirq.sim.simulator

# Copyright 2018 The Cirq Developers
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""Abstract base classes for different types of simulators.

Simulator types include:

SimulatesSamples: mimics the interface of quantum hardware.

SimulatesAmplitudes: computes amplitudes of desired bitstrings in the
final state of the simulation.

SimulatesFinalState: allows access to the final state of the simulation.

SimulatesIntermediateState: allows for access to the state of the simulation
as the simulation iterates through the moments of a cirq.
"""

from typing import (
Any,
Dict,
Iterator,
List,
Sequence,
Tuple,
Optional,
TYPE_CHECKING,
)

import abc
import collections

import numpy as np

from cirq import circuits, ops, protocols, study, value, work

if TYPE_CHECKING:
import cirq

[docs]class SimulatesSamples(work.Sampler, metaclass=abc.ABCMeta):
"""Simulator that mimics running on quantum hardware.

Implementors of this interface should implement the _run method.
"""

[docs]    def run_sweep(
self,
program: 'cirq.Circuit',
params: study.Sweepable,
repetitions: int = 1,
) -> List[study.TrialResult]:
"""Runs the supplied Circuit, mimicking quantum hardware.

In contrast to run, this allows for sweeping over different parameter
values.

Args:
program: The circuit to simulate.
params: Parameters to run with the program.
repetitions: The number of repetitions to simulate.

Returns:
TrialResult list for this run; one for each possible parameter
resolver.
"""
if not program.has_measurements():
raise ValueError("Circuit has no measurements to sample.")

_verify_unique_measurement_keys(program)

trial_results = []  # type: List[study.TrialResult]
for param_resolver in study.to_resolvers(params):
measurements = self._run(circuit=program,
param_resolver=param_resolver,
repetitions=repetitions)
trial_results.append(
study.TrialResult.from_single_parameter_set(
params=param_resolver, measurements=measurements))
return trial_results

@abc.abstractmethod
def _run(
self,
circuit: circuits.Circuit,
param_resolver: study.ParamResolver,
repetitions: int
) -> Dict[str, np.ndarray]:
"""Run a simulation, mimicking quantum hardware.

Args:
circuit: The circuit to simulate.
param_resolver: Parameters to run with the program.
repetitions: Number of times to repeat the run.

Returns:
A dictionary from measurement gate key to measurement
results. Measurement results are stored in a 2-dimensional
numpy array, the first dimension corresponding to the repetition
and the second to the actual boolean measurement results (ordered
by the qubits being measured.)
"""
raise NotImplementedError()

[docs]class SimulatesAmplitudes(metaclass=abc.ABCMeta):
"""Simulator that computes final amplitudes of given bitstrings.

Given a circuit and a list of bitstrings, computes the amplitudes
of the given bitstrings in the state obtained by applying the circuit
to the all zeros state. Implementors of this interface should implement
the compute_amplitudes_sweep method.
"""

[docs]    def compute_amplitudes(
self,
program: 'cirq.Circuit',
bitstrings: Sequence[int],
param_resolver: 'study.ParamResolverOrSimilarType' = None,
qubit_order: ops.QubitOrderOrList = ops.QubitOrder.DEFAULT,
) -> Sequence[complex]:
"""Computes the desired amplitudes.

The initial state is assumed to be the all zeros state.

Args:
program: The circuit to simulate.
bitstrings: The bitstrings whose amplitudes are desired, input
as an integer array where each integer is formed from measured
qubit values according to qubit_order from most to least
significant qubit, i.e. in big-endian ordering.
param_resolver: Parameters to run with the program.
qubit_order: Determines the canonical ordering of the qubits. This
is often used in specifying the initial state, i.e. the
ordering of the computational basis states.

Returns:
List of amplitudes.
"""
return self.compute_amplitudes_sweep(
program, bitstrings, study.ParamResolver(param_resolver),
qubit_order)[0]

[docs]    @abc.abstractmethod
def compute_amplitudes_sweep(
self,
program: 'cirq.Circuit',
bitstrings: Sequence[int],
params: study.Sweepable,
qubit_order: ops.QubitOrderOrList = ops.QubitOrder.DEFAULT,
) -> Sequence[Sequence[complex]]:
"""Computes the desired amplitudes.

The initial state is assumed to be the all zeros state.

Args:
program: The circuit to simulate.
bitstrings: The bitstrings whose amplitudes are desired, input
as an integer array where each integer is formed from measured
qubit values according to qubit_order from most to least
significant qubit, i.e. in big-endian ordering.
params: Parameters to run with the program.
qubit_order: Determines the canonical ordering of the qubits. This
is often used in specifying the initial state, i.e. the
ordering of the computational basis states.

Returns:
List of lists of amplitudes. The outer dimension indexes the
circuit parameters and the inner dimension indexes the bitstrings.
"""
raise NotImplementedError()

[docs]class SimulatesFinalState(metaclass=abc.ABCMeta):
"""Simulator that allows access to a quantum computer's final state.

Implementors of this interface should implement the simulate_sweep
method. This simulator only returns the state of the quantum system
for the final step of a simulation. This simulator state may be a wave
function, the density matrix, or another representation, depending on the
implementation.  For simulators that also allow stepping through
a circuit see SimulatesIntermediateState.
"""

[docs]    def simulate(
self,
program: 'cirq.Circuit',
param_resolver: 'study.ParamResolverOrSimilarType' = None,
qubit_order: ops.QubitOrderOrList = ops.QubitOrder.DEFAULT,
initial_state: Any = None,
) -> 'SimulationTrialResult':
"""Simulates the supplied Circuit.

This method returns a result which allows access to the entire
wave function.

Args:
program: The circuit to simulate.
param_resolver: Parameters to run with the program.
qubit_order: Determines the canonical ordering of the qubits. This
is often used in specifying the initial state, i.e. the
ordering of the computational basis states.
initial_state: The initial state for the simulation. The  form of
this state depends on the simulation implementation.  See
documentation of the implementing class for details.

Returns:
SimulationTrialResults for the simulation. Includes the final state.
"""
return self.simulate_sweep(
program,
study.ParamResolver(param_resolver),
qubit_order,
initial_state)[0]

[docs]    @abc.abstractmethod
def simulate_sweep(
self,
program: 'cirq.Circuit',
params: study.Sweepable,
qubit_order: ops.QubitOrderOrList = ops.QubitOrder.DEFAULT,
initial_state: Any = None,
) -> List['SimulationTrialResult']:
"""Simulates the supplied Circuit.

This method returns a result which allows access to the entire
wave function. In contrast to simulate, this allows for sweeping
over different parameter values.

Args:
program: The circuit to simulate.
params: Parameters to run with the program.
qubit_order: Determines the canonical ordering of the qubits. This
is often used in specifying the initial state, i.e. the
ordering of the computational basis states.
initial_state: The initial state for the simulation. The form of
this state depends on the simulation implementation.  See
documentation of the implementing class for details.

Returns:
List of SimulationTrialResults for this run, one for each
possible parameter resolver.
"""
raise NotImplementedError()

[docs]class SimulatesIntermediateState(SimulatesFinalState, metaclass=abc.ABCMeta):
"""A SimulatesFinalState that simulates a circuit by moments.

Whereas a general SimulatesFinalState may return the entire wave
function at the end of a circuit, a SimulatesIntermediateState can
simulate stepping through the moments of a circuit.

Implementors of this interface should implement the _simulator_iterator
method.
"""

[docs]    def simulate_sweep(
self,
program: 'cirq.Circuit',
params: study.Sweepable,
qubit_order: ops.QubitOrderOrList = ops.QubitOrder.DEFAULT,
initial_state: Any = None,
) -> List['SimulationTrialResult']:
"""Simulates the supplied Circuit.

This method returns a result which allows access to the entire
wave function. In contrast to simulate, this allows for sweeping
over different parameter values.

Args:
program: The circuit to simulate.
params: Parameters to run with the program.
qubit_order: Determines the canonical ordering of the qubits. This
is often used in specifying the initial state, i.e. the
ordering of the computational basis states.
initial_state: The initial state for the simulation. The form of
this state depends on the simulation implementation. See
documentation of the implementing class for details.

Returns:
List of SimulationTrialResults for this run, one for each
possible parameter resolver.
"""
trial_results = []
qubit_order = ops.QubitOrder.as_qubit_order(qubit_order)
for param_resolver in study.to_resolvers(params):
all_step_results = self.simulate_moment_steps(
program, param_resolver, qubit_order, initial_state)
measurements = {}  # type: Dict[str, np.ndarray]
for step_result in all_step_results:
for k, v in step_result.measurements.items():
measurements[k] = np.array(v, dtype=np.uint8)
trial_results.append(
self._create_simulator_trial_result(
params=param_resolver,
measurements=measurements,
final_simulator_state=step_result._simulator_state()))
return trial_results

[docs]    def simulate_moment_steps(
self,
circuit: circuits.Circuit,
param_resolver: 'study.ParamResolverOrSimilarType' = None,
qubit_order: ops.QubitOrderOrList = ops.QubitOrder.DEFAULT,
initial_state: Any = None
) -> Iterator:
"""Returns an iterator of StepResults for each moment simulated.

If the circuit being simulated is empty, a single step result should
be returned with the state being set to the initial state.

Args:
circuit: The Circuit to simulate.
param_resolver: A ParamResolver for determining values of Symbols.
qubit_order: Determines the canonical ordering of the qubits. This
is often used in specifying the initial state, i.e. the
ordering of the computational basis states.
initial_state: The initial state for the simulation. The form of
this state depends on the simulation implementation. See
documentation of the implementing class for details.

Returns:
Iterator that steps through the simulation, simulating each
moment and returning a StepResult for each moment.
"""
return self._simulator_iterator(
circuit,
study.ParamResolver(param_resolver),
qubit_order,
initial_state)

@abc.abstractmethod
def _simulator_iterator(
self,
circuit: circuits.Circuit,
param_resolver: study.ParamResolver,
qubit_order: ops.QubitOrderOrList,
initial_state: Any,
) -> Iterator:
"""Iterator over StepResult from Moments of a Circuit.

Args:
circuit: The circuit to simulate.
param_resolver: A ParamResolver for determining values of
Symbols.
qubit_order: Determines the canonical ordering of the qubits. This
is often used in specifying the initial state, i.e. the
ordering of the computational basis states.
initial_state: The initial state for the simulation. The form of
this state depends on the simulation implementation. See
documentation of the implementing class for details.

Yields:
StepResults from simulating a Moment of the Circuit.
"""
raise NotImplementedError()

def _create_simulator_trial_result(self,
params: study.ParamResolver,
measurements: Dict[str, np.ndarray],
final_simulator_state: Any) \
-> 'SimulationTrialResult':
"""This method can be overridden to creation of a trial result.

Args:
params: The ParamResolver for this trial.
measurements: The measurement results for this trial.
final_simulator_state: The final state of the simulator for the
StepResult.

Returns:
The SimulationTrialResult.
"""
return SimulationTrialResult(
params=params,
measurements=measurements,
final_simulator_state=final_simulator_state)

[docs]class StepResult(metaclass=abc.ABCMeta):
"""Results of a step of a SimulatesIntermediateState.

Attributes:
measurements: A dictionary from measurement gate key to measurement
results, ordered by the qubits that the measurement operates on.
"""

[docs]    def __init__(self,
measurements: Optional[Dict[str, List[int]]] = None) -> None:
self.measurements = measurements or collections.defaultdict(list)

@abc.abstractmethod
def _simulator_state(self) -> Any:
"""Returns the simulator state of the simulator after this step.

This method starts with an underscore to indicate that it is private.
To access public state, see public methods on StepResult.

The form of the simulator_state depends on the implementation of the
simulation,see documentation for the implementing class for the form of
details.
"""

[docs]    @abc.abstractmethod
def sample(self,
qubits: List[ops.Qid],
repetitions: int = 1,
seed: 'cirq.RANDOM_STATE_OR_SEED_LIKE' = None) -> np.ndarray:
"""Samples from the system at this point in the computation.

Note that this does not collapse the wave function.

Args:
qubits: The qubits to be sampled in an order that influence the
returned measurement results.
repetitions: The number of samples to take.
seed: A seed for the pseudorandom number generator.

Returns:
Measurement results with True corresponding to the |1⟩ state.
The outer list is for repetitions, and the inner corresponds to
measurements ordered by the supplied qubits. These lists
are wrapped as an numpy ndarray.
"""
raise NotImplementedError()

[docs]    def sample_measurement_ops(self,
measurement_ops: List[ops.GateOperation],
repetitions: int = 1,
seed: 'cirq.RANDOM_STATE_OR_SEED_LIKE' = None
) -> Dict[str, np.ndarray]:
"""Samples from the system at this point in the computation.

Note that this does not collapse the wave function.

In contrast to sample which samples qubits, this takes a list of
cirq.GateOperation instances whose gates are cirq.MeasurementGate
instances and then returns a mapping from the key in the measurement
gate to the resulting bit strings. Different measurement operations must
not act on the same qubits.

Args:
measurement_ops: GateOperation instances whose gates are
MeasurementGate instances to be sampled form.
repetitions: The number of samples to take.
seed: A seed for the pseudorandom number generator.

Returns: A dictionary from measurement gate key to measurement
results. Measurement results are stored in a 2-dimensional
numpy array, the first dimension corresponding to the repetition
and the second to the actual boolean measurement results (ordered
by the qubits being measured.)

Raises:
ValueError: If the operation's gates are not MeasurementGate
instances or a qubit is acted upon multiple times by different
operations from measurement_ops.
"""
bounds = {}  # type: Dict[str, Tuple]
all_qubits = []  # type: List[ops.Qid]
meas_ops = {}
current_index = 0
for op in measurement_ops:
gate = op.gate
if not isinstance(gate, ops.MeasurementGate):
raise ValueError('{} was not a MeasurementGate'.format(gate))
key = protocols.measurement_key(gate)
meas_ops[key] = gate
if key in bounds:
raise ValueError(
'Duplicate MeasurementGate with key {}'.format(key))
bounds[key] = (current_index, current_index + len(op.qubits))
all_qubits.extend(op.qubits)
current_index += len(op.qubits)
indexed_sample = self.sample(all_qubits, repetitions, seed=seed)

results = {}
for k, (s, e) in bounds.items():
before_invert_mask = indexed_sample[:, s:e]
results[k] = before_invert_mask ^ (np.logical_and(
return results

[docs]@value.value_equality(unhashable=True)
class SimulationTrialResult:
"""Results of a simulation by a SimulatesFinalState.

Unlike TrialResult these results contain the final simulator_state of the
system. This simulator_state is dependent on the simulation implementation
and may be, for example, the wave function of the system or the density
matrix of the system.

Attributes:
params: A ParamResolver of settings used for this result.
measurements: A dictionary from measurement gate key to measurement
results. Measurement results are a numpy ndarray of actual boolean
measurement results (ordered by the qubits acted on by the
measurement gate.)
"""

[docs]    def __init__(self,
params: study.ParamResolver,
measurements: Dict[str, np.ndarray],
final_simulator_state: Any) -> None:
self.params = params
self.measurements = measurements
self._final_simulator_state = final_simulator_state

def __repr__(self) -> str:
return (f'cirq.SimulationTrialResult(params={self.params!r}, '
f'measurements={self.measurements!r}, '
f'final_simulator_state={self._final_simulator_state!r})')

def __str__(self) -> str:

def bitstring(vals):
separator = ' ' if np.max(vals) >= 10 else ''
return separator.join(str(int(v)) for v in vals)

results = sorted(
[(key, bitstring(val)) for key, val in self.measurements.items()])
if not results:
return '(no measurements)'
return ' '.join([f'{key}={val}' for key, val in results])

def _repr_pretty_(self, p: Any, cycle: bool) -> None:
"""Text output in Jupyter."""
if cycle:
# There should never be a cycle.  This is just in case.
p.text('SimulationTrialResult(...)')
else:
p.text(str(self))

def _value_equality_values_(self) -> Any:
measurements = {k: v.tolist() for k, v in
sorted(self.measurements.items())}
return (self.params, measurements, self._final_simulator_state)

@property
def qubit_map(self) -> Dict[ops.Qid, int]:
"""A map from Qid to index used to define the ordering of the basis in
the result.
"""
return self._final_simulator_state.qubit_map

def _qid_shape_(self) -> Tuple[int, ...]:
return _qubit_map_to_shape(self.qubit_map)

def _qubit_map_to_shape(qubit_map: Dict[ops.Qid, int]) -> Tuple[int, ...]:
qid_shape: List[int] = [-1] * len(qubit_map)
try:
for q, i in qubit_map.items():
qid_shape[i] = q.dimension
except IndexError:
raise ValueError(
'Invalid qubit_map. Qubit index out of bounds. Map is <{!r}>.'.
format(qubit_map))
if -1 in qid_shape:
raise ValueError(
'Invalid qubit_map. Duplicate qubit index. Map is <{!r}>.'.format(
qubit_map))
return tuple(qid_shape)

def _verify_unique_measurement_keys(circuit: circuits.Circuit):
result = collections.Counter(
protocols.measurement_key(op, default=None)
for op in ops.flatten_op_tree(iter(circuit)))
result[None] = 0
duplicates = [k for k, v in result.most_common() if v > 1]
if duplicates:
raise ValueError('Measurement key {} repeated'.format(
",".join(duplicates)))