jsMath

Playing with SAGE and Drinfeld associators

Drinfeld Associators

An associator is a formal power serie in two non-commuting variables ΦF2=〈〈a,b〉〉  satisfying a complicated set of algebraic equations. They first appear in the work of Vladimir Drinfeld on the monodromy of the so-called KZ equation, and are of fundamental importance in many areas of mathematics, e.g. quantum groups and deformation quantization, Lie theory, knot theory, ...

If A is a complete associative algebra and x,yA, there is a unique algebra morphism F2A mapping a to x and b to y. Let us denote by Φ(x,y) the image of Φ under such a map. The most important defining equation of Drinfeld associators is the pentagon equation

Φ(t12,t23+tt24)Φ(t13+t23,t34)=Φ(t23,t34)Φ(t12+t13,t24+t34)Φ(t12,t23)

where the tij are generators of a graded algebra A4.

There are two fundamental results of Drinfeld about associators. First of all, associators does exists, which is not obvious at all. This is proved by analytic methods, and the resulting associator has transcendental coefficients (which are multi-zeta values). Secondly, he proved that a rational, even associator exists. Here even mean that each of its odd degree homogoneous part has zero coefficient. Although no explicit formula is known for such an associator, it can be computed recursively in the following way: assume that we know an element P of F2 which solve the pentagon up to terms of degree k for some k. It means that the difference ψU(t5) between the left hand side and the right hand side of the pentagon is homogoneous of degree k up to terms of degree k+1. Let φ be an element of degree k of F2, set P=P+φ and plug it in the pentagon equation. If we want P to satisfies the pentagon equation modulo terms of degree k+1, we are left with the following linear equation for φ:

φ(t12,t23+tt24)+φ(t13+t23,t34)φ(t23,t34)φ(t12+t13,t24+t34)φ(t12,t23)=ψ

The theorem of Drinfeld asserts that this equation can always be solved, i.e. that it is always possible to extend an associator up to degree k to an associator up to degree k+1.

Actually, there is a whole family of algebras An, generated by tij, 1i<jn with a quite simple set of relations. It can be proven that there are "close" to free algebras. A basis of An is given by non-decreasing sequence of tij's with respect to the second indice. It is also known that t1,2,t23 generate a free algebra, and it is convenient to identify Φ with its image Φ(t12,t13)A3. An and An+1 are related by natural algebra maps di,n,0in+1 and the linear map dn=i=0n+1(1)idi,n is a differential, i.e. dn+1°dn=0. The above linear map can be rewritten d3φ=ψ.

The following files contains the implementation of two algebraic structures. Note that according to the SAGE philosophy, they are in particular free modules meaning that we get for free a lot of usefull SAGE procedure dealing with linear algebra. The only things we have to do is to constructs a basis, and to define the product of two basis elements. The product is extended linearly automatically. The first structure is a class for An where product are taken modulo terms of degree >k, together with the definition of the di,n. The second one is a generic free algebra where product are also taken modulo degree >k, together with some usefull functions like exp, log and a procedure which write, if possible, an element in a basis of the free Lie algebra which is a sub-space of F2. So let us first load the python file:

load("/home/u2/brochier/KD.py") 
       

and construct the algebra A4 and F2A3 modulo degree k+1. Here "diag" is an object representing the tij. The "a" is an optional parameter which controls how this element is printed to screen (a is more compact than tij and it allows to distinguish it from the generators of A4).

A=KDAlgebra(QQ, 4,4);F=myFreeAlgebra(QQ,(diag(1,2,"a"),diag(2,3,"b")),4) 
       

Recall that there are free modules, hence all corresponding SAGE functions are available

A.dimension(),F.dimension() 
       
(423, 31)
(a,b)=F.algebra_generators();(t12,t13,t14,t23,t24,t34)=A.algebra_generators() 
       

A rewriting algorithm is automatically applied to put elements in a normal form. A using a nice feature of Python, the product is a "cached method", meaning that the product of two given elements is computed only once and stored in memory for further use. The basis of A4 is indexed by words in the tij, and B[some word w] represents the basis element associated to w.

%time (1+t34*t12*t13+t23)^10 
       
B[word: ] + 45*B[word: t12,t13,t23,t34] + 10*B[word: t12,t13,t34] -
45*B[word: t12,t13,t34,t24] + 45*B[word: t12,t13,t24,t34] +
45*B[word: t12,t23,t13,t34] - 90*B[word: t12,t23,t34,t14] +
90*B[word: t12,t23,t14,t34] - 10*B[word: t12,t34,t14] + 45*B[word:
t12,t34,t24,t14] - 45*B[word: t12,t24,t34,t14] + 10*B[word:
t12,t14,t34] - 45*B[word: t12,t14,t34,t24] + 45*B[word:
t12,t14,t24,t34] + 10*B[word: t23] + 45*B[word: t23,t23] +
120*B[word: t23,t23,t23] + 210*B[word: t23,t23,t23,t23] - 45*B[word:
t23,t13,t13,t34] + 45*B[word: t23,t13,t34,t14] - 45*B[word:
t23,t13,t14,t34] + 45*B[word: t13,t23,t13,t34] - 45*B[word:
t13,t23,t34,t14] + 45*B[word: t13,t23,t14,t34]
CPU time: 0.18 s,  Wall time: 0.18 s

So our algebra A4 already know the various di,n, which allow us to define the coboundary map on basis elements

def OnBasis(x): return A.d(1,x)+A.d(3,x)-A.d(0,x)-A.d(2,x)-A.d(4,x) 
       

Now SAGE can turn it into a module map

f=F.module_morphism(OnBasis,codomain=A) 
       

It is easily seen that the hexagon equation implies that the first terms of Φ are 1+124[a,b] which allows us to begin the reccurence. In order to do computation with Φ, it is more convenient to define it as a function.

def P(x,y): return 1+1/24*(x*y-y*x) 
       

Now we expand the pentagon equation and store the defect

%time penta=P(t12, t23 + t24)*P(t13 + t23, t34)-P(t23, t34)*P(t12 + t13, t24 + t34)*P(t12, t23);penta 
       
1/576*B[word: t13,t23,t24,t14] - 1/576*B[word: t23,t13,t24,t14] +
1/576*B[word: t23,t13,t14,t24] + 1/576*B[word: t24,t14,t34,t14] +
1/576*B[word: t24,t14,t34,t24] - 1/576*B[word: t24,t14,t14,t34] -
1/192*B[word: t24,t14,t24,t34] - 1/576*B[word: t34,t14,t24,t24] -
1/288*B[word: t34,t14,t24,t34] - 1/576*B[word: t34,t24,t24,t14] -
1/576*B[word: t34,t24,t34,t14] + 1/192*B[word: t34,t24,t14,t24] +
1/192*B[word: t34,t24,t14,t34] + 1/576*B[word: t34,t24,t14,t14] -
1/576*B[word: t34,t34,t24,t14] + 1/576*B[word: t34,t34,t14,t24] +
1/576*B[word: t14,t24,t34,t14] + 1/576*B[word: t14,t24,t34,t24] +
1/576*B[word: t14,t24,t14,t34] + 1/576*B[word: t14,t24,t24,t34] +
1/576*B[word: t14,t24,t34,t34] + 1/576*B[word: t14,t14,t34,t24] -
1/576*B[word: t14,t14,t24,t34] - 1/576*B[word: t14,t34,t24,t24] -
1/288*B[word: t14,t34,t24,t14] + 1/576*B[word: t24,t34,t34,t14] -
1/576*B[word: t24,t34,t14,t24] - 1/576*B[word: t24,t34,t14,t14] -
1/576*B[word: t24,t34,t14,t34] - 1/576*B[word: t24,t34,t24,t14] -
1/576*B[word: t24,t14,t34,t34] + 1/576*B[word: t24,t24,t34,t14] +
1/576*B[word: t24,t24,t14,t34] - 1/576*B[word: t13,t23,t14,t24]
CPU time: 0.04 s,  Wall time: 0.04 s

We construct the matrix of the above linear map. Note that it can be done in a quite elegant way thanks to the list comprehension feature of Python

m=Matrix([f(x).to_vector() for x in F.basis().values()]) 
       

We can have a look on the kernel of f

ker=m.left_kernel(); [F.from_vector(x) for x in ker.basis()] 
       
[B[word: aab] - 2*B[word: aba] - B[word: abb] + B[word: baa] +
2*B[word: bab] - B[word: bba], B[word: ab] - B[word: ba]]

It means that the kernel in degree 4 is zero, so that our associator is, up to now, uniquely determined. The kernel in degree too is (linearly) spanned by [a,b], but the hexagon forces the coefficient 124.

Now we get the coordinates of the defect in the pentagon equation, and try to solve the corresponding equation

V=penta.to_vector(); phi=m.solve_left(-V); F.from_vector(phi) 
       
-1/1440*B[word: aaab] + 1/480*B[word: aaba] + 1/5760*B[word: aabb] -
1/480*B[word: abaa] + 1/1920*B[word: abab] - 1/1152*B[word: abba] -
1/1440*B[word: abbb] + 1/1440*B[word: baaa] - 1/1152*B[word: baab] +
7/5760*B[word: baba] + 1/480*B[word: babb] - 1/5760*B[word: bbaa] -
1/480*B[word: bbab] + 1/1440*B[word: bbba]

We update our definition of Phi

def P(x,y): return 1+1/24*(x*y-y*x)-1/1440*x*x*x*y + 1/480*x*x*y*x + 1/5760*x*x*y*y -1/480*x*y*x*x + 1/1920*x*y*x*y - 1/1152*x*y*y*x -1/1440*x*y*y*y + 1/1440*y*x*x*x - 1/1152*y*x*x*y +7/5760*y*x*y*x + 1/480*y*x*y*y - 1/5760*y*y*x*x -1/480*y*y*x*y + 1/1440*y*y*y*x 
       

We check all the relations

print P(t12, t23 + t24)*P(t13 + t23, t34)-P(t23, t34)*P(t12 + t13, t24 + t34)*P(t12, t23) print P(a,b)*P(b,a) c=-b-a print F.exp(a/2)*P(c,a)*F.exp(c/2)*P(b,c)*F.exp(b/2)*P(a,b) 
       
0
B[word: ]
B[word: ]

and we know that log(Φ) must be an element of the free Lie algebra on two generators, hence:

F.writeInLieBasis(F.log(P(a,b))) 
       
+1/24['a', 'b']
-1/1440['a', ['a', ['a', 'b']]]
+1/5760['a', [['a', 'b'], 'b']]
-1/1440[[['a', 'b'], 'b'], 'b']

Now let's go the the degree 6. First, redefine our algebras:

A=KDAlgebra(QQ, 4,6);F=myFreeAlgebra(QQ,(diag(1,2,"a"),diag(2,3,"b")),6) (a,b)=F.algebra_generators();(t12,t13,t14,t23,t24,t34)=A.algebra_generators() def OnBasis(x): return A.d(1,x)+A.d(3,x)-A.d(0,x)-A.d(2,x)-A.d(4,x) f=F.module_morphism(OnBasis,codomain=A) 
       

The construction of the matrix begin to be quite hard. On the other hand, this requires to compute the images of each basis elements of F2, which are independant tasks. So we will see how it can be parallzlized in a very simple way. We just have to take care of the order of the elements, so let us store basis elements as a list

baseList=F.basis().values(); 
       

No we define a function which will split into 16 independant process. Note that unlike what we did just a few lines before, the parameter of the function is an indice and not directly a basis element. It will help us to keep track of the order of the list.

@parallel(16) def paraF(i): return f(baseList[i]).to_vector() 
       

Now we apply this function to the list (0,...,size of the basis). The computation are not done at this point ! This returns a Python object called an "iterator", and computation will be done on demand.

L=paraF(range(len(baseList))) 
       

Now we create a list listMat of the right size but filled with zeros, and loop over L. Then we fill listMat by putting each vector at its right place. Elements of the iterator are of the form, hence elt[0][0][0] contains the indice, and elt[1] the result of the computation. The Python syntax is really natural for a mathematician: if l1 and l2 are two lists, l1+l2 is the concatenation of them, hence N*l1 where N is a natural integer is the concatenation of N copies of l1.

%time listMat=len(baseList)*[0] for elt in L: listMat[elt[0][0][0]]=elt[1] 
       
CPU time: 6.54 s,  Wall time: 35.86 s

Now we expand the pentagon

%time penta=P(t12, t23 + t24)*P(t13 + t23, t34)-P(t23, t34)*P(t12 + t13, t24 + t34)*P(t12, t23) 
       
CPU time: 1.40 s,  Wall time: 1.40 s
m=Matrix(listMat,sparse=true) 
       
V=penta.to_vector() 
       
phi=m.solve_left(-V) 
       
lgp=F.from_vector(phi) 
       
F.writeInLieBasis(F.log(P(a,b)+lgp)) 
       
+1/24['a', 'b']
-1/1440['a', ['a', ['a', 'b']]]
+1/5760['a', [['a', 'b'], 'b']]
-1/1440[[['a', 'b'], 'b'], 'b']
+1/60480['a', ['a', ['a', ['a', ['a', 'b']]]]]
-1/80640['a', ['a', ['a', [['a', 'b'], 'b']]]]
+17/1451520['a', [['a', ['a', 'b']], ['a', 'b']]]
+23/967680['a', ['a', [[['a', 'b'], 'b'], 'b']]]
+1/725760['a', [['a', 'b'], [['a', 'b'], 'b']]]
-199/5806080[['a', [['a', 'b'], 'b']], ['a', 'b']]
-1/80640['a', [[[['a', 'b'], 'b'], 'b'], 'b']]
-19/1451520[['a', 'b'], [[['a', 'b'], 'b'], 'b']]
+1/60480[[[[['a', 'b'], 'b'], 'b'], 'b'], 'b']
ker=m.left_kernel() 
       
ker 
       
Vector space of degree 127 and dimension 3 over Rational Field
Basis matrix:
3 x 127 dense matrix over Rational Field
for x in ker.basis(): print F.writeInLieBasis(F.from_vector(x)) print " " 
       
+1['a', ['a', ['a', ['a', 'b']]]]
-2['a', ['a', [['a', 'b'], 'b']]]
+3/2[['a', ['a', 'b']], ['a', 'b']]
+2['a', [[['a', 'b'], 'b'], 'b']]
+1/2[['a', 'b'], [['a', 'b'], 'b']]
-1[[[['a', 'b'], 'b'], 'b'], 'b']
None
 
+1['a', ['a', 'b']]
-1[['a', 'b'], 'b']
None
 
+1['a', 'b']
None