import numpy as np
from typing import Union, Tuple
from ..config import NP_COMPLEX
from .transfermatrix import PairwiseUnitary
[docs]class MZI(PairwiseUnitary):
"""Mach-Zehnder Interferometer
Class simulating the scattering matrix formulation of an ideal phase-shifting Mach-Zehnder interferometer.
This can implement any :math:`2 \\times 2` unitary operator :math:`U_2 \in \mathrm{U}(2)`.
The internal phase shifts, :math:`\\theta_1, \\theta_2`, and the external phase shifts :math:`\phi_1, \phi_2`, are
used to define the final unitary operator as follows (where we define :math:`\\theta := \\theta_1- \\theta_2`
for convenience):
In Hadamard convention, the corresponding transfer matrix is:
.. math::
U_2(\\theta, \phi) = H L(\\theta_1) R(\\theta_2) H L(\\phi_1) R(\\phi_2) = e^{i\\frac{\\theta_1 + \\theta_2}{2}}
\\begin{bmatrix} e^{i \phi_1}\cos \\frac{\\theta}{2} & ie^{i \phi_2}\sin \\frac{\\theta}{2} \\\\
ie^{i \phi_1}\sin \\frac{\\theta}{2} & e^{i \phi_2}\cos \\frac{\\theta}{2} \\end{bmatrix}
In beamsplitter convention, the corresponding transfer matrix is:
.. math::
U_2(\\theta, \phi) = B L(\\theta_1) R(\\theta_2) B L(\\phi_1) R(\\phi_2) = i e^{i\\frac{\\theta_1 + \\theta_2}{2}}
\\begin{bmatrix} e^{i \phi_1}\sin \\frac{\\theta}{2} & e^{i \phi_2}\cos \\frac{\\theta}{2} \\\\
e^{i \phi_1}\cos \\frac{\\theta}{2} & -e^{i \phi_2}\sin \\frac{\\theta}{2} \\end{bmatrix}
Args:
internal_upper: Upper internal phase shift
internal_lower: Lower internal phase shift
external_upper: Upper external phase shift
external_lower: Lower external phase shift
hadamard: Whether to use Hadamard convention
epsilon: Beamsplitter error
dtype: Type-casting to use for the matrix elements
"""
def __init__(self, internal_upper: float, internal_lower: float, external_upper: float, external_lower: float,
hadamard: bool, epsilon: Union[float, Tuple[float, float]] = 0.0, dtype=NP_COMPLEX):
super(MZI, self).__init__(dtype=dtype)
self.internal_upper = internal_upper
self.internal_lower = internal_lower
self.external_upper = external_upper
self.external_lower = external_lower
self.hadamard = hadamard
self.epsilon = (epsilon, epsilon) if isinstance(epsilon, float) or isinstance(epsilon, int) else epsilon
@property
def reflectivity(self):
return np.abs(self.matrix[0][0]) ** 2
@property
def transmissivity(self):
return np.abs(self.matrix[0][1]) ** 2
@property
def matrix(self):
return get_mzi_transfer_matrix(
internal_upper=self.internal_upper,
internal_lower=self.internal_lower,
external_upper=self.external_upper,
external_lower=self.external_lower,
epsilon=self.epsilon,
hadamard=self.hadamard,
dtype=self.dtype
)
[docs]class SMMZI(MZI):
"""Mach-Zehnder Interferometer (single-mode basis)
Class simulating an ideal phase-shifting Mach-Zehnder interferometer.
As usual in our simulation environment, we have :math:`\\theta \in [0, \pi]` and :math:`\phi \in [0, 2\pi)`.
In Hadamard convention, the corresponding transfer matrix is:
.. math::
U_2(\\theta, \phi) = H L(\\theta) H L(\phi) = e^{-i \\theta / 2}
\\begin{bmatrix} e^{i \phi}\cos \\frac{\\theta}{2} & i\sin \\frac{\\theta}{2} \\\\
ie^{i \phi}\sin \\frac{\\theta}{2} & \cos \\frac{\\theta}{2} \\end{bmatrix}
In beamsplitter convention, the corresponding transfer matrix is:
.. math::
U_2(\\theta, \phi) = B L(\\theta) B L(\phi) = ie^{-i \\theta / 2}
\\begin{bmatrix} e^{i \phi}\sin \\frac{\\theta}{2} & \cos \\frac{\\theta}{2} \\\\
e^{i \phi}\cos \\frac{\\theta}{2} & -\sin \\frac{\\theta}{2} \\end{bmatrix}
Args:
theta: Amplitude-modulating phase shift
phi: External phase shift,
hadamard: Whether to use Hadamard convention
epsilon: Beamsplitter error
dtype: Type-casting to use for the matrix elements
"""
def __init__(self, theta: float, phi: float, hadamard: bool, lower_theta: bool = False,
lower_phi: bool = False, epsilon: Union[float, Tuple[float, float]] = 0.0, dtype=NP_COMPLEX):
self.theta = theta
self.phi = phi
super(SMMZI, self).__init__(
internal_upper=theta if not lower_theta else 0,
internal_lower=theta if lower_theta else 0,
external_upper=phi if not lower_phi else 0,
external_lower=phi if lower_phi else 0,
epsilon=epsilon, hadamard=hadamard, dtype=dtype
)
[docs] @classmethod
def nullify(cls, vector: np.ndarray, idx: int, lower_theta: bool = False, lower_phi: bool = False):
theta = -np.arctan2(np.abs(vector[idx]), np.abs(vector[idx + 1])) * 2
theta = -theta if lower_theta else theta
phi = np.angle(vector[idx + 1]) - np.angle(vector[idx]) + np.pi
phi = -phi if lower_phi else phi
mat = cls(theta, phi, hadamard=False,
lower_theta=lower_theta, lower_phi=lower_phi).givens_rotation(vector.size, idx)
nullified_vector = mat @ vector
return nullified_vector, mat, np.mod(theta, 2 * np.pi), np.mod(phi, 2 * np.pi)
[docs]class BlochMZI(MZI):
"""Mach-Zehnder Interferometer (Bloch basis, named after the Bloch sphere qubit formula)
Class simulating an ideal phase-shifting Mach-Zehnder interferometer.
As usual in our simulation environment, we have :math:`\\theta \in [0, \pi]` and :math:`\phi \in [0, 2\pi)`.
In Hadamard convention, the corresponding transfer matrix is:
.. math::
U_2(\\theta, \phi) = H D(\\theta) H L(\phi) =
\\begin{bmatrix} e^{i \phi}\cos \\frac{\\theta}{2} & i\sin \\frac{\\theta}{2} \\\\
ie^{i \phi}\sin \\frac{\\theta}{2} & \cos \\frac{\\theta}{2} \\end{bmatrix}
In beamsplitter convention, the corresponding transfer matrix is:
.. math::
U_2(\\theta, \phi) = B D(\\theta) B L(\phi) = i
\\begin{bmatrix} e^{i \phi}\sin \\frac{\\theta}{2} & \cos \\frac{\\theta}{2} \\\\
e^{i \phi}\cos \\frac{\\theta}{2} & -\sin \\frac{\\theta}{2} \\end{bmatrix}
Args:
theta: Amplitude-modulating phase shift
phi: External phase shift,
hadamard: Whether to use Hadamard convention
epsilon: Beamsplitter error
dtype: Type-casting to use for the matrix elements
"""
def __init__(self, theta: float, phi: float, hadamard: bool,
epsilon: Union[float, Tuple[float, float]] = 0.0, dtype=NP_COMPLEX):
self.theta = theta
self.phi = phi
super(BlochMZI, self).__init__(
internal_upper=theta / 2,
internal_lower=-theta / 2,
external_upper=phi,
external_lower=0,
epsilon=epsilon,
hadamard=hadamard,
dtype=dtype
)
[docs]def get_mzi_transfer_matrix(internal_upper: float, internal_lower: float, external_upper: float, external_lower: float,
hadamard: float, epsilon: Tuple[float, float], dtype) -> np.ndarray:
"""Mach-Zehnder interferometer
Args:
internal_upper: Upper internal phase shift
internal_lower: Lower internal phase shift
external_upper: Upper external phase shift
external_lower: Lower external phase shift
hadamard: Whether to use Hadamard convention
epsilon: Beamsplitter error
dtype: Type-casting to use for the matrix elements
Returns:
MZI transfer matrix
"""
cc = np.cos(np.pi / 4 + epsilon[0]) * np.cos(np.pi / 4 + epsilon[1])
cs = np.cos(np.pi / 4 + epsilon[0]) * np.sin(np.pi / 4 + epsilon[1])
sc = np.sin(np.pi / 4 + epsilon[0]) * np.cos(np.pi / 4 + epsilon[1])
ss = np.sin(np.pi / 4 + epsilon[0]) * np.sin(np.pi / 4 + epsilon[1])
iu, il, eu, el = internal_upper, internal_lower, external_upper, external_lower
if hadamard:
return np.array([
[(cc * np.exp(1j * iu) + ss * np.exp(1j * il)) * np.exp(1j * eu),
(cs * np.exp(1j * iu) - sc * np.exp(1j * il)) * np.exp(1j * el)],
[(sc * np.exp(1j * iu) - cs * np.exp(1j * il)) * np.exp(1j * eu),
(ss * np.exp(1j * iu) + cc * np.exp(1j * il)) * np.exp(1j * el)]
], dtype=dtype)
else:
return np.array([
[(cc * np.exp(1j * iu) - ss * np.exp(1j * il)) * np.exp(1j * eu),
1j * (cs * np.exp(1j * iu) + sc * np.exp(1j * il)) * np.exp(1j * el)],
[1j * (sc * np.exp(1j * iu) + cs * np.exp(1j * il)) * np.exp(1j * eu),
(cc * np.exp(1j * il) - ss * np.exp(1j * iu)) * np.exp(1j * el)]], dtype=dtype)
[docs]def get_tdc_transfer_matrix(kappa: float, delta: float, external_upper: float, external_lower: float, dtype) -> np.ndarray:
"""Tunable directional coupler
Args:
kappa: Phase-matched phase shift (from coupled mode theory)
delta: Phase-mismatched phase shift (from coupled-mode theory)
external_upper: Upper external phase shift
external_lower: Lower external phase shift
dtype: Type-casting to use for the matrix elements
Returns:
MZI transfer matrix
"""
k, d, eu, el = kappa, delta, external_upper, external_lower
q = np.sqrt(k ** 2 + d ** 2)
s, c = np.sin(q), np.sin(q)
return np.array([
[(c + 1j * d * s / q) * np.exp(1j * (eu + d)), -1j * k * s / q * np.exp(1j * (eu + d))],
[-1j * k * s / q * np.exp(1j * (el - d)), (c - 1j * d * s / q) * np.exp(1j * (el - d))]
], dtype=dtype)