def build_circuit(self):
        params_counter = 0
        sgates = []
        dgates = []
        kgates = []
        vgates = []
        for gate_structure in self.gates_structure:
            if gate_structure[0] is Sgate:
                sgates.append(
                    ParametrizedGate(gate_structure[0], gate_structure[1], [
                        make_param(**gate_structure[2]),
                        make_param(**gate_structure[3])
                    ]))
            if gate_structure[0] is Dgate:
                dgates.append(
                    ParametrizedGate(gate_structure[0], gate_structure[1], [
                        make_param(**gate_structure[2]),
                        make_param(**gate_structure[3])
                    ]))
            if gate_structure[0] is Kgate:
                kgates.append(
                    ParametrizedGate(gate_structure[0], gate_structure[1],
                                     [make_param(**gate_structure[2])]))
            if gate_structure[0] is Vgate:
                vgates.append(
                    ParametrizedGate(gate_structure[0], gate_structure[1],
                                     [make_param(**gate_structure[2])]))

        eng, q = sf.Engine(self.n_qumodes)

        rl, U = takagi(self.adj_matrix)
        initial_squeezings = np.arctanh(rl)

        with eng:
            Interferometer(U) | q

            for i, squeeze_value in enumerate(initial_squeezings):
                Sgate(squeeze_value) | i

            if len(sgates) != 0:
                Interferometer(self.interferometer_matrix) | q
                for gate in sgates:
                    gate.gate(gate.params[0], gate.params[1]) | gate.qumodes

            if len(dgates) != 0:
                Interferometer(self.interferometer_matrix) | q
                for gate in dgates:
                    gate.gate(gate.params[0], gate.params[1]) | gate.qumodes

            for gate in kgates:
                gate.gate(gate.params[0]) | gate.qumodes

            for gate in vgates:
                gate.gate(gate.params[0]) | gate.qumodes

        circuit = {}
        circuit['eng'] = eng
        circuit['q'] = q

        return circuit
 def test_zeros(self):
     """Verify that the Takagi decomposition returns a zero vector and identity matrix when
     input a matrix of zeros"""
     dim = 4
     a = np.zeros((dim, dim))
     rl, U = dec.takagi(a)
     assert np.allclose(rl, np.zeros(dim))
     assert np.allclose(U, np.eye(dim))
 def test_random_symm(self, tol):
     """Verify that the Takagi decomposition, applied to a random symmetric
     matrix, produced a decomposition that can be used to reconstruct the matrix."""
     A = np.random.random([6, 6]) + 1j * np.random.random([6, 6])
     A += A.T
     rl, U = dec.takagi(A)
     res = U @ np.diag(rl) @ U.T
     assert np.allclose(res, A, atol=tol, rtol=0)
 def test_real_degenerate(self):
     """Verify that the Takagi decomposition returns a matrix that is unitary and results in a
     correct decomposition when input a real but highly degenerate matrix. This test uses the
     adjacency matrix of a balanced tree graph."""
     g = nx.balanced_tree(2, 4)
     a = nx.to_numpy_array(g)
     rl, U = dec.takagi(a)
     assert np.allclose(U @ U.conj().T, np.eye(len(a)))
     assert np.allclose(U @ np.diag(rl) @ U.T, a)
    def test_takagi_random_symm(self):
        error = np.empty(nsamples)
        for i in range(nsamples):
            X = random_degenerate_symmetric()
            rl, U = dec.takagi(X)
            Xr = U @ np.diag(rl) @ np.transpose(U)
            diff = np.linalg.norm(Xr - X)

            error[i] = diff

        self.assertAlmostEqual(error.mean(), 0)
Beispiel #6
0
def jsa_from_m(m):
    """Given a phase sensitive moment m returns the joint spectral amplitude associated with it.

    Args:
        m (array): phase sentive moment

    Returns:
        (array): joint spectral amplitude

    """
    ls, u = takagi(m)
    return u @ np.diag(0.5 * np.arcsinh(2 * ls)) @ u.T
 def test_symmetric_validation(self):
     """Test that the takagi decomposition raises exception if not symmetric"""
     A = np.random.random([5, 5]) + 1j * np.random.random([5, 5])
     with pytest.raises(ValueError, match="matrix is not symmetric"):
         dec.takagi(A)
 def test_square_validation(self):
     """Test that the takagi decomposition raises exception if not square"""
     A = np.random.random([4, 5]) + 1j * np.random.random([4, 5])
     with pytest.raises(ValueError, match="matrix must be square"):
         dec.takagi(A)
Beispiel #9
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
Beispiel #10
0
def matelem(l, m, n, U, Up, ls, alpha):
    """ Calculates a Fock matrix element <m|W(alpha,U,ls,Up)|n> of the Gaussian
    unitary W specified by alpha, U, ls, Up.

    Args:
    	l (integer): Number of modes
    	m (list): List of integers specifying the input Fock states
    	n (list): List of integers specifying the output Fock states
    	U (array): Unitary matrix of size l
    	Up (array): Unitary matrix of size l
    	ls (array): Squeezing parameters
    	alpha (array): Complex displacements
    Returns:
    	(complex): Value of the required matrix element
    """
    assert l == len(m)
    assert l == len(n)
    assert U.shape == (l, l)
    assert Up.shape == (l, l)
    assert len(ls) == l
    assert len(alpha) == l

    idl = np.identity(l)
    # Define extended unitaries that are identities in the second half of the mode
    Ue = np.block([[U, 0 * idl], [0 * idl, idl]])
    Uep = np.block([[Up, 0 * idl], [0 * idl, idl]])

    # Define the ts of the squeezing parameters
    # pylint: disable=assignment-from-no-return
    ts = np.arcsinh(np.sqrt(1.0 * np.array(n)))

    # Now we generate the circuit in Fig 4.(b)
    nmodes = 2 * l
    state = gc.GaussianModes(nmodes)
    for i, t in enumerate(ts):
        tmsq(state, i, i + l, -t)

    state.apply_u(Uep)

    for i, lval in enumerate(ls):
        state.squeeze(-lval, 0, i)

    state.apply_u(Ue)

    # Shortcircuited Bloch-Messiah using Takagi
    Mt = state.mmat
    lt, ut = takagi(Mt, 15)

    # Define the lambda tilde and the u tilde
    lt = -0.5 * np.arcsinh(2 * lt)
    ut = ut.conj()

    alphat = np.array(list(alpha) + list(np.zeros_like(alpha)))
    B = ut @ np.diag(np.tanh(lt)) @ ut.T
    zeta = alphat - B @ alphat.conj()

    pref = -0.5 * alphat.conj() @ zeta

    p = m + n

    # Calculating prefactors
    R = 1.0 / np.prod((np.tanh(ts)**n) / np.cosh(ts))
    prefns = np.sqrt(np.prod(np.array([np.math.factorial(i) for i in p])))
    T = np.exp(pref) / (prefns * np.sqrt(np.prod(np.cosh(lt))))

    # Calculating the multiset S_p
    sp = []
    for k, pval in enumerate(p):
        for i in range(pval):
            sp.append(k)

    # Generate Bp with possibly repeated rows and columns
    Bp = B[:, sp][sp, :]

    # Generate zetap with possibly repeated entries
    zetap = zeta[sp]

    # Calculate Bt
    np.fill_diagonal(Bp, zetap)

    if Bp.shape == (0, 0):
        amp = 1.0
    else:
        amp = hafnian(Bp, loop=True)

    mu = R * T * amp

    return mu
    def build_circuit(self, adj_matrix):
        params_counter = 0
        number_of_layers = 2
        all_sgates = [[]] * number_of_layers
        all_dgates = [[]] * number_of_layers
        all_kgates = [[]] * number_of_layers
        all_vgates = [[]] * number_of_layers

        for gate_structure in self.gates_structure:
            current_layer = int(gate_structure[2]['name'].split('_')[-1][0])
            if gate_structure[0] is Sgate:
                current_gate = ParametrizedGate(
                    gate_structure[0], gate_structure[1], [
                        make_param(**gate_structure[2]),
                        make_param(**gate_structure[3])
                    ])
                all_sgates[current_layer].append(current_gate)
            if gate_structure[0] is Dgate:
                current_gate = ParametrizedGate(
                    gate_structure[0], gate_structure[1], [
                        make_param(**gate_structure[2]),
                        make_param(**gate_structure[3])
                    ])
                all_dgates[current_layer].append(current_gate)
            if gate_structure[0] is Kgate:
                current_gate = ParametrizedGate(
                    gate_structure[0], gate_structure[1],
                    [make_param(**gate_structure[2])])
                all_kgates[current_layer].append(current_gate)
            if gate_structure[0] is Vgate:
                current_gate = ParametrizedGate(
                    gate_structure[0], gate_structure[1],
                    [make_param(**gate_structure[2])])
                all_vgates[current_layer].append(current_gate)

        eng, q = sf.Engine(self.n_qumodes)
        rl, U = takagi(adj_matrix)
        initial_squeezings = np.arctanh(rl)

        with eng:
            for i, squeeze_value in enumerate(initial_squeezings):
                Sgate(squeeze_value) | i

            Interferometer(U) | q
            for layer in range(number_of_layers):
                sgates = all_sgates[layer]
                dgates = all_dgates[layer]
                kgates = all_kgates[layer]
                vgates = all_vgates[layer]

                if len(sgates) != 0:
                    Interferometer(self.interferometer_matrix) | q
                    for gate in sgates:
                        gate.gate(gate.params[0],
                                  gate.params[1]) | gate.qumodes

                if len(dgates) != 0:
                    Interferometer(self.interferometer_matrix) | q
                    for gate in dgates:
                        gate.gate(gate.params[0],
                                  gate.params[1]) | gate.qumodes

                for gate in kgates:
                    gate.gate(gate.params[0]) | gate.qumodes

                for gate in vgates:
                    gate.gate(gate.params[0]) | gate.qumodes

        circuit = {}
        circuit['eng'] = eng
        circuit['q'] = q

        return circuit