1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
"""
This is the file with the main intersection code!
"""

try:
    from sage.all import *
    from TautRing3 import *
    from tau import *
    #from checkin import *
    from remember import remember_convert_args
    from push_down import push_down
    

except ImportError:
    pass

    
class monomial_data(object):
    """
    This class parses a monomial and stores the important data about it.  The data can then be easily passed around.
    """

    def __init__(self, space, monomial=None):
        """
        This constructor initializes the data and parses the monomial.  
        
        If you don't pass in a monomial, then it is assumed that you will call the recieve method manually.  This happens in instances of monomial_data_product.
        """
        self.space = space
        self.psis = []
        self.red_boundaries = []
        self.irr_boundaries = 0
        self.kappas = []
        self.cherns = []
        self.lambdas = []
        if None != monomial:
            self.monomial = monomial
            for tclass, expon in monomial.decompose_monomial():
                self.recieve(tclass, expon)
            self.recieve = None #make sure we don't double populate it  
        else:
            self.monomial = 1
            
    #########################################################################
    #The following methods are used to do the parsing and intialize the data.
    
    def psi(self, tclass, expon):
        self.psis.append((tclass, expon))
        
    def irr(self, tclass, expon):
        self.irr_space = tclass.space
        self.irr_boundaries += expon
        
    def red(self, tclass, expon):
        self.red_boundaries += [tclass]*expon
        
    def kappa(self, tclass, expon):
        self.kappas += [tclass.degree]*expon
        
    def chern(self, tclass, expon):
        self.cherns += [tclass.degree]*expon
        
    def lamb(self, tclass, expon):
        self.lambdas += [tclass.degree]*expon
        
    # This is a class variable (dict) that associates a method to each class.
    route = {   psi_class: psi,
                reducible_boundary: red,
                irreducible_boundary: irr,
                kappa_class: kappa,
                chern_char: chern,
                lambda_class: lamb }
                
    #  end parsing methods
    ##################################################################
    
    def recieve(self, tclass, expon):
        """
        Recieves a TautRingElement (tclass) and an integer exponent, and calls the appropriate method to record the data.
        """
        self.route[tclass.__class__](self, tclass, expon)
        
    def psi_exp(self):
        """
        Returns a list of the exponents of the psis.
        """
        return [t[1] for t in self.psis]
        
    def degree(self):
        """
        Computes the degree of the class represented by this monomial.
        """
        return sum(self.psi_exp()) + sum(self.kappas) + len(self.red_boundaries) + self.irr_boundaries + sum(self.cherns) + sum(self.lambdas)        
    
    def get_chern(self):
        """
        Returns the chern_char class that you should deal with first.
        
        TODO:  decide which one you should do first. for now, it just returns the first one.
        """
        return chern_char(self.space, self.cherns[0])
    
    def get_lambda(self):
        """
        Returns the lambda_class that you should substitute out first, and its exponent.
        
        TODO:  decide which one you should do first.
        """
        return lambda_class(self.space, self.lambdas[0]), self.lambdas.count(self.lambdas[0])
        
    def get_red(self):
        """
        Returns the reducible_boundary class that you should pull back onto first.
        
        Assume it has already been sorted since self.key will call sort(self.irr_boundaries).  See the reducible_boundary class in the file TautRing3.py for the comparison methods.
        """
        return self.red_boundaries[0]
        
    def vanishes_by_degree(self):
        """
        Returns True if the class represented by this monomial does not have degree equal to the space it is on.  Returns False if it does have the right degree.
        """
        return self.degree() != self.space.dimension
        
    def base_case(self):
        """
        Checks if this monomial is covered by some base cases.  If it is, then return the value.  If not, return None.
        
        TODO:  Is this the best way to handle this, or should I have a dict or something?
        """
        if self.space.genus == 0 and (self.space.n == 4 or self.space.n == 3):            
            return 1
        if self.space.genus == 1 and self.space.n == 1:
            #hard code in these answers
            if self.irr_boundaries == 1:
                return Rational((1,2))
            else: # ch1, psi1, or kappa1, or lambda??
                return Rational((1,24))
        if self.space.genus == 2 and self.space.n == 0:
            if len(self.cherns) == 1 and self.cherns[0] == 3:
                #hard code in this special case
                return Rational((-1, 34560))
                
        return None #meaning it is not a base case.
                
    def key(self):
        """
        Returns a tuple of tuples, suitable for use as a dict key, that represents this monomial up to permutation of marked points.
        
        If there are only psis, returns a simplified tuple compatible with the function from tau.py.
        """
        self.psis.sort(key = lambda t: t[1]) #sort by exponent
        if len(self.red_boundaries) == 0 and len(self.cherns) == 0 and len(self.lambdas) == 0 and self.irr_boundaries == 0 and len(self.kappas) == 0:
            d = from_exp_list_to_tau(self.space.genus, self.space.n, self.psi_exp())            
            return (self.space.genus, tuple(d))
        self.red_boundaries.sort() 
        self.cherns.sort()
        self.lambdas.sort()
        self.kappas.sort()
        reindex_marks = dict( ((psi_tuple[0].mark, i) for i, psi_tuple in enumerate(self.psis) ))
        return ( self.space.genus, self.space.n, tuple( self.psi_exp() ),
                      tuple( self.kappas),
                      tuple( (rb.table_key(reindex_marks) for rb in self.red_boundaries) ),
                      self.irr_boundaries,
                      tuple( self.cherns), 
                      tuple( self.lambdas)
        )
        
    def can_push_down(self):
        """
        Returns true if we can use Faber's trick to push down self-intersections of irreducible boundaries.
        """
        return len(self.red_boundaries) == 0 and len(self.cherns) == 0 and len(self.lambdas) == 0
        
    def push_down_psis(self):
        r"""
        Assume that there are only psis and irrs.  
        
        Returns a polynomial on Mbar_g,0 that is the push down under the forgetful map of the monomial represented by self.
        """
        M = self.space.pushed_down()
        return push_down(self.psi_exp(), self.space.genus, self.space.n) * prod([kappa_class(M,d) for d in self.kappas]) * irreducible_boundary(M)**self.irr_boundaries
        
    def push_down_psis_one(self, mark):
        """
        Compute the intersection number by first pushing down one step using the string equation or the formula (1.7) in [AC96].
        
        Assume that there are only psis and irrs and kappas, and that we have already compensated for pulling back the kappas (i.e. that that kappa_classes in self are really pullbacks of kappa_classes.
        
        This returns a number, since it calls the intersect method itself.
        TODO:  Why?  Does that make sense.        
        """
        M = self.space.forget(mark)
        try:
            index = [psituple[0].mark == mark for psituple in self.psis].index(True) #find the psi class whose mark is being forgotten 
        except ValueError:
            index = None
        
        if index != None:            
            #this is because the mark we want to forget has a psi class, so the string equation does not apply.  We must use (1.7) of [AC96].
            new_kappas = self.kappas + [self.psis[index][1]-1]
            new_psis = list(self.psis)
            new_psis.remove(self.psis[index])
            m = prod([kappa_class(M, d) for d in new_kappas])*prod( [psi_class(M, psi_tuple[0].mark)**psi_tuple[1] for psi_tuple in new_psis]) * irreducible_boundary(M) ** self.irr_boundaries
            return intersect([M], m)
        else:  #the string equation applies (see (1.9) of [AC96])
            value = 0
            for psi_tuple in self.psis:
                value += intersect_monomial_not_product([M],self.monomial.decrease_return_new(psi_tuple[0]))  #optimization potential here!
                #notice that the psis will then not be on the right space... does it matter???
            return value
    

        
    def get_MgnLb_indexes(self):
        r"""
        Returns a list of indexes corresponding to Faber's "MgnLb.txt" program.  This is useful for automated testing purposes.
        """
        l = []
        for tclass, expon in self.monomial.decompose_monomial():
            l += [self.space.num(tclass)]*expon
        return l
        
    def get_a_point(self):
        """
        Returns a marked point that does not have a corresponding psi class if there is such, or any mark otherwise.
        """
        pick_from = list(self.space.marks.difference(set([psituple[1] for psituple in self.psis])))
        if len(pick_from) > 0:
            return pick_from[0]
        else:
            return self.psis[0][1]
         
        
        
class monomial_data_from_product(monomial_data):
    """
    This class is the same as monomial_data except that you have to manually call recieve.  This is useful when dealing with intersections on products, see intersect_monomial_product.
    """
    def __init__(self, space):
        """
        This does not parse the data of the monomial, you have call recieve manually.
        """
        monomial_data.__init__(self, space)
        
    def recieve(self, tclass, expon):
        """
        Notice that this version of recieve also updates self.monomial.
        """
        self.monomial *= tclass**expon
        monomial_data.recieve(self, tclass, expon)

#@breadth_first_tree     
def intersect(spaces, p, check_degree_for_user = False):
    """
    This is the main entry point.
    
    INPUT: 
     - spaces -- A list of Mgn objects that represents the spaces involved, i.e. we are computing an intersection on the product of all the spaces in this list.
     - p -- a polynomial in tautalogical ring classes (see the file TautRing3.py)
     - check_degree_for_user -- if this is true, the function should raise an exception instead of returning 0 if the polynomial contains a monomial of the wrong degree.
     
    OUTPUT:
     - The intersection number for the polynomial. 
    """    
    #print p, spaces#, p.expr
    result = 0
    if len(spaces) == 1:
        intersect_method = intersect_monomial_not_product
    else:
        intersect_method = intersect_monomial_product
    try:
        for m, coeff in p.monomials():
            result += coeff * intersect_method(spaces, m, check_degree_for_user)
        return result
    except AttributeError:
        if isinstance(p, int) or p in QQ:
            tot_dim = sum([space.dimension for space in spaces])
            if tot_dim == 0:
                return p
            else:
                return 0
        else:
            raise  

#@breadth_first_tree  
def intersect_monomial_product(spaces, p, check_degree = False):
    """
    Computed the top intersection number of a monomial on a product space.
    
    INPUT:
     - spaces -- A list of spaces involved in this intersection, i.e. we are computing the intersection on the product of the spaces in this list.
     - p -- A monomial (not a polynomial, and with no coefficient) to compute the intergral of.
     - check_degree -- If true, it should raise an error instead of returning 0 for the wrong degree.
    """
    datas = dict( [(space, monomial_data_from_product(space)) for space in spaces])
    for tclass, expon in p.decompose_monomial():
        datas[tclass.space].recieve(tclass, expon)
    
    ans = 1
    for space in spaces:
        if datas[space].vanishes_by_degree():
            return 0
            
    for space in spaces:
        base_case = datas[space].base_case()
        if base_case != None:
            ans *= base_case
        else:
            ans *= intersect_monomial_with_data(datas[space])
    
    return ans
    
class BadDegreeException(Exception):
    """
    This is the exception to be raised for a wrong degree.
    """
    pass
    
    
#@breadth_first_tree  
def intersect_monomial_not_product(space, m, check_degree = False):
    """
    Computes the intersection of a monomial on a space (not a product space).
    
    INPUT:
     - space -- A length 1 list (to be compatible with the signature of intersect_monomial_product) containing the space to compute the intersection on.
     - m -- A monomial to compute the integral of.
     - check_degree -- Raise an error instead of returning 0 for bad degree.
    """
    data = monomial_data(space[0], m)

        
    if data.vanishes_by_degree():
        if check_degree:
            raise BadDegreeException, "The monomial {0} has degree {1}, while the space {2} has dimension {3}.".format(m, data.degree(), repr(space[0]), space[0].dimension)            
        else:
            return 0
    
    base_case = data.base_case()
    if base_case != None:
        return base_case
    else:   
        return intersect_monomial_with_data(data)
    

@remember_convert_args(lambda data: data.key(), master_table)
#@breadth_first_tree#_MgnLb
def intersect_monomial_with_data(data):  
    """
    Computes the itegral based on data from a monomial_with_data object.  
    
    Does not check vanishing or base cases!!! (These should have already been checked)
    
    This is the main implementation of the algorithm.
    """ 
    if len(data.lambdas) != 0:
        to_convert, num_lamb = data.get_lambda()
        data.monomial.decrease_by(to_convert, num_lamb)
        return intersect([data.space], data.monomial * (to_convert.as_chern_chars())**num_lamb)

    if len(data.red_boundaries) != 0:
    
        p1, p2 = data.space.next_marks()
        
        pullback_by = data.get_red() 
        data.monomial.decrease(pullback_by)                   
        
        M1 = pullback_by.component1.add_marks(p1)
        M2 = pullback_by.component2.add_marks(p2)
        
        return Rational((1, pullback_by.degree_of_map)) * intersect( [M1,M2], prod([cl.pullback_red((M1,p1), (M2,p2))**expon for cl, expon in data.monomial.decompose_monomial()]) )
        
    if len(data.cherns) != 0:
            
        chern = data.get_chern()
        data.monomial.decrease(chern)
        value = 0
        # #######
        # Here we implement formula (13) of [Yan10].  Notice that we save a little work by taking advantage of symmety and multiply by 1/degree_of_map instead of 1/2.
        # #######
        for i in range(0, chern.degree): 
            ivalue = 0
            #do the red boundaries
            for psp1, psp2, psi_pair, degree_of_map in chern.get_red_boundaries_and_psis(i): 
                ivalue += Rational((1, degree_of_map)) * intersect([psp1[0], psp2[0]], psi_pair * prod([cl.pullback_red(psp1,psp2)**expon for cl, expon in data.monomial.decompose_monomial()]) )
            
            #do the irr boundary
            (irr_pullback_space, p1, p2), psi_pair = chern.get_irr_boundary_and_psis(i)

            ivalue += Rational((1,2)) * intersect([irr_pullback_space], psi_pair * prod([cl.pullback_irr(irr_pullback_space, p1, p2)**expon for cl, expon in data.monomial.decompose_monomial()]) )
            
            #alternating
            value += (-1)**i * ivalue
        

        value += intersect([data.space], chern.get_kappa()*data.monomial)
        value *= chern.get_coeff()
        
        return value
        
    if data.irr_boundaries > 1:
        #special relation in Faber's paper
        m = max([data.space.genus, 3*data.space.genus - 3])
        if data.irr_boundaries >= m + 1:
            return 0
        # 
    
        if data.space.not_pushed_down(): #use this to avoid self-intersections of irrs on spaces with marked points, speeds it up a bit.
            if len(data.kappas) != 0:
                new_poly = data.monomial
                p = data.get_a_point()
                for d in set(data.kappas):
                    new_poly = new_poly.subs(kappa_class(data.space, d), kappa_class(data.space,d) + psi_class(data.space,p)**d)
                ans = 0
                for m, coef in new_poly.monomials():
                    mdata = monomial_data(data.space,m)
                    ans += coef * mdata.push_down_psis_one(p)  
                return ans
            else:
                return intersect([data.space.pushed_down()], data.push_down_psis())
                
            
    if data.irr_boundaries != 0: #pullback is irreducible boundary
        p1, p2 = data.space.next_marks()   
    
        pullback_by = irreducible_boundary(data.irr_space)
        data.monomial.decrease(pullback_by)
        
        M = Mgn(data.space.genus - 1, data.space.marks.union([p1,p2]) ) 
        
        return Rational((1,2)) * intersect([M], prod([cl.pullback_irr(M, p1, p2)**expon for cl, expon in data.monomial.decompose_monomial()]))    
        

    elif len(data.kappas) != 0:
        p = data.space.next_mark()
        M = data.space.add_marks(p)
        psi_new = psi_class(M,p)
        return intersect([M], psi_new**(data.kappas[0] + 1) * prod( [psi.change_space(M)**expon for psi, expon in data.psis]) * prod( [kappa_class(M, d) - psi_new**(d) for d in data.kappas[1:]]) )  
  
    else:
        return psi_intersect(data.space.genus, data.space.n, data.psi_exp())