Approximate Quantum Compiler
qiskit.transpiler.synthesis.aqc
Implementation of Approximate Quantum Compiler as described in the paper [1].
Interface
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 , 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 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 and the target unitary matrix , i.e., . 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 be the number of qubits and . Given a CNOT structure and a vector of rotation angles , the parametric circuit forms a matrix . If we are given a target circuit forming a matrix , then we would like to compute
where the inner product is the Frobenius inner product. Note that for all unitaries and , so the objective has range in .
Our strategy is to maximize
using its gradient. We will now discuss the specifics by going through an example.
While the range of is a subset of by construction, the target circuit may form a general unitary matrix. However, for any ,
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 denote the un-normalized target matrix and the normalized target matrix. Now that we have , we give the gradient function to the Nesterov’s method optimizer and compute .
To add the global phase back in, we can form the control circuit as
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(
num_qubits=num_qubits,
network_layout=layout,
connectivity_type=connectivity,
depth=depth
)
# 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
aqc.compile_unitary(
target_matrix=unitary,
approximate_circuit=approximate_circuit,
approximating_objective=approximating_objective
)
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
.
make_cnot_network
qiskit.transpiler.synthesis.aqc.make_cnot_network(num_qubits, network_layout='spin', connectivity_type='full', depth=0)
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.
Parameters
- num_qubits (int) – number of qubits.
- network_layout (str) – type of network geometry,
{"sequ", "spin", "cart", "cyclic_spin", "cyclic_line"}
. - connectivity_type (str) – type of inter-qubit connectivity,
{"full", "line", "star"}
. - depth (int) – 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
.
Returns
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.
Raises
ValueError – if unsupported type of CNOT-network layout or number of qubits or combination of parameters are passed.
Return type
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.
References
[1]: Liam Madden, Andrea Simonetto, Best Approximate Quantum Compiling Problems.