Source code for pennylane.templates.tensornetworks.mera
# Copyright 2018-2021 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 MERA template."""# pylint: disable-msg=too-many-branches,too-many-arguments,protected-accessimportwarningsfromcollections.abcimportCallableimportnumpyasnpimportpennylaneasqmlfrompennylane.operationimportAnyWires,Operationdefcompute_indices(wires,n_block_wires):"""Generate a list containing the wires for each block. Args: wires (Iterable): wires that the template acts on n_block_wires (int): number of wires per block Returns: layers (array): array of wire labels for each block """n_wires=len(wires)ifn_block_wires%2!=0:raiseValueError(f"n_block_wires must be an even integer; got {n_block_wires}")ifn_block_wires<2:raiseValueError(f"number of wires in each block must be larger than or equal to 2; got n_block_wires = {n_block_wires}")ifn_block_wires>n_wires:raiseValueError(f"n_block_wires must be smaller than or equal to the number of wires; "f"got n_block_wires = {n_block_wires} and number of wires = {n_wires}")ifnotnp.log2(n_wires/n_block_wires).is_integer():# pylint:disable=no-memberwarnings.warn(f"The number of wires should be n_block_wires times 2^n; got n_wires/n_block_wires = {n_wires/n_block_wires}")# number of layers in MERAn_layers=np.floor(np.log2(n_wires/n_block_wires)).astype(int)*2+1wires_list=[]wires_list.append(list(wires[0:n_block_wires]))highest_index=n_block_wires# compute block indices for all layersforiinrange(n_layers-1):# number of blocks in previous layern_elements_pre=2**((i+1)//2)ifi%2==0:# layer with new wiresnew_list=[]list_len=len(wires_list)forjinrange(list_len-n_elements_pre,list_len):new_wires=[wires[k]forkinrange(highest_index,highest_index+n_block_wires//2)]highest_index+=n_block_wires//2new_list.append(wires_list[j][0:n_block_wires//2]+new_wires)new_wires=[wires[k]forkinrange(highest_index,highest_index+n_block_wires//2)]highest_index+=n_block_wires//2new_list.append(new_wires+wires_list[j][n_block_wires//2::])wires_list=wires_list+new_listelse:# layer only using previous wireslist_len=len(wires_list)new_list=[]forjinrange(list_len-n_elements_pre,list_len-1):new_list.append(wires_list[j][n_block_wires//2::]+wires_list[j+1][0:n_block_wires//2])new_list.append(wires_list[j+1][n_block_wires//2::]+wires_list[list_len-n_elements_pre][0:n_block_wires//2])wires_list=wires_list+new_listreturntuple(tuple(l)forlinwires_list[::-1])
[docs]classMERA(Operation):"""The MERA template broadcasts an input circuit across many wires following the architecture of a multi-scale entanglement renormalization ansatz tensor network. This architecture can be found in `arXiv:quant-ph/0610099 <https://arxiv.org/abs/quant-ph/0610099>`_ and closely resembles `quantum convolutional neural networks <https://arxiv.org/abs/1810.03787>`_. The argument ``block`` is a user-defined quantum circuit. Each ``block`` may depend on a different set of parameters. These are passed as a list by the ``template_weights`` argument. For more details, see *Usage Details* below. Args: wires (Iterable): wires that the template acts on n_block_wires (int): number of wires per block block (Callable): quantum circuit that defines a block n_params_block (int): the number of parameters in a block template_weights (Sequence): list containing the weights for all blocks .. details:: :title: Usage Details In general, the block takes D parameters and **must** have the following signature: .. code-block:: python unitary(parameter1, parameter2, ... parameterD, wires) For a block with multiple parameters, ``n_params_block`` is equal to the number of parameters in ``block``. For a block with a single parameter, ``n_params_block`` is equal to the length of the parameter array. To avoid using ragged arrays, all block parameters should have the same dimension. The length of the ``template_weights`` argument should match the number of blocks. The expected number of blocks can be obtained from ``qml.MERA.get_n_blocks(wires, n_block_wires)``. This example demonstrates the use of ``MERA`` for a simple block. .. code-block:: python import pennylane as qml import numpy as np def block(weights, wires): qml.CNOT(wires=[wires[0],wires[1]]) qml.RY(weights[0], wires=wires[0]) qml.RY(weights[1], wires=wires[1]) n_wires = 4 n_block_wires = 2 n_params_block = 2 n_blocks = qml.MERA.get_n_blocks(range(n_wires),n_block_wires) template_weights = [[0.1,-0.3]]*n_blocks dev= qml.device('default.qubit',wires=range(n_wires)) @qml.qnode(dev) def circuit(template_weights): qml.MERA(range(n_wires),n_block_wires,block, n_params_block, template_weights) return qml.expval(qml.Z(1)) It may be necessary to reorder the wires to see the MERA architecture clearly: >>> print(qml.draw(circuit, level='device', wire_order=[2,0,1,3])(template_weights)) 2: ───────────────╭●──RY(0.10)──╭X──RY(-0.30)───────────────┤ 0: ─╭X──RY(-0.30)─│─────────────╰●──RY(0.10)──╭●──RY(0.10)──┤ 1: ─╰●──RY(0.10)──│─────────────╭X──RY(-0.30)─╰X──RY(-0.30)─┤ <Z> 3: ───────────────╰X──RY(-0.30)─╰●──RY(0.10)────────────────┤ """num_wires=AnyWiresgrad_method=None@propertydefnum_params(self):return1@classmethoddef_primitive_bind_call(cls,wires,n_block_wires,block,n_params_block,template_weights=None,id=None):# pylint: disable=arguments-differreturnsuper()._primitive_bind_call(wires=wires,n_block_wires=n_block_wires,block=block,n_params_block=n_params_block,template_weights=template_weights,id=id,)@classmethoddef_unflatten(cls,data,metadata):new_op=cls.__new__(cls)new_op._hyperparameters=dict(metadata[1])Operation.__init__(new_op,data,wires=metadata[0])returnnew_opdef__init__(self,wires,n_block_wires,block:Callable,n_params_block,template_weights=None,id=None,):ind_gates=compute_indices(wires,n_block_wires)n_wires=len(wires)shape=qml.math.shape(template_weights)# (n_params_block, n_blocks)n_blocks=int(2**(np.floor(np.log2(n_wires/n_block_wires))+2)-3)ifshape==():template_weights=np.random.rand(n_params_block,n_blocks)else:ifshape[0]!=n_blocks:raiseValueError(f"Weights tensor must have first dimension of length {n_blocks}; got {shape[0]}")ifshape[-1]!=n_params_block:raiseValueError(f"Weights tensor must have last dimension of length {n_params_block}; got {shape[-1]}")self._hyperparameters={"ind_gates":ind_gates,"block":block}super().__init__(template_weights,wires=wires,id=id)
[docs]@staticmethoddefcompute_decomposition(weights,wires,block,ind_gates):# pylint: disable=arguments-differ,unused-argumentr"""Representation of the operator as a product of other operators. .. math:: O = O_1 O_2 \dots O_n. .. seealso:: :meth:`~.MERA.decomposition`. Args: weights (list[tensor_like]): list containing the weights for all blocks wires (Iterable): wires that the template acts on block (Callable): quantum circuit that defines a block ind_gates (array): array of wire indices Returns: list[.Operator]: decomposition of the operator """op_list=[]block_gen=qml.tape.make_qscript(block)ifblock.__code__.co_argcount>2:foridx,winenumerate(ind_gates):op_list+=block_gen(*weights[idx],wires=w)elifblock.__code__.co_argcount==2:foridx,winenumerate(ind_gates):op_list+=block_gen(weights[idx],wires=w)else:forwinind_gates:op_list+=block_gen(wires=w)return[qml.apply(op)foropinop_list]ifqml.QueuingManager.recording()elseop_list
[docs]@staticmethoddefget_n_blocks(wires,n_block_wires):"""Returns the expected number of blocks for a set of wires and number of wires per block. Args: wires (Sequence): number of wires the template acts on n_block_wires (int): number of wires per block Returns: n_blocks (int): number of blocks; expected length of the template_weights argument """n_wires=len(wires)ifnotnp.log2(n_wires/n_block_wires).is_integer():# pylint:disable=no-memberwarnings.warn(f"The number of wires should be n_block_wires times 2^n; got n_wires/n_block_wires = {n_wires/n_block_wires}")ifn_block_wires>n_wires:raiseValueError(f"n_block_wires must be smaller than or equal to the number of wires; got n_block_wires = {n_block_wires} and number of wires = {n_wires}")n_blocks=2**(np.floor(np.log2(n_wires/n_block_wires))+2)-3returnint(n_blocks)