Next: , Previous: Indexed objects, Up: Basic concepts


4.15 Non-commutative objects

GiNaC is equipped to handle certain non-commutative algebras. Three classes of non-commutative objects are built-in which are mostly of use in high energy physics:

The clifford and color classes are subclasses of indexed because the elements of these algebras usually carry indices. The matrix class is described in more detail in Matrices.

Unlike most computer algebra systems, GiNaC does not primarily provide an operator (often denoted ‘&*’) for representing inert products of arbitrary objects. Rather, non-commutativity in GiNaC is a property of the classes of objects involved, and non-commutative products are formed with the usual ‘*’ operator, as are ordinary products. GiNaC is capable of figuring out by itself which objects commutate and will group the factors by their class. Consider this example:

         ...
         varidx mu(symbol("mu"), 4), nu(symbol("nu"), 4);
         idx a(symbol("a"), 8), b(symbol("b"), 8);
         ex e = -dirac_gamma(mu) * (2*color_T(a)) * 8 * color_T(b) * dirac_gamma(nu);
         cout << e << endl;
          // -> -16*(gamma~mu*gamma~nu)*(T.a*T.b)
         ...

As can be seen, GiNaC pulls out the overall commutative factor ‘-16’ and groups the non-commutative factors (the gammas and the su(3) generators) together while preserving the order of factors within each class (because Clifford objects commutate with color objects). The resulting expression is a commutative product with two factors that are themselves non-commutative products (‘gamma~mu*gamma~nu’ and ‘T.a*T.b’). For clarification, parentheses are placed around the non-commutative products in the output.

Non-commutative products are internally represented by objects of the class ncmul, as opposed to commutative products which are handled by the mul class. You will normally not have to worry about this distinction, though.

The advantage of this approach is that you never have to worry about using (or forgetting to use) a special operator when constructing non-commutative expressions. Also, non-commutative products in GiNaC are more intelligent than in other computer algebra systems; they can, for example, automatically canonicalize themselves according to rules specified in the implementation of the non-commutative classes. The drawback is that to work with other than the built-in algebras you have to implement new classes yourself. Both symbols and user-defined functions can be specified as being non-commutative.

Information about the commutativity of an object or expression can be obtained with the two member functions

     unsigned      ex::return_type() const;
     return_type_t ex::return_type_tinfo() const;

The return_type() function returns one of three values (defined in the header file flags.h), corresponding to three categories of expressions in GiNaC:

The return_type_tinfo() method returns an object of type return_type_t that contains information about the type of the expression and, if given, its representation label (see section on dirac gamma matrices for more details). The objects of type return_type_t can be tested for equality to test whether two expressions belong to the same category and therefore may not commute.

Here are a couple of examples:

Expression return_type()
42 commutative
2*x-y commutative
dirac_ONE() noncommutative
dirac_gamma(mu)*dirac_gamma(nu) noncommutative
2*color_T(a) noncommutative
dirac_ONE()*color_T(a) noncommutative_composite

A last note: With the exception of matrices, positive integer powers of non-commutative objects are automatically expanded in GiNaC. For example, pow(a*b, 2) becomes ‘a*b*a*b’ if ‘a’ and ‘b’ are non-commutative expressions).

4.15.1 Clifford algebra

Clifford algebras are supported in two flavours: Dirac gamma matrices (more physical) and generic Clifford algebras (more mathematical).

4.15.1.1 Dirac gamma matrices

Dirac gamma matrices (note that GiNaC doesn't treat them as matrices) are designated as ‘gamma~mu’ and satisfy ‘gamma~mu*gamma~nu + gamma~nu*gamma~mu = 2*eta~mu~nu’ where ‘eta~mu~nu’ is the Minkowski metric tensor. Dirac gammas are constructed by the function

     ex dirac_gamma(const ex & mu, unsigned char rl = 0);

which takes two arguments: the index and a representation label in the range 0 to 255 which is used to distinguish elements of different Clifford algebras (this is also called a spin line index). Gammas with different labels commutate with each other. The dimension of the index can be 4 or (in the framework of dimensional regularization) any symbolic value. Spinor indices on Dirac gammas are not supported in GiNaC.

The unity element of a Clifford algebra is constructed by

     ex dirac_ONE(unsigned char rl = 0);

Please notice: You must always use dirac_ONE() when referring to multiples of the unity element, even though it's customary to omit it. E.g. instead of dirac_gamma(mu)*(dirac_slash(q,4)+m) you have to write dirac_gamma(mu)*(dirac_slash(q,4)+m*dirac_ONE()). Otherwise, GiNaC will complain and/or produce incorrect results.

There is a special element ‘gamma5’ that commutates with all other gammas, has a unit square, and in 4 dimensions equals ‘gamma~0 gamma~1 gamma~2 gamma~3’, provided by

     ex dirac_gamma5(unsigned char rl = 0);

The chiral projectors ‘(1+/-gamma5)/2’ are also available as proper objects, constructed by

     ex dirac_gammaL(unsigned char rl = 0);
     ex dirac_gammaR(unsigned char rl = 0);

They observe the relations ‘gammaL^2 = gammaL’, ‘gammaR^2 = gammaR’, and ‘gammaL gammaR = gammaR gammaL = 0’.

Finally, the function

     ex dirac_slash(const ex & e, const ex & dim, unsigned char rl = 0);

creates a term that represents a contraction of ‘e’ with the Dirac Lorentz vector (it behaves like a term of the form ‘e.mu gamma~mu’ with a unique index whose dimension is given by the dim argument). Such slashed expressions are printed with a trailing backslash, e.g. ‘e\’.

In products of dirac gammas, superfluous unity elements are automatically removed, squares are replaced by their values, and ‘gamma5’, ‘gammaL’ and ‘gammaR’ are moved to the front.

The simplify_indexed() function performs contractions in gamma strings, for example

     {
         ...
         symbol a("a"), b("b"), D("D");
         varidx mu(symbol("mu"), D);
         ex e = dirac_gamma(mu) * dirac_slash(a, D)
              * dirac_gamma(mu.toggle_variance());
         cout << e << endl;
          // -> gamma~mu*a\*gamma.mu
         e = e.simplify_indexed();
         cout << e << endl;
          // -> -D*a\+2*a\
         cout << e.subs(D == 4) << endl;
          // -> -2*a\
         ...
     }

To calculate the trace of an expression containing strings of Dirac gammas you use one of the functions

     ex dirac_trace(const ex & e, const std::set<unsigned char> & rls,
                    const ex & trONE = 4);
     ex dirac_trace(const ex & e, const lst & rll, const ex & trONE = 4);
     ex dirac_trace(const ex & e, unsigned char rl = 0, const ex & trONE = 4);

These functions take the trace over all gammas in the specified set rls or list rll of representation labels, or the single label rl; gammas with other labels are left standing. The last argument to dirac_trace() is the value to be returned for the trace of the unity element, which defaults to 4.

The dirac_trace() function is a linear functional that is equal to the ordinary matrix trace only in D = 4 dimensions. In particular, the functional is not cyclic in D != 4 dimensions when acting on expressions containing ‘gamma5’, so it's not a proper trace. This ‘gamma5’ scheme is described in greater detail in the article The Role of gamma5 in Dimensional Regularization (Bibliography).

The value of the trace itself is also usually different in 4 and in D != 4 dimensions:

     {
         // 4 dimensions
         varidx mu(symbol("mu"), 4), nu(symbol("nu"), 4), rho(symbol("rho"), 4);
         ex e = dirac_gamma(mu) * dirac_gamma(nu) *
                dirac_gamma(mu.toggle_variance()) * dirac_gamma(rho);
         cout << dirac_trace(e).simplify_indexed() << endl;
          // -> -8*eta~rho~nu
     }
     ...
     {
         // D dimensions
         symbol D("D");
         varidx mu(symbol("mu"), D), nu(symbol("nu"), D), rho(symbol("rho"), D);
         ex e = dirac_gamma(mu) * dirac_gamma(nu) *
                dirac_gamma(mu.toggle_variance()) * dirac_gamma(rho);
         cout << dirac_trace(e).simplify_indexed() << endl;
          // -> 8*eta~rho~nu-4*eta~rho~nu*D
     }

Here is an example for using dirac_trace() to compute a value that appears in the calculation of the one-loop vacuum polarization amplitude in QED:

     {
         symbol q("q"), l("l"), m("m"), ldotq("ldotq"), D("D");
         varidx mu(symbol("mu"), D), nu(symbol("nu"), D);
     
         scalar_products sp;
         sp.add(l, l, pow(l, 2));
         sp.add(l, q, ldotq);
     
         ex e = dirac_gamma(mu) *
                (dirac_slash(l, D) + dirac_slash(q, D) + m * dirac_ONE()) *
                dirac_gamma(mu.toggle_variance()) *
                (dirac_slash(l, D) + m * dirac_ONE());
         e = dirac_trace(e).simplify_indexed(sp);
         e = e.collect(lst(l, ldotq, m));
         cout << e << endl;
          // -> (8-4*D)*l^2+(8-4*D)*ldotq+4*D*m^2
     }

The canonicalize_clifford() function reorders all gamma products that appear in an expression to a canonical (but not necessarily simple) form. You can use this to compare two expressions or for further simplifications:

     {
         varidx mu(symbol("mu"), 4), nu(symbol("nu"), 4);
         ex e = dirac_gamma(mu) * dirac_gamma(nu) + dirac_gamma(nu) * dirac_gamma(mu);
         cout << e << endl;
          // -> gamma~mu*gamma~nu+gamma~nu*gamma~mu
     
         e = canonicalize_clifford(e);
         cout << e << endl;
          // -> 2*ONE*eta~mu~nu
     }

4.15.1.2 A generic Clifford algebra

A generic Clifford algebra, i.e. a 2^n dimensional algebra with generators e_k satisfying the identities e~i e~j + e~j e~i = M(i, j) + M(j, i) for some bilinear form (metric) M(i, j), which may be non-symmetric (see arXiv:math.QA/9911180) and contain symbolic entries. Such generators are created by the function

         ex clifford_unit(const ex & mu, const ex & metr, unsigned char rl = 0);

where mu should be a idx (or descendant) class object indexing the generators. Parameter metr defines the metric M(i, j) and can be represented by a square matrix, tensormetric or indexed class object. In fact, any expression either with two free indices or without indices at all is admitted as metr. In the later case an indexed object with two newly created indices with metr as its op(0) will be used. Optional parameter rl allows to distinguish different Clifford algebras, which will commute with each other.

Note that the call clifford_unit(mu, minkmetric()) creates something very close to dirac_gamma(mu), although dirac_gamma have more efficient simplification mechanism. The method clifford::get_metric() returns a metric defining this Clifford number.

If the matrix M(i, j) is in fact symmetric you may prefer to create the Clifford algebra units with a call like that

         ex e = clifford_unit(mu, indexed(M, sy_symm(), i, j));

since this may yield some further automatic simplifications. Again, for a metric defined through a matrix such a symmetry is detected automatically.

Individual generators of a Clifford algebra can be accessed in several ways. For example

     {
         ...
         idx i(symbol("i"), 4);
         realsymbol s("s");
         ex M = diag_matrix(lst(1, -1, 0, s));
         ex e = clifford_unit(i, M);
         ex e0 = e.subs(i == 0);
         ex e1 = e.subs(i == 1);
         ex e2 = e.subs(i == 2);
         ex e3 = e.subs(i == 3);
         ...
     }

will produce four anti-commuting generators of a Clifford algebra with properties pow(e0, 2) = 1, pow(e1, 2) = -1, pow(e2, 2) = 0 and pow(e3, 2) = s.

A similar effect can be achieved from the function

         ex lst_to_clifford(const ex & v, const ex & mu,  const ex & metr,
                            unsigned char rl = 0);
         ex lst_to_clifford(const ex & v, const ex & e);

which converts a list or vector ‘v = (v~0, v~1, ..., v~n)’ into the Clifford number ‘v~0 e.0 + v~1 e.1 + ... + v~n e.n’ with ‘e.k’ directly supplied in the second form of the procedure. In the first form the Clifford unit ‘e.k’ is generated by the call of clifford_unit(mu, metr, rl). If the number of components supplied by v exceeds the dimensionality of the Clifford unit e by 1 then function lst_to_clifford() uses the following pseudo-vector representation: ‘v~0 ONE + v~1 e.0 + v~2 e.1 + ... + v~[n+1] e.n

The previous code may be rewritten with the help of lst_to_clifford() as follows

     {
         ...
         idx i(symbol("i"), 4);
         realsymbol s("s");
         ex M = diag_matrix(lst(1, -1, 0, s));
         ex e0 = lst_to_clifford(lst(1, 0, 0, 0), i, M);
         ex e1 = lst_to_clifford(lst(0, 1, 0, 0), i, M);
         ex e2 = lst_to_clifford(lst(0, 0, 1, 0), i, M);
         ex e3 = lst_to_clifford(lst(0, 0, 0, 1), i, M);
       ...
     }

There is the inverse function

         lst clifford_to_lst(const ex & e, const ex & c, bool algebraic = true);

which takes an expression e and tries to find a list ‘v = (v~0, v~1, ..., v~n)’ such that the expression is either vector ‘e = v~0 c.0 + v~1 c.1 + ... + v~n c.n’ or pseudo-vector ‘v~0 ONE + v~1 e.0 + v~2 e.1 + ... + v~[n+1] e.n’ with respect to the given Clifford units c. Here none of the ‘v~k’ should contain Clifford units c (of course, this may be impossible). This function can use an algebraic method (default) or a symbolic one. With the algebraic method the ‘v~k’ are calculated as ‘(e c.k + c.k e)/pow(c.k, 2)’. If ‘pow(c.k, 2)’ is zero or is not numeric for some ‘k’ then the method will be automatically changed to symbolic. The same effect is obtained by the assignment (algebraic = false) in the procedure call.

There are several functions for (anti-)automorphisms of Clifford algebras:

         ex clifford_prime(const ex & e)
         inline ex clifford_star(const ex & e) { return e.conjugate(); }
         inline ex clifford_bar(const ex & e) { return clifford_prime(e.conjugate()); }

The automorphism of a Clifford algebra clifford_prime() simply changes signs of all Clifford units in the expression. The reversion of a Clifford algebra clifford_star() coincides with the conjugate() method and effectively reverses the order of Clifford units in any product. Finally the main anti-automorphism of a Clifford algebra clifford_bar() is the composition of the previous two, i.e. it makes the reversion and changes signs of all Clifford units in a product. These functions correspond to the notations e', e* and \bar{e} used in Clifford algebra textbooks.

The function

         ex clifford_norm(const ex & e);

calculates the norm of a Clifford number from the expression ||e||^2 = e \bar{e} The inverse of a Clifford expression is returned by the function

         ex clifford_inverse(const ex & e);

which calculates it as e^{-1} = \bar{e}/||e||^2 If ||e||=0 then an exception is raised.

If a Clifford number happens to be a factor of dirac_ONE() then we can convert it to a “real” (non-Clifford) expression by the function

         ex remove_dirac_ONE(const ex & e);

The function canonicalize_clifford() works for a generic Clifford algebra in a similar way as for Dirac gammas.

The next provided function is

         ex clifford_moebius_map(const ex & a, const ex & b, const ex & c,
                                 const ex & d, const ex & v, const ex & G,
                                 unsigned char rl = 0);
         ex clifford_moebius_map(const ex & M, const ex & v, const ex & G,
                                 unsigned char rl = 0);

It takes a list or vector v and makes the Moebius (conformal or linear-fractional) transformation ‘v -> (av+b)/(cv+d)’ defined by the matrix ‘M = [[a, b], [c, d]]’. The parameter G defines the metric of the surrounding (pseudo-)Euclidean space. This can be an indexed object, tensormetric, matrix or a Clifford unit, in the later case the optional parameter rl is ignored even if supplied. Depending from the type of v the returned value of this function is either a vector or a list holding vector's components.

Finally the function

     char clifford_max_label(const ex & e, bool ignore_ONE = false);

can detect a presence of Clifford objects in the expression e: if such objects are found it returns the maximal representation_label of them, otherwise -1. The optional parameter ignore_ONE indicates if dirac_ONE objects should be ignored during the search.

LaTeX output for Clifford units looks like \clifford[1]{e}^{{\nu}}, where 1 is the representation_label and \nu is the index of the corresponding unit. This provides a flexible typesetting with a suitable definition of the \clifford command. For example, the definition

         \newcommand{\clifford}[1][]{}

typesets all Clifford units identically, while the alternative definition

         \newcommand{\clifford}[2][]{\ifcase #1 #2\or \tilde{#2} \or \breve{#2} \fi}

prints units with representation_label=0 as e, with representation_label=1 as \tilde{e} and with representation_label=2 as \breve{e}.

4.15.2 Color algebra

For computations in quantum chromodynamics, GiNaC implements the base elements and structure constants of the su(3) Lie algebra (color algebra). The base elements T_a are constructed by the function

     ex color_T(const ex & a, unsigned char rl = 0);

which takes two arguments: the index and a representation label in the range 0 to 255 which is used to distinguish elements of different color algebras. Objects with different labels commutate with each other. The dimension of the index must be exactly 8 and it should be of class idx, not varidx.

The unity element of a color algebra is constructed by

     ex color_ONE(unsigned char rl = 0);

Please notice: You must always use color_ONE() when referring to multiples of the unity element, even though it's customary to omit it. E.g. instead of color_T(a)*(color_T(b)*indexed(X,b)+1) you have to write color_T(a)*(color_T(b)*indexed(X,b)+color_ONE()). Otherwise, GiNaC may produce incorrect results.

The functions

     ex color_d(const ex & a, const ex & b, const ex & c);
     ex color_f(const ex & a, const ex & b, const ex & c);

create the symmetric and antisymmetric structure constants d_abc and f_abc which satisfy {T_a, T_b} = 1/3 delta_ab + d_abc T_c and [T_a, T_b] = i f_abc T_c.

These functions evaluate to their numerical values, if you supply numeric indices to them. The index values should be in the range from 1 to 8, not from 0 to 7. This departure from usual conventions goes along better with the notations used in physical literature.

There's an additional function

     ex color_h(const ex & a, const ex & b, const ex & c);

which returns the linear combination ‘color_d(a, b, c)+I*color_f(a, b, c)’.

The function simplify_indexed() performs some simplifications on expressions containing color objects:

     {
         ...
         idx a(symbol("a"), 8), b(symbol("b"), 8), c(symbol("c"), 8),
             k(symbol("k"), 8), l(symbol("l"), 8);
     
         e = color_d(a, b, l) * color_f(a, b, k);
         cout << e.simplify_indexed() << endl;
          // -> 0
     
         e = color_d(a, b, l) * color_d(a, b, k);
         cout << e.simplify_indexed() << endl;
          // -> 5/3*delta.k.l
     
         e = color_f(l, a, b) * color_f(a, b, k);
         cout << e.simplify_indexed() << endl;
          // -> 3*delta.k.l
     
         e = color_h(a, b, c) * color_h(a, b, c);
         cout << e.simplify_indexed() << endl;
          // -> -32/3
     
         e = color_h(a, b, c) * color_T(b) * color_T(c);
         cout << e.simplify_indexed() << endl;
          // -> -2/3*T.a
     
         e = color_h(a, b, c) * color_T(a) * color_T(b) * color_T(c);
         cout << e.simplify_indexed() << endl;
          // -> -8/9*ONE
     
         e = color_T(k) * color_T(a) * color_T(b) * color_T(k);
         cout << e.simplify_indexed() << endl;
          // -> 1/4*delta.b.a*ONE-1/6*T.a*T.b
         ...

To calculate the trace of an expression containing color objects you use one of the functions

     ex color_trace(const ex & e, const std::set<unsigned char> & rls);
     ex color_trace(const ex & e, const lst & rll);
     ex color_trace(const ex & e, unsigned char rl = 0);

These functions take the trace over all color ‘T’ objects in the specified set rls or list rll of representation labels, or the single label rl; ‘T’s with other labels are left standing. For example:

         ...
         e = color_trace(4 * color_T(a) * color_T(b) * color_T(c));
         cout << e << endl;
          // -> -I*f.a.c.b+d.a.c.b
     }