Explanatory Material on AQC-Tensor¶
Background¶
This Qiskit addon allows one to compress the initial portion of a circuit using Approximate Quantum Compilation (AQC) with tensor networks, following the method introduced by Ref. [1].
In the following, we will assume that the input circuit has already been truncated such that its entire depth can be simulated by a tensor network with a reasonable bond dimension. In order for AQC to be useful on quantum hardware, one must use the resulting circuit as a state preparation procedure and then perform subsequent quantum computation on that state in a way that is beyond the reach of a tensor network simulator alone.
In essence, AQC-Tensor requires three things as input:
A description of the target state in the form of a tensor network. This may be generated by simulating a circuit on a tensor network simulator (this is what Ref. [1] did), or it could be generated in some other way, e.g., by performing time-dependent variational principle (TDVP) time evolution directly on a matrix-product state.
A parametrized ansatz circuit, ideally with hardware-efficient connectivity, such that it will have a reasonable depth on the target hardware.
Initial parameters to plug into the ansatz circuit, such that the resulting state is already a good approximation to the target state.
The technique is to then use an iterative optimization method to make the good state as close to the target state as possible.
[The third item is not required in principle, but it really helps to give the optimizer a sensible starting point; otherwise, it is much more likely to get stuck in a local (but not global) minimum.]
Ansatz generation (motivation)¶
As mentioned in the previous section, three closely related things are
needed as input to AQC-Tensor. Rather than code all three
individually, this package provides a
generate_ansatz_from_circuit()
function that can generate (2)
and (3) from a circuit. In the case of Trotter evolution (and likely
other circuit classes of interest), one can use the same
circuit-generation function to generate (1) as well as the input to
generate_ansatz_from_circuit()
, thus enabling the generation of
all three inputs from a single function. The tutorial is a good
demonstration of this re-use of a single circuit-generation function.
Specifically, generate_ansatz_from_circuit()
will take an input circuit and generate
the ansatz circuit (2) and initial parameters
(3) that follow the two-qubit connectivity of
the input circuit. However, the returned circuit will be parametrized, based on generalized one- and two-qubit
rotations. The initial parameters returned by the function, when plugged
into the ansatz, will result in a circuit that is exactly equivalent to
the circuit passed as input, up to a global phase.
Given the ansatz generation function, the user must provide only two things in order to use AQC-Tensor:
The target state, i.e., a great description of the desired state; and,
A circuit with the desired connectivity of the ansatz that prepares a good approximation to the target state.
The optimization procedure then takes the good ansatz parameters and brings them as close as possible to the great description (the target state).
In the case of a Trotter circuit, the same function can actually provide
both things. For instance, the spinhalf_trotter_circuit
function
could be called with num_trotter_steps=50
to produce the target
state, then again with num_trotter_steps=5
to produce the ansatz
structure to be passed to generate_ansatz_from_circuit
. (Note that
the evolution_time
is fixed, such that the time step dt = evolution_time / num_trotter_steps
.
the time per single Trotter step as
dt = evolution_time / num_trotter_steps
, so these two function calls
would leave the total evolution_time
fixed, while changing the time
per Trotter step, dt
.) So, in order for a user to apply AQC-Tensor to a new
physical model of Trotter circuit, they would only need to generalize a
single function with the details of that model.
The ansatz used by
generate_ansatz_from_circuit
uses 9 parameters per
two-qubit block, which is the theoretical minimum (see, e.g., Fig. 2 of
https://arxiv.org/abs/quant-ph/0308033).
The automatic ansatz is based
on the KAK
decomposition,
which parametrizes any two-qubit gate in terms of three parameters, up
to single-qubit rotations. The single-qubit rotation are then decomposed as ZXZ, each of which has three parameters.
So, each two-qubit block of the original
circuit will result in 3 parameters for the two-qubit rotation, plus 3
parameters for an outgoing single-qubit rotation on each of the two
qubits, for a total of 9 parameters per block.
The ansatz is completed by adding a layer of single-qubit rotations to
each active qubit at the start of the circuit.
Tensor-network simulation¶
The simplest tensor network is a matrix-product state (MPS).
Currently, AQC-Tensor supports the following tensor-network simulators:
Qiskit Aer’s MPS simulator
Quimb’s eager
CircuitMPS
simulatorQuimb’s lazy
Circuit
simulator (may work only on small circuits so far; we’re working to fix this soon with more clever contractions)
The most important parameter of a tensor network is its maximum bond dimension, which limits how much entanglement it can represent (and thus to what depth a given circuit can be faithfully simulated). The bond dimension is often represented by the Greek letter \(\chi\).
Given a general circuit on \(L\) qubits, a matrix-product state needs at most a bond dimension of \(\chi_\mathrm{exact} = 2^{\lfloor L/2 \rfloor}\) in order to be able to simulate it to any depth. Of course, general circuits on 100+ qubits cannot be classically simulated, so it will be intractable to set the bond dimension this high for those circuits.
For this reason, if you are attempting to experiment with AQC-Tensor on a toy problem with few qubits, it is important to ensure that \(\chi < 2^{\lfloor L/2 \rfloor}\). Otherwise, any circuit can be simulated to any depth, and there is no point in performing AQC.
Objective function¶
Currently, this addon provides one very simple objective function, OneMinusFidelity
, which is equivalent to Eq. (7) in Ref. [1]. When an object of this class is called with an array of parameters, it will return both the value and the gradient of that objective function at that point in parameter space.
Gradient¶
This package provides a few different methods for calculating the gradient.
The Aer backend always uses the explicit gradient code in the explicit_gradient
module.
The Quimb backend will typically be used with an automatic differentiation backend; the user is to select a backend from among those supported by Quimb. Alternatively, one can instead pass "explicit"
as the autodiff_backend
when instantiating the QuimbSimulator
object; in this case, the explicit_gradient
module will be used. It is only recommended to use explicit gradients with Quimb’s eager CircuitMPS
simulator, not the lazy Circuit
simulator.
Regardless of which backend is chosen, the gradient code can understand linear parameter expressions (ParameterExpression objects). This support is essential, as linear expressions are returned by the ansatz generation code.
Optimization method¶
Users are encouraged to use scipy.optimize
to perform the optimization.
L-BFGS is the optimizer demonstrated in the tutorial notebook. It works well in practice because it uses the function value and its gradient to approximate the Hessian. It works well when given an initial point and seems to work particularly well in the case of Trotter circuits. However, it might early terminate if it starts in a barren plateau. In that case, performing a handful of steps using the ADAM optimizer first may help.