# 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."""This module contains tape expansion functions and stopping criteria togenerate such functions from."""# pylint: disable=unused-argument,invalid-unary-operand-type, unsupported-binary-operation, no-memberimportcontextlibimportwarningsimportpennylaneasqmlfrompennylane.operationimport(gen_is_multi_term_hamiltonian,has_gen,has_grad_method,has_nopar,has_unitary_gen,is_measurement,is_trainable,not_tape,)def_update_trainable_params(tape):params=tape.get_parameters(trainable_only=False)tape.trainable_params=qml.math.get_trainable_indices(params)defcreate_expand_fn(depth,stop_at=None,device=None,docstring=None):""" .. warning:: Please use the :func:`qml.transforms.decompose <.transforms.decompose>` function for decomposing circuits. Create a function for expanding a tape to a given depth, and with a specific stopping criterion. This is a wrapper around :meth:`~.QuantumTape.expand`. Args: depth (int): Depth for the expansion stop_at (callable): Stopping criterion. This must be a function with signature ``stop_at(obj)``, where ``obj`` is a *queueable* PennyLane object such as :class:`~.Operation` or :class:`~.MeasurementProcess`. It must return a boolean, indicating if the expansion should stop at this object. device (pennylane.devices.LegacyDevice): Ensure that the expanded tape only uses native gates of the given device. docstring (str): docstring for the generated expansion function Returns: callable: Tape expansion function. The returned function accepts a :class:`~.QuantumTape`, and returns an expanded :class:`~.QuantumTape`. **Example** Let us construct an expansion function that expands a tape in order to decompose trainable multi-parameter gates. We allow for up to five expansion steps, which can be controlled with the argument ``depth``. The stopping criterion is easy to write as >>> stop_at = ~(qml.operation.has_multipar & qml.operation.is_trainable) Then the expansion function can be obtained via >>> expand_fn = qml.transforms.create_expand_fn(depth=5, stop_at=stop_at) We can test the newly generated function on an example tape: .. code-block:: python ops = [ qml.RX(0.2, wires=0), qml.RX(qml.numpy.array(-2.4, requires_grad=True), wires=1), qml.Rot(1.7, 0.92, -1.1, wires=0), qml.Rot(*qml.numpy.array([-3.1, 0.73, 1.36], requires_grad=True), wires=1) ] tape = qml.tape.QuantumTape(ops) >>> new_tape = expand_fn(tape) >>> print(qml.drawer.tape_text(tape, decimals=1)) 0: ──RX(0.2)───Rot(1.7,0.9,-1.1)─┤ 1: ──RX(-2.4)──Rot(-3.1,0.7,1.4)─┤ >>> print(qml.drawer.tape_text(new_tape, decimals=1)) 0: ──RX(0.2)───Rot(1.7,0.9,-1.1)───────────────────┤ 1: ──RX(-2.4)──RZ(-3.1)───────────RY(0.7)──RZ(1.4)─┤ """# pylint: disable=unused-argumentifdeviceisnotNone:ifstop_atisNone:stop_at=device.stopping_conditionelse:stop_at&=device.stopping_conditiondefexpand_fn(tape,depth=depth,**kwargs):withqml.QueuingManager.stop_recording():ifnotall(stop_at(op)foropintape.operations):(tape,),_=qml.transforms.decompose(tape,max_expansion=depth,gate_set=stop_at)else:returntape_update_trainable_params(tape)returntapeifdocstring:expand_fn.__doc__=docstringreturnexpand_fn_expand_multipar_doc="""Expand out a tape so that all its parametrizedoperations have a single parameter.This is achieved by decomposing all parametrized operations that do not havea generator, up to maximum depth ``depth``.For a sufficient ``depth``, it should always be possible to obtain a tape containingonly single-parameter operations.Args: tape (.QuantumTape): the input tape to expand depth (int) : the maximum expansion depth **kwargs: additional keyword arguments are ignoredReturns: .QuantumTape: the expanded tape"""expand_multipar=create_expand_fn(depth=None,stop_at=not_tape|is_measurement|has_nopar|(has_gen&~gen_is_multi_term_hamiltonian),docstring=_expand_multipar_doc,)_expand_trainable_multipar_doc="""Expand out a tape so that all its trainableoperations have a single parameter.This is achieved by decomposing all trainable operations that do not havea generator, up to maximum depth ``depth``.For a sufficient ``depth``, it should always be possible to obtain a tape containingonly single-parameter operations.Args: tape (.QuantumTape): the input tape to expand depth (int) : the maximum expansion depth **kwargs: additional keyword arguments are ignoredReturns: .QuantumTape: the expanded tape"""expand_trainable_multipar=create_expand_fn(depth=None,stop_at=not_tape|is_measurement|has_nopar|(~is_trainable)|(has_gen&~gen_is_multi_term_hamiltonian),docstring=_expand_trainable_multipar_doc,)defcreate_expand_trainable_multipar(tape,use_tape_argnum=False):"""Creates the expand_trainable_multipar expansion transform with an option to include argnums."""ifnotuse_tape_argnum:returnexpand_trainable_multipar# pylint: disable=protected-accesstrainable_par_info=[tape.par_info[i]foriintape.trainable_params]trainable_ops=[info["op"]forinfointrainable_par_info]@qml.BooleanFndef_is_trainable(obj):returnobjintrainable_opsreturncreate_expand_fn(depth=None,stop_at=not_tape|is_measurement|has_nopar|(~_is_trainable)|(has_gen&~gen_is_multi_term_hamiltonian),docstring=_expand_trainable_multipar_doc,)_expand_nonunitary_gen_doc="""Expand out a tape so that all its parametrizedoperations have a unitary generator.This is achieved by decomposing all parametrized operations that either do not havea generator or have a non-unitary generator, up to maximum depth ``depth``.For a sufficient ``depth``, it should always be possible to obtain a tape containingonly unitarily generated operations.Args: tape (.QuantumTape): the input tape to expand depth (int) : the maximum expansion depth **kwargs: additional keyword arguments are ignoredReturns: .QuantumTape: the expanded tape"""expand_nonunitary_gen=create_expand_fn(depth=None,stop_at=not_tape|is_measurement|has_nopar|(has_gen&has_unitary_gen),docstring=_expand_nonunitary_gen_doc,)_expand_invalid_trainable_doc="""Expand out a tape so that it supports differentiationof requested operations.This is achieved by decomposing all trainable operations that have``Operation.grad_method=None`` until all resulting operationshave a defined gradient method, up to maximum depth ``depth``. Note that thismight not be possible, in which case the gradient rule will fail to apply.Args: tape (.QuantumTape): the input tape to expand depth (int) : the maximum expansion depth **kwargs: additional keyword arguments are ignoredReturns: .QuantumTape: the expanded tape"""expand_invalid_trainable=create_expand_fn(depth=None,stop_at=not_tape|is_measurement|(~is_trainable)|has_grad_method,docstring=_expand_invalid_trainable_doc,)@contextlib.contextmanagerdef_custom_decomp_context(custom_decomps):"""A context manager for applying custom decompositions of operations."""# Creates an individual context@contextlib.contextmanagerdef_custom_decomposition(obj,fn):# Covers the case where the user passes a string to indicate the Operatorifisinstance(obj,str):obj=getattr(qml,obj)original_decomp_method=obj.compute_decompositionoriginal_has_decomp_property=obj.has_decompositiontry:# Explicitly set the new compute_decomposition methodobj.compute_decomposition=staticmethod(fn)obj.has_decomposition=lambdaobj:Trueyieldfinally:obj.compute_decomposition=staticmethod(original_decomp_method)obj.has_decomposition=original_has_decomp_property# Loop through the decomposition dictionary and create all the contextstry:withcontextlib.ExitStack()asstack:forobj,fnincustom_decomps.items():# We enter a new context for each decomposition the user passesstack.enter_context(_custom_decomposition(obj,fn))stack=stack.pop_all()yieldfinally:stack.close()defcreate_decomp_expand_fn(custom_decomps,dev,decomp_depth=None):""" .. warning:: Please use the :func:`qml.transforms.decompose <.transforms.decompose>` function for decomposing circuits. Creates a custom expansion function for a device that applies a set of specified custom decompositions. Args: custom_decomps (Dict[Union(str, qml.operation.Operation), Callable]): Custom decompositions to be applied by the device at runtime. dev (pennylane.devices.LegacyDevice): A quantum device. decomp_depth: The maximum depth of the expansion. Returns: Callable: A custom expansion function that a device can call to expand its tapes within a context manager that applies custom decompositions. **Example** Suppose we would like a custom expansion function that decomposes all CNOTs into CZs. We first define a decomposition function: .. code-block:: python def custom_cnot(wires): return [ qml.Hadamard(wires=wires[1]), qml.CZ(wires=[wires[0], wires[1]]), qml.Hadamard(wires=wires[1]) ] We then create the custom function (passing a device, in order to pick up any additional stopping criteria the expansion should have), and then register the result as a custom function of the device: >>> custom_decomps = {qml.CNOT : custom_cnot} >>> expand_fn = qml.transforms.create_decomp_expand_fn(custom_decomps, dev) >>> dev.custom_expand(expand_fn) """custom_op_names=[opifisinstance(op,str)elseop.__name__foropincustom_decomps.keys()]# Create a new expansion function; stop at things that do not have# custom decompositions, or that satisfy the regular device stopping criteriacustom_fn=qml.transforms.create_expand_fn(decomp_depth,stop_at=qml.BooleanFn(lambdaobj:obj.namenotincustom_op_names),device=dev,)# Finally, we set the device's custom_expand_fn to a new one that# runs in a context where the decompositions have been replaced.defcustom_decomp_expand(self,circuit,max_expansion=decomp_depth):with_custom_decomp_context(custom_decomps):returncustom_fn(circuit,max_expansion=max_expansion)returncustom_decomp_expanddef_create_decomp_preprocessing(custom_decomps,dev,decomp_depth=None):"""Creates a custom preprocessing method for a device that applies a set of specified custom decompositions. Args: custom_decomps (Dict[Union(str, qml.operation.Operation), Callable]): Custom decompositions to be applied by the device at runtime. dev (pennylane.devices.Device): A quantum device. decomp_depth: The maximum depth of the expansion. Returns: Callable: A custom preprocessing method that a device can call to expand its tapes. **Example** Suppose we would like a custom expansion function that decomposes all CNOTs into CZs. We first define a decomposition function: .. code-block:: python def custom_cnot(wires): return [ qml.Hadamard(wires=wires[1]), qml.CZ(wires=[wires[0], wires[1]]), qml.Hadamard(wires=wires[1]) ] We then create the custom function (passing a device, in order to pick up any additional stopping criteria the expansion should have), and then register the result as a custom function of the device: >>> custom_decomps = {qml.CNOT : custom_cnot} >>> new_preprocessing = _create_decomp_preprocessing(custom_decomps, dev) >>> dev.preprocess = new_preprocessing """defdecomposer(op):ifisinstance(op,qml.ops.Controlled)andtype(op.base)incustom_decomps:op.base.compute_decomposition=custom_decomps[type(op.base)]returnop.decomposition()ifop.nameincustom_decomps:returncustom_decomps[op.name](*op.data,wires=op.wires,**op.hyperparameters)iftype(op)incustom_decomps:returncustom_decomps[type(op)](*op.data,wires=op.wires,**op.hyperparameters)returnop.decomposition()original_preprocess=dev.preprocess# pylint: disable=cell-var-from-loopdefnew_preprocess(execution_config=qml.devices.DefaultExecutionConfig):program,config=original_preprocess(execution_config)forcontainerinprogram:ifcontainer.transform==qml.devices.preprocess.decompose.transform:container.kwargs["decomposer"]=decomposercontainer.kwargs["max_expansion"]=decomp_depthforcondin["stopping_condition","stopping_condition_shots"]:# Devices that do not support native mid-circuit measurements# will not have "stopping_condition_shots".ifcondincontainer.kwargs:original_stopping_condition=container.kwargs[cond]defstopping_condition(obj):ifobj.nameincustom_decompsortype(obj)incustom_decomps:returnFalsereturnoriginal_stopping_condition(obj)container.kwargs[cond]=stopping_conditionbreakreturnprogram,configreturnnew_preprocess
[docs]@contextlib.contextmanagerdefset_decomposition(custom_decomps,dev,decomp_depth=None):"""Context manager for setting custom decompositions. .. warning:: The ``decomp_depth`` argument is deprecated and will be removed in version 0.41. Args: custom_decomps (Dict[Union(str, qml.operation.Operation), Callable]): Custom decompositions to be applied by the device at runtime. dev (pennylane.devices.LegacyDevice): A quantum device. decomp_depth: The maximum depth of the expansion. **Example** Suppose we would like a custom expansion function that decomposes all CNOTs into CZs. We first define a decomposition function: .. code-block:: python def custom_cnot(wires): return [ qml.Hadamard(wires=wires[1]), qml.CZ(wires=[wires[0], wires[1]]), qml.Hadamard(wires=wires[1]) ] This context manager can be used to temporarily change a devices expansion function to one that takes into account the custom decompositions. .. code-block:: python dev = qml.device("default.qubit", wires=2) @qml.qnode(dev) def circuit(): qml.CNOT(wires=[0, 1]) return qml.expval(qml.Z(0)) >>> print(qml.draw(circuit, level=None)()) 0: ─╭●─┤ <Z> 1: ─╰X─┤ Now let's set up a context where the custom decomposition will be applied: >>> with qml.transforms.set_decomposition({qml.CNOT : custom_cnot}, dev): ... print(qml.draw(circuit, wire_order=[0, 1])()) 0: ────╭●────┤ <Z> 1: ──H─╰Z──H─┤ """ifdecomp_depthisnotNone:warnings.warn("The decomp_depth argument is deprecated and will be removed in version v0.41.",qml.PennyLaneDeprecationWarning,)ifisinstance(dev,qml.devices.LegacyDeviceFacade):dev=dev.target_deviceoriginal_custom_expand_fn=dev.custom_expand_fn# Create a new expansion function; stop at things that do not have# custom decompositions, or that satisfy the regular device stopping criterianew_custom_expand_fn=create_decomp_expand_fn(custom_decomps,dev,decomp_depth=decomp_depth)# Set the custom expand function within this context onlytry:dev.custom_expand(new_custom_expand_fn)yieldfinally:dev.custom_expand_fn=original_custom_expand_fnelse:withwarnings.catch_warnings():warnings.filterwarnings(action="ignore",message=r"max_expansion argument is deprecated",category=qml.PennyLaneDeprecationWarning,)original_preprocess=dev.preprocessnew_preprocess=_create_decomp_preprocessing(custom_decomps,dev,decomp_depth=decomp_depth)try:dev.preprocess=new_preprocessyieldfinally:dev.preprocess=original_preprocess