.. _explanation: ################################## 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 :func:`.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 :func:`.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, :func:`.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 `__ :class:`~quimb.tensor.CircuitMPS` simulator - Quimb's `lazy `__ :class:`~quimb.tensor.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 :math:`\chi`. Given a general circuit on :math:`L` qubits, a matrix-product state needs at most a bond dimension of :math:`\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 :math:`\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, :class:`.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 :mod:`~qiskit_addon_aqc_tensor.simulation.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 :class:`.QuimbSimulator` object; in this case, the :mod:`~qiskit_addon_aqc_tensor.simulation.explicit_gradient` module will be used. It is only recommended to use explicit gradients with Quimb's eager :class:`~quimb.tensor.CircuitMPS` simulator, not the lazy :class:`~quimb.tensor.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 :mod:`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 ---------- .. [1] `arXiv:2301.08609v6 `__.