Customizing Randomized Compiling with different Compilation Options

Randomized Compiling in True-Q™ can be configured in various ways. For example, the Customizing Randomized Compiling with Cycle Markers example shows how the user can specify which cycles in a circuit are hard cycles and thus subject to the random twirls that the randomly_compile() inserts around those hard cycles. Two other important configuration options include:

1. Randomized Compiling for non-Clifford entangling gates

Randomized Compiling can be used for circuits that contain hard cycles with gates which are not from the Clifford group by specifying the entangler argument.

2. Specifying the action on idle qubits

In systems with large single-qubit errors it might be desirable to not introduce any additional single-qubit gates on what would otherwise be idle qubits. This compilation option can be specified through the Compiler passes: RCCycle and RCLocal.

The following examples show how these configuration options work in practice.

Note

Randomized Compiling produces a new circuit collection after each call, so the output of this example will be different if it’s executed again.

Randomized Compiling for non-Clifford Entangling Gates

The trueq.randomly_compile() function by default assumes that all hard cycles in the input circuit consist purely of Clifford gates. This is because under the standard Pauli twirl, the correction gates for the twirl gate can always be expressed as single-qubit gates which can be compiled into neighboring easy cycles such that the overall circuit depth is maintained [11].

It is possible to twirl a cycle that has non-Clifford gates in two ways. One solution is to customize the set of twirling gates. The other approach, as shown here, is to use True-Q™'s built-in Compiler to convert the cycle into a Clifford-based representation. This can be done either implicitly by specifying the entangler argument of the trueq.randomly_compile() function, or explicitly through defining a custom Compiler. Let’s take a look at both options.

As a concrete example, consider the Quantum Fourier Transform (QFT) circuit, that is commonly expressed in terms of the $$CROT$$ gate (where $$CROT(\phi):=\exp(i \phi |11 \rangle \langle 11|)$$):

import trueq as tq
import trueq.compilation as tqc
from trueq.algorithms import qft


define a QFT circuit on 4 qubits

qft_circuit = qft(range(4))
# display the output
qft_circuit.draw()


In order to produce randomly compiled versions of this circuit, we need to replace these non-Clifford entangling gates with a Clifford gate, such as the $$CX$$ gate. To tell the trueq.randomly_compile() function to use $$CX$$ gates for the compilation, we specify the entangler argument in the function call as follows:

rc_circuits = tq.randomly_compile(qft_circuit, entangler=tq.Gate.cx)

# display the first circuit in this collection:
rc_circuits[0].draw()


Note that this circuit (and all other circuits in this collection) now consists purely of Clifford gates, and the entangling gates are all $$CX$$ gates, while implementing the same logical operation as the original circuit.

When the entangler argument is specified, the trueq.randomly_compile() function will first create a new circuit that only uses the specified entangling gate, and then generate randomly compiled versions of that circuit. This happens implicitly in the background. If desired however, it can also be done explicitly through creating a custom Compiler:

entangler = tq.Gate.cx
# Define a pass for the compiler that specifies which two-qubit gates to use
entangler_pass = tqc.Native2Q([tq.config.GateFactory.from_matrix("CX", entangler.mat)])
# define the compiler
compiler = tqc.Compiler(passes=[entangler_pass, tqc.Merge()])

# compile the QFT circuit
compiled_qft_circuit = compiler.compile(qft_circuit)
# display the resulting circuit
compiled_qft_circuit.draw()


This circuit implements the same operation as the original QFT circuit but all its entangling gates have been replaced by a combination of single-qubit gates and two-qubit $$CX$$ gates.

Note

For more information on the usage of True-Q™'s Compiler take a look at the Compilation Basics and Defining Custom Compilers examples.

Since this compiled circuit consists purely of Clifford gates, we can call the trueq.randomly_compile() function on it without any further arguments:

rc_circuits = tq.randomly_compile(compiled_qft_circuit)
# display a sample circuit from this collection:
rc_circuits[0].draw()


Both methods, i.e. using the entangler argument and compiling the circuit explicitly, yield the same result and allow for the generation of randomly compiled circuits of an input circuit with non-Clifford entangling gates.

Specifying the Action on Idle Qubits

By default, the trueq.randomly_compile() function will insert a single-qubit twirling and a matching compensation gate around every hard cycle in the circuit. There are however applications in which only a subset of the qubits is involved in the algorithm in a given cycle while the other qubits remain idle. If, in addition to that, the computation is performed on a system with non-negligible single-qubit errors, it might be desirable to not apply any extra gates to those qubits which would otherwise be idle.

This can be achieved through performing the compilation of the input circuit explicitly by defining a custom :py:class~trueq.compilation.Compiler and specifying the Randomized Compiling Pass manually. There are two options:

1. RCCycle: this is the default Pass that performs Randomized Compiling on a set of input cycles by inserting single-qubit gates on either side of the cycles.

2. RCLocal: this Pass also performs Randomized Compiling on a set of input cycles, however the single-qubit gates are only added to qubits that the cycles they surround act on; idling qubits are left alone.

Let’s take a look at a concrete example. Consider the follwing circuit of stacked $$CX$$ gates that has two idling qubits in each cycle:

circuit = tq.Circuit(
[
{0: tq.Gate.h, 3: tq.Gate.h},
{(0, 1): tq.Gate.cx},
{(1, 2): tq.Gate.cx},
{(2, 3): tq.Gate.cx},
]
)
circuit.draw()


We can apply our default Randomized Compiling to this circuit through the following Compiler configuration (and the tq.randomly_compile() would produce the same result):

rc_cycle_compiler = tqc.Compiler(
passes=[
# specify a pass to mark hard cycles:
tqc.MarkCycles(),
# specify the main RC pass:
tqc.RCCycle(),
# merge single-qubit cycles together:
tqc.Merge(),
]
)
# generate one randomly compiled circuit using this compiler:
rc_cycle_circuit = rc_cycle_compiler.compile(circuit)
rc_cycle_circuit.draw()


Note that with the RCCycle pass, every hard cycle is now fully surrounded by single-qubit gates.

In contrast, when we apply the RCLocal pass, single-qubit gates are only applied to qubits that the hard cycles act on:

rc_local_compiler = tqc.Compiler(
passes=[
# specify a pass to mark hard cycles:
tqc.MarkCycles(),
# specify the main RC pass:
tqc.RCLocal(),
# merge single-qubit cycles together:
tqc.Merge(),
]
)
# generate one randomly compiled circuit using this compiler:
rc_local_circuit = rc_local_compiler.compile(circuit)
rc_local_circuit.draw()


Total running time of the script: ( 0 minutes 0.668 seconds)

Gallery generated by Sphinx-Gallery