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)
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)
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)
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)
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)
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)
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)
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)
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)
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)
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)
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)
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)
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)
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)
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)
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)
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)
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)
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)
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)
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)
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)
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
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)