jiahuimbzuai/CoQuIR huggingface dataset:
//  [python]
def histograma(path):
    
    ### Datos ###
    os.chdir(path)      
    rawData = np.loadtxt('cuentas.csv', delimiter=',') #carga cuentas.csv. Guardar con savetxt
    data = rawData[rawData < 20] #Acá podés eliminar cuentas muy altas, producto de malas mediciones
    data.sort()
    ###
    ### Histograma ###
    hist, bins = np.histogram(data,bins = np.arange(np.max(data)), density = True)
    bins = bins[:-1]
    ###
    ### Ajuste ###
    ### Fuciones de ayuda ###
    poissonPDF = lambda j, lambd: (lambd**j) * np.exp(-lambd) / scipy.misc.factorial(j)
    bePDF = lambda j, lambd: np.power(lambd, j) / np.power(1+lambd,1+j)
    ###
    pPoisson, pconv = opt.curve_fit(poissonPDF, bins, hist, p0 = 3)
    pBE, pconv = opt.curve_fit(bePDF, bins, hist, p0 = 3)
    with open('p-valor','w+') as f:
        f.write("Poisson p-value: {}\n".format(stats.chisquare(hist,poissonPDF(bins,pPoisson),ddof = 1)[1]))
        f.write("BE p-value: {}".format(stats.chisquare(hist,bePDF(bins,pBE),ddof = 1)[1]))
    ######
    
    ### Ploteo ###
    width = 24 / 2.54 #24cm de ancho
    figSize = (width * (1+np.sqrt(5)) / 2, width) #relación aurea
    
    #Figura 1    
    plt.figure(figsize=figSize)
    plt.plot(poissonPDF(bins,pPoisson), 'r^', label='Poisson', markersize = 10) #azul
    plt.plot(bePDF(bins,pBE), 'go', label = 'BE', markersize = 10) #verde
    plt.bar(bins,hist, width = 0.1,label='Datos')    
    #Configuración
    plt.grid()
    ### Ejes ###
    plt.axis('tight')
    plt.xlim((-1,10))
    plt.ylim((0,0.25))
    plt.xlabel('Numero de eventos',fontsize=22)
    plt.ylabel('Frecuencias relativas',fontsize=22)
    plt.tick_params(labelsize = 20)
    ###
    ### Texto a agregar ###
    text = 'Poisson\n'
    text += r'$<n> = {0:.2f}$'.format(pPoisson[0])
    text += '\n'
    text += r'$\chi^2_{{ \nu = {0} }} = {1:.3f}$'.format(hist.size, poissonChisq)
    text += '\n\n'
    text += 'BE\n'
    text += r'$<n> = {0:.2f}$'.format(pBE[0])
    text += '\n'
    text += r'$\chi^2_{{ \nu = {0} }} = {1:.2f}$'.format(hist.size, beChisq)
    plt.text(0.7,0.3, text, transform = plt.gca().transAxes, fontsize = fontSize)
    ######
    plt.legend(loc=0,fontsize=20)
    plt.savefig('histograma.png', bbox_inches = 'tight')
    
    #Figura 2
    plt.figure(2,figsize=figSize)
    plt.plot(np.log(poissonPDF(bins,pPoisson)), 'r^', label='Log(Poisson)', markersize = 10) #azul
    plt.plot(np.log(bePDF(bins,pBE)), 'go', label = 'Log(BE)', markersize = 10) #verde
    plt.plot(bins,np.log(hist),'bd',label='Log(Datos)', markersize = 10)    
    #### Configuración ####
    plt.grid()
    ### Ejes ###
    plt.axis('tight')
    plt.xlim((-1,10))
    plt.ylim((-5,-1))
    plt.xlabel('Numero de eventos',fontsize=22)
    plt.ylabel('Log(Frecuencias relativas)',fontsize=22)
    plt.tick_params(labelsize = 20)
    ###
    plt.legend(loc=0,fontsize=20)
    ########
    plt.savefig('log_histograma.png', bbox_inches = 'tight')
//  [python]
def histograma(path):
    
    ### Datos ###
    os.chdir(path)      
    rawData = np.loadtxt('cuentas.csv', delimiter=',') #carga cuentas.csv. Guardar con savetxt
    data = rawData[rawData < 20] #Acá podés eliminar cuentas muy altas, producto de malas mediciones
    data.sort()
    ###
    ### Histograma ###
    hist, bins = np.histogram(data,bins = np.arange(np.max(data)), density = True)
    bins = bins[:-1]
    ###
    ### Ajuste ###
    ### Fuciones de ayuda ###
    poissonPDF = lambda j, lambd: (lambd**j) * np.exp(-lambd) / scipy.special.factorial(j)
    bePDF = lambda j, lambd: np.power(lambd, j) / np.power(1+lambd,1+j)
    ###
    pPoisson, pconv = opt.curve_fit(poissonPDF, bins, hist, p0 = 3)
    pBE, pconv = opt.curve_fit(bePDF, bins, hist, p0 = 3)
    with open('p-valor','w+') as f:
        f.write("Poisson p-value: {}\n".format(stats.chisquare(hist,poissonPDF(bins,pPoisson),ddof = 1)[1]))
        f.write("BE p-value: {}".format(stats.chisquare(hist,bePDF(bins,pBE),ddof = 1)[1]))
    ######
    
    ### Ploteo ###
    width = 24 / 2.54 #24cm de ancho
    figSize = (width * (1+np.sqrt(5)) / 2, width) #relación aurea
    
    #Figura 1    
    plt.figure(figsize=figSize)
    plt.plot(poissonPDF(bins,pPoisson), 'r^', label='Poisson', markersize = 10) #azul
    plt.plot(bePDF(bins,pBE), 'go', label = 'BE', markersize = 10) #verde
    plt.bar(bins,hist, width = 0.1,label='Datos')    
    #Configuración
    plt.grid()
    ### Ejes ###
    plt.axis('tight')
    plt.xlim((-1,10))
    plt.ylim((0,0.25))
    plt.xlabel('Numero de eventos',fontsize=22)
    plt.ylabel('Frecuencias relativas',fontsize=22)
    plt.tick_params(labelsize = 20)
    ###
    ### Texto a agregar ###
    text = 'Poisson\n'
    text += r'$<n> = {0:.2f}$'.format(pPoisson[0])
    text += '\n'
    text += r'$\chi^2_{{ \nu = {0} }} = {1:.3f}$'.format(hist.size, poissonChisq)
    text += '\n\n'
    text += 'BE\n'
    text += r'$<n> = {0:.2f}$'.format(pBE[0])
    text += '\n'
    text += r'$\chi^2_{{ \nu = {0} }} = {1:.2f}$'.format(hist.size, beChisq)
    plt.text(0.7,0.3, text, transform = plt.gca().transAxes, fontsize = fontSize)
    ######
    plt.legend(loc=0,fontsize=20)
    plt.savefig('histograma.png', bbox_inches = 'tight')
    
    #Figura 2
    plt.figure(2,figsize=figSize)
    plt.plot(np.log(poissonPDF(bins,pPoisson)), 'r^', label='Log(Poisson)', markersize = 10) #azul
    plt.plot(np.log(bePDF(bins,pBE)), 'go', label = 'Log(BE)', markersize = 10) #verde
    plt.plot(bins,np.log(hist),'bd',label='Log(Datos)', markersize = 10)    
    #### Configuración ####
    plt.grid()
    ### Ejes ###
    plt.axis('tight')
    plt.xlim((-1,10))
    plt.ylim((-5,-1))
    plt.xlabel('Numero de eventos',fontsize=22)
    plt.ylabel('Log(Frecuencias relativas)',fontsize=22)
    plt.tick_params(labelsize = 20)
    ###
    plt.legend(loc=0,fontsize=20)
    ########
    plt.savefig('log_histograma.png', bbox_inches = 'tight')
//  [python]
def _mpmath_kraft_burrows_nousek(N, B, CL):
    '''Upper limit on a poisson count rate

    The implementation is based on Kraft, Burrows and Nousek in
    `ApJ 374, 344 (1991) <http://adsabs.harvard.edu/abs/1991ApJ...374..344K>`_.
    The XMM-Newton upper limit server used the same formalism.

    Parameters
    ----------
    N : int
        Total observed count number
    B : float
        Background count rate (assumed to be known with negligible error
        from a large background area).
    CL : float
       Confidence level (number between 0 and 1)

    Returns
    -------
    S : source count limit

    Notes
    -----
    Requires the `mpmath <http://mpmath.org/>`_ library.  See
    `~astropy.stats.scipy_poisson_upper_limit` for an implementation
    that is based on scipy and evaluates faster, but runs only to about
    N = 100.
    '''
    from mpmath import mpf, factorial, findroot, fsum, power, exp, quad

    N = mpf(N)
    B = mpf(B)
    CL = mpf(CL)

    def eqn8(N, B):
        sumterms = [power(B, n) / factorial(n) for n in range(int(N) + 1)]
        return 1. / (exp(-B) * fsum(sumterms))

    eqn8_res = eqn8(N, B)
    factorial_N = factorial(N)

    def eqn7(S, N, B):
        SpB = S + B
        return eqn8_res * (exp(-SpB) * SpB**N / factorial_N)

    def eqn9_left(S_min, S_max, N, B):
        def eqn7NB(S):
            return eqn7(S, N, B)
        return quad(eqn7NB, [S_min, S_max])

    def find_s_min(S_max, N, B):
        '''
        Kraft, Burrows and Nousek suggest to integrate from N-B in both
        directions at once, so that S_min and S_max move similarly (see
        the article for details). Here, this is implemented differently:
        Treat S_max as the optimization parameters in func and then
        calculate the matching s_min that has has eqn7(S_max) =
        eqn7(S_min) here.
        '''
        y_S_max = eqn7(S_max, N, B)
        if eqn7(0, N, B) >= y_S_max:
            return 0.
        else:
            def eqn7ysmax(x):
                return eqn7(x, N, B) - y_S_max
            return findroot(eqn7ysmax, (N - B) / 2.)

    def func(s):
        s_min = find_s_min(s, N, B)
        out = eqn9_left(s_min, s, N, B)
        return out - CL

    S_max = findroot(func, N - B, tol=1e-4)
    S_min = find_s_min(S_max, N, B)
    return float(S_min), float(S_max)
//  [python]
def _mpmath_kraft_burrows_nousek(N, B, CL):
    '''Upper limit on a poisson count rate

    The implementation is based on Kraft, Burrows and Nousek in
    `ApJ 374, 344 (1991) <http://adsabs.harvard.edu/abs/1991ApJ...374..344K>`_.
    The XMM-Newton upper limit server used the same formalism.

    Parameters
    ----------
    N : int
        Total observed count number
    B : float
        Background count rate (assumed to be known with negligible error
        from a large background area).
    CL : float
       Confidence level (number between 0 and 1)

    Returns
    -------
    S : source count limit

    Notes
    -----
    Requires the `mpmath <http://mpmath.org/>`_ library.  See
    `~astropy.stats.scipy_poisson_upper_limit` for an implementation
    that is based on scipy and evaluates faster, but runs only to about
    N = 100.
    '''
    from mpmath import mpf, factorial, findroot, fsum, power, exp, quad
    from scipy.special import factorial as scipy_factorial

    N = mpf(N)
    B = mpf(B)
    CL = mpf(CL)

    def eqn8(N, B):
        sumterms = [power(B, n) / scipy_factorial(n) for n in range(int(N) + 1)]
        return 1. / (exp(-B) * fsum(sumterms))

    eqn8_res = eqn8(N, B)
    factorial_N = scipy_factorial(N)

    def eqn7(S, N, B):
        SpB = S + B
        return eqn8_res * (exp(-SpB) * SpB**N / factorial_N)

    def eqn9_left(S_min, S_max, N, B):
        def eqn7NB(S):
            return eqn7(S, N, B)
        return quad(eqn7NB, [S_min, S_max])

    def find_s_min(S_max, N, B):
        '''
        Kraft, Burrows and Nousek suggest to integrate from N-B in both
        directions at once, so that S_min and S_max move similarly (see
        the article for details). Here, this is implemented differently:
        Treat S_max as the optimization parameters in func and then
        calculate the matching s_min that has has eqn7(S_max) =
        eqn7(S_min) here.
        '''
        y_S_max = eqn7(S_max, N, B)
        if eqn7(0, N, B) >= y_S_max:
            return 0.
        else:
            def eqn7ysmax(x):
                return eqn7(x, N, B) - y_S_max
            return findroot(eqn7ysmax, (N - B) / 2.)

    def func(s):
        s_min = find_s_min(s, N, B)
        out = eqn9_left(s_min, s, N, B)
        return out - CL

    S_max = findroot(func, N - B, tol=1e-4)
    S_min = find_s_min(S_max, N, B)
    return float(S_min), float(S_max)
//  [python]
def __init__(self, n, smooth = 0.0):
    """Initialises the model for n dimensions. Can also include a smoothing parameter, which allows it to select a smoother model by not interpolating the given points perfectly."""
    
    # For evaluating the green function...
    self.green_pow = 4 - n
    if n==2 or n==4:
      green_mult = numpy.power(-1.0, 0.5*n + 1.0) / (8.0*numpy.sqrt(numpy.pi)*scipy.misc.factorial(2 - n//2))
      self.green_inc_ln = True
    else:
      green_mult = scipy.special.gamma(0.5*n - 2.0) / (16.0*numpy.power(numpy.pi, 0.5*n))
      self.green_inc_ln = False
    
    # Record the smoothing, with the green multiplication factored in...
    self.smooth = smooth / green_mult
    
    # Set the needed variables to None, ready for initialisation...
    self.n = n
    self.x = None
    self.a = None
    self.b = None
//  [python]
def __init__(self, n, smooth = 0.0):
    """Initialises the model for n dimensions. Can also include a smoothing parameter, which allows it to select a smoother model by not interpolating the given points perfectly."""
    
    # For evaluating the green function...
    self.green_pow = 4 - n
    if n==2 or n==4:
      green_mult = numpy.power(-1.0, 0.5*n + 1.0) / (8.0*numpy.sqrt(numpy.pi)*scipy.special.factorial(2 - n//2))
      self.green_inc_ln = True
    else:
      green_mult = scipy.special.gamma(0.5*n - 2.0) / (16.0*numpy.power(numpy.pi, 0.5*n))
      self.green_inc_ln = False
    
    # Record the smoothing, with the green multiplication factored in...
    self.smooth = smooth / green_mult
    
    # Set the needed variables to None, ready for initialisation...
    self.n = n
    self.x = None
    self.a = None
    self.b = None
//  [python]
def ratio_order_automorphism_to_symmetric_group(groupsize, order):
    """
    Calculates the ratio of the size of a graph's automorphism group, to the
    maximum possible automorphism group for the most symmetric graph with the same
    number of vertices.  For a general graph, this is the order of the symmetric
    group with n vertices, or n!.

    For a rooted tree with n vertices, the maximum symmetry is achieved by the
    corolla with n vertices (i.e., n-1 leaves on the root).

    If the order of the graph is zero, this method returns 0

    """
    if order == 0:
        return 0
    size_sn = sm.factorial(order)
    ratio = float(groupsize) / float(size_sn)
    frac_order = float(1) / float(order)
    beta = np.power(ratio,frac_order)
    return beta
//  [python]
def ratio_order_automorphism_to_symmetric_group(groupsize, order):
    """
    Calculates the ratio of the size of a graph's automorphism group, to the
    maximum possible automorphism group for the most symmetric graph with the same
    number of vertices.  For a general graph, this is the order of the symmetric
    group with n vertices, or n!.

    For a rooted tree with n vertices, the maximum symmetry is achieved by the
    corolla with n vertices (i.e., n-1 leaves on the root).

    If the order of the graph is zero, this method returns 0

    """
    if order == 0:
        return 0
    size_sn = sp.special.factorial(order)
    ratio = float(groupsize) / float(size_sn)
    frac_order = float(1) / float(order)
    beta = np.power(ratio,frac_order)
    return beta
//  [python]
def test_factorial(self):
        assert_array_almost_equal([6., 24., 120.],
                s.factorial([3, 4, 5], exact=False))
        assert_equal(s.factorial(5, exact=True), 120)
//  [python]
def test_factorial(self):
        assert_array_almost_equal([6., 24., 120.],
                special.factorial([3, 4, 5], exact=False))
        assert_equal(special.factorial(5, exact=True), 120)
//  [python]
def photon_error(evt, type_data, key_data, type_fit, key_fit, adu_per_photon):
    import scipy.misc
    data = np.array(evt[type_data][key_data].data / (1.*adu_per_photon), dtype="float")
    fit = np.array(evt[type_fit][key_fit].data / (1.*adu_per_photon), dtype="float")
    data_best = fit.round()
    data = data.copy()
    M = fit != 0
    M *= data > 0
    M *= data_best > 0

    K = data[M]
    W = fit[M]
    Ks = data_best[M]
    
    # Stirling
    lKf = K*np.log(K)-K
    tmp = K < 5
    if tmp.sum():
        lKf[tmp] = np.log( scipy.misc.factorial(K[tmp], exact=False) )

    # Stirling
    lKsf = Ks*np.log(Ks)-Ks
    tmp = Ks < 5
    if tmp.sum():
        lKsf[tmp] = np.log( scipy.misc.factorial(Ks[tmp], exact=False) )
    
    error = ( Ks * np.log(W) - lKsf ) - ( K * np.log(W) - lKf )
    error = error.sum()
    add_record(evt["analysis"], "analysis", "photon error", error, unit='')
//  [python]
def photon_error(evt, type_data, key_data, type_fit, key_fit, adu_per_photon):
    import scipy.special
    data = np.array(evt[type_data][key_data].data / (1.*adu_per_photon), dtype="float")
    fit = np.array(evt[type_fit][key_fit].data / (1.*adu_per_photon), dtype="float")
    data_best = fit.round()
    data = data.copy()
    M = fit != 0
    M *= data > 0
    M *= data_best > 0

    K = data[M]
    W = fit[M]
    Ks = data_best[M]
    
    # Stirling
    lKf = K*np.log(K)-K
    tmp = K < 5
    if tmp.sum():
        lKf[tmp] = np.log( scipy.special.factorial(K[tmp], exact=False) )

    # Stirling
    lKsf = Ks*np.log(Ks)-Ks
    tmp = Ks < 5
    if tmp.sum():
        lKsf[tmp] = np.log( scipy.special.factorial(Ks[tmp], exact=False) )
    
    error = ( Ks * np.log(W) - lKsf ) - ( K * np.log(W) - lKf )
    error = error.sum()
    add_record(evt["analysis"], "analysis", "photon error", error, unit='')
//  [python]
def reconSCFpyrLevs(self, coeff, log_rad, Xrcos, Yrcos, angle):

		if (len(coeff) == 1):
			return np.fft.fftshift(np.fft.fft2(coeff[0]))

		else:

			Xrcos = Xrcos - 1
    		
    		# ========================== Orientation residue==========================
			himask = self.pointOp(log_rad, Yrcos, Xrcos)

			lutsize = 1024
			Xcosn = np.pi * np.array(range(-(2*lutsize+1),(lutsize+2)))/lutsize
			order = self.nbands - 1
			const = np.power(2, 2*order) * np.square(sc.factorial(order)) / (self.nbands * sc.factorial(2*order))
			Ycosn = np.sqrt(const) * np.power(np.cos(Xcosn), order)

			orientdft = np.zeros(coeff[0][0].shape)

			for b in range(self.nbands):
				anglemask = self.pointOp(angle, Ycosn, Xcosn + np.pi* b/self.nbands)
				banddft = np.fft.fftshift(np.fft.fft2(coeff[0][b]))
				orientdft = orientdft + np.power(np.complex(0,1), order) * banddft * anglemask * himask

			# ============== Lowpass component are upsampled and convoluted ============
			dims = np.array(coeff[0][0].shape)
			
			lostart = (np.ceil((dims+0.5)/2) - np.ceil((np.ceil((dims-0.5)/2)+0.5)/2)).astype(np.int32)
			loend = lostart + np.ceil((dims-0.5)/2).astype(np.int32) 
			lostart = lostart.astype(int)
			loend = loend.astype(int)

			nlog_rad = log_rad[lostart[0]:loend[0], lostart[1]:loend[1]]
			nangle = angle[lostart[0]:loend[0], lostart[1]:loend[1]]
			YIrcos = np.sqrt(np.abs(1 - Yrcos * Yrcos))
			lomask = self.pointOp(nlog_rad, YIrcos, Xrcos)

			nresdft = self.reconSCFpyrLevs(coeff[1:], nlog_rad, Xrcos, Yrcos, nangle)

			res = np.fft.fftshift(np.fft.fft2(nresdft))

			resdft = np.zeros(dims, 'complex')
			resdft[lostart[0]:loend[0], lostart[1]:loend[1]] = nresdft * lomask

			return resdft + orientdft
//  [python]
def reconSCFpyrLevs(self, coeff, log_rad, Xrcos, Yrcos, angle):

		if (len(coeff) == 1):
			return np.fft.fftshift(np.fft.fft2(coeff[0]))

		else:

			Xrcos = Xrcos - 1
    		
    		# ========================== Orientation residue==========================
			himask = self.pointOp(log_rad, Yrcos, Xrcos)

			lutsize = 1024
			Xcosn = np.pi * np.array(range(-(2*lutsize+1),(lutsize+2)))/lutsize
			order = self.nbands - 1
			const = np.power(2, 2*order) * np.square(sc.special.factorial(order)) / (self.nbands * sc.special.factorial(2*order))
			Ycosn = np.sqrt(const) * np.power(np.cos(Xcosn), order)

			orientdft = np.zeros(coeff[0][0].shape)

			for b in range(self.nbands):
				anglemask = self.pointOp(angle, Ycosn, Xcosn + np.pi* b/self.nbands)
				banddft = np.fft.fftshift(np.fft.fft2(coeff[0][b]))
				orientdft = orientdft + np.power(np.complex(0,1), order) * banddft * anglemask * himask

			# ============== Lowpass component are upsampled and convoluted ============
			dims = np.array(coeff[0][0].shape)
			
			lostart = (np.ceil((dims+0.5)/2) - np.ceil((np.ceil((dims-0.5)/2)+0.5)/2)).astype(np.int32)
			loend = lostart + np.ceil((dims-0.5)/2).astype(np.int32) 
			lostart = lostart.astype(int)
			loend = loend.astype(int)

			nlog_rad = log_rad[lostart[0]:loend[0], lostart[1]:loend[1]]
			nangle = angle[lostart[0]:loend[0], lostart[1]:loend[1]]
			YIrcos = np.sqrt(np.abs(1 - Yrcos * Yrcos))
			lomask = self.pointOp(nlog_rad, YIrcos, Xrcos)

			nresdft = self.reconSCFpyrLevs(coeff[1:], nlog_rad, Xrcos, Yrcos, nangle)

			res = np.fft.fftshift(np.fft.fft2(nresdft))

			resdft = np.zeros(dims, 'complex')
			resdft[lostart[0]:loend[0], lostart[1]:loend[1]] = nresdft * lomask

			return resdft + orientdft
//  [python]
def test_factorial_0d_return_type(self, x, exact):
        assert np.isscalar(s.factorial(x, exact=exact))
//  [python]
def test_factorial_0d_return_type(self, x, exact):
        assert np.isscalar(special.factorial(x, exact=exact))
//  [python]
def test_nan_inputs(self, x, exact):
        result = s.factorial(x, exact=exact)
        assert_(np.isnan(result))
//  [python]
def test_nan_inputs(self, x, exact):
        result = special.factorial(x, exact=exact)
        assert_(np.isnan(result))
//  [python]
def buildSCFpyrlevs(self, lodft, log_rad, angle, Xrcos, Yrcos, ht):
		if (ht <=1):
			lo0 = np.fft.ifft2(np.fft.ifftshift(lodft))
			coeff = [lo0.real]
		
		else:
			Xrcos = Xrcos - 1

			# ==================== Orientation bandpass =======================
			himask = self.pointOp(log_rad, Yrcos, Xrcos)

			lutsize = 1024
			Xcosn = np.pi * np.array(range(-(2*lutsize+1),(lutsize+2)))/lutsize
			order = self.nbands - 1
			const = np.power(2, 2*order) * np.square(sc.factorial(order)) / (self.nbands * sc.factorial(2*order))

			alpha = (Xcosn + np.pi) % (2*np.pi) - np.pi
			Ycosn = 2*np.sqrt(const) * np.power(np.cos(Xcosn), order) * (np.abs(alpha) < np.pi/2)

			orients = []

			for b in range(self.nbands):
				anglemask = self.pointOp(angle, Ycosn, Xcosn + np.pi*b/self.nbands)
				banddft = np.power(np.complex(0,-1), self.nbands - 1) * lodft * anglemask * himask
				band = np.fft.ifft2(np.fft.ifftshift(banddft))
				orients.append(band)

			# ================== Subsample lowpass ============================
			lostart = (0, 0)
			loend = lodft.shape

			log_rad = log_rad[lostart[0]:loend[0], lostart[1]:loend[1]]
			angle = angle[lostart[0]:loend[0], lostart[1]:loend[1]]
			lodft = lodft[lostart[0]:loend[0], lostart[1]:loend[1]]
			YIrcos = np.abs(np.sqrt(1 - Yrcos*Yrcos))
			lomask = self.pointOp(log_rad, YIrcos, Xrcos)

			lodft = lomask * lodft

			coeff = self.buildSCFpyrlevs(lodft, log_rad, angle, Xrcos, Yrcos, ht-1)
			coeff.insert(0, orients)

		return coeff
//  [python]
def buildSCFpyrlevs(self, lodft, log_rad, angle, Xrcos, Yrcos, ht):
		if (ht <=1):
			lo0 = np.fft.ifft2(np.fft.ifftshift(lodft))
			coeff = [lo0.real]
		
		else:
			Xrcos = Xrcos - 1

			# ==================== Orientation bandpass =======================
			himask = self.pointOp(log_rad, Yrcos, Xrcos)

			lutsize = 1024
			Xcosn = np.pi * np.array(range(-(2*lutsize+1),(lutsize+2)))/lutsize
			order = self.nbands - 1
			const = np.power(2, 2*order) * np.square(sc.special.factorial(order)) / (self.nbands * sc.special.factorial(2*order))

			alpha = (Xcosn + np.pi) % (2*np.pi) - np.pi
			Ycosn = 2*np.sqrt(const) * np.power(np.cos(Xcosn), order) * (np.abs(alpha) < np.pi/2)

			orients = []

			for b in range(self.nbands):
				anglemask = self.pointOp(angle, Ycosn, Xcosn + np.pi*b/self.nbands)
				banddft = np.power(np.complex(0,-1), self.nbands - 1) * lodft * anglemask * himask
				band = np.fft.ifft2(np.fft.ifftshift(banddft))
				orients.append(band)

			# ================== Subsample lowpass ============================
			lostart = (0, 0)
			loend = lodft.shape

			log_rad = log_rad[lostart[0]:loend[0], lostart[1]:loend[1]]
			angle = angle[lostart[0]:loend[0], lostart[1]:loend[1]]
			lodft = lodft[lostart[0]:loend[0], lostart[1]:loend[1]]
			YIrcos = np.abs(np.sqrt(1 - Yrcos*Yrcos))
			lomask = self.pointOp(log_rad, YIrcos, Xrcos)

			lodft = lomask * lodft

			coeff = self.buildSCFpyrlevs(lodft, log_rad, angle, Xrcos, Yrcos, ht-1)
			coeff.insert(0, orients)

		return coeff
//  [python]
def test_mixed_nan_inputs(self):
        x = np.array([np.nan, 1, 2, 3, np.nan])
        with suppress_warnings() as sup:
            sup.filter(DeprecationWarning, "Using factorial\\(\\) with floats is deprecated")
            result = s.factorial(x, exact=True)
            assert_equal(np.array([np.nan, 1, 2, 6, np.nan]), result)
            result = s.factorial(x, exact=False)
            assert_equal(np.array([np.nan, 1, 2, 6, np.nan]), result)
//  [python]
def test_mixed_nan_inputs(self):
        x = np.array([np.nan, 1, 2, 3, np.nan])
        with suppress_warnings() as sup:
            sup.filter(DeprecationWarning, "Using factorial\\(\\) with floats is deprecated")
            result = special.factorial(x, exact=True)
            assert_equal(np.array([np.nan, 1, 2, 6, np.nan]), result)
            result = special.factorial(x, exact=False)
            assert_equal(np.array([np.nan, 1, 2, 6, np.nan]), result)
//  [python]
def _apply_normalization(a):
  """Applies normalization to the associated Legendre functions."""
  num_l, num_m, _ = a.shape

  a_normalized = np.zeros_like(a)
  for l in range(num_l):
    for m in range(num_m):
      c0 = (2.0 * l + 1.0) * s.factorial(l - m)
      c1 = (4.0 * math.pi) * s.factorial(l + m)
      c2 = np.sqrt(c0 / c1)
      a_normalized[l, m] = c2 * a[l, m]

  return a_normalized
//  [python]
def _apply_normalization(a):
  """Applies normalization to the associated Legendre functions."""
  num_l, num_m, _ = a.shape

  a_normalized = np.zeros_like(a)
  for l in range(num_l):
    for m in range(num_m):
      c0 = (2.0 * l + 1.0) * sp_special.factorial(l - m)
      c1 = (4.0 * math.pi) * sp_special.factorial(l + m)
      c2 = np.sqrt(c0 / c1)
      a_normalized[l, m] = c2 * a[l, m]

  return a_normalized
//  [python]
def _scipy_kraft_burrows_nousek(N, B, CL):
    """Upper limit on a poisson count rate.

    The implementation is based on Kraft, Burrows and Nousek
    `ApJ 374, 344 (1991) <https://ui.adsabs.harvard.edu/abs/1991ApJ...374..344K>`_.
    The XMM-Newton upper limit server uses the same formalism.

    Parameters
    ----------
    N : int or np.int32/np.int64
        Total observed count number
    B : float or np.float32/np.float64
        Background count rate (assumed to be known with negligible error
        from a large background area).
    CL : float or np.float32/np.float64
       Confidence level (number between 0 and 1)

    Returns
    -------
    S : source count limit

    Notes
    -----
    Requires :mod:`~scipy`. This implementation will cause Overflow Errors for
    about N > 100 (the exact limit depends on details of how scipy was
    compiled). See `~astropy.stats.mpmath_poisson_upper_limit` for an
    implementation that is slower, but can deal with arbitrarily high numbers
    since it is based on the `mpmath <https://mpmath.org/>`_ library.
    """
    from math import exp

    from scipy.integrate import quad
    from scipy.optimize import brentq
    from scipy.special import factorial

    def eqn8(N, B):
        n = np.arange(N + 1, dtype=np.float64)
        return 1.0 / (exp(-B) * np.sum(np.power(B, n) / factorial(n)))

    # The parameters of eqn8 do not vary between calls so we can calculate the
    # result once and reuse it. The same is True for the factorial of N.
    # eqn7 is called hundred times so "caching" these values yields a
    # significant speedup (factor 10).
    eqn8_res = eqn8(N, B)
    factorial_N = float(math.factorial(N))

    def eqn7(S, N, B):
        SpB = S + B
        return eqn8_res * (exp(-SpB) * SpB**N / factorial_N)

    def eqn9_left(S_min, S_max, N, B):
        return quad(eqn7, S_min, S_max, args=(N, B), limit=500)

    def find_s_min(S_max, N, B):
        """
        Kraft, Burrows and Nousek suggest to integrate from N-B in both
        directions at once, so that S_min and S_max move similarly (see
        the article for details). Here, this is implemented differently:
        Treat S_max as the optimization parameters in func and then
        calculate the matching s_min that has has eqn7(S_max) =
        eqn7(S_min) here.
        """
        y_S_max = eqn7(S_max, N, B)
        if eqn7(0, N, B) >= y_S_max:
            return 0.0
        else:
            return brentq(lambda x: eqn7(x, N, B) - y_S_max, 0, N - B)

    def func(s):
        s_min = find_s_min(s, N, B)
        out = eqn9_left(s_min, s, N, B)
        return out[0] - CL

    S_max = brentq(func, N - B, 100)
    S_min = find_s_min(S_max, N, B)
    return S_min, S_max
//  [python]
def delta_methfessel_paxton(x: NDArray, n: int) -> NDArray:
    """
    D_n (x) = exp -x^2 * sum_i=0^n A_i H_2i(x)
    where H is a Hermite polynomial and
    A_i = (-1)^i / ( i! 4^i sqrt(pi) ).
    """
    ii = np.arange(0, n + 1)
    A = (-1) ** ii / (s.factorial(ii) * 4**ii * np.sqrt(np.pi))
    H = scipy.special.eval_hermite(ii * 2, np.tile(x, (len(ii), 1)).T)
    return np.exp(-(x * x)) * np.dot(A, H.T)
//  [python]
def delta_methfessel_paxton(x: NDArray, n: int) -> NDArray:
    """
    D_n (x) = exp -x^2 * sum_i=0^n A_i H_2i(x)
    where H is a Hermite polynomial and
    A_i = (-1)^i / ( i! 4^i sqrt(pi) ).
    """
    ii = np.arange(0, n + 1)
    A = (-1) ** ii / (scipy.special.factorial(ii) * 4**ii * np.sqrt(np.pi))
    H = scipy.special.eval_hermite(ii * 2, np.tile(x, (len(ii), 1)).T)
    return np.exp(-(x * x)) * np.dot(A, H.T)
//  [python]
def nloglike(params, data, eps=10e-4) -> float:
        """Static method to estumate the negative likelihood (used in :meth:`statinf.distributions.discrete.NegativeBinomial.fit` method).

        :formula: The log-likelihood function :math:`l` is defined by

            .. math:: \\mathcal{l}(x_1, ..., x_n | r, p) &= \\sum_{i=1}^{n} {\\log(\\Gamma(x_i + r))} \\sum_{i=1}^{n} {\\log(x_i!)} - N \\log(\\Gamma(r)) \\\\ &+ \\sum_{i=1}^{n} {x_i \\log(1-p)} + N r \\log(p)

        :param params: List of parameters :math:`r` and :math:`p`
        :type params: :obj:`list`
        :param data: Data to evaluate the netative log-likelihood on
        :type data: :obj:`numpy.array` or :obj:`list` or :obj:`pandas.Series`

        :return: Negative log-likelihood
        :rtype: :obj:`float`
        """
        r_ = params[0]
        p_ = params[1]
        X = np.asarray(data)
        n = len(X)

        ll = np.sum(gammaln(X + r_))
        ll += - np.sum(np.log(s.factorial(X)))
        ll += - n * (gammaln(r_))
        ll += n * r_ * np.log(p_)
        ll += np.sum(X * np.log(1 - (p_ if p_ < 1 else 1 - eps)))
        return -ll
//  [python]
def nloglike(params, data, eps=10e-4) -> float:
        """Static method to estumate the negative likelihood (used in :meth:`statinf.distributions.discrete.NegativeBinomial.fit` method).

        :formula: The log-likelihood function :math:`l` is defined by

            .. math:: \\mathcal{l}(x_1, ..., x_n | r, p) &= \\sum_{i=1}^{n} {\\log(\\Gamma(x_i + r))} \\sum_{i=1}^{n} {\\log(x_i!)} - N \\log(\\Gamma(r)) \\\\ &+ \\sum_{i=1}^{n} {x_i \\log(1-p)} + N r \\log(p)

        :param params: List of parameters :math:`r` and :math:`p`
        :type params: :obj:`list`
        :param data: Data to evaluate the netative log-likelihood on
        :type data: :obj:`numpy.array` or :obj:`list` or :obj:`pandas.Series`

        :return: Negative log-likelihood
        :rtype: :obj:`float`
        """
        r_ = params[0]
        p_ = params[1]
        X = np.asarray(data)
        n = len(X)

        ll = np.sum(gammaln(X + r_))
        ll += - np.sum(np.log(factorial(X)))
        ll += - n * (gammaln(r_))
        ll += n * r_ * np.log(p_)
        ll += np.sum(X * np.log(1 - (p_ if p_ < 1 else 1 - eps)))
        return -ll
//  [python]
def test_factorial(self):
        # Some known values, float math
        assert_array_almost_equal(s.factorial(0), 1)
        assert_array_almost_equal(s.factorial(1), 1)
        assert_array_almost_equal(s.factorial(2), 2)
        assert_array_almost_equal([6., 24., 120.],
                                  s.factorial([3, 4, 5], exact=False))
        assert_array_almost_equal(s.factorial([[5, 3], [4, 3]]),
                                  [[120, 6], [24, 6]])

        # Some known values, integer math
        assert_equal(s.factorial(0, exact=True), 1)
        assert_equal(s.factorial(1, exact=True), 1)
        assert_equal(s.factorial(2, exact=True), 2)
        assert_equal(s.factorial(5, exact=True), 120)
        assert_equal(s.factorial(15, exact=True), 1307674368000)

        # ndarray shape is maintained
        assert_equal(s.factorial([7, 4, 15, 10], exact=True),
                     [5040, 24, 1307674368000, 3628800])

        assert_equal(s.factorial([[5, 3], [4, 3]], True),
                     [[120, 6], [24, 6]])

        # object arrays
        assert_equal(s.factorial(np.arange(-3, 22), True),
                     s.factorial(np.arange(-3, 22), False))

        # int64 array
        assert_equal(s.factorial(np.arange(-3, 15), True),
                     s.factorial(np.arange(-3, 15), False))

        # int32 array
        assert_equal(s.factorial(np.arange(-3, 5), True),
                     s.factorial(np.arange(-3, 5), False))

        # Consistent output for n < 0
        for exact in (True, False):
            assert_array_equal(0, s.factorial(-3, exact))
            assert_array_equal([1, 2, 0, 0],
                               s.factorial([1, 2, -5, -4], exact))

        for n in range(0, 22):
            # Compare all with math.factorial
            correct = math.factorial(n)
            assert_array_equal(correct, s.factorial(n, True))
            assert_array_equal(correct, s.factorial([n], True)[0])

            assert_allclose(float(correct), s.factorial(n, False))
            assert_allclose(float(correct), s.factorial([n], False)[0])

            # Compare exact=True vs False, scalar vs array
            assert_array_equal(s.factorial(n, True),
                               s.factorial(n, False))

            assert_array_equal(s.factorial([n], True),
                               s.factorial([n], False))
//  [python]
def test_factorial(self):
        # Some known values, float math
        assert_array_almost_equal(special.factorial(0), 1)
        assert_array_almost_equal(special.factorial(1), 1)
        assert_array_almost_equal(special.factorial(2), 2)
        assert_array_almost_equal([6., 24., 120.],
                                  special.factorial([3, 4, 5], exact=False))
        assert_array_almost_equal(special.factorial([[5, 3], [4, 3]]),
                                  [[120, 6], [24, 6]])

        # Some known values, integer math
        assert_equal(special.factorial(0, exact=True), 1)
        assert_equal(special.factorial(1, exact=True), 1)
        assert_equal(special.factorial(2, exact=True), 2)
        assert_equal(special.factorial(5, exact=True), 120)
        assert_equal(special.factorial(15, exact=True), 1307674368000)

        # ndarray shape is maintained
        assert_equal(special.factorial([7, 4, 15, 10], exact=True),
                     [5040, 24, 1307674368000, 3628800])

        assert_equal(special.factorial([[5, 3], [4, 3]], True),
                     [[120, 6], [24, 6]])

        # object arrays
        assert_equal(special.factorial(np.arange(-3, 22), True),
                     special.factorial(np.arange(-3, 22), False))

        # int64 array
        assert_equal(special.factorial(np.arange(-3, 15), True),
                     special.factorial(np.arange(-3, 15), False))

        # int32 array
        assert_equal(special.factorial(np.arange(-3, 5), True),
                     special.factorial(np.arange(-3, 5), False))

        # Consistent output for n < 0
        for exact in (True, False):
            assert_array_equal(0, special.factorial(-3, exact))
            assert_array_equal([1, 2, 0, 0],
                               special.factorial([1, 2, -5, -4], exact))

        for n in range(0, 22):
            # Compare all with math.factorial
            correct = math.factorial(n)
            assert_array_equal(correct, special.factorial(n, True))
            assert_array_equal(correct, special.factorial([n], True)[0])

            assert_allclose(float(correct), special.factorial(n, False))
            assert_allclose(float(correct), special.factorial([n], False)[0])

            # Compare exact=True vs False, scalar vs array
            assert_array_equal(special.factorial(n, True),
                               special.factorial(n, False))

            assert_array_equal(special.factorial([n], True),
                               special.factorial([n], False))
//  [python]
def testPmfUniform(self):
    # Check that distribution with equal scores gives uniform probability.
    scores = self._rng.uniform() * np.ones(shape=(8, 2, 10), dtype=np.float32)
    dist = plackett_luce.PlackettLuce(scores=scores, validate_args=True)
    np_sample = self.evaluate(dist.sample(seed=test_util.test_seed()))
    np_prob = self.evaluate(dist.log_prob(np_sample))
    n_fac = s.factorial(scores.shape[-1]).astype(np.float32)
    expected_prob = self.evaluate(-tf.math.log(n_fac) * tf.ones_like(np_prob))
    self.assertAllClose(np_prob, expected_prob)
//  [python]
def testPmfUniform(self):
    # Check that distribution with equal scores gives uniform probability.
    scores = self._rng.uniform() * np.ones(shape=(8, 2, 10), dtype=np.float32)
    dist = plackett_luce.PlackettLuce(scores=scores, validate_args=True)
    np_sample = self.evaluate(dist.sample(seed=test_util.test_seed()))
    np_prob = self.evaluate(dist.log_prob(np_sample))
    n_fac = scipy.special.factorial(scores.shape[-1]).astype(np.float32)
    expected_prob = self.evaluate(-tf.math.log(n_fac) * tf.ones_like(np_prob))
    self.assertAllClose(np_prob, expected_prob)
//  [python]
def normal_reference_constant(self):
        """
        Constant used for silverman normal reference asymtotic bandwidth
        calculation.

        C  = 2((pi^(1/2)*(nu!)^3 R(k))/(2nu(2nu)!kap_nu(k)^2))^(1/(2nu+1))
        nu = kernel order
        kap_nu = nu'th moment of kernel
        R = kernel roughness (square of L^2 norm)

        Note: L2Norm property returns square of norm.
        """
        nu = self._order

        if not nu == 2:
            msg = "Only implemented for second order kernels"
            raise NotImplementedError(msg)

        if self._normal_reference_constant is None:
            C = np.pi**(.5) * s.factorial(nu)**3 * self.L2Norm
            C /= (2 * nu * s.factorial(2 * nu) * self.moments(nu)**2)
            C = 2*C**(1.0/(2*nu+1))
            self._normal_reference_constant = C

        return self._normal_reference_constant
//  [python]
def normal_reference_constant(self):
        """
        Constant used for silverman normal reference asymtotic bandwidth
        calculation.

        C  = 2((pi^(1/2)*(nu!)^3 R(k))/(2nu(2nu)!kap_nu(k)^2))^(1/(2nu+1))
        nu = kernel order
        kap_nu = nu'th moment of kernel
        R = kernel roughness (square of L^2 norm)

        Note: L2Norm property returns square of norm.
        """
        nu = self._order

        if not nu == 2:
            msg = "Only implemented for second order kernels"
            raise NotImplementedError(msg)

        if self._normal_reference_constant is None:
            C = np.pi**(.5) * factorial(nu)**3 * self.L2Norm
            C /= (2 * nu * factorial(2 * nu) * self.moments(nu)**2)
            C = 2*C**(1.0/(2*nu+1))
            self._normal_reference_constant = C

        return self._normal_reference_constant
//  [python]
def step_methfessel_paxton(x: NDArray, n: int) -> NDArray:
    """
    S_n (x) = (1 + erf x)/2 - exp -x^2 * sum_i=1^n A_i H_{2i-1}(x)
    where H is a Hermite polynomial and
    A_i = (-1)^i / ( i! 4^i sqrt(pi) ).
    """
    ii = np.arange(1, n + 1)
    A = (-1) ** ii / (scipy.special.factorial(ii) * 4**ii * np.sqrt(np.pi))
    H = scipy.special.eval_hermite(ii * 2 - 1, np.tile(x, (len(ii), 1)).T)
    return (1.0 + scipy.special.erf(x)) / 2.0 - np.exp(-(x * x)) * np.dot(A, H.T)
//  [python]
def cyl_mom(L, M, dens, H, R):
    gamFac = np.sqrt(np.exp(sp.gammaln(L-M+1)-sp.gammaln(L+M+1)))
    fact = np.sqrt((2*L+1)/(4*np.pi))*gamFac*sp.factorial(L-M)/(2.*np.pi)
    R22 = np.sqrt(R**2+H**2)
    theta22 = H/R22
    theta21 = -H/R22
    mom = 0
    PLM22 = sp.lpmn(L+2, L+2, theta22)[0][:, -1]
    PLM21 = sp.lpmn(L+2, L+2, theta21)[0][:, -1]
    momval = PLM22-PLM21
    ks = np.arange(L-M)
    fac2 = (ks*2+1+M)*2*M/((M+2*ks)*(M+2*ks+2))
    mom = fact*fac2*R*R22**(L+2)*momval[-len(ks):]
    mom = np.sum(mom)
    if M == 0:
        mom = fact*R*R22**(L+2)*momval[1]
    return mom
//  [python]
def test_orthonormality_physicists_hermite_polynomial(self):
        rho = 0.
        degree = 2
        probability_measure = False

        ab = hermite_recurrence(
            degree+1, rho, probability=probability_measure)
        x, w = np.polynomial.hermite.hermgauss(degree+1)

        p = evaluate_orthonormal_polynomial_1d(x, degree, ab)
        p_exact = np.asarray([1+0.*x, 2*x, 4.*x**2-2]).T/np.sqrt(
            sp.factorial(np.arange(degree+1))*np.sqrt(np.pi)*2**np.arange(
                degree+1))

        assert np.allclose(p, p_exact)

        # test orthogonality
        exact_moments = np.zeros((degree+1))
        # basis is orthonormal so integration of constant basis will be
        # non-zero but will not integrate to 1.0
        exact_moments[0] = np.pi**0.25
        assert np.allclose(np.dot(p.T, w), exact_moments)
        # test orthonormality
        assert np.allclose(np.dot(p.T*w, p), np.eye(degree+1))
//  [python]
def nuclear(ax, ay, az, bx, by, bz, aa, bb, Ra, Rb, Rn, Zn):
    """
    Compute nuclear-electron interaction integrals.

    INPUT:
    AX,AY,AZ: Angular momentum components for the first Gaussian.
    BX,BY,BZ: Angular momentum components for the second Gaussian.
    AA: Exponential coefficient for the first Gaussian.
    BB: Exponential coefficient for the second Gaussian.
    RA: Center of the first Gaussian.
    RB: Center of the second Gaussian.
    RN: Nuclear coordinates.
    ZN: Nuclear charge.

    Source:

        Handbook of Computational Chemistry
        David Cook
        Oxford University Press
        1998
    """

    Vn = 0

    # Intermediate variable
    g = aa + bb
    eps = 1.0 / (4 * g)

    Rp, c = gaussian_product(aa, bb, Ra, Rb)  # Gaussian product

    def A(l, r, i, l1, l2, Ra, Rb, Rc, Rp):
        """
        Expansion coefficient A.

        Source:
            Handbook of Computational Chemistry
            David Cook
            Oxford University Press
            1998
        """

        A = 1
        A *= (-1) ** (l)
        A *= f(l, l1, l2, Rp - Ra, Rp - Rb)
        A *= (-1) ** i
        A *= s.factorial(l)
        A *= (Rp - Rc) ** (l - 2 * r - 2 * i)
        A *= eps ** (r + i)
        A /= s.factorial(r)
        A /= s.factorial(i)
        A /= s.factorial(l - 2 * r - 2 * i)

        return A

    for l in range(0, ax + bx + 1):
        for r in range(0, int(l / 2) + 1):
            for i in range(0, int((l - 2 * r) / 2) + 1):
                Ax = A(l, r, i, ax, bx, Ra[0], Rb[0], Rn[0], Rp[0])

                for m in range(0, ay + by + 1):
                    for s in range(0, int(m / 2) + 1):
                        for j in range(0, int((m - 2 * s) / 2) + 1):
                            Ay = A(m, s, j, ay, by, Ra[1], Rb[1], Rn[1], Rp[1])

                            for n in range(0, az + bz + 1):
                                for t in range(0, int(n / 2) + 1):
                                    for k in range(0, int((n - 2 * t) / 2) + 1):
                                        Az = A(
                                            n, t, k, az, bz, Ra[2], Rb[2], Rn[2], Rp[2]
                                        )

                                        nu = (
                                            l + m + n - 2 * (r + s + t) - (i + j + k)
                                        )  # Index of Boys function

                                        ff = F(
                                            nu, g * np.dot(Rp - Rn, Rp - Rn)
                                        )  # Boys function

                                        Vn += Ax * Ay * Az * ff

    # Compute normalization
    Na = norm(ax, ay, az, aa)
    Nb = norm(bx, by, bz, bb)

    Vn *= -Zn * Na * Nb * c * 2 * mpmath.pi / g

    return Vn
//  [python]
def nuclear(ax, ay, az, bx, by, bz, aa, bb, Ra, Rb, Rn, Zn):
    """
    Compute nuclear-electron interaction integrals.

    INPUT:
    AX,AY,AZ: Angular momentum components for the first Gaussian.
    BX,BY,BZ: Angular momentum components for the second Gaussian.
    AA: Exponential coefficient for the first Gaussian.
    BB: Exponential coefficient for the second Gaussian.
    RA: Center of the first Gaussian.
    RB: Center of the second Gaussian.
    RN: Nuclear coordinates.
    ZN: Nuclear charge.

    Source:

        Handbook of Computational Chemistry
        David Cook
        Oxford University Press
        1998
    """

    Vn = 0

    # Intermediate variable
    g = aa + bb
    eps = 1.0 / (4 * g)

    Rp, c = gaussian_product(aa, bb, Ra, Rb)  # Gaussian product

    def A(l, r, i, l1, l2, Ra, Rb, Rc, Rp):
        """
        Expansion coefficient A.

        Source:
            Handbook of Computational Chemistry
            David Cook
            Oxford University Press
            1998
        """

        A = 1
        A *= (-1) ** (l)
        A *= f(l, l1, l2, Rp - Ra, Rp - Rb)
        A *= (-1) ** i
        A *= special.factorial(l, exact=True)
        A *= (Rp - Rc) ** (l - 2 * r - 2 * i)
        A *= eps ** (r + i)
        A /= special.factorial(r, exact=True)
        A /= special.factorial(i, exact=True)
        A /= special.factorial(l - 2 * r - 2 * i, exact=True)

        return A

    for l in range(0, ax + bx + 1):
        for r in range(0, int(l / 2) + 1):
            for i in range(0, int((l - 2 * r) / 2) + 1):
                Ax = A(l, r, i, ax, bx, Ra[0], Rb[0], Rn[0], Rp[0])

                for m in range(0, ay + by + 1):
                    for s in range(0, int(m / 2) + 1):
                        for j in range(0, int((m - 2 * s) / 2) + 1):
                            Ay = A(m, s, j, ay, by, Ra[1], Rb[1], Rn[1], Rp[1])

                            for n in range(0, az + bz + 1):
                                for t in range(0, int(n / 2) + 1):
                                    for k in range(0, int((n - 2 * t) / 2) + 1):
                                        Az = A(
                                            n, t, k, az, bz, Ra[2], Rb[2], Rn[2], Rp[2]
                                        )

                                        nu = (
                                            l + m + n - 2 * (r + s + t) - (i + j + k)
                                        )  # Index of Boys function

                                        ff = F(
                                            nu, g * np.dot(Rp - Rn, Rp - Rn)
                                        )  # Boys function

                                        Vn += Ax * Ay * Az * ff

    # Compute normalization
    Na = norm(ax, ay, az, aa)
    Nb = norm(bx, by, bz, bb)

    Vn *= -Zn * Na * Nb * c * 2 * mpmath.pi / g

    return Vn
//  [python]
def phi_eval(self, factors, indeces, phi, lmbda):
        """
        Evaluate the phi_k functions at the points factors[i]*lmbda, for all k in indeces

        Arguments:
            factors: list of factors to multiply lmbda with.
            indeces: list of indeces k for the phi_k functions. Since we use the integral formulation, k=0 is not allowed (not needed neither).
            phi: an instance of mesh with shape (len(factors),len(indeces),*lmbda.shape) (i.e., some space to store the results)
                 it will filled as: phi[i,k][:] = phi_{indeces[k]}(factor[i]*lmbda[:])
            lmbda: dtype_u: the value of lmbda
        """

        assert 0 not in indeces, "phi_0 is not implemented, since the integral definition is not valid for k=0."

        # the quadrature rule used to evaluate the phi functions as integrals. This is not the same as the one used in the ESDC method!!!!
        c = self.phi_coll.nodes
        b = self.phi_coll.weights

        k = np.array(indeces)
        km1_fac = s.factorial(k - 1)  # (k-1)!

        # Here we use the quadrature rule to approximate the integral
        # phi_{k}(factor[i]*lmbda[:,:])= \int_0^1 e^{(1-s)*factor[i]*lambda[:,:]}*s^{k-1}/(k-1)! ds

        # First, compute e^((1-c[j])*factor[i]*lmbda[:]) for nodes c[j] on the quadrature rule and all factors[i]
        exp_terms = np.exp(((1.0 - c[None, :, None, None]) * factors[:, None, None, None]) * lmbda[None, None, :, :])
        # Then, compute the terms c[j]^{k-1}/(k-1)! for all nodes c[j] and all k and multiply with the weights b[j]
        wgt_tmp = (b[:, None] * c[:, None] ** (k[None, :] - 1)) / km1_fac[None, :]
        # Finally, compute the integral by summing over the quadrature nodes
        phi[:] = np.sum(wgt_tmp[None, :, :, None, None] * exp_terms[:, :, None, :, :], axis=1)