Skip to main contentIBM Quantum Documentation
You are viewing the API reference for an old version of Qiskit SDK. Switch to latest version

Approximate Quantum Compiler


Implementation of Approximate Quantum Compiler as described in the paper [1].


The main public interface of this module is reached by passing unitary_synthesis_method='aqc' to transpile. This will swap the synthesis method to use AQCSynthesisPlugin. The individual classes are:

AQC([optimizer, seed])A generic implementation of the Approximate Quantum Compiler.
AQCSynthesisPlugin()An AQC-based Qiskit unitary synthesis plugin.
ApproximateCircuit(num_qubits[, name])A base class that represents an approximate circuit.
ApproximatingObjective()A base class for an optimization problem definition.
CNOTUnitCircuit(num_qubits, cnots[, tol, name])A class that represents an approximate circuit based on CNOT unit blocks.
CNOTUnitObjective(num_qubits, cnots)A base class for a problem definition based on CNOT unit.
DefaultCNOTUnitObjective(num_qubits, cnots)A naive implementation of the objective function based on CNOT units.
FastCNOTUnitObjective(num_qubits, cnots)Implementation of objective function and gradient calculator, which is similar to DefaultCNOTUnitObjective but several times faster.

Mathematical Detail

We are interested in compiling a quantum circuit, which we formalize as finding the best circuit representation in terms of an ordered gate sequence of a target unitary matrix UU(d)U\in U(d), with some additional hardware constraints. In particular, we look at representations that could be constrained in terms of hardware connectivity, as well as gate depth, and we choose a gate basis in terms of CNOT and rotation gates. We recall that the combination of CNOT and rotation gates is universal in SU(d)SU(d) and therefore it does not limit compilation.

To properly define what we mean by best circuit representation, we define the metric as the Frobenius norm between the unitary matrix of the compiled circuit VV and the target unitary matrix UU, i.e., VUF\|V - U\|_{\mathrm{F}}. This choice is motivated by mathematical programming considerations, and it is related to other formulations that appear in the literature. Let’s take a look at the problem in more details.

Let nn be the number of qubits and d=2nd=2^n. Given a CNOT structure ctct and a vector of rotation angles θ\theta, the parametric circuit forms a matrix Vct(θ)SU(d)Vct(\theta)\in SU(d). If we are given a target circuit forming a matrix USU(d)U\in SU(d), then we would like to compute

argmaxθ1dVct(θ),Uargmax_{\theta}\frac{1}{d}|\langle Vct(\theta),U\rangle|

where the inner product is the Frobenius inner product. Note that V,Ud|\langle V,U\rangle|\leq d for all unitaries UU and VV, so the objective has range in [0,1][0,1].

Our strategy is to maximize

1dVct(θ),U\frac{1}{d}\Re \langle Vct(\theta),U\rangle

using its gradient. We will now discuss the specifics by going through an example.

While the range of VctVct is a subset of SU(d)SU(d) by construction, the target circuit may form a general unitary matrix. However, for any UU(d)U\in U(d),

exp(2πik/d)det(U)1/dUSU(d) for all k{0,,d1}.\frac{\exp(2\pi i k/d)}{\det(U)^{1/d}}U\in SU(d)\text{ for all }k\in\{0,\ldots,d-1\}.

Thus, we should normalize the target circuit by its global phase and then approximately compile the normalized circuit. We can add the global phase back in afterwards.

In the algorithm let UU' denote the un-normalized target matrix and UU the normalized target matrix. Now that we have UU, we give the gradient function to the Nesterov’s method optimizer and compute θ\theta.

To add the global phase back in, we can form the control circuit as

Vct(θ),UVct(θ),UVct(θ).\frac{\langle Vct(\theta),U'\rangle}{|\langle Vct(\theta),U'\rangle|}Vct(\theta).

Note that while we optimized using Nesterov’s method in the paper, this was for its convergence guarantees, not its speed in practice. It is much faster to use L-BFGS which is used as a default optimizer in this implementation.

A basic usage of the AQC algorithm should consist of the following steps:

# Define a target circuit as a unitary matrix
unitary = ...
# Define a number of qubits for the algorithm, at least 3 qubits
num_qubits = int(round(np.log2(unitary.shape[0])))
# Choose a layout of the CNOT structure for the approximate circuit, e.g. ``spin`` for
# a linear layout.
layout = options.get("layout") or "spin"
# Choose a connectivity type, e.g. ``full`` for full connectivity between qubits.
connectivity = options.get("connectivity") or "full"
# Define a targeted depth of the approximate circuit in the number of CNOT units.
depth = int(options.get("depth") or 0)
# Generate a network made of CNOT units
cnots = make_cnot_network(
# Create an optimizer to be used by AQC
optimizer = L_BFGS_B()
# Create an instance
aqc = AQC(optimizer)
# Create a template circuit that will approximate our target circuit
approximate_circuit = CNOTUnitCircuit(num_qubits=num_qubits, cnots=cnots)
# Create an objective that defines our optimization problem
approximating_objective = DefaultCNOTUnitObjective(num_qubits=num_qubits, cnots=cnots)
# Run optimization process to compile the unitary

Now approximate_circuit is a circuit that approximates the target unitary to a certain degree and can be used instead of the original matrix.

This uses a helper function, make_cnot_network.


qiskit.transpiler.synthesis.aqc.make_cnot_network(num_qubits, network_layout='spin', connectivity_type='full', depth=0)

GitHub(opens in a new tab)

Generates a network consisting of building blocks each containing a CNOT gate and possibly some single-qubit ones. This network models a quantum operator in question. Note, each building block has 2 input and outputs corresponding to a pair of qubits. What we actually return here is a chain of indices of qubit pairs shared by every building block in a row.


  • num_qubits (int(opens in a new tab)) – number of qubits.
  • network_layout (str(opens in a new tab)) – type of network geometry, {"sequ", "spin", "cart", "cyclic_spin", "cyclic_line"}.
  • connectivity_type (str(opens in a new tab)) – type of inter-qubit connectivity, {"full", "line", "star"}.
  • depth (int(opens in a new tab)) – depth of the CNOT-network, i.e. the number of layers, where each layer consists of a single CNOT-block; default value will be selected, if L <= 0.


A matrix of size (2, N) matrix that defines layers in cnot-network, where N

is either equal L, or defined by a concrete type of the network.


ValueError(opens in a new tab) – if unsupported type of CNOT-network layout or number of qubits or combination of parameters are passed.

Return type

ndarray(opens in a new tab)

One can take advantage of accelerated version of objective function. It implements the same mathematical algorithm as the default one DefaultCNOTUnitObjective but runs several times faster. Instantiation of accelerated objective function class is similar to the default case:

# Create an objective that defines our optimization problem approximating_objective = FastCNOTUnitObjective(num_qubits=num_qubits, cnots=cnots)

The rest of the code in the above example does not change.


[1]: Liam Madden, Andrea Simonetto, Best Approximate Quantum Compiling Problems.

arXiv:2106.05649(opens in a new tab)

Was this page helpful?
Report a bug or request content on GitHub.