Source code for pennylane.templates.subroutines.basis_rotation
# Copyright 2018-2023 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 the template for performing basis transformation defined by a set of fermionic ladder operators.
"""
import numpy as np
import pennylane as qml
from pennylane.operation import AnyWires, Operation
from pennylane.qchem.givens_decomposition import givens_decomposition
# pylint: disable-msg=too-many-arguments
[docs]class BasisRotation(Operation):
r"""Implement a circuit that provides a unitary that can be used to do an exact single-body basis rotation.
The :class:`~.pennylane.BasisRotation` template performs the following unitary transformation :math:`U(u)` determined by the single-particle fermionic
generators as given in `arXiv:1711.04789 <https://arxiv.org/abs/1711.04789>`_\ :
.. math::
U(u) = \exp{\left( \sum_{pq} \left[\log u \right]_{pq} (a_p^\dagger a_q - a_q^\dagger a_p) \right)}.
The unitary :math:`U(u)` is implemented efficiently by performing its Givens decomposition into a sequence of
:class:`~.PhaseShift` and :class:`~.SingleExcitation` gates using the construction scheme given in
`Optica, 3, 1460 (2016) <https://opg.optica.org/optica/fulltext.cfm?uri=optica-3-12-1460&id=355743>`_\ .
Args:
wires (Iterable[Any]): wires that the operator acts on
unitary_matrix (array): matrix specifying the basis transformation
check (bool): test unitarity of the provided `unitary_matrix`
Raises:
ValueError: if the provided matrix is not square.
ValueError: if length of the wires is less than two.
.. details::
:title: Usage Details
:href: usage-basis-rotation
The :class:`~.pennylane.BasisRotation` template can be used to implement the evolution :math:`e^{iH}` where
:math:`H = \sum_{pq} V_{pq} a^\dagger_p a_q` and :math:`V` is an :math:`N \times N` Hermitian matrix.
When the unitary matrix :math:`u` is the transformation matrix that diagonalizes :math:`V`, the evolution is:
.. math::
e^{i \sum_{pq} V_{pq} a^\dagger_p a_q} = U(u)^\dagger \prod_k e^{i\lambda_k \sigma_z^k} U(u),
where :math:`\lambda_k` denotes the eigenvalues of matrix :math:`V`, the Hamiltonian coefficients matrix.
>>> V = np.array([[ 0.53672126+0.j , -0.1126064 -2.41479668j],
... [-0.1126064 +2.41479668j, 1.48694623+0.j ]])
>>> eigen_vals, eigen_vecs = np.linalg.eigh(V)
>>> umat = eigen_vecs.T
>>> wires = range(len(umat))
>>> def circuit():
... qml.adjoint(qml.BasisRotation(wires=wires, unitary_matrix=umat))
... for idx, eigenval in enumerate(eigen_vals):
... qml.RZ(eigenval, wires=[idx])
... qml.BasisRotation(wires=wires, unitary_matrix=umat)
>>> circ_unitary = qml.matrix(circuit)()
>>> np.round(circ_unitary/circ_unitary[0][0], 3)
tensor([[ 1. -0.j , -0. +0.j , -0. +0.j , -0. +0.j ],
[-0. +0.j , -0.516-0.596j, -0.302-0.536j, -0. +0.j ],
[-0. +0.j , 0.35 +0.506j, -0.311-0.724j, -0. +0.j ],
[-0. +0.j , -0. +0.j , -0. +0.j , -0.438+0.899j]], requires_grad=True)
.. details::
:title: Theory
:href: theory-basis-rotation
The overall effect of :math:`U(u)` can be viewed as performing a transformation from one basis to a new basis
that is defined by the linear combination of fermionic ladder operators:
.. math::
U(u) a_p^\dagger U(u)^\dagger = b_p^\dagger,
where :math:`a_p^\dagger` and :math:`b_p^\dagger` are the original and transformed creation operators, respectively.
The operators :math:`a_p^\dagger` and :math:`b_p^\dagger` are related to each other by the following equation:
.. math::
b_p^\dagger = \sum_{q}u_{pq} a_p^\dagger.
"""
num_wires = AnyWires
grad_method = None
@classmethod
def _primitive_bind_call(cls, wires, unitary_matrix, check=False, id=None):
# pylint: disable=arguments-differ
if cls._primitive is None:
# guard against this being called when primitive is not defined.
return type.__call__(cls, wires, unitary_matrix, check=check, id=id) # pragma: no cover
return cls._primitive.bind(*wires, unitary_matrix, check=check, id=id)
def __init__(self, wires, unitary_matrix, check=False, id=None):
M, N = unitary_matrix.shape
if M != N:
raise ValueError(
f"The unitary matrix should be of shape NxN, got {unitary_matrix.shape}"
)
if check:
umat = qml.math.toarray(unitary_matrix)
if not np.allclose(umat @ umat.conj().T, np.eye(M, dtype=complex), atol=1e-6):
raise ValueError("The provided transformation matrix should be unitary.")
if len(wires) < 2:
raise ValueError(f"This template requires at least two wires, got {len(wires)}")
self._hyperparameters = {
"unitary_matrix": unitary_matrix,
}
super().__init__(wires=wires, id=id)
@property
def num_params(self):
return 0
[docs] @staticmethod
def compute_decomposition(
wires, unitary_matrix, check=False
): # pylint: disable=arguments-differ
r"""Representation of the operator as a product of other operators.
.. math:: O = O_1 O_2 \dots O_n.
.. seealso:: :meth:`~.BasisRotation.decomposition`.
Args:
wires (Any or Iterable[Any]): wires that the operator acts on
unitary_matrix (array): matrix specifying the basis transformation
check (bool): test unitarity of the provided `unitary_matrix`
Returns:
list[.Operator]: decomposition of the operator
"""
M, N = unitary_matrix.shape
if M != N:
raise ValueError(
f"The unitary matrix should be of shape NxN, got {unitary_matrix.shape}"
)
if check:
umat = qml.math.toarray(unitary_matrix)
if not np.allclose(umat @ umat.conj().T, np.eye(M, dtype=complex), atol=1e-4):
raise ValueError("The provided transformation matrix should be unitary.")
if len(wires) < 2:
raise ValueError(f"This template requires at least two wires, got {len(wires)}")
op_list = []
phase_list, givens_list = givens_decomposition(unitary_matrix)
for idx, phase in enumerate(phase_list):
op_list.append(qml.PhaseShift(np.angle(phase), wires=wires[idx]))
for grot_mat, indices in givens_list:
theta = np.arccos(np.real(grot_mat[1, 1]))
phi = np.angle(grot_mat[0, 0])
op_list.append(
qml.SingleExcitation(2 * theta, wires=[wires[indices[0]], wires[indices[1]]])
)
if not np.isclose(phi, 0.0):
op_list.append(qml.PhaseShift(phi, wires=wires[indices[0]]))
return op_list
# Program capture needs to unpack and re-pack the wires to support dynamic wires. For
# BasisRotation, the unconventional argument ordering requires custom def_impl code.
# See capture module for more information on primitives
# If None, jax isn't installed so the class never got a primitive.
if BasisRotation._primitive is not None: # pylint: disable=protected-access
@BasisRotation._primitive.def_impl # pylint: disable=protected-access
def _(*args, **kwargs):
# If there are more than two args, we are calling with unpacked wires, so that
# we have to repack them. This replaces the n_wires logic in the general case.
if len(args) != 2:
args = (args[:-1], args[-1])
return type.__call__(BasisRotation, *args, **kwargs)
_modules/pennylane/templates/subroutines/basis_rotation
Download Python script
Download Notebook
View on GitHub