# # Copyright 2022 Keysight Technologies Inc. # """ Simulator: Introduction ======================= """ #%% # |True-Q| offers a highly versatile built-in simulator that allows # users to simulate arbitrary circuits with a wide variety of noise models and options # for noise model customization. # # In this example, we go through the basic features of the |True-Q| simulator. # We begin by instantiating a circuit to play with: import matplotlib.pyplot as plt import numpy as np import trueq as tq import trueq.simulation as tqs circuit = tq.Circuit([{0: tq.Gate.x, 1: tq.Gate.y}, {(0, 1): tq.Gate.cz}]) circuit.measure_all() circuit.draw() #%% # Simulator Basics # ---------------- #%% # In its simplest configuration, a (noiseless) simulator can be instantiated from # |True-Q|'s :py:class:~trueq.Simulator class as follows: sim = tq.Simulator() #%% # We can use it to simulate the final state of the circuit, with all initial states # prepared as :math:|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. sim.state(circuit).mat() #%% # If we want to force the output to be a density matrix, we can use: sim.state(circuit).upgrade().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.plot_mat(sim.operator(circuit).mat()) #%% # Finally, we can use a simulator to populate the :py:attr:~trueq.Circuit.results # attribute of the circuit. Currently, this attribute is just an empty dictionary: circuit.results #%% # But after calling the :py:meth:~trueq.Simulator.run method, it has # :py:attr:~trueq.Circuit.results that are randomly sampled bitstrings from the final # state of a state simulation: sim.run(circuit, n_shots=100) circuit.results #%% # 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 # :py:attr:~trueq.Circuit.results unless # otherwise specified. Another option is to call the simulator's # :py:meth:~trueq.Simulator.sample method which returns a :py:class:~trueq.Results # object directly without affecting the circuit's :py:attr:~trueq.Circuit.results # attribute: results = sim.sample(circuit, n_shots=100) results #%% # Adding Noise Sources # -------------------- #%% # 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: # 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) #%% # .. note:: # # Note that simulation is cycle-based. # Each noise source is called to add noise to the quantum state (or to the # superoperator if :py:meth:~trueq.Simulator.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: tq.plot_mat(sim.state(circuit).mat()) #%% # After calling the :py:meth:~trueq.Simulator.run method (overwriting the # :py:attr:~trueq.Circuit.results from above), we end up with noisy outcomes: 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 #%% # Built-in Noise Sources # ---------------------- # # The simulator supports a series of common noise models, such as: # # #. :py:meth:~trueq.Simulator.add_depolarizing\: This method adds depolarizing noise # to every location where a gate acts. It takes the depolarizing parameter p as # an argument. # #. :py:meth:~trueq.Simulator.add_stochastic_pauli\: This method adds stochastic # Pauli noise to every location where a gate acts. It takes as parameters the # probabilities of each Pauli error for the noise channel, px, py and # pz. # #. :py:meth:~trueq.Simulator.add_overrotation\: This method adds an overrotation to # every single- and/or two-qubit gate. It takes as parameters the single_sys # angle which specifies how much the single-qudit gates are under/overrotated, and # the multi_sys parameter, which specifies how much the two-qudit gates are # under/overrotated. # #. :py:meth:~trueq.Simulator.add_relaxation\: This method adds amplitude damping # (:math:T1\) and/or dephasing (:math:T2\) to every location where a gate acts. # This method takes as arguments the noise parameters t1 and t2 and the # amount of time t_single (t_multi) a single- (multi-)qubit gate takes. # #. :py:meth:~trueq.Simulator.add_kraus\: This method allows for the creation of a # custom noise source specified through Kraus operators and takes as input a list # of operators specifed in matrix-form. # # For a full list of built-in noise sources, check out the :py:class:~trueq.Simulator # API reference. #%% # 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.plot_mat(sim0.state(circuit).mat(), ax=plt.subplot(131)) tq.plot_mat(sim1.state(circuit).mat(), ax=plt.subplot(132)) tq.plot_mat(sim2.state(circuit).mat(), ax=plt.subplot(133))