Note

Click here to download the full example code

# Simulator: Introduction¶

In this example, we go through the basic features of the True-Qᵀᴹ simulator. We begin by importing True-Qᵀᴹ and instantiating a circuit to play with.

```
import trueq as tq
import matplotlib.pyplot as plt
import numpy as np
circuit = tq.Circuit([{0: tq.Gate.x, 1: tq.Gate.y}, {(0, 1): tq.Gate.cz}])
circuit.measure_all()
```

## Simulator Basics¶

When we instantiate a new simulator object, it is always noiseless.

```
sim = tq.Simulator()
```

We can use it to simulate the final state of the circuit, with all initial states
prepared as \(|0\rangle\). Since the simulator is noiseless, the output state
is a pure state; a noisy simulator will generally return a density matrix instead.
If we want to force the output to be a density matrix, we can use
`sim0.state(circuit).upgrade().mat()`

.

```
sim.state(circuit).mat()
```

Out:

```
array([0.+0.j, 0.+0.j, 0.+0.j, 0.-1.j])
```

We can also use it to compute the overall action of the circuit. Since the simulator is currently noiseless, the output is a unitary matrix; a noisy simulator will return a superoperator (in the rowstacked basis).

```
tq.visualization.plot_mat(sim.operator(circuit).mat())
```

Finally, we can use a simulator to populate the results of the circuit. Currently, the simulator has no results:

```
circuit.results
```

Out:

```
Results({})
```

But after calling the `run()`

method, it has results that are randomly sampled
bitstrings from the final state of a state simulation. In this case all 100 shots
end in the `"11"`

state because the final state is a computational eigenstate.

```
sim.run(circuit, n_shots=100)
circuit.results
```

Out:

```
Results({'11': 100})
```

## Adding Noise Sources¶

We construct a noisy simulator by appending noise sources to a noiseless simulator.

*Simulation is cycle-based.*
Each noise source is called to add noise to the quantum state (or to the superoperator
if `operator()`

is called) for each cycle in a circuit. The order that noise sources
are applied is dictated by the order in which they were added to the simulator.

```
# Add an overrotation noise, which causes single qubit gates to be simulated as U^1.02
sim.add_overrotation(0.02)
# Add a depolarizing noise source at a rate of 0.8% per acted-on qubit per cycle
sim.add_depolarizing(0.008)
# Note that noisy simulators can be constructed as one-liners
other_sim = tq.Simulator().add_overrotation(0.02).add_depolarizing(0.008)
```

Now when we ask for the final state, we get a density operator rather than a pure state:

```
tq.visualization.plot_mat(sim.state(circuit).mat())
```

And after calling the `run()`

method (overwriting the results from above),
we end up with noisy results:

```
sim.run(circuit, n_shots=100, overwrite=True)
circuit.results
```

Out:

```
Results({'01': 1, '10': 1, '11': 98})
```

We can also specify infinite shots to get the expectation values of each bitstring.

```
sim.run(circuit, n_shots=np.inf, overwrite=True)
circuit.results
```

Out:

```
Results({'00': 7.99041612947897e-05, '01': 0.008859008596685874, '10': 0.008859008596685874, '11': 0.9822020786453306})
```

## Restricting Noise to Certain Labels or Gates¶

Many of the noise sources have the options `noisy_labels`

and `noisy_gates`

which
specify that only certain qubits or gates should recieve the noise source in question.

```
# initialize a simulator with different dephasing rates on the qubits
bitflip = lambda p: [np.sqrt(1 - p) * np.eye(2), np.sqrt(p) * np.fliplr(np.eye(2))]
sim0 = tq.Simulator()
sim0.add_kraus(bitflip(0.05), noisy_labels=0)
sim0.add_kraus(bitflip(0.09), noisy_labels=1)
# initialize a simulator that targets only a specific gate
sim1 = tq.Simulator().add_kraus(bitflip(0.15), noisy_gates=tq.Gate.x)
# initialize a simulator that targets only specific gates on specific labels
sim2 = tq.Simulator()
sim2.add_kraus(bitflip(0.1), noisy_gates=[tq.Gate.y, tq.Gate.s], noisy_labels={1, 2})
# plot the final states
plt.figure(figsize=(10, 3))
tq.visualization.plot_mat(sim0.state(circuit).mat(), ax=plt.subplot("131"))
tq.visualization.plot_mat(sim1.state(circuit).mat(), ax=plt.subplot("132"))
tq.visualization.plot_mat(sim2.state(circuit).mat(), ax=plt.subplot("133"))
```

## Warning: Two Types of Noise Sources¶

This section explains a slightly subtle/advanced topic with respect to the simulator
object, which nonetheless comes up from time to time in regular usage of the simulator. However,
this topic is rather simple to deal with once the basic premise is understood:
*exactly one of the noise sources must be responsible for actually trying to simulate
the gates*.

Indeed, if no noise sources tried to simulate the gates, it would not really be a
simulation of the circuit, and if more than one of the noise sources tried to simulate
the circuit, the end result would be quite incorrect. Therefore, every noise source
defines a flag that specifies whether it is the kind of noise source that tries to
*actually implement the gates* 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.

*If no noise sources in a simulator have this flag as true* then an “ideal noise
source” is automatically *prepended* to the noise list. This “noise source” simulates
the gates it sees and adds no noise.

If more than one noise source is added whose flag is true, a warning is raised, as in the following example.

```
bad_sim = tq.Simulator()
bad_sim.add_overrotation(0.01, noisy_gates=tq.Gate.x)
bad_sim.add_overrotation(0.02, noisy_gates=tq.Gate.y)
```

Out:

```
Warning: You have added multiple propagators to your simulator that are not noise-only. A noise-only simulator adds noise to the state in each cycle (and possibly depends on the contents of the cycle) but does not actually try to implement the cycle. Therefore your simulator is likely not going to work because it will apply each cycle to the state multiple times.
(/home/user/jenkins/workspace/release trueq (centos)/trueq/simulation/simulator.py:207)
<trueq.simulation.simulator.Simulator object at 0x7efce6387c50>
```

In the example above, it happens to be easy to get around the problem by noting that we can use Kraus maps with one unitary operator as overrotation noise

```
good_sim = tq.Simulator()
good_sim.add_kraus([(tq.Gate.x ** 0.01).mat], noisy_gates=tq.Gate.x)
good_sim.add_kraus([(tq.Gate.y ** 0.02).mat], noisy_gates=tq.Gate.y)
```

Out:

```
<trueq.simulation.simulator.Simulator object at 0x7efce53ab6d8>
```

**Total running time of the script:** ( 0 minutes 0.184 seconds)