def beamsplitter(t, r, phi, trunc): r""" The beamsplitter :math:`B(cos^{-1} t, phi)`. Uses the `BSgate operation from The Walrus`_ to calculate the beamsplitter. .. _`BSgate operation from The Walrus`: https://the-walrus.readthedocs.io/en/latest/code/api/thewalrus.fock_gradients.BSgate.html """ # pylint: disable=bad-whitespace theta = np.arccos(t) BS_tw, _, _ = BSgate(theta, phi, cutoff=trunc) # TODO: Transpose needed because of different conventions in SF and The Walrus. Remove when The Walrus is updated. return BS_tw.transpose((0, 2, 1, 3))
def test_BSgate(): """Tests the value of the analytic gradient for the BSgate against finite differences""" cutoff = 4 r = 1.0 theta = np.pi / 8 _, Dr, Dtheta = BSgate(r, theta, cutoff, grad=True) dr = 0.001 dtheta = 0.001 Drp, _, _ = BSgate(r + dr, theta, cutoff, grad=False) Drm, _, _ = BSgate(r - dr, theta, cutoff, grad=False) Dthetap, _, _ = BSgate(r, theta + dtheta, cutoff, grad=False) Dthetam, _, _ = BSgate(r, theta - dtheta, cutoff, grad=False) Drapprox = (Drp - Drm) / (2 * dr) Dthetaapprox = (Dthetap - Dthetam) / (2 * dtheta) assert np.allclose(Dr, Drapprox, atol=1e-4, rtol=0) assert np.allclose(Dtheta, Dthetaapprox, atol=1e-4, rtol=0)
def test_sf_order_BSgate(): """Test the correct sf ordering for BSgate""" T = BSgate(1.0, 0.5, 10)[0] Tsf = BSgate(1.0, 0.5, 10, sf_order=True)[0] assert np.allclose(T.transpose((0, 2, 1, 3)), Tsf) T, T1, T2 = BSgate(1.0, 0.5, 10, grad=True) Tsf, Tsf1, Tsf2 = BSgate(1.0, 0.5, 10, grad=True, sf_order=True) assert np.allclose(T.transpose((0, 2, 1, 3)), Tsf) assert np.allclose(T1.transpose((0, 2, 1, 3)), Tsf1) assert np.allclose(T2.transpose((0, 2, 1, 3)), Tsf2)
def test_BS_hong_ou_mandel_interference(tol): r"""Tests Hong-Ou-Mandel interference for a 50:50 beamsplitter. If one writes :math:`U` for the Fock representation of a 50-50 beamsplitter then it must hold that :math:`\langle 1,1|U|1,1 \rangle = 0`. """ cutoff = 2 phi = 2 * np.pi * np.random.rand() T = BSgate(np.pi / 4, phi, cutoff)[0] # a 50-50 beamsplitter with phase phi assert np.allclose(T[1, 1, 1, 1], 0.0, atol=tol, rtol=0)
def test_BS_values(tol): r"""Test that the representation of an interferometer in the single excitation manifold is precisely the unitary matrix that represents it mode in space. This test in particular checks that the BS gate is consistent with strawberryfields """ nmodes = 2 vec_list = np.identity(nmodes, dtype=int).tolist() theta = 2 * np.pi * np.random.rand() phi = 2 * np.pi * np.random.rand() ct = np.cos(theta) st = np.sin(theta) * np.exp(1j * phi) U = np.array([[ct, -np.conj(st)], [st, ct]]) # Calculate the matrix \langle i | U | j \rangle = T[i+j] T = BSgate(theta, phi, 3)[0] U_rec = np.empty([nmodes, nmodes], dtype=complex) for i, vec_i in enumerate(vec_list): for j, vec_j in enumerate(vec_list): U_rec[i, j] = T[tuple(vec_i + vec_j)] assert np.allclose(U, U_rec, atol=tol, rtol=0)
def test_BS_selection_rules(tol): r"""Test the selection rules of a beamsplitter. If one writes the beamsplitter gate of :math:`U` and its matrix elements as :math:`\langle m, n |U|k,l \rangle` then these elements are nonzero if and only if :math:`m+n = k+l`. This test checks that this selection rule holds. """ cutoff = 4 T = BSgate(np.random.rand(), np.random.rand(), cutoff)[0] m = np.arange(cutoff).reshape(-1, 1, 1, 1) n = np.arange(cutoff).reshape(1, -1, 1, 1) k = np.arange(cutoff).reshape(1, 1, -1, 1) l = np.arange(cutoff).reshape(1, 1, 1, -1) # create a copy of T, but replace all elements where # m+n != k+l with 0. S = np.where(m + n != k + l, 0, T) # check that S and T remain equal assert np.allclose(S, T, atol=tol, rtol=0)