Tree-level Gromov–Witten invariants of projective space
I am currently writing my master’s thesis on Gromov–Witten invariants \(GW_{g,n,\beta}^X(\gamma_1\otimes\cdots\otimes\gamma_n)\). They roughly correspond to the number of genus \(g\) holomorphic curves in a projective variety \(X\) that map to a \(\beta\in H_2(X;\mathbb{Z})\), together with \(n\) marked points such that the \(i\)-th marked point passes through the rational cohomology class \(\gamma_i\). Thanks to the mapping to a point and divisor axioms (Kontsevich–Manin, 1994), it is possible to simplify the calculation of Gromov–Witten invariants to some countable set of rational numbers
\begin{equation} GW_{g,n,\beta}^X(q_1^{\otimes n_1}\otimes\cdots\otimes q_D^{\otimes n_D}) = \prod_{i=1}^r \langle q_i,\beta\rangle^{n_i} N_g(n_{r+2},\dots,n_D;\beta). \end{equation}
Here, \(\beta\) is assumed to be nonzero and torsion-free, and \(q_0\dots,q_D\) are integral generators of \(H^*(X;\mathbb{Q})\) such that \(q_0=1\) and \(q_1,\dots,q_r\) generate \(H^2(X;\mathbb{Q})\).
An important tool in calculating genus zero (tree-level) Gromov–Witten invariants is the Witten–Dijkgraaf–Verlinde–Verlinde (WDVV) equation. It is a differential equation on a generating function of the Gromov–Witten invariants known as the Gromov–Witten potential \(\Phi(x_0,\dots,x_D)\), such that, roughly,
\[\partial_{i_1}\cdots\partial_{i_k}\Phi(0) = \sum_{\beta\in H_2(X;\mathbb{Z})}GW_{0,k,\beta}^X(q_{i_1}\otimes\cdots\otimes q_{i_k}).\]
The WDVV equation is a method of generating recursive methods. However, it does not directly provide a formula, which makes it challenging to work with. If we then know some initial value of the tree-level Gromov–Witten invariants of \(X\), we may we able to calculate all others using the WDVV equation.
Complex projective space
Consider now \(X=\mathbb{CP}^n\), and let \(q\) be the standard positive generator of \(H^*(X;\mathbb{Q})\), and identify \(H_2(X;\mathbb{Z})\) with \(\mathbb{Z}\) such that \(\langle q,1\rangle=1\). The Gromov–Witten invariants are numbers \(N_0(k_3,\dots,k_n;d)\). We already know some initial values:
\begin{equation} GW_{0,3,0}^X(q^i\otimes q^j\otimes q^k) = \delta_{n,i+j+k},\quad GW_{0,3,1}^X(q^i\otimes q^j\otimes q^k) = \delta_{2n+1,i+j+k},\label{eq:initvals} \end{equation}
the latter of which is found by proving that all such Gromov–Witten invariants are equal to \(GW_{0,3,1}^X(q\otimes q^n\otimes q^n)\), which is the number of lines through two points.
\(n=1\)
First, since \(3>n\), we discover that the Gromov–Witten invariants of interest are \(N_0(d)\) for \(d>0\). By equation \eqref{eq:initvals}, we already know that \(N_0(1)=1\). Furthermore, due to the grading axiom, all other \(N_0(d)\) are zero, so we are done.
\(n=2\)
Again, \(3>n\), so we want to find \(N_0(d)\) for \(d>0\) with initial value \(N_0(1)=1\). The WDVV equation tells us that
\begin{equation} N_0(d) = \sum_{k+l=d} k^2lN_0(k)N_0(l)\left[l\binom{3d-4}{3k-2} - k\binom{3d-4}{3k-1}\right], \end{equation}
known as Kontsevich’s formula. It is a straightforward formula that, while difficult to calculate by hand, is perfect for numerical calculations (see implementation below). Up to \(d=8\), they are given by
| \(\boldsymbol{d}\) | \(\boldsymbol{1}\) | \(\boldsymbol{2}\) | \(\boldsymbol{3}\) | \(\boldsymbol{4}\) | \(\boldsymbol{5}\) | \(\boldsymbol{6}\) | \(\boldsymbol{7}\) | \(\boldsymbol{8}\) |
|---|---|---|---|---|---|---|---|---|
| \(\boldsymbol{N_0(d)}\) | \(1\) | \(1\) | \(12\) | \(620\) | \(87304\) | \(26312976\) | \(14616808192\) | \(13525751027392\) |
\(n=3\)
This case is immediately more complicated, since the Gromov–Witten invariants of interest are \(N_0(n;d)\): the number of \(d\)-fold curves that pass through \(n\) points and \(4d-2n\) lines. The following equations hold for \(n\leq 2d-2\):
\(\begin{multline} N_0(n;d) - 2dN_0(n+1;d) = \sum_{k+l=d}\sum_{a+b=n}\binom{n}{a}k^2N_0(a;k)N_0(b;l)\\ \cdot\left[l\binom{4d-2n-3}{4k-2a-1} - k\binom{4d-2n-3}{4k-2a}\right],\label{eq:cp31} \end{multline}\)
\(\begin{multline} N_0(n+1;d) - dN_0(n+2;d) = \sum_{k+l=d}\sum_{a+b=n}\binom{n}{a}kN_0(a;k)N_0(b+1;l)\\ \cdot\left[l^2\binom{4d-2n-4}{4k-2a-2} - k^2\binom{4d-2n-4}{4k-2a}\right].\label{eq:cp32} \end{multline}\)
This equation holds for \(d\geq 2\) and \(n\leq 2d-3\):
\(\begin{multline} N_0(n+2;d) = \sum_{k+l=d}\sum_{a+b=n}\binom{n}{a}\left\{2k^2l\binom{4d-2n-5}{4k-2a-2}N_0(a+1;k)N_0(b+1;l)\right.\\ \left.{}- k^2N_0(a;k)N_0(b+2;l)\left[k\binom{4d-2n-5}{4k-2a} + l\binom{4d-2n-5}{4k-2a-1}\right]\right\}.\label{eq:cp33} \end{multline}\)
The initial value in equation \eqref{eq:initvals} is \(N_0(2;1)=1\). Inductively, these three equations give us all other invariants. Indeed, let us assume that for some \(d\geq 1\) we know \(N_0(2;d)\) and \(N_0(a;k)\) for all \(k\lt d\). By \eqref{eq:cp32} with \(n=0\), we find that
\begin{equation} N_0(1;d) = dN_0(2;d) + \sum_{k+l=d} kN_0(0;k)N_0(1;l)\left[l^2\binom{4d-4}{4k-2} - k^2\binom{4d-4}{4k}\right], \end{equation}
and by \eqref{eq:cp31} with \(n=0\),
\begin{equation} N_0(0;d) - 2dN_0(1;d) = \sum_{k+l=d}k^2N_0(0;k)N_0(0;l)\left[l\binom{4d-3}{4k-1} - k\binom{4d-3}{4k}\right]. \end{equation}
Equation \eqref{eq:cp32} then gives all other \(N_0(n;d)\). Finally, we complete the induction by using \eqref{eq:cp33} with \(n=0\) to find
\(\begin{multline} N_0(2;d+1) = \sum_{k+l=d+1}2k^2l\binom{4d-1}{4k-2}N_0(1;k)N_0(1;l)\\ {}- k^2N_0(0;k)N_0(2;l)\left[k\binom{4d-1}{4k} + l\binom{4d-1}{4k-1}\right]. \end{multline}\)
This algorithm is even more complicated, but perfectly doable for Python. Up to \(d=6\), we find
| \(\boldsymbol{n\setminus d}\) | \(\boldsymbol{1}\) | \(\boldsymbol{2}\) | \(\boldsymbol{3}\) | \(\boldsymbol{4}\) | \(\boldsymbol{5}\) | \(\boldsymbol{6}\) |
|---|---|---|---|---|---|---|
| \(\boldsymbol{0}\) | \(2\) | \(92\) | \(80160\) | \(383306880\) | \(6089786376960\) | \(244274488980962304\) |
| \(\boldsymbol{1}\) | \(1\) | \(18\) | \(9864\) | \(34382544\) | \(429750191232\) | \(14207926965714432\) |
| \(\boldsymbol{2}\) | \(1\) | \(4\) | \(1312\) | \(3259680\) | \(31658432256\) | \(855909223176192\) |
| \(\boldsymbol{3}\) | \(0\) | \(1\) | \(190\) | \(327888\) | \(2440235712\) | \(53486265350784\) |
| \(\boldsymbol{4}\) | \(0\) | \(0\) | \(30\) | \(35104\) | \(197240400\) | \(3472451647488\) |
| \(\boldsymbol{5}\) | \(0\) | \(0\) | \(5\) | \(4000\) | \(16744080\) | \(234526910784\) |
| \(\boldsymbol{6}\) | \(0\) | \(0\) | \(1\) | \(480\) | \(1492616\) | \(16492503552\) |
| \(\boldsymbol{7}\) | \(0\) | \(0\) | \(0\) | \(58\) | \(139098\) | \(1207360512\) |
| \(\boldsymbol{8}\) | \(0\) | \(0\) | \(0\) | \(4\) | \(13354\) | \(91797312\) |
| \(\boldsymbol{9}\) | \(0\) | \(0\) | \(0\) | \(0\) | \(1265\) | \(7200416\) |
| \(\boldsymbol{10}\) | \(0\) | \(0\) | \(0\) | \(0\) | \(105\) | \(573312\) |
| \(\boldsymbol{11}\) | \(0\) | \(0\) | \(0\) | \(0\) | \(0\) | \(44416\) |
| \(\boldsymbol{12}\) | \(0\) | \(0\) | \(0\) | \(0\) | \(0\) | \(2576\) |
Implementation
Here, I describe the script I used to calculate the Gromov–Witten invariants of \(\mathbb{CP}^2\) and \(\mathbb{CP}^3\).
\(n=2\)
from math import comb
import numpy as np
import pandas as pd
from fractions import Fraction # GW invariants are rational, so this is more precise
def combb(n,r):
# A binomial that returns zero whenever arguments are illegal
if n<0 or r<0:
return 0
else:
return comb(n,r)
def calc_GW(max_degree: int) -> np.array:
# max_degree: maximum degree
# Output: N[d] is N_0(d)
N = np.zeros((max_degree+1),Fraction)
# Initial values
N[0] = np.nan
N[1] = Fraction(1,1)
for d in range(2,len(N)):
# Calculating N_0(d)
N[d] = 0
for k in range(1,d):
l = d-k
N[d] += k**2*l*N[k]*N[l]*(l*combb(3*d-4,3*k-2)-k*combb(3*d-4,3*k-1))
return N
def GW_table(max_degree: int) -> pd.DataFrame:
# Turn GWs into table
N = calc_GW(max_degree)
table = pd.DataFrame(N[1:],
index = range(1,max_degree+1),
columns = ["N_0(d)"])
table.columns.name = "d"
return table
if __name__ == "__main__":
results = GW_table(8).astype(str)
# Add math mode to all cells
def str_to_math(x: str) -> str:
return f"\\\({x}\\\)"
def str_to_boldmath(x: str) -> str:
return str_to_math(f"\\boldsymbol}")
results = results.rename(str_to_boldmath).rename(columns=str_to_boldmath).map(str_to_math)
results.columns.name = str_to_boldmath("d")
with open("CP2.md", "w") as f:
f.write(results.transpose().to_markdown())
\(n=3\)
from math import comb
import numpy as np
import pandas as pd
from fractions import Fraction # GW invariants are rational, so this is more precise
def combb(n,r):
# A binomial that returns zero whenever arguments are illegal
if n<0 or r<0:
return 0
else:
return comb(n,r)
def calc_GW(max_degree: int) -> np.array:
# max_degree: maximum degree
# Output: N[d][n] is N_0(n;d)
N = np.zeros((max_degree+1,2*max_degree+1),Fraction)
# Initial values
N[0] = np.nan
N[1][2] = Fraction(1,1)
for d in range(1,len(N)):
# Calculating N_0(1;d)
N[d][1] = d*N[d][2]
for k in range(1,d):
l = d-k
N[d][1] += k*N[k][0]*N[l][1]*(l**2*combb(4*d-4,4*k-2)-k**2*combb(4*d-4,4*k))
# Calculating N_0(0;d)
N[d][0] = 2*d*N[d][1]
for k in range(1,d):
l = d-k
N[d][0] += k**2*N[k][0]*N[l][0]*(l*combb(4*d-3,4*k-1)-k*combb(4*d-3,4*k))
# Calculating all other N_0(n;d)
for n in range(3,2*d+1):
N[d][n] = N[d][n-1]/d
for k in range(1,d):
l = d-k
for a in range(0,n-1):
b = n-2-a
N[d][n] -= combb(n-2,a)*k*N[k][a]*N[l][b+1]*(l**2*combb(4*d-2*n,4*k-2*a-2)-k**2*combb(4*d-2*n,4*k-2*a))/d
# Calculating N_0(2;d+1)
if d+1 <= max_degree:
for k in range(1,d+1):
l = d+1-k
N[d+1][2] += 2*k**2*l*combb(4*d-1,4*k-2)*N[k][1]*N[l][1] - k**2*N[k][0]*N[l][2]*(k*combb(4*d-1,4*k)+l*combb(4*d-1,4*k-1))
return N
def GW_table(max_degree: int) -> pd.DataFrame:
# Turn GWs into table
N = calc_GW(max_degree)
table = pd.DataFrame(N[1:],
index = range(1,max_degree+1),
columns = range(2*max_degree+1))
table.columns.name = "d\\n"
return table
if __name__ == "__main__":
results = GW_table(6).astype(str)
# Add math mode to all cells
def str_to_math(x: str) -> str:
return f"\\\({x}\\\)"
def str_to_boldmath(x: str) -> str:
return str_to_math(f"\\boldsymbol}")
results = results.rename(str_to_boldmath).rename(columns=str_to_boldmath).map(str_to_math)
results.columns.name = str_to_boldmath("n\\setminus d")
with open("CP3.md", "w") as f:
f.write(results.transpose().to_markdown())