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:
clifford)
color)
matrix)
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:
return_types::commutative: Commutates with everything. Most GiNaC
classes are of this kind.
return_types::noncommutative: Non-commutative, belonging to a
certain class of non-commutative objects which can be determined with the
return_type_tinfo() method. Expressions of this category commutate
with everything except noncommutative expressions of the same
class.
return_types::noncommutative_composite: Non-commutative, composed
of non-commutative objects of different classes. Expressions of this
category don't commutate with any other noncommutative or
noncommutative_composite expressions.
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:
|
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).
Clifford algebras are supported in two flavours: Dirac gamma matrices (more physical) and generic Clifford algebras (more mathematical).
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’.
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
}
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);
...
}
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.
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.
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}.
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.
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
}