Esempio n. 1
0
def test_is_classical_cov_squeezed():
    """ Tests that a squeezed state is not classical"""
    nbar = 1.0
    phase = np.pi / 8
    r = np.arcsinh(np.sqrt(nbar))
    cov = two_mode_squeezing(2 * r, phase)
    assert not is_classical_cov(cov)
Esempio n. 2
0
    def test_expand_two(self, m1, m2, tol):
        """Test expanding a two mode gate"""
        r = 0.1
        phi = 0.423
        N = 4

        S = symplectic.two_mode_squeezing(r, phi)
        res = symplectic.expand(S, modes=[m1, m2], N=N)

        expected = np.identity(2 * N)

        # mode1 terms
        expected[m1, m1] = S[0, 0]
        expected[m1, m1 + N] = S[0, 2]
        expected[m1 + N, m1] = S[2, 0]
        expected[m1 + N, m1 + N] = S[2, 2]

        # mode2 terms
        expected[m2, m2] = S[1, 1]
        expected[m2, m2 + N] = S[1, 3]
        expected[m2 + N, m2] = S[3, 1]
        expected[m2 + N, m2 + N] = S[3, 3]

        # cross terms
        expected[m1, m2] = S[0, 1]
        expected[m1, m2 + N] = S[0, 3]
        expected[m1 + N, m2] = S[2, 1]
        expected[m1 + N, m2 + N] = S[2, 3]

        expected[m2, m1] = S[1, 0]
        expected[m2, m1 + N] = S[3, 0]
        expected[m2 + N, m1] = S[1, 2]
        expected[m2 + N, m1 + N] = S[3, 2]

        assert np.allclose(res, expected, atol=tol, rtol=0)
Esempio n. 3
0
    def test_loss_complete(self, hbar, tol):
        """Test full loss on half a TMS"""
        r = 0.543
        phi = 0.432
        T = 0

        S = symplectic.two_mode_squeezing(r, phi)
        mu = np.zeros([4])
        cov = S @ S.T * (hbar / 2)

        mu, cov = symplectic.loss(mu, cov, T, mode=0, hbar=hbar)

        # expected state mode 0
        expected0 = np.zeros([2]), np.identity(2) * hbar / 2
        res0 = symplectic.reduced_state(mu, cov, 0)

        # expected state mode 1
        nbar = np.sinh(r)**2
        expected1 = np.zeros([2]), np.identity(2) * (2 * nbar + 1) * hbar / 2
        res1 = symplectic.reduced_state(mu, cov, 1)

        assert np.allclose(res0[0], expected0[0], atol=tol, rtol=0)
        assert np.allclose(res0[1], expected0[1], atol=tol, rtol=0)

        assert np.allclose(res1[0], expected1[0], atol=tol, rtol=0)
        assert np.allclose(res1[1], expected1[1], atol=tol, rtol=0)
Esempio n. 4
0
    def test_decompose(self, tol):
        """Test the two mode squeezing symplectic transform decomposes correctly."""
        r = 0.543
        phi = 0.123
        S = symplectic.two_mode_squeezing(r, phi)

        # test that S = B^\dagger(pi/4, 0) [S(z) x S(-z)] B(pi/4)
        # fmt: off
        B = np.array([[1, -1, 0, 0], [1, 1, 0, 0], [0, 0, 1, -1], [0, 0, 1, 1]
                      ]) / np.sqrt(2)

        Sq1 = np.array([[
            np.cosh(r) - np.cos(phi) * np.sinh(r), -np.sin(phi) * np.sinh(r)
        ], [-np.sin(phi) * np.sinh(r),
            np.cosh(r) + np.cos(phi) * np.sinh(r)]])

        Sq2 = np.array([[
            np.cosh(-r) - np.cos(phi) * np.sinh(-r), -np.sin(phi) * np.sinh(-r)
        ], [
            -np.sin(phi) * np.sinh(-r),
            np.cosh(-r) + np.cos(phi) * np.sinh(-r)
        ]])
        # fmt: on

        Sz = block_diag(Sq1, Sq2)[:, [0, 2, 1, 3]][[0, 2, 1, 3]]
        expected = B.conj().T @ Sz @ B
        assert np.allclose(S, expected, atol=tol, rtol=0)
Esempio n. 5
0
def test_entanglement_entropy_two_modes(r, hbar):
    """Tests the log_negativity for two modes states following Eq. 1 of https://journals.aps.org/pra/pdf/10.1103/PhysRevA.63.022305"""
    cov = (hbar / 2) * two_mode_squeezing(2 * r, 0)
    expected = (np.cosh(r) ** 2) * np.log(np.cosh(r) ** 2) - np.sinh(r) ** 2 * np.log(
        np.sinh(r) ** 2
    )
    obtained = entanglement_entropy(cov, modes_A=[0], hbar=hbar)
    assert np.allclose(expected, obtained)
Esempio n. 6
0
def test_Amat_TMS_using_cov():
    """test Amat returns correct result for a two-mode squeezed state"""
    V = two_mode_squeezing(2 * np.arcsinh(1), 0)
    res = Amat(V)

    B = np.fliplr(np.diag([1 / np.sqrt(2)] * 2))
    O = np.zeros_like(B)
    ex = np.block([[B, O], [O, B]])
    assert np.allclose(res, ex)
Esempio n. 7
0
def test_Qmat_TMS():
    """test Qmat returns correct result for a two-mode squeezed state"""
    V = two_mode_squeezing(2 * np.arcsinh(1), 0)
    res = Qmat(V)

    q = np.fliplr(np.diag([2.0] * 4))
    np.fill_diagonal(q, np.sqrt(2))
    ex = np.fliplr(q)
    assert np.allclose(res, ex)
Esempio n. 8
0
def test_pnd_two_mode_squeeze_vacuum(tol, r, phi, hbar):
    """Test the photon number distribution for the two-mode squeezed vacuum"""
    S = two_mode_squeezing(r, phi)
    mu = np.zeros(4)

    cov = hbar / 2 * (S @ S.T)
    pnd_cov = photon_number_covmat(mu, cov, hbar=hbar)
    n = np.sinh(r) ** 2

    assert np.allclose(pnd_cov, np.full((2, 2), n ** 2 + n), atol=tol, rtol=0)
Esempio n. 9
0
def test_sf_ordering_in_fock_tensor(tol):
    """Test that the reordering works when using sf_order=True"""
    cutoff = 5
    nmodes = 2
    s = np.arcsinh(1.0)
    phi = np.pi / 6
    alphas = np.zeros([nmodes])
    S = two_mode_squeezing(s, phi)
    T = fock_tensor(S, alphas, cutoff)
    Tsf = fock_tensor(S, alphas, cutoff, sf_order=True)
    assert np.allclose(T.transpose([0, 2, 1, 3]), Tsf, atol=tol, rtol=0)
Esempio n. 10
0
def test_state_vector_two_mode_squeezed():
    """ Tests state_vector for a two mode squeezed vacuum state """
    nbar = 1.0
    cutoff = 5
    phase = np.pi / 8
    r = np.arcsinh(np.sqrt(nbar))
    cov = two_mode_squeezing(2 * r, phase)
    mu = np.zeros([4], dtype=np.complex)
    exact = np.array([(np.exp(1j * i * phase) * (nbar / (1.0 + nbar)) ** (i / 2) / np.sqrt(1.0 + nbar)) for i in range(cutoff)])
    psi = state_vector(mu, cov, cutoff=cutoff)
    expected = np.diag(exact)
    assert np.allclose(psi, expected)
Esempio n. 11
0
    def test_symplectic(self, tol):
        """Test that the two mode squeeze operator is symplectic"""
        r = 0.543
        phi = 0.123
        S = symplectic.expand(symplectic.two_mode_squeezing(r, phi),
                              modes=[0, 2],
                              N=4)

        # the symplectic matrix
        O = np.block([[np.zeros([4, 4]), np.identity(4)],
                      [-np.identity(4), np.zeros([4, 4])]])

        assert np.allclose(S @ O @ S.T, O, atol=tol, rtol=0)
Esempio n. 12
0
def test_log_negativity_two_modes(r, etaA, etaB, hbar):
    """Tests the log_negativity for two modes states following Eq. 13 of https://arxiv.org/pdf/quant-ph/0506124.pdf"""
    cov = (hbar / 2) * two_mode_squeezing(2 * r, 0)
    _, cov_lossy = passive_transformation(np.zeros([4]), cov, np.diag([etaA, etaB]), hbar=hbar)
    cov_xpxp = xxpp_to_xpxp(cov_lossy) / (hbar / 2)
    alpha = cov_xpxp[:2, :2]
    gamma = cov_xpxp[2:, :2]
    beta = cov_xpxp[2:, 2:]
    invariant = np.linalg.det(alpha) + np.linalg.det(beta) - 2 * np.linalg.det(gamma)
    detcov = np.linalg.det(cov_xpxp)
    expected = -np.log(np.sqrt((invariant - np.sqrt(invariant**2 - 4 * detcov)) / 2))
    obtained = log_negativity(cov_lossy, modes_A=[0], hbar=hbar)
    assert np.allclose(expected, obtained)
Esempio n. 13
0
def test_pure_state_amplitude_two_mode_squeezed(i, j):
    """ Tests pure state amplitude for a two mode squeezed vacuum state """
    nbar = 1.0
    phase = np.pi / 8
    r = np.arcsinh(np.sqrt(nbar))
    cov = two_mode_squeezing(2 * r, phase)
    mu = np.zeros([4], dtype=np.complex)
    if i != j:
        exact = 0.0
    else:
        exact = np.exp(1j * i * phase) * (nbar / (1.0 + nbar)) ** (i / 2) / np.sqrt(1.0 + nbar)
    num = pure_state_amplitude(mu, cov, [i, j])

    assert np.allclose(exact, num)
Esempio n. 14
0
    def test_loss_none(self, hbar, tol):
        """Test no loss on half a TMS leaves state unchanged"""
        r = 0.543
        phi = 0.432
        T = 1

        S = symplectic.two_mode_squeezing(r, phi)
        mu = np.zeros([4])
        cov = S @ S.T * (hbar / 2)

        res = symplectic.loss(mu, cov, T, mode=0, hbar=hbar)
        expected = mu, cov

        assert np.allclose(res[0], expected[0], atol=tol, rtol=0)
        assert np.allclose(res[1], expected[1], atol=tol, rtol=0)
Esempio n. 15
0
def test_state_vector_two_mode_squeezed_post_normalize():
    """ Tests state_vector for a two mode squeezed vacuum state """
    nbar = 1.0
    cutoff = 5
    phase = np.pi / 8
    r = np.arcsinh(np.sqrt(nbar))
    cov = two_mode_squeezing(2 * r, phase)
    mu = np.zeros([4], dtype=np.complex)
    exact = np.diag(np.array([(np.exp(1j * i * phase) * (nbar / (1.0 + nbar)) ** (i / 2) / np.sqrt(1.0 + nbar)) for i in range(cutoff)]))
    val = 2
    post_select = {0: val}
    psi = state_vector(mu, cov, cutoff=cutoff, post_select=post_select, normalize=True)
    expected = exact[val]
    expected = expected / np.linalg.norm(expected)
    assert np.allclose(psi, expected)
Esempio n. 16
0
def test_four_modes(hbar):
    """Test that probabilities are correctly updates for a four modes system under loss"""
    # All this block is to generate the correct covariance matrix.
    # It correnponds to num_modes=4 modes that undergo two mode squeezing between modes i and i + (num_modes / 2).
    # Then they undergo displacement.
    # The signal and idlers see and interferometer with unitary matrix u2x2.
    # And then they see loss by amount etas[i].
    num_modes = 4
    theta = 0.45
    phi = 0.7
    u2x2 = np.array([
        [np.cos(theta / 2),
         np.exp(1j * phi) * np.sin(theta / 2)],
        [-np.exp(-1j * phi) * np.sin(theta / 2),
         np.cos(theta / 2)],
    ])

    u4x4 = block_diag(u2x2, u2x2)

    cov = np.identity(2 * num_modes) * hbar / 2
    means = 0.5 * np.random.rand(2 * num_modes) * np.sqrt(hbar / 2)
    rs = [0.1, 0.9]
    n_half = num_modes // 2

    for i, r_val in enumerate(rs):
        Sexpanded = expand(two_mode_squeezing(r_val, 0.0), [i, n_half + i],
                           num_modes)
        cov = Sexpanded @ cov @ (Sexpanded.T)

    Su = expand(interferometer(u4x4), range(num_modes), num_modes)
    cov = Su @ cov @ (Su.T)
    cov_lossless = np.copy(cov)
    means_lossless = np.copy(means)
    etas = [0.9, 0.7, 0.9, 0.1]

    for i, eta in enumerate(etas):
        means, cov = loss(means, cov, eta, i, hbar=hbar)

    cutoff = 3
    probs_lossless = probabilities(means_lossless,
                                   cov_lossless,
                                   4 * cutoff,
                                   hbar=hbar)
    probs = probabilities(means, cov, cutoff, hbar=hbar)
    probs_updated = update_probabilities_with_loss(etas, probs_lossless)
    assert np.allclose(probs,
                       probs_updated[:cutoff, :cutoff, :cutoff, :cutoff],
                       atol=1e-6)
Esempio n. 17
0
    def test_tms(self, hbar, tol):
        """Test reduced state of a TMS state is a thermal state"""
        r = 0.543
        phi = 0.432

        S = symplectic.two_mode_squeezing(r, phi)
        mu = np.zeros([4])
        cov = S @ S.T * (hbar / 2)

        res = symplectic.reduced_state(mu, cov, 0)

        # expected state
        nbar = np.sinh(r)**2
        expected = np.zeros([2]), np.identity(2) * (2 * nbar + 1) * hbar / 2

        assert np.allclose(res[0], expected[0], atol=tol, rtol=0)
        assert np.allclose(res[1], expected[1], atol=tol, rtol=0)
Esempio n. 18
0
def test_is_symplectic():
    """ Tests that the matrices generated in the symplectic module are indeed symplectic"""
    theta = np.pi / 6
    r = np.arcsinh(1.0)
    phi = np.pi / 8
    S = symplectic.rotation(theta)
    assert symplectic.is_symplectic(S)
    S = symplectic.squeezing(r, theta)
    assert symplectic.is_symplectic(S)
    S = symplectic.beam_splitter(theta, phi)
    assert symplectic.is_symplectic(S)
    S = symplectic.two_mode_squeezing(r, theta)
    assert symplectic.is_symplectic(S)
    A = np.array([[2.0, 3.0], [4.0, 6.0]])
    assert not symplectic.is_symplectic(A)
    A = np.identity(3)
    assert not symplectic.is_symplectic(A)
    A = np.array([[2.0, 3.0], [4.0, 6.0], [4.0, 6.0]])
    assert not symplectic.is_symplectic(A)
Esempio n. 19
0
def test_two_mode_squeezing(choi_r, tol):
    r"""Tests the selection rules of a two mode squeezing operation.
    If one writes the squeezing gate as :math:`S_2` and its matrix elements as
    :math:`\langle p_0 p_1|S_2|q_0 q_1 \rangle` then these elements are nonzero
    if and only if :math:`p_0 - q_0 = p_1 - q_1`. This test checks that this
    selection rule holds.
    """
    cutoff = 5
    nmodes = 2
    s = np.arcsinh(1.0)
    phi = np.pi / 6
    alphas = np.zeros([nmodes])
    S = two_mode_squeezing(s, phi)
    T = fock_tensor(S, alphas, cutoff, choi_r=choi_r)
    for p in product(list(range(cutoff)), repeat=nmodes):
        for q in product(list(range(cutoff)), repeat=nmodes):
            if p[0] - q[0] != p[1] - q[1]:
                t = tuple(list(p) + list(q))
                assert np.allclose(T[t], 0, atol=tol, rtol=0)
Esempio n. 20
0
    def test_coherent(self, hbar, tol):
        """Test the two mode squeezing symplectic transform acts correctly
        on coherent states"""
        r = 0.543
        phi = 0.123
        S = symplectic.two_mode_squeezing(r, phi)

        # test that S |a1, a2> = |ta1+ra2, ta2+ra1>
        a1 = 0.23 + 0.12j
        a2 = 0.23 + 0.12j
        out = S @ np.array([a1.real, a2.real, a1.imag, a2.imag]) * np.sqrt(
            2 * hbar)

        T = np.cosh(r)
        R = np.exp(1j * phi) * np.sinh(r)
        a1out = T * a1 + R * np.conj(a2)
        a2out = T * a2 + R * np.conj(a1)
        expected = np.array([a1out.real, a2out.real, a1out.imag, a2out.imag
                             ]) * np.sqrt(2 * hbar)
        assert np.allclose(out, expected, atol=tol, rtol=0)
Esempio n. 21
0
    def test_TMS_against_interferometer(self, hbar, tol):
        """Test that the loss channel on a TMS state corresponds to a beamsplitter
        acting on the mode with loss and an ancilla vacuum state"""
        r = 0.543
        phi = 0.432
        T = 0.812

        S = symplectic.two_mode_squeezing(r, phi)
        cov = S @ S.T * (hbar / 2)

        # perform loss
        _, cov_res = symplectic.loss(np.zeros([4]), cov, T, mode=0, hbar=hbar)

        # create a two mode beamsplitter acting on modes 0 and 2
        B = np.array([
            [np.sqrt(T), -np.sqrt(1 - T), 0, 0],
            [np.sqrt(1 - T), np.sqrt(T), 0, 0],
            [0, 0, np.sqrt(T), -np.sqrt(1 - T)],
            [0, 0, np.sqrt(1 - T), np.sqrt(T)],
        ])

        B = symplectic.expand(B, modes=[0, 2], N=3)

        # add an ancilla vacuum state in mode 2
        cov_expand = np.identity(6) * hbar / 2
        cov_expand[:2, :2] = cov[:2, :2]
        cov_expand[3:5, :2] = cov[2:, :2]
        cov_expand[:2, 3:5] = cov[:2, 2:]
        cov_expand[3:5, 3:5] = cov[2:, 2:]

        # apply the beamsplitter to modes 0 and 2
        cov_expand = B @ cov_expand @ B.T

        # compare loss function result to an interferometer mixing mode 0 with the vacuum
        _, cov_expected = symplectic.reduced_state(np.zeros([6]),
                                                   cov_expand,
                                                   modes=[0, 1])
        assert np.allclose(cov_expected, cov_res, atol=tol, rtol=0)
Esempio n. 22
0
def test_disp_torontonian(r, alpha):
    """Calculates click probabilities of displaced two mode squeezed state"""

    p00a = np.exp(-2 * (abs(alpha) ** 2 - abs(alpha) ** 2 * np.tanh(r))) / (np.cosh(r) ** 2)

    fact_0 = np.exp(-(abs(alpha) ** 2) / (np.cosh(r) ** 2))
    p01a = fact_0 / (np.cosh(r) ** 2) - p00a

    fact_0 = np.cosh(r) ** 2
    fact_1 = -2 * np.exp(-(abs(alpha) ** 2) / (np.cosh(r) ** 2))
    fact_2 = np.exp(-2 * (abs(alpha) ** 2 - abs(alpha) ** 2.0 * np.tanh(r)))
    p11a = (fact_0 + fact_1 + fact_2) / (np.cosh(r) ** 2)

    cov = two_mode_squeezing(abs(2 * r), np.angle(2 * r))
    mu = 2 * np.array([alpha.real, alpha.real, alpha.imag, alpha.imag])

    p00n = threshold_detection_prob(mu, cov, np.array([0, 0]))
    p01n = threshold_detection_prob(mu, cov, np.array([0, 1]))
    p11n = threshold_detection_prob(mu, cov, np.array([1, 1]))

    assert np.isclose(p00a, p00n)
    assert np.isclose(p01a, p01n)
    assert np.isclose(p11a, p11n)
Esempio n. 23
0
    def test_inverse_ops_cancel(self, hbar, tol):
        """Test that applying squeezing and interferometers to a four mode circuit,
        followed by applying the inverse operations, return the state to the vacuum"""

        # the symplectic matrix
        O = np.block([[np.zeros([4, 4]), np.identity(4)],
                      [-np.identity(4), np.zeros([4, 4])]])

        # begin in the vacuum state
        mu_init, cov_init = symplectic.vacuum_state(4, hbar=hbar)

        # add displacement
        alpha = np.random.random(size=[4]) + np.random.random(size=[4]) * 1j
        D = np.concatenate([alpha.real, alpha.imag])
        mu = mu_init + D
        cov = cov_init.copy()

        # random squeezing
        r = np.random.random()
        phi = np.random.random()
        S = symplectic.expand(symplectic.two_mode_squeezing(r, phi),
                              modes=[0, 1],
                              N=4)

        # check symplectic
        assert np.allclose(S @ O @ S.T, O, atol=tol, rtol=0)

        # random interferometer
        # fmt:off
        u = np.array([[
            -0.06658906 - 0.36413058j, 0.07229868 + 0.65935896j,
            0.59094625 - 0.17369183j, -0.18254686 - 0.10140904j
        ],
                      [
                          0.53854866 + 0.36529723j, 0.61152793 + 0.15022026j,
                          0.05073631 + 0.32624882j, -0.17482023 - 0.20103772j
                      ],
                      [
                          0.34818923 + 0.51864844j, -0.24334624 + 0.0233729j,
                          0.3625974 - 0.4034224j, 0.10989667 + 0.49366039j
                      ],
                      [
                          0.16548085 + 0.14792642j, -0.3012549 - 0.11387682j,
                          -0.12731847 - 0.44851389j, -0.55816075 - 0.5639976j
                      ]])
        # fmt on
        U = symplectic.interferometer(u)

        # check unitary
        assert np.allclose(u @ u.conj().T, np.identity(4), atol=tol, rtol=0)
        # check symplectic
        assert np.allclose(U @ O @ U.T, O, atol=tol, rtol=0)

        # apply squeezing and interferometer
        cov = U @ S @ cov @ S.T @ U.T
        mu = U @ S @ mu

        # check we are no longer in the vacuum state
        assert not np.allclose(mu, mu_init, atol=tol, rtol=0)
        assert not np.allclose(cov, cov_init, atol=tol, rtol=0)

        # return the inverse operations
        Sinv = symplectic.expand(symplectic.two_mode_squeezing(-r, phi),
                                 modes=[0, 1],
                                 N=4)
        Uinv = symplectic.interferometer(u.conj().T)

        # check inverses
        assert np.allclose(Uinv, np.linalg.inv(U), atol=tol, rtol=0)
        assert np.allclose(Sinv, np.linalg.inv(S), atol=tol, rtol=0)

        # apply the inverse operations
        cov = Sinv @ Uinv @ cov @ Uinv.T @ Sinv.T
        mu = Sinv @ Uinv @ mu

        # inverse displacement
        mu -= D

        # check that we return to the vacuum state
        assert np.allclose(mu, mu_init, atol=tol, rtol=0)
        assert np.allclose(cov, cov_init, atol=tol, rtol=0)
Esempio n. 24
0
def TMS_cov(r, phi, hbar=2):
    """returns the covariance matrix of a TMS state"""

    S = two_mode_squeezing(r, phi)

    return S @ S.T * hbar / 2
Esempio n. 25
0
    def compile(self, seq, registers):
        """Try to arrange a quantum circuit into the canonical Symplectic form.

        This method checks whether the circuit can be implemented as a sequence of Gaussian operations.
        If the answer is yes it arranges them in the canonical order with displacement at the end.

        Args:
            seq (Sequence[Command]): quantum circuit to modify
            registers (Sequence[RegRefs]): quantum registers
        Returns:
            List[Command]: modified circuit
        Raises:
            CircuitError: the circuit does not correspond to a Gaussian unitary
        """

        # Check which modes are actually being used
        used_modes = []
        for operations in seq:
            modes = [modes_label.ind for modes_label in operations.reg]
            used_modes.append(modes)
        # pylint: disable=consider-using-set-comprehension
        used_modes = list(
            set([item for sublist in used_modes for item in sublist]))

        # dictionary mapping the used modes to consecutive non-negative integers
        dict_indices = {used_modes[i]: i for i in range(len(used_modes))}
        nmodes = len(used_modes)

        # This is the identity transformation in phase-space, multiply by the identity and add zero
        Snet = np.identity(2 * nmodes)
        rnet = np.zeros(2 * nmodes)

        # Now we will go through each operation in the sequence `seq` and apply it in quadrature space
        # We will keep track of the net transforation in the Symplectic matrix `Snet` and the quadrature
        # vector `rnet`.
        for operations in seq:
            name = operations.op.__class__.__name__
            params = par_evaluate(operations.op.p)
            modes = [modes_label.ind for modes_label in operations.reg]
            if name == "Dgate":
                rnet = rnet + expand_vector(
                    params[0] *
                    (np.exp(1j * params[1])), dict_indices[modes[0]], nmodes)
            else:
                if name == "Rgate":
                    S = expand(rotation(params[0]), dict_indices[modes[0]],
                               nmodes)
                elif name == "Sgate":
                    S = expand(squeezing(params[0], params[1]),
                               dict_indices[modes[0]], nmodes)
                elif name == "S2gate":
                    S = expand(
                        two_mode_squeezing(params[0], params[1]),
                        [dict_indices[modes[0]], dict_indices[modes[1]]],
                        nmodes,
                    )
                elif name == "Interferometer":
                    S = expand(interferometer(params[0]),
                               [dict_indices[mode] for mode in modes], nmodes)
                elif name == "GaussianTransform":
                    S = expand(params[0],
                               [dict_indices[mode] for mode in modes], nmodes)
                elif name == "BSgate":
                    S = expand(
                        beam_splitter(params[0], params[1]),
                        [dict_indices[modes[0]], dict_indices[modes[1]]],
                        nmodes,
                    )
                elif name == "MZgate":
                    v = np.exp(1j * params[0])
                    u = np.exp(1j * params[1])
                    U = 0.5 * np.array([[u * (v - 1), 1j *
                                         (1 + v)], [1j * u * (1 + v), 1 - v]])
                    S = expand(
                        interferometer(U),
                        [dict_indices[modes[0]], dict_indices[modes[1]]],
                        nmodes,
                    )
                Snet = S @ Snet
                rnet = S @ rnet

        # Having obtained the net displacement we simply convert it into complex notation
        alphas = 0.5 * (rnet[0:nmodes] + 1j * rnet[nmodes:2 * nmodes])
        # And now we just pass the net transformation as a big Symplectic operation plus displacements
        ord_reg = [r for r in list(registers) if r.ind in used_modes]
        ord_reg = sorted(list(ord_reg), key=lambda x: x.ind)
        if np.allclose(Snet, np.identity(2 * nmodes)):
            A = []
        else:
            A = [Command(ops.GaussianTransform(Snet), ord_reg)]
        B = [
            Command(ops.Dgate(np.abs(alphas[i]), np.angle(alphas[i])),
                    ord_reg[i]) for i in range(len(ord_reg))
            if not np.allclose(alphas[i], 0.0)
        ]
        return A + B
    def test_no_unitary(self, tol):
        """Test compilation works with no unitary provided"""
        prog = sf.Program(8)

        with prog.context as q:
            ops.S2gate(SQ_AMPLITUDE) | (q[0], q[4])
            ops.S2gate(SQ_AMPLITUDE) | (q[1], q[5])
            ops.S2gate(SQ_AMPLITUDE) | (q[2], q[6])
            ops.S2gate(SQ_AMPLITUDE) | (q[3], q[7])
            ops.MeasureFock() | q

        res = prog.compile("Xunitary")
        expected = sf.Program(8)

        with expected.context as q:
            ops.S2gate(SQ_AMPLITUDE, 0) | (q[0], q[4])
            ops.S2gate(SQ_AMPLITUDE, 0) | (q[1], q[5])
            ops.S2gate(SQ_AMPLITUDE, 0) | (q[2], q[6])
            ops.S2gate(SQ_AMPLITUDE, 0) | (q[3], q[7])

            # corresponds to an identity on modes [0, 1, 2, 3]
            # This can be easily seen from below by noting that:
            # MZ(pi, pi) = R(0) = I
            # MZ(pi, 0) @ MZ(pi, 0) = I
            # [R(pi) \otimes I] @ MZ(pi, 0) = I
            ops.MZgate(np.pi, 0) | (q[0], q[1])
            ops.MZgate(np.pi, 0) | (q[2], q[3])
            ops.MZgate(np.pi, np.pi) | (q[1], q[2])
            ops.MZgate(np.pi, np.pi) | (q[0], q[1])
            ops.MZgate(np.pi, 0) | (q[2], q[3])
            ops.MZgate(np.pi, np.pi) | (q[1], q[2])
            ops.Rgate(np.pi) | (q[0])
            ops.Rgate(0) | (q[1])
            ops.Rgate(0) | (q[2])
            ops.Rgate(0) | (q[3])

            # corresponds to an identity on modes [4, 5, 6, 7]
            ops.MZgate(np.pi, 0) | (q[4], q[5])
            ops.MZgate(np.pi, 0) | (q[6], q[7])
            ops.MZgate(np.pi, np.pi) | (q[5], q[6])
            ops.MZgate(np.pi, np.pi) | (q[4], q[5])
            ops.MZgate(np.pi, 0) | (q[6], q[7])
            ops.MZgate(np.pi, np.pi) | (q[5], q[6])
            ops.Rgate(np.pi) | (q[4])
            ops.Rgate(0) | (q[5])
            ops.Rgate(0) | (q[6])
            ops.Rgate(0) | (q[7])

            ops.MeasureFock() | q

        assert program_equivalence(res, expected, atol=tol, compare_params=False)

        # double check that the applied symplectic is correct

        # remove the Fock measurements
        res.circuit = res.circuit[:-1]

        # extract the Gaussian symplectic matrix
        O = res.compile("gaussian_unitary").circuit[0].op.p[0]

        # construct the expected symplectic matrix corresponding
        # to just the initial two mode squeeze gates
        S = two_mode_squeezing(SQ_AMPLITUDE, 0)
        num_modes = 8
        expected = np.identity(2 * num_modes)
        for i in range(num_modes // 2):
            expected = expand(S, [i, i + num_modes // 2], num_modes) @ expected
        # Note that the comparison has to be made at the level of covariance matrices
        # Not at the level of symplectic matrices
        assert np.allclose(O @ O.T, expected @ expected.T, atol=tol)