# 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 submodule contains the discrete-variable quantum operations that comefrom quantum chemistry applications."""# pylint:disable=abstract-method,arguments-differ,protected-accessimportfunctoolsfromtypingimportOptional,Unionimportnumpyasnpfromscipy.sparseimportcsr_matriximportpennylaneasqmlfrompennylane.decompositionimportadd_decomps,register_resourcesfrompennylane.operationimportOperationfrompennylane.typingimportTensorLikefrompennylane.wiresimportWiresLikeI4=np.eye(4)I16=np.eye(16)stack_last=functools.partial(qml.math.stack,axis=-1)def_single_excitations_matrix(phi:TensorLike,phase_prefactor:TensorLike)->TensorLike:"""This helper function unifies the `compute_matrix` methods of `SingleExcitation`, `SingleExcitationPlus` and `SingleExcitationMinus`. `phase_prefactor` determines which operation is produced: `phase_prefactor=0.` : `SingleExcitation` `phase_prefactor=0.5j` : `SingleExcitationPlus` `phase_prefactor=-0.5j` : `SingleExcitationMinus` """interface=qml.math.get_interface(phi)ifinterface=="tensorflow":ifisinstance(phase_prefactor,complex):phi=qml.math.cast_like(phi,1j)c=qml.math.cos(phi/2)s=qml.math.sin(phi/2)e=qml.math.exp(phase_prefactor*phi)zeros=qml.math.zeros_like(phi)rows=[[e,zeros,zeros,zeros],[zeros,c,-s,zeros],[zeros,s,c,zeros],[zeros,zeros,zeros,e],]returnqml.math.stack([stack_last(row)forrowinrows],axis=-2)c=qml.math.cos(phi/2)s=qml.math.sin(phi/2)e=qml.math.exp(phase_prefactor*phi)mask_e=np.diag([1,0,0,1])mask_c=np.diag([0,1,1,0])mask_s=np.array([[0,0,0,0],[0,0,-1,0],[0,1,0,0],[0,0,0,0]])ifqml.math.ndim(phi)==0:ifinterface=="torch":mask_e=qml.math.convert_like(mask_e,phi)mask_c=qml.math.convert_like(mask_c,phi)mask_s=qml.math.convert_like(mask_s,phi)returne*mask_e+c*mask_c+s*mask_sreturn(qml.math.einsum("i,jk->ijk",e,mask_e)+qml.math.einsum("i,jk->ijk",c,mask_c)+qml.math.einsum("i,jk->ijk",s,mask_s))def_double_excitations_matrix(phi:TensorLike,phase_prefactor:TensorLike)->TensorLike:"""This helper function unifies the `compute_matrix` methods of `DoubleExcitation`, `DoubleExcitationPlus` and `DoubleExcitationMinus`. `phase_prefactor` determines which operation is produced: `phase_prefactor=0.` : `DoubleExcitation` `phase_prefactor=0.5j` : `DoubleExcitationPlus` `phase_prefactor=-0.5j` : `DoubleExcitationMinus` """interface=qml.math.get_interface(phi)ifinterface=="tensorflow"andisinstance(phase_prefactor,complex):phi=qml.math.cast_like(phi,1j)c=qml.math.cos(phi/2)s=qml.math.sin(phi/2)e=qml.math.exp(phase_prefactor*phi)ifqml.math.ndim(phi)==0:diag=qml.math.diag([e]*3+[c]+[e]*8+[c]+[e]*3)ifinterface=="torch":returndiag+s*qml.math.convert_like(DoubleExcitation.mask_s,phi)returndiag+s*DoubleExcitation.mask_sifisinstance(phase_prefactor,complex):c=(1+0j)*cdiag=qml.math.stack([e]*3+[c]+[e]*8+[c]+[e]*3,axis=-1)diag=qml.math.einsum("ij,jk->ijk",diag,I16)off_diag=qml.math.einsum("i,jk->ijk",s,DoubleExcitation.mask_s)returndiag+off_diag
[docs]classSingleExcitation(Operation):r""" Single excitation rotation. .. math:: U(\phi) = \begin{bmatrix} 1 & 0 & 0 & 0 \\ 0 & \cos(\phi/2) & -\sin(\phi/2) & 0 \\ 0 & \sin(\phi/2) & \cos(\phi/2) & 0 \\ 0 & 0 & 0 & 1 \end{bmatrix}. This operation performs a rotation in the two-dimensional subspace :math:`\{|01\rangle, |10\rangle\}`. The name originates from the occupation-number representation of fermionic wavefunctions, where the transformation from :math:`|10\rangle` to :math:`|01\rangle` is interpreted as "exciting" a particle from the first qubit to the second. **Details:** * Number of wires: 2 * Number of parameters: 1 * Number of dimensions per parameter: (0,) * Gradient recipe: The ``SingleExcitation`` operator satisfies a four-term parameter-shift rule (see Appendix F, https://doi.org/10.1088/1367-2630/ac2cb3): Args: phi (float): rotation angle :math:`\phi` wires (Sequence[int]): the wires the operation acts on id (str or None): String representing the operation (optional) **Example** The following circuit performs the transformation :math:`|10\rangle\rightarrow \cos( \phi/2)|10\rangle -\sin(\phi/2)|01\rangle`: .. code-block:: dev = qml.device('default.qubit', wires=2) @qml.qnode(dev) def circuit(phi): qml.X(0) qml.SingleExcitation(phi, wires=[0, 1]) return qml.state() circuit(0.1) """num_wires=2"""int: Number of wires that the operator acts on."""num_params=1"""int: Number of trainable parameters that the operator depends on."""ndim_params=(0,)"""tuple[int]: Number of dimensions per trainable parameter that the operator depends on."""grad_method="A""""Gradient computation method."""parameter_frequencies=[(0.5,1.0)]"""Frequencies of the operation parameter with respect to an expectation value."""resource_keys=set()@propertydefresource_params(self)->dict:return{}
[docs]@staticmethoddefcompute_matrix(phi:TensorLike)->TensorLike:# pylint: disable=arguments-differr"""Representation of the operator as a canonical matrix in the computational basis (static method). The canonical matrix is the textbook matrix representation that does not consider wires. Implicitly, this assumes that the wires of the operator correspond to the global wire order. .. seealso:: :meth:`~.SingleExcitation.matrix` Args: phi (tensor_like or float): rotation angle Returns: tensor_like: canonical matrix **Example** >>> qml.SingleExcitation.compute_matrix(torch.tensor(0.5)) tensor([[ 1.0000, 0.0000, 0.0000, 0.0000], [ 0.0000, 0.9689, -0.2474, 0.0000], [ 0.0000, 0.2474, 0.9689, 0.0000], [ 0.0000, 0.0000, 0.0000, 1.0000]]) """return_single_excitations_matrix(phi,0.0)
[docs]@staticmethoddefcompute_decomposition(phi:TensorLike,wires:WiresLike)->list["qml.operation.Operator"]:r"""Representation of the operator as a product of other operators (static method). : .. math:: O = O_1 O_2 \dots O_n. .. seealso:: :meth:`~.SingleExcitation.decomposition`. Args: phi (TensorLike): rotation angle :math:`\phi` wires (Iterable, Wires): wires that the operator acts on Returns: list[Operator]: decomposition into lower level operations **Example:** >>> qml.SingleExcitation.compute_decomposition(1.23, wires=(0,1)) [Adjoint(T(0)), H(0), S(0), Adjoint(T(1)), Adjoint(S(1)), H(1), CNOT(wires=[1, 0]), RZ(-0.615, wires=[0]), RY(0.615, wires=[1]), CNOT(wires=[1, 0]), Adjoint(S(0)), H(0), T(0), H(1), S(1), T(1)] """# This decomposition was found by plugging the matrix representation# into transforms.two_qubit_decomposition and post-processing some of# the resulting single-qubit gates.decomp_ops=[qml.adjoint(qml.T)(wires=wires[0]),qml.Hadamard(wires=wires[0]),qml.S(wires=wires[0]),qml.adjoint(qml.T)(wires=wires[1]),qml.adjoint(qml.S)(wires=wires[1]),qml.Hadamard(wires=wires[1]),qml.CNOT(wires=[wires[1],wires[0]]),qml.RZ(-phi/2,wires=wires[0]),qml.RY(phi/2,wires=wires[1]),qml.CNOT(wires=[wires[1],wires[0]]),qml.adjoint(qml.S)(wires=wires[0]),qml.Hadamard(wires=wires[0]),qml.T(wires=wires[0]),qml.Hadamard(wires=wires[1]),qml.S(wires=wires[1]),qml.T(wires=wires[1]),]returndecomp_ops
[docs]classSingleExcitationMinus(Operation):r""" Single excitation rotation with negative phase-shift outside the rotation subspace. .. math:: U_-(\phi) = \begin{bmatrix} e^{-i\phi/2} & 0 & 0 & 0 \\ 0 & \cos(\phi/2) & -\sin(\phi/2) & 0 \\ 0 & \sin(\phi/2) & \cos(\phi/2) & 0 \\ 0 & 0 & 0 & e^{-i\phi/2} \end{bmatrix}. **Details:** * Number of wires: 2 * Number of parameters: 1 * Number of dimensions per parameter: (0,) * Gradient recipe: :math:`\frac{d}{d\phi}f(U_-(\phi)) = \frac{1}{2}\left[f(U_-(\phi+\pi/2)) - f(U_-(\phi-\pi/2))\right]` where :math:`f` is an expectation value depending on :math:`U_-(\phi)`. Args: phi (float): rotation angle :math:`\phi` wires (Sequence[int] or int): the wires the operation acts on id (str or None): String representing the operation (optional) """num_wires=2"""int: Number of wires that the operator acts on."""num_params=1"""int: Number of trainable parameters that the operator depends on."""ndim_params=(0,)"""tuple[int]: Number of dimensions per trainable parameter that the operator depends on."""grad_method="A""""Gradient computation method."""parameter_frequencies=[(1,)]"""Frequencies of the operation parameter with respect to an expectation value."""resource_keys=set()@propertydefresource_params(self)->dict:return{}
[docs]@staticmethoddefcompute_matrix(phi:TensorLike)->TensorLike:# pylint: disable=arguments-differr"""Representation of the operator as a canonical matrix in the computational basis (static method). The canonical matrix is the textbook matrix representation that does not consider wires. Implicitly, this assumes that the wires of the operator correspond to the global wire order. .. seealso:: :meth:`~.SingleExcitationMinus.matrix` Args: phi (tensor_like or float): rotation angle Returns: tensor_like: canonical matrix **Example** >>> qml.SingleExcitationMinus.compute_matrix(torch.tensor(0.5)) tensor([[ 0.9689-0.2474j, 0.0000+0.0000j, 0.0000+0.0000j, 0.0000+0.0000j], [ 0.0000+0.0000j, 0.9689+0.0000j, -0.2474+0.0000j, 0.0000+0.0000j], [ 0.0000+0.0000j, 0.2474+0.0000j, 0.9689+0.0000j, 0.0000+0.0000j], [ 0.0000+0.0000j, 0.0000+0.0000j, 0.0000+0.0000j, 0.9689-0.2474j]]) """return_single_excitations_matrix(phi,-0.5j)
[docs]@staticmethoddefcompute_decomposition(phi:TensorLike,wires:WiresLike)->list["qml.operation.Operator"]:r"""Representation of the operator as a product of other operators (static method). : .. math:: O = O_1 O_2 \dots O_n. .. seealso:: :meth:`~.SingleExcitationMinus.decomposition`. Args: phi (float): rotation angle :math:`\phi` wires (Iterable, Wires): wires that the operator acts on Returns: list[Operator]: decomposition into lower level operations **Example:** >>> qml.SingleExcitationMinus.compute_decomposition(1.23, wires=(0,1)) [X(0), X(1), ControlledPhaseShift(-0.615, wires=[1, 0]), X(0), X(1), ControlledPhaseShift(-0.615, wires=[0, 1]), CNOT(wires=[0, 1]), CRY(1.23, wires=[1, 0]), CNOT(wires=[0, 1])] """decomp_ops=[qml.X(wires[0]),qml.X(wires[1]),qml.ControlledPhaseShift(-phi/2,wires=[wires[1],wires[0]]),qml.X(wires[0]),qml.X(wires[1]),qml.ControlledPhaseShift(-phi/2,wires=[wires[0],wires[1]]),qml.CNOT(wires=[wires[0],wires[1]]),qml.CRY(phi,wires=[wires[1],wires[0]]),qml.CNOT(wires=[wires[0],wires[1]]),]returndecomp_ops
[docs]classSingleExcitationPlus(Operation):r""" Single excitation rotation with positive phase-shift outside the rotation subspace. .. math:: U_+(\phi) = \begin{bmatrix} e^{i\phi/2} & 0 & 0 & 0 \\ 0 & \cos(\phi/2) & -\sin(\phi/2) & 0 \\ 0 & \sin(\phi/2) & \cos(\phi/2) & 0 \\ 0 & 0 & 0 & e^{i\phi/2} \end{bmatrix}. **Details:** * Number of wires: 2 * Number of parameters: 1 * Number of dimensions per parameter: (0,) * Gradient recipe: :math:`\frac{d}{d\phi}f(U_+(\phi)) = \frac{1}{2}\left[f(U_+(\phi+\pi/2)) - f(U_+(\phi-\pi/2))\right]` where :math:`f` is an expectation value depending on :math:`U_+(\phi)`. Args: phi (float): rotation angle :math:`\phi` wires (Sequence[int] or int): the wires the operation acts on id (str or None): String representing the operation (optional) """num_wires=2"""int: Number of wires that the operator acts on."""num_params=1"""int: Number of trainable parameters that the operator depends on."""ndim_params=(0,)"""tuple[int]: Number of dimensions per trainable parameter that the operator depends on."""grad_method="A""""Gradient computation method."""parameter_frequencies=[(1,)]"""Frequencies of the operation parameter with respect to an expectation value."""resource_keys=set()@propertydefresource_params(self)->dict:return{}
[docs]@staticmethoddefcompute_matrix(phi:TensorLike)->TensorLike:# pylint: disable=arguments-differr"""Representation of the operator as a canonical matrix in the computational basis (static method). The canonical matrix is the textbook matrix representation that does not consider wires. Implicitly, this assumes that the wires of the operator correspond to the global wire order. .. seealso:: :meth:`~.SingleExcitationPlus.matrix` Args: phi (tensor_like or float): rotation angle Returns: tensor_like: canonical matrix **Example** >>> qml.SingleExcitationPlus.compute_matrix(torch.tensor(0.5)) tensor([[ 0.9689+0.2474j, 0.0000+0.0000j, 0.0000+0.0000j, 0.0000+0.0000j], [ 0.0000+0.0000j, 0.9689+0.0000j, -0.2474+0.0000j, 0.0000+0.0000j], [ 0.0000+0.0000j, 0.2474+0.0000j, 0.9689+0.0000j, 0.0000+0.0000j], [ 0.0000+0.0000j, 0.0000+0.0000j, 0.0000+0.0000j, 0.9689+0.2474j]]) """return_single_excitations_matrix(phi,0.5j)
[docs]@staticmethoddefcompute_decomposition(phi:TensorLike,wires:WiresLike)->list["qml.operation.Operator"]:r"""Representation of the operator as a product of other operators (static method). : .. math:: O = O_1 O_2 \dots O_n. .. seealso:: :meth:`~.SingleExcitationPlus.decomposition`. Args: phi (float): rotation angle :math:`\phi` wires (Iterable, Wires): wires that the operator acts on Returns: list[Operator]: decomposition into lower level operations **Example:** >>> qml.SingleExcitationPlus.compute_decomposition(1.23, wires=(0,1)) [X(0), X(1), ControlledPhaseShift(0.615, wires=[1, 0]), X(0), X(1), ControlledPhaseShift(0.615, wires=[0, 1]), CNOT(wires=[0, 1]), CRY(1.23, wires=[1, 0]), CNOT(wires=[0, 1])] """decomp_ops=[qml.X(wires[0]),qml.X(wires[1]),qml.ControlledPhaseShift(phi/2,wires=[wires[1],wires[0]]),qml.X(wires[0]),qml.X(wires[1]),qml.ControlledPhaseShift(phi/2,wires=[wires[0],wires[1]]),qml.CNOT(wires=[wires[0],wires[1]]),qml.CRY(phi,wires=[wires[1],wires[0]]),qml.CNOT(wires=[wires[0],wires[1]]),]returndecomp_ops
[docs]classDoubleExcitation(Operation):r""" Double excitation rotation. This operation performs an :math:`SO(2)` rotation in the two-dimensional subspace :math:`\{ |1100\rangle,|0011\rangle\}`. More precisely, it performs the transformation .. math:: &|0011\rangle \rightarrow \cos(\phi/2) |0011\rangle + \sin(\phi/2) |1100\rangle\\ &|1100\rangle \rightarrow \cos(\phi/2) |1100\rangle - \sin(\phi/2) |0011\rangle, while leaving all other basis states unchanged. The name originates from the occupation-number representation of fermionic wavefunctions, where the transformation from :math:`|1100\rangle` to :math:`|0011\rangle` is interpreted as "exciting" two particles from the first pair of qubits to the second pair of qubits. **Details:** * Number of wires: 4 * Number of parameters: 1 * Number of dimensions per parameter: (0,) * Gradient recipe: The ``DoubleExcitation`` operator satisfies a four-term parameter-shift rule (see Appendix F, https://doi.org/10.1088/1367-2630/ac2cb3): Args: phi (float): rotation angle :math:`\phi` wires (Sequence[int]): the wires the operation acts on id (str or None): String representing the operation (optional) **Example** The following circuit performs the transformation :math:`|1100\rangle\rightarrow \cos( \phi/2)|1100\rangle - \sin(\phi/2)|0011\rangle)`: .. code-block:: dev = qml.device('default.qubit', wires=4) @qml.qnode(dev) def circuit(phi): qml.X(0) qml.X(1) qml.DoubleExcitation(phi, wires=[0, 1, 2, 3]) return qml.state() circuit(0.1) """num_wires=4"""int: Number of wires that the operator acts on."""num_params=1"""int: Number of trainable parameters that the operator depends on."""ndim_params=(0,)"""tuple[int]: Number of dimensions per trainable parameter that the operator depends on."""grad_method="A""""Gradient computation method."""parameter_frequencies=[(0.5,1.0)]"""Frequencies of the operation parameter with respect to an expectation value."""resource_keys=set()@propertydefresource_params(self)->dict:return{}
[docs]@staticmethoddefcompute_matrix(phi:TensorLike)->TensorLike:# pylint: disable=arguments-differr"""Representation of the operator as a canonical matrix in the computational basis (static method). The canonical matrix is the textbook matrix representation that does not consider wires. Implicitly, this assumes that the wires of the operator correspond to the global wire order. .. seealso:: :meth:`~.DoubleExcitation.matrix` Args: phi (tensor_like or float): rotation angle Returns: tensor_like: canonical matrix """return_double_excitations_matrix(phi,0.0)
[docs]@staticmethoddefcompute_decomposition(phi:TensorLike,wires:WiresLike)->list["qml.operation.Operator"]:r"""Representation of the operator as a product of other operators (static method). : .. math:: O = O_1 O_2 \dots O_n. .. seealso:: :meth:`~.DoubleExcitation.decomposition`. For the source of this decomposition, see page 17 of `"Local, Expressive, Quantum-Number-Preserving VQE Ansatze for Fermionic Systems" <https://doi.org/10.1088/1367-2630/ac2cb3>`_ . Args: phi (float): rotation angle :math:`\phi` wires (Iterable, Wires): wires that the operator acts on Returns: list[Operator]: decomposition into lower level operations **Example:** >>> qml.DoubleExcitation.compute_decomposition(1.23, wires=(0,1,2,3)) [CNOT(wires=[2, 3]), CNOT(wires=[0, 2]), H(3), H(0), CNOT(wires=[2, 3]), CNOT(wires=[0, 1]), RY(0.15375, wires=[1]), RY(-0.15375, wires=[0]), CNOT(wires=[0, 3]), H(3), CNOT(wires=[3, 1]), RY(0.15375, wires=[1]), RY(-0.15375, wires=[0]), CNOT(wires=[2, 1]), CNOT(wires=[2, 0]), RY(-0.15375, wires=[1]), RY(0.15375, wires=[0]), CNOT(wires=[3, 1]), H(3), CNOT(wires=[0, 3]), RY(-0.15375, wires=[1]), RY(0.15375, wires=[0]), CNOT(wires=[0, 1]), CNOT(wires=[2, 0]), H(0), H(3), CNOT(wires=[0, 2]), CNOT(wires=[2, 3])] """# This decomposition is the "upside down" version of that on p17 of https://arxiv.org/abs/2104.05695decomp_ops=[qml.CNOT(wires=[wires[2],wires[3]]),qml.CNOT(wires=[wires[0],wires[2]]),qml.Hadamard(wires=wires[3]),qml.Hadamard(wires=wires[0]),qml.CNOT(wires=[wires[2],wires[3]]),qml.CNOT(wires=[wires[0],wires[1]]),qml.RY(phi/8,wires=wires[1]),qml.RY(-phi/8,wires=wires[0]),qml.CNOT(wires=[wires[0],wires[3]]),qml.Hadamard(wires=wires[3]),qml.CNOT(wires=[wires[3],wires[1]]),qml.RY(phi/8,wires=wires[1]),qml.RY(-phi/8,wires=wires[0]),qml.CNOT(wires=[wires[2],wires[1]]),qml.CNOT(wires=[wires[2],wires[0]]),qml.RY(-phi/8,wires=wires[1]),qml.RY(phi/8,wires=wires[0]),qml.CNOT(wires=[wires[3],wires[1]]),qml.Hadamard(wires=wires[3]),qml.CNOT(wires=[wires[0],wires[3]]),qml.RY(-phi/8,wires=wires[1]),qml.RY(phi/8,wires=wires[0]),qml.CNOT(wires=[wires[0],wires[1]]),qml.CNOT(wires=[wires[2],wires[0]]),qml.Hadamard(wires=wires[0]),qml.Hadamard(wires=wires[3]),qml.CNOT(wires=[wires[0],wires[2]]),qml.CNOT(wires=[wires[2],wires[3]]),]returndecomp_ops
[docs]classDoubleExcitationPlus(Operation):r""" Double excitation rotation with positive phase-shift outside the rotation subspace. This operation performs an :math:`SO(2)` rotation in the two-dimensional subspace :math:`\{ |1100\rangle,|0011\rangle\}` while applying a phase-shift on other states. More precisely, it performs the transformation .. math:: &|0011\rangle \rightarrow \cos(\phi/2) |0011\rangle - \sin(\phi/2) |1100\rangle\\ &|1100\rangle \rightarrow \cos(\phi/2) |1100\rangle + \sin(\phi/2) |0011\rangle\\ &|x\rangle \rightarrow e^{i\phi/2} |x\rangle, for all other basis states :math:`|x\rangle`. **Details:** * Number of wires: 4 * Number of parameters: 1 * Number of dimensions per parameter: (0,) * Gradient recipe: :math:`\frac{d}{d\phi}f(U_+(\phi)) = \frac{1}{2}\left[f(U_+(\phi+\pi/2)) - f(U_+(\phi-\pi/2))\right]` where :math:`f` is an expectation value depending on :math:`U_+(\phi)` Args: phi (float): rotation angle :math:`\phi` wires (Sequence[int]): the wires the operation acts on id (str or None): String representing the operation (optional) """num_wires=4"""int: Number of wires that the operator acts on."""num_params=1"""int: Number of trainable parameters that the operator depends on."""ndim_params=(0,)"""tuple[int]: Number of dimensions per trainable parameter that the operator depends on."""grad_method="A""""Gradient computation method."""parameter_frequencies=[(1,)]"""Frequencies of the operation parameter with respect to an expectation value."""resource_keys=set()@propertydefresource_params(self)->dict:return{}
[docs]@staticmethoddefcompute_matrix(phi:TensorLike)->TensorLike:# pylint: disable=arguments-differr"""Representation of the operator as a canonical matrix in the computational basis (static method). The canonical matrix is the textbook matrix representation that does not consider wires. Implicitly, this assumes that the wires of the operator correspond to the global wire order. .. seealso:: :meth:`~.DoubleExcitationPlus.matrix` Args: phi (tensor_like or float): rotation angle Returns: tensor_like: canonical matrix """return_double_excitations_matrix(phi,0.5j)
[docs]classDoubleExcitationMinus(Operation):r""" Double excitation rotation with negative phase-shift outside the rotation subspace. This operation performs an :math:`SO(2)` rotation in the two-dimensional subspace :math:`\{ |1100\rangle,|0011\rangle\}` while applying a phase-shift on other states. More precisely, it performs the transformation .. math:: &|0011\rangle \rightarrow \cos(\phi/2) |0011\rangle - \sin(\phi/2) |1100\rangle\\ &|1100\rangle \rightarrow \cos(\phi/2) |1100\rangle + \sin(\phi/2) |0011\rangle\\ &|x\rangle \rightarrow e^{-i\phi/2} |x\rangle, for all other basis states :math:`|x\rangle`. **Details:** * Number of wires: 4 * Number of parameters: 1 * Number of dimensions per parameter: (0,) * Gradient recipe: :math:`\frac{d}{d\phi}f(U_-(\phi)) = \frac{1}{2}\left[f(U_-(\phi+\pi/2)) - f(U_-(\phi-\pi/2))\right]` where :math:`f` is an expectation value depending on :math:`U_-(\phi)` Args: phi (float): rotation angle :math:`\phi` wires (Sequence[int]): the wires the operation acts on id (str or None): String representing the operation (optional) """num_wires=4"""int: Number of wires that the operator acts on."""num_params=1"""int: Number of trainable parameters that the operator depends on."""ndim_params=(0,)"""tuple[int]: Number of dimensions per trainable parameter that the operator depends on."""grad_method="A""""Gradient computation method."""parameter_frequencies=[(1,)]"""Frequencies of the operation parameter with respect to an expectation value."""resource_keys=set()@propertydefresource_params(self)->dict:return{}
[docs]@staticmethoddefcompute_matrix(phi:TensorLike)->TensorLike:# pylint: disable=arguments-differr"""Representation of the operator as a canonical matrix in the computational basis (static method). The canonical matrix is the textbook matrix representation that does not consider wires. Implicitly, this assumes that the wires of the operator correspond to the global wire order. .. seealso:: :meth:`~.DoubleExcitationMinus.matrix` Args: phi (tensor_like or float): rotation angle Returns: tensor_like: canonical matrix """return_double_excitations_matrix(phi,-0.5j)
[docs]classOrbitalRotation(Operation):r""" Spin-adapted spatial orbital rotation. For two neighbouring spatial orbitals :math:`\{|\Phi_{0}\rangle, |\Phi_{1}\rangle\}`, this operation performs the following transformation .. math:: &|\Phi_{0}\rangle = \cos(\phi/2)|\Phi_{0}\rangle - \sin(\phi/2)|\Phi_{1}\rangle\\ &|\Phi_{1}\rangle = \cos(\phi/2)|\Phi_{0}\rangle + \sin(\phi/2)|\Phi_{1}\rangle, with the same orbital operation applied in the :math:`\alpha` and :math:`\beta` spin orbitals. .. figure:: ../../_static/qchem/orbital_rotation.jpeg :align: center :width: 100% :target: javascript:void(0); Here, :math:`G(\phi)` represents a single-excitation Givens rotation and :math:`f\text{SWAP}(\pi)` represents the fermionic swap operator, implemented in PennyLane as the :class:`~.SingleExcitation` operation and :class:`~.FermionicSWAP` operation, respectively. This implementation is a modified version of the one given in `Anselmetti et al. (2021) <https://doi.org/10.1088/1367-2630/ac2cb3>`__\ , and is consistent with the Jordan-Wigner mapping in interleaved ordering. **Details:** * Number of wires: 4 * Number of parameters: 1 * Number of dimensions per parameter: (0,) * Gradient recipe: The ``OrbitalRotation`` operator has 4 equidistant frequencies :math:`\{0.5, 1, 1.5, 2\}`, and thus permits an 8-term parameter-shift rule. (see `Wierichs et al. (2022) <https://doi.org/10.22331/q-2022-03-30-677>`__). Args: phi (float): rotation angle :math:`\phi` wires (Sequence[int]): the wires the operation acts on id (str or None): String representing the operation (optional) **Example** .. code-block:: >>> dev = qml.device('default.qubit', wires=4) >>> @qml.qnode(dev) ... def circuit(phi): ... qml.BasisState(np.array([1, 1, 0, 0]), wires=[0, 1, 2, 3]) ... qml.OrbitalRotation(phi, wires=[0, 1, 2, 3]) ... return qml.state() >>> circuit(0.1) array([ 0. +0.j, 0. +0.j, 0. +0.j, 0.00249792+0.j, 0. +0.j, 0. +0.j, 0.04991671+0.j, 0. +0.j, 0. +0.j, -0.04991671+0.j, 0. +0.j, 0. +0.j, 0.99750208+0.j, 0. +0.j, 0. +0.j, 0. +0.j]) """num_wires=4"""int: Number of wires that the operator acts on."""num_params=1"""int: Number of trainable parameters that the operator depends on."""ndim_params=(0,)"""tuple[int]: Number of dimensions per trainable parameter that the operator depends on."""grad_method="A""""Gradient computation method."""parameter_frequencies=[(0.5,1.0,1.5,2.0)]"""Frequencies of the operation parameter with respect to an expectation value."""resource_keys=set()@propertydefresource_params(self)->dict:return{}
[docs]@staticmethoddefcompute_matrix(phi:TensorLike)->TensorLike:# pylint: disable=arguments-differr"""Representation of the operator as a canonical matrix in the computational basis (static method). The canonical matrix is the textbook matrix representation that does not consider wires. Implicitly, this assumes that the wires of the operator correspond to the global wire order. .. seealso:: :meth:`~.OrbitalRotation.matrix` Args: phi (tensor_like or float): rotation angle Returns: tensor_like: canonical matrix """# This matrix is the "sign flipped" version of that on p18 of https://arxiv.org/abs/2104.05695,# where the sign flip is to adjust for the opposite convention used by authors for naming wires.# Additionally, there was a typo in the sign of a matrix element "s" at [2, 8], which is fixed here.# It has been further modifided for the decomposition consistent with interleaved JW ordering.c=qml.math.cos(phi/2)s=qml.math.sin(phi/2)ifqml.math.ndim(phi)==0:diag=qml.math.diag([1.0,c,c,c**2,c,1.0,c**2,c,c,c**2,1.0,c,c**2,c,c,1.0])ifqml.math.get_interface(phi)=="torch":mask_s=qml.math.convert_like(OrbitalRotation.mask_s,phi)mask_cs=qml.math.convert_like(OrbitalRotation.mask_cs,phi)mask_s2=qml.math.convert_like(OrbitalRotation.mask_s2,phi)returndiag+s*mask_s+(c*s)*mask_cs+s**2*mask_s2return(diag+s*OrbitalRotation.mask_s+(c*s)*OrbitalRotation.mask_cs+s**2*OrbitalRotation.mask_s2)ones=qml.math.ones_like(c)diag=qml.math.stack([ones,c,c,c**2,c,ones,c**2,c,c,c**2,ones,c,c**2,c,c,ones],axis=-1,)diag=qml.math.einsum("ij,jk->ijk",diag,I16)off_diag=(qml.math.einsum("i,jk->ijk",s,OrbitalRotation.mask_s)+qml.math.einsum("i,jk->ijk",c*s,OrbitalRotation.mask_cs)+qml.math.einsum("i,jk->ijk",s**2,OrbitalRotation.mask_s2))returndiag+off_diag
[docs]@staticmethoddefcompute_decomposition(phi:TensorLike,wires:WiresLike)->list["qml.operation.Operator"]:r"""Representation of the operator as a product of other operators (static method). : .. math:: O = O_1 O_2 \dots O_n. .. seealso:: :meth:`~.OrbitalRotation.decomposition`. This operator is decomposed into two :class:`~.SingleExcitation` gates. For a decomposition into more elementary gates, see page 18 of `"Local, Expressive, Quantum-Number-Preserving VQE Ansatze for Fermionic Systems" <https://doi.org/10.1088/1367-2630/ac2cb3>`_ . Args: phi (float): rotation angle :math:`\phi` wires (Iterable, Wires): wires that the operator acts on Returns: list[Operator]: decomposition into lower level operations **Example:** >>> qml.OrbitalRotation.compute_decomposition(1.2, wires=[0, 1, 2, 3]) [qml.FermionicSWAP(np.pi, wires=[1, 2]), SingleExcitation(1.2, wires=[0, 2]), SingleExcitation(1.2, wires=[1, 3]), qml.FermionicSWAP(np.pi, wires=[1, 2])] """return[qml.FermionicSWAP(np.pi,wires=[wires[1],wires[2]]),qml.SingleExcitation(phi,wires=[wires[0],wires[1]]),qml.SingleExcitation(phi,wires=[wires[2],wires[3]]),qml.FermionicSWAP(np.pi,wires=[wires[1],wires[2]]),]
[docs]classFermionicSWAP(Operation):r"""Fermionic SWAP rotation. .. math:: U(\phi) = \begin{bmatrix} 1 & 0 & 0 & 0 \\ 0 & e^{i \phi/2} \cos(\phi/2) & -ie^{i \phi/2} \sin(\phi/2) & 0 \\ 0 & -ie^{i \phi/2} \sin(\phi/2) & e^{i \phi/2} \cos(\phi/2) & 0 \\ 0 & 0 & 0 & e^{i \phi} \end{bmatrix}. This operation performs a rotation in the adjacent fermionic modes under the Jordan-Wigner mapping, and is realized by the following transformation of basis states: .. math:: &|00\rangle \mapsto |00\rangle\\ &|01\rangle \mapsto e^{i \phi/2} \cos(\phi/2)|01\rangle - ie^{i \phi/2} \sin(\phi/2)|10\rangle\\ &|10\rangle \mapsto -ie^{i \phi/2} \sin(\phi/2)|01\rangle + e^{i \phi/2} \cos(\phi/2)|10\rangle\\ &|11\rangle \mapsto e^{i \phi}|11\rangle, where qubits in :math:`|0\rangle` and :math:`|1\rangle` states represent a hole and a fermion in the orbital, respectively. It preserves anti-symmetrization of orbitals by applying a phase factor of :math:`e^{i \phi/2}` to the state for each qubit initially in :math:`|1\rangle` state. Consequently, for :math:`\phi=\pi`, the given rotation will essentially perform a SWAP operation on the qubits while applying a global phase of :math:`-1`, if both qubits are :math:`|1\rangle`. **Details:** * Number of wires: 2 * Number of parameters: 1 * Number of dimensions per parameter: (0,) * Gradient recipe: :math:`\frac{d}{d\phi}f(U(\phi)) = \frac{1}{2}\left[f(U(\phi+\pi/2)) - f(U(\phi-\pi/2))\right]` where :math:`f` is an expectation value depending on :math:`U(\phi)` Args: phi (float): rotation angle :math:`\phi` wires (Sequence[int]): the wires the operation acts on id (str or None): String representing the operation (optional) **Example** The following circuit performs the transformation: :math:`|01\rangle \mapsto e^{i \phi/2} \cos(\phi/2)|01\rangle - ie^{i \phi/2} \sin(\phi/2)|10\rangle`, where :math:`\phi=0.1`: .. code-block:: >>> dev = qml.device('default.qubit', wires=2) >>> @qml.qnode(dev) ... def circuit(phi): ... qml.X(1) ... qml.FermionicSWAP(phi, wires=[0, 1]) ... return qml.state() >>> circuit(0.1) array([0.+0.j, 0.9975+0.04992j, 0.0025-0.04992j, 0.+0.j]) """num_wires=2"""int: Number of wires that the operator acts on."""num_params=1"""int: Number of trainable parameters that the operator depends on."""ndim_params=(0,)"""tuple[int]: Number of dimensions per trainable parameter that the operator depends on."""grad_method="A""""Gradient computation method."""parameter_frequencies=[(1,)]"""Frequencies of the operation parameter with respect to an expectation value."""resource_keys=set()@propertydefresource_params(self)->dict:return{}
[docs]@staticmethoddefcompute_matrix(phi:TensorLike)->TensorLike:# pylint: disable=arguments-differr"""Representation of the operator as a canonical matrix in the computational basis (static method). The canonical matrix is the textbook matrix representation that does not consider wires. Implicitly, this assumes that the wires of the operator correspond to the global wire order. .. seealso:: :meth:`~.FermionicSWAP.matrix` Args: phi (tensor_like or float): rotation angle Returns: tensor_like: canonical matrix **Example** >>> qml.FermionicSWAP.compute_matrix(torch.tensor(0.5)) tensor([1. +0.j, 0. +0.j , 0. +0.j , 0. +0.j ], [0. +0.j, 0.939+0.24j, 0.061-0.24j, 0. +0.j ], [0. +0.j, 0.061-0.24j, 0.939+0.24j, 0. +0.j ], [0. +0.j, 0. +0.j , 0. +0.j , 0.878+0.479j]]) """ifqml.math.get_interface(phi)=="tensorflow":phi=qml.math.cast_like(phi,1j)c=qml.math.cast_like(qml.math.cos(phi/2),1j)s=qml.math.cast_like(qml.math.sin(phi/2),1j)g=qml.math.cast_like(qml.math.exp(1j*phi/2),1j)p=qml.math.cast_like(qml.math.exp(1j*phi),1j)zeros=qml.math.zeros_like(phi)ones=qml.math.ones_like(phi)rows=[[ones,zeros,zeros,zeros],[zeros,g*c,-1j*g*s,zeros],[zeros,-1j*g*s,g*c,zeros],[zeros,zeros,zeros,p],]returnqml.math.stack([stack_last(row)forrowinrows],axis=-2)
[docs]@staticmethoddefcompute_decomposition(phi:TensorLike,wires:WiresLike)->list["qml.operation.Operator"]:r"""Representation of the operator as a product of other operators (static method). : .. math:: O = O_1 O_2 \dots O_n. .. seealso:: :meth:`~.FermionicSWAP.decomposition`. Args: phi (float): rotation angle :math:`\phi` wires (Iterable, Wires): wires that the operator acts on Returns: list[Operator]: decomposition into lower level operations **Example:** >>> qml.FermionicSWAP.compute_decomposition(0.2, wires=(0, 1)) [H(0), H(1), MultiRZ(0.1, wires=[0, 1]), H(0), H(1), RX(1.5707963267948966, wires=[0]), RX(1.5707963267948966, wires=[1]), MultiRZ(0.1, wires=[0, 1]), RX(-1.5707963267948966, wires=[0]), RX(-1.5707963267948966, wires=[1]), RZ(0.1, wires=[0]), RZ(0.1, wires=[1]), Exp(0.1j Identity)] """decomp_ops=[qml.Hadamard(wires=wires[0]),qml.Hadamard(wires=wires[1]),qml.MultiRZ(phi/2,wires=[wires[0],wires[1]]),qml.Hadamard(wires=wires[0]),qml.Hadamard(wires=wires[1]),qml.RX(np.pi/2,wires=wires[0]),qml.RX(np.pi/2,wires=wires[1]),qml.MultiRZ(phi/2,wires=[wires[0],wires[1]]),qml.RX(-np.pi/2,wires=wires[0]),qml.RX(-np.pi/2,wires=wires[1]),qml.RZ(phi/2,wires=wires[0]),qml.RZ(phi/2,wires=wires[1]),# for correcting global phaseqml.exp(qml.Identity(wires=[wires[0],wires[1]]),0.5j*phi),]returndecomp_ops
def_fermionic_swap_decomp_resources():return{qml.Hadamard:4,qml.resource_rep(qml.MultiRZ,num_wires=2):2,qml.RX:4,qml.RZ:2,qml.GlobalPhase:1,}@register_resources(_fermionic_swap_decomp_resources)def_fermionic_swap_decomp(phi,wires:WiresLike,**__):qml.Hadamard(wires=wires[0])qml.Hadamard(wires=wires[1])qml.MultiRZ(phi/2,wires=[wires[0],wires[1]])qml.Hadamard(wires=wires[0])qml.Hadamard(wires=wires[1])qml.RX(np.pi/2,wires=wires[0])qml.RX(np.pi/2,wires=wires[1])qml.MultiRZ(phi/2,wires=[wires[0],wires[1]])qml.RX(-np.pi/2,wires=wires[0])qml.RX(-np.pi/2,wires=wires[1])qml.RZ(phi/2,wires=wires[0])qml.RZ(phi/2,wires=wires[1])# for correcting global phaseqml.GlobalPhase(-0.5*phi,wires=[wires[0],wires[1]])add_decomps(FermionicSWAP,_fermionic_swap_decomp)