Source code for pennylane.templates.subroutines.qdrift
# 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 template for QDrift subroutine."""importcopyimportpennylaneasqmlfrompennylane.mathimportrequires_grad,unwrapfrompennylane.operationimportOperationfrompennylane.opsimportLinearCombination,Sumfrompennylane.wiresimportWiresdef_check_hamiltonian_type(hamiltonian):ifnotisinstance(hamiltonian,Sum):raiseTypeError(f"The given operator must be a PennyLane ~.Sum, got {hamiltonian}")def_extract_hamiltonian_coeffs_and_ops(hamiltonian):"""Extract the coefficients and operators from a Hamiltonian that is a ``LinearCombination`` or a ``Sum``."""# Note that potentially_trainable_coeffs does *not* contain all coeffsifisinstance(hamiltonian,LinearCombination):coeffs,ops=hamiltonian.terms()elifisinstance(hamiltonian,Sum):coeffs,ops=[],[]foropinhamiltonian:coeff=getattr(op,"scalar",None)ifcoeffisNone:# coefficient is 1.0coeffs.append(1.0)ops.append(op)else:coeffs.append(coeff)ops.append(op.base)returncoeffs,ops@qml.QueuingManager.stop_recording()def_sample_decomposition(coeffs,ops,time,n=1,seed=None):"""Generate the randomly sampled decomposition Args: coeffs (array): the coefficients of the operations from each term in the Hamiltonian ops (list[~.Operator]): the normalized operations from each term in the Hamiltonian time (float): time to evolve under the target Hamiltonian n (int): number of samples in the product, defaults to 1 seed (int): random seed. defaults to None Returns: list[~.Operator]: the decomposition of operations as per the approximation """normalization_factor=qml.math.sum(qml.math.abs(coeffs))probs=qml.math.abs(coeffs)/normalization_factorexps=[qml.exp(base,(coeff/qml.math.abs(coeff))*normalization_factor*time*1j/n)forbase,coeffinzip(ops,coeffs)]choice_rng=qml.math.random.default_rng(seed)returnlist(choice_rng.choice(exps,p=probs,size=n,replace=True))
[docs]classQDrift(Operation):r"""An operation representing the QDrift approximation for the complex matrix exponential of a given Hamiltonian. The QDrift subroutine provides a method to approximate the matrix exponential of a Hamiltonian expressed as a linear combination of terms which in general do not commute. For the Hamiltonian :math:`H = \Sigma_j h_j H_{j}`, the product formula is constructed by random sampling from the terms of the Hamiltonian with the probability :math:`p_j = h_j / \sum_{j} hj` as: .. math:: \prod_{j}^{n} e^{i \lambda H_j \tau / n}, where :math:`\tau` is time, :math:`\lambda = \sum_j |h_j|` and :math:`n` is the total number of terms to be sampled and added to the product. Note, the terms :math:`H_{j}` are assumed to be normalized such that the "impact" of each term is fully encoded in the magnitude of :math:`h_{j}`. The number of samples :math:`n` required for a given error threshold can be approximated by: .. math:: n \ \approx \ \frac{2\lambda^{2}t^{2}}{\epsilon} For more details see `Phys. Rev. Lett. 123, 070503 (2019) <https://arxiv.org/abs/1811.08017>`_. Args: hamiltonian (Union[.Hamiltonian, .Sum]): The Hamiltonian written as a sum of operations time (float): The time of evolution, namely the parameter :math:`t` in :math:`e^{iHt}` n (int): An integer representing the number of exponentiated terms. seed (int): The seed for the random number generator. Raises: TypeError: The ``hamiltonian`` is not of type :class:`~.Sum` QuantumFunctionError: If the coefficients of ``hamiltonian`` are trainable and are used in a differentiable workflow. ValueError: If there is only one term in the Hamiltonian. **Example** .. code-block:: python3 coeffs = [0.25, 0.75] ops = [qml.X(0), qml.Z(0)] H = qml.dot(coeffs, ops) dev = qml.device("default.qubit", wires=2) @qml.qnode(dev) def my_circ(): # Prepare some state qml.Hadamard(0) # Evolve according to H qml.QDrift(H, time=1.2, n=10, seed=10) # Measure some quantity return qml.probs() >>> my_circ() array([0.65379493, 0. , 0.34620507, 0. ]) .. note:: The option to pass a custom ``decomposition`` to ``QDrift`` has been removed. Instead, the custom decomposition can be applied using :func:`~.pennylane.apply` on all operations in the decomposition. .. details:: :title: Usage Details We currently **Do NOT** support computing gradients with respect to the coefficients of the input Hamiltonian. We can however compute the gradient with respect to the evolution time: .. code-block:: python3 dev = qml.device("default.qubit", wires=2) @qml.qnode(dev) def my_circ(time): # Prepare H: H = qml.dot([0.2, -0.1], [qml.Y(0), qml.Z(1)]) # Prepare some state qml.Hadamard(0) # Evolve according to H qml.QDrift(H, time, n=10, seed=10) # Measure some quantity return qml.expval(qml.Z(0) @ qml.Z(1)) >>> time = np.array(1.23) >>> print(qml.grad(my_circ)(time)) 0.27980654844422853 The error in the approximation of time evolution with the QDrift protocol is directly related to the number of samples used in the product. We provide a method to upper-bound the error: >>> H = qml.dot([0.25, 0.75], [qml.X(0), qml.Z(0)]) >>> print(qml.QDrift.error(H, time=1.2, n=10)) 0.3661197552925645 """@classmethoddef_primitive_bind_call(cls,*args,**kwargs):returncls._primitive.bind(*args,**kwargs)def_flatten(self):h=self.hyperparameters["base"]hashable_hyperparameters=tuple(itemforiteminself.hyperparameters.items()ifitem[0]!="base")return(h,self.data[-1]),hashable_hyperparameters@classmethoddef_unflatten(cls,data,metadata):returncls(*data,**dict(metadata))def__init__(# pylint: disable=too-many-argumentsself,hamiltonian,time,n=1,seed=None,id=None):r"""Initialize the QDrift class"""_check_hamiltonian_type(hamiltonian)coeffs,ops=_extract_hamiltonian_coeffs_and_ops(hamiltonian)iflen(ops)<2:raiseValueError("There should be at least 2 terms in the Hamiltonian. Otherwise use `qml.exp`")ifany(requires_grad(coeff)forcoeffincoeffs):raiseqml.QuantumFunctionError("The QDrift template currently doesn't support differentiation through the ""coefficients of the input Hamiltonian.")self._hyperparameters={"n":n,"seed":seed,"base":hamiltonian}super().__init__(*hamiltonian.data,time,wires=hamiltonian.wires,id=id)
[docs]@staticmethoddefcompute_decomposition(*args,**kwargs):# pylint: disable=unused-argumentr"""Representation of the operator as a product of other operators (static method). .. math:: O = O_1 O_2 \dots O_n. .. note:: Operations making up the decomposition should be queued within the ``compute_decomposition`` method. .. seealso:: :meth:`~.Operator.decomposition`. Args: *params (list): trainable parameters of the operator, as stored in the ``parameters`` attribute wires (Iterable[Any], Wires): wires that the operator acts on **hyperparams (dict): non-trainable hyperparameters of the operator, as stored in the ``hyperparameters`` attribute Returns: list[Operator]: decomposition of the operator """time=args[-1]hamiltonian=kwargs["base"]seed=kwargs["seed"]n=kwargs["n"]coeffs,ops=_extract_hamiltonian_coeffs_and_ops(hamiltonian)decomposition=_sample_decomposition(unwrap(coeffs),ops,time,n=n,seed=seed)ifqml.QueuingManager.recording():foropindecomposition:qml.apply(op)returndecomposition
[docs]@staticmethoddeferror(hamiltonian,time,n=1):r"""A method for determining the upper-bound for the error in the approximation of the true matrix exponential. The error is bounded according to the following expression: .. math:: \epsilon \ \leq \ \frac{2\lambda^{2}t^{2}}{n} e^{\frac{2 \lambda t}{n}}, where :math:`t` is time, :math:`\lambda = \sum_j |h_j|` and :math:`n` is the total number of terms to be added to the product. For more details see `Phys. Rev. Lett. 123, 070503 (2019) <https://arxiv.org/abs/1811.08017>`_. Args: hamiltonian (Sum): The Hamiltonian written as a sum of operations time (float): The time of evolution, namely the parameter :math:`t` in :math:`e^{-iHt}` n (int): An integer representing the number of exponentiated terms. default is 1 Raises: TypeError: The given operator must be a PennyLane .Hamiltonian or .Sum Returns: float: upper bound on the precision achievable using the QDrift protocol """_check_hamiltonian_type(hamiltonian)coeffs,_=_extract_hamiltonian_coeffs_and_ops(hamiltonian)lmbda=qml.math.sum(qml.math.abs(coeffs))return(2*lmbda**2*time**2/n)*qml.math.exp(2*lmbda*time/n)