# 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})


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

# Add a depolarizing noise source at a rate of 0.8% per acted-on qubit per cycle

# Note that noisy simulators can be constructed as one-liners


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()

# initialize a simulator that targets only a specific gate

# initialize a simulator that targets only specific gates on specific labels
sim2 = tq.Simulator()

# 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()


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()

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