# Simulator¶

True-Q™ includes a built-in Simulator that can be configured with custom or pre-defined noise models. Noise models can define gate and cycle noise, as well as preparation and measurement noise. A simulator object can be used to:

1. Simulate the final quantum state of a circuit. This will be a pure state vector if the noise model is ideal or has only unitary noise, and will be a density matrix otherwise.

2. Simulate the total effective operator of a circuit. This will be a unitary matrix if the noise model is ideal or has only unitary noise, and will be a superoperator otherwise.

3. Sample a given number of bitstrings (or ditstrings if higher energy levels are defined) from the distribution defined by the final simulated quantum state of a circuit. These results can be returned, or can be automatically populated into the respective results attributes of the given circuits.

## Noise Models¶

A Simulator instance owns an ordered list of noise models, represented as NoiseSource objects. There are a number of built-in noise models, which can be added to the simulator via the add_*() wrapper functions, which are formatted such that they can be chained together in a single simulator instance as follows:

import trueq as tq

# make a simulator that first over-rotates single qubit gates by 0.03, and then
# adds stochastic error in the X direction


These add_* functions create and append a noise source to the simulator, without requiring the direct use of NoiseSources. If desired, both built-in and custom defined noise sources can be appended to the simulator directly via append_noise_source(). An example of defining a custom noise source by extending NoiseSource and appending it to the simulator is provided at the end of this guide.

A NoiseSource object contains two important features: a Match object as a property, and a method called apply(). The apply method takes as arguments the current cycle to simulate 1, the simulation backend (described below), and a cache of information that can optionally be used to store information about the circuit containing the cycle. The job of the apply method is to inform the simulation backend about all of the noise it wishes to apply. The noise can come in the form of Gates, Superops, state preparation errors, or measurement errors.

## Conditional Noise¶

The Match class provides the ability to instruct noise sources to act on a filtered set of operations in each cycle. There are a variety of built-in matches, which allow matching noise sources to specific labels, gates, cycles, as well as matching to only single qubit gates, 90 degree rotations in the XY plane of the Bloch sphere, logical combinations of all of these, etc. Match objects are passed to built-in noise sources via the match argument as seen in some of the examples below.

## Simulation Backends¶

There are many strategies for performing a quantum simulation on a classical computer, and users can change the backend of a Simulator during construction.

Only advanced usage of the simulator requires an understanding of how simulation backends work, and what follows is a high level overview. The actual simulation of a quantum circuit is abstracted away from the Simulator and its noise sources into some implementation of the abstract SimulationBackend class.

A simulation backend contains a Runner class with methods like process_gate() and process_superop() which the noise sources invoke. After all noise sources have been run on all cycles in the circuit, one can access the final simulation via the simulation backend attribute value, though this is usually done automatically by the helper methods operator(), run(),sample(), or state().

The default simulation backend is PropagationBackend whose runner performs simulation by updating the value of one of the following objects, depending on the nature of the simulation at hand:

• A StateTensor which can store either a pure state or a mixed state. Anytime it represents a pure state and a noise model asks it to update by a non-unitary operation, it casts itself into a mixed state.

• A OperatorTensor which can store either a unitary or a superoperator. Anytime it represents a unitary and a noise model asks it to update itself by a non-unitary operation, it casts itself into a superoperator.

During the simulation of a circuit, the simulator iterates through cycles and passes each cycle (within the cycle wrapper class) to each noise model in turn 1 . The order of noise sources is defined by the order in which they are added to the simulator. Note that if none of the added noise sources attempt the simulation of a particular gate (e.g. all noise sources only add perturbations to the state based on the operations they see), then an ideal simulation of these missing gates automatically follows.

The following note is included for advanced users of the simulator and for the benefit of users who are interested in writing their own noise model (an example is provided in the examples section).

Every simulator has a default noise source whose job is to attempt to simulate any previously un-simulated operations. This default noise source is automatically appended to a simulator’s noise sources during simulation. In order to ensure an operation is simulated only once, every noise source defines a flag that specifies whether it is the kind of noise source that tries to actually implement the operations it finds, as opposed to only injecting noise. For example, add_overrotation() sets this flag to True because it implements gates as $$U^{1+\epsilon}$$ which is an attempt to actually implement $$U$$, but add_depolarizing() sets this flag to False because it just adds a small perturbation to the state or superoperator during each cycle. During simulation each operator is temporarily wrapped in an OpWrapper whose purpose is to store this metadata via the attribute has_been_simulated.

For example, consider the following simulator:

import trueq as tq
import trueq.simulation as tqs


Here, every X operation encountered by the simulator will be overrotated, and will also be marked as has_been_simulated=True. Any other operations encountered by the simulator, such as a Z gate, will subsequently be simulated ideally.
This description was slightly simplified for brevity. In order to allow non-Markovian noise, instead of accepting only the current cycle to simulate, apply() instead accepts a list of all cycles up to and including the current cycle. Markovian noise sources, which are the most common cases, will choose to only look at the current cycle.