#
# Copyright 2021 Quantum Benchmark Inc.
#
"""
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()
#%%
# 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 :math:`|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()
#%%
# 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
#%%
# 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
#%%
# 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(single_sys=0.02)
# Add a depolarizing noise source at a rate of 0.8% per acted-on qubit per cycle
sim.add_depolarizing(p=0.008)
# Note that noisy simulators can be constructed as one-liners
other_sim = tq.Simulator().add_overrotation(single_sys=0.02).add_depolarizing(p=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
#%%
# 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
#%%
# 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
# :py:class:`~trueq.simulation.match.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()
sim0.add_kraus(bitflip(0.05), match=tqs.LabelMatch(0))
sim0.add_kraus(bitflip(0.09), match=tqs.LabelMatch(1))
# initialize a simulator that targets only a specific gate
xmatch = tqs.GateMatch(tq.Gate.x)
sim1 = tq.Simulator().add_kraus(bitflip(0.15), match=xmatch)
# 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])
sim2.add_kraus(bitflip(0.1), match=gate_label_match)
# 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))