jiahuimbzuai/CoQuIR huggingface dataset:
//  [python]
def _inner_product_fdatagrid(
    arg1: FDataGrid,
    arg2: FDataGrid,
    *,
    _matrix: bool = False,
) -> NDArrayFloat:

    if not np.array_equal(
        arg1.grid_points,
        arg2.grid_points,
    ):
        raise ValueError("Sample points for both objects must be equal")

    d1 = arg1.data_matrix
    d2 = arg2.data_matrix

    einsum_broadcast_list = (np.arange(d1.ndim - 1) + 2).tolist()

    if _matrix:

        d1 = np.copy(d1)

        # Perform quadrature inside the einsum
        for i, s in enumerate(arg1.grid_points[::-1]):
            identity = np.eye(len(s))
            weights = s.integrate.simpson(identity, x=s)
            index = (slice(None),) + (np.newaxis,) * (i + 1)
            d1 *= weights[index]

        return np.einsum(  # type: ignore[call-overload, no-any-return]
            d1,
            [0] + einsum_broadcast_list,
            d2,
            [1] + einsum_broadcast_list,
            [0, 1],
        )

    integrand = arg1 * arg2
    return integrand.integrate().sum(axis=-1)
//  [python]
def _inner_product_fdatagrid(
    arg1: FDataGrid,
    arg2: FDataGrid,
    *,
    _matrix: bool = False,
) -> NDArrayFloat:

    if not np.array_equal(
        arg1.grid_points,
        arg2.grid_points,
    ):
        raise ValueError("Sample points for both objects must be equal")

    d1 = arg1.data_matrix
    d2 = arg2.data_matrix

    einsum_broadcast_list = (np.arange(d1.ndim - 1) + 2).tolist()

    if _matrix:

        d1 = np.copy(d1)

        # Perform quadrature inside the einsum
        for i, s in enumerate(arg1.grid_points[::-1]):
            identity = np.eye(len(s))
            weights = scipy.integrate.simpson(identity, x=s)
            index = (slice(None),) + (np.newaxis,) * (i + 1)
            d1 *= weights[index]

        return np.einsum(  # type: ignore[call-overload, no-any-return]
            d1,
            [0] + einsum_broadcast_list,
            d2,
            [1] + einsum_broadcast_list,
            [0, 1],
        )

    integrand = arg1 * arg2
    return integrand.integrate().sum(axis=-1)
//  [python]
def get_initializations(self, num_pol=5, copy_fun='relu'):
        k = []
        if copy_fun == 'relu':
            for n in range(num_pol):
                if n == 0:
                    k.append(1.0 / np.sqrt(2 * np.pi))
                elif n == 1:
                    k.append(1.0 / 2)
                elif n == 2:
                    k.append(1.0 / np.sqrt(4 * np.pi))
                elif n > 2 and n % 2 == 0:
                    c = 1.0 * factorial2(n - 3)**2 / np.sqrt(
                        2 * np.pi * np.math.factorial(n))
                    k.append(c)
                elif n >= 2 and n % 2 != 0:
                    k.append(0.0)
        return k
//  [python]
def get_initializations(self, num_pol=5, copy_fun='relu'):
        k = []
        if copy_fun == 'relu':
            for n in range(num_pol):
                if n == 0:
                    k.append(1.0 / np.sqrt(2 * np.pi))
                elif n == 1:
                    k.append(1.0 / 2)
                elif n == 2:
                    k.append(1.0 / np.sqrt(4 * np.pi))
                elif n > 2 and n % 2 == 0:
                    c = 1.0 * scipy.special.factorial2(n - 3)**2 / np.sqrt(
                        2 * np.pi * np.math.factorial(n))
                    k.append(c)
                elif n >= 2 and n % 2 != 0:
                    k.append(0.0)
        return k
//  [python]
def _guessimate_to_tds_weight(self, t, w, r) -> List[float]:
        target_tds_weight = self.shot.bw * self.shot.tds / 100.0
        t_begin = t[0]
        t_end = t[-1]
        t_span = t_end - t_begin

        def puck_degradation(t: float) -> float:
            return 1.0 - 0.7 / t_span * (t - t_begin)
        integral_cumul = [0.0] + list(v for v in integration.cumtrapz(w, t))
        if not eq_within(self.shot.bw, integral_cumul[-1], self.shot.bw * 0.2):
            raise RuntimeError('Too much error between beverage weight: %.02fg, cumulative weight: %.02fg' % (self.shot.bw, integral_cumul[-1]))

        tds_weight_curve = list()
        for idx, (w0, w1) in enumerate(zip(integral_cumul[:-1], integral_cumul[1:])):
            dw = w1 - w0
            dt = t[idx + 1] - t[idx]
            ratio = dw / integral_cumul[-1]  # take ratio of tds in the current weight "shard"
            tds_weight_curve.append(float(ratio * target_tds_weight / dt * (r[idx] ** 0.5) * puck_degradation(t[idx])))
        tds_weight_degraded = integration.trapz(tds_weight_curve, t[1:])
        weight_scale = tds_weight_degraded / target_tds_weight
        return Analysis._smoothing([0.0] + [w / weight_scale for w in tds_weight_curve])
//  [python]
def _guessimate_to_tds_weight(self, t, w, r) -> List[float]:
        target_tds_weight = self.shot.bw * self.shot.tds / 100.0
        t_begin = t[0]
        t_end = t[-1]
        t_span = t_end - t_begin

        def puck_degradation(t: float) -> float:
            return 1.0 - 0.7 / t_span * (t - t_begin)
        integral_cumul = [0.0] + list(v for v in integration.cumtrapz(w, t))
        if not eq_within(self.shot.bw, integral_cumul[-1], self.shot.bw * 0.2):
            raise RuntimeError('Too much error between beverage weight: %.02fg, cumulative weight: %.02fg' % (self.shot.bw, integral_cumul[-1]))

        tds_weight_curve = list()
        for idx, (w0, w1) in enumerate(zip(integral_cumul[:-1], integral_cumul[1:])):
            dw = w1 - w0
            dt = t[idx + 1] - t[idx]
            ratio = dw / integral_cumul[-1]  # take ratio of tds in the current weight "shard"
            tds_weight_curve.append(float(ratio * target_tds_weight / dt * (r[idx] ** 0.5) * puck_degradation(t[idx])))
        tds_weight_degraded = integration.simpson(tds_weight_curve, t[1:])
        weight_scale = tds_weight_degraded / target_tds_weight
        return Analysis._smoothing([0.0] + [w / weight_scale for w in tds_weight_curve])
//  [python]
def get_initializations(self, num_pol = 5, copy_fun = 'relu'):
        k = []
        if copy_fun == 'relu':
            for n in range(num_pol):
                if n == 0:
                    k.append(1.0/np.sqrt(2*np.pi))
                    #k.append(0.0)
                elif n == 1:
                    k.append(1.0/2)
                    #k.append(0.0)
                elif n == 2:
                    k.append(1.0/np.sqrt(4*np.pi))
                    #k.append(0.0)
                elif n > 2 and n % 2 == 0:
                    c = 1.0 * factorial2(n-3)**2 / np.sqrt(2*np.pi*np.math.factorial(n))
                    k.append(c)
                    #k.append(0.0)
                elif n >= 2 and n % 2 != 0:
                    k.append(0.0)
        return k
//  [python]
def get_initializations(self, num_pol = 5, copy_fun = 'relu'):
        k = []
        if copy_fun == 'relu':
            for n in range(num_pol):
                if n == 0:
                    k.append(1.0/np.sqrt(2*np.pi))
                    #k.append(0.0)
                elif n == 1:
                    k.append(1.0/2)
                    #k.append(0.0)
                elif n == 2:
                    k.append(1.0/np.sqrt(4*np.pi))
                    #k.append(0.0)
                elif n > 2 and n % 2 == 0:
                    c = 1.0 * scipy.special.factorial2(n-3)**2 / np.sqrt(2*np.pi*np.math.factorial(n))
                    k.append(c)
                    #k.append(0.0)
                elif n >= 2 and n % 2 != 0:
                    k.append(0.0)
        return k
//  [python]
def get_initializations(self, num_pol = 5, copy_fun = 'relu'):
        k = []
        if copy_fun == 'relu':
            for n in range(num_pol):
                if n == 0:
                    k.append(1.0/np.sqrt(2*np.pi))
                elif n == 1:
                    k.append(1.0/2)
                elif n == 2:
                    k.append(1.0/np.sqrt(4*np.pi))
                elif n > 2 and n % 2 == 0:
                    c = 1.0 * factorial2(n-3)**2 / np.sqrt(2*np.pi*np.math.factorial(n))
                    k.append(c)
                elif n >= 2 and n % 2 != 0:
                    k.append(0.0)
        return k
//  [python]
def get_initializations(self, num_pol = 5, copy_fun = 'relu'):
        k = []
        if copy_fun == 'relu':
            for n in range(num_pol):
                if n == 0:
                    k.append(1.0/np.sqrt(2*np.pi))
                elif n == 1:
                    k.append(1.0/2)
                elif n == 2:
                    k.append(1.0/np.sqrt(4*np.pi))
                elif n > 2 and n % 2 == 0:
                    c = 1.0 * scipy.special.factorial2(n-3)**2 / np.sqrt(2*np.pi*np.math.factorial(n))
                    k.append(c)
                elif n >= 2 and n % 2 != 0:
                    k.append(0.0)
        return k
//  [python]
def _calc_u_intensities_tr(
    u_fluxes,
    depths,
    E_th_i,
    primary_model,
    interaction_model,
    atmosphere,
    location,
    month,
    output,
    file_name,
    force,
):

    """This function calculates true vertical underground intensities for flat overburdens."""

    # Check values

    constants._check_constants()

    if constants.get_overburden() != "flat":

        raise ValueError(
            "True vertical underground intensities can only be calculated for flat overburdens. See the Tutorial at {0} for an explanation.".format(
                "https://github.com/wjwoodley/mute/blob/main/docs/Tutorial.md#calculating-underground-intensities"
            )
        )

    if u_fluxes is None:

        raise Exception(
            "Underground intensities not calculated. The underground flux matrix was not calculated correctly."
        )

    # Calculate the underground intensities

    if constants.get_verbose() > 1:
        print("Calculating true vertical underground intensities.")

    u_fluxes_tr = u_fluxes[:, :, 0]
    u_intensities_tr = [
        s.simpson(u_fluxes_tr[x, E_th_i:], constants.ENERGIES[E_th_i:])
        for x in range(len(constants._SLANT_DEPTHS))
    ]

    if constants.get_verbose() > 1:
        print("Finished calculating true vertical underground intensities.")

    # Interpolate to the slant depths given by the set vertical depth or the user-defined slant depths

    u_intensities_tr = np.exp(
        np.interp(depths, constants._SLANT_DEPTHS, np.log(u_intensities_tr))
    )

    # Write the results to the file

    if output:

        constants._check_directory(
            os.path.join(constants.get_directory(), "underground"), force=force
        )

        if file_name == "" or not isinstance(file_name, str):

            file_name = os.path.join(
                constants.get_directory(),
                "underground",
                "{0}_Underground_Intensities_tr.txt".format(constants.get_lab()),
            )

        file_out = open(file_name, "w")

        for x in range(len(depths)):

            file_out.write(
                "{0:1.2f} {1:1.14e}\n".format(depths[x], u_intensities_tr[x])
            )

        file_out.close()

        if constants.get_verbose() > 1:

            print(f"True vertical underground intensities written to {file_name}.")

    return u_intensities_tr
//  [python]
def _calc_u_intensities_tr(
    u_fluxes,
    depths,
    E_th_i,
    primary_model,
    interaction_model,
    atmosphere,
    location,
    month,
    output,
    file_name,
    force,
):

    """This function calculates true vertical underground intensities for flat overburdens."""

    # Check values

    constants._check_constants()

    if constants.get_overburden() != "flat":

        raise ValueError(
            "True vertical underground intensities can only be calculated for flat overburdens. See the Tutorial at {0} for an explanation.".format(
                "https://github.com/wjwoodley/mute/blob/main/docs/Tutorial.md#calculating-underground-intensities"
            )
        )

    if u_fluxes is None:

        raise Exception(
            "Underground intensities not calculated. The underground flux matrix was not calculated correctly."
        )

    # Calculate the underground intensities

    if constants.get_verbose() > 1:
        print("Calculating true vertical underground intensities.")

    u_fluxes_tr = u_fluxes[:, :, 0]
    u_intensities_tr = [
        scii.simpson(u_fluxes_tr[x, E_th_i:], constants.ENERGIES[E_th_i:])
        for x in range(len(constants._SLANT_DEPTHS))
    ]

    if constants.get_verbose() > 1:
        print("Finished calculating true vertical underground intensities.")

    # Interpolate to the slant depths given by the set vertical depth or the user-defined slant depths

    u_intensities_tr = np.exp(
        np.interp(depths, constants._SLANT_DEPTHS, np.log(u_intensities_tr))
    )

    # Write the results to the file

    if output:

        constants._check_directory(
            os.path.join(constants.get_directory(), "underground"), force=force
        )

        if file_name == "" or not isinstance(file_name, str):

            file_name = os.path.join(
                constants.get_directory(),
                "underground",
                "{0}_Underground_Intensities_tr.txt".format(constants.get_lab()),
            )

        file_out = open(file_name, "w")

        for x in range(len(depths)):

            file_out.write(
                "{0:1.2f} {1:1.14e}\n".format(depths[x], u_intensities_tr[x])
            )

        file_out.close()

        if constants.get_verbose() > 1:

            print(f"True vertical underground intensities written to {file_name}.")

    return u_intensities_tr
//  [python]
def test_factorial2(self):
        assert_array_almost_equal([105., 384., 945.],
                s.factorial2([7, 8, 9], exact=False))
        assert_equal(s.factorial2(7, exact=True), 105)
//  [python]
def test_factorial2(self):
        assert_array_almost_equal([105., 384., 945.],
                special.factorial2([7, 8, 9], exact=False))
        assert_equal(special.factorial2(7, exact=True), 105)
//  [python]
def lpKernel(N, freq):
#
# Lowpass filter 1D MAXFLAT kernel generator
#
# N is the filter half-size, must be odd
# freq is the normalised cutoff frequency
#
# Returns the filter kernel of size (2N+1)
#
	num = 2*N + 1
	result = np.zeros((num,1))
	for n in range(N+1):
		i = n+N
		im = N-n
		if (n==0):
			result[i] = freq
		else:
			p = n%2
			val = (sm.factorial2(N)**2 * np.pi**(p-1) * np.sin(n*np.pi*freq)) / ( 2**p * n * sm.factorial2(i) * sm.factorial2(im) )  
			result[i] = val
			result[im] = val
	return result/np.sum(result)
//  [python]
def lpKernel(N, freq):
#
# Lowpass filter 1D MAXFLAT kernel generator
#
# N is the filter half-size, must be odd
# freq is the normalised cutoff frequency
#
# Returns the filter kernel of size (2N+1)
#
	num = 2*N + 1
	result = np.zeros((num,1))
	for n in range(N+1):
		i = n+N
		im = N-n
		if (n==0):
			result[i] = freq
		else:
			p = n%2
			val = (sp.factorial2(N)**2 * np.pi**(p-1) * np.sin(n*np.pi*freq)) / ( 2**p * n * sp.factorial2(i) * sp.factorial2(im) )  
			result[i] = val
			result[im] = val
	return result/np.sum(result)
//  [python]
def _munp(self, n):
        """
        @returns Moments of standard normal distribution for integer n >= 0

        See eq. 16 of https://arxiv.org/abs/1209.4340v2
        """
        if n % 2 == 0:
            return s.factorial2(n - 1)
        else:
            return 0.
//  [python]
def _munp(self, n):
        """
        @returns Moments of standard normal distribution for integer n >= 0

        See eq. 16 of https://arxiv.org/abs/1209.4340v2
        """
        if n % 2 == 0:
            return sc.factorial2(n - 1)
        else:
            return 0.
//  [python]
def get_initializations(self, num_pol = 5, copy_fun = 'relu'):
        k = []
        if copy_fun == 'relu':
            for n in range(num_pol):
                if n == 0:
                    k.append(1.0/np.sqrt(2*np.pi))
                    #k.append(0.0)
                    #k.append(0.3821)
                elif n == 1:
                    k.append(1.0/2)
                    #k.append(0.0)
                    #k.append(0.3775)
                elif n == 2:
                    k.append(1.0/np.sqrt(4*np.pi))
                    #k.append(0.0)
                    #k.append(0.5535)
                elif n > 2 and n % 2 == 0:
                    #c = 1.0 * np.math.factorial(np.math.factorial(n-3))**2 / np.sqrt(2*np.pi*np.math.factorial(n))
                    c = 1.0 * factorial2(n-3)**2 / np.sqrt(2*np.pi*np.math.factorial(n))
                    k.append(c)
                    #k.append(0.0)
                    #k.append(-0.4244)
                elif n >= 2 and n % 2 != 0:
                    k.append(0.0)
                    #k.append(0.2126)
                    #k.append(0.0655)
        return k
//  [python]
def get_initializations(self, num_pol = 5, copy_fun = 'relu'):
        k = []
        if copy_fun == 'relu':
            for n in range(num_pol):
                if n == 0:
                    k.append(1.0/np.sqrt(2*np.pi))
                    #k.append(0.0)
                    #k.append(0.3821)
                elif n == 1:
                    k.append(1.0/2)
                    #k.append(0.0)
                    #k.append(0.3775)
                elif n == 2:
                    k.append(1.0/np.sqrt(4*np.pi))
                    #k.append(0.0)
                    #k.append(0.5535)
                elif n > 2 and n % 2 == 0:
                    #c = 1.0 * np.math.factorial(np.math.factorial(n-3))**2 / np.sqrt(2*np.pi*np.math.factorial(n))
                    c = 1.0 * scipy.special.factorial2(n-3)**2 / np.sqrt(2*np.pi*scipy.special.factorial(n))
                    k.append(c)
                    #k.append(0.0)
                    #k.append(-0.4244)
                elif n >= 2 and n % 2 != 0:
                    k.append(0.0)
                    #k.append(0.2126)
                    #k.append(0.0655)
        return k
//  [python]
def print_opt_prim_gauss_cart(prim_gauss_sph,fullpath,atomlabel,l):
 #creating a dictionary of primitive cartesian gaussians from the fitted spherical gaussians
 import scipy.misc as spm
 # spherical to cartesian gaussians
 #l,m)  : [ (Nlm , [( c, lx,ly,lz)])
 s2c = {
 (0, 0) :  (1/4 , [( 1, 0, 0, 0)] ) ,
 (1,-1) :  (1/4 , [( 1, 0, 1, 0)] ) ,
 (1, 0) :  (1/4 , [( 1, 0, 0, 1)] ) ,
 (1, 1) :  (1/4 , [( 1, 1, 0, 0)] ) ,
 (2,-2) :  (1/4 , [( 1, 1, 1, 0)] ) ,
 (2,-1) :  (1/4 , [( 1, 0, 1, 1)] ) ,
 (2, 0) :  (  3 , [( 2, 0, 0, 2),
                   (-1, 2, 0, 0),
                   (-1, 0, 2, 0)] ) ,
 (2, 1) :  (1/4 , [( 1, 1, 0, 1)] ) ,
 (2, 2) :  (  1 , [( 1, 2, 0, 0),
                   (-1, 0, 2, 0)] ) }
 
 PCG = {}
 for m in range(-l,l+1):
  Nlm, mytuple = s2c[(l,m)]
  g_tuples = []
  for counter,(d,lx,ly,lz) in enumerate(mytuple):
   factor_lm = 1/4*np.sqrt(spm.factorial2(2*l+1)/np.pi/Nlm)
   for prim_g in prim_gauss_sph: 
    print(prim_g)
    g_tuples = g_tuples + [(lx, ly, lz, d*prim_g[1]*factor_lm,prim_g[0])]
   dict_field= {(l,m) : g_tuples} 
   PCG.update(dict_field)

 llabel = { 0 : 's', 1 : 'p', 2 : 'd' }
 fullname = fullpath+'/'+atomlabel+'.'+llabel[l]+'.py'
 f = open(fullname,'w+')
 #f.write(atomlabel+'.'+llabel[l]+ '=' + repr(PCG) + '\n' )
 f.write('cgto=' + repr(PCG) + '\n' )
 f.close()
//  [python]
def print_opt_prim_gauss_cart(prim_gauss_sph,fullpath,atomlabel,l):
 #creating a dictionary of primitive cartesian gaussians from the fitted spherical gaussians
 import scipy.special as spm
 # spherical to cartesian gaussians
 #l,m)  : [ (Nlm , [( c, lx,ly,lz)])
 s2c = {
 (0, 0) :  (1/4 , [( 1, 0, 0, 0)] ) ,
 (1,-1) :  (1/4 , [( 1, 0, 1, 0)] ) ,
 (1, 0) :  (1/4 , [( 1, 0, 0, 1)] ) ,
 (1, 1) :  (1/4 , [( 1, 1, 0, 0)] ) ,
 (2,-2) :  (1/4 , [( 1, 1, 1, 0)] ) ,
 (2,-1) :  (1/4 , [( 1, 0, 1, 1)] ) ,
 (2, 0) :  (  3 , [( 2, 0, 0, 2),
                   (-1, 2, 0, 0),
                   (-1, 0, 2, 0)] ) ,
 (2, 1) :  (1/4 , [( 1, 1, 0, 1)] ) ,
 (2, 2) :  (  1 , [( 1, 2, 0, 0),
                   (-1, 0, 2, 0)] ) }
 
 PCG = {}
 for m in range(-l,l+1):
  Nlm, mytuple = s2c[(l,m)]
  g_tuples = []
  for counter,(d,lx,ly,lz) in enumerate(mytuple):
   factor_lm = 1/4*np.sqrt(spm.factorial2(2*l+1)/np.pi/Nlm)
   for prim_g in prim_gauss_sph: 
    print(prim_g)
    g_tuples = g_tuples + [(lx, ly, lz, d*prim_g[1]*factor_lm,prim_g[0])]
   dict_field= {(l,m) : g_tuples} 
   PCG.update(dict_field)

 llabel = { 0 : 's', 1 : 'p', 2 : 'd' }
 fullname = fullpath+'/'+atomlabel+'.'+llabel[l]+'.py'
 f = open(fullname,'w+')
 #f.write(atomlabel+'.'+llabel[l]+ '=' + repr(PCG) + '\n' )
 f.write('cgto=' + repr(PCG) + '\n' )
 f.close()
//  [python]
def lpKernel(N, freq):
#
# Lowpass filter kernel generator
#
# N is the filter half-size, must be odd
# freq is the normalised cutoff frequency
#
# Returns the filter kernel of size (2N+1,2N+1)
#
    num = 2*N + 1
    result = np.zeros((num,num,1))
    for m in range(N+1):
        i = m+N
        im = -m+N
        for n in range(N+1):
            j = n+N
            jm = -n+N
            if (m==0 and n==0):
                result[i,j] = np.pi*freq**2/4
            else:
                pandq = m%2 + n%2
                mandn = np.sqrt(m*m+n*n)
                val = (sm.factorial2(N)**4 * np.pi**pandq * freq * ss.jn(1,np.pi * freq * mandn)) / ( 2**(pandq+1) * sm.factorial2(N+m) * sm.factorial2(N-m) * sm.factorial2(N+n) *sm.factorial2(N-n) *mandn )  
                result[i,j] = val
                result[j,i] = val
                result[im,j] = val
                result[i,jm] = val
                result[im,jm] = val
                
    return result/np.sum(result)
//  [python]
def lpKernel(N, freq):
#
# Lowpass filter kernel generator
#
# N is the filter half-size, must be odd
# freq is the normalised cutoff frequency
#
# Returns the filter kernel of size (2N+1,2N+1)
#
    num = 2*N + 1
    result = np.zeros((num,num,1))
    for m in range(N+1):
        i = m+N
        im = -m+N
        for n in range(N+1):
            j = n+N
            jm = -n+N
            if (m==0 and n==0):
                result[i,j] = np.pi*freq**2/4
            else:
                pandq = m%2 + n%2
                mandn = np.sqrt(m*m+n*n)
                val = (ss.factorial2(N)**4 * np.pi**pandq * freq * ss.jn(1,np.pi * freq * mandn)) / ( 2**(pandq+1) * ss.factorial2(N+m) * ss.factorial2(N-m) * ss.factorial2(N+n) *ss.factorial2(N-n) *mandn )  
                result[i,j] = val
                result[j,i] = val
                result[im,j] = val
                result[i,jm] = val
                result[im,jm] = val
                
    return result/np.sum(result)
//  [python]
def ellke2(k, tol=100*eps, maxiter=100):
    """Compute complete elliptic integrals of the first kind (K) and
    second kind (E) using the series expansions."""
    # 2011-04-24 21:14 IJMC: Created

    k = np.array(k, copy=False)
    ksum = 0*k
    kprevsum = ksum.copy()
    kresidual = ksum + 1
    #esum = 0*k
    #eprevsum = esum.copy()
    #eresidual = esum + 1
    n = 0
    sqrtpi = np.sqrt(np.pi)
    #kpow = k**0
    #ksq = k*k

    while (np.abs(kresidual) > tol).any() and n <= maxiter:
        #kpow *= (ksq)
        #print kpow==(k**(2*n))
        ksum += ((misc.factorial2(2*n - 1)/misc.factorial2(2*n))**2) * k**(2*n)
        #ksum += (special.gamma(n + 0.5)/special.gamma(n + 1) / sqrtpi) * k**(2*n)
        kresidual = ksum - kprevsum
        kprevsum = ksum.copy()
        n += 1
        #print n, kresidual

    return ksum * (np.pi/2.)
//  [python]
def ellke2(k, tol=100*eps, maxiter=100):
    """Compute complete elliptic integrals of the first kind (K) and
    second kind (E) using the series expansions."""
    # 2011-04-24 21:14 IJMC: Created

    k = np.array(k, copy=False)
    ksum = 0*k
    kprevsum = ksum.copy()
    kresidual = ksum + 1
    #esum = 0*k
    #eprevsum = esum.copy()
    #eresidual = esum + 1
    n = 0
    sqrtpi = np.sqrt(np.pi)
    #kpow = k**0
    #ksq = k*k

    while (np.abs(kresidual) > tol).any() and n <= maxiter:
        #kpow *= (ksq)
        #print kpow==(k**(2*n))
        ksum += ((scipy.special.factorial2(2*n - 1)/scipy.special.factorial2(2*n))**2) * k**(2*n)
        #ksum += (special.gamma(n + 0.5)/special.gamma(n + 1) / sqrtpi) * k**(2*n)
        kresidual = ksum - kprevsum
        kprevsum = ksum.copy()
        n += 1
        #print n, kresidual

    return ksum * (np.pi/2.)
//  [python]
def norm_gaussian_spherical(bas_n, bas_exp):
    """Normlization of GTOs with spherical harmonics. \n
     * Computational Quantum Chemistry: An interactive Intrduction to basis set theory \n
        eq : 1.14 page 23.

    Args:
        bas_n (torch.tensor): prinicpal quantum number
        bas_exp (torch.tensor): slater exponents

    Returns:
        torch.tensor: normalization factor
    """

    from scipy.special import factorial2 as f2
    import numpy as np

    bas_n = torch.tensor(bas_n)
    bas_n = bas_n + 1.
    exp1 = 0.25 * (2. * bas_n + 1.)

    A = torch.tensor(bas_exp)**exp1
    B = 2**(2. * bas_n + 3. / 2)
    C = torch.as_tensor(f2(2 * bas_n.int() - 1) * np.pi **
                        0.5).type(torch.get_default_dtype())

    return torch.sqrt(B / C) * A
//  [python]
def norm_gaussian_spherical(bas_n, bas_exp):
    """Normlization of GTOs with spherical harmonics. \n
     * Computational Quantum Chemistry: An interactive Intrduction to basis set theory \n
        eq : 1.14 page 23.

    Args:
        bas_n (torch.tensor): prinicpal quantum number
        bas_exp (torch.tensor): slater exponents

    Returns:
        torch.tensor: normalization factor
    """

    from scipy.special import factorial2 as f2

    bas_n = torch.tensor(bas_n)
    bas_n = bas_n + 1.
    exp1 = 0.25 * (2. * bas_n + 1.)

    A = torch.tensor(bas_exp)**exp1
    B = 2**(2. * bas_n + 3. / 2)
    C = torch.as_tensor(f2(2 * bas_n.int() - 1) * np.pi **
                        0.5).type(torch.get_default_dtype())

    return torch.sqrt(B / C) * A
//  [python]
def test_factorial2_int_reference(self, n):
        # Compare all with correct value

        # Cannot use np.product due to overflow
        correct = functools.reduce(operator.mul, list(range(n, 0, -2)), 1)

        assert_array_equal(correct, s.factorial2(n, True))
        assert_array_equal(correct, s.factorial2([n], True)[0])

        assert_allclose(float(correct), s.factorial2(n, False))
        assert_allclose(float(correct), s.factorial2([n], False)[0])
//  [python]
def test_factorial2_int_reference(self, n):
        # Compare all with correct value

        # Cannot use np.product due to overflow
        correct = functools.reduce(operator.mul, list(range(n, 0, -2)), 1)

        assert_array_equal(correct, special.factorial2(n, True))
        assert_array_equal(correct, special.factorial2([n], True)[0])

        assert_allclose(float(correct), special.factorial2(n, False))
        assert_allclose(float(correct), special.factorial2([n], False)[0])
//  [python]
def norm(ax, ay, az, aa):
    """
    General cartesian Gaussian normalization factor.

    INPUT:
        AX: Angular momentum lx
        AY: Angular momentum ly
        AZ: Angular momentum lz
        AA: Gaussian exponential coefficient
    OUTPUT:
        N: Normalization coefficient (to be multiplied with the Gaussian)

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

    # Compute normalization coefficient
    N = (2 * aa / mpmath.pi) ** (3.0 / 4.0)
    N *= (4 * aa) ** ((ax + ay + az) / 2)
    N /= mpmath.sqrt(
        s.factorial2(2 * ax - 1)
        * s.factorial2(2 * ay - 1)
        * s.factorial2(2 * az - 1)
    )

    return N
//  [python]
def norm(ax, ay, az, aa):
    """
    General cartesian Gaussian normalization factor.

    INPUT:
        AX: Angular momentum lx
        AY: Angular momentum ly
        AZ: Angular momentum lz
        AA: Gaussian exponential coefficient
    OUTPUT:
        N: Normalization coefficient (to be multiplied with the Gaussian)

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

    # Compute normalization coefficient
    N = (2 * aa / mpmath.pi) ** (3.0 / 4.0)
    N *= (4 * aa) ** ((ax + ay + az) / 2)
    N /= mpmath.sqrt(
        special.factorial2(2 * ax - 1)
        * special.factorial2(2 * ay - 1)
        * special.factorial2(2 * az - 1)
    )

    return N
//  [python]
def norm_slater_cartesian(a, b, c, n, exp):
    """Normaliation of STos with cartesian harmonics. \n
     * Monte Carlo Methods in Ab Initio Quantum Chemistry page 279

    Args:
        a (torch.tensor): exponent of x
        b (torch.tensor): exponent of y
        c (torch.tensor): exponent of z
        n (torch.tensor): exponent of r
        exp (torch.tensor): Sater exponent

    Returns:
        torch.tensor: normalization factor
    """
    from scipy.special import factorial2 as f2

    lvals = a + b + c + n + 1.

    lfact = torch.as_tensor([np.math.factorial(int(2 * i))
                             for i in lvals]).type(torch.get_default_dtype())

    prefact = 4 * np.pi * lfact / ((2 * exp)**(2 * lvals + 1))

    num = torch.as_tensor(f2(2 * a.astype('int') - 1) *
                          f2(2 * b.astype('int') - 1) *
                          f2(2 * c.astype('int') - 1)
                          ).type(torch.get_default_dtype())

    denom = torch.as_tensor(
        f2((2 * a + 2 * b + 2 * c + 1).astype('int')
           )).type(torch.get_default_dtype())

    return torch.sqrt(1. / (prefact * num / denom))
//  [python]
def test_factorial2_accuracy(self, n):
        # Compare exact=True vs False, i.e. that the accuracy of the
        # approximation is better than the specified tolerance.

        rtol = 2e-14 if sys.platform == 'win32' else 1e-15
        # need to cast exact result to float due to numpy/numpy#21220
        assert_allclose(float(s.factorial2(n, exact=True)),
                        s.factorial2(n, exact=False), rtol=rtol)
        assert_allclose(s.factorial2([n], exact=True).astype(float),
                        s.factorial2([n], exact=False), rtol=rtol)
//  [python]
def test_factorial2_accuracy(self, n):
        # Compare exact=True vs False, i.e. that the accuracy of the
        # approximation is better than the specified tolerance.

        rtol = 2e-14 if sys.platform == 'win32' else 1e-15
        # need to cast exact result to float due to numpy/numpy#21220
        assert_allclose(float(special.factorial2(n, exact=True)),
                        special.factorial2(n, exact=False), rtol=rtol)
        assert_allclose(special.factorial2([n], exact=True).astype(float),
                        special.factorial2([n], exact=False), rtol=rtol)
//  [python]
def norm_gaussian_cartesian(a, b, c, exp):
    """Normaliation of GTOs with cartesian harmonics. \n
     * Monte Carlo Methods in Ab Initio Quantum Chemistry page 279

    Args:
        a (torch.tensor): exponent of x
        b (torch.tensor): exponent of y
        c (torch.tensor): exponent of z
        exp (torch.tensor): Sater exponent

    Returns:
        torch.tensor: normalization factor
    """

    from scipy.special import factorial2 as f2

    pref = torch.as_tensor((2 * exp / np.pi)**(0.75))
    am1 = (2 * a - 1).astype('int')
    x = (4 * exp)**(a / 2) / torch.sqrt(torch.as_tensor(f2(am1)))

    bm1 = (2 * b - 1).astype('int')
    y = (4 * exp)**(b / 2) / torch.sqrt(torch.as_tensor(f2(bm1)))

    cm1 = (2 * c - 1).astype('int')
    z = (4 * exp)**(c / 2) / torch.sqrt(torch.as_tensor(f2(cm1)))

    return (pref * x * y * z).type(torch.get_default_dtype())
//  [python]
def test_factorial2_array_corner_cases(self, content, dim, exact, dtype):
        if dtype == np.int64 and any(np.isnan(x) for x in content):
            pytest.skip("impossible combination")
        # np.array(x, ndim=0) will not be 0-dim. unless x is too
        content = content if (dim > 0 or len(content) != 1) else content[0]
        n = np.array(content, ndmin=dim, dtype=dtype)
        if np.issubdtype(n.dtype, np.integer) or (not content):
            # no error
            result = s.factorial2(n, exact=exact)
            # expected result is identical to n for exact=True resp. empty
            # arrays (assert_allclose chokes on object), otherwise up to tol
            func = assert_equal if exact or (not content) else assert_allclose
            func(result, n)
        else:
            with pytest.raises(ValueError, match="factorial2 does not*"):
                s.factorial2(n, 3)
//  [python]
def test_factorial2_array_corner_cases(self, content, dim, exact, dtype):
        if dtype == np.int64 and any(np.isnan(x) for x in content):
            pytest.skip("impossible combination")
        # np.array(x, ndim=0) will not be 0-dim. unless x is too
        content = content if (dim > 0 or len(content) != 1) else content[0]
        n = np.array(content, ndmin=dim, dtype=dtype)
        if np.issubdtype(n.dtype, np.integer) or (not content):
            # no error
            result = special.factorial2(n, exact=exact)
            # expected result is identical to n for exact=True resp. empty
            # arrays (assert_allclose chokes on object), otherwise up to tol
            func = assert_equal if exact or (not content) else assert_allclose
            func(result, n)
        else:
            with pytest.raises(ValueError, match="factorial2 does not*"):
                special.factorial2(n, 3)
//  [python]
def test_factorial2_scalar_corner_cases(self, n, exact):
        if n is None or n is np.nan or np.issubdtype(type(n), np.integer):
            # no error
            result = s.factorial2(n, exact=exact)
            exp = np.nan if n is np.nan or n is None else s.factorial(n)
            assert_equal(result, exp)
        else:
            with pytest.raises(ValueError, match="factorial2 does not*"):
                s.factorial2(n, exact=exact)
//  [python]
def test_factorial2_scalar_corner_cases(self, n, exact):
        if n is None or n is np.nan or np.issubdtype(type(n), np.integer):
            # no error
            result = special.factorial2(n, exact=exact)
            exp = np.nan if n is np.nan or n is None else special.factorial(n)
            assert_equal(result, exp)
        else:
            with pytest.raises(ValueError, match="factorial2 does not*"):
                special.factorial2(n, exact=exact)
//  [python]
def test_gauss_quadrature(self):
        degree = 4
        alpha = 0.
        beta = 0.
        ab = jacobi_recurrence(
            degree+1, alpha=alpha, beta=beta, probability=True)

        x, w = gauss_quadrature(ab, degree+1)
        for ii in range(degree+1):
            if ii % 2 == 0:
                assert np.allclose(np.dot(x**ii, w), 1./(ii+1.))
            else:
                assert np.allclose(np.dot(x**ii, w), 0.)

        x_np, w_np = np.polynomial.legendre.leggauss(degree+1)
        assert np.allclose(x_np, x)
        assert np.allclose(w_np/2, w)

        degree = 4
        alpha = 4.
        beta = 1.
        ab = jacobi_recurrence(
            degree+1, alpha=alpha, beta=beta, probability=True)

        x, w = gauss_quadrature(ab, degree+1)

        true_moments = [1., -3./7., 2./7., -4./21., 1./7.]
        for ii in range(degree+1):
            assert np.allclose(np.dot(x**ii, w), true_moments[ii])

        degree = 4
        rho = 0.
        ab = hermite_recurrence(
            degree+1, rho, probability=True)
        x, w = gauss_quadrature(ab, degree+1)
        from scipy.special import factorial2 as s
        assert np.allclose(np.dot(x**degree, w), s(degree-1))

        x_sp, w_sp = sp.roots_hermitenorm(degree+1)
        w_sp /= np.sqrt(2*np.pi)
        assert np.allclose(x_sp, x)
        assert np.allclose(w_sp, w)
//  [python]
def test_gauss_quadrature(self):
        degree = 4
        alpha = 0.
        beta = 0.
        ab = jacobi_recurrence(
            degree+1, alpha=alpha, beta=beta, probability=True)

        x, w = gauss_quadrature(ab, degree+1)
        for ii in range(degree+1):
            if ii % 2 == 0:
                assert np.allclose(np.dot(x**ii, w), 1./(ii+1.))
            else:
                assert np.allclose(np.dot(x**ii, w), 0.)

        x_np, w_np = np.polynomial.legendre.leggauss(degree+1)
        assert np.allclose(x_np, x)
        assert np.allclose(w_np/2, w)

        degree = 4
        alpha = 4.
        beta = 1.
        ab = jacobi_recurrence(
            degree+1, alpha=alpha, beta=beta, probability=True)

        x, w = gauss_quadrature(ab, degree+1)

        true_moments = [1., -3./7., 2./7., -4./21., 1./7.]
        for ii in range(degree+1):
            assert np.allclose(np.dot(x**ii, w), true_moments[ii])

        degree = 4
        rho = 0.
        ab = hermite_recurrence(
            degree+1, rho, probability=True)
        x, w = gauss_quadrature(ab, degree+1)
        from scipy.special import factorial2
        assert np.allclose(np.dot(x**degree, w), factorial2(degree-1))

        x_sp, w_sp = sp.roots_hermitenorm(degree+1)
        w_sp /= np.sqrt(2*np.pi)
        assert np.allclose(x_sp, x)
        assert np.allclose(w_sp, w)
//  [python]
def test_factorialk_scalar_corner_cases(self, n, k, exact):
        if not exact:
            with pytest.raises(NotImplementedError):
                special.factorialk(n, k=k, exact=exact)
        elif n is None or n is np.nan or np.issubdtype(type(n), np.integer):
            # no error
            result = s.factorial2(n, exact=exact)
            nan_cond = n is np.nan or n is None
            expected = np.nan if nan_cond else special.factorialk(n, k=k)
            assert_equal(result, expected)
        else:
            with pytest.raises(ValueError, match="factorialk does not*"):
                special.factorialk(n, k=k, exact=exact)
//  [python]
def test_factorialk_scalar_corner_cases(self, n, k, exact):
        if not exact:
            with pytest.raises(NotImplementedError):
                special.factorialk(n, k=k, exact=exact)
        elif n is None or n is np.nan or np.issubdtype(type(n), np.integer):
            # no error
            result = special.factorial2(n, exact=exact)
            nan_cond = n is np.nan or n is None
            expected = np.nan if nan_cond else special.factorialk(n, k=k)
            assert_equal(result, expected)
        else:
            with pytest.raises(ValueError, match="factorialk does not*"):
                special.factorialk(n, k=k, exact=exact)
//  [python]
def test_factorial2_array_corner_cases(self, content, dim, exact, dtype):
        if dtype == np.int64 and any(np.isnan(x) for x in content):
            pytest.skip("impossible combination")
        n = np.array(content, ndmin=dim, dtype=dtype)
        if np.issubdtype(n.dtype, np.integer) or (not content):
            # no error
            result = s.factorial2(n, exact=exact)
            # expected result is identical to n for exact=True resp. empty
            # arrays (assert_allclose chokes on object), otherwise up to tol
            func = assert_equal if exact or (not content) else assert_allclose
            func(result, n)
        else:
            with pytest.raises(ValueError, match="factorial2 does not*"):
                s.factorial2(n, 3)
//  [python]
def test_factorial2_array_corner_cases(self, content, dim, exact, dtype):
        if dtype == np.int64 and any(np.isnan(x) for x in content):
            pytest.skip("impossible combination")
        n = np.array(content, ndmin=dim, dtype=dtype)
        if np.issubdtype(n.dtype, np.integer) or (not content):
            # no error
            result = special.factorial2(n, exact=exact)
            # expected result is identical to n for exact=True resp. empty
            # arrays (assert_allclose chokes on object), otherwise up to tol
            func = assert_equal if exact or (not content) else assert_allclose
            func(result, n)
        else:
            with pytest.raises(ValueError, match="factorial2 does not*"):
                special.factorial2(n, 3)
//  [python]
def asslegendre_torch(m, l, x):
    if m < 0:
        m = np.abs(m)
        return (-1)**m*np.math.factorial(l-m)/np.math.factorial(l+m)*asslegendre_torch(m, l, x)
    if m == l:
        return (-1)**l*scipy.special.factorial2(2*l-1)*torch.pow(1.0-x**2, torch.tensor([l]).to(x.device)/2.0)
    elif m > l:
        return torch.zeros_like(x)
    else:
        output = (2*l-1)*x*asslegendre_torch(m, l-1, x) 
        output = output - (l+m-1)*asslegendre_torch(m, l-2, x)
        output = output/(l-m)
        return output
//  [python]
def asslegendre_torch(m, l, x):
    if m < 0:
        m = np.abs(m)
        return (-1)**m*np.math.factorial(l-m)/np.math.factorial(l+m)*asslegendre_torch(m, l, x)
    if m == l:
        return (-1)**l*factorial2(2*l-1)*torch.pow(1.0-x**2, torch.tensor([l]).to(x.device)/2.0)
    elif m > l:
        return torch.zeros_like(x)
    else:
        output = (2*l-1)*x*asslegendre_torch(m, l-1, x) 
        output = output - (l+m-1)*asslegendre_torch(m, l-2, x)
        output = output/(l-m)
        return output
//  [python]
def factorial2(n):
    """Wrap scipy.special.factorial2 to return 1.0 when the input is not positive.

    This is a temporary workaround to address issue #129, while we wait for Scipy's update.
    To learn more, see https://github.com/scipy/scipy/issues/18409.

    Parameters
    ----------
    n : int or np.ndarray
        Values to calculate n!! for. If n <= 0, the return value is 1.
    """
    # Scipy  1.11.x returns an integer when n is an integer, but 1.10.x returns an array,
    # so np.array(n) is passed to make sure the output is always an array.
    out = scipy.special.factorial2(np.array(n))
    out[out <= 0] = 1.0
    return out
//  [python]
def lpKernel(N, freq):
#
# Lowpass filter kernel generator
#
# N is the filter half-size, must be odd
# freq is the normalised cutoff frequency
#
# Returns the filter kernel of size (2N+1,2N+1)
#
    num = 2*N + 1
    result = np.zeros((num,num,1))
    for m in range(N+1):
        i = m+N
        im = -m+N
        for n in range(N+1):
            j = n+N
            jm = -n+N
            if (m==0 and n==0):
                result[i,j] = np.pi*freq**2/4
            else:
                pandq = m%2 + n%2
                mandn = np.sqrt(m*m+n*n)
                val = (s.factorial2(N)**4 * np.pi**pandq * freq * s.jn(1,np.pi * freq * mandn)) / ( 2**(pandq+1) * s.factorial2(N+m) * s.factorial2(N-m) * s.factorial2(N+n) *s.factorial2(N-n) *mandn )
                result[i,j] = val
                result[j,i] = val
                result[im,j] = val
                result[i,jm] = val
                result[im,jm] = val

    return result/np.sum(result)
//  [python]
def lpKernel(N, freq):
#
# Lowpass filter kernel generator
#
# N is the filter half-size, must be odd
# freq is the normalised cutoff frequency
#
# Returns the filter kernel of size (2N+1,2N+1)
#
    num = 2*N + 1
    result = np.zeros((num,num,1))
    for m in range(N+1):
        i = m+N
        im = -m+N
        for n in range(N+1):
            j = n+N
            jm = -n+N
            if (m==0 and n==0):
                result[i,j] = np.pi*freq**2/4
            else:
                pandq = m%2 + n%2
                mandn = np.sqrt(m*m+n*n)
                val = (ss.factorial2(N)**4 * np.pi**pandq * freq * ss.jn(1,np.pi * freq * mandn)) / ( 2**(pandq+1) * ss.factorial2(N+m) * ss.factorial2(N-m) * ss.factorial2(N+n) *ss.factorial2(N-n) *mandn )
                result[i,j] = val
                result[j,i] = val
                result[im,j] = val
                result[i,jm] = val
                result[im,jm] = val

    return result/np.sum(result)
//  [python]
def get_sn(L0):
    """
    Compute s_n vector from eq (12) in [1].

    s_n = int_0^1 (P_n(x) dx)
    """
    dtype = np.longdouble
    # dtype = np.float
    # print('using double precision and exact factorial2')
    fact_type = np.int
    print('using exact factorial2')

    sn = np.arange(L0+1, dtype=dtype)
    sign = -1.0 * np.ones(sn[3::2].shape[0]).astype(dtype)
    sign[1::2] = 1.0
    # factorials
    nf = np.array([scipy.special.factorial2(f, exact=True)
                   for f in sn[3::2].astype(fact_type)]).astype(dtype)
    nfm1 = np.array([scipy.special.factorial2(f, exact=True)
                     for f in sn[2:-1:2].astype(fact_type)]).astype(dtype)
    # odd numbers
    sn[3::2] = sign * nf / (sn[3::2]*(sn[3::2]+1)*nfm1)
    # even numbers
    sn[2::2] = 0.0
    # n==0 and n==1
    sn[0] = 1.0
    sn[1] = 0.5

    return sn.astype(float)
//  [python]
def numeric_integation(func, n_samples=10 ** 5, bound_lower=-10**3, bound_upper=10**3):
  """ Numeric integration over one dimension using the trapezoidal rule

     Args:
       func: function to integrate over - must take numpy arrays of shape (n_samples,) as first argument
             and return a numpy array of shape (n_samples,)
       n_samples: (int) number of samples

     Returns:
       approximated integral - numpy array of shape (ndim_out,)
    """
  # proposal distribution
  y_samples = np.squeeze(np.linspace(bound_lower, bound_upper, num=n_samples))
  values = func(y_samples)
  integral = integrate.trapz(values, y_samples)
  return integral
//  [python]
def numeric_integation(func, n_samples=10 ** 5, bound_lower=-10**3, bound_upper=10**3):
  """ Numeric integration over one dimension using the trapezoidal rule

     Args:
       func: function to integrate over - must take numpy arrays of shape (n_samples,) as first argument
             and return a numpy array of shape (n_samples,)
       n_samples: (int) number of samples

     Returns:
       approximated integral - numpy array of shape (ndim_out,)
    """
  # proposal distribution
  y_samples = np.squeeze(np.linspace(bound_lower, bound_upper, num=n_samples))
  values = func(y_samples)
  integral = integrate.trapezoid(values, y_samples)
  return integral
//  [python]
def factorial2(n, exact=False):
    """Wrap scipy.special.factorial2 to return 1.0 when the input is -1.

    This is a temporary workaround while we wait for Scipy's update.
    To learn more, see https://github.com/scipy/scipy/issues/18409.

    Parameters
    ----------
    n : int or np.ndarray
        Values to calculate n!! for. If n={0, -1}, the return value is 1.
        For n < -1, the return value is 0.
    """
    # Scipy  1.11.x returns an integer when n is an integer, but 1.10.x returns an array,
    # so np.array(n) is passed to make sure the output is always an array.
    out = s.factorial2(np.array(n), exact=exact)
    out[out <= 0] = 1.0
    out[out <= -2] = 0.0
    return out
//  [python]
def factorial2(n, exact=False):
    """Wrap scipy.special.factorial2 to return 1.0 when the input is -1.

    This is a temporary workaround while we wait for Scipy's update.
    To learn more, see https://github.com/scipy/scipy/issues/18409.

    Parameters
    ----------
    n : int or np.ndarray
        Values to calculate n!! for. If n={0, -1}, the return value is 1.
        For n < -1, the return value is 0.
    """
    # Scipy  1.11.x returns an integer when n is an integer, but 1.10.x returns an array,
    # so np.array(n) is passed to make sure the output is always an array.
    out = scipy.special.factorial2(np.array(n), exact=exact)
    out[out <= 0] = 1.0
    out[out <= -2] = 0.0
    return out
//  [python]
def estimate_g_cutoff(precision: float, coeffs: torch.Tensor, alphas: torch.Tensor) -> float:
    # g-point cut off estimation based on cubic lattice
    # based on _estimate_ke_cutoff from pyscf
    # https://github.com/pyscf/pyscf/blob/c9aa2be600d75a97410c3203abf35046af8ca615/pyscf/pbc/gto/cell.py#L498

    langmom = 1
    log_k0 = 3 + torch.log(alphas) / 2
    l2fac2 = scipy.special.factorial2(langmom * 2 + 1)
    a = precision * l2fac2 ** 2 * (4 * alphas) ** (langmom * 2 + 1) / (128 * np.pi ** 4 * coeffs ** 4)
    log_rest = torch.log(a)
    Ecut = 2 * alphas * (log_k0 * (4 * langmom + 3) - log_rest)
    Ecut[Ecut <= 0] = .5
    log_k0 = .5 * torch.log(Ecut * 2)
    Ecut = 2 * alphas * (log_k0 * (4 * langmom + 3) - log_rest)
    Ecut[Ecut <= 0] = .5
    Ecut_max = float(torch.max(Ecut).detach())

    # KE ~ 1/2 * g^2
    gcut = (2 * Ecut_max) ** 0.5
    return gcut