--- jupytext: text_representation: extension: .md format_name: myst format_version: 0.13 jupytext_version: 1.11.1 kernelspec: display_name: Python 3 language: python name: python3 --- # What happens when I use PT? ```{admonition} Warning: Pauli Twirling in Mitiq is still under construction. This users guide will change in the future after some utility functions are introduced. ``` The workflow of Pauli Twirling (PT) in Mitiq is represented in the figure below. ```{figure} ../img/pt_workflow.svg --- width: 700px name: pt-workflow-overview-2 --- Workflow of the PT technique in Mitiq, detailed in the [What happens when I use PT?](pt-4-low-level.md) section. ``` - The user provides a `QPROGRAM`, (i.e. a quantum circuit defined via any of the supported [frontends](frontends-backends.md)). - Mitiq modifies the input circuit with the insertion of PT gates on noisy operations. - The modified circuit is executed via a user-defined [Executor](executors.md). - The error mitigated expectation value is returned to the user. With respect to the workflows of other error-mitigation techniques (e.g. [ZNE](zne-4-low-level.md) or [PEC](pec-4-low-level.md)), PT involves the generation of a _single_ circuit with random modifications, and subsequently averages over many executions. For this reason, there is no need for a complex final inference step, which is necessary for other techniques, and so this average is instead trivial for PT. ```{note} When setting the `num_trials` option to a value larger than one, multiple circuits are actually generated by Mitiq and the associated results are averaged to obtain the final expectation value. This more general case is not shown in the figure since it can be considered as an average of independent single-circuit workflows. ``` As shown in [How do I use PT?](pt-1-intro.md), the function {func}`.pauli_twirl_circuit()` applies PT behind the scenes and returns the different versions of Pauli Twirled circuits. In the next sections instead, we show how one can apply PT at a lower level, i.e., by: - Twirling CZ and CNOT gates in the circuit; - Executing the modified circuit (still under construction). - Estimate expectation value by averaging over randomized twirling circuits ## Twirling CZ and CNOT gates in the circuit To twirl about particular gates, we need the Pauli group for those gates. These groups are stored as lookup tables, in {attr}`mitiq.pt.pt.CNOT_twirling_gates` and {attr}`mitiq.pt.pt.CZ_twirling_gates`, so that we can randomly select a tuple from the group. Now we're ready to twirl our gates. First let's define our circuit: ```{code-cell} ipython3 from cirq import LineQubit, Circuit, CZ, CNOT a, b, c, d = LineQubit.range(4) circuit = Circuit( CNOT.on(a, b), CZ.on(b, c), CNOT.on(c, d), ) print(circuit) ``` Now, we can see what happens when we apply the PT functions, through {func}`.twirl_CNOT_gates()` and the subsequent {func}`.twirl_CZ_gates()` ```{code-cell} ipython3 from mitiq import pt circuit_to_twirl = circuit.copy() CNOT_twirled_circuits = pt.twirl_CNOT_gates(circuit_to_twirl, num_circuits=10) twirled_circuits = [ pt.twirl_CZ_gates(c, num_circuits=1)[0] for c in CNOT_twirled_circuits ] print("Twirling just the CNOT gates: \n", CNOT_twirled_circuits[0], "\n") print("Twirling both CNOT and CZ gates: \n" ,twirled_circuits[0]) ``` We see that we return lists of the randomly twirled circuits, and so we must take a simple average over their expectation values. ## Executing the modified circuits ```{admonition} Warning: Pauli Twirling in Mitiq is still under construction. Some lines in the code blocks below are commented out as intended behavior is currently a WIP. ``` Now that we have our twirled circuits, let's simulate some noise and execute those circuits, using the {class}`mitiq.Executor` to collect the results. ```{code-cell} ipython3 from cirq import DensityMatrixSimulator, amplitude_damp from mitiq import Executor def execute(circuit, noise_level=0.003): """Returns Tr[ρ |00..⟩⟨00..|] where ρ is the state prepared by the circuit executed with depolarizing noise. """ noisy_circuit = circuit.with_noise(amplitude_damp(noise_level)) rho = DensityMatrixSimulator().simulate(noisy_circuit).final_density_matrix return rho[0, 0].real executor = Executor(execute) # expvals = executor.evaluate(twirled_circuits, None) ``` ## Estimate expectation value by averaging over randomized twirling circuits Pauli Twirling doesn't require running the circuit at different noise levels or with different noise models. It applies a randomized sequence of Pauli operations within the same quantum circuit and averages the results to reduce the effect of the noise. ```{code-cell} ipython3 # import numpy as np # from typing import cast # average = cast(float, np.average(expvals)) # print(average) ``` Keep in mind, ths code is for illustration and that the noise level, type of noise (here amplitude damping), and the observable need to be adapted to the specific experiment. If executed on a noiseless backend, a given `circuit_with_pt` and `circuit` are equivalent. On a real backend, they have a different sensitivity to noise. The core idea of the PT technique is that, `circuits_with_pt` (hopefully) tailors the noise into stochastic Pauli channels, such that a simple average over results will return a mitigated result. As a final remark, we stress that the low-level procedure that we have shown is exactly what {func}`.pauli_twirl_circuit()` does behind the scenes. Let's verify this fact: ```{code-cell} ipython3 # np.isclose( # pt.pauli_twirl_circuit(circuit, executor), # average, # ) ```