Source code for pennylane.templates.subroutines.fable

# Copyright 2018-2024 Xanadu Quantum Technologies Inc.

# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at

#     http://www.apache.org/licenses/LICENSE-2.0

# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""
This module contains the template for the Fast Approximate BLock Encoding (FABLE) technique.
"""
import warnings

import numpy as np

import pennylane as qml
from pennylane.operation import AnyWires, Operation
from pennylane.templates.state_preparations.mottonen import compute_theta, gray_code
from pennylane.wires import Wires


[docs]class FABLE(Operation): r""" Construct a unitary with the fast approximate block encoding method. The FABLE method allows to simplify block encoding circuits without reducing accuracy, for matrices of specific structure [`arXiv:2205.00081 <https://arxiv.org/abs/2205.00081>`_]. Args: input_matrix (tensor_like): a :math:`(2^n \times 2^n)` matrix to be encoded, where :math:`n` is an integer wires (Iterable[int, str], Wires): the wires the operation acts on. The number of wires can be computed as :math:`(2 \times n + 1)`. tol (float): rotation gates that have an angle value smaller than this tolerance are removed id (str or None): string representing the operation (optional) Raises: ValueError: if the number of wires doesn't fit the dimensions of the matrix **Example** We can define a matrix and a block-encoding circuit as follows: .. code-block:: python input_matrix = np.array([[0.1, 0.2],[0.3, -0.2]]) dev = qml.device('default.qubit', wires=3) @qml.qnode(dev) def example_circuit(): qml.FABLE(input_matrix, wires=range(3), tol=0) return qml.state() We can see that the input matrix has been block encoded in the matrix of the circuit: >>> s = int(np.ceil(np.log2(max(len(input_matrix), len(input_matrix[0]))))) >>> expected = 2**s * qml.matrix(example_circuit)().real[0 : 2**s, 0 : 2**s] >>> print(f"Block-encoded matrix:\n{expected}") Block-encoded matrix: [[0.1 0.2] [0.3 -0.2]] .. note:: FABLE can be implemented for matrices of arbitrary shape and size. When given a :math:`(N \times M)` matrix, the matrix is padded with zeroes until it is of :math:`(N \times N)` dimension, where :math:`N` is equal to :math:`2^n`, and :math:`n` is an integer. It is also assumed that the values of the input matrix are within :math:`[-1, 1]`. Apply a subnormalization factor if needed. """ num_wires = AnyWires """int: Number of wires that the operator acts on.""" num_params = 1 """int: Number of trainable parameters that the operator depends on.""" grad_method = None """Gradient computation method.""" def __init__(self, input_matrix, wires, tol=0, id=None): wires = Wires(wires) if not qml.math.is_abstract(input_matrix): if qml.math.any(qml.math.iscomplex(input_matrix)): raise ValueError("Support for imaginary values has not been implemented.") alpha = qml.math.linalg.norm(qml.math.ravel(input_matrix), np.inf) if alpha > 1: raise ValueError( "The subnormalization factor should be lower than 1." + "Ensure that the values of the input matrix are within [-1, 1]." ) else: if tol != 0: raise ValueError( "JIT is not supported for tolerance values greater than 0. Set tol = 0 to run." ) row, col = qml.math.shape(input_matrix) if row != col: warnings.warn( f"The input matrix should be of shape NxN, got {input_matrix.shape}." + "Zeroes were padded automatically." ) dimension = max(row, col) input_matrix = qml.math.pad(input_matrix, ((0, dimension - row), (0, dimension - col))) row, col = qml.math.shape(input_matrix) n = int(qml.math.ceil(qml.math.log2(col))) if n == 0: ### For edge case where someone puts a 1x1 array. n = 1 if col < 2**n: input_matrix = qml.math.pad(input_matrix, ((0, 2**n - col), (0, 2**n - col))) col = 2**n warnings.warn( "The input matrix should be of shape NxN, where N is a power of 2." + f"Zeroes were padded automatically. Input is now of shape {input_matrix.shape}." ) if len(wires) != 2 * n + 1: raise ValueError(f"Number of wires is incorrect, expected {2*n+1} but got {len(wires)}") self._hyperparameters = {"tol": tol} super().__init__(input_matrix, wires=wires, id=id)
[docs] @staticmethod def compute_decomposition(input_matrix, wires, tol=0): # pylint:disable=arguments-differ r"""Sequence of gates that represents the efficient circuit produced by the FABLE technique Args: input_matrix (tensor_like): an :math:`(N \times N)` matrix to be encoded wires (Any or Iterable[Any]): wires that the operator acts on tol (float): rotation gates that have an angle value smaller than this tolerance are removed Returns: list[.Operator]: list of gates for efficient circuit """ op_list = [] alphas = qml.math.arccos(input_matrix).flatten() thetas = compute_theta(alphas) ancilla = [wires[0]] wires_i = wires[1 : 1 + len(wires) // 2][::-1] wires_j = wires[1 + len(wires) // 2 : len(wires)][::-1] code = gray_code((2 * qml.math.log2(len(input_matrix)))) n_selections = len(code) control_wires = [ int(qml.math.log2(int(code[i], 2) ^ int(code[(i + 1) % n_selections], 2))) for i in range(n_selections) ] wire_map = dict(enumerate(wires_j + wires_i)) for w in wires_i: op_list.append(qml.Hadamard(w)) nots = {} for theta, control_index in zip(thetas, control_wires): if qml.math.is_abstract(theta): for c_wire in nots: op_list.append(qml.CNOT(wires=[c_wire] + ancilla)) op_list.append(qml.RY(2 * theta, wires=ancilla)) nots = {} nots[wire_map[control_index]] = 1 continue if qml.math.abs(2 * theta) > tol: for c_wire in nots: op_list.append(qml.CNOT(wires=[c_wire] + ancilla)) op_list.append(qml.RY(2 * theta, wires=ancilla)) nots = {} if wire_map[control_index] in nots: del nots[wire_map[control_index]] else: nots[wire_map[control_index]] = 1 for c_wire in nots: op_list.append(qml.CNOT([c_wire] + ancilla)) for w_i, w_j in zip(wires_i, wires_j): op_list.append(qml.SWAP(wires=[w_i, w_j])) for w in wires_i: op_list.append(qml.Hadamard(w)) return op_list