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())