Source code for pennylane.templates.subroutines.out_poly
# Copyright 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."""Contains the OutPoly template."""importpennylaneasqmlfrompennylane.operationimportOperationfrompennylane.wiresimportWiresLikedef_get_polynomial(f,mod,*variable_sizes):"""Calculate the polynomial binary representation of a given function using the `Möbius inversion formula <https://en.wikipedia.org/wiki/Möbius_inversion_formula#On_posets>`_ . Args: f (callable): the function from which the polynomial is extracted mod (int): the modulus to use for the arithmetic operation *variable_sizes (int): variadic arguments that specify the number of bits needed to represent each of the variables of the function Returns: dict[tuple -> int]: dictionary with keys representing the variable terms of the polynomial and values representing the coefficients associated with those terms Example: For the function `f(x, y) = 4 * x * y`, setting `variable_sizes=(2, 1)` means that `x` is represented by two bits and `y` by one bit. We can expand `f(x, y)` as `4 * (2x_0 + x_1) * y_0`, where `x_0` and `x_1` are the binary digits of `x`. When fully expanded, the function becomes: `4 * x1 * y0 + 8 * x0 * y0`. Applying modulus 5, this can be represented as ``` { (0, 1, 1): 4, # represents the term x1 * y0 with coefficient 4 (1, 0, 1): 3 # represents the term x0 * y0 with coefficient 3 because 8 mod 5 = 3 } ``` Note that in each tuple, the first two bits correspond to the binary representation of the variable `x` and the third bit corresponds to the binary representation of the variable `y`. For example, `(0, 1, 1)` means `x1 * y0`, since `x0` is absent the first number in the tuple is zero. """total_wires=sum(variable_sizes)num_combinations=2**total_wiresall_binary_list=[list(map(int,bin(i)[2:].zfill(total_wires)))foriinrange(num_combinations)]f_values=[0]*num_combinationsforsinrange(num_combinations):bin_list=all_binary_list[s]decimal_values=[]start=0forwire_lengthinvariable_sizes:segment=bin_list[start:start+wire_length]# segment corresponding to the i-th variabledecimal=int("".join(map(str,segment)),2)# decimal representation of the i-th variabledecimal_values.append(decimal)start+=wire_length# compute the zeta transform of the target polynomialf_values[s]=f(*decimal_values)%modf_values=_mobius_inversion_of_zeta_transform(f_values,mod)coeffs_dict={}fors,f_valueinenumerate(f_values):ifnotqml.math.isclose(f_value,0.0):bin_tuple=tuple(all_binary_list[s])coeffs_dict[bin_tuple]=f_valuereturncoeffs_dictdef_mobius_inversion_of_zeta_transform(f_values,mod):""" Applies the `Möbius inversion <https://codeforces.com/blog/entry/72488>`_ to reverse the zeta transform and recover the original function values. The function loops over each bit position (from `total_wires`) and adjusts the values in `f_values` based on whether the corresponding bit in the bitmask is set (1) or not (0). The bitmask is a sequence of bits used to enable (1) or disable (0) specific positions in binary operations, allowing individual bits in data structures or numeric values to be checked or modified. The aim is to reverse the effect of the zeta transform by subtracting contributions from supersets in each step, effectively recovering the original values before the transformation. Args: f_values (List[int]): A list of integers representing the zeta transform over subsets of a bitmask. mod (int): The modulus used to perform the arithmetic operations. Returns: List[int]: The list `f_values` after applying the Möbius inversion, reduced modulo `mod`. """total_wires=int(qml.math.log2(len(f_values)))num_combinations=len(f_values)foriinrange(total_wires):ith_bit_on=1<<iformaskinrange(num_combinations):ifmask&ith_bit_on:f_values[mask]=(f_values[mask]-f_values[mask^ith_bit_on])%modreturnf_values
[docs]classOutPoly(Operation):r"""Performs the out-of-place polynomial operation. Given a function :math:`f(x_1, \dots, x_m)` and an integer modulus :math:`mod`, this operator performs: .. math:: \text{OutPoly}_{f, mod} |x_1 \rangle \dots |x_m \rangle |0 \rangle = |x_1 \rangle \dots |x_m \rangle |f(x_1, \dots, x_m)\, \text{mod} \; mod\rangle, where the integer inputs :math:`x_i` are embedded in the ``input_registers``. The result of the polynomial function :math:`f(x_1, \dots, x_m)` is computed modulo :math:`mod` in the computational basis and stored in the ``output_wires``. If the output wires are not initialized to zero, the evaluated result :math:`f(x_1, \dots, x_m)\ \text{mod}\ mod` will be added to the value initialized in the output register. This implementation is based on the Section II-B of `arXiv:2112.10537 <https://arxiv.org/abs/2112.10537>`_. .. note:: The integer values :math:`x_i` stored in each input register must be smaller than the modulus ``mod``. Args: polynomial_function (callable): The polynomial function to be applied. The number of arguments in the function must be equal to the number of input registers. input_registers (List[Union[Wires, Sequence[int]]]): List containing the wires (or the wire indices) used to store each variable of the polynomial. output_wires (Union[Wires, Sequence[int]]): The wires (or wire indices) used to store the output of the operation. mod (int, optional): The integer for performing the modulo on the result of the polynomial operation. If not provided, it defaults to :math:`2^{n}`, where :math:`n` is the number of qubits in the output register. work_wires (Union[Wires, Sequence[int]], optional): The auxiliary wires to use for performing the polynomial operation. The work wires are not needed if :math:`mod=2^{\text{length(output_wires)}}`, otherwise two work wires should be provided. Defaults to empty tuple. Raises: ValueError: If `mod` is not :math:`2^{\text{length(output_wires)}}` and insufficient number of work wires are provided. ValueError: If the wires used in the input and output registers overlap. ValueError: If the function is not defined with integer coefficients. Example: Given a polynomial function :math:`f(x, y) = x^2 + y`, we can calculate :math:`f(3, 2)` as follows: .. code-block:: python wires = qml.registers({"x": 2, "y": 2, "output": 4}) def f(x, y): return x ** 2 + y @qml.qnode(qml.device("default.qubit", shots = 1)) def circuit(): # load values of x and y qml.BasisEmbedding(3, wires=wires["x"]) qml.BasisEmbedding(2, wires=wires["y"]) # apply the polynomial qml.OutPoly( f, input_registers = [wires["x"], wires["y"]], output_wires = wires["output"]) return qml.sample(wires=wires["output"]) print(circuit()) .. code-block:: pycon >>> print(circuit()) [1 0 1 1] The result, :math:`[1 0 1 1]`, is the binary representation of :math:`3^2 + 2 = 11`. Note that the default value of `mod` in this example is :math:`2^{\text{len(output_wires)}} = 2^4 = 16`. For more information on using `mod`, see the Usage Details section. .. seealso:: The decomposition of this operator consists of controlled :class:`~.PhaseAdder` gates. .. details:: :title: Usage Details If the value of `mod` is not :math:`2^{\text{length(output_wires)}}`, then two auxiliary qubits must be provided. .. code-block:: python x_wires = [0, 1, 2] y_wires = [3, 4, 5] input_registers = [x_wires, y_wires] output_wires = [6, 7, 8] work_wires = [9,10] def f(x, y): return x ** 2 + y @qml.qnode(qml.device("default.qubit", shots = 1)) def circuit(): # loading values for x and y qml.BasisEmbedding(3, wires=x_wires) qml.BasisEmbedding(2, wires=y_wires) qml.BasisEmbedding(1, wires=output_wires) # applying the polynomial qml.OutPoly( f, input_registers, output_wires, mod = 7, work_wires = work_wires ) return qml.sample(wires=output_wires) .. code-block:: pycon >>> print(circuit()) [1 0 1] The result, :math:`[1 0 1]`, is the binary representation of :math:`1 + f(3, 2) = 1 + 3^2 + 2 \; \text{mod} \; 7 = 5`. In this example ``output_wires`` is initialized to :math:`1`, so this value is added to the solution. Generically, the expression is definded as: .. math:: \text{OutPoly}_{f, mod} |x_1 \rangle \dots |x_m \rangle |b \rangle = |x_1 \rangle \dots |x_m \rangle |b + f(x_1, \dots, x_m) \mod mod \rangle. """grad_method=Nonedef__init__(self,polynomial_function,input_registers,output_wires:WiresLike,mod=None,work_wires:WiresLike=(),id=None,**kwargs,):# pylint: disable=too-many-argumentsr"""Initialize the OutPoly class"""registers_wires=[*input_registers,output_wires]work_wires=qml.wires.Wires(()ifwork_wiresisNoneelsework_wires)num_work_wires=len(work_wires)ifmodisNone:mod=2**len(registers_wires[-1])elifmod!=2**len(registers_wires[-1])andnum_work_wires!=2:raiseValueError(f"If mod is not 2^{len(registers_wires[-1])}, two work wires should be provided")ifnotisinstance(mod,int):raiseValueError("mod must be an integer.")all_wires=[]inp_regs=[]forregininput_registers:wires=qml.wires.Wires(reg)inp_regs.append(wires)all_wires+=wiresself.hyperparameters["input_registers"]=tuple(inp_regs)wires=qml.wires.Wires(output_wires)self.hyperparameters["output_wires"]=wiresall_wires+=wiresself.hyperparameters["polynomial_function"]=polynomial_functionself.hyperparameters["mod"]=modself.hyperparameters["work_wires"]=work_wireswires_vars=[len(w)forwinregisters_wires[:-1]]self.hyperparameters["coeffs_list"]=kwargs.get("coeffs_list",tuple((key,value)forkey,valuein_get_polynomial(polynomial_function,mod,*wires_vars).items()),)coeffs=[c[1]forcinself.hyperparameters["coeffs_list"]]assertqml.math.allclose(coeffs,qml.math.floor(coeffs)),"The polynomial function must have integer coefficients"iflen(work_wires)!=0:all_wires+=work_wiresiflen(all_wires)!=sum(len(register)forregisterinregisters_wires)+num_work_wires:raiseValueError("None of the wires in a register should be included in other register.")super().__init__(wires=all_wires,id=id)def_flatten(self):metadata1=tuple((key,value)forkey,valueinself.hyperparameters.items())returntuple(),metadata1@classmethoddef_unflatten(cls,data,metadata):hyperparams_dict=dict(metadata)returncls(*data,**hyperparams_dict)