# Built-In Simulator¶

After defining a circuit or generating a circuit collection, the next step is to run it on a simulator or quantum device. For ease of implementation, the True-Qᵀᴹ SDK includes a built-in Simulator that can be configured with custom or pre-defined noise models.

1. Generate circuits

The first step, before using a Simulator to simulate a Circuit or CircuitCollection, is to define the ideal version of the circuit(s) to be run. In this example, we will write a custom Circuit with an X gate acting on the $$0^{th}$$ qubit, followed by a controlled-Z gate on qubits 1 and 2 at the same time as a Y gate acts on the $$0^{th}$$ qubit:

import tq as trueq

circuit = tq.Circuit(tq.Cycle({(0, ):tq.Gate.x}),
tq.Cycle({(0, ): tq.Gate.y, (1, 2):tq.Gate.cz}))

2. Initialize a Simulator

The next step is to initialize a Simulator, and (optionally) add noise. In this example we will not add noise; for a noisy implementation, see the example below. To initialize a simulator, run:

sim=tq.Simulator()

3. Add Noise (Optional)

To simulate a noisy implementation of a Circuit or CircuitCollection, a noise process should be added to the simulator prior to running the circuit(s). The following code adds depolarizing noise to the Simulator initialized in step 2.

sim.add_depolarizing(p = 0.001)


See Noise for details about the True-Qᵀᴹ SDK suite of noise models.

4. Add an initial state (Optional)

By default, every qubit will initialize in the $$|0\rangle$$state. To prepare a different initial state, use add_prep(). An initial state can be specified by a $$d\times d$$ density matrix or a length $$d$$ vector representing a pure state. The following code specifies the initial state of qubit 1.

sim.add_prep({1: [[0.8, 0.1], [0.1, 0.2]]})

5. Run the Simulation

The final step is to run the Circuit or CircuitCollection on the simulator. The following line of code runs the simulation and accordingly populates the results of the circuit with 100 random shots.

sim.run(circuit, n_shots = 100)


## Noise¶

The True-Qᵀᴹ SDK includes several functions for adding noise to a Simulator, including several common noise models and the option to define a custom noise process using Kraus operators. Every function below causes the specified noise model to act at every location where a circuit acts in a simulator.

1. Depolarizing

The add_depolarizing function adds depolarizing noise to a simulator, so that when a circuit is run on the simulator, depolarizing noise acts independently and isotropically on every location in the circuit. The following code initializes a Simulator with depolarizing noise with depolarizing parameter $$p = 0.03$$:

sim = tq.Simulator().add_depolarizing(p = 0.03)

2. Stochastic Pauli

The add_stochastic_pauli function adds a stochastic pauli noise to a simulator. The following code initializes a simulator with stochastic pauli noise, where the probability of an $$X$$, $$Y$$, and $$Z$$ error are $$px = 0.01$$, $$py = 0.02$$, and $$pz = 0.03$$, respectively:

sim = tq.Simulator().add_stochastic_pauli(px = 0.01, py = 0.02, pz = 0.03)

3. Overrotation

The add_overrotation function adds an overrotation error at every location where a single qubit or multi-qubit gate acts in a simulator. The angle of overrotation can be specified separately for single and multi-qubit gates. The following code initializes a Simulator with an overrotation by $$\epsilon=0.01$$ to single qubit gates and $$\epsilon=0.05$$ to multi-qubit gates:

sim = tq.Simulator().add_overrotation(single_sys = 0.01, multi_sys = 0.05)

4. Custom Kraus

The add_kraus allows users to add custom noise to a simulator, specified by Kraus operators. The following code initializes a Simulator with amplitude damping noise constructed from Kraus operators, with a decay probability of $$p = 0.01$$:

import numpy as np

p = 0.01
kraus_ops = [np.sqrt(np.array([[0, p], [0, 0]])), np.sqrt(np.diag([1, 1-p]))]

5. State preparation errors

The add_prep method customizes the preparation of quantum registers. This can be used to set preparations to a specific pure or mixed state, or to add simple bitflip error.

# add 1% bitflip error by default, but 6% for qubits 2 and 3
sim = tq.Simulator().add_prep({None: 0.01, 2: 0.06, 3: 0.06})

# add specific density matrix to qubit 1, but 1% bitflip error by default
sim = tq.Simulator().add_prep({None: 0.01, 1: [[0.8, 0.1], [0.1, 0.2]]})

# prepare all qubits in the |+> state
sim = tq.Simulator().add_prep([1 / np.sqrt(2), 1 / np.sqrt(2)])

6. Measurement errors

The trueq.Simulator.add_readout_error method allows users to add measurement errors to a simulator.

# all qubits get a 0.5% readout error

# all qubits get a 1% readout error, except qubit 1 gets a 3% readout error

# all qubits get a 1% readout error, except qubit 1 gets a 4% readout error, and
# qubit 3 gets an asymmetric readout error of 1% on |0> and 8% on |1>
sim = tq.Simulator().add_readout_error({None: 0.01, 1: 0.04, 3: [0.01, 0.08]})


## Example¶

#
# Noisy simulator example.
# Copyright 2019 Quantum Benchmark Inc.
#

import trueq as tq
import numpy as np

# Define the circuit we are interested in
circuit = tq.Circuit({0: tq.Gate.x, 1: tq.Gate.y}, {(0, 1): tq.Gate.cz})
circuit.measure_all()

# Initialize a noiseless simulator.
sim0 = tq.Simulator()

# Initialize a simulator with depolarizing noise at 8% per qubit.

# Initialize a simulator with pauli noise and overrotation.
sim2 = (
tq.Simulator()
)

# Initialize a simulator with dephasing noise.
kraus_ops = [np.sqrt(0.99) * np.eye(2), np.sqrt(0.01) * np.diag([1, -1])]

# Initialize a simulator with state preparation and measurement errors

# Run circuit on each of the simulators above and print the results.
sim0.run(circuit)
print(f"{'Ideal':>28}:", circuit.results)

sim1.run(circuit, overwrite=True)
print(f"{'Depolarizing':>28}:", circuit.results)

sim2.run(circuit, overwrite=True)
print(f"{'Stochastic w/ overrotation':>28}:", circuit.results)

sim3.run(circuit, overwrite=True)
print(f"{'Dephasing':>28}:", circuit.results)

sim4.run(circuit, overwrite=True)
print(f"{'SPAM Error':>28}:", circuit.results)


This produces the following output:

                       Ideal: Results({'11': 50})
Depolarizing: Results({'01': 3, '10': 1, '11': 46})
Stochastic w/ overrotation: Results({'01': 1, '10': 5, '11': 44})
Dephasing: Results({'11': 50})
SPAM Error: Results({'11': 50})


Note

Every time that a Circuit is run on a Simulator, the results are overwritten.

## Example¶

The built-in simulator has functions which allow users to retrieve the output state after a circuit has run, and to retrieve the operator representation of a circuit. The below example demonstrates these functions.

#
# Noisy simulator example 2.
# Copyright 2019 Quantum Benchmark Inc.
#

import trueq as tq

# Define a circuit that we are interested in
circuit = tq.Circuit({0: tq.Gate.y, 1: tq.Gate.z}, {(0, 1): tq.Gate.cx})

# Compute the unitary matrix of the circuit by using a noiseless simulator
print("Unitary:\n", tq.Simulator().operator(circuit).mat(), "\n")

# Compute the final pure state in the case of no noise
print("Final pure state:\n", tq.Simulator().state(circuit).mat(), "\n")

Unitary:
[[ 0.+0.j  0.+0.j  0.-1.j  0.+0.j]
[ 0.+0.j -0.+0.j  0.+0.j  0.+1.j]
[ 0.+1.j  0.+0.j  0.+0.j  0.+0.j]
[ 0.+0.j -0.-1.j  0.+0.j -0.+0.j]]

Final pure state:
[0.+0.j 0.+0.j 0.+1.j 0.+0.j]