def test_squeezing_no_phi_array(self, tol): """Test multimode squeezing symplectic transform without specifying phi""" r = np.random.randn(6) phi = np.zeros_like(r) S = symplectic.squeezing(r) S_phi = symplectic.squeezing(r, phi) assert np.allclose(S, S_phi, atol=tol, rtol=0)
def test_dtype(self, tol): """Test multimode version gives symplectic matrix""" r = [0.543] * 4 phi = [0.123] * 4 S = symplectic.squeezing(r, phi) S32_bit = symplectic.squeezing(r, phi, dtype=np.float32) # the symplectic matrix O = symplectic.sympmat(4) assert np.allclose(S32_bit @ O @ S32_bit.T, O, atol=tol, rtol=0) assert np.allclose(S, S32_bit, atol=tol, rtol=0)
def test_symplectic(self, tol): """Test that the squeeze operator is symplectic""" r = 0.543 phi = 0.123 S = symplectic.squeezing(r, phi) # the symplectic matrix O = np.array([[0, 1], [-1, 0]]) assert np.allclose(S @ O @ S.T, O, atol=tol, rtol=0)
def test_symplectic_multimode(self, tol): """Test multimode version gives symplectic matrix""" r = [0.543] * 4 phi = [0.123] * 4 S = symplectic.squeezing(r, phi) # the symplectic matrix O = symplectic.sympmat(4) assert np.allclose(S @ O @ S.T, O, atol=tol, rtol=0)
def test_squeezing(self, tol): """Test the squeezing symplectic transform.""" r = 0.543 phi = 0.123 S = symplectic.squeezing(r, phi) out = S @ S.T # apply to an identity covariance matrix rotation = np.array([[np.cos(phi / 2), -np.sin(phi / 2)], [np.sin(phi / 2), np.cos(phi / 2)]]) expected = rotation @ np.diag(np.exp([-2 * r, 2 * r])) @ rotation.T assert np.allclose(out, expected, atol=tol, rtol=0)
def squeeze(self, r, phi, k): r"""Squeeze mode ``k`` by the amount ``r*exp(1j*phi)``. Args: r (float): squeezing magnitude phi (float): squeezing phase k (int): mode to be squeezed Raises: ValueError: if the mode is not in the list of active modes """ if self.active[k] is None: raise ValueError("Cannot squeeze mode, mode does not exist") sq = symp.expand(symp.squeezing(r, phi), k, self.nlen) self.means = update_means(self.means, sq, self.from_xp) self.covs = update_covs(self.covs, sq, self.from_xp)
def test_pnd_squeeze_displace(tol, r, phi, alpha, hbar): """Test the photon number distribution for the squeezed displaced state Eq. (17) in 'Benchmarking of Gaussian boson sampling using two-point correlators', Phillips et al. (https://ris.utwente.nl/ws/files/122721825/PhysRevA.99.023836.pdf). """ S = squeezing(r, phi) mu = [np.sqrt(2 * hbar) * np.real(alpha), np.sqrt(2 * hbar) * np.imag(alpha)] cov = hbar / 2 * (S @ S.T) pnd_cov = photon_number_covmat(mu, cov, hbar=hbar) pnd_cov_analytic = np.sinh(r) ** 2 * np.cosh(r) ** 2 + np.sinh(r) ** 4 \ + np.sinh(r) ** 2 + np.abs(alpha) ** 2 * (1 + 2 * np.sinh(r) ** 2) \ - 2 * np.real(alpha ** 2 * np.exp(-1j * phi) * np.sinh(r) * np.cosh(r)) assert np.isclose(float(pnd_cov), pnd_cov_analytic, 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_single_mode_displacement_squeezing(choi_r, tol): """Tests the correct construction of the single mode squeezing operation followed by the single mode displacement operation""" nmodes = 1 s = 1.0 cutoff = 5 S = squeezing(s, 0.0) alphas = (0.5 + 0.4 * 1j) * np.ones([nmodes]) # This data is obtained by using qutip # np.array((displace(40,alpha)*squeeze(40,r)).data.todense())[0:5,0:5] expected = np.array( [ [0.6263739 + 0.09615331j, -0.22788717 + 0.13121343j, 0.36548296 - 0.0200537j, -0.20708137 + 0.14004403j, 0.25645667 - 0.06275564j], [0.5425389 + 0.14442404j, 0.19268911 + 0.15615312j, 0.11497303 + 0.13744549j, 0.21448948 + 0.08109308j, -0.03652914 + 0.15069359j], [-0.00915607 + 0.07475267j, 0.48081922 + 0.10576742j, -0.00961086 + 0.20535144j, 0.33089303 + 0.09864247j, 0.02555522 + 0.19950786j], [-0.34614367 - 0.05229875j, 0.11543956 + 0.01112537j, 0.16672961 + 0.07439407j, 0.02323121 + 0.15103267j, 0.27233637 + 0.08297028j], [-0.14390852 - 0.08884069j, -0.37898007 - 0.07630228j, 0.12911863 - 0.08963054j, -0.12164023 + 0.04431394j, 0.1141808 + 0.01581529j], ] ) T = fock_tensor(S, alphas, cutoff, choi_r=choi_r) assert np.allclose(T, expected, atol=tol, rtol=0)
def test_single_mode_squeezing(choi_r, tol): """Tests the correct construction of the single mode squeezing operation""" nmodes = 1 s = 1.0 cutoff = 5 S = squeezing(s, 0.0) alphas = np.zeros([nmodes]) # This data is obtained by using qutip # np.array(squeeze(40,r).data.todense())[0:5,0:5] expected = np.array( [ [0.80501818 + 0.0j, 0.0 + 0.0j, 0.43352515 + 0.0j, 0.0 + 0.0j, 0.2859358 + 0.0j], [0.0 + 0.0j, 0.52169547 + 0.0j, 0.0 + 0.0j, 0.48661591 + 0.0j, 0.0 + 0.0j], [-0.43352515 + 0.0j, 0.0 + 0.0j, 0.10462138 + 0.0j, 0.0 + 0.0j, 0.29199268 + 0.0j], [0.0 + 0.0j, -0.48661591 + 0.0j, 0.0 + 0.0j, -0.23479643 + 0.0j, 0.0 + 0.0j], [0.2859358 + 0.0j, 0.0 + 0.0j, -0.29199268 + 0.0j, 0.0 + 0.0j, -0.34474749 + 0.0j], ] ) T = fock_tensor(S, alphas, cutoff, choi_r=choi_r) assert np.allclose(T, expected, atol=tol, rtol=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_cumulants_three_mode_random_state(hbar): # pylint: disable=too-many-statements """Tests third order cumulants for a random state""" M = 3 O = interferometer(random_interferometer(3)) mu = np.random.rand(2 * M) - 0.5 hbar = 2 cov = 0.5 * hbar * O @ squeezing(np.random.rand(M)) @ O.T cutoff = 50 probs = probabilities(mu, cov, cutoff, hbar=hbar) n = np.arange(cutoff) probs0 = np.sum(probs, axis=(1, 2)) probs1 = np.sum(probs, axis=(0, 2)) probs2 = np.sum(probs, axis=(0, 1)) # Check one body cumulants n0_1 = n @ probs0 n1_1 = n @ probs1 n2_1 = n @ probs2 assert np.allclose(photon_number_cumulant(mu, cov, [0], hbar=hbar), n0_1) assert np.allclose(photon_number_cumulant(mu, cov, [1], hbar=hbar), n1_1) assert np.allclose(photon_number_cumulant(mu, cov, [2], hbar=hbar), n2_1) n0_2 = n**2 @ probs0 n1_2 = n**2 @ probs1 n2_2 = n**2 @ probs2 var0 = n0_2 - n0_1**2 var1 = n1_2 - n1_1**2 var2 = n2_2 - n2_1**2 assert np.allclose(photon_number_cumulant(mu, cov, [0, 0], hbar=hbar), var0) assert np.allclose(photon_number_cumulant(mu, cov, [1, 1], hbar=hbar), var1) assert np.allclose(photon_number_cumulant(mu, cov, [2, 2], hbar=hbar), var2) n0_3 = n**3 @ probs0 - 3 * n0_2 * n0_1 + 2 * n0_1**3 n1_3 = n**3 @ probs1 - 3 * n1_2 * n1_1 + 2 * n1_1**3 n2_3 = n**3 @ probs2 - 3 * n2_2 * n2_1 + 2 * n2_1**3 assert np.allclose(photon_number_cumulant(mu, cov, [0, 0, 0], hbar=hbar), n0_3) assert np.allclose(photon_number_cumulant(mu, cov, [1, 1, 1], hbar=hbar), n1_3) assert np.allclose(photon_number_cumulant(mu, cov, [2, 2, 2], hbar=hbar), n2_3) # Check two body cumulants probs01 = np.sum(probs, axis=(2)) probs02 = np.sum(probs, axis=(1)) probs12 = np.sum(probs, axis=(0)) n0n1 = n @ probs01 @ n n0n2 = n @ probs02 @ n n1n2 = n @ probs12 @ n covar01 = n0n1 - n0_1 * n1_1 covar02 = n0n2 - n0_1 * n2_1 covar12 = n1n2 - n1_1 * n2_1 assert np.allclose(photon_number_cumulant(mu, cov, [0, 1], hbar=hbar), covar01) assert np.allclose(photon_number_cumulant(mu, cov, [0, 2], hbar=hbar), covar02) assert np.allclose(photon_number_cumulant(mu, cov, [1, 2], hbar=hbar), covar12) kappa001 = n**2 @ probs01 @ n - 2 * n0n1 * n0_1 - n0_2 * n1_1 + 2 * n0_1**2 * n1_1 kappa011 = n @ probs01 @ n**2 - 2 * n0n1 * n1_1 - n1_2 * n0_1 + 2 * n1_1**2 * n0_1 kappa002 = n**2 @ probs02 @ n - 2 * n0n2 * n0_1 - n0_2 * n2_1 + 2 * n0_1**2 * n2_1 kappa022 = n @ probs02 @ n**2 - 2 * n0n2 * n2_1 - n2_2 * n0_1 + 2 * n2_1**2 * n0_1 kappa112 = n**2 @ probs12 @ n - 2 * n1n2 * n1_1 - n1_2 * n2_1 + 2 * n1_1**2 * n2_1 kappa122 = n @ probs12 @ n**2 - 2 * n1n2 * n2_1 - n2_2 * n1_1 + 2 * n2_1**2 * n1_1 assert np.allclose(photon_number_cumulant(mu, cov, [0, 0, 1], hbar=hbar), kappa001) assert np.allclose(photon_number_cumulant(mu, cov, [0, 1, 1], hbar=hbar), kappa011) assert np.allclose(photon_number_cumulant(mu, cov, [0, 0, 2], hbar=hbar), kappa002) assert np.allclose(photon_number_cumulant(mu, cov, [0, 2, 2], hbar=hbar), kappa022) assert np.allclose(photon_number_cumulant(mu, cov, [1, 1, 2], hbar=hbar), kappa112) assert np.allclose(photon_number_cumulant(mu, cov, [1, 2, 2], hbar=hbar), kappa122) # Finally, the three body cumulant n0n1n2 = np.einsum("ijk, i, j, k", probs, n, n, n) kappa012 = n0n1n2 - n0n1 * n2_1 - n0n2 * n1_1 - n1n2 * n0_1 + 2 * n0_1 * n1_1 * n2_1 assert np.allclose(photon_number_cumulant(mu, cov, [0, 1, 2], hbar=hbar), kappa012)