Source code for pennylane.ops.cv
# 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.
# pylint: disable=too-many-arguments
r"""
This module contains the available built-in continuous-variable
quantum operations supported by PennyLane, as well as their conventions.
.. todo:: Add gradient recipes for Gaussian state preparations
.. todo::
The gradient computation assumes all parameters are real (floats), some
docstrings here allow complex or even array parameter values. This includes
:class:`~.DisplacedSqueezedState` and :class:`~.CatState`.
Possible solution: disallow such operations to depend on free parameters,
this way they won't be differentiated.
.. note::
For the Heisenberg matrix representation of CV operations, we use the ordering
:math:`(\hat{\mathbb{1}}, \hat{x}, \hat{p})` for single modes
and :math:`(\hat{\mathbb{1}}, \hat{x}_1, \hat{p}_2, \hat{x}_1,\hat{p}_2)` for two modes .
"""
# As the qubit based ``decomposition``, ``_matrix``, ``diagonalizing_gates``
# abstract methods are not defined in the CV case, disabling the related check
# pylint: disable=abstract-method
import math
import numpy as np
from scipy.linalg import block_diag
from pennylane import math as qml_math
from pennylane.operation import AnyWires, CVObservable, CVOperation
from .identity import I, Identity # pylint: disable=unused-import
from .meta import Snapshot # pylint: disable=unused-import
_two_term_shift_rule = [[0.5, 1, np.pi / 2], [-0.5, 1, -np.pi / 2]]
def _rotation(phi, bare=False):
r"""Utility function, returns the Heisenberg transformation of a phase rotation gate.
The transformation matrix returned is:
.. math:: M = \begin{bmatrix}
1 & 0 & 0\\
0 & \cos\phi & -\sin\phi\\
0 & \sin\phi & \cos\phi
\end{bmatrix}
Args:
phi (float): rotation angle.
bare (bool): if True, return a simple 2d rotation matrix
Returns:
array[float]: transformation matrix
"""
c = math.cos(phi)
s = math.sin(phi)
temp = np.array([[c, -s], [s, c]])
if bare:
return temp
return block_diag(1, temp) # pylint: disable=no-member
[docs]class Rotation(CVOperation):
r"""
Phase space rotation.
.. math::
R(\phi) = \exp\left(i \phi \ad \a\right)=\exp\left(i \frac{\phi}{2}
\left(\frac{\x^2+ \p^2}{\hbar}-\I\right)\right).
**Details:**
* Number of wires: 1
* Number of parameters: 1
* Gradient recipe: :math:`\frac{d}{dr}f(R(r)) = \frac{1}{2} \left[f(R(\phi+\pi/2)) - f(R(\phi-\pi/2))\right]`
where :math:`f` is an expectation value depending on :math:`R(r)`.
* Heisenberg representation:
.. math:: M = \begin{bmatrix}
1 & 0 & 0\\
0 & \cos\phi & -\sin\phi\\
0 & \sin\phi & \cos\phi
\end{bmatrix}
Args:
phi (float): the rotation angle
wires (Sequence[Any] or Any): the wire the operation acts on
id (str or None): String representing the operation (optional)
"""
num_params = 1
num_wires = 1
grad_method = "A"
grad_recipe = (_two_term_shift_rule,)
def __init__(self, phi, wires, id=None):
super().__init__(phi, wires=wires, id=id)
@staticmethod
def _heisenberg_rep(p):
return _rotation(p[0])
[docs] def label(self, decimals=None, base_label=None, cache=None):
return super().label(decimals=decimals, base_label=base_label or "R", cache=cache)
[docs]class Squeezing(CVOperation):
r"""
Phase space squeezing.
.. math::
S(z) = \exp\left(\frac{1}{2}(z^* \a^2 -z {\a^\dagger}^2)\right).
where :math:`z = r e^{i\phi}`.
**Details:**
* Number of wires: 1
* Number of parameters: 2
* Gradient recipe: :math:`\frac{d}{dr}f(S(r,\phi)) = \frac{1}{2\sinh s} \left[f(S(r+s, \phi)) - f(S(r-s, \phi))\right]`,
where :math:`s` is an arbitrary real number (:math:`0.1` by default) and
:math:`f` is an expectation value depending on :math:`S(r,\phi)`.
* Heisenberg representation:
.. math:: M = \begin{bmatrix}
1 & 0 & 0 \\
0 & \cosh r - \cos\phi \sinh r & -\sin\phi\sinh r \\
0 & -\sin\phi\sinh r & \cosh r+\cos\phi\sinh r
\end{bmatrix}
Args:
r (float): squeezing amount
phi (float): squeezing phase angle :math:`\phi`
wires (Sequence[Any] or Any): the wire the operation acts on
id (str or None): String representing the operation (optional)
"""
num_params = 2
num_wires = 1
grad_method = "A"
shift = 0.1
multiplier = 0.5 / math.sinh(shift)
a = 1
grad_recipe = ([[multiplier, a, shift], [-multiplier, a, -shift]], _two_term_shift_rule)
def __init__(self, r, phi, wires, id=None):
super().__init__(r, phi, wires=wires, id=id)
@staticmethod
def _heisenberg_rep(p):
R = _rotation(p[1] / 2)
return R @ np.diag([1, math.exp(-p[0]), math.exp(p[0])]) @ R.T
[docs] def adjoint(self):
r, phi = self.parameters
new_phi = (phi + np.pi) % (2 * np.pi)
return Squeezing(r, new_phi, wires=self.wires)
[docs] def label(self, decimals=None, base_label=None, cache=None):
return super().label(decimals=decimals, base_label=base_label or "S", cache=cache)
[docs]class Displacement(CVOperation):
r"""
Phase space displacement.
.. math::
D(a,\phi) = D(\alpha) = \exp(\alpha \ad -\alpha^* \a)
= \exp\left(-i\sqrt{\frac{2}{\hbar}}(\re(\alpha) \hat{p} -\im(\alpha) \hat{x})\right).
where :math:`\alpha = ae^{i\phi}` has magnitude :math:`a\geq 0` and phase :math:`\phi`.
The result of applying a displacement to the vacuum is a coherent state
:math:`D(\alpha)\ket{0} = \ket{\alpha}`.
**Details:**
* Number of wires: 1
* Number of parameters: 2
* Gradient recipe: :math:`\frac{d}{da}f(D(a,\phi)) = \frac{1}{2s} \left[f(D(a+s, \phi)) - f(D(a-s, \phi))\right]`,
where :math:`s` is an arbitrary real number (:math:`0.1` by default) and
:math:`f` is an expectation value depending on :math:`D(a,\phi)`.
* Heisenberg representation:
.. math:: M = \begin{bmatrix} 1 & 0 & 0 \\ 2a\cos\phi & 1 & 0 \\ 2a\sin\phi & 0 & 1\end{bmatrix}
Args:
a (float): displacement magnitude :math:`a=|\alpha|`
phi (float): phase angle :math:`\phi`
wires (Sequence[Any] or Any): the wire the operation acts on
id (str or None): String representing the operation (optional)
"""
num_params = 2
num_wires = 1
grad_method = "A"
shift = 0.1
multiplier = 0.5 / shift
a = 1
grad_recipe = ([[multiplier, a, shift], [-multiplier, a, -shift]], _two_term_shift_rule)
def __init__(self, a, phi, wires, id=None):
super().__init__(a, phi, wires=wires, id=id)
@staticmethod
def _heisenberg_rep(p):
c = math.cos(p[1])
s = math.sin(p[1])
scale = 2 # sqrt(2 * hbar)
return np.array([[1, 0, 0], [scale * c * p[0], 1, 0], [scale * s * p[0], 0, 1]])
[docs] def adjoint(self):
a, phi = self.parameters
new_phi = (phi + np.pi) % (2 * np.pi)
return Displacement(a, new_phi, wires=self.wires)
[docs] def label(self, decimals=None, base_label=None, cache=None):
return super().label(decimals=decimals, base_label=base_label or "D", cache=cache)
[docs]class Beamsplitter(CVOperation):
r"""
Beamsplitter interaction.
.. math::
B(\theta,\phi) = \exp\left(\theta (e^{i \phi} \a \hat{b}^\dagger -e^{-i \phi}\ad \hat{b}) \right).
**Details:**
* Number of wires: 2
* Number of parameters: 2
* Gradient recipe: :math:`\frac{d}{d \theta}f(B(\theta,\phi)) = \frac{1}{2} \left[f(B(\theta+\pi/2, \phi)) - f(B(\theta-\pi/2, \phi))\right]`
where :math:`f` is an expectation value depending on :math:`B(\theta,\phi)`.
* Heisenberg representation:
.. math:: M = \begin{bmatrix}
1 & 0 & 0 & 0 & 0\\
0 & \cos\theta & 0 & -\cos\phi\sin\theta & -\sin\phi\sin\theta \\
0 & 0 & \cos\theta & \sin\phi\sin\theta & -\cos\phi\sin\theta\\
0 & \cos\phi\sin\theta & -\sin\phi\sin\theta & \cos\theta & 0\\
0 & \sin\phi\sin\theta & \cos\phi\sin\theta & 0 & \cos\theta
\end{bmatrix}
Args:
theta (float): Transmittivity angle :math:`\theta`. The transmission amplitude
of the beamsplitter is :math:`t = \cos(\theta)`.
The value :math:`\theta=\pi/4` gives the 50-50 beamsplitter.
phi (float): Phase angle :math:`\phi`. The reflection amplitude of the
beamsplitter is :math:`r = e^{i\phi}\sin(\theta)`.
The value :math:`\phi = \pi/2` gives the symmetric beamsplitter.
wires (Sequence[Any]): the wire the operation acts on
id (str or None): String representing the operation (optional)
"""
num_params = 2
num_wires = 2
grad_method = "A"
grad_recipe = (_two_term_shift_rule, _two_term_shift_rule)
def __init__(self, theta, phi, wires, id=None):
super().__init__(theta, phi, wires=wires, id=id)
# For the beamsplitter, both parameters are rotation-like
@staticmethod
def _heisenberg_rep(p):
R = _rotation(p[1], bare=True)
c = math.cos(p[0])
s = math.sin(p[0])
U = c * np.eye(5)
U[0, 0] = 1
U[1:3, 3:5] = -s * R.T
U[3:5, 1:3] = s * R
return U
[docs] def adjoint(self):
theta, phi = self.parameters
return Beamsplitter(-theta, phi, wires=self.wires)
[docs] def label(self, decimals=None, base_label=None, cache=None):
return super().label(decimals=decimals, base_label=base_label or "BS", cache=cache)
[docs]class TwoModeSqueezing(CVOperation):
r"""
Phase space two-mode squeezing.
.. math::
S_2(z) = \exp\left(z^* \a \hat{b} -z \ad \hat{b}^\dagger \right)
= \exp\left(r (e^{-i\phi} \a\hat{b} -e^{i\phi} \ad \hat{b}^\dagger \right).
where :math:`z = r e^{i\phi}`.
**Details:**
* Number of wires: 2
* Number of parameters: 2
* Gradient recipe: :math:`\frac{d}{dr}f(S_2(r,\phi)) = \frac{1}{2\sinh s} \left[f(S_2(r+s, \phi)) - f(S_2(r-s, \phi))\right]`,
where :math:`s` is an arbitrary real number (:math:`0.1` by default) and
:math:`f` is an expectation value depending on :math:`S_2(r,\phi)`.
* Heisenberg representation:
.. math:: M = \begin{bmatrix}
1 & 0 & 0 & 0 & 0 \\
0 & \cosh r & 0 & \sinh r \cos \phi & \sinh r \sin \phi\\
0 & 0 & \cosh r & \sinh r \sin \phi & -\sinh r \cos \phi\\
0 & \sinh r \cos \phi & \sinh r \sin \phi & \cosh r & 0\\
0 & \sinh r \sin \phi & -\sinh r \cos \phi & 0 & \cosh r
\end{bmatrix}
Args:
r (float): squeezing amount
phi (float): squeezing phase angle :math:`\phi`
wires (Sequence[Any]): the wire the operation acts on
id (str or None): String representing the operation (optional)
"""
num_params = 2
num_wires = 2
grad_method = "A"
shift = 0.1
multiplier = 0.5 / math.sinh(shift)
a = 1
grad_recipe = ([[multiplier, a, shift], [-multiplier, a, -shift]], _two_term_shift_rule)
def __init__(self, r, phi, wires, id=None):
super().__init__(r, phi, wires=wires, id=id)
@staticmethod
def _heisenberg_rep(p):
R = _rotation(p[1], bare=True)
S = math.sinh(p[0]) * np.diag([1, -1])
U = math.cosh(p[0]) * np.identity(5)
U[0, 0] = 1
U[1:3, 3:5] = S @ R.T
U[3:5, 1:3] = S @ R.T
return U
[docs] def adjoint(self):
r, phi = self.parameters
new_phi = (phi + np.pi) % (2 * np.pi)
return TwoModeSqueezing(r, new_phi, wires=self.wires)
[docs] def label(self, decimals=None, base_label=None, cache=None):
return super().label(decimals=decimals, base_label=base_label or "S", cache=cache)
[docs]class QuadraticPhase(CVOperation):
r"""
Quadratic phase shift.
.. math::
P(s) = e^{i \frac{s}{2} \hat{x}^2/\hbar}.
**Details:**
* Number of wires: 1
* Number of parameters: 1
* Gradient recipe: :math:`\frac{d}{ds}f(P(s)) = \frac{1}{2 a} \left[f(P(s+a)) - f(P(s-a))\right]`,
where :math:`a` is an arbitrary real number (:math:`0.1` by default) and
:math:`f` is an expectation value depending on :math:`P(s)`.
* Heisenberg representation:
.. math:: M = \begin{bmatrix}
1 & 0 & 0 \\
0 & 1 & 0 \\
0 & s & 1 \\
\end{bmatrix}
Args:
s (float): parameter
wires (Sequence[Any] or Any): the wire the operation acts on
id (str or None): String representing the operation (optional)
"""
num_params = 1
num_wires = 1
grad_method = "A"
shift = 0.1
multiplier = 0.5 / shift
a = 1
grad_recipe = ([[multiplier, a, shift], [-multiplier, a, -shift]],)
def __init__(self, s, wires, id=None):
super().__init__(s, wires=wires, id=id)
@staticmethod
def _heisenberg_rep(p):
U = np.identity(3)
U[2, 1] = p[0]
return U
[docs] def label(self, decimals=None, base_label=None, cache=None):
return super().label(decimals=decimals, base_label=base_label or "P", cache=cache)
[docs]class ControlledAddition(CVOperation):
r"""
Controlled addition operation.
.. math::
\text{CX}(s) = \int dx \ket{x}\bra{x} \otimes D\left({\frac{1}{\sqrt{2\hbar}}}s x\right)
= e^{-i s \: \hat{x} \otimes \hat{p}/\hbar}.
**Details:**
* Number of wires: 2
* Number of parameters: 1
* Gradient recipe: :math:`\frac{d}{ds}f(\text{CX}(s)) = \frac{1}{2 a} \left[f(\text{CX}(s+a)) - f(\text{CX}(s-a))\right]`,
where :math:`a` is an arbitrary real number (:math:`0.1` by default) and
:math:`f` is an expectation value depending on :math:`\text{CX}(s)`.
* Heisenberg representation:
.. math:: M = \begin{bmatrix}
1 & 0 & 0 & 0 & 0 \\
0 & 1 & 0 & 0 & 0 \\
0 & 0 & 1 & 0 & -s \\
0 & s & 0 & 1 & 0 \\
0 & 0 & 0 & 0 & 1
\end{bmatrix}
Args:
s (float): addition multiplier
wires (Sequence[Any]): the wire the operation acts on
id (str or None): String representing the operation (optional)
"""
num_params = 1
num_wires = 2
grad_method = "A"
shift = 0.1
multiplier = 0.5 / shift
a = 1
grad_recipe = ([[multiplier, a, shift], [-multiplier, a, -shift]],)
def __init__(self, s, wires, id=None):
super().__init__(s, wires=wires, id=id)
@staticmethod
def _heisenberg_rep(p):
U = np.identity(5)
U[2, 4] = -p[0]
U[3, 1] = p[0]
return U
[docs] def label(self, decimals=None, base_label=None, cache=None):
return super().label(decimals=decimals, base_label=base_label or "X", cache=cache)
[docs]class ControlledPhase(CVOperation):
r"""
Controlled phase operation.
.. math::
\text{CZ}(s) = \iint dx dy \: e^{i sxy/\hbar} \ket{x,y}\bra{x,y}
= e^{i s \: \hat{x} \otimes \hat{x}/\hbar}.
**Details:**
* Number of wires: 2
* Number of parameters: 1
* Gradient recipe: :math:`\frac{d}{ds}f(\text{CZ}(s)) = \frac{1}{2 a} \left[f(\text{CZ}(s+a)) - f(\text{CZ}(s-a))\right]`,
where :math:`a` is an arbitrary real number (:math:`0.1` by default) and
:math:`f` is an expectation value depending on :math:`\text{CZ}(s)`.
* Heisenberg representation:
.. math:: M = \begin{bmatrix}
1 & 0 & 0 & 0 & 0 \\
0 & 1 & 0 & 0 & 0 \\
0 & 0 & 1 & s & 0 \\
0 & 0 & 0 & 1 & 0 \\
0 & s & 0 & 0 & 1
\end{bmatrix}
Args:
s (float): phase shift multiplier
wires (Sequence[Any]): the wire the operation acts on
id (str or None): String representing the operation (optional)
"""
num_params = 1
num_wires = 2
grad_method = "A"
shift = 0.1
multiplier = 0.5 / shift
a = 1
grad_recipe = ([[multiplier, a, shift], [-multiplier, a, -shift]],)
def __init__(self, s, wires, id=None):
super().__init__(s, wires=wires, id=id)
@staticmethod
def _heisenberg_rep(p):
U = np.identity(5)
U[2, 3] = p[0]
U[4, 1] = p[0]
return U
[docs] def label(self, decimals=None, base_label=None, cache=None):
return super().label(decimals=decimals, base_label=base_label or "Z", cache=cache)
[docs]class Kerr(CVOperation):
r"""
Kerr interaction.
.. math::
K(\kappa) = e^{i \kappa \hat{n}^2}.
**Details:**
* Number of wires: 1
* Number of parameters: 1
* Gradient recipe: None (uses finite difference)
Args:
kappa (float): parameter
wires (Sequence[Any] or Any): the wire the operation acts on
id (str or None): String representing the operation (optional)
"""
num_params = 1
num_wires = 1
grad_method = "F"
def __init__(self, kappa, wires, id=None):
super().__init__(kappa, wires=wires, id=id)
[docs]class CrossKerr(CVOperation):
r"""
Cross-Kerr interaction.
.. math::
CK(\kappa) = e^{i \kappa \hat{n}_1\hat{n}_2}.
**Details:**
* Number of wires: 2
* Number of parameters: 1
* Gradient recipe: None (uses finite difference)
Args:
kappa (float): parameter
wires (Sequence[Any]): the wire the operation acts on
id (str or None): String representing the operation (optional)
"""
num_params = 1
num_wires = 2
grad_method = "F"
def __init__(self, kappa, wires, id=None):
super().__init__(kappa, wires=wires, id=id)
[docs]class CubicPhase(CVOperation):
r"""
Cubic phase shift.
.. math::
V(\gamma) = e^{i \frac{\gamma}{3} \hat{x}^3/\hbar}.
**Details:**
* Number of wires: 1
* Number of parameters: 1
* Gradient recipe: None (uses finite difference)
Args:
gamma (float): parameter
wires (Sequence[Any] or Any): the wire the operation acts on
id (str or None): String representing the operation (optional)
"""
num_params = 1
num_wires = 1
grad_method = "F"
def __init__(self, gamma, wires, id=None):
super().__init__(gamma, wires=wires, id=id)
[docs] def label(self, decimals=None, base_label=None, cache=None):
return super().label(decimals=decimals, base_label=base_label or "V", cache=cache)
[docs]class InterferometerUnitary(CVOperation):
r"""
A linear interferometer transforming the bosonic operators according to
the unitary matrix :math:`U`.
.. note::
This operation implements a **fixed** linear interferometer given a known
unitary matrix.
If you instead wish to parametrize the interferometer,
and calculate the gradient/optimize with respect to these parameters,
consider instead the :func:`pennylane.template.Interferometer` template,
which constructs an interferometer from a combination of beamsplitters
and rotation gates.
**Details:**
* Number of wires: Any
* Number of parameters: 1
* Gradient recipe: None
* Heisenberg representation:
.. math:: M = \begin{bmatrix}
1 & 0\\
0 & S\\
\end{bmatrix}
where :math:`S` is the Gaussian symplectic transformation representing the interferometer.
Args:
U (array): A shape ``(len(wires), len(wires))`` complex unitary matrix
wires (Sequence[Any] or Any): the wires the operation acts on
id (str or None): String representing the operation (optional)
"""
num_params = 1
num_wires = AnyWires
grad_method = None
grad_recipe = None
def __init__(self, U, wires, id=None):
super().__init__(U, wires=wires, id=id)
@staticmethod
def _heisenberg_rep(p):
N = len(p[0])
A = p[0].real
B = p[0].imag
rows = np.arange(2 * N).reshape(2, -1).T.flatten()
S = np.vstack([np.hstack([A, -B]), np.hstack([B, A])])[:, rows][rows]
M = np.eye(2 * N + 1)
M[1 : 2 * N + 1, 1 : 2 * N + 1] = S
return M
[docs] def adjoint(self):
U = self.parameters[0]
return InterferometerUnitary(qml_math.T(qml_math.conj(U)), wires=self.wires)
[docs] def label(self, decimals=None, base_label=None, cache=None):
return super().label(decimals=decimals, base_label=base_label or "U", cache=cache)
# =============================================================================
# State preparation
# =============================================================================
# TODO: put Heisenberg reps of state preparations in docstrings?
[docs]class CoherentState(CVOperation):
r"""
Prepares a coherent state.
**Details:**
* Number of wires: 1
* Number of parameters: 2
* Gradient recipe: None (uses finite difference)
Args:
a (float): displacement magnitude :math:`r=|\alpha|`
phi (float): phase angle :math:`\phi`
wires (Sequence[Any] or Any): the wire the operation acts on
id (str or None): String representing the operation (optional)
"""
num_params = 2
num_wires = 1
grad_method = "F"
def __init__(self, a, phi, wires, id=None):
super().__init__(a, phi, wires=wires, id=id)
[docs]class SqueezedState(CVOperation):
r"""
Prepares a squeezed vacuum state.
**Details:**
* Number of wires: 1
* Number of parameters: 2
* Gradient recipe: None (uses finite difference)
Args:
r (float): squeezing magnitude
phi (float): squeezing angle :math:`\phi`
wires (Sequence[Any] or Any): the wire the operation acts on
id (str or None): String representing the operation (optional)
"""
num_params = 2
num_wires = 1
grad_method = "F"
def __init__(self, r, phi, wires, id=None):
super().__init__(r, phi, wires=wires, id=id)
[docs]class DisplacedSqueezedState(CVOperation):
r"""
Prepares a displaced squeezed vacuum state.
A displaced squeezed state is prepared by squeezing a vacuum state, and
then applying a displacement operator,
.. math::
\ket{\alpha,z} = D(\alpha)\ket{0,z} = D(\alpha)S(z)\ket{0},
with the displacement parameter :math:`\alpha=ae^{i\phi_a}` and the squeezing parameter :math:`z=re^{i\phi_r}`.
**Details:**
* Number of wires: 1
* Number of parameters: 4
* Gradient recipe: None (uses finite difference)
Args:
a (float): displacement magnitude :math:`a=|\alpha|`
phi_a (float): displacement angle :math:`\phi_a`
r (float): squeezing magnitude :math:`r=|z|`
phi_r (float): squeezing angle :math:`\phi_r`
wires (Sequence[Any] or Any): the wire the operation acts on
id (str or None): String representing the operation (optional)
"""
num_params = 4
num_wires = 1
grad_method = "F"
def __init__(self, a, phi_a, r, phi_r, wires, id=None):
super().__init__(a, phi_a, r, phi_r, wires=wires, id=id)
[docs]class ThermalState(CVOperation):
r"""
Prepares a thermal state.
**Details:**
* Number of wires: 1
* Number of parameters: 1
* Gradient recipe: None (uses finite difference)
Args:
nbar (float): mean thermal population of the mode
wires (Sequence[Any] or Any): the wire the operation acts on
id (str or None): String representing the operation (optional)
"""
num_params = 1
num_wires = 1
grad_method = "F"
def __init__(self, nbar, wires, id=None):
super().__init__(nbar, wires=wires, id=id)
[docs] def label(self, decimals=None, base_label=None, cache=None):
return super().label(decimals=decimals, base_label=base_label or "Thermal", cache=cache)
[docs]class GaussianState(CVOperation):
r"""
Prepare subsystems in a given Gaussian state.
**Details:**
* Number of wires: Any
* Number of parameters: 2
* Gradient recipe: None
Args:
V (array): the :math:`2N\times 2N` (real and positive definite) covariance matrix
r (array): a length :math:`2N` vector of means, of the
form :math:`(\x_0,\dots,\x_{N-1},\p_0,\dots,\p_{N-1})`
wires (Sequence[Any] or Any): the wire the operation acts on
id (str or None): String representing the operation (optional)
"""
num_params = 2
num_wires = AnyWires
grad_method = "F"
def __init__(self, V, r, wires, id=None):
super().__init__(V, r, wires=wires, id=id)
[docs] def label(self, decimals=None, base_label=None, cache=None):
return super().label(decimals=decimals, base_label=base_label or "Gaussian", cache=cache)
[docs]class FockState(CVOperation):
r"""
Prepares a single Fock state.
**Details:**
* Number of wires: 1
* Number of parameters: 1
* Gradient recipe: None (not differentiable)
Args:
n (int): Fock state to prepare
wires (Sequence[Any] or Any): the wire the operation acts on
id (str or None): String representing the operation (optional)
"""
num_params = 1
num_wires = 1
grad_method = None
def __init__(self, n, wires, id=None):
super().__init__(n, wires=wires, id=id)
[docs] def label(self, decimals=None, base_label=None, cache=None):
r"""A customizable string representation of the operator.
Args:
decimals=None (int): If ``None``, no parameters are included. Else,
specifies how to round the parameters.
base_label=None (str): overwrite the non-parameter component of the label
cache=None (dict): dictionary that caries information between label calls
in the same drawing
Returns:
str: label to use in drawings
**Example:**
>>> qml.FockState(7, wires=0).label()
'|7⟩'
"""
if base_label is not None:
if decimals is None:
return base_label
p = format(qml_math.asarray(self.parameters[0]), ".0f")
return base_label + f"\n({p})"
return f"|{qml_math.asarray(self.parameters[0])}⟩"
[docs]class FockStateVector(CVOperation):
r"""
Prepare subsystems using the given ket vector in the Fock basis.
**Details:**
* Number of wires: Any
* Number of parameters: 1
* Gradient recipe: None (uses finite difference)
Args:
state (array): a single ket vector, for single mode state preparation,
or a multimode ket, with one array dimension per mode
wires (Sequence[Any] or Any): the wire the operation acts on
id (str or None): String representing the operation (optional)
.. details::
:title: Usage Details
For a single mode with cutoff dimension :math:`N`, the input is a
1-dimensional vector of length :math:`N`.
.. code-block::
dev_fock = qml.device("strawberryfields.fock", wires=4, cutoff_dim=4)
state = np.array([0, 0, 1, 0])
@qml.qnode(dev_fock)
def circuit():
qml.FockStateVector(state, wires=0)
return qml.expval(qml.NumberOperator(wires=0))
For multiple modes, the input is the tensor product of single mode
kets. For example, given a set of :math:`M` single mode vectors of
length :math:`N`, the input should have shape ``(N, ) * M``.
.. code-block::
used_wires = [0, 3]
cutoff_dim = 5
dev_fock = qml.device("strawberryfields.fock", wires=4, cutoff_dim=cutoff_dim)
state_1 = np.array([0, 1, 0, 0, 0])
state_2 = np.array([0, 0, 0, 1, 0])
combined_state = np.kron(state_1, state_2).reshape(
(cutoff_dim, ) * len(used_wires)
)
@qml.qnode(dev_fock)
def circuit():
qml.FockStateVector(combined_state, wires=used_wires)
return qml.expval(qml.NumberOperator(wires=0))
"""
num_params = 1
num_wires = AnyWires
grad_method = "F"
def __init__(self, state, wires, id=None):
super().__init__(state, wires=wires, id=id)
[docs] def label(self, decimals=None, base_label=None, cache=None):
r"""A customizable string representation of the operator.
Args:
decimals=None (int): If ``None``, no parameters are included. Else,
specifies how to round the parameters.
base_label=None (str): overwrite the non-parameter component of the label
cache=None (dict): dictionary that caries information between label calls
in the same drawing
Returns:
str: label to use in drawings
**Example:**
>>> qml.FockStateVector([1,2,3], wires=(0,1,2)).label()
'|123⟩'
"""
if base_label is not None:
return base_label
basis_string = "".join(str(int(i)) for i in self.parameters[0])
return f"|{basis_string}⟩"
[docs]class FockDensityMatrix(CVOperation):
r"""
Prepare subsystems using the given density matrix in the Fock basis.
**Details:**
* Number of wires: Any
* Number of parameters: 1
* Gradient recipe: None (uses finite difference)
Args:
state (array): a single mode matrix :math:`\rho_{ij}`, or
a multimode tensor :math:`\rho_{ij,kl,\dots,mn}`, with two indices per mode
wires (Sequence[Any] or Any): the wire the operation acts on
id (str or None): String representing the operation (optional)
"""
num_params = 1
num_wires = AnyWires
grad_method = "F"
def __init__(self, state, wires, id=None):
super().__init__(state, wires=wires, id=id)
[docs]class CatState(CVOperation):
r"""
Prepares a cat state.
A cat state is the coherent superposition of two coherent states,
.. math::
\ket{\text{cat}(\alpha)} = \frac{1}{N} (\ket{\alpha} +e^{ip\pi} \ket{-\alpha}),
where :math:`\ket{\pm\alpha} = D(\pm\alpha)\ket{0}` are coherent states with displacement
parameters :math:`\pm\alpha=\pm ae^{i\phi}` and
:math:`N = \sqrt{2 (1+\cos(p\pi)e^{-2|\alpha|^2})}` is the normalization factor.
**Details:**
* Number of wires: 1
* Number of parameters: 3
* Gradient recipe: None (uses finite difference)
Args:
a (float): displacement magnitude :math:`a=|\alpha|`
phi (float): displacement angle :math:`\phi`
p (float): parity, where :math:`p=0` corresponds to an even
cat state, and :math:`p=1` an odd cat state.
wires (Sequence[Any] or Any): the wire the operation acts on
id (str or None): String representing the operation (optional)
"""
num_params = 3
num_wires = 1
grad_method = "F"
def __init__(self, a, phi, p, wires, id=None):
super().__init__(a, phi, p, wires=wires, id=id)
# =============================================================================
# Observables
# =============================================================================
[docs]class NumberOperator(CVObservable):
r"""
The photon number observable :math:`\langle \hat{n}\rangle`.
The number operator is defined as
:math:`\hat{n} = \a^\dagger \a = \frac{1}{2\hbar}(\x^2 +\p^2) -\I/2`.
When used with the :func:`~pennylane.expval` function, the mean
photon number :math:`\braket{\hat{n}}` is returned.
**Details:**
* Number of wires: 1
* Number of parameters: 0
* Observable order: 2nd order in the quadrature operators
* Heisenberg representation:
.. math:: M = \frac{1}{2\hbar}\begin{bmatrix}
-\hbar & 0 & 0\\
0 & 1 & 0\\
0 & 0 & 1
\end{bmatrix}
Args:
wires (Sequence[Any] or Any): the wire the operation acts on
"""
num_params = 0
num_wires = 1
ev_order = 2
def __init__(self, wires):
super().__init__(wires=wires)
@staticmethod
def _heisenberg_rep(p):
hbar = 2
return np.diag([-0.5, 0.5 / hbar, 0.5 / hbar])
[docs]class TensorN(CVObservable):
r"""
The tensor product of the :class:`~.NumberOperator` acting on different wires.
If a single wire is defined, returns a :class:`~.NumberOperator` instance for convenient gradient computations.
When used with the :func:`~pennylane.expval` function, the expectation value
:math:`\langle \hat{n}_{i_0} \hat{n}_{i_1}\dots \hat{n}_{i_{N-1}}\rangle`
for a (sub)set of modes :math:`[i_0, i_1, \dots, i_{N-1}]` of the system is
returned.
**Details:**
* Number of wires: Any
* Number of parameters: 0
Args:
wires (Sequence[Any] or Any): the wire the operation acts on
.. details::
:title: Usage Details
Example for multiple modes:
>>> cv_obs = qml.TensorN(wires=[0, 1])
>>> cv_obs
TensorN(wires=[0, 1])
>>> cv_obs.ev_order is None
True
Example for a single mode (yields a :class:`~.NumberOperator`):
>>> cv_obs = qml.TensorN(wires=[1])
>>> cv_obs
NumberOperator(wires=[1])
>>> cv_obs.ev_order
2
"""
num_params = 0
num_wires = AnyWires
ev_order = None
def __init__(self, wires):
super().__init__(wires=wires)
def __new__(cls, wires=None):
# Custom definition for __new__ needed such that a NumberOperator can
# be returned when a single mode is defined
if wires is not None and (isinstance(wires, int) or len(wires) == 1):
return NumberOperator(wires=wires)
return super().__new__(cls)
[docs] def label(self, decimals=None, base_label=None, cache=None):
if base_label is not None:
return base_label
return "⊗".join("n" for _ in self.wires)
[docs]class QuadX(CVObservable):
r"""
The position quadrature observable :math:`\hat{x}`.
When used with the :func:`~pennylane.expval` function, the position expectation
value :math:`\braket{\hat{x}}` is returned. This corresponds to
the mean displacement in the phase space along the :math:`x` axis.
**Details:**
* Number of wires: 1
* Number of parameters: 0
* Observable order: 1st order in the quadrature operators
* Heisenberg representation:
.. math:: d = [0, 1, 0]
Args:
wires (Sequence[Any] or Any): the wire the operation acts on
"""
num_params = 0
num_wires = 1
ev_order = 1
def __init__(self, wires):
super().__init__(wires=wires)
@staticmethod
def _heisenberg_rep(p):
return np.array([0, 1, 0])
[docs]class QuadP(CVObservable):
r"""
The momentum quadrature observable :math:`\hat{p}`.
When used with the :func:`~pennylane.expval` function, the momentum expectation
value :math:`\braket{\hat{p}}` is returned. This corresponds to
the mean displacement in the phase space along the :math:`p` axis.
**Details:**
* Number of wires: 1
* Number of parameters: 0
* Observable order: 1st order in the quadrature operators
* Heisenberg representation:
.. math:: d = [0, 0, 1]
Args:
wires (Sequence[Any] or Any): the wire the operation acts on
"""
num_params = 0
num_wires = 1
ev_order = 1
def __init__(self, wires):
super().__init__(wires=wires)
@staticmethod
def _heisenberg_rep(p):
return np.array([0, 0, 1])
[docs]class QuadOperator(CVObservable):
r"""
The generalized quadrature observable :math:`\x_\phi = \x cos\phi+\p\sin\phi`.
When used with the :func:`~pennylane.expval` function, the expectation
value :math:`\braket{\hat{\x_\phi}}` is returned. This corresponds to
the mean displacement in the phase space along axis at angle :math:`\phi`.
**Details:**
* Number of wires: 1
* Number of parameters: 1
* Observable order: 1st order in the quadrature operators
* Heisenberg representation:
.. math:: d = [0, \cos\phi, \sin\phi]
Args:
phi (float): axis in the phase space at which to calculate
the generalized quadrature observable
wires (Sequence[Any] or Any): the wire the operation acts on
id (str or None): String representing the operation (optional)
"""
num_params = 1
num_wires = 1
grad_method = "A"
ev_order = 1
def __init__(self, phi, wires, id=None):
super().__init__(phi, wires=wires, id=id)
@staticmethod
def _heisenberg_rep(p):
phi = p[0]
return np.array([0, math.cos(phi), math.sin(phi)]) # TODO check
[docs] def label(self, decimals=None, base_label=None, cache=None):
r"""A customizable string representation of the operator.
Args:
decimals=None (int): If ``None``, no parameters are included. Else,
specifies how to round the parameters.
base_label=None (str): overwrite the non-parameter component of the label
cache=None (dict): dictionary that caries information between label calls
in the same drawing
Returns:
str: label to use in drawings
**Example:**
>>> op = qml.QuadOperator(1.234, wires=0)
>>> op.label()
'cos(φ)x\n+sin(φ)p'
>>> op.label(decimals=2)
'cos(1.23)x\n+sin(1.23)p'
>>> op.label(base_label="Quad", decimals=2)
'Quad\n(1.23)'
"""
if base_label is not None:
return super().label(decimals=decimals, base_label=base_label, cache=cache)
if decimals is None:
p = "φ"
else:
p = format(qml_math.array(self.parameters[0]), f".{decimals}f")
return f"cos({p})x\n+sin({p})p"
[docs]class PolyXP(CVObservable):
r"""
An arbitrary second-order polynomial observable.
Represents an arbitrary observable :math:`P(\x,\p)` that is a second order
polynomial in the basis :math:`\mathbf{r} = (\I, \x_0, \p_0, \x_1, \p_1, \ldots)`.
For first-order observables the representation is a real vector
:math:`\mathbf{d}` such that :math:`P(\x,\p) = \mathbf{d}^T \mathbf{r}`.
For second-order observables the representation is a real symmetric
matrix :math:`A` such that :math:`P(\x,\p) = \mathbf{r}^T A \mathbf{r}`.
Used for evaluating arbitrary order-2 CV expectation values of
:class:`~.pennylane.tape.CVParamShiftTape`.
**Details:**
* Number of wires: Any
* Number of parameters: 1
* Observable order: 2nd order in the quadrature operators
* Heisenberg representation: :math:`A`
Args:
q (array[float]): expansion coefficients
wires (Sequence[Any] or Any): the wire the operation acts on
id (str or None): String representing the operation (optional)
"""
num_params = 1
num_wires = AnyWires
grad_method = "F"
ev_order = 2
def __init__(self, q, wires, id=None):
super().__init__(q, wires=wires, id=id)
@staticmethod
def _heisenberg_rep(p):
return p[0]
[docs]class FockStateProjector(CVObservable):
r"""
The number state observable :math:`\ket{n}\bra{n}`.
Represents the non-Gaussian number state observable
.. math:: \ket{n}\bra{n} = \ket{n_0, n_1, \dots, n_P}\bra{n_0, n_1, \dots, n_P}
where :math:`n_i` is the occupation number of the :math:`i` th wire.
The expectation of this observable is
.. math::
E[\ket{n}\bra{n}] = \text{Tr}(\ket{n}\bra{n}\rho)
= \text{Tr}(\braketT{n}{\rho}{n})
= \braketT{n}{\rho}{n}
corresponding to the probability of measuring the quantum state in the state
:math:`\ket{n}=\ket{n_0, n_1, \dots, n_P}`.
.. note::
If ``expval(FockStateProjector)`` is applied to a subset of wires,
the unaffected wires are traced out prior to the expectation value
calculation.
**Details:**
* Number of wires: Any
* Number of parameters: 1
* Observable order: None (non-Gaussian)
Args:
n (array): Array of non-negative integers representing the number state
observable :math:`\ket{n}\bra{n}=\ket{n_0, n_1, \dots, n_P}\bra{n_0, n_1, \dots, n_P}`.
For example, to return the observable :math:`\ket{0,4,2}\bra{0,4,2}` acting on
wires 0, 1, and 3 of a QNode, you would call ``FockStateProjector(np.array([0, 4, 2], wires=[0, 1, 3]))``.
Note that ``len(n)==len(wires)``, and that ``len(n)`` cannot exceed the
total number of wires in the QNode.
wires (Sequence[Any] or Any): the wire the operation acts on
id (str or None): String representing the operation (optional)
"""
num_params = 1
num_wires = AnyWires
grad_method = None
ev_order = None
def __init__(self, n, wires, id=None):
super().__init__(n, wires=wires, id=id)
[docs] def label(self, decimals=None, base_label=None, cache=None):
r"""A customizable string representation of the operator.
Args:
decimals=None (int): If ``None``, no parameters are included. Else,
specifies how to round the parameters.
base_label=None (str): overwrite the non-parameter component of the label
cache=None (dict): dictionary that caries information between label calls
in the same drawing
Returns:
str: label to use in drawings
**Example:**
>>> qml.FockStateProjector([1,2,3], wires=(0,1,2)).label()
'|123⟩⟨123|'
"""
if base_label is not None:
return super().label(decimals=decimals, base_label=base_label, cache=cache)
basis_string = "".join(str(int(i)) for i in self.parameters[0])
return f"|{basis_string}⟩⟨{basis_string}|"
__ops__ = {
"Identity",
"Snapshot",
"Beamsplitter",
"ControlledAddition",
"ControlledPhase",
"Displacement",
"Kerr",
"CrossKerr",
"QuadraticPhase",
"Rotation",
"Squeezing",
"TwoModeSqueezing",
"CubicPhase",
"InterferometerUnitary",
"CatState",
"CoherentState",
"FockDensityMatrix",
"DisplacedSqueezedState",
"FockState",
"FockStateVector",
"SqueezedState",
"ThermalState",
"GaussianState",
}
__obs__ = {
"QuadOperator",
"NumberOperator",
"TensorN",
"QuadP",
"QuadX",
"PolyXP",
"FockStateProjector",
}
__all__ = list(__ops__ | __obs__)
_modules/pennylane/ops/cv
Download Python script
Download Notebook
View on GitHub