Example #1
0
    def test_decomposition(self, tol):
        """Test that an graph is correctly decomposed"""
        n = 3
        prog = sf.Program(n)

        A = np.random.random([n, n]) + 1j * np.random.random([n, n])
        A += A.T
        A -= np.trace(A) * np.identity(n) / n

        sq, U = dec.graph_embed(A)

        G = ops.GraphEmbed(A)
        cmds = G.decompose(prog.register)

        assert np.all(sq == G.sq)
        assert np.all(U == G.U)

        S = np.identity(2 * n)

        # calculating the resulting decomposed symplectic
        for cmd in cmds:
            # all operations should be BSgates, Rgates, or Sgates
            assert isinstance(
                cmd.op, (ops.Interferometer, ops.BSgate, ops.Rgate, 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], cmd.op.p[1], modes, n) @ S

            if isinstance(cmd.op, ops.Rgate):
                S = _rotation(cmd.op.p[0], modes, n) @ S

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

            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

        # calculate Hamilton's A matrix: A = X.(I-Q^{-1})*
        A_res = np.real_if_close(Amat(cov))

        # The bottom right corner of A_res should be identical to A,
        # up to some constant scaling factor. Check if the ratio
        # of all elements is one
        ratio = np.real_if_close(A_res[n:, n:] / A)
        ratio /= ratio[0, 0]

        assert np.allclose(ratio, np.ones([n, n]), atol=tol, rtol=0)
Example #2
0
def test_Amat_TMS_using_cov():
    """test Amat returns correct result for a two-mode squeezed state"""
    V = TMS_cov(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)
Example #3
0
def test_numba_tor(N):
    """Tests numba implementations of the torontonian against the default implementation"""
    cov = random_covariance(N)
    O = Xmat(N) @ Amat(cov)
    t1 = tor(O)
    t2 = numba_tor(O)
    t3 = rec_torontonian(O)
    assert np.isclose(t1, t2)
    assert np.isclose(t1, t3)
Example #4
0
def test_Amat_TMS_using_Q():
    """test Amat returns correct result for a two-mode squeezed state"""
    q = np.fliplr(np.diag([2.0] * 4))
    np.fill_diagonal(q, np.sqrt(2))
    Q = np.fliplr(q)
    res = Amat(Q, cov_is_qmat=True)

    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)
Example #5
0
def test_numba_ltor(N):
    """Tests numba implementations of the loop torontonian against the default
    implementation"""
    alpha = np.random.random(N) + np.random.random(N) * 1j
    alpha = np.concatenate((alpha, alpha.conj()))
    cov = random_covariance(N)
    O = Xmat(N) @ Amat(cov)
    mu = O @ alpha
    t1 = ltor(O, mu)
    t2 = numba_ltor(O, mu)
    t3 = rec_ltorontonian(O, mu)
    assert np.isclose(t1, t2)
    assert np.isclose(t1, t3)
Example #6
0
def reference_count_unnormalized(cov, only_modes=None):
    A = Amat(cov)
    modes = cov.shape[0] // 2

    if only_modes is not None:
        mask = only_modes + tuple(x + modes for x in only_modes)
        A = A[mask, :][:, mask]
        xmat_size = len(only_modes)
    else:
        xmat_size = modes

    O = Xmat(xmat_size) @ A
    return tor(O).real
Example #7
0
def test_tor_and_threshold_displacement_prob_agree(n_modes):
    """Tests that threshold_detection_prob, ltor and the usual tor expression all agree
    when displacements are zero"""
    cv = random_covariance(n_modes)
    mu = np.zeros([2 * n_modes])
    Q = Qmat(cv)
    O = Xmat(n_modes) @ Amat(cv)
    expected = tor(O) / np.sqrt(np.linalg.det(Q))
    prob = threshold_detection_prob(mu, cv, np.array([1] * n_modes))
    prob2 = numba_ltor(O, mu) / np.sqrt(np.linalg.det(Q))
    prob3 = rec_ltorontonian(O, mu) / np.sqrt(np.linalg.det(Q))
    prob4 = numba_vac_prob(mu, Q) * ltor(O, mu)
    assert np.isclose(expected, prob)
    assert np.isclose(expected, prob2)
    assert np.isclose(expected, prob3)
    assert np.isclose(expected, prob4)
Example #8
0
    def compile(self, seq, registers):
        # the number of modes in the provided program
        n_modes = len(registers)

        # Number of modes must be even
        if n_modes % 2 != 0:
            raise CircuitError("The X series only supports programs with an even number of modes.")

        # Call the GBS compiler to do basic measurement validation.
        # The GBS compiler also merges multiple measurement commands
        # into a single MeasureFock command at the end of the circuit.
        seq = GBSSpecs().compile(seq, registers)

        # ensure that all modes are measured
        if len(seq[-1].reg) != n_modes:
            raise CircuitError("All modes must be measured.")

        # Use the GaussianUnitary compiler to compute the symplectic
        # matrix representing the Gaussian operations.
        # Note that the Gaussian unitary compiler does not accept measurements,
        # so we append the measurement separately.
        meas_seq = [seq[-1]]
        seq = GaussianUnitary().compile(seq[:-1], registers) + meas_seq

        # determine the modes that are acted on by the symplectic transformation
        used_modes = [x.ind for x in seq[0].reg]

        # extract the compiled symplectic matrix
        S = seq[0].op.p[0]

        if len(used_modes) != n_modes:
            # The symplectic transformation acts on a subset of
            # the programs registers. We must expand the symplectic
            # matrix to one that acts on all registers.
            # simply extract the computed symplectic matrix
            S = expand(seq[0].op.p[0], used_modes, n_modes)

        half_n_modes = n_modes // 2

        # Construct the covariance matrix of the state.
        # Note that hbar is a global variable that is set by the user
        cov = (sf.hbar / 2) * S @ S.T

        # Construct the A matrix
        A = Amat(cov, hbar=sf.hbar)

        # Construct the adjacency matrix represented by the A matrix.
        # This must be an weighted, undirected bipartite graph. That is,
        # B00 = B11 = 0 (no edges between the two vertex sets 0 and 1),
        # and B01 == B10.T (undirected edges between the two vertex sets).
        B = A[:n_modes, :n_modes]
        B00 = B[:half_n_modes, :half_n_modes]
        B01 = B[:half_n_modes, half_n_modes:]
        B10 = B[half_n_modes:, :half_n_modes]
        B11 = B[half_n_modes:, half_n_modes:]

        # Perform unitary validation to ensure that the
        # applied unitary is valid.

        if not np.allclose(B00, 0) or not np.allclose(B11, 0):
            # Not a bipartite graph
            raise CircuitError(
                "The applied unitary cannot mix between the modes {}-{} and modes {}-{}.".format(
                    0, half_n_modes - 1, half_n_modes, n_modes - 1
                )
            )

        if not np.allclose(B01, B10):
            # Not a symmetric bipartite graph
            raise CircuitError(
                "The applied unitary on modes {}-{} must be identical to the applied unitary on modes {}-{}.".format(
                    0, half_n_modes - 1, half_n_modes, n_modes - 1
                )
            )

        # Now that the unitary has been validated, perform the Takagi decomposition
        # to determine the constituent two-mode squeezing and interferometer
        # parameters.
        sqs, U = takagi(B01)
        sqs = np.arctanh(sqs)

        # ensure provided S2gates all have the allowed squeezing values
        if not all(s in self.allowed_sq_ranges for s in sqs):
            wrong_sq_values = [np.round(s, 4) for s in sqs if s not in self.allowed_sq_ranges]
            raise CircuitError(
                "Incorrect squeezing value(s) r={}. Allowed squeezing "
                "value(s) are {}.".format(wrong_sq_values, self.allowed_sq_ranges)
            )

        # Convert the squeezing values into a sequence of S2gate commands
        sq_seq = [
            Command(ops.S2gate(sqs[i]), [registers[i], registers[i + half_n_modes]])
            for i in range(half_n_modes)
        ]

        # NOTE: at some point, it might make sense to add a keyword argument to this method,
        # to allow the user to specify if they want the interferometers decomposed or not.

        # Convert the unitary into a sequence of MZgate and Rgate commands on the signal modes
        U1 = ops.Interferometer(U, mesh="rectangular_symmetric", drop_identity=False)._decompose(
            registers[:half_n_modes]
        )
        U2 = copy.deepcopy(U1)

        for Ui in U2:
            Ui.reg = [registers[r.ind + half_n_modes] for r in Ui.reg]

        return sq_seq + U1 + U2 + meas_seq
Example #9
0
def test_Amat_vacuum_using_Q():
    """test Amat returns correct result for a vacuum state"""
    Q = np.identity(2)
    res = Amat(Q, cov_is_qmat=True)
    ex = np.zeros([2, 2])
    assert np.allclose(res, ex)
Example #10
0
def test_Amat_vacuum_using_cov():
    """test Amat returns correct result for a vacuum state"""
    V = np.identity(2)
    res = Amat(V)
    ex = np.zeros([2, 2])
    assert np.allclose(res, ex)
Example #11
0
    def test_decomposition(self, tol):
        """Test that a graph is correctly decomposed"""
        n = 3
        prog = sf.Program(2*n)

        A = np.zeros([2*n, 2*n])
        B = np.random.random([n, n])

        A[:n, n:] = B
        A += A.T

        sq, U, V = dec.bipartite_graph_embed(B)

        G = ops.BipartiteGraphEmbed(A)
        cmds = G.decompose(prog.register)

        S = np.identity(4 * n)

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

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

            if isinstance(cmd.op, ops.S2gate):
                # check that the registers are i, i+n
                assert len(modes) == 2
                assert modes[1] == modes[0] + n

                r, phi = par_evaluate(cmd.op.p)
                assert -r in sq
                assert phi == 0

                S = _two_mode_squeezing(r, phi, modes, 2*n) @ S

            if isinstance(cmd.op, ops.Interferometer):
                # check that each unitary only applies to half the modes
                assert len(modes) == n
                assert modes in ([0, 1, 2], [3, 4, 5])

                # check matrix is unitary
                U1 = par_evaluate(cmd.op.p[0])
                assert np.allclose(U1 @ U1.conj().T, np.identity(n), atol=tol, rtol=0)

                if modes[0] == 0:
                    assert np.allclose(U1, U, atol=tol, rtol=0)
                else:
                    assert modes[0] == 3
                    assert np.allclose(U1, V, atol=tol, rtol=0)

                S_U = np.vstack(
                    [np.hstack([U1.real, -U1.imag]), np.hstack([U1.imag, U1.real])]
                )

                S = expand(S_U, modes, 2*n) @ S

        # the resulting covariance state
        cov = S @ S.T
        A_res = Amat(cov)[:2*n, :2*n]

        # The bottom right corner of A_res should be identical to A,
        # up to some constant scaling factor. Check if the ratio
        # of all elements is one
        ratio = np.real_if_close(A_res[n:, :n] / B.T)
        ratio /= ratio[0, 0]

        assert np.allclose(ratio, np.ones([n, n]), atol=tol, rtol=0)