Beispiel #1
0
    def test_merge(self, hbar, tol):
        """Test that two symplectics merge: S = S2 @ S1"""
        n = 3
        S1 = random_symplectic(n)
        S2 = random_symplectic(n)

        G1 = ops.GaussianTransform(S1)
        G1inv = ops.GaussianTransform(np.linalg.inv(S1))
        G2 = ops.GaussianTransform(S2)

        # a symplectic merged with its inverse is identity
        assert G1.merge(G1inv) is None

        # two merged symplectics are the same as their product
        assert np.allclose(G1.merge(G2).p[0].x, S2 @ S1, atol=tol, rtol=0)
Beispiel #2
0
    def test_decomposition_passive(self, tol):
        """Test that a passive symplectic is correctly decomposed into an interferometer"""
        n = 3
        S = random_symplectic(n, passive=True)
        X1 = S[:n, :n]
        P1 = S[n:, :n]
        U1 = X1 + 1j * P1

        prog = sf.Program(n)
        G = ops.GaussianTransform(S)
        cmds = G.decompose(prog.register)

        S = np.identity(2 * n)

        # command queue should have 1 interferometer
        assert len(cmds) == 1

        # calculating the resulting decomposed symplectic
        for cmd in cmds:
            # all operations should be Interferometers
            assert isinstance(cmd.op, ops.Interferometer)

            # build up the symplectic transform
            #modes = [i.ind for i in cmd.reg]

            if isinstance(cmd.op, ops.Interferometer):
                U1 = cmd.op.p[0]
                S_U = np.vstack(
                    [np.hstack([U1.real, -U1.imag]), np.hstack([U1.imag, U1.real])]
                )
                S = S_U @ S

        # the resulting covariance state
        cov = S @ S.T
        assert np.allclose(cov, S @ S.T, atol=tol, rtol=0)
    def test_setting_hbar(self, hbar):
        """Test that an exception is raised if hbar not provided"""
        prog = sf.Program(3, hbar=hbar)
        S1 = random_symplectic(3, passive=False)

        with pytest.raises(ValueError, match="specify the hbar keyword argument"):
            ops.GaussianTransform(S1)

        # hbar can be passed as a keyword arg
        G = ops.GaussianTransform(S1, hbar=hbar)
        assert G.hbar == hbar

        # or determined via the engine context
        with eng:
            G = ops.GaussianTransform(S1)

        assert G.hbar == hbar
Beispiel #4
0
    def test_gaussian_transform(self, setup_eng, hbar, tol):
        """Test applying a Gaussian symplectic transform"""
        eng, prog = setup_eng(3)

        with prog.context as q:
            ops.GaussianTransform(S) | q

        state = eng.run(prog).state
        assert np.allclose(state.cov(), S @ S.T * hbar / 2, atol=tol)
Beispiel #5
0
    def test_passive(self, tol):
        """Test that a passive decomposition is correctly flagged as requiring
        only a single interferometer"""
        G = ops.GaussianTransform(np.identity(6))

        assert not G.active
        assert hasattr(G, "U1")
        assert not hasattr(G, "Sq")
        assert not hasattr(G, "U2")
Beispiel #6
0
    def test_active_gaussian_transform_on_vacuum(self, setup_eng, hbar, tol):
        """Test applying a passive Gaussian symplectic transform,
        which is simply squeezing and ONE interferometer"""
        eng, prog = setup_eng(3)

        with prog.context as q:
            ops.GaussianTransform(S, vacuum=True) | q

        state = eng.run(prog).state
        assert np.allclose(state.cov(), S @ S.T * hbar / 2, atol=tol)
Beispiel #7
0
    def test_active(self, tol):
        """Test that an active decomposition is correctly flagged as requiring
        two interferometers and squeezing"""
        S1 = random_symplectic(3, passive=False)
        G = ops.GaussianTransform(S1)

        assert G.active
        assert hasattr(G, "U1")
        assert hasattr(G, "Sq")
        assert hasattr(G, "U2")
    def test_decomposition_active(self, hbar, tol):
        """Test that an active symplectic is correctly decomposed into
        two interferometers and squeezing"""
        n = 3
        S = random_symplectic(n, passive=False)

        O1, Sq, O2 = dec.bloch_messiah(S)
        X1 = O1[:n, :n]
        P1 = O1[n:, :n]
        X2 = O2[:n, :n]
        P2 = O2[n:, :n]
        U1 = X1 + 1j * P1
        U2 = X2 + 1j * P2

        prog = sf.Program(n, hbar=hbar)

        with eng:
            G = ops.GaussianTransform(S)
            cmds = G.decompose(q)

        assert np.all(U1 == G.U1)
        assert np.all(U2 == G.U2)
        assert np.all(np.diag(Sq)[:n] == G.Sq)

        S = np.identity(2 * n)

        # command queue should have 2 interferometers, 3 squeezers
        assert len(cmds) == 5

        # calculating the resulting decomposed symplectic
        for cmd in cmds:
            # all operations should be BSgates, Rgates, or Sgates
            assert isinstance(cmd.op, (ops.Interferometer, ops.Sgate))

            # build up the symplectic transform
            modes = [i.ind for i in cmd.reg]

            if isinstance(cmd.op, ops.Sgate):
                S = _squeezing(cmd.op.p[0].x, cmd.op.p[1].x, modes, n) @ S

            if isinstance(cmd.op, ops.Interferometer):
                U1 = cmd.op.p[0].x
                S_U = np.vstack([
                    np.hstack([U1.real, -U1.imag]),
                    np.hstack([U1.imag, U1.real])
                ])
                S = S_U @ S

        # the resulting covariance state
        cov = S @ S.T

        assert np.allclose(cov, S @ S.T * hbar / 2, atol=tol, rtol=0)
def test_symplectic_composition(depth, width):
    """Tests that symplectic operations are composed correctly"""
    eng = sf.LocalEngine(backend="gaussian")
    eng1 = sf.LocalEngine(backend="gaussian")
    circuit = sf.Program(width)
    Snet = np.identity(2 * width)
    with circuit.context as q:
        for _ in range(depth):
            S = random_symplectic(width, scale=0.2)
            Snet = S @ Snet
            ops.GaussianTransform(S) | q
    compiled_circuit = circuit.compile(compiler="gaussian_unitary")
    assert np.allclose(compiled_circuit.circuit[0].op.p[0], Snet)
Beispiel #10
0
    def test_passive_gaussian_transform(self, setup_eng, tol):
        """Test applying a passive Gaussian symplectic transform,
        which is simply an interferometer"""
        eng, q = setup_eng(3)
        O = np.vstack(
            [np.hstack([u1.real, -u1.imag]),
             np.hstack([u1.imag, u1.real])])

        with eng:
            ops.All(ops.Squeezed(0.5)) | q
            init = eng.run()
            ops.GaussianTransform(O) | q

        state = eng.run()
        assert np.allclose(state.cov(), O @ init.cov() @ O.T, atol=tol)
Beispiel #11
0
    def test_active_on_vacuum(self, hbar, tol):
        """Test that an active symplectic applied to a vacuum is
        correctly decomposed into just squeezing and one interferometer"""
        n = 3
        S = random_symplectic(n, passive=False)

        O1, Sq, O2 = dec.bloch_messiah(S)
        X1 = O1[:n, :n]
        P1 = O1[n:, :n]
        X2 = O2[:n, :n]
        P2 = O2[n:, :n]

        U1 = X1 + 1j * P1
        U2 = X2 + 1j * P2

        prog = sf.Program(n)
        G = ops.GaussianTransform(S, vacuum=True)
        cmds = G.decompose(prog.register)

        S = np.identity(2 * n)

        # command queue should have 3 Sgates, 1 interferometer
        assert len(cmds) == 4

        # calculating the resulting decomposed symplectic
        for cmd in cmds:
            # all operations should be Interferometers or Sgates
            assert isinstance(cmd.op, (ops.Interferometer, ops.Sgate))

            # build up the symplectic transform
            modes = [i.ind for i in cmd.reg]

            if isinstance(cmd.op, ops.Sgate):
                S = _squeezing(cmd.op.p[0].x, cmd.op.p[1].x, modes, n) @ S

            if isinstance(cmd.op, ops.Interferometer):
                U1 = cmd.op.p[0].x
                S_U = np.vstack([
                    np.hstack([U1.real, -U1.imag]),
                    np.hstack([U1.imag, U1.real])
                ])
                S = S_U @ S

        # the resulting covariance state
        cov = S @ S.T

        assert np.allclose(cov, S @ S.T, atol=tol, rtol=0)
Beispiel #12
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