# # Copyright 2021 Quantum Benchmark Inc. # """ Simulating Leakage ================== """ #%% # The simulator is capable of adding extra energy levels to allow noise models that # include leakage. For example, if a circuit acts on qubits, so that gates are either # 2-by-2 for single qubit gates and 4-by-4 for two-qubit gates, then one can direct the # simulator to add an extra energy level to each qubit, so that each subsystem is # simulated with 3 dimensions. Noise models can interact with the extra level(s). # # Extra leakage dimensions are added by the simulator whenever some noise source # explicitly asks for a specific dimension. Certain noise sources, such as overrotation, # work no matter how many extra levels are included. import trueq as tq import trueq.simulation as tqs import numpy as np import scipy.linalg as la #%% # Kraus Noise # ----------- # The most versatile method of adding leakage is through Kraus operator noise with the # :py:meth:`~trueq.Simulator.add_kraus` method. Recall that Kraus noise is specified as # a set of operators :math:`\{K_i\}_{i=1}^m` and acts on a given density matrix as # :math:`\rho\mapsto\sum_{i=1}^m K_i\rho K_i^\dagger`. To add leakage to a model, we # construct Kraus operators with a dimension strictly bigger than the dimension of our # gates, and add terms that cause population transfer into these new levels, either # stochastically or coherently. The increased dimension must be specified using the # ``dim`` argument of the :py:meth:`~trueq.Simulator.add_kraus` method. # # In the following toy example, we add Kraus operators that add no noise with 95% # probability, but apply a random (but fixed) qutrit unitary with probability 5%. # This induces a stochastic population transfer into the leakage level. p = 0.05 sim = tq.Simulator() sim.add_kraus( [np.sqrt(1 - p) * np.eye(3), np.sqrt(p) * tq.math.random_unitary(3)], dim=3 ) circuit = tq.Circuit([{0: tq.Gate.x}]) tq.visualization.plot_mat(sim.state(circuit).mat()) #%% # In the next example, we add a coherent leakage term to a CNOT gate with the # transitions :math:`|00\rangle\leftrightarrow|20\rangle` and # :math:`|10\rangle\leftrightarrow|12\rangle` that occur at different rates. sim = tq.Simulator() ham = np.zeros((9, 9), dtype=np.complex128) ham[([0, 6], [6, 0])] = 0.5 ham[([3, 5], [5, 3])] = 1 ops = la.expm(-1j * 0.3 * ham) sim.add_kraus([ops], match=tqs.GateMatch(tq.Gate.cnot), dim=3) circuit = tq.Circuit([{0: tq.Gate.h}, {(0, 1): tq.Gate.cx}]) tq.visualization.plot_mat(sim.state(circuit).mat(), xlabels=3, ylabels=3) #%% # Measurement Classification # -------------------------- # In the first example, we add asymmetric classification error to all three levels, # where a :math:`|0\rangle` is reported as a '0' 99% of the time, a :math:`|1\rangle` is # reported as a '1' 92% of the time, and a :math:`|2\rangle` is reported as a '2' 80% of # the time. Off-diagonals are the probabilities of misclassification into specific # outcomes corresponding to the row they are located in. We do this by using the # :py:meth:`~trueq.Simulator.add_readout_error` simulator method that supports confusion # matrix inputs. # define a confusion matrix for a qutrit confusion = [[0.99, 0.03, 0.02], [0.005, 0.92, 0.18], [0.005, 0.05, 0.8]] # simulate the effects on all three basis states circuit = tq.Circuit([{0: tq.Prep()}, {0: tq.Meas()}]) for idx, prep_state in enumerate(np.eye(3)): sim = tq.Simulator().add_readout_error(errors={0: confusion}).add_prep(prep_state) sim.run(circuit, n_shots=np.inf, overwrite=True) print(f"Prepare |{idx}>:", circuit.results) #%% # In the next example, we simulate with a qutrit, but we only classify into bits. This # situation arises, for example, in superconducting qubits when there is leakage into # the third level of the anharmonic oscillator, but readout always reports a '0' or a # '1'. We achieve this by defining a rectangular confusion matrix where # :math:`|2\rangle` is classified as a '0' 70% of the time, and as a '1' 30% of the # time. Additionally, :math:`|0\rangle` and :math:`|1\rangle` also have their own # classification errors, too. confusion = [[0.99, 0.08, 0.7], [0.01, 0.92, 0.3]] # simulate the effects on all three basis states circuit = tq.Circuit([{0: tq.Prep()}, {0: tq.Meas()}]) for idx, prep_state in enumerate(np.eye(3)): sim = tq.Simulator().add_readout_error(errors={0: confusion}).add_prep(prep_state) sim.run(circuit, n_shots=np.inf, overwrite=True) print(f"Prepare |{idx}>:", circuit.results) #%% # We can see the effect that this has on a 1-qubit benchmarking experiment. # Here, we add gate noise that stochastically populates the third level with probability # 1% every time a gate is applied. p = 0.01 perm02 = np.array([[0, 0, 1], [0, 1, 0], [1, 0, 0]]) perm12 = np.array([[1, 0, 0], [0, 0, 1], [0, 1, 0]]) sim.add_kraus( [np.sqrt(1 - p) * np.eye(3), np.sqrt(p / 2) * perm02, np.sqrt(p / 2) * perm12], dim=3, ) circuits = tq.make_srb(0, np.linspace(4, 100, 10).astype(int)) sim.run(circuits) circuits.plot.raw()