def principal_volume(g_or_stratum, n=None): ''' Return the Masur-Veech volume of the principal stratum. By convention an empty stratum has volume 0. Input can be either a single argument that is a list of orders of zeroes or two arguments g and n, where g is the genus and n is the number of simple poles. ''' if g_or_stratum in ZZ: assert n != None, "Input Error: the input must be either g,n or stratum" g = ZZ(g_or_stratum) stratum = [1] * (4 * g - 4 + n) + [-1] * n elif type(g_or_stratum) in {list, tuple}: assert n == None, "Input Error: the input must be either g,n or stratum" stratum = g_or_stratum n = stratum.count(-1) g = (sum(stratum) + 4) / ZZ(4) else: raise ValueError( "Input Error: the input must be either g,n or stratum") if sum(stratum) < -4 or sum(stratum) % 4 != 0 or n < 0 or (g, n) in { (0, 0), (0, 1), (0, 2), (0, 3), (1, 0) }: return 0 memo = {} if g == 0: volume = 2 * pi**(2 * n - 6) / ZZ(2**(n - 4)) else: volume = ZZ(2**(2 * g + 1)) * pi**(6 * g - 6 + 2 * n) * factorial( 4 * g - 4 + n) / factorial(6 * g - 7 + 2 * n) * sum( dfactorial(5 * g - 7 + 2 * n - k) / dfactorial(5 * g - 3 - k) * CKazarian(g, k, memo) for k in range(g + 1)) return volume
def sequence(el, n, list=False, x_required=True): R = el.parent() if (R is SR): variables = [str(v) for v in el.variables()] if ('x' in variables): variable = el.variables()[variables.index('x')] elif (not x_required): variable = el.variables()[0] else: if (n > 0): return 0 else: return el if (list): return [ el.derivative(variable, i)(**{ str(variable): 0 }) / factorial(i) for i in range(n) ] else: return el.derivative(variable, n)(**{ str(variable): 0 }) / factorial(n) if (not isinstance(R, Ring_w_Sequence)): R = Wrap_w_Sequence_Ring(R) return R.sequence(el, n)
def sequence(self, el, n, list=False): if (list): return [self.sequence(el, i) for i in range(n)] self_gen = 'x' try: self_gen = self.base().gens()[-1] except: pass if (self_gen == 1): if (n > 0): return 0 elif (n == 0): return el else: raise ValueError( "Impossible get negative element of the sequence") if (el in self): res = el for _ in range(n): try: res = derivative(res, self_gen) except AttributeError: ## Not derivative possible. Considering a constant if (n == 0): return res return 0 try: return res(**{repr(self_gen): 0}) / factorial(n) except TypeError: ## Not callable element. Returning the element without evaluation return res / factorial(n) else: raise TypeError("Element not in `self` to compute the sequence.")
def test_kontsevich_genus0_formula(): from surface_dynamics.topological_recursion.kontsevich import psi_correlator # d1 + ... + dn = n-3 # equivalently (d1+1) + ... + (dn+1) = 2n-3 # <tau_{d1} ... tau_{dn}> = (n-3)! / prod d_i! for n in range(3, 10): for p in Partitions(2 * n - 3, length=n): p = tuple(i - 1 for i in p) val = QQ((factorial(n - 3), prod(factorial(d) for d in p))) assert psi_correlator(*p) == val, p
def khovanskii_characteristic(self): """ Given k polytopes P[0], ..., P[k-1] in n-space, compute the sum (-1)^(n+k) * n! * MV(P[0], ..., P[0], P[1], ..., P[1], ...) over all compositions of n. This number is an integer which is equal to the Euler characteristic of the subvariety of TT^n defined by sufficiently non-degenerate (Laurent) polynomials with Newton polytopes P[0], ..., P[k-1]. """ if not self.polynomials: return 1 if self.torus_dim == 0 else 0 elif any(f.is_constant() and f != 0 for f in self.polynomials): return 0 P = [f.newton_polytope() for f in self.polynomials] k = len(P) n = P[0].ambient_dim() if any(q.ambient_dim() != n for q in P): raise TypeError( 'All polytopes must have the same ambient dimension') if k > n: return 0 return ((-1)**(n + k) * factorial(n) * sum( mixed_volume(chain.from_iterable([q] * a for (q, a) in zip(P, c))) for c in MyCompositions(n, length=k)))
def smale_gamma(df, xip1, yij): """Smale gamma function. Parameters ---------- df : MultivariatePolynomial A list of all of the y-derivatives of f, including f itself. xip1 : complex The x-point to analytically continue to. yij : complex A y-root at xi. The root that we'll analytically continue. Returns ------- double The Smale gamma function. """ df0 = df[0] df1 = df[1] deg = len(df) - 1 df1y = df1(xip1, yij) gamma = numpy.double(0) for n in range(2, deg + 1): dfn = df[n] gamman = numpy.abs(dfn(xip1, yij) / (factorial(n) * df1y)) gamman = gamman**(1.0 / (n - 1.0)) if gamman > gamma: gamma = gamman return gamma
def main2(): t, n = var('t, n') G = symbolic_expression(t**n * (t - Integer(1))**n / factorial(n) * e**t).function(t) T = symbolic_expression(G.integral(t, Integer(0), Integer(1))).function(n) print([T(j) for j in range(Integer(10))]) print([T(j).n() for j in range(Integer(10))])
def smale_gamma(df, xip1, yij): """Smale gamma function. Parameters ---------- df : MultivariatePolynomial A list of all of the y-derivatives of f, including f itself. xip1 : complex The x-point to analytically continue to. yij : complex A y-root at xi. The root that we'll analytically continue. Returns ------- double The Smale gamma function. """ df0 = df[0] df1 = df[1] deg = len(df) - 1 df1y = df1(xip1,yij) gamma = numpy.double(0) for n in range(2,deg+1): dfn = df[n] gamman = numpy.abs(dfn(xip1,yij) / (factorial(n)*df1y)) gamman = gamman**(1.0/(n-1.0)) if gamman > gamma: gamma = gamman return gamma
def ogf_egf(f): ''' Method that receives a sequence in the form of a black-box function and return a sequence h(n) such that egf(h) = ogf(f). Then h(n) = f(n)*factorial(n) ''' return lambda n: f(n)*factorial(n)
def test_kontsevich_witten_formula(): from surface_dynamics.topological_recursion.kontsevich import psi_correlator # <tau_{3g-2}>_{g,1} = 1 / (24^g g!) from surface_dynamics.topological_recursion import KontsevichTR K = KontsevichTR() for g in range(1, 10): assert 24**g * factorial(g) * psi_correlator(3 * g - 2) == 1, g
def c_f(dim_or_stratum): ''' Return the renormalization coefficient for stratum: 2^(d+1)/(d-1)! where d is dimension. Input can be a dimension or a stratum itself. ''' if type(dim_or_stratum) in {list, tuple}: d = c_d(dim_or_stratum) else: d = dim_or_stratum return ZZ(2**(d + 1)) / factorial(d - 1)
def lambda_helper(phi, NN, p=pp): """ Helper function for affine and projective factorization probabilities. """ d = deg_fp(phi) return prod([ prod([NN(j, p) - i for i in range(m1(phi, j))]) // prod([factorial(m2(phi, [j, i])) for i in range(1, d + 1)]) for j in range(1, d + 1) ])
def cvolume_by_graphs(stratum, graphs): ''' Return the contribution of given stable graphs to the completed volume of the stratum. INPUT: - ``stratum`` -- list or tuple, orders of zeros of the stratum - ``graphs`` -- iterable, e.g. a set, of Labeled Stable Graphs ''' f = c_f(stratum) mu = prod(factorial(stratum.count(i)) for i in range(-1, max(stratum) + 1)) return f * mu * sum(operator(graph_poly(stg) for stg in graphs))
def monom(par): ''' Given a partition ``par`` = :math:`\\left[0^{i_0},1^{i_1},\\ldots,n^{i_n}\\right]` return a monomial (in Multivariate Polynomial Ring over Q) :math:`{t_0}^{i_0}\\cdot\\ldots\\cdot{t_n}^{i_n} \\big/ {i_0}!\\cdot\\ldots\\cdot{i_n}!`. EXAMPLE: Here we generate all monomials of weight 5:: sage: from cvolume.series import monom sage: [monom(l) for l in Partitions(5)] [t4, t0*t3, t1*t2, 1/2*t0^2*t2, 1/2*t0*t1^2, 1/6*t0^3*t1, 1/120*t0^5] ''' exp = par.to_exp() return prod(ZZ(1)/factorial(k) for k in exp)*prod([T_vars[i-1] for i in par])
def L_operator(k, m, _A, _D, r_ls, pol, us, d_up_down_mlt): ''' Return (k)_m * Fourier coefficient of L_tilde^{k, m}(pol exp(2pi block_matrix([[A, R/2], [R^t/2, D]])Z))/ exp(-2pi block_matrix([[A, R/2], [R^t/2, D]])Z). as an element of _Z_ring or _Z_U_ring. ''' if m == 0: return pol zero = _Z_U_ring(0) res = zero u1, u2, u3, u4 = us for n in range(m // 2 + 1): pol_tmp = _Z_U_ring(pol) for _ in range(m - 2 * n): pol_tmp = _D_D_up_D_down(u1, u2, u3, u4, r_ls, pol_tmp) for _ in range(n): pol_tmp *= d_up_down_mlt pol_tmp *= QQ(factorial(n) * factorial(m - 2 * n) * mul(2 - k - m + i for i in range(n))) ** (-1) res += pol_tmp return res
def Aut(self): ''' Return the order of the group of automorphisms of this Labeled Stable Graph. EXAMPLES: Here we compute the order of automorphism group of a single vertex graph with two loops:: sage: from cvolume import LabeledStableGraph sage: edges, loops, kappa = [], [2], [[3, 3, 1, -1]] sage: stg = LabeledStableGraph(edges,loops,kappa) sage: stg.Aut() 8 Here we compute the order of automorphism group of a two vertex graph with no loops, but a symmetry due to isomorphic vertices:: sage: edges, loops, kappa = [(0, 1, 1)], [0, 0], [[3, -1],[3, -1]] sage: stg = LabeledStableGraph(edges,loops,kappa) sage: stg.Aut() 2 Here we compute the order of automorphism group of a two vertex graph with no loops, but five edges between non-isomorphic vertices:: sage: edges, loops, kappa = [(0, 1, 5)], [0, 0], [[5, 1],[7, -1]] sage: stg = LabeledStableGraph(edges,loops,kappa) sage: stg.Aut() 120 ''' partition = k_to_p(self.edges, self.loops, self.kappa, self.graph) graph_aut = self.graph.automorphism_group(partition=partition, edge_labels=True, order=True, return_group=False) loops_aut = prod(2**i * factorial(i) for i in self.loops) weights_aut = prod(factorial(e[2]) for e in self.edges) return graph_aut * loops_aut * weights_aut
def test_kontsevich_genus1_formula(): # from Andersen-Borot-Charbonnier-Delecroix-Giacchetto-Lewanski-Wheeler # appendix A from surface_dynamics.topological_recursion.kontsevich import psi_correlator # d1 + ... + dn = n # equivalently (d1+1) + ... + (dn+1) = 2n for n in range(1, 8): for p in Partitions(2 * n, length=n): p = tuple(i - 1 for i in p) s = sum( multinomial([i - j for i, j in zip(p, diff)]) * factorial(sum(diff) - 2) for diff in itertools.product([0, 1], repeat=n) if sum(diff) >= 2) assert 24 * psi_correlator(*p) == multinomial(p) - s, (p, s)
def B0plus_old(self, m, n, k=2, N=10): if n == 0: summa = self._CF(0) for c in range(1, N): cn = c * self._N #term=self._CF(self.K(m,n,cn))/self._RF(cn)**2 term = self.K( m, n, cn) # Ktriv(self._N,m,n,cn,self._prec)/self._RF(cn)**2 #print "K,m,n(",cn,")=",term term = self._CF(term) / self._RF(cn)**k #print "term(",cn,")=",term summa = summa + term f = self._RF(2**k) * self._RF.pi()**k f = f * self._RF(m)**(k - 1) f = f * self._CF(0, 1)**(self._RF(k + 2)) f = f / self._RF(factorial(k - 1)) return f * summa
def _mixed_volume_naive(gen): """ Naive computation of the normalised mixed volume using Cox et al., 'Using Algebraic Geometry', Thm 7.4.12. """ P = list(gen) n = len(P) if any(q.ambient_dim() != n for q in P): raise TypeError( 'Number of polytopes and ambient dimension do not match') res = 0 for I in Subsets(range(n)): if not I: continue res += (-1)**len(I) * (sum(P[i] for i in I)).volume() return (-1)**n / Integer(factorial(n)) * res
def X_to_kappa(f,kappa): """ f -- A polynomial in X. The constant term is ignored. kappa -- A function so that kappa(a) is kappa_a. Returns:: A polynomial in kappas, converting ... """ psi_list = [] for expon,coef in f.dict().items(): #print expon[0], coef if expon[0] !=0: psi_list += [expon[0]]*coef #print psi_list result = 0 for p in SetPartitions(range(len(psi_list))): #print p #print [ kappa( sum((psi_list[i] for i in s)) ) for s in p] result += prod((factorial(len(s) - 1) for s in p)) * prod(( kappa( sum((psi_list[i] for i in s)) ) for s in p )) return result
def X_to_kappa(f, kappa): """ f -- A polynomial in X. The constant term is ignored. kappa -- A function so that kappa(a) is kappa_a. Returns:: A polynomial in kappas, converting ... """ psi_list = [] for expon, coef in f.dict().items(): #print expon[0], coef if expon[0] != 0: psi_list += [expon[0]] * coef #print psi_list result = 0 for p in SetPartitions(list(range(len(psi_list)))): #print p #print [ kappa( sum((psi_list[i] for i in s)) ) for s in p] result += prod((factorial(len(s) - 1) for s in p)) * prod( (kappa(sum((psi_list[i] for i in s))) for s in p)) return result
def basis_integrals(self, s=None): """ Return a list of numbers corresponding to the integrals of the basis elements in the top codimension. This is computed via the FZ relations, so it is probably not fast. However, it is a nice check. The value is cached, so you only have to compute it once per session. This is used by :meth:`~strataalgebra.StrataAlgebraElement.integrate` which is the more likely way you will want to use it. :: sage: from strataalgebra import * sage: s = StrataAlgebra(QQ,1,(1,)) sage: s.print_strata(1) **** i: 0 D_irr <BLANKLINE> **** i: 1 ka1 <BLANKLINE> **** i: 2 ps1 <BLANKLINE> sage: s.basis_integrals() [1, 1/24, 1/24] """ int_kappa = Rational((1, 24**self.g * factorial(self.g))) G = StrataGraph(self.strataP.open_stratum().M) G.M[1, 0] += StrataGraph.Xvar**self.moduli_dim kappa_index = self.strataP.index_from_graph_codim( G, self.moduli_dim) #._dstrata[self.moduli_dim][G] M = self.FZ_matrix(self.moduli_dim) ker = M.right_kernel() if ker.dimension() != 1: raise Exception("Something unexpected here!") return list(int_kappa / ker.gen(0)[kappa_index] * ker.gen(0))
def _mixed_volume_gfan(gen): """ Use gfan to compute the mixed volume of a collection of polytopes. Just like Khovanskii, we use the normalised mixed volume which satisfies mixed_volume([P,...,P]) = volume(P). """ P = list(gen) n = len(P) if any(Q.ambient_dim() != n for Q in P): raise TypeError( 'Number of polytopes and ambient dimension do not match') if n == 0: return 0 elif n == 1: return P[0].volume() R = PolynomialRing(QQ, 'x', n) I = R.ideal([sum(monomial_exp(R, e) for e in Q.vertices()) for Q in P]) return ZZ.one() / Integer(factorial(n)) * I.groebner_fan().mixed_volume()
def basis_integrals(self, s=None): """ Return a list of numbers corresponding to the integrals of the basis elements in the top codimension. This is computed via the FZ relations, so it is probably not fast. However, it is a nice check. The value is cached, so you only have to compute it once per session. This is used by :meth:`~strataalgebra.StrataAlgebraElement.integrate` which is the more likely way you will want to use it. :: sage: from strataalgebra import * sage: s = StrataAlgebra(QQ,1,(1,)) sage: s.print_strata(1) **** i: 0 D_irr <BLANKLINE> **** i: 1 ka1 <BLANKLINE> **** i: 2 ps1 <BLANKLINE> sage: s.basis_integrals() [1, 1/24, 1/24] """ int_kappa = Rational((1, 24**self.g * factorial(self.g))) G = StrataGraph(self.strataP.open_stratum().M) G.M[1, 0] += StrataGraph.Xvar ** self.moduli_dim kappa_index = self.strataP.index_from_graph_codim(G,self.moduli_dim) #._dstrata[self.moduli_dim][G] M = self.FZ_matrix(self.moduli_dim) ker = M.right_kernel() if ker.dimension() != 1: raise Exception("Something unexpected here!") return list( int_kappa / ker.gen(0)[kappa_index] * ker.gen(0))
def kappa_coeff(sigma,kappa_0,F): #maybe not optimal to compute the target_partition twice here... #global kappa_coeff_dict mmm = F.degree() target_partition = [] for i in range(1,mmm+1): for j in range(F[i]): target_partition.append(i) #key = (tuple(sigma),kappa_0,tuple(target_partition)) #if kappa_coeff_dict.has_key(key): # return kappa_coeff_dict[key] total = 0 num_ones = sum(1 for i in sigma if i == 1) for i in range(0,num_ones+1): for injection in Permutations(list(range(len(target_partition))),len(sigma)-i): term = binomial(num_ones,i)*binomial(kappa_0 + len(target_partition) + i-1, i)*factorial(i) for j in range(len(sigma)-i): term *= C_coeff(sigma[j+i],target_partition[injection[j]]) for j in range(len(target_partition)): if j in injection: continue term *= C_coeff(0,target_partition[j]) total += term return (-1)**(len(target_partition)+len(sigma))*total * Rational((1,aut(target_partition)))
def combination(n, m): return factorial(n) / (factorial(m) * factorial(n - m))
def completed_volume(stratum, verbose=False, one_vertex=False, with_pi=True, cache=None): ''' Return the completed volume of the stratum. INPUT: - ``stratum`` -- list or tuple, orders of zeros of the stratum - ``verbose`` -- boolean (default `False`), when True prints progress of the computation: time to generate and the number of stable graphs in each codimension and in total; progress of computing contribution of stable graphs - ``one_vertex`` -- boolean (default `False`), when True only computes contribution of one-vertex labeled stable graphs. - ``with_pi`` -- boolean (default `True`), when False returns completed volume as a rational number (volume divided by an appropriate degree of pi - ``cache`` -- dict (default `None`), a cache to store computed local polynomials EXAMPLES: Here we compute completed volume of an empty stratum :math:`\\mathcal{Q}(3,1)`:: sage: from cvolume import completed_volume sage: completed_volume([3, 1]) 23/90*pi^4 Here we demonstrate the verbose mode by computing completed volume of stratum :math:`\\mathcal{Q}(1,-1)`:: sage: completed_volume([3, 1, 1, -1], verbose = True) Computing completed volume of stratum [3,1^2,-1]... Generated 2 codimension 1 graphs in ... s Generated 4 codimension 2 graphs in ... s Generated 3 codimension 3 graphs in ... s The total number of stable graphs for stratum [3,1^2,-1] is: 9. Generated in: ... s Computed contribution of 9/9 graphs. Time elapsed: ... s. ETA: ... s Completed volume of [3,1^2,-1] is: 7/60*pi^6. Computed in: ... s 7/60*pi^6 Here we compute one-vertex graphs contribution to the completed volume of :math:`\\mathcal{Q}(3,1,1,-1)`:: sage: completed_volume([3, 1, 1, -1], one_vertex=True) 1346/14175*pi^6 Here are some examples for principal strata, where completed volume coincides with Masur-Veech volume:: sage: assert completed_volume([1, -1, -1, -1, -1, -1]) == 1*pi^4 sage: assert completed_volume([1, 1, -1, -1]) == 1/3*pi^4 sage: assert completed_volume([1, -1]) == 2/3*pi^2 sage: assert completed_volume([-1, -1, -1, -1]) == 2*pi^2 ''' if cache is None: cache = {} stratum = tuple(sorted(stratum, reverse=True)) if stratum in cvolumes_dict and one_vertex == False: cvolume = cvolumes_dict[stratum] if with_pi: return cvolume else: return cvolume.coefficient(pi**cvolume.degree(pi)) def max_weight(stratum): return sum(stratum) / ZZ(2) + len(stratum) / ZZ(2) + stratum.count( -1) + 1 higher_part = [i for i in stratum if i > 1] lower_part = [abs(i) for i in stratum if i not in higher_part] for new_higher_part in reversed(Combinations(higher_part).list()): rest_is_odd = (sum(higher_part) - sum(new_higher_part)) % 2 w = max_weight(new_higher_part + lower_part) - rest_is_odd s_part = tuple(sorted((i + 1) / ZZ(2) for i in new_higher_part)) _ = Fs(w, s_part) d = c_d(stratum) f = c_f(d) mu = prod( [factorial(stratum.count(i)) for i in range(-1, max(stratum) + 1)]) if verbose: tic_total = time.time() print( f"Computing completed volume of stratum {stratum2print(stratum)}..." ) tic = time.time() stgs = stable_lab_graphs(stratum, one_vertex=one_vertex, verbose=verbose) total_num = len(stgs) vol, count, period = 0, 0, 10 tic = time.time() for stg in stgs: vol += operator(graph_poly(stg, cache=cache)) count += 1 if verbose and (count % period == 0 or count == total_num): toc = time.time() print( f"\rComputed contribution of {count}/{total_num} graphs. Time elapsed: {float2time(toc-tic,3)}. ETA: {float2time((toc-tic)/count*(total_num-count),3)} ", end="") if verbose: toc_total = time.time() print( f"\nCompleted volume of {stratum2print(stratum)} is: {f*mu*vol*pi**d}. Computed in: {float2time(toc_total-tic_total,3)}" ) cvolume, cvolume_pi = f * mu * vol, f * mu * vol * pi**d if one_vertex == False: cvolumes_dict[stratum] = cvolume_pi if with_pi: return cvolume_pi else: return cvolume
def replmon(n): ''' Return the result of application of :math:`\\mathcal{Z}`-operator to the monomial :math:`b_i^n`. ''' if n == 0: return 1 else: return factorial(n) * Rational(zeta(n + 1) / pi**(n + 1))
def memo_Nlocal(g, n, stratum, labeled, mode): #print('Computing for (%s,%s,%s)...Cache size %s' % (g,n,stratum,len(cache_poly))) if not stratum or n != 2 - 2 * g + 1 / ZZ(2) * sum( stratum) or g < 0 or n < 1: return B.zero() if type(stratum) == list: stratum = tuple(stratum) if (g, n, stratum) in cache_poly: if labeled: return cache_poly[(g, n, stratum)] else: return cache_poly[(g, n, stratum)] / cache_labeling[(g, n, stratum)] if not labeled: memo_Nlocal(g, n, stratum, True, mode) return cache_poly[(g, n, stratum)] / cache_labeling[(g, n, stratum)] #weight_check(g,n,stratum) m = [ stratum.count(2 * i - 1) for i in range((max(stratum) + 1) / ZZ(2) + 1) ] if -1 in stratum: # case with poles elderstratum = list(stratum) elderstratum.remove(-1) elderstratum.append(1) N_lab = memo_Nlocal(g, n + 1, elderstratum, True, mode)(*vanish(b, [n + 1])) for i in range(len(elderstratum)): if elderstratum[i] > 1: newstratum = list(elderstratum) newstratum[i] = newstratum[i] - 2 N_lab -= elderstratum[i] * memo_Nlocal( g, n, newstratum, True, mode) N_lab *= ZZ(1) / elderstratum.count(1) cache_poly[(g, n, stratum)] = N_lab cache_labeling[(g, n, stratum)] = ZZ( prod( factorial(stratum.count(i)) for i in range(-1, max(stratum) + 1))) return cache_poly[(g, n, stratum)] elif mode == 'derivative' or len(m) == 2: # case without poles _Fs = stratum_to_F(g, n, stratum) deg = ZZ(3 * g - 3 + n - 1 / 2 * sum(d - 1 for d in stratum)) M = sum(m[i] * (i - 1) for i in range(len(m))) mondeg = Partitions(deg + n, length=n) const = 2**(5 * g - 6 + 2 * n - 2 * M) labeling = prod( factorial(stratum.count(i)) for i in range(1, max(stratum) + 1)) N_lab = ZZ(1)/const*labeling*sum(prod(ZZ(1)/factorial(d-1) for d in par)*\ prod(factorial(i) for i in par.to_exp())*\ _Fs.monomial_coefficient(prod(T.gen(d-1) for d in par))*\ sum(prod(B.gen(j)**(2*(sympar[j]-1)) for j in range(n)) for sympar in Permutations(par))\ for par in mondeg) cache_poly[(g, n, stratum)] = N_lab cache_labeling[(g, n, stratum)] = ZZ( prod( factorial(stratum.count(i)) for i in range(-1, max(stratum) + 1))) return cache_poly[(g, n, stratum)] elif mode == 'recursive': assert m[2] == 1 and len( m ) == 3, "'recursive' mode is only available for stratum = [3,1,1,...,-1,-1]" first_term = 2**4 * diff( memo_Nlocal(g, n + 1, [1] * (m[1] + 5), False, mode), b[n], 4)(*vanish(b, [n + 1])) second_term = -ZZ(1) / 2 * 2 * memo_Nlocal( g - 1, n + 2, [1] * (m[1] + 3), False, mode)(*vanish(b, [n + 1, n + 2])) third_term = 0 for par in OrderedSetPartitions(b[:n], 2): n_1, n_2 = len(par[0]), len(par[1]) assert n_1 + n_2 == n third_term += -ZZ(1)/2*sum(memo_Nlocal(g_1,n_1+1,[1]*(4*g_1-4+2*(n_1+1)),False,mode)(*list(par[0])+[0]*(30-n_1))\ *memo_Nlocal(g-g_1,n_2+1,[1]*(4*(g-g_1)-4+2*(n_2+1)),False,mode)(*list(par[1])+[0]*(30-n_2))\ for g_1 in range(g+1)) N_unlab = first_term + second_term + third_term cache_labeling[(g, n, stratum)] = ZZ( prod( factorial(stratum.count(i)) for i in range(-1, max(stratum) + 1))) cache_poly[(g, n, stratum)] = N_unlab * cache_labeling[(g, n, stratum)] return cache_poly[(g, n, stratum)]
def genf_koszul_dual(P, Q, p): if p < 0: return ZZ_X_Y() f1 = genf_polygon(P, [1]*p) // factorial(p) f2 = genf_polygon_interior(Q) return f1 * f2
def genf_koszul_points(P, Q, p): if p < 0: return ZZ_X_Y() f1 = genf_points(P, [1]*p) // factorial(p) f2 = genf_points(Q) return f1 * f2
def multiset_perms(k_counts): "Simple multinomial coefficient using standard lib `math` functions" n_factorial = factorial(sum(k_counts)) k_count_factorial_prod = prod([factorial(x) for x in k_counts]) n_perms = n_factorial / k_count_factorial_prod return int(n_perms)
def proba(l, k): return exp(-l) * (l**k / factorial(k))
def test_kontsevich(): from surface_dynamics.topological_recursion import KontsevichTR K = KontsevichTR() # dim 0 assert K.F(0, 3, [0, 0, 0]) == 1 # dim 1 assert K.F(1, 1, [0]) == 0 assert K.F(1, 1, [1]) == QQ((1, 8)) assert K.F(0, 4, [1, 0, 0, 0]) == 3 # dim 2 assert K.F(0, 5, [2, 0, 0, 0, 0]) == 15 assert K.F(0, 5, [1, 1, 0, 0, 0]) == 18 assert K.F(1, 2, [2, 0]) == QQ((5, 8)) assert K.F(1, 2, [1, 1]) == QQ((3, 8)) # dim 3 assert K.F(2, 1, [4]) == QQ((105, 128)) # dim 4 assert K.F(2, 2, [5, 0]) == QQ((1155, 128)) assert K.F(2, 2, [4, 1]) == QQ((945, 128)) assert K.F(2, 2, [3, 2]) == QQ((1015, 128)) # genus zero assert K.F(0, 3, (0, 0, 0)) == QQ((1, 1)) assert K.F(0, 4, (1, 0, 0, 0)) == QQ((3, 1)) assert K.F(0, 5, (2, 0, 0, 0, 0)) == QQ((15, 1)) assert K.F(0, 5, (1, 1, 0, 0, 0)) == QQ((18, 1)) assert K.F(0, 6, (3, 0, 0, 0, 0, 0)) == QQ((105, 1)) assert K.F(0, 6, (2, 1, 0, 0, 0, 0)) == QQ((135, 1)) assert K.F(0, 6, (1, 1, 1, 0, 0, 0)) == QQ((162, 1)) assert K.F(0, 7, (4, 0, 0, 0, 0, 0, 0)) == QQ((945, 1)) assert K.F(0, 7, (3, 1, 0, 0, 0, 0, 0)) == QQ((1260, 1)) assert K.F(0, 7, (2, 2, 0, 0, 0, 0, 0)) == QQ((1350, 1)) assert K.F(0, 7, (2, 1, 1, 0, 0, 0, 0)) == QQ((1620, 1)) assert K.F(0, 7, (1, 1, 1, 1, 0, 0, 0)) == QQ((1944, 1)) # genus one assert K.F(1, 1, (1, )) == QQ((1, 8)) assert K.F(1, 2, (2, 0)) == QQ((5, 8)) assert K.F(1, 2, (1, 1)) == QQ((3, 8)) assert K.F(1, 3, (3, 0, 0)) == QQ((35, 8)) assert K.F(1, 3, (2, 1, 0)) == QQ((15, 4)) assert K.F(1, 3, (1, 1, 1)) == QQ((9, 4)) assert K.F(1, 4, (4, 0, 0, 0)) == QQ((315, 8)) assert K.F(1, 4, (3, 1, 0, 0)) == QQ((315, 8)) assert K.F(1, 4, (2, 2, 0, 0)) == QQ((75, 2)) assert K.F(1, 4, (2, 1, 1, 0)) == QQ((135, 4)) assert K.F(1, 4, (1, 1, 1, 1)) == QQ((81, 4)) # genus two assert K.F(2, 1, [4]) == QQ((105, 128)) assert K.F(2, 2, [5, 0]) == QQ((1155, 128)) assert K.F(2, 2, [4, 1]) == QQ((945, 128)) assert K.F(2, 2, [3, 2]) == QQ((1015, 128)) assert K.F(2, 3, [6, 0, 0]) == QQ((15015, 128)) assert K.F(2, 3, [5, 1, 0]) == QQ((3465, 32)) assert K.F(2, 3, [4, 2, 0]) == QQ((3465, 32)) assert K.F(2, 3, [3, 3, 0]) == QQ((7105, 64)) assert K.F(2, 3, [4, 1, 1]) == QQ((2835, 32)) assert K.F(2, 3, [3, 2, 1]) == QQ((3045, 32)) assert K.F(2, 4, [7, 0, 0, 0]) == QQ((225225, 128)) assert K.F(2, 4, [6, 1, 0, 0]) == QQ((225225, 128)) assert K.F(2, 4, [5, 2, 0, 0]) == QQ((3465, 2)) assert K.F(2, 4, [5, 1, 1, 0]) == QQ((51975, 32)) assert K.F(2, 4, [4, 3, 0, 0]) == QQ((112455, 64)) assert K.F(2, 4, [4, 2, 1, 0]) == QQ((51975, 32)) assert K.F(2, 4, [4, 1, 1, 1]) == QQ((42525, 32)) assert K.F(2, 4, [3, 3, 1, 0]) == QQ((106575, 64)) assert K.F(2, 4, [3, 2, 2, 0]) == QQ((13125, 8)) assert K.F(2, 4, [3, 2, 1, 1]) == QQ((45675, 32)) assert K.F(2, 4, [2, 2, 2, 1]) == QQ((23625, 16)) # genus three assert K.F(3, 1, [7]) == QQ((25025, 1024)) assert K.F(3, 2, [8, 0]) == QQ((425425, 1024)) assert K.F(3, 2, [7, 1]) == QQ((375375, 1024)) assert K.F(3, 2, [6, 2]) == QQ((385385, 1024)) assert K.F(3, 2, [5, 3]) == QQ((193655, 512)) assert K.F(3, 2, [4, 4]) == QQ((191205, 512)) assert K.F(3, 3, [9, 0, 0]) == QQ((8083075, 1024)) assert K.F(3, 3, [8, 1, 0]) == QQ((3828825, 512)) assert K.F(3, 3, [7, 2, 0]) == QQ((3828825, 512)) assert K.F(3, 3, [7, 1, 1]) == QQ((3378375, 512)) assert K.F(3, 3, [6, 3, 0]) == QQ((7732725, 1024)) assert K.F(3, 3, [6, 2, 1]) == QQ((3468465, 512)) assert K.F(3, 3, [5, 4, 0]) == QQ((1923075, 256)) assert K.F(3, 3, [5, 3, 1]) == QQ((1742895, 256)) assert K.F(3, 3, [5, 2, 2]) == QQ((883575, 128)) assert K.F(3, 3, [4, 4, 1]) == QQ((1720845, 256)) assert K.F(3, 3, [4, 3, 2]) == QQ((1765575, 256)) assert K.F(3, 3, [3, 3, 3]) == QQ((3570875, 512)) # Witten <tau_{3g-2}>_{g,1} = 1 / (24^g g!) assert all((24**g * factorial(g) * K.F(g, 1, [3 * g - 2]) == ZZ(6 * g - 3).multifactorial(2)) for g in range(1, 10))