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:

  1. 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.

  2. A parametrized ansatz circuit, ideally with hardware-efficient connectivity, such that it will have a reasonable depth on the target hardware.

  3. 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:

  1. The target state, i.e., a great description of the desired state; and,

  2. 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 simulator

  • Quimb’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.

References