コード例 #1
0
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)
コード例 #2
0
    def test_displaced_loss_against_interferometer(self, hbar, tol):
        """Test that the loss channel on a displaced state corresponds to a beamsplitter
        acting on the mode with loss and an ancilla vacuum state"""
        T = 0.812

        alpha = np.random.random(size=[2]) + np.random.random(size=[2]) * 1j
        mu = np.concatenate([alpha.real, alpha.imag])

        # perform loss
        mu_res, _ = symplectic.loss(mu, np.identity(4), 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)

        # apply the beamsplitter to modes 0 and 2
        mu_expand = np.zeros([6])
        mu_expand[np.array([0, 1, 3, 4])] = mu
        mu_expected, _ = symplectic.reduced_state(B @ mu_expand,
                                                  np.identity(6),
                                                  modes=[0, 1])

        # compare loss function result to an interferometer mixing mode 0 with the vacuum
        assert np.allclose(mu_expected, mu_res, atol=tol, rtol=0)
コード例 #3
0
    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)
コード例 #4
0
    def expandS(self, modes, S):
        """Expands symplectic matrix on subset of modes to symplectic matrix for the whole system.

        Args:
            modes (list): list of modes on which S acts
            S (array): symplectic matrix
        """
        return symp.expand(S, modes, self.nlen)
コード例 #5
0
    def expandXY(self, modes, X, Y):
        """Expands deterministic Gaussian CPTP matrices ``(X,Y)`` on subset of modes to
        transformations for the whole system.

        Args:
            modes (list): list of modes on which ``(X,Y)`` act
            X (array): matrix for mutltiplicative part of transformation
            Y (array): matrix for additive part of transformation
        """
        X2 = symp.expand(X, modes, self.nlen)
        Y2 = symp.expand(Y, modes, self.nlen)
        for i in range(self.nlen):
            if i not in modes:
                Y2[i, i] = 0
                Y2[i + self.nlen, i + self.nlen] = 0

        return X2, Y2
コード例 #6
0
    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)
コード例 #7
0
def test_two_mode_gate_expand(M, tol):
    """test _apply_symp_two_mode_gate applies correctly"""
    S = np.random.random((2 * M, 2 * M))
    r = np.random.random(2 * M)
    S_G = interferometer(0.5**0.5 * np.fft.fft(np.eye(2)))

    S1, r1 = _apply_symp_two_mode_gate(S_G, S.copy(), r.copy(), 1, 3)

    S_G_expand = expand(S_G, [1, 3], M)
    S2 = S_G_expand @ S
    r2 = S_G_expand @ r

    assert np.allclose(S1, S2, atol=tol, rtol=0)
    assert np.allclose(r1, r2, atol=tol, rtol=0)
コード例 #8
0
def test_one_mode_gate_expand(M, tol):
    """test _apply_symp_one_mode_gate applies correctly on a larger matrices"""
    S = np.random.random((2 * M, 2 * M))
    r = np.random.random(2 * M)
    S_G = interferometer(np.exp(1j * 0.3))

    S1, r1 = _apply_symp_one_mode_gate(S_G, S.copy(), r.copy(), 1)

    S_G_expand = expand(S_G, [1], M)
    S2 = S_G_expand @ S
    r2 = S_G_expand @ r

    assert np.allclose(S1, S2, atol=tol, rtol=0)
    assert np.allclose(r1, r2, atol=tol, rtol=0)
コード例 #9
0
    def phase_shift(self, phi, k):
        r"""Implement a phase shift in mode k.

        Args:
           phi (float): phase
           k (int): mode to be phase shifted

        Raises:
            ValueError: if the mode is not in the list of active modes
        """
        if self.active[k] is None:
            raise ValueError("Cannot phase shift mode, mode does not exist")

        rot = symp.expand(symp.rotation(phi), k, self.nlen)
        self.means = update_means(self.means, rot, self.from_xp)
        self.covs = update_covs(self.covs, rot, self.from_xp)
コード例 #10
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)
コード例 #11
0
    def test_expand_one(self, mode, tol):
        """Test expanding a one mode gate"""
        r = 0.1
        phi = 0.423
        N = 3

        S = 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)],
        ])

        res = symplectic.expand(S, modes=mode, N=N)

        expected = np.identity(2 * N)
        expected[mode, mode] = S[0, 0]
        expected[mode, mode + N] = S[0, 1]
        expected[mode + N, mode] = S[1, 0]
        expected[mode + N, mode + N] = S[1, 1]

        assert np.allclose(res, expected, atol=tol, rtol=0)
コード例 #12
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)
コード例 #13
0
    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)
コード例 #14
0
    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)
コード例 #15
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
コード例 #16
0
ファイル: quantum.py プロジェクト: fagan2888/thewalrus
def fock_tensor(S,
                alpha,
                cutoff,
                choi_r=np.arcsinh(1.0),
                check_symplectic=True,
                sf_order=False):
    r"""
    Calculates the Fock representation of a Gaussian unitary parametrized by
    the symplectic matrix S and the displacements alpha up to cutoff in Fock space.

    Args:
        S (array): symplectic matrix
        alpha (array): complex vector of displacements
        cutoff (int): cutoff in Fock space
        choi_r (float): squeezing parameter used for the Choi expansion
        check_symplectic (boolean): checks whether the input matrix is symplectic
        sf_order (boolean): reshapes the tensor so that it follows the sf ordering of indices
    Return:
        (array): Tensor containing the Fock representation of the Gaussian unitary
    """
    # Check the matrix is symplectic
    if check_symplectic:
        if not is_symplectic(S):
            raise ValueError("The matrix S is not symplectic")

    # And that S and alpha have compatible dimensions
    m, _ = S.shape
    if m // 2 != len(alpha):
        raise ValueError(
            "The matrix S and the vector alpha do not have compatible dimensions"
        )

    # Construct the covariance matrix of l two-mode squeezed vacua pairing modes i and i+l
    l = m // 2
    ch = np.cosh(choi_r) * np.identity(l)
    sh = np.sinh(choi_r) * np.identity(l)
    zh = np.zeros([l, l])
    Schoi = np.block([[ch, sh, zh, zh], [sh, ch, zh, zh], [zh, zh, ch, -sh],
                      [zh, zh, -sh, ch]])
    # And then its Choi expanded symplectic
    S_exp = expand(S, list(range(l)), 2 * l) @ Schoi
    # And this is the corresponding covariance matrix
    cov = S_exp @ S_exp.T
    alphat = np.array(list(alpha) + ([0] * l))
    x = 2 * alphat.real
    p = 2 * alphat.imag
    mu = np.concatenate([x, p])

    tensor = state_vector(mu,
                          cov,
                          normalize=False,
                          cutoff=cutoff,
                          hbar=2,
                          check_purity=False,
                          choi_r=choi_r)

    if sf_order:
        sf_indexing = tuple(chain.from_iterable([[i, i + l]
                                                 for i in range(l)]))
        return tensor.transpose(sf_indexing)

    return tensor
コード例 #17
0
    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)
コード例 #18
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
コード例 #19
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."
            )
        half_n_modes = n_modes // 2
        # 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 = GBS().compile(seq, registers)

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

        # Check circuit begins with two-mode squeezers
        # --------------------------------------------
        A, B, C = group_operations(seq, lambda x: isinstance(x, ops.S2gate))
        # If there are no two-mode squeezers add squeezers at the beginning with squeezing param equal to zero.
        if B == []:
            initS2 = [
                Command(ops.S2gate(0, 0),
                        [registers[i], registers[i + half_n_modes]])
                for i in range(half_n_modes)
            ]
            seq = initS2 + seq
            A, B, C = group_operations(seq,
                                       lambda x: isinstance(x, ops.S2gate))

        if A != []:
            raise CircuitError(
                "There can be no operations before the S2gates.")

        regrefs = set()

        if B:
            # get set of circuit registers as a tuple for each S2gate
            regrefs = {(cmd.reg[0].ind, cmd.reg[1].ind) for cmd in B}

        # the set of allowed mode-tuples the S2gates must have
        allowed_modes = set(
            zip(range(0, half_n_modes), range(half_n_modes, n_modes)))

        if not regrefs.issubset(allowed_modes):
            raise CircuitError("S2gates do not appear on the correct modes.")

        # determine which modes do not have input S2gates specified
        missing = allowed_modes - regrefs

        for i, j in missing:
            # insert S2gates with 0 squeezing
            B.insert(0, Command(ops.S2gate(0, 0),
                                [registers[i], registers[j]]))

        # get list of circuit registers as a tuple for each S2gate
        regrefs = [(cmd.reg[0].ind, cmd.reg[1].ind) for cmd in B]

        # merge S2gates
        if len(regrefs) > half_n_modes:
            for mode, indices in list_duplicates(regrefs):
                r = 0
                phi = 0

                for k, i in enumerate(sorted(indices, reverse=True)):
                    removed_cmd = B.pop(i)
                    r += removed_cmd.op.p[0]
                    phi_new = removed_cmd.op.p[1]

                    if k > 0 and phi_new != phi:
                        raise CircuitError(
                            "Cannot merge S2gates with different phase values."
                        )

                    phi = phi_new

                i, j = mode
                B.insert(
                    indices[0],
                    Command(ops.S2gate(r, phi), [registers[i], registers[j]]))

        meas_seq = [C[-1]]
        seq = GaussianUnitary().compile(C[:-1], registers)

        # extract the compiled symplectic matrix
        if seq == []:
            S = np.identity(2 * n_modes)
            used_modes = list(range(n_modes))
        else:
            S = seq[0].op.p[0]
            # determine the modes that are acted on by the symplectic transformation
            used_modes = [x.ind for x in seq[0].reg]

        if not np.allclose(S @ S.T, np.identity(len(S))):
            raise CircuitError(
                "The operations after squeezing do not correspond to an interferometer."
            )

        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)

        U = S[:n_modes, :n_modes] - 1j * S[:n_modes, n_modes:]
        U11 = U[:half_n_modes, :half_n_modes]
        U12 = U[:half_n_modes, half_n_modes:]
        U21 = U[half_n_modes:, :half_n_modes]
        U22 = U[half_n_modes:, half_n_modes:]
        if not np.allclose(U12, 0) or not np.allclose(U21, 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(U11, U22):
            # 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))
        U1 = ops.Interferometer(U11,
                                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 B + U1 + U2 + meas_seq