# 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 trueq.simulation as tqs
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()

True-Q formatting will not be loaded without trusting this notebook or rerunning the affected cells. Notebooks can be marked as trusted by clicking "File -> Trust Notebook".
 Circuit Key: No key present in circuit. Marker 0 Compilation tools may only recompile cycles with equal markers. (0): Gate.x Name: Gate.x Aliases: Gate.x Gate.cliff1 Generators: 'X': 180.0 Matrix: 1.00 1.00 (1): Gate.y Name: Gate.y Aliases: Gate.y Gate.cliff2 Generators: 'Y': 180.0 Matrix: -1.00j 1.00j Marker 0 Compilation tools may only recompile cycles with equal markers. (0, 1): Gate.cz Name: Gate.cz Aliases: Gate.cz Likeness: CNOT Generators: 'IZ': 90.0 'ZI': 90.0 'ZZ': -90.0 Matrix: 1.00 1.00 1.00 -1.00 1 Marker 1 Compilation tools may only recompile cycles with equal markers. (0): Meas() Name: Meas() (1): Meas() Name: Meas()

## 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 sim.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': 2, '10': 1, '11': 97})


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.990416129478986e-05, '01': 0.008859008596685893, '10': 0.008859008596685893, '11': 0.9822020786453329})


## Restricting Noise via Conditional Noise Sources¶

Every noise source has a property called match, which can be used to create a conditional noise source. A match can specify specific qubits, operations, cycles, or even more specific properties. Refer to the children of Match for more details.

# 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
xmatch = tqs.GateMatch(tq.Gate.x)

# initialize a simulator that targets only specific gates on specific labels
sim2 = tq.Simulator()
gate_label_match = tqs.LabelMatch((1, 2)) & tqs.GateMatch([tq.Gate.y, tq.Gate.s])