Exemple #1
0
def differential(poly, diffvar):
    """
    Polynomial differential operator.

    Args:
        poly (Poly) : Polynomial to be differentiated.
        diffvar (Poly) : Polynomial to differentiate by. Must be decomposed. If
                polynomial array, the output is the Jacobian matrix.

    Examples:
        >>> q0, q1 = chaospy.variable(2)
        >>> poly = chaospy.Poly([1, q0, q0*q1**2+1])
        >>> print(poly)
        [1, q0, q0q1^2+1]
        >>> print(differential(poly, q0))
        [0, 1, q1^2]
        >>> print(differential(poly, q1))
        [0, 0, 2q0q1]
    """
    poly = Poly(poly)
    diffvar = Poly(diffvar)

    if not chaospy.poly.caller.is_decomposed(diffvar):
        sum(differential(poly, chaospy.poly.caller.decompose(diffvar)))

    if diffvar.shape:
        return Poly([differential(poly, pol) for pol in diffvar])

    if diffvar.dim > poly.dim:
        poly = chaospy.poly.dimension.setdim(poly, diffvar.dim)
    else:
        diffvar = chaospy.poly.dimension.setdim(diffvar, poly.dim)

    qkey = diffvar.keys[0]

    core = {}
    for key in poly.keys:

        newkey = np.array(key) - np.array(qkey)

        if np.any(newkey < 0):
            continue
        newkey = tuple(newkey)
        core[newkey] = poly.A[key] * np.prod(
            [fac(key[idx], exact=True) / fac(newkey[idx], exact=True)
             for idx in range(poly.dim)])

    return Poly(core, poly.dim, poly.shape, poly.dtype)
Exemple #2
0
def differential(P, Q):
    """
    Polynomial differential operator.

    Args:
        P (Poly):
            Polynomial to be differentiated.
        Q (Poly):
            Polynomial to differentiate by. Must be decomposed. If polynomial
            array, the output is the Jacobian matrix.
    """
    P, Q = Poly(P), Poly(Q)

    if not chaospy.poly.is_decomposed(Q):
        differential(chaospy.poly.decompose(Q)).sum(0)

    if Q.shape:
        return Poly([differential(P, q) for q in Q])

    if Q.dim>P.dim:
        P = chaospy.poly.setdim(P, Q.dim)
    else:
        Q = chaospy.poly.setdim(Q, P.dim)

    qkey = Q.keys[0]

    A = {}
    for key in P.keys:

        newkey = numpy.array(key) - numpy.array(qkey)

        if numpy.any(newkey<0):
            continue

        A[tuple(newkey)] = P.A[key]*numpy.prod([fac(key[i], \
            exact=True)/fac(newkey[i], exact=True) \
            for i in range(P.dim)])

    return Poly(B, P.dim, P.shape, P.dtype)
def pre_fac(k, m, n):
    """
    Returns the prefactor for the relative weighting of the rho terms in R_mn
    """
    return -2 * (k % 2 - 0.5) * fac(n - k) / (fac(k) * fac((n + m) / 2 - k) *
                                              fac((n - m) / 2 - k))
Exemple #4
0
 def E(n):
     """the loss channel amplitudes in the Fock basis"""
     return ((1 - T) / T) ** (n / 2) * np.dot(aToN(n) / sqrt(fac(n)), TToAdaggerA)
Exemple #5
0
def fock_prob(cov, mu, event, hbar=2.0):
    r"""Returns the probability of detection of a particular PNR detection event.

    For more details, see:

    * Kruse, R., Hamilton, C. S., Sansoni, L., Barkhofen, S., Silberhorn, C., & Jex, I.
      "A detailed study of Gaussian Boson Sampling." `arXiv:1801.07488. (2018).
      <https://arxiv.org/abs/1801.07488>`_

    * Hamilton, C. S., Kruse, R., Sansoni, L., Barkhofen, S., Silberhorn, C., & Jex, I.
      "Gaussian boson sampling." `Physical review letters, 119(17), 170501. (2017).
      <https://journals.aps.org/prl/abstract/10.1103/PhysRevLett.119.170501>`_

    Args:
        cov (array): :math:`2N\times 2N` covariance matrix
        mu (array): length-:math:`2N` means vector
        event (array): length-:math:`N` array of non-negative integers representing the
            PNR detection event of the multi-mode system.
        hbar (float): (default 2) the value of :math:`\hbar` in the commutation
            relation :math:`[\x,\p]=i\hbar`.

    Returns:
        float: probability of detecting the event
    """
    # number of modes
    N = len(mu) // 2
    I = np.identity(N)

    # mean displacement of each mode
    alpha = (mu[:N] + 1j * mu[N:]) / math.sqrt(2 * hbar)
    # the expectation values (<a_1>, <a_2>,...,<a_N>, <a^\dagger_1>, ..., <a^\dagger_N>)
    beta = np.concatenate([alpha, alpha.conj()])

    x = cov[:N, :N] * 2 / hbar
    xp = cov[:N, N:] * 2 / hbar
    p = cov[N:, N:] * 2 / hbar
    # the (Hermitian) matrix elements <a_i^\dagger a_j>
    aidaj = (x + p + 1j * (xp - xp.T) - 2 * I) / 4
    # the (symmetric) matrix elements <a_i a_j>
    aiaj = (x - p + 1j * (xp + xp.T)) / 4

    # calculate the covariance matrix sigma_Q appearing in the Q function:
    # Q(alpha) = exp[-(alpha-beta).sigma_Q^{-1}.(alpha-beta)/2]/|sigma_Q|
    Q = np.block([[aidaj, aiaj.conj()], [aiaj, aidaj.conj()]]) + np.identity(2 * N)

    # inverse Q matrix
    Qinv = np.linalg.inv(Q)
    # 1/sqrt(|Q|)
    sqrt_Qdet = 1 / math.sqrt(np.linalg.det(Q).real)

    prefactor = cmath.exp(-beta @ Qinv @ beta.conj() / 2)

    if np.all(np.array(event) == 0):
        # all PNRs detect the vacuum state
        return (prefactor * sqrt_Qdet).real / np.prod(fac(event))

    # the matrix X_n = [[0, I_n], [I_n, 0]]
    O = np.zeros_like(I)
    X = np.block([[O, I], [I, O]])

    gamma = X @ Qinv.conj() @ beta

    # For each mode, repeat the mode number event[i] times
    ind = [i for sublist in [[idx] * j for idx, j in enumerate(event)] for i in sublist]
    # extend the indices for xp-ordering of the Gaussian state
    ind += [i + N for i in ind]

    if np.linalg.norm(beta) < tolerance:
        # state has no displacement
        part = partitions(ind, include_singles=False)
    else:
        part = partitions(ind, include_singles=True)

    # calculate Hamilton's A matrix: A = X.(I-Q^{-1})*
    A = X @ (np.identity(2 * N) - Qinv).conj()
    summation = np.sum([np.prod([gamma[i[0]] if len(i) == 1 else A[i] for i in p]) for p in part])

    return (prefactor * sqrt_Qdet * summation).real / np.prod(fac(event))
Exemple #6
0
 def entry(n):
     """coherent summation term"""
     return alpha ** n / sqrt(fac(n))
Exemple #7
0
 def entry(n):
     """squeezed summation term"""
     return (sqrt(fac(2 * n)) / (2 ** n * fac(n))) * (-exp(1j * theta) * tanh(r)) ** n
 def ket(n):
     """Squeezed state kets"""
     return (np.sqrt(fac(2*n))/(2**n*fac(n))) * (-np.exp(1j*phi)*np.tanh(r))**n
def displaced_squeezed_state(a, r, phi, basis='fock', fock_dim=5, hbar=2.):
    r""" Returns the squeezed coherent state

    This can be returned either in the Fock basis,

    .. math::

        |\alpha,z\rangle = e^{-\frac{1}{2}|\alpha|^2-\frac{1}{2}{\alpha^*}^2 e^{i\phi}\tanh{(r)}}
        \sum_{n=0}^\infty\frac{\left[\frac{1}{2}e^{i\phi}\tanh(r)\right]^{n/2}}{\sqrt{n!\cosh(r)}}
        H_n\left[ \frac{\alpha\cosh(r)+\alpha^*e^{i\phi}\sinh(r)}{\sqrt{e^{i\phi}\sinh(2r)}} \right]|n\rangle

    where :math:`H_n(x)` is the Hermite polynomial, or as a Gaussian:

    .. math:: \mu = (\text{Re}(\alpha),\text{Im}(\alpha))

    .. math::
        :nowrap:

        \begin{align*}
            \sigma = R(\phi/2)\begin{bmatrix}e^{-2r} & 0 \\0 & e^{2r} \\\end{bmatrix}R(\phi/2)^T
        \end{align*}

    where :math:`z = re^{i\phi}` is the squeezing factor
    and :math:`\alpha` is the displacement.

    Args:
        a (complex): the displacement
        r (complex): the squeezing magnitude
        phi (float): the squeezing phase :math:`\phi`
        basis (str): if 'fock', calculates the initial state
            in the Fock basis. If 'gaussian', returns the
            vector of means and the covariance matrix.
        fock_dim (int): the size of the truncated Fock basis if
            using the Fock basis representation.
        hbar (float): (default 2) the value of :math:`\hbar` in the commutation
            relation :math:`[\x,\p]=i\hbar`.
    Returns:
        array: the squeezed coherent state
    """
    # pylint: disable=too-many-arguments
    if basis == 'fock':

        if r != 0:
            phase_factor = np.exp(1j*phi)
            ch = np.cosh(r)
            sh = np.sinh(r)
            th = np.tanh(r)

            gamma = a*ch+np.conj(a)*phase_factor*sh
            N = np.exp(-0.5*np.abs(a)**2-0.5*np.conj(a)**2*phase_factor*th)

            coeff = np.diag(
                [(0.5*phase_factor*th)**(n/2)/np.sqrt(fac(n)*ch)
                 for n in range(fock_dim)]
            )

            vec = [hermval(gamma/np.sqrt(phase_factor*np.sinh(2*r)), row)
                   for row in coeff]

            state = N*np.array(vec)

        else:
            state = coherent_state(
                a, basis='fock', fock_dim=fock_dim)  # pragma: no cover

    elif basis == 'gaussian':
        means = np.array([a.real, a.imag]) * np.sqrt(2*hbar)
        state = [means, squeezed_cov(r, phi, hbar)]

    return state
def projgauss(uvecs: [float, np.ndarray],
              a: float,
              order: Optional[int] = None,
              chunk_order: int = 0,
              usesmall: bool = False,
              uselarge: bool = False,
              atol: float = 1e-10,
              rtol: float = 1e-8,
              ret_cumsum: bool = False) -> [float, np.ndarray]:
    """
    Solution for I(r) = exp(-r^2/2 a^2) * cos(r).

    Parameters
    ----------
    uvecs: float or ndarray of float
        The cartesian baselines in units of wavelengths. If a float, assumed to be the magnitude of
        the baseline. If an array of one dimension, each entry is assumed to be a magnitude.
        If a 2D array, may have shape (Nbls, 2) or (Nbls, 3). In the first case, w is
        assumed to be zero.
    a: float
        Gaussian width parameter.
    order: int
        If not `chunk_order`, the expansion order. Otherwise, this is the *maximum*
        order of the expansion.
    chunk_order: int
        If non-zero, the expansion will be summed (in chunks of ``chunk_order``) until
        convergence (or max order) is reached. Setting to higher values tends to make
        the sum more efficient, but can over-step the convergence.
    usesmall: bool, optional
        Use small-a expansion, regardless of ``a``. By default, small-a expansion is used
        for ``a<=0.25``.
    uselarge: bool, optional
        Use large-a expansion, regardless of ``a``. By default, large-a expansion is used
        for ``a >= 0.25``.
    ret_cumsum : bool, optional
        Whether to return the full cumulative sum of the expansion.

    Returns
    -------
    ndarray of complex
        Visibilities, shape (Nbls,) (or (Nbls, nk) if `ret_cumsum` is True.)
    """
    u_amps = vec_to_amp(uvecs)

    if uselarge and usesmall:
        raise ValueError(
            "Cannot use both small and large approximations at once")

    usesmall = (a < 0.25 and not uselarge) or usesmall

    if usesmall:
        fnc = lambda ks, u: (
            (-1)**ks *
            (np.pi * u * a)**(2 * ks) * gammaincc(ks + 1, 1 / a**2) /
            (fac(ks))**2)
    else:
        fnc = lambda ks, u: (np.pi * (-1)**ks * vhyp1f2(
            1 + ks, 1, 2 + ks, -np.pi**2 * u**2) / (a**(2 * ks) * fac(ks + 1)))

    result = _perform_convergent_sum(fnc,
                                     u_amps,
                                     order,
                                     chunk_order,
                                     atol,
                                     rtol,
                                     ret_cumsum,
                                     complex=False)

    if usesmall:
        exp = np.exp(-(np.pi * a * u_amps)**2)
        if np.shape(result) != np.shape(exp):
            exp = exp[..., None]
        result = np.pi * a**2 * (exp - result).squeeze()
    return result
 def test_ones(self, n, dtype, recursive):
     """Check hafnian(J_2n)=(2n)!/(n!2^n)"""
     A = dtype(np.ones([2 * n, 2 * n]))
     haf = hafnian(A, recursive=recursive)
     expected = fac(2 * n) / (fac(n) * (2**n))
     assert np.allclose(haf, expected)
def Laguerre(x, q, k):  #k-th term of the Laguerre polynomial.
    #x has range from neg inf to pos inf;
    #q has range from 0 to pos inf;
    #k has range from 0 to q
    return (
        (fac(q) / fac(q - k))**2) * (e**(1j * pi * k) / fac(k)) * x**(q - k)
Exemple #13
0
def pure_state_amplitude(mu,
                         cov,
                         i,
                         include_prefactor=True,
                         tol=1e-10,
                         hbar=2,
                         check_purity=True):
    r"""Returns the :math:`\langle i | \psi\rangle` element of the state ket
    of a Gaussian state defined by covariance matrix cov.


    Args:
        mu (array): length-:math:`2N` quadrature displacement vector
        cov (array): length-:math:`2N` covariance matrix
        i (list): list of amplitude elements
        include_prefactor (bool): if ``True``, the prefactor is automatically calculated
            used to scale the result.
        tol (float): tolerance for determining if displacement is negligible
        hbar (float): the value of :math:`\hbar` in the commutation
            relation :math:`[\x,\p]=i\hbar`.
        check_purity (bool): if ``True``, the purity of the Gaussian state is checked
            before calculating the state vector.

    Returns:
        complex: the pure state amplitude
    """
    if check_purity:
        if not is_pure_cov(cov, hbar=2, rtol=1e-05, atol=1e-08):
            raise ValueError(
                "The covariance matrix does not correspond to a pure state")

    rpt = i
    beta = Beta(mu, hbar=hbar)
    Q = Qmat(cov, hbar=hbar)
    A = Amat(cov, hbar=hbar)
    (n, _) = cov.shape
    N = n // 2
    B = A[0:N, 0:N]
    alpha = beta[0:N]

    if np.linalg.norm(alpha) < tol:
        # no displacement
        if np.prod([k + 1 for k in rpt])**(1 / len(rpt)) < 3:
            B_rpt = reduction(B, rpt)
            haf = hafnian(B_rpt)
        else:
            haf = hafnian_repeated(B, rpt)
    else:
        # replace the diagonal of A with gamma
        # gamma = X @ np.linalg.inv(Q).conj() @ beta
        zeta = alpha - B @ (alpha.conj())

        if np.prod([k + 1 for k in rpt])**(1 / len(rpt)) < 3:
            B_rpt = reduction(B, rpt)
            np.fill_diagonal(B_rpt, reduction(zeta, rpt))
            haf = hafnian(B_rpt, loop=True)
        else:
            haf = hafnian_repeated(B, rpt, mu=zeta, loop=True)

    if include_prefactor:
        pref = np.exp(
            -0.5 *
            (np.linalg.norm(alpha)**2 - alpha.conj() @ B @ alpha.conj()))
        haf *= pref

    return haf / np.sqrt(np.prod(fac(rpt)) * np.sqrt(np.linalg.det(Q)))
def test_ones_approx(n):
    """Check hafnian_approx(J_2n)=(2n)!/(n!2^n)"""
    A = np.float64(np.ones([2 * n, 2 * n]))
    haf = hafnian(A, approx=True, num_samples=1e4)
    expected = fac(2 * n) / (fac(n) * (2**n))
    assert np.abs(haf - expected) / expected < 0.15
Exemple #15
0
 def test_coherent_state_fock(self, alpha, cutoff, hbar, tol):
     """test coherent state returns correct Fock basis state vector"""
     state = utils.coherent_state(alpha, basis="fock", fock_dim=cutoff, hbar=hbar)
     n = np.arange(cutoff)
     expected = np.exp(-0.5 * np.abs(alpha) ** 2) * alpha ** n / np.sqrt(fac(n))
     assert np.allclose(state, expected, atol=tol, rtol=0)
 def test_ones(self, n):
     """Check all ones matrix has perm(J_n)=n!"""
     A = np.array([[1]])
     p = permanent_repeated(A, [n])
     assert np.allclose(p, fac(n))
Exemple #17
0
def a(j, m):
    return np.sqrt(((2 * j + 1) / (4 * pi)) * (fac(j - m) / fac(j + m)))
Exemple #18
0
def generate_hafnian_sample(cov,
                            mean=None,
                            hbar=2,
                            cutoff=6,
                            max_photons=30,
                            approx=False,
                            approx_samples=1e5):  # pylint: disable=too-many-branches
    r"""Returns a single sample from the Hafnian of a Gaussian state.

    Args:
        cov (array): a :math:`2N\times 2N` ``np.float64`` covariance matrix
            representing an :math:`N` mode quantum state. This can be obtained
            via the ``scovmavxp`` method of the Gaussian backend of Strawberry Fields.
        mean (array): a :math:`2N`` ``np.float64`` vector of means representing the Gaussian
            state.
        hbar (float): (default 2) the value of :math:`\hbar` in the commutation
            relation :math:`[\x,\p]=i\hbar`.
        cutoff (int): the Fock basis truncation.
        max_photons (int): specifies the maximum number of photons that can be counted.
        approx (bool): if ``True``, the approximate hafnian algorithm is used.
            Note that this can only be used for real, non-negative matrices.
        approx_samples: the number of samples used to approximate the hafnian if ``approx=True``.

    Returns:
        np.array[int]: a photon number sample from the Gaussian states.
    """
    N = len(cov) // 2
    result = []
    prev_prob = 1.0
    nmodes = N
    if mean is None:
        local_mu = np.zeros(2 * N)
    else:
        local_mu = mean
    A = Amat(Qmat(cov), hbar=hbar)

    for k in range(nmodes):
        probs1 = np.zeros([cutoff + 1], dtype=np.float64)
        kk = np.arange(k + 1)
        mu_red, V_red = reduced_gaussian(local_mu, cov, kk)

        if approx:
            Q = Qmat(V_red, hbar=hbar)
            A = Amat(Q, hbar=hbar, cov_is_qmat=True)

        for i in range(cutoff):
            indices = result + [i]
            ind2 = indices + indices
            if approx:
                factpref = np.prod(fac(indices))
                mat = reduction(A, ind2)
                probs1[i] = (hafnian(np.abs(mat.real),
                                     approx=True,
                                     num_samples=approx_samples) / factpref)
            else:
                probs1[i] = density_matrix_element(mu_red,
                                                   V_red,
                                                   indices,
                                                   indices,
                                                   include_prefactor=True,
                                                   hbar=hbar).real

        if approx:
            probs1 = probs1 / np.sqrt(np.linalg.det(Q).real)

        probs2 = probs1 / prev_prob
        probs3 = np.maximum(probs2, np.zeros_like(probs2))  # pylint: disable=assignment-from-no-return
        ssum = np.sum(probs3)
        if ssum < 1.0:
            probs3[-1] = 1.0 - ssum

        # The following normalization of probabilities is needed when approx=True
        if approx:
            if ssum > 1.0:
                probs3 = probs3 / ssum

        result.append(np.random.choice(a=range(len(probs3)), p=probs3))
        if result[-1] == cutoff:
            return -1
        if sum(result) > max_photons:
            return -1
        prev_prob = probs1[result[-1]]

    return result