# Example: Introduction to the Simulator

True-Q™ offers a highly versatile Simulator that allows users to simulate arbitrary circuits with a wide variety of noise models and options for noise model customization. In particular, the simulator 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.

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

[2]:

import numpy as np
import trueq as tq

circuit = tq.Circuit([{0: tq.Gate.x, 1: tq.Gate.y}, {(0, 1): tq.Gate.cz}])
circuit.measure_all()
circuit.draw()

[2]:


## Simulator Basics

In its simplest configuration, a (noiseless) simulator can be instantiated from True-Q™’s Simulator class as follows:

[3]:

sim = tq.Simulator()


We can use it to simulate the final state of the circuit, with all initial states prepared as $$|0\rangle$$ by default. Since the simulator is noiseless, the output state is a pure state; a noisy simulator will generally return a density matrix instead.

[4]:

sim.state(circuit).mat()

[4]:

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


If we want to force the output to be a density matrix, we can use:

[5]:

sim.state(circuit).upgrade().mat()

[5]:

array([[0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j],
[0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j],
[0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j],
[0.-0.j, 0.-0.j, 0.-0.j, 1.+0.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).

[6]:

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


Finally, we can use a simulator to populate the results attribute of the circuit. Currently, this attribute is just an empty dictionary:

[7]:

circuit.results

[7]:

Results({}, dim=None)


But after calling the run() method, it has results that are randomly sampled bitstrings from the final state of a state simulation:

[8]:

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

[8]:

Results({'11': 100})


In this case, all 100 shots end in the 11 state because the final state is a computational eigenstate.

When re-running the simulation, it will overwrite the existing results unless otherwise specified. Another option is to call the simulator’s sample() method which returns a Results object directly without affecting the circuit’s results attribute:

[9]:

results = sim.sample(circuit, n_shots=100)
results

[9]:

Results({'11': 100})


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

[10]:

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

[10]:

Results({'11': 1.0})


We construct a noisy simulator by appending noise sources to a noiseless simulator. The example below demonstrates this using two of True-Q™'s built-in noise sources:

[11]:

# 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


Note

Note that 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 in which noise sources are applied is dictated by the order in which they were added to the simulator.

Now, since the simulator applies a depolarizing noise source, we get a density operator rather than a pure state:

[12]:

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


After calling the run() method (overwriting the results from above), we end up with noisy outcomes:

[13]:

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

[13]:

Results({'01': 2, '11': 98})


For more information on both built-in and custom noise sources, check out Example: Advanced Simulator Usage next.