r"""
Double ramification cycle
"""
from __future__ import absolute_import, print_function
import itertools
from six.moves import range
from admcycles.admcycles import Tautv_to_tautclass, Tautvb_to_tautclass, Tautvecttobasis, tautgens, psiclass, tautclass
from admcycles.stable_graph import StableGraph
import admcycles.DR as DR
from sage.combinat.subset import Subsets
from sage.arith.all import factorial
from sage.functions.other import floor, ceil
from sage.misc.misc_c import prod
from sage.rings.all import PolynomialRing, QQ, ZZ
from sage.modules.free_module_element import vector
from sage.rings.polynomial.polynomial_ring import polygen
from sage.rings.polynomial.multi_polynomial_element import MPolynomial
def DR_cycle_old(g, Avector, d=None, k=None, tautout=True, basis=False):
r"""Returns the k-twisted Double ramification cycle in genus g and codimension d
for the partition Avector of k*(2g-2+n).
In the notation of the [JPPZ]-paper, the output is 2^(-d) * P_g^{d,k}(Avector).
Note: This is based on the old implementation DR_compute by Pixton. A new one,
which can be faster in many examples is DR_cycle.
INPUT:
- ``tautout` -- bool (default: `True`); if False, returns a vector
(in all generators for basis=false, in the basis of the ring for basis=true)
- ``basis` -- bool (default: `False`); if True, use FZ relations to give out the
DR cycle in a basis of the tautological ring
"""
if d is None:
d = g
n = len(Avector)
if k is None:
k = floor(sum(Avector)/(2*g-2+n))
if sum(Avector) != k*(2*g-2+n):
raise ValueError('2g-2+n must divide the sum of entries of Avector.')
v = DR.DR_compute(g, d, n, Avector, k)
v1 = vector([QQ(a) for a in DR.convert_vector_to_monomial_basis(v, g, d, tuple(range(1, n+1)), DR.MODULI_ST)])
if basis:
v2 = Tautvecttobasis(v1, g, n, d)
if tautout:
return Tautvb_to_tautclass(v2, g, n, d)
return v2
else:
if tautout:
return Tautv_to_tautclass(v1, g, n, d)
return v1
def DR_cycle(g, Avector, d=None, k=None, rpoly=False, tautout=True, basis=False):
r"""Returns the k-twisted Double ramification cycle in genus g and codimension d
for the partition Avector of k*(2g-2+n). If some elements of Avector are elements of a
polynomial ring, compute DR-polynomial and substitute the entries of Avector - the result
then has coefficients in a polynomial ring.
In the notation of the [JPPZ]-paper, the output is 2^(-d) * P_g^{d,k}(Avector).
Note: This is a reimplementation of Pixton's DR_compute which can be faster in many examples.
To access the old version, use DR_cycle_old - it might be faster on older SageMath-versions.
INPUT:
- ``rpoly`` -- bool (default: `False`); if True, return tautclass 2^(-d) * P_g^{d,r,k}(Avector)
whose coefficients are polynomials in the variable r.
- ``tautout` -- bool (default: `True`); if False, returns a vector
(in all generators for basis=false, in the basis of the ring for basis=true)
- ``basis` -- bool (default: `False`); if True, use FZ relations to give out the
DR cycle in a basis of the tautological ring
EXAMPLES::
sage: from admcycles import DR_cycle, DR_cycle_old
sage: DR_cycle(1, (2, 3, -5))
Graph : [1] [[1, 2, 3]] []
Polynomial : 2*psi_1^1
<BLANKLINE>
Graph : [1] [[1, 2, 3]] []
Polynomial : 9/2*psi_2^1
<BLANKLINE>
Graph : [1] [[1, 2, 3]] []
Polynomial : 25/2*psi_3^1
<BLANKLINE>
Graph : [0, 1] [[2, 3, 5], [1, 6]] [(5, 6)]
Polynomial : (-2)*
<BLANKLINE>
Graph : [0, 1] [[1, 3, 5], [2, 6]] [(5, 6)]
Polynomial : (-9/2)*
<BLANKLINE>
Graph : [0, 1] [[1, 2, 5], [3, 6]] [(5, 6)]
Polynomial : (-25/2)*
<BLANKLINE>
Graph : [0] [[5, 6, 1, 2, 3]] [(5, 6)]
Polynomial : (-1/24)*
sage: D = DR_cycle(1, (1, 3, -4), d=2)
sage: D2 = DR_cycle_old(1, (1, 3, -4), d=2) # long time
sage: D.toTautvect() == D2.toTautvect() # long time
True
sage: D.is_zero() # Clader-Janda: P_g^{d,k}(Avector)=0 for d>g
True
sage: v = DR_cycle(2, (3,), d=4, rpoly=true).evaluate()
sage: v # Conjecture by Longting Wu predicts that r^1-term vanishes
-1/1728*r^6 + 1/576*r^5 + 37/34560*r^4 - 13/6912*r^3 - 1/1152*r^2
We can create a polynomial ring and use an Avector with entries in this ring.
The result is a tautological class with polynomial coefficients::
sage: R.<a1,a2>=PolynomialRing(QQ,2)
sage: Q=DR_cycle(1,(a1,a2,-a1-a2))
sage: Q
Graph : [1] [[1, 2, 3]] []
Polynomial : 1/2*a1^2*psi_1^1
<BLANKLINE>
Graph : [1] [[1, 2, 3]] []
Polynomial : 1/2*a2^2*psi_2^1
<BLANKLINE>
Graph : [1] [[1, 2, 3]] []
Polynomial : (1/2*a1^2 + a1*a2 + 1/2*a2^2)*psi_3^1
<BLANKLINE>
Graph : [0, 1] [[2, 3, 5], [1, 6]] [(5, 6)]
Polynomial : (-1/2*a1^2)*
<BLANKLINE>
Graph : [0, 1] [[1, 3, 5], [2, 6]] [(5, 6)]
Polynomial : (-1/2*a2^2)*
<BLANKLINE>
Graph : [0, 1] [[1, 2, 5], [3, 6]] [(5, 6)]
Polynomial : (-1/2*a1^2 - a1*a2 - 1/2*a2^2)*
<BLANKLINE>
Graph : [0] [[5, 6, 1, 2, 3]] [(5, 6)]
Polynomial : (-1/24)*
TESTS::
sage: from admcycles import DR_cycle, DR_cycle_old
sage: D = DR_cycle(2, (3, 4, -2), k=1) # long time
sage: D2 = DR_cycle_old(2, (3, 4, -2), k=1) # long time
sage: D.toTautvect() == D2.toTautvect() # long time
True
sage: D = DR_cycle(2, (1, 4, 5), k=2) # long time
sage: D2 = DR_cycle_old(2, (1, 4, 5), k=2) # long time
sage: D.toTautvect() == D2.toTautvect() # long time
True
sage: D = DR_cycle(3, (2,4), k=1) # long time
sage: D2 = DR_cycle_old(3, (2,4), k=1) # long time
sage: D.toTautvect() == D2.toTautvect() # long time
True
sage: D = DR_cycle(2, (3,),tautout=False); D
(0, 1/8, -9/4, 81/8, 1/4, -9/4, -1/8, 1/8, 1/4, -1/8, 1/48, 1/240, -3/16, -41/240, 1/48, 1/48, 1/1152)
sage: D2=DR_cycle_old(2, (3,), tautout=False); D2
(0, 1/8, -9/4, 81/8, 1/4, -9/4, -1/8, 1/8, 1/4, -1/8, 1/48, 1/240, -3/16, -41/240, 1/48, 1/48, 1/1152)
sage: D3 = DR_cycle(2, (3,), tautout=False, basis=True); D3
(-5, 2, -8, 18, 3/2)
sage: D4 = DR_cycle_old(2, (3,), tautout=False, basis=True); D4
(-5, 2, -8, 18, 3/2)
"""
if d is None:
d = g
n = len(Avector)
if any(isinstance(ai, MPolynomial) for ai in Avector):
pol, gens = DRpoly(g, d, n, tautout=tautout, basis=basis, gensout=True)
subsdict = {gens[i]: Avector[i] for i in range(n)}
if tautout:
pol.coeff_subs(subsdict)
pol.simplify()
return pol
return pol.apply_map(lambda x: x.subs(subsdict))
if k is None:
k = floor(sum(Avector)/(2*g-2+n))
if sum(Avector) != k*(2*g-2+n):
raise ValueError('2g-2+n must divide the sum of entries of Avector.')
gens = tautgens(g, n, d, decst=True)
v = vector([DR_coeff_new(decs, g, n, d, Avector, k, rpoly) for decs in gens]) / ZZ(2)**d
if basis:
v1 = Tautvecttobasis(v, g, n, d)
if tautout:
return Tautvb_to_tautclass(v1, g, n, d)
return v1
else:
if tautout:
return Tautv_to_tautclass(v, g, n, d)
return v
def interpolate(A, B):
r"""
Univariate Lagrange interpolation over the rationals.
EXAMPLES::
sage: from admcycles.double_ramification_cycle import interpolate
sage: p = interpolate([1/2, -2, 3], [4/5, 2/3, -7/6])
sage: p(1/2)
4/5
sage: p(-2)
2/3
sage: p(3)
-7/6
TESTS::
sage: from admcycles.double_ramification_cycle import interpolate
sage: parent(interpolate([], []))
Univariate Polynomial Ring in r over Rational Field
"""
R = polygen(QQ, 'r').parent()
return R.lagrange_polynomial(zip(A, B))
def DR_coeff_setup(G, g, n, d, Avector, k):
gamma = G.gamma
kappa, psi = G.poly.monom[0]
exp_list = []
scalar_factor = QQ((1, G.automorphism_number()))
given_weights = [-k * (2 * gv - 2 + len(gamma._legs[i]))
for i, gv in enumerate(gamma._genera)]
for v in range(gamma.num_verts()):
if len(kappa[v]) == 1:
scalar_factor *= (-k**2)**(kappa[v][0]) / factorial(kappa[v][0])
for i in range(1, n + 1):
v = gamma.vertex(i)
psipow = psi.get(i, 0)
given_weights[v] += Avector[i - 1]
scalar_factor *= (Avector[i - 1])**(2 * psipow) / factorial(psipow)
for (lu, lv) in gamma._edges:
psipowu = psi.get(lu, 0)
psipowv = psi.get(lv, 0)
exp_list.append(psipowu + psipowv + 1)
scalar_factor *= ((-1)**(psipowu+psipowv)) / factorial(psipowv) / factorial(psipowu) / (psipowu+psipowv+1)
return exp_list, given_weights, scalar_factor
def DR_coeff_new(G, g, n, d, Avector, k, rpoly):
gamma = G.gamma
kappa, _ = G.poly.monom[0]
if any(len(kalist) > 1 for kalist in kappa):
return 0
m0 = ceil(sum([abs(i) for i in Avector]) / ZZ(2)) + g*abs(k)
h0 = gamma.num_edges() - gamma.num_verts() + 1
exp_list, given_weights, scalar_factor = DR_coeff_setup(G, g, n, d, Avector, k)
deg = 2 * sum(exp_list)
u = gamma.flow_solve(given_weights)
Vbasis = gamma.cycle_basis()
mpoly = mpoly_special(exp_list, h0, u, Vbasis, m0+1, deg)
if rpoly:
return mpoly * scalar_factor
return mpoly.subs(r=0) * scalar_factor
def mpoly_special(exp_list, h0, u, Vbasis, start, degr):
r"""
Return the sum of the rational function in DR_coeff_new over the
affine space u + V modulo r in ZZ^n as a univariate polynomial in r.
V is the lattice with basis Vbasis.
Use that this sum is polynomial in r starting from r=start and the polynomial has
degree degr (for now).
"""
mrange = list(range(start, start + degr + 2))
mvalues = []
rank = len(Vbasis)
ulen = len(u)
for m in mrange:
total = 0
for coeff in itertools.product(*[list(range(m)) for i in range(rank)]):
v = u + sum([coeff[i]*Vbasis[i] for i in range(rank)])
v = [vi % m for vi in v]
total += prod([(v[i]*(m-v[i]))**exp_list[i] for i in range(ulen)])
mvalues.append(total/QQ(m**h0))
return interpolate(mrange, mvalues)
def mpoly_affine_sum(P, u, Vbasis, start, degr):
r"""
Return the sum of the polynomial P in variables r, d1, ..., dn over the
affine space u + V modulo r in ZZ^n as a univariate polynomial in r.
V is the lattice with basis Vbasis.
Use that this sum is polynomial in r starting from r=start and the polynomial has
degree degr (for now).
"""
mrange = list(range(start, start + degr + 2))
mvalues = []
rank = len(Vbasis)
for m in mrange:
total = 0
for coeff in itertools.product(*[list(range(m)) for i in range(rank)]):
v = u + sum([coeff[i] * Vbasis[i] for i in range(rank)])
v = [vi % m for vi in v]
total += P(m, *v)
mvalues.append(total)
return interpolate(mrange, mvalues)
def multivariate_interpolate(f, d, n, gridwidth=1, R=None, generator=None):
r"""Takes a vector/number-valued function f on n variables and interpolates it on a grid around 0.
Returns a vector with entries in a polynomial ring.
INPUT:
- ``d`` -- integer; maximal degree of output-polynomial in any of the variables
- ``gridwidth` -- integer (default: `1`); width of the grid used for interpolation
- ``R` -- polynomial ring (default: `None`); expected to be polynomial ring in
at least n variables; if None, use ring over QQ in variables z0,...,z(n-1)
- ``generator` -- list (default: `None`); list of n variables in the above polynomial
ring to be used in output; if None, use first n generators of ring
EXAMPLES::
sage: from admcycles.double_ramification_cycle import multivariate_interpolate
sage: from sage.modules.free_module_element import vector
sage: def foo(x,y):
....: return vector((x**2+y,2*x*y))
....:
sage: multivariate_interpolate(foo,2,2)
(z0^2 + z1, 2*z0*z1)
"""
if R is None:
R = PolynomialRing(QQ, 'z', n)
if generator is None:
generator = R.gens()
if n == 0:
return R.one() * f()
cube = [list(range(d + 1))] * n
points = itertools.product(*cube)
shift = floor((d + 1) / QQ(2)) * gridwidth
result = 0
for p in points:
lagr = prod([prod([generator[i]-(j*gridwidth-shift) for j in range(d+1) if j != p[i]]) for i in range(n)])
pval = [gridwidth*coord-shift for coord in p]
value = lagr.subs({generator[i]: pval[i] for i in range(n)})
if n == 1:
mult = lagr/value
result += vector((e*mult for e in f(*pval)))
else:
result += f(*pval) / value * lagr
return result
def DRpoly(g, d, n, dplus=0, tautout=True, basis=False, ring=None, gens=None, gensout=False):
r"""
Return the Double ramification cycle in genus g with n markings in degree d as a
tautclass with polynomial coefficients. Evaluated at a point (a_1, ..., a_n) with
sum a_i = k(2g-2+n) it equals DR_cycle(g,(a_1, ..., a_n),d).
The computation uses interpolation and the fact that DR is a polynomial in the a_i
of degree 2*d.
INPUT:
- ``dplus`` -- integer (default: `0`); if dplus>0, the interpolation is performed
on a larger grid as a consistency check
- ``tautout` -- bool (default: `True`); if False, returns a polynomial-valued vector
(in all generators for basis=false, in the basis of the ring for basis=true)
- ``basis` -- bool (default: `False`); if True, use FZ relations to give out the
DR cycle in a basis of the tautological ring
- ``ring` -- polynomial ring (default: `None`); expected to be polynomial ring in
at least n variables; if None, use ring over QQ in variables a1,...,an
- ``gens` -- list (default: `None`); list of n variables in the above polynomial
ring to be used in output; if None, use first n generators of ring
- ``gensout` -- bool (default: `False`); if True, return (DR polynomial, list of generators
of coefficient ring)
EXAMPLES::
sage: from admcycles import DRpoly, DR_cycle, psiclass
sage: D, (a1, a2) = DRpoly(1, 1, 2, gensout=True)
sage: D.coeff_subs({a2:-a1}).simplify()
Graph : [1] [[1, 2]] []
Polynomial : 1/2*a1^2*psi_1^1
<BLANKLINE>
Graph : [1] [[1, 2]] []
Polynomial : 1/2*a1^2*psi_2^1
<BLANKLINE>
Graph : [0] [[4, 5, 1, 2]] [(4, 5)]
Polynomial : (-1/24)*
sage: (D*psiclass(1,1,2)).evaluate() # intersection number with psi_1
1/24*a1^2 - 1/24
sage: (D.coeff_subs({a1:4})-DR_cycle(1,(4,-4))).is_zero() # polynomial agrees with DR_cycle at (4,-4)
True
sage: DRpoly(1,2,2).is_zero() # Clader-Janda: DR vanishes in degree d>g
True
TESTS::
sage: R.<a1,a2,a3,b1,b2,b3> = PolynomialRing(QQ, 6)
sage: D = DRpoly(1, 1, 3, ring=R, gens=[a1, a2, a3])
sage: Da = deepcopy(D)
sage: Db = deepcopy(D).coeff_subs({a1:b1, a2:b2, a3:b3})
sage: Dab = deepcopy(D).coeff_subs({a1:a1+b1, a2:a2+b2, a3:a3+b3})
sage: diff = Da*Db - Da*Dab # this should vanish in compact type by [Holmes-Pixton-Schmitt]
sage: diff.toTautbasis(moduli='ct')
(0)
"""
def f(*Avector):
return DR_cycle(g, Avector, d, tautout=False, basis=basis)
if ring is None:
ring = PolynomialRing(QQ, ['a%s' % i for i in range(1, n + 1)])
if gens is None:
gens = ring.gens()[0:n]
gridwidth = 2*g - 2 + n
interp = multivariate_interpolate(f, 2*d + dplus, n, gridwidth, R=ring, generator=gens)
if not tautout:
ans = interp
else:
if not basis:
ans = Tautv_to_tautclass(interp, g, n, d)
else:
ans = Tautvb_to_tautclass(interp, g, n, d)
if gensout:
return (ans, gens)
return ans
def degree_filter(polyvec, d):
r"""Takes a vector of polynomials in several variables and returns a vector containing the (total)
degree d part of these polynomials.
INPUT:
- vector of polynomials
- integer degree
EXAMPLES::
sage: from admcycles.double_ramification_cycle import degree_filter
sage: R.<x,y> = PolynomialRing(QQ, 2)
sage: v = vector((x+y**2+2*x*y,1+x**3+x*y))
sage: degree_filter(v, 2)
(2*x*y + y^2, x*y)
"""
resultvec = []
for pi in polyvec:
s = 0
for c, m in pi:
if m.degree() == d:
s += c * m
resultvec.append(s)
return vector(resultvec)
def Hain_divisor(g, A):
r"""
Returns a divisor class D extending the pullback of the theta-divisor under the Abel-Jacobi map (on compact type) given by partition A of zero.
Note: D^g/g! agrees with the Double-Ramification cycle in compact type.
EXAMPLES::
sage: from admcycles import *
sage: R = PolynomialRing(QQ, 'z', 3)
sage: z0, z1, z2 = R.gens()
sage: u = Hain_divisor(2, (z0, z1, z2))
sage: g = DRpoly(2, 1, 3, ring=R, gens=[z0, z1, z2]) #u,g should agree inside compact type
sage: (u.toTautvect() - g.toTautvect()).subs({z0: -z1-z2})
(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1/24)
"""
n = len(A)
def delt(l, g, n, I):
I = set(I)
if l == 0 and len(I) == 1:
return -psiclass(list(I)[0], g, n)
if l == g and len(I) == n-1:
i_set = set(range(1, n+1)) - I
return -psiclass(list(i_set)[0], g, n)
if (l == 0 and len(I) == 0) or (l == g and len(I) == n):
return tautclass([])
Icomp = set(range(1, n+1)) - I
gra = StableGraph([l, g-l], [list(I) + [n+1], list(Icomp) + [n+2]],
[(n+1, n+2)])
return QQ((1, gra.automorphism_number())) * gra.to_tautclass()
result = sum([sum([(sum([A[i - 1] for i in I])**2) * delt(l, g, n, I)
for I in Subsets(list(range(1, n + 1)))])
for l in range(g + 1)])
return QQ((-1, 4)) * result