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)
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))
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)
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))
def entry(n): """coherent summation term""" return alpha ** n / sqrt(fac(n))
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)
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
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))
def a(j, m): return np.sqrt(((2 * j + 1) / (4 * pi)) * (fac(j - m) / fac(j + m)))
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