1 import scipy
    2 import scipy.linalg
    3 import math
    4 
    5 """
    6 
    7     A code to simplify calculation of BKL billiard systems based on 
    8     three-dimensional coset models.
    9     
   10     By Daniel H. Wesley 
   11     D.H.Wesley at damtp.cam.ac.uk
   12     
   13 	If this code is used in your research, please cite:
   14 	
   15 	"Coxeter group structure of cosmological billiards on compact spatial
   16 	 manifolds" Marc Henneaux, Daniel Persson, Daniel H. Wesley (to appear)
   17 
   18 """
   19 
   20 
   21 class Billiard:
   22 
   23     def __init__(self, d, forms, dil=True ):
   24         """
   25             d:      spatial dimensions
   26             forms:  a list of tuples (p, lamb, label)
   27             dil:    True if dilaton, False if no dilaton
   28 
   29             Given this information, the class constructs the gravitational,
   30             symmetry, and p-form walls corresponding to this billiard.  The
   31             walls themselves are stored as 1D SciPy arrays for easy manip-
   32             ulation.
   33 
   34             The p-forms must be labelled to make things easier:
   35             Labels are just strings that are carried along for the ride.  
   36             Ordering of the labels is important.  For
   37             p-forms, the letter "E" or "B" is prepended to the label given
   38             depending on whether the electric or magnetic components are
   39             being discussed.
   40             
   41             All of the couplings and so on are assumed to be in the Einstein
   42             frame.  This allows the study of theories with and without dilatons
   43             in a single framework.  Note that only one dilaton is currently
   44             supported.
   45 
   46             The caller has to manually set the dominant walls.  This can be
   47             done by just setting which labelled walls one wants to have in
   48             the dominant wall set.
   49         """
   50 
   51         self.d = d
   52         self.forms = forms
   53         self.dil = dil
   54 
   55         # This has to be done manually, since it isn't clear
   56         # algorithmically how one selects the dominant walls.
   57         self.domWallMatrix = None
   58 
   59         # This is a dictionary holding the map between the label for
   60         # and the wall itself.  Newly created walls should register
   61         # themselves here.
   62         self.walls = { }
   63 
   64         self.domWallLabels = []
   65         self.domWalls = []
   66 
   67         self.coweightMatrix = None
   68         
   69         self.makeWalls()
   70 
   71     def makeWalls(self):
   72     	"""
   73     	This function makes all of the wall types.
   74     	"""
   75 
   76         self.makeSWalls()
   77         self.makeEWalls()
   78         self.makeBWalls()
   79         self.makeGWalls()
   80 
   81 
   82     def makeSWalls(self):
   83     	"""
   84     	Make the symmetry walls, starting from beta^2-beta^1 up to beta^d-beta^(d-1)
   85     	"""
   86 
   87         # These go from w2 = \beta^2 - \beta^1 all the way
   88         # to \beta^d - \beta^{d-1}
   89 
   90         sWalls = []
   91         
   92         for j in range(2,self.d+1):
   93             new_wall = self.makeSWall(j)
   94             new_wall_label = self.makeSymName(j)
   95             sWalls.append( new_wall )
   96             self.walls[ new_wall_label ] = new_wall
   97             
   98         self.sWalls = sWalls
   99 
  100 
  101     def makeSWall(self, j):
  102         """ Make the symmetry wall with \beta^j - (etc) """
  103 
  104         if ( (j>self.d) or (j<2) ):
  105             return None
  106 
  107         if self.dil:
  108             wall = [0.]  # first component is always for the scalar
  109         else:
  110             wall = []
  111         
  112         niz = j-2 # digits in 1...(j-1)
  113         if (niz > 0):
  114             wall = wall + ( [0.] * niz )
  115             
  116         wall = wall + ( [-1., 1.] )
  117         
  118         nfz = self.d-j # digits in j ... d
  119         if (nfz > 0):
  120             wall = wall + ( [0.] * nfz )
  121                 
  122         return scipy.array(wall)
  123 
  124     
  125     def makeEWalls(self):
  126     	"""
  127     	Make the electric wall set
  128     	"""
  129         
  130         eWalls = []
  131         
  132         for f in self.forms:
  133             # We have to unpack the tuple, since makeEWall only
  134             # cares about "p" and "d" here, while we care about the
  135             # label as well.
  136             
  137             new_wall = self.makeEWall( f[:2]  )
  138             eWalls.append( new_wall )
  139 
  140             new_wall_label = "E" + f[2]
  141             self.walls[ new_wall_label ] = new_wall
  142 
  143         self.eWalls = eWalls
  144 
  145 
  146     def makeEWall(self, ft):
  147     	"""
  148     	Make an individual electric wall. 
  149     	The second argument is ft=(p,lamb)
  150     	"""
  151 
  152         (p,lamb) = ft
  153 
  154         if self.dil:
  155             wall = [ 0.5*lamb ]
  156         else:
  157             wall = []
  158 
  159         if (p > 0):
  160             wall = wall + ( [1.] * p )
  161         
  162         if (self.d-p > 0):
  163             wall = wall + ( [0.] * (self.d-p) )
  164 
  165         return scipy.array(wall)
  166 
  167     
  168     def makeBWalls(self):
  169 
  170         bWalls = []
  171 
  172         for f in self.forms:
  173             new_wall = self.makeBWall( f[:2] )
  174             bWalls.append( new_wall )
  175 
  176             new_wall_label = "B" + f[2]
  177             self.walls[ new_wall_label ] = new_wall
  178 
  179         self.bWalls = bWalls
  180 
  181 
  182     def makeBWall(self, ft):
  183 
  184         (p,lamb) = ft
  185 
  186         if self.dil:
  187             wall = [ -0.5*lamb ]
  188         else:
  189             wall = []
  190 
  191         if (self.d - p - 1 > 0):
  192             wall = wall + [1.] * (self.d - p - 1)
  193 
  194         wall = wall + [0.] * (p+1)
  195 
  196         return scipy.array(wall)
  197 
  198     
  199     def makeGWalls(self):
  200 
  201         gwall = self.makeGWall()
  202         self.gWalls = [ gwall ]
  203         self.walls[ "G" ] = gwall
  204 
  205 
  206     def makeGWall(self):
  207 
  208         if self.dil:
  209             wall = [ 0. ]
  210         else:
  211             wall = []
  212 
  213         wall = wall + [2.]
  214 
  215         if ( self.d-3 > 0):
  216             wall = wall + [1.]*(self.d-3)
  217 
  218         wall = wall + [0.]*2
  219 
  220         return scipy.array(wall)
  221         
  222 
  223     def wallDot(self, w1, w2 ):
  224         """ 
  225         	Compute the inner product of two walls 
  226         """
  227 
  228         if self.dil:
  229             dil1 = w1[0]
  230             dil2 = w2[0]
  231             x1 = w1[1:]
  232             x2 = w2[1:]
  233         else:
  234             dil1 = 0.
  235             dil2 = 0.
  236             x1 = w1
  237             x2 = w2
  238         
  239         dot = x1.sum() * x2.sum()
  240         dot = dot * -1./float(self.d-1.)
  241 
  242         dot = dot + (x1*x2).sum()
  243         dot = dot + dil1*dil2
  244 
  245         return dot
  246 
  247     
  248     def wallNorm(self, w):
  249         return self.wallDot(w,w)
  250 
  251 
  252     def getNonSymWalls(self):
  253     	"""
  254     		Return a list of labels for walls that do not correspond to
  255     		symmetry walls.  
  256     		
  257     		Right now this is just implemented by checking the first character
  258     		of the wall label.  Maybe in the future there will be a more 
  259     		sophisticated way of doing this.
  260     	"""
  261     	
  262     	return filter( lambda s : s[0] != 'S', self.walls.keys() )    
  263     
  264     
  265     def setDomWalls(self, domWallLabels ):
  266         """
  267             domWallLabels: a list of wall labels (strings)
  268 
  269             This sets which walls one wants in the dominant wall set.
  270             One simply chooses which (non-symmetry) walls are desired.
  271             The symmetry walls are automatically included.
  272             
  273             In the end this function also calculates the coweights
  274             and their norms.
  275         """
  276 
  277         # Implementation note: the symmetry walls are found by just
  278         # looking at the list of self.sWalls
  279 
  280         # Now we have to copy over the desired walls without losing track
  281         # of the labels.  The wrinkle is that to form the dominant wall array
  282         # we have to change the arrays to lists, which we can then mold into
  283         # an array.
  284 
  285         # Implementation note: these have to be lists, because we need to keep
  286         # ordering information about them!
  287 
  288         self.domWallLabels = []
  289         self.domWalls = []
  290 
  291         # First add the requested walls
  292         
  293         # This seems perverse, but the idea is that the caller has requested that
  294         # the walls in domWallLabels be properly added to self.domWallLables
  295         for wl in domWallLabels:
  296             self.domWallLabels.append(wl)
  297             self.domWalls.append( self.walls[ wl ] )
  298 
  299         # Now add on the symmetry walls
  300 
  301         for j in range(len(self.sWalls)):
  302             # Can't reverse the lookup in a dict
  303             self.domWallLabels.append( self.makeSymName(j+2) )
  304             self.domWalls.append( self.sWalls[j] )
  305 
  306         # Now the dominant walls are all set.  Make the wall matrix
  307 
  308         tmp = [] 
  309         for w in self.domWalls:
  310             tmp.append( w.tolist() )
  311 
  312         self.domWallMatrix = scipy.asarray(tmp)
  313 
  314         # Finally, to keep everything straight, we have to also calculate
  315         # the coweights so all is synchronized.
  316 
  317         self.calcCoweights()
  318         
  319         # And we have to compute the Cartan matrix.
  320         # Potentially, this is the transpose of the Cartan matrix
  321         
  322         A_mtrx = []
  323         for dw1 in self.domWalls:
  324         	A_row = []
  325         	for dw2 in self.domWalls:
  326         		A_row = A_row + [ 2. * self.wallDot( dw1, dw2 )\
  327         		                 / self.wallNorm( dw1 )  ]
  328         	A_mtrx.append( A_row )
  329         
  330         self.cartanMatrix = scipy.asarray(A_mtrx)
  331         self.cartanMatrixEigenvalues = scipy.linalg.eigvals(self.cartanMatrix)
  332         self.cartanMatrixEigenvalues = self.cartanMatrixEigenvalues.real
  333 
  334 
  335     def calcCoweights(self):
  336 
  337         (size, size1) = self.domWallMatrix.shape
  338 
  339         if (size != size1):
  340             print "Billiard.calcCoweights: matrix not square"
  341             self.coweights = None
  342             self.coweightNorms = None
  343             return
  344 
  345         tmp = scipy.linalg.inv(self.domWallMatrix)
  346         self.coweightMatrix = tmp.transpose()
  347 
  348         # Now we have the coweights.  At this point there is just the
  349         # task of unpacking the coweights and computing their norms.
  350 
  351         # By symmetry, let's create a dictionary called coweights that
  352         # holds these labels.
  353 
  354         # By construction, the domWallLabels are exactly those of the
  355         # coweights.  So we can just re-use them.
  356 
  357         self.coweights = {}
  358         self.coweightNorms = {}
  359 
  360         for n in range(size):
  361             cw = self.coweightMatrix[n]
  362             
  363             self.coweights[ self.domWallLabels[n] ] = cw 
  364             self.coweightNorms[ self.domWallLabels[n] ] = \
  365                                 self.coweightDot(cw,cw)
  366 
  367 
  368     def coweightDot(self, cw1, cw2):
  369         """
  370             Calculate the dot product of two coweights.
  371         """
  372         
  373         if self.dil:
  374             dil1 = cw1[0]
  375             dil2 = cw2[0]
  376             x1 = cw1[1:]
  377             x2 = cw2[1:]
  378         else:
  379             dil1 = 0.
  380             dil2 = 0.
  381             x1 = cw1
  382             x2 = cw2
  383         
  384         dot = -1. * x1.sum() * x2.sum()
  385         dot = dot + (x1*x2).sum()
  386         dot = dot + dil1*dil2
  387 
  388         return dot
  389         
  390     def isChaotic(self):
  391         """
  392             Return whether the theory is chaotic or not.
  393         """
  394 		
  395         # in some cases coweights are not defined
  396         ct = self.countCoweightTypes()
  397 		
  398         if not ct:
  399             return None
  400 		
  401         (nT, nN, nS) = self.countCoweightTypes()
  402 
  403         if ( (nT>0) and (nS>0) ):
  404             return False
  405 
  406         return True
  407 
  408 
  409     def isNonChaotic(self):
  410         
  411         if self.isChaotic == None:
  412             return None
  413         
  414         return (not self.isChaotic() )
  415 
  416     
  417     def countCoweightTypes(self):
  418 		"""
  419 			Return the number of timelike, null, spacelike coweights.
  420 
  421 			This assumes you've already set the dominant walls, of course!
  422 		"""
  423 		
  424 		# coweightNorms have failed (error checking)
  425 		if not self.coweightNorms:
  426 			return None
  427 		
  428 		numTimelike = 0
  429 		numNull = 0
  430 		numSpacelike = 0
  431 		
  432 		tol = 1.e-6
  433                         
  434 		for n in self.coweightNorms.values():
  435 
  436 			if n > tol:
  437 				numSpacelike += 1
  438 			elif (n+tol) > 0.:
  439 				numNull += 1
  440 			else:
  441 				numTimelike += 1
  442 
  443 		return (numTimelike, numNull, numSpacelike)
  444 	    
  445     def __str__(self):
  446     	s =     "Original model       " + self.__class__.__name__ + "\n"
  447     	s = s + "Dominant walls       " + str(self.domWallLabels) + "\n"
  448     	s = s + "Coweights (T,N,S)    " + str(self.countCoweightTypes()) + "\n"
  449     	s = s + "Chaotic?             " + str(self.isChaotic()) + "\n"
  450     	s = s + "\n"
  451     	s = s + self.dynkinString()
  452     	return s
  453 	
  454     def printDynkin(self):
  455 		print
  456 		print self.dynkinString()
  457     
  458     def dynkinString(self):
  459         """
  460             Print a Dynkin-ish diagram.
  461 
  462             Basically, the cheat is that we always know that the symmetry
  463             walls form the backbone.  So just print out a list of the symm
  464             walls, together with the walls that have a nonzero inner product,
  465             and the number of lines connecting them.
  466 
  467             Something like --- =>= etc.
  468 
  469             Option to only print the dominant ones, perhaps.
  470         """
  471 
  472         s = ""
  473 
  474         nonSymsLabels = [e for e in self.domWallLabels if e[0] != 'S' ]
  475 
  476         # Keep track of the nodes that we've printed
  477         isPrinted = {}
  478         for nsl in nonSymsLabels:
  479             isPrinted[nsl] = False
  480         
  481         for j in range(2,self.d+1):
  482             symLabel = self.makeSymName(j)
  483             
  484             # If we're not the first, then we must have a symmetry node
  485             # already above us, which we should connect with the gravity line
  486             if (j != 2):
  487                 s = s + "\n      |"
  488 
  489             # Do something clever to only loop through the non-symmetry
  490             # walls.  Then we must define a function that gives the number
  491             # of Dynkin links between two nodes according to the usual
  492             # rules of Dynkin diagrams.
  493             
  494             if ( j != 2):
  495                 s = s + "\n"
  496             
  497             s = s + "  %3s o" % symLabel
  498 
  499             # Now print all of the non-symmetry walls that have nonzero IP
  500             # with this wall.
  501 
  502             for nsLabel in nonSymsLabels:
  503                 d = self.wallDot( self.walls[symLabel], self.walls[nsLabel] )
  504                 nsNorm = self.wallNorm( self.walls[nsLabel] )
  505                 if (round(d) != 0):
  506                     s = s + self.makeDynkinConnect(2., nsNorm, d) + "o"
  507                     s = s + " %s" % nsLabel
  508                     # mark it printed
  509                     isPrinted[nsLabel] = True
  510 
  511             # print s
  512             #s = s + "\n"
  513 
  514         # There should also be a check to report any additional linkages
  515         # that are not just symmetry wall connected with another wall
  516         
  517         for j in range(len(nonSymsLabels)):
  518             for k in range(j+1,len(nonSymsLabels)):
  519                 w1 = self.walls[nonSymsLabels[j]]
  520                 w2 = self.walls[nonSymsLabels[k]]
  521                 d = self.wallDot( w1, w2 )
  522                 if (round(d) != 0):
  523                     s = s + "\n\n%5s o%so %s" % (nonSymsLabels[j],\
  524                         self.makeDynkinConnect( self.wallNorm(w1), \
  525                                                 self.wallNorm(w2), d ),\
  526                         nonSymsLabels[k])
  527                     isPrinted[nonSymsLabels[j]]=True
  528                     isPrinted[nonSymsLabels[k]]=True
  529 
  530         # Finally, there may be nodes that haven't beeen printed anywhere!
  531         # Also I want to avoid a terminating carriage return.
  532         for nsl in nonSymsLabels:
  533             if not isPrinted[nsl]:
  534                 s = s + "\n\n      o %s" % nsl
  535         
  536         return s
  537 
  538 
  539     def makeDynkinConnect(self, norm1, norm2, dot):
  540         
  541         # New line algorithm that avoids some rare issues
  542         
  543         iN1 = int(round(norm1))
  544         iN2 = int(round(norm2))
  545         
  546         norm = norm1
  547         
  548         if norm2 < norm1:
  549         	norm = norm2
  550         
  551         iLines = int( round( -2. * dot / norm ) )
  552         
  553         if iLines == 1:
  554         	line = "-"
  555         elif iLines == 2:
  556         	line = "="
  557         elif iLines == 3:
  558         	line = "3"
  559         elif iLines == 4:
  560         	line = "4"
  561         else:
  562         	line = "?"
  563 
  564         if (dot>1e-3):
  565             line = "+"
  566 
  567         if (iN1 > iN2):
  568             mid = ">"
  569         elif (iN1 < iN2):
  570             mid = "<"
  571         else:
  572             mid = line
  573 
  574         return line+mid+line
  575             
  576 
  577     def makeSymName(self, j):
  578         return "S" + str(j)
  579         
  580 
  581 
  582 hetBill = Billiard(9, [ (1,-1./math.sqrt(2.),"F2"), (2,-1.*math.sqrt(2.),"H3") ] )
  583 
  584 
  585 class Compact:
  586     """
  587     Defines a compactification of a wall system.
  588     
  589     This class does very little computation, and really just defines an interface for
  590     setting the zero betti numbers.
  591     """	
  592 
  593     def __init__(self, d):
  594         self.d=d
  595         self.betti=[1]*(self.d+1)
  596 
  597 
  598     def setZeroBetti(self, zeroB):
  599         """
  600             Sets a given list of Betti numbers to zero.
  601             Enforces the duality.
  602             
  603             This should be called in any descendendant classes
  604             since it helps with error-checking.
  605         """
  606         
  607         self.betti = [1] * (self.d+1)
  608         #print "Compact.setZeroBetti"
  609         for zb in zeroB:
  610             self.betti[zb] = 0
  611             # Enforce the duality
  612             self.betti[self.d-zb] = 0
  613 
  614     def getBettis(self):
  615         return self.betti
  616     
  617     def _prepCompactRules(self, billiard):
  618     	"""
  619     		Intented to be a helper function.  In each class that implements the
  620     		Compact interface, there is some kind of decision structure that decides
  621     		which walls are dominant based on 1. the compactification deletions, and
  622     		2. some kind of dominance heirarchy.
  623     		
  624     		This function does the beginning bit of this process.  It creates some 
  625     		dictionaries, keyed on the non-symmetry walls.  dom keeps track of whether
  626     		a wall is dominant.  ext keeps track of whether it exists.
  627     		
  628     		This function then takes care of all of the compactification rules, and
  629     		passes a tuple to the caller with (wks,ext,dom) where wks are the wall
  630     		names.
  631     		
  632     		To do this, it needs to know about the Billiard object so that it can get
  633     		the ps.
  634     		
  635     		This function is considered private because it assumes implementation 
  636     		details of the other stuff.
  637     	"""
  638     	
  639     	dom = {}    # whether the wall is dominant (untouched here except to init)
  640     	ext = {}    # whether the wall exists
  641     	sbn = {}    # to which betti is this wall sensitive?
  642     	wks = billiard.getNonSymWalls()
  643     	
  644     	# to which betti number is a given form sensitive?
  645     	for (p,lamb,nom) in billiard.forms:
  646     		sbn[ "E"+nom ] = p
  647     		sbn[ "B"+nom ] = p+1
  648     	
  649     	# gravity walls are not symmetry walls
  650     	sbn[ "G" ] = -1
  651     	
  652     	for wk in wks:
  653     		dom[wk] = True
  654     		ext[wk] = True
  655     	
  656     	# Now remove walls that are excluded by compactification rules
  657     	
  658     	bs = self.betti
  659     	
  660     	for wk in wks:
  661     		if not self.betti[sbn[wk]]:
  662     			ext[wk] = False
  663     	ext["G"] = True
  664     	
  665     	return (wks,ext,dom)
  666     
  667     def _postCompactRules(self, billiard, ext, dom):
  668     	"""
  669     		After the dominance heirarchy logic, set the dominant walls.
  670     	"""
  671     	
  672     	dwl = []
  673     	for w in dom.keys():
  674     		if dom[w] and ext[w]:
  675     			dwl.append(w)
  676 
  677     	#print "Compact.__postCompactRules: setting to dom ",dwl
  678             
  679     	billiard.setDomWalls(dwl)
  680 
  681 
  682 
  683 class cosetA(Billiard,Compact):
  684     """
  685 
  686     The billiard based on the An groups.  This yields a wall system defined
  687     by An^^ = AE_{n+2}.  Compactification does not affect this at all.
  688 
  689     """
  690 
  691     def __init__(self, n):
  692         self.n = n
  693         Billiard.__init__(self, n+2, (), False )
  694         Compact.__init__(self, n+2)
  695         self.setDomWalls( [ "G" ] )
  696         
  697     def setZeroBetti(self, zeroB):
  698         Compact.setZeroBetti(self, zeroB)
  699         print "bill.cosetAn.setZeroBetti has no effect"
  700 
  701 
  702 class cosetB(Billiard,Compact):
  703     """
  704 
  705     The billiard for the Bn groups.  The Bn coset in D=3 oxidizes to
  706     D=n+2, unless n=3 in which case it oxidizes to D=6.
  707 
  708     one obtains the following forms:
  709 
  710     p    lamb           name
  711 
  712     1    a sqrt(2)/2    F
  713     2    a sqrt(2)      G
  714 
  715     where a^2 = 8/n.  (So far as I know the sign does not matter here)
  716     
  717     When n=3 in one obtains
  718 
  719     2    0 (no dilaton) G  which is self-dual
  720 
  721     """
  722 
  723     def __init__(self, n):
  724         self.n = n
  725         sqrt=scipy.sqrt
  726         lamb1 = sqrt(8./float(n)) * sqrt(2.) / 2.
  727         lamb2 = sqrt(8./float(n)) * sqrt(2.)
  728 
  729         if (n != 3):
  730             Billiard.__init__( self, n+1, [ (1, lamb1, "F2"), \
  731                                             (2, lamb2, "G3") ] )
  732             Compact.__init__(self, n+1)
  733             self.setDomWalls( [ "EF2", "BG3" ] )
  734         else:
  735         	# In this case, the maximal oxidation is to a higher dimension.
  736             Billiard.__init__( self, 5, [ (2, 0., "G3") ], False )
  737             Compact.__init__( self, 5)
  738             self.setDomWalls( [ "BG3" ] )
  739 
  740     def setZeroBetti(self, zeroB):
  741         Compact.setZeroBetti(self,zeroB)
  742         
  743         #print "bill.cosetB.setZeroBetti ", zeroB
  744         
  745         if (self.n==3):
  746             return self.setZeroBettiSpecial()
  747         
  748         (wkl,ext,dom) = self._prepCompactRules(self)
  749         
  750         # Construction rules
  751         
  752         # Example: if EF2 and BG3 exist, then BF2 is not dominant.
  753         if ext['EF2'] and ext['BG3']:
  754             dom['BF2']=False
  755 
  756         if ext['EF2']:
  757             dom['EG3']=False
  758 
  759         if ext['BG3'] and ext['EF2']:
  760             dom['G']=False
  761 
  762         if ext['G'] and ext['BG3']:
  763             dom['BF2']=False
  764 
  765         if ext['EG3'] and ext['BG3']:
  766             dom['G']=False
  767 
  768         if ext['EF2'] and ext['BF2']:
  769             dom['G']=False
  770 
  771         self._postCompactRules(self,ext,dom)
  772 
  773 
  774     def setZeroBettiSpecial(self):
  775 		"""
  776     		In this case, the oxidation can proceed further, to D=6
  777     		where there is no dilaton.
  778     		
  779     		Oddly, here the electric and magnetic walls are the same.
  780     		The reason here is that G3 is self-dual.
  781 		"""
  782 		
  783 		print "bill.cosetB.setZeroBettiSpecial"
  784 		
  785 		(wkl,ext,dom) = self._prepCompactRules(self)
  786 		
  787 		# The dominance heirarchy
  788 		
  789 		if ext['BG3']:
  790 			dom['G'] = False
  791 		
  792 		# The convention we take is that BG3 is the wall.
  793 		
  794 		dom['EG3'] = False
  795 				
  796 		self._postCompactRules(self, ext, dom)
  797 
  798 
  799 class cosetC(Billiard,Compact):
  800     """
  801     The billiard for the Cn groups.  The Cn cosets do not oxidize beyond D=4.
  802     Also, there are many dilatons.   Therefore they are kind of a special case
  803     that has to be handled carefully.  
  804     
  805     Fortunately this will be easy because of
  806     the way in which Python handles inhertiance!
  807     """
  808     
  809     def __init__(self, n):
  810     	self.d = 3
  811     	self.n = n
  812 
  813         # Here I differ from usual practice
  814         self.dil = n-1
  815 
  816         # This has to be done manually, since it isn't clear
  817         # algorithmically how one selects the dominant walls.
  818         self.domWallMatrix = None
  819 
  820         # This is a dictionary holding the map between the label for
  821         # and the wall itself.  Newly created walls should register
  822         # themselves here.
  823         self.walls = { }
  824 
  825         self.domWallLabels = []
  826         self.domWalls = []
  827         self.coweightMatrix = None
  828         
  829         self.makeWalls()
  830         
  831         dw = []
  832         for wk in self.walls.keys():
  833         	if wk != "G" and wk[0] != "S":
  834         		dw.append(wk)
  835         self.setDomWalls( dw )
  836     
  837     def _padWall(self, walla ):
  838     	"""
  839     		Pad the wall created by another function to include all the
  840     		dilatons
  841     	"""
  842     	
  843     	walll = walla.tolist()
  844     	walll = [0.] * (self.dil-1) + walll
  845     	return scipy.array(walll)
  846     
  847     
  848     def makeSWall(self,j):
  849     	"""
  850     		The regular version does all right, but only has one dilaton.
  851     	"""
  852     	return self._padWall( Billiard.makeSWall(self,j) )
  853     
  854     def makeGWall(self):
  855     	return self._padWall( Billiard.makeGWall(self) )
  856     	
  857     
  858     def setZeroBetti(self, zeroB):
  859     	"""
  860     		Again, a departure from normalcy
  861     	"""
  862     	
  863     	Compact.setZeroBetti(self, zeroB)
  864     	
  865     	b1zero = False
  866     	
  867     	for b in zeroB:
  868     		if b == 1 or b == 2:
  869     			b1zero = True
  870     	
  871     	if b1zero:
  872     		dw = []
  873     		for wk in self.walls.keys():
  874     			if wk[0] != "S" and wk[0] != "B":
  875     				dw.append(wk)
  876     		self.setDomWalls( dw )	
  877     
  878     def makeEWalls(self):
  879     	"""
  880     	Thuis function needs to make both the walls and their labels.  I'm 
  881     	going to cheat here, because all of the relevant walls here are either
  882     	dominant or don't exist.  These are all axion electric walls.  So:
  883     	"""
  884     	
  885     	x = math.sqrt(2.)/2.
  886     	
  887     	self.eWalls = []
  888     	
  889     	# A unique wall
  890     	curr_wall = [0.] * (self.d + self.n - 1)
  891     	curr_wall[0] = math.sqrt(2.)
  892     	self.eWalls.append(curr_wall)
  893     	self.walls[ "E%d0" % self.n ] = scipy.asarray(curr_wall)
  894     	
  895     	for j in range(2,self.n):
  896     		curr_wall = [ 0. ] * (self.d + self.n - 1)
  897     		curr_wall[self.n  -j] = x
  898     		curr_wall[self.n-1-j] = -1. * x
  899     		self.eWalls.append( curr_wall )
  900     		label = "EX%d0" % j
  901     		self.walls[ label ] = scipy.asarray(curr_wall)
  902     
  903     def makeBWalls(self):
  904     	# A unique wall
  905     	curr_wall = [0.] * (self.d + self.n - 1)
  906     	curr_wall[self.n-2] = -1. * math.sqrt(2.)/2.
  907     	curr_wall[self.n-1] = 1.
  908     	self.bWalls = [ curr_wall ]
  909     	self.walls[ "BA1" ] = scipy.asarray(curr_wall)
  910     
  911     def wallDot(self, w1, w2 ):
  912     	# Just remove everything but the last dilaton entry.  There are n-1
  913     	# dilatons, so this means
  914     	n = self.n
  915     	d = self.d
  916     	ndils = self.dil
  917     	
  918     	dils1 = w1[:ndils]
  919     	dils2 = w2[:ndils]
  920     	bets1 = w1[ndils:]
  921     	bets2 = w2[ndils:]
  922     	
  923     	dot  = sum( dils1*dils2 )
  924     	dot += -1. * bets1.sum()*bets2.sum()/float(self.d-1)
  925     	dot += (bets1*bets2).sum()
  926     	
  927     	return dot
  928 
  929     def coweightDot(self, cw1, cw2):
  930         """
  931         Calculate the dot product of two coweights.
  932         """
  933                 
  934         # Just remove everything but the last dilaton entry.  There are n-1
  935     	# dilatons, so this means
  936     	n = self.n
  937     	d = self.d
  938     	ndils = self.dil
  939     	
  940     	dils1 = cw1[:ndils]
  941     	dils2 = cw2[:ndils]
  942     	bets1 = cw1[ndils:]
  943     	bets2 = cw2[ndils:]
  944     	    	
  945     	dot = (dils1*dils2).sum()        
  946     	dot = dot -1. * bets1.sum() * bets2.sum()
  947     	dot = dot + (bets1*bets2).sum()
  948     	    	
  949     	return dot
  950 
  951 
  952 class cosetD(Billiard,Compact):
  953 	"""
  954 
  955 	The billiard for the Dn groups.  These oxidize to D=n+2 dimensions where
  956 	one obtains the following form fields.
  957 	
  958 	p    lamb          name
  959 	
  960 	2     a sqrt(2)    H
  961 	
  962 	where a^2=8/n
  963 	
  964 	"""
  965 	
  966 	def __init__(self, n):
  967 		self.n = n
  968 		sqrt = scipy.sqrt
  969 		lamb = sqrt(8./float(n)) * sqrt(2.)
  970 		
  971 		Billiard.__init__( self, n+1, [ (2, lamb, "H3") ], True )
  972 		Compact.__init__( self, n+1 )
  973 		
  974 		self.setDomWalls( [ "EH3", "BH3" ] )
  975 	
  976 		
  977 	def setZeroBetti(self, zeroB):
  978 		Compact.setZeroBetti(self, zeroB)
  979 		
  980 		#print "bill.cosetD.setZeroBetti: ",zeroB
  981 		
  982 		(wkl,ext,dom) = self._prepCompactRules(self)
  983 		
  984 		# Dominance heirarchy
  985 		
  986 		if ext["BH3"] and ext["EH3"]:
  987 			dom["G"] = False
  988 		
  989 		self._postCompactRules(self,ext,dom)
  990 
  991 
  992 class cosetE6(Billiard,Compact):
  993 	
  994 	def __init__(self):
  995 		
  996 		root2 = scipy.sqrt(2.)
  997 		
  998 		Billiard.__init__(self, 7, [ (0, 2.*root2, "X1"), \
  999 		                             (3, -1.*root2, "G4" ) ], True )
 1000 		Compact.__init__(self, 7 )
 1001 		
 1002 		self.setDomWalls( [ "EX1", "EG4" ] )
 1003 		
 1004 	
 1005 	def setZeroBetti( self, zeroB ):
 1006 		Compact.setZeroBetti( self, zeroB )
 1007 		
 1008 		#print "bill.cosetE6.setZeroBetti: ", zeroB
 1009 		
 1010 		(wkl, ext, dom) = self._prepCompactRules(self)
 1011 		
 1012 		# all bs nonzero
 1013 		if ext["EX1"] and ext["EG4"]:
 1014 			dom["BX1"] = False
 1015 			dom["BG4"] = False
 1016 			dom["G"] = False
 1017 		
 1018 		self._postCompactRules(self, ext, dom)
 1019 
 1020 
 1021 class cosetE7(Billiard, Compact):
 1022 	
 1023 	def __init__(self):
 1024 		lamb = 2. * math.sqrt(2./7.)
 1025 		Billiard.__init__( self, 8, \
 1026 			               [ (1, -2.*lamb, "F2"), (3, lamb, "G4") ], True )
 1027 		Compact.__init__( self, 8 )
 1028 		self.setDomWalls( [ "EF2", "EG4" ] )
 1029 	
 1030 	def setZeroBetti( self, zeroB ):
 1031 		Compact.setZeroBetti( self, zeroB )
 1032 		#print "bill.cosetE7.setZeroBetti: ", zeroB
 1033 		(wkl, ext, dom) = self._prepCompactRules(self)
 1034 		
 1035 		# all bs nonzero
 1036 		if ext["EF2"] and ext["EG4"]:
 1037 			dom["BF2"] = False
 1038 			dom["BG4"] = False
 1039 			dom["G"] = False
 1040 		
 1041 		if ext["EG4"] and ext["BG4"]:
 1042 			dom["BF2"] = False
 1043 			dom["G"] = False
 1044 		
 1045 		if ext["EF2"] and ext["BF2"]:
 1046 			dom["G"] = False
 1047 		
 1048 		self._postCompactRules(self, ext, dom)
 1049 
 1050 	
 1051 class cosetE8( Billiard, Compact ):
 1052 	
 1053 	def __init__(self):
 1054 		Billiard.__init__( self, 10, [ ( 3, 0., "G4" ) ], False )
 1055 		Compact.__init__( self, 10 )
 1056 		self.setDomWalls( [ "EG4" ] )
 1057 	
 1058 	def setZeroBetti( self, zeroB ):
 1059 		Compact.setZeroBetti( self, zeroB )
 1060 		( wkl, ext, dom ) = self._prepCompactRules( self )
 1061 		
 1062 		if ext[ "EG4" ]:
 1063 			dom[ "BG4" ] = False
 1064 		
 1065 		if ext["EG4"] and ext["BG4"]:
 1066 			dom["G"] = False
 1067 		
 1068 		self._postCompactRules( self, ext, dom )
 1069 		
 1070 		
 1071 class cosetF4( Billiard, Compact ):
 1072 	
 1073 	def __init__(self):
 1074 		Billiard.__init__( self, 5, [ (0, 2., "X1"), (1, 1., "F+2"), \
 1075 									  (1, -1., "F-2" ), (2,	-2., "H3"), \
 1076 									  (2, 0., "G3" ) ], True )
 1077 		Compact.__init__(self, 5)
 1078 		self.setDomWalls( [ "EX1", "EF-2" ] )
 1079 	
 1080 	def setZeroBetti(self, zeroB):
 1081 		Compact.setZeroBetti(self,zeroB)
 1082 		
 1083 		#print "bill.cosetF4.setZeroBetti: ", zeroB
 1084 		
 1085 		(wkl,ext,dom) = self._prepCompactRules(self)
 1086 		
 1087 		if ext["EX1"] and ext["EH3"]:
 1088 			dom["EG3"] = False
 1089 		
 1090 		# alls bs nonzero
 1091 		if ext["EX1"] and ext["EF-2"]:
 1092 			dom["EF+2"] = False
 1093 			dom["EH3"] = False
 1094 			dom["BX1"] = False
 1095 			dom["BF+2"] = False
 1096 			dom["BF-2"] = False
 1097 			dom["BH3"] = False
 1098 			dom["BG3"] = False
 1099 			dom["G"] = False
 1100 		
 1101 		# b1=0, killing EF+2,EF-2,BX1
 1102 		if ext["EX1"] and ext["EH3"]:
 1103 			dom["EG3"] = False
 1104 			dom["BX1"] = False
 1105 			dom["BF+2"] = False
 1106 			dom["BF-2"] = False
 1107 			dom["BH3"] = False
 1108 			dom["BG3"] = False
 1109 			dom["G"] = False
 1110 			
 1111 			
 1112 		
 1113 		self._postCompactRules(self, ext, dom)
 1114 			
 1115 		
 1116 class cosetG2(Billiard,Compact):
 1117 	
 1118 	def __init__(self):
 1119 		Billiard.__init__(self, 4, [ (1, 0., "F2") ], False )
 1120 		Compact.__init__(self, 4)
 1121 		self.setDomWalls( [ "EF2" ] )
 1122 	
 1123 	def setZeroBetti(self):
 1124 		return
 1125 
 1126 
 1127 
 1128 def test_run():
 1129 	b5=cosetE6()
 1130 	    
 1131 	zblists = [ [], [1], [2], [3], [1,2], [1,3], [2,3], [1,2,3] ]
 1132     
 1133 	print "-" * 79
 1134 	for lzb in zblists:
 1135 		b5.setZeroBetti( lzb )
 1136 		print "Coweights (T,N,S): ", b5.countCoweightTypes()
 1137 		print "Chaotic:           ", b5.isChaotic()
 1138 		b5.printDynkin()
 1139 		print "-" * 79
 1140 
 1141 def all( cos ):
 1142 	    
 1143 	zblists = [ [], [1], [2], [3], [1,2], [1,3], [2,3], [1,2,3] ]
 1144     
 1145 	print "-" * 79
 1146 	for lzb in zblists:
 1147 		cos.setZeroBetti( lzb )
 1148 		print "Zero Betti:        ", lzb
 1149 		print "Coweights (T,N,S): ", cos.countCoweightTypes()
 1150 		print "Chaotic:           ", cos.isChaotic()
 1151 		cos.printDynkin()
 1152 		print "-" * 79
 1153 
 1154 def test_print():
 1155 	e6 = cosetE6()
 1156 	print e6
 1157 
 1158 def chaosTable( c, zb ):
 1159 	for n in range(2,12):
 1160 		b = c(n)
 1161 		b.setZeroBetti(zb)
 1162 		print "%2d    %s" % ( n, str(b.isChaotic()) )
 1163 
 1164 if __name__ == "__main__":
 1165     cos = [ cosetB(8), cosetD(8), cosetF4(), cosetE6(), cosetE7() ]
 1166     zblist_3 = [ [], [1], [2], [3], [1,2], [1,3], [2,3], [1,2,3] ]
 1167     
 1168     zblist_4 = [] + zblist_3
 1169     for zb in zblist_3:
 1170     	zblist_4.append( zb + [4] )
 1171     #zblist_4.append( [] )
 1172     
 1173     for c in cos:
 1174     	print "="*79
 1175     	
 1176     	max_p = -1
 1177     	for (p,l,s) in c.forms:
 1178     		if p > max_p:
 1179     			max_p = p
 1180     	
 1181     	zblists = zblist_3
 1182     	
 1183     	if max_p > 2:
 1184     		zblists = zblist_4
 1185     	
 1186     	for zb in zblists:
 1187     		print "-"*79
 1188     		print "Zero Betti:         ", str(zb)
 1189     		c.setZeroBetti(zb)
 1190     		print c
 1191     
 1192 
 1193     
 1194