Description:

A tutorial for the software package admcycles for SageMath.

Views: 590
Visibility: Unlisted (only visible to those who know the link)
Kernel: SageMath 9.2
$\def\CC{\bf C} \def\QQ{\bf Q} \def\RR{\bf R} \def\ZZ{\bf Z} \def\NN{\bf N}$

Below we show how to use admcycles for computations in the tautological ring of the moduli space $\overline{\mathcal{M}}_{g,n}$ of stable curves. More detailed explanations are available in the preprint [Delecroix-Schmitt-vanZelm].

If you opened this document on share.cocalc.com, you can open an interactive copy by clicking the button Open in CoCalc with one click above. Then you can execute commands and do calculations below by clicking on a box containing code (such as from admcycles import * below) and pressing Shift + Enter. Note that many commands rely on earlier lines having been executed before, in particular the first line from admcycles import * should be executed once at the start.

To insert a new box for writing own commands, you can choose Insert -> Insert cell below in the menu above or click left of the text of a box and press b.

To use admcycles, the first thing you need to do is import it:

In [1]:
from admcycles import *


Then many functions become available. For example you can enter tautological classes as combinations of divisors (here on $\overline{\mathcal{M}}_{3,4}$):

In [0]:
t1 = 3*sepbdiv(1,(1,2),3,4)-psiclass(4,3,4)^2


Above, the function sepbdiv(g1,A1,g,n) returns the class of a boundary divisor, the pushforward of the fundamental class under the gluing map $\overline{\mathcal{M}}_{g_1, A_1 \cup \{\bullet\}} \times \overline{\mathcal{M}}_{g-g_1, A_1^c \cup \{\bullet\}} \to \overline{\mathcal{M}}_{g, n}$, and psiclass(i,g,n) returns $\psi_i$ on $\overline{\mathcal{M}}_{g,n}$. To avoid having to type g,n in long formulas, we can use reset_g_n(g,n) to set them once:

In [0]:
reset_g_n(2,1)
t2 = -1/3*irrbdiv()*lambdaclass(1)


To get explanations about a function, you can type the name of the function followed by a question mark. E.g. type irrbdiv? below and press Shift+Enter to see the documentation of this function:

In [0]:



## Tautological classes

### Creating tautological classes

One way to enter a tautological class is to first use the function list_tautgens(g,n,r) to print a list of generators of $\mathrm{RH}^{2r}(\overline{\mathcal{M}}_{g,n})$ :

In [0]:
list_tautgens(2,0,2)


Generators are given by a stable graph, decorated with a monomial in $\kappa$ and $\psi$-classes (see below for an explanation of the representation of stable graphs). One can create a list L of the generators we printed above using tautgens(g,n,r) and compute linear combinations of the elements L[i] of this list:

In [0]:
L = tautgens(2,0,2)
t3=2*L[3]+L[4]
t3


Stable graphs are represented by three lists:

• a list genera of the genera $g_i$ of the vertices,

• a list legs of lists of legs and half-edges at these vertices,

• a list edges of pairs (h1,h2) of half-edges forming an edge.

A stable graph can be created manually using StableGraph(genera,legs,edges) by specifying these three lists. Below we create a stable graph with two vertices of genus 1, carrying half-edges 2,3 which form an edge:

In [0]:
G = StableGraph([1,1],[[2],[3]],[(2,3)]); G


### Basic operations

Tautological classes can be manipulated using standard arithmetic operations:

In [0]:
s1 = psiclass(3,1,3)^2 # square of psi_3 on \Mbar_{1,3}


They can also be pushed forward under forgetful morphisms, by specifying the list of markings that are forgotten. As an example, we push forward s_1, the class $\psi_3^2$ on $\overline{\mathcal{M}}_{1,3}$, under the map forgetting marking $3$, obtaining the class $\kappa_1$ on $\overline{\mathcal{M}}_{1,2}$ as expected:

In [0]:
s1.forgetful_pushforward([3])


Similarly, we can pull back the class $\psi_2$ on $\overline{\mathcal{M}}_{1,2}$ :

In [0]:
s2 = psiclass(2,1,2)
s2.forgetful_pullback([3])


Given a tautological class t, the function t.evaluate() computes the integral of t against the fundamental class of $\overline{\mathcal{M}}_{g,n}$, i.e. the degree of the zero-cycle part of t. Below we compute the intersection number $\int_{\overline{\mathcal{M}}_{1,3}} \psi_2 \psi_3^2$ We check the equality

$\int_{\overline{\mathcal{M}}_{1,3}} \psi_2 \psi_3^2 = \int_{\overline{\mathcal{M}}_{1,2}} \psi_2^2 + \psi_1 \psi_2$

predicted by the String equation:

In [0]:
s3 = psiclass(2,1,3)*psiclass(3,1,3)^2
s3.evaluate()

In [0]:
s4 = psiclass(2,1,2)^2+psiclass(1,1,2)*psiclass(2,1,2)
s4.evaluate()


Using simplify() to reduce number of terms in tautclass:

In [0]:
psisum = psiclass(1,2,1) + 3 * psiclass(1,2,1); psisum

In [0]:
psisimple = psisum.simplify(); psisimple


### A basis of the tautological ring and tautological relations

The package can compute the generalized Faber-Zagier relations between the generators above. The function generating_indices(g,n,r) computes a list of indices of tautgens(g,n,r) forming a basis of $\mathrm{RH}^{2r}(\overline{\mathcal{M}}_{g,n})$ :

In [0]:
generating_indices(2,0,2)


Then, the function toTautbasis(g,n,r) can be used to express a tautological class in this basis:

In [0]:
t3.toTautbasis(2,0,2)


This means that the class t3 we defined above as the linear combination t3=2*L[3]+L[4] can be expressed as t3=-48*L[0]+22*L[1] in terms of the basis L[0],L[1] of $\mathrm{RH}^{4}(\overline{\mathcal{M}}_{2,0})$.

We can also use the function is_zero to check a tautological relation. Below, we verify the divisor relation $\kappa - \psi + \delta_0 = 0$ on $\overline{\mathcal{M}}_{1,4}$ :

In [0]:
g=1; n=4
reset_g_n(g,n)
bgraphs = [bd for bd in list_strata(g,n,1) if bd.numvert()>1]
del0 = sum([bd.to_tautclass() for bd in bgraphs]) # sum of boundary classes with separating node
psisum = sum([psiclass(i) for i in range(1,n+1)]) # sum of psi-classes
rel = kappaclass(1)-psisum+del0
rel.is_zero()


Comparing classes on open subsets of $\overline{\mathcal{M}}_{1,4}$ using parameter moduli to be one of 'st', 'tl', 'ct', 'rt' or 'sm' :

In [0]:
kappaclass(1,3,0).toTautbasis(moduli='sm')

In [0]:
lambdaclass(1,3,0).toTautbasis(moduli='sm')

In [0]:
diff = lambdaclass(1,3,0) - (1/12)*kappaclass(1,3,0)
diff.is_zero(moduli='sm')


### Pulling back tautological classes to a boundary divisor

Below we create a stable graph bdry and compute a pullback of a tautological class under the corresponding boundary gluing map. The result is expressed in terms of a basis of the tautological ring on $\overline{\mathcal{M}}_{2,1} \times \overline{\mathcal{M}}_{2,1}$ :

In [0]:
bdry = StableGraph([2,2],[[1],[2]],[(1,2)])
generator = tautgens(4,0,2)[3]
generator

In [0]:
pullback = bdry.boundary_pullback(generator)
pullback.totensorTautbasis(2)

In [0]:
pullback.totensorTautbasis(2,vecout=true)


We can see that in the Kunneth decomposition of $\mathrm{H}^4(\overline{\mathcal{M}}_{2,1} \times \overline{\mathcal{M}}_{2,1})$ the pullback has no component along $\mathrm{H}^2(\overline{\mathcal{M}}_{2,1}) \otimes \mathrm{H}^2(\overline{\mathcal{M}}_{2,1})$ and the contributions to $\mathrm{H}^0(\overline{\mathcal{M}}_{2,1}) \otimes \mathrm{H}^4(\overline{\mathcal{M}}_{2,1})$ and $\mathrm{H}^4(\overline{\mathcal{M}}_{2,1}) \otimes \mathrm{H}^0(\overline{\mathcal{M}}_{2,1})$ are symmetric, as expected.

### Pushing forward classes from the boundary

We can also compute the pushforward of the product of classes under a boundary gluing map:

In [0]:
B = StableGraph([2,1],[[4,1,2],[3,5]],[(4,5)])
Bclass = B.boundary_pushforward() # class of undecorated boundary divisor
si1 = B.boundary_pushforward([fundclass(2,3),-psiclass(2,1,2)]); si1

In [0]:
si2 = B.boundary_pushforward([-psiclass(1,2,3),fundclass(1,2)]); si2


si1 is obtained by pushing forward the fundamental class on the genus 2 vertex times $-\psi_h$ on the second vertex (where $h$ is the half-edge). We can then check the self-intersection formula for the boundary divisor above:

In [0]:
(Bclass*Bclass-si1-si2).is_zero()


## Special cycle classes

### Double ramification cycles

Double ramification cycles are computed by the function DR_cycle(g,A). Below we verify a multiplicativity relation between DR-cycles from the paper [Holmes-Pixton-Schmitt]:

In [0]:
A = vector((2,4,-6)); B = vector((-3,-1,4))
diff = DR_cycle(1,A)*DR_cycle(1,B)-DR_cycle(1,A)*DR_cycle(1,A+B)
diff.is_zero(moduli='tl') # vanishing on treelike locus

In [0]:
diff.is_zero(moduli='st') # does not vanish on locus of all stable curves


Calculating DR-cycles as classes with polynomial coefficients in the input:

In [0]:
R.<a1,a2,a3,b1,b2,b3> = PolynomialRing(QQ,6)
A = vector((a1,a2,a3)); B = vector((b1,b2,b3))
diff = DR_cycle(1,A)*DR_cycle(1,B)-DR_cycle(1,A)*DR_cycle(1,A+B)
diff.is_zero(moduli='tl')


Checking intersection numbers of DR-cycles with lambdaclass from [Buryak-Rossi]:

In [0]:
intersect = DR_cycle(1,A)*DR_cycle(1,B)*lambdaclass(1,1,3)
f = intersect.evaluate(); factor(f)

In [0]:
g = f.subs({a3:-a1-a2,b3:-b1-b2}); factor(g)


### Strata of k-differentials

Strata of k-differentials using Strataclass(g,k,mu) with mu vector of zero and pole multiplicities:

In [0]:
L = Strataclass(2,1,(3,-1)); L.is_zero()

In [0]:
L = Strataclass(2,1,(2,)); (L-Hyperell(2,1)).is_zero()


### Generalized lambda classes

Computing Chern classes of $R \pi_* \mathcal{O}(D)$ for the universal curve $\mathcal{C}_{g,n} \to \overline{\mathcal{M}}_{g,n}$ using generalized_lambda :

In [0]:
g=3; n=1
l=1; d=[0]; a=[]
s = lambdaclass(2,g,n)
t = generalized_lambda(2,l,d,a,g,n)
(s-t).is_zero()


#### Hyperelliptic and bielliptic cycles

Computing the cycle of the hyperelliptic locus in genus 3:

In [0]:
H = Hyperell(3,0,0)


The cycle of hyperelliptic curves of genus 3 with 0 marked fixed points of the involution and 0 marked pairs of conjugate points:

In [0]:
H.toTautbasis()


We compare with the known expression $H=9 \cdot \lambda_1-\delta_0-3\cdot \delta_1$ :

In [0]:
reset_g_n(3, 0)
H2 = 9*lambdaclass(1)-(1/2)*irrbdiv()-3*sepbdiv(1,())
H2.toTautbasis()


#### Creating and identifying general admissible cover cycles

Below we define the group $G=\mathbb{Z}/2\mathbb{Z}$ and ramification data H, specifying that we look at double covers with two points of stabilizer G[1], which is the generator of the group $G$ :

In [0]:
G = PermutationGroup([(1,2)]) # G=Z/2Z
H = HurData(G,[G[1],G[1]])


An example with this ramification behaviour is the locus of bielliptic curves $(C,p,q)$ in $\overline{\mathcal{M}}_{2,2}$ of genus 2 curves $C$ admitting a double cover of an elliptic curve with marked ramification points $p,q$ . The following identifies the class of this locus in terms of the generating set tautgens(2,2,3) of $\mathrm{RH}^6(\overline{\mathcal{M}}_{2,2})$ :

In [0]:
# The following computation might take very long, and will possibly not finish on the free version of cocalc
# Remove the # symbols below and press Shift+Enter to try anyway
# vbeta = Hidentify(2,H,vecout=true)
# vector(vbeta)


If instead we wanted to specify a locus with two points of generator G[1] and one pair of points with generator G[0], we would consider:

In [0]:
H2 = HurData(G,[G[1],G[1],G[0]])


We can also identify the pushforward of the locus of bielliptic curves $(C,p,q)$ under the map forgetting both markings, obtaining (a multiple of) the locus of bielliptic curves $C$ inside $\overline{\mathcal{M}}_{2,0}$. For this we use the optional parameter markings to specify that no marking should be remembered:

In [2]:
G = PermutationGroup([(1,2)])
H = HurData(G,[G[1],G[1]])
Biell = Hidentify(2,H,markings=[])
Biell.toTautbasis(2,0,1)

Out[2]:
(30, -9)

We can compare this to a known formula $[\overline{B}_2] = 3/2 \delta_{\text{irr}} + 3 \delta_1$. When entering this, note that irrbdiv returns two times the class $\delta_{\text{irr}}$ since in general the convention is not to divide by automorphisms of stable graphs:

In [0]:
reset_g_n(2, 0)
Biell2 = 3/4*irrbdiv()+ 3*sepbdiv(1,())
Biell2.toTautbasis(2,0,1)


### Example: Hurwitz-Hodge integrals

Computing the Hurwitz-Hodge integral $\int_{\overline{B}_{2,2,0}} \lambda_2$ :

In [3]:
(Biell*lambdaclass(2,2,0)).evaluate()

Out[3]:
1/48

Computing Hurwitz-Hodge integral of cyclic triple covers of genus 0 curves against $\lambda_1$, see [Owens-Somerstep]:

In [0]:
G = PermutationGroup([(1,2,3)])
g1 = G('(1,2,3)')
g2 = G('(1,3,2)')
H = HurData(G,[g1, g1, g2, g2]) #n=2, m=2
t = Hidentify(2,H,markings=[])
(t*lambdaclass(1,2,0)).evaluate()


## Citing admcycles

If you use admcycles in your research, consider citing the preprint [Delecroix-Schmitt-vanZelm].

In [0]: