def test_beamsplitter(self, tol): """Test that an interferometer returns correct symplectic for an arbitrary beamsplitter""" theta = 0.98 phi = 0.41 U = symplectic.beam_splitter(theta, phi) S = symplectic.interferometer(U) expected = np.block([[U.real, -U.imag], [U.imag, U.real]]) np.allclose(S, expected, atol=tol, rtol=0)
def test_hong_ou_mandel_interference(choi_r, phi, 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`. """ S = beam_splitter(np.pi / 4, phi) # a 50-50 beamsplitter with phase phi cutoff = 2 nmodes = 2 alphas = np.zeros([nmodes]) T = fock_tensor(S, alphas, cutoff, choi_r=choi_r) assert np.allclose(T[1, 1, 1, 1], 0.0, 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 beamsplitter(self, theta, phi, k, l): r"""Implement a beam splitter operation between modes k and l. Args: theta (float): real beamsplitter angle phi (float): complex beamsplitter angle k (int): first mode l (int): second mode Raises: ValueError: if any of the two modes is not in the list of active modes ValueError: if the first mode equals the second mode """ if self.active[k] is None or self.active[l] is None: raise ValueError( "Cannot perform beamsplitter, mode(s) do not exist") if k == l: raise ValueError( "Cannot use the same mode for beamsplitter inputs.") bs = symp.expand(symp.beam_splitter(theta, phi), [k, l], self.nlen) self.means = update_means(self.means, bs, self.from_xp) self.covs = update_covs(self.covs, bs, self.from_xp)
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