GiNaC TutorialAn open framework for symbolic computation within the C++ programming languageThe GiNaC GroupChristianBauerChristian.Bauer@Uni-Mainz.DEAlexanderFrinkAlexander.Frink@Uni-Mainz.DERichardB.KreckelRichard.Kreckel@Uni-Mainz.DEOtherswhoever@ThEP.Physik.Uni-Mainz.DEIntroductionThe motivation behind GiNaC derives from the observation that
most present day computer algebra systems (CAS) are linguistically and
semantically impoverished. It is an attempt to overcome the current
situation by extending a well established and standardized computer
language (C++) by some fundamental symbolic capabilities, thus
allowing for integrated systems that embed symbolic manipulations
together with more established areas of computer science (like
computation-intense numeric applications, graphical interfaces, etc.)
under one roof.This tutorial is intended for the novice user who is new to GiNaC
but already has some background in C++ programming. However, since a
hand made documentation like this one is difficult to keep in sync
with the development the actual documentation is inside the sources in
the form of comments. That documentation may be parsed by one of the
many Javadoc-like documentation systems. The generated HTML
documenatation is included in the distributed sources (subdir
doc/reference/) or can be accessed directly at URL
http://wwwthep.physik.uni-mainz.de/GiNaC/reference/.
It is an invaluable resource not only for the advanced user who wishes
to extend the system (or chase bugs) but for everybody who wants to
comprehend the inner workings of GiNaC. This little tutorial on the
other hand only covers the basic things that are unlikely to change in
the near future.
LicenseThe GiNaC framework for symbolic computation within the C++
programming language is Copyright (C) 1999 Johannes Gutenberg
Universität Mainz, Germany.This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License as
published by the Free Software Foundation; either version 2 of the
License, or (at your option) any later version.This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.You should have received a copy of the GNU General Public License
along with this program; see the file COPYING. If not, write to the
Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
MA 02111-1307, USA.A Tour of GiNaCThis quick tour of GiNaC wants to rise your interest in in the
subsequent chapters by showing off a bit. Please excuse us if it
leaves many open questions.How to use it from within C++The GiNaC
open framework for symbolic computation within the C++ programming
language does not try to define a language of it's own as conventional
CAS do. Instead, it extends the capabilities of C++ by symbolic
manipulations. Here is how to generate and print a simple (and
pointless) bivariate polynomial with some large coefficients:
My first GiNaC program (a bivariate polynomial)
#include <GiNaC/ginac.h>
int main()
{
symbol x("x"), y("y");
ex poly;
for (int i=0; i<3; ++i)
poly += factorial(i+16)*pow(x,i)*pow(y,2-i);
cout << poly << endl;
return 0;
}
Assuming the file is called hello.cc, on
our system we can compile and run it like this:sysprompt> c++ hello.cc -o hello -lcln -lginac
sysprompt> ./hello
355687428096000*x*y+20922789888000*y^2+6402373705728000*x^2
Next, there is a more meaningful C++ program that calls a
function which generates Hermite polynomials in a specified free
variable.
My second GiNaC program (Hermite polynomials)
#include <GiNaC/ginac.h>
ex HermitePoly(symbol x, int deg)
{
ex HKer=exp(-pow(x,2));
// uses the identity H_n(x) == (-1)^n exp(x^2) (d/dx)^n exp(-x^2)
return normal(pow(-1,deg) * diff(HKer, x, deg) / HKer);
}
int main()
{
symbol z("z");
for (int i=0; i<6; ++i)
cout << "H_" << i << "(z) == " << HermitePoly(z,i) << endl;
return 0;
}
When run, this will type out
H_0(z) == 1
H_1(z) == 2*z
H_2(z) == 4*z^2-2
H_3(z) == -12*z+8*z^3
H_4(z) == -48*z^2+16*z^4+12
H_5(z) == 120*z-160*z^3+32*z^5
This method of generating the coefficients is of course far from
optimal for production purposes.In order to show some more examples of what GiNaC can do we
will now use ginsh, a simple GiNaC interactive
shell that provides a convenient window into GiNaC's capabilities.
What it can do for youAfter invoking ginsh one can test and
experiment with GiNaC's features much like in other Computer Algebra
Systems except that it does not provide programming constructs like
loops or conditionals. For a concise description of the
ginsh syntax we refer to its accompanied
man-page.It can manipulate arbitrary precision integers in a very fast
way. Rational numbers are automatically converted to fractions of
coprime integers:
> x=3^150;
369988485035126972924700782451696644186473100389722973815184405301748249
> y=3^149;
123329495011708990974900260817232214728824366796574324605061468433916083
> x/y;
3
> y/x;
1/3
All numbers occuring in GiNaC's expressions can be converted
into floating point numbers with the evalf method,
to arbitrary accuracy:
> evalf(1/7);
0.14285714285714285714
> Digits=150;
150
> evalf(1/7);
0.1428571428571428571428571428571428571428571428571428571428571428571428
5714285714285714285714285714285714285
Exact numbers other than rationals that can be manipulated in
GiNaC include predefined constants like Archimedes' Pi. They can both
be used in symbolic manipulations (as an exact number) as well as in
numeric expressions (as an inexact number):
> a=Pi^2+x;
x+Pi^2
> evalf(a);
x+9.869604401089358619L0
> x=2;
2
> evalf(a);
11.869604401089358619L0
Built-in functions evaluate immediately to exact numbers if
this is possible. Conversions that can be safely performed are done
immediately; conversions that are not generally valid are not done:
> cos(42*Pi);
1
> cos(acos(x));
x
> acos(cos(x));
acos(cos(x))
(Note that converting the last input to x would
allow one to conclude that 42*Pi is equal to
0.)Linear equation systems can be solved along with basic linear
algebra manipulations over symbolic expressions. In C++ there is a
matrix class for this purpose but we can see what it can do using
ginsh's notation of double brackets to type them in:
> lsolve(a+x*y==z,x);
y^(-1)*(z-a);
lsolve([3*x+5*y == 7, -2*x+10*y == -5], [x, y]);
[x==19/8,y==-1/40]
> M = [[ [[1, 3]], [[-3, 2]] ]];
[[ [[1,3]], [[-3,2]] ]]
> determinant(M);
11
> charpoly(M,lambda);
lambda^2-3*lambda+11
Multivariate polynomials and rational functions may be expanded,
collected and normalized (i.e. converted to a ratio of two coprime
polynomials):
> a = x^4 + 2*x^2*y^2 + 4*x^3*y + 12*x*y^3 - 3*y^4;
-3*y^4+x^4+12*x*y^3+2*x^2*y^2+4*x^3*y
> b = x^2 + 4*x*y - y^2;
-y^2+x^2+4*x*y
> expand(a*b);
3*y^6+x^6-24*x*y^5+43*x^2*y^4+16*x^3*y^3+17*x^4*y^2+8*x^5*y
> collect(a*b,x);
3*y^6+48*x*y^4+2*x^2*y^2+x^4*(-y^2+x^2+4*x*y)+4*x^3*y*(-y^2+x^2+4*x*y)
> normal(a/b);
3*y^2+x^2
You can differentiate functions and expand them as Taylor or Laurent
series (the third argument of series is the evaluation point, the
fourth defines the order):
> diff(tan(x),x);
tan(x)^2+1
> series(sin(x),x,0,4);
x-1/6*x^3+Order(x^4)
> series(1/tan(x),x,0,4);
x^(-1)-1/3*x+Order(x^2)
InstallationGiNaC's installation follows the spirit of most GNU software. It is
easily installed on your system by three steps: configuration, build,
installation.PrerequistesIn order to install GiNaC on your system, some prerequistes need
to be met. First of all, you need to have a C++-compiler adhering to
the ANSI-standard ISO/IEC 14882:1998(E). We used
GCC for development so if you have a different
compiler you are on your own. For the configuration to succeed you
need a Posix compliant shell installed in /bin/sh,
GNU bash is fine. Perl is needed by the built
process as well, since some of the source files are automatically
generated by Perl scripts. Last but not least, Bruno Haible's library
CLN is extensively used and needs to be installed
on your system. Please get it from ftp://ftp.santafe.edu/pub/gnu/
or from ftp://ftp.ilog.fr/pub/Users/haible/gnu/
(it is covered by GPL) and install it prior to trying to install
GiNaC. The configure script checks if it can find it and if it cannot
it will refuse to continue.ConfigurationTo configure GiNaC means to prepare the source distribution for
building. It is done via a shell script called
configure that is shipped with the sources.
(Actually, this script is by itself created with GNU Autoconf from the
files configure.in and
aclocal.m4.) Since a configure script generated by
GNU Autoconf never prompts, all customization must be done either via
command line parameters or environment variables. It accepts a list
of parameters, the complete set of which can be listed by calling it
with the --help option. The most important ones
will be shortly described in what follows:
--enable-shared: When given, this option
switches on the build of a shared library, i.e. a
.so-file. A static libarary (i.e. a
.a-file) is still built. For this to succeed,
GNU libtool needs to be installed on your system. Hence,
configure checks if it can find an executable
libtool in the PATH. If it
doesn't this option is ignored and the default restored, which
means that only a static library will be build.--prefix=PREFIX: The
directory where the compiled library and headers are installed. It
defaults to /usr/local which means that the
library is installed in the directory
/usr/local/lib and the header files in
/usr/local/include/GiNaC and the documentation
(like this one) into /usr/local/share/doc/GiNaC.--libdir=LIBDIR: Use
this option in case you want to have the library installed in some
other directory than
PREFIX/lib/.--includedir=INCLUDEDIR:
Use this option in case you want to have the header files
installed in some other directory than
PREFIX/include/GiNaC/. For
instance, if you specify
--includedir=/usr/include you will end up with
the header files sitting in the directory
/usr/include/GiNaC/. Note that the subdirectory
GiNaC is enforced by this process in order to
keep the header files separated from others. This avoids some
clashes and allows for an easier deinstallation of GiNaC. This ought
to be considered A Good Thing (tm).--datadir=DATADIR:
This option may be given in case you want to have the documentation
installed in some other directory than
PREFIX/share/doc/GiNaC/.
In addition, you may specify some environment variables.
CXX holds the path and the name of the C++ compiler
in case you want to override the default in your path. (The
configure script searches your path for
c++, g++,
gcc, CC, cxx
and cc++ in that order.) It may be very useful to
define some compiler flags with the CXXFLAGS
environment variable, like optimization, debugging information and
warning levels. If ommitted, it defaults to -g
-O2.The whole process is illustrated in the following two
examples. (Substitute setenv VARIABLE value for
export VARIABLE=value if the Berkeley C shell is
your login shell.)
Sample sessions of how to call the
configure-scriptSimple configuration for a site-wide
GiNaC library assuming everything is in default paths:sysprompt> export CXXFLAGS="-Wall -O2"
sysprompt> ./configure --enable-shared
Configuration for a private GiNaC library with several
components sitting in custom places (site-wide GCC
and private CLN):sysprompt> export CXX=/usr/local/gnu/bin/c++
sysprompt> export CPPFLAGS="${CPPFLAGS} -I${HOME}/include"
sysprompt> export CXXFLAGS="${CXXFLAGS} -ggdb -Wall -ansi -pedantic -O2"
sysprompt> export LDFLAGS="${LDFLAGS} -L${HOME}/lib"
sysprompt> ./configure --enable-shared --prefix=${HOME}
Building GiNaCAfter proper configuration you should just build the whole
library by typing make at the command
prompt and go for a cup of coffee.Just to make sure GiNaC works properly you may run a simple test
suite by typing make check. This will compile some
sample programs, run them and compare the output to reference output.
Each of the checks should return a message passed
together with the CPU time used for that particular test. If it does
not, something went wrong. This is mostly intended to be a check if
something was broken during the development, but not a sanity check of
your system. Another intent is to allow people to fiddle around with
optimization. If CLN was installed all right this
step is unlikely to return any errors.InstallationTo install GiNaC on your system, simply type make
install. As described in the section about configuration
the files will be installed in the following directories (the
directories will be created if they don't already exist):
libginac.a will go into
PREFIX/lib/ (or
LIBDIR) which defaults to
/usr/local/lib/. So will
libginac.so if the the configure script was
given the option --enable-shared. In that
case, the proper symlinks will be established as well (by running
ldconfig).All the header files will be installed into
PREFIX/include/GiNaC/ (or
INCLUDEDIR/GiNaC/, if
specified).All documentation (HTML, Postscript and DVI) will be stuffed
into
PREFIX/share/doc/GiNaC/
(or DATADIR/doc/GiNaC/, if
specified).Just for the record we will list some other useful make targets:
make clean deletes all files generated by
make, i.e. all the object files. In addition
make distclean removes all files generated by
configuration. And finally make uninstall removes
the installed library and header files.
Uninstallation does not work
after you have called make distclean since the
Makefile is itself generated by the configuration
from Makefile.in and hence deleted by
make distclean. There are two obvious ways out
of this dilemma. First, you can run the configuration again with
the same PREFIX thus creating a
Makefile with a working
uninstall target. Second, you can do it by hand
since you now know where all the files went during
installation.Basic ConceptsThis chapter will describe the different fundamental objects
that can be handled with GiNaC. But before doing so, it is worthwhile
introducing you to the more commonly used class of expressions,
representing a flexible meta-class for storing all mathematical
objects.ExpressionsThe most common class of objects a user deals with is the
expression ex, representing a mathematical object
like a variable, number, function, sum, product, etc... Expressions
may be put together to form new expressions, passed as arguments to
functions, and so on. Here is a little collection of valid
expressions:
Examples of expressions
ex MyEx1 = 5; // simple number
ex MyEx2 = x + 2*y; // polynomial in x and y
ex MyEx3 = (x + 1)/(x - 1); // rational expression
ex MyEx4 = sin(x + 2*y) + 3*z + 41; // containing a function
ex MyEx5 = MyEx4 + 1; // similar to above
Before describing the more fundamental objects that form the building
blocks of expressions we'll have a quick look under the hood by
describing how expressions are internally managed.Digression: Expressions are reference countedAn expression is extremely light-weight since internally it
works like a handle to the actual representation and really holds
nothing more than a pointer to some other object. What this means in
practice is that whenever you create two ex and set
the second equal to the first no copying process is involved. Instead,
the copying takes place as soon as you try to change the second.
Consider the simple sequence of code:
Simple copy-on-write semantics
#include <GiNaC/ginac.h>
int main()
{
symbol x("x"), y("y"), z("z");
ex e1, e2;
e1 = sin(x + 2*y) + 3*z + 41;
e2 = e1; // e2 points to same object as e1
cout << e2 << endl; // prints sin(x+2*y)+3*z+41
e2 += 1; // e2 is copied into a new object
cout << e2 << endl; // prints sin(x+2*y)+3*z+42
// ...
}
The line e2 = e1; creates a second expression
pointing to the object held already by e1. The
time involved for this operation is therefore constant, no matter how
large e1 was. Actual copying, however, must take
place in the line e2 += 1 because
e1 and e2 are not handles for
the same object any more. This concept is called
copy-on-write semantics. It increases
performance considerably whenever one object occurs multiple times and
represents a simple garbage collection scheme because when an
ex runs out of scope its destructor checks whether
other expressions handle the object it points to too and deletes the
object from memory if that turns out not to be the case. A slightly
less trivial example of differentiation using the chain-rule should
make clear how powerful this can be. Advanced
copy-on-write semantics
#include <GiNaC/ginac.h>
int main()
{
symbol x("x"), y("y");
ex e1 = x + 3*y;
ex e2 = pow(e1, 3);
ex e3 = diff(sin(e2), x); // first derivative of sin(e2) by x
cout << e1 << endl // prints x+3*y
<< e2 << endl // prints (x+3*y)^3
<< e3 << endl; // prints 3*(x+3*y)^2*cos((x+3*y)^3)
// ...
}
Here, e1 will actually be referenced three times
while e2 will be referenced two times. When the
power of an expression is built, that expression needs not be
copied. Likewise, since the derivative of a power of an expression can
be easily expressed in terms of that expression, no copying of
e1 is involved when e3 is
constructed. So, when e3 is constructed it will
print as 3*(x+3*y)^2*cos((x+3*y)^3) but the
argument of cos() only holds a reference to
e2 and the factor in front is just
3*e1^2.
As a user of GiNaC, you cannot see this mechanism of
copy-on-write semantics. When you insert an expression into a
second expression, the result behaves exactly as if the contents of
the first expression were inserted. But it may be useful to remember
that this is not what happens. Knowing this will enable you to write
much more efficient code.So much for expressions. But what exactly are these expressions
handles of? This will be answered in the following sections.The Class HierarchyGiNaC's class hierarchy consists of several classes representing
mathematical objects, all of which (except for ex
and some helpers) are internally derived from one abstract base class
called basic. You do not have to deal with objects
of class basic, instead you'll be dealing with
symbols and functions of symbols. You'll soon learn in this chapter
how many of the functions on symbols are really classes. This is
because simple symbolic arithmetic is not supported by languages like
C++ so in a certain way GiNaC has to implement its own arithmetic.To give an idea about what kinds of symbolic composits may be
built we have a look at the most important classes in the class
hierarchy. The dashed line symbolizes a "points to" or "handles"
relationship while the solid lines stand for "inherits from"
relationships.
Some of the classes shown here (the ones sitting in white boxes) are
abstract base classes that are of no interest at all for the user.
They are used internally in order to avoid code duplication if
two or more classes derived from them share certain features. An
example would be expairseq, which is a container
for a sequence of pairs each consisting of one expression and a number
(numeric). What is visible to
the user are the derived classes add and
mul, representing sums of terms and products,
respectively. We'll come back later to some more details about these
two classes and motivate the use of pairs in sums and products here.Digression: Internal representation of products and sumsAlthough it should be completely transparent for the user of
GiNaC a short discussion of this topic helps understanding the sources
and also explains performance to a large degree. Consider the
symbolic expression (a+2*b-c)*d, which could
naively be represented by a tree of linear containers for addition and
multiplication together with atomic leaves of symbols and integer
numbers in this fashion:
However, doing so results in a rather deeply nested tree which will
quickly become rather slow to manipulate. If we represent the sum
instead as a sequence of terms, each having a purely numeric
coefficient, the tree becomes much more flat.
The number 1 under the symbol d
is a hint that multiplication objects can be treated similarly where
the coeffiecients are interpreted as exponents
now. Addition of sums of terms or multiplication of products with
numerical exponents can be made very efficient with a
pair-representation. Internally, this handling is done by most CAS in
this way. It typically speeds up manipulations by an order of
magnitude. Now it should be clear, why both classes
add and mul are derived from the
same abstract class: the representation is the same, only the
interpretation differs. SymbolsSymbols are for symbolic manipulation what atoms are for
chemistry. You can declare objects of type symbol as any other object
simply by saying symbol x,y;. There is, however, a
catch in here having to do with the fact that C++ is a compiled
language. The information about the symbol's name is thrown away by
the compiler but at a later stage you may want to print expressions
holding your symbols. In order to avoid confusion GiNaC's symbols are
able to know their own name. This is accomplished by declaring its
name for output at construction time in the fashion symbol
x("x");.Although symbols can be assigned expressions for internal
reasons, you should not do it (and we are not going to tell you how it
is done). If you want to replace a symbol with something else in an
expression, you can use the expression's .subs()
method.NumbersFor storing numerical things, GiNaC uses Bruno Haible's library
CLN. The classes therein serve as foundation
classes for GiNaC. CLN stands for Class Library
for Numbers or alternatively for Common Lisp Numbers. In order to
find out more about CLN's internals the reader is
refered to the documentation of that library. Suffice to say that it
is by itself build on top of another library, the GNU Multiple
Precision library GMP, which is a extremely fast
library for arbitrary long integers and rationals as well as arbitrary
precision floating point numbers. It is very commonly used by several
popular cryptographic applications. CLN extends
GMP by several useful things: First, it introduces
the complex number field over either reals (i.e. floating point
numbers with arbitrary precision) or rationals. Second, it
automatically converts rationals to integers if the denominator is
unity and complex numbers to real numbers if the imaginary part
vanishes and also correctly treats algebraic functions. Third it
provides good implementations of state-of-the-art algorithms for all
trigonometric and hyperbolic functions as well as for calculation of
some useful constants.The user can construct an object of class
numeric in several ways. The following example
shows the four most important constructors: construction from
C-integer, construction of fractions from two integers, construction
from C-float and construction from a string.
Sample C++ program
#include <GiNaC/ginac.h>
int main()
{
numeric two(2); // exact integer 2
numeric r(2,3); // exact fraction 2/3
numeric e(2.71828); // floating point number
numeric p("3.1415926535897932385"); // floating point number
cout << two*p << endl; // floating point 6.283...
// ...
}
Note that all those constructors are explicit
which means you are not allowed to write numeric
two=2;. This is because the basic objects to be handled by
GiNaC are the expressions and we want to keep things simple and wish
objects like pow(x,2) to be handled the same way
as pow(x,a), which means that we need to allow a
general expression as base and exponent. Therefore there is an
implicit construction from a C-integer directly to an expression
handling a numeric in the first example. This design really becomes
convenient when one declares own functions having more than one
parameter but it forbids using implicit constructors because that
would lead to ambiguities.
We have seen now the distinction between exact numbers and
floating point numbers. Clearly, the user should never have to worry
about dynamically created exact numbers, since their "exactness"
always determines how they ought to be handled. The situation is
different for floating point numbers. Their accuracy is handled by
one global variable, called
Digits. (For those readers who know about Maple:
it behaves very much like Maple's Digits). All
objects of class numeric that are constructed from then on will be
stored with a precision matching that number of decimal digits:
Controlling the precision of floating point numbers
#include
<GiNaC/ginac.h>
void foo()
{
numeric three(3.0), one(1.0);
numeric x = one/three;
cout << "in " << Digits << " digits:" << endl;
cout << x << endl;
cout << Pi.evalf() << endl;
}
int main()
{
foo();
Digits = 60;
foo();
return 0;
}
The above example prints the following output to screen:
in 17 digits:
0.333333333333333333
3.14159265358979324
in 60 digits:
0.333333333333333333333333333333333333333333333333333333333333333333
3.14159265358979323846264338327950288419716939937510582097494459231
Tests on numbersOnce you have declared some numbers, assigned them to
expressions and done some arithmetic with them it is frequently
desired to retrieve some kind of information from them like asking
whether that number is integer, rational, real or complex. For those
cases GiNaC provides several useful methods. (Internally, they fall
back to invocations of certain CLN functions.)As an example, let's construct some rational number, multiply it
with some multiple of its denominator and check what comes out:
Sample test on objects of type numeric
#include <GiNaC/ginac.h>
int main()
{
numeric twentyone(21);
numeric ten(10);
numeric answer(21,5);
cout << answer.is_integer() << endl; // false, it's 21/5
answer *= ten;
cout << answer.is_integer() << endl; // true, it's 42 now!
// ...
}
Note that the variable answer is constructed here
as an integer but in an intermediate step it holds a rational number
represented as integer numerator and denominator. When multiplied by
10, the denominator becomes unity and the result is automatically
converted to a pure integer again. Internally, the underlying
CLN is responsible for this behaviour and we refer
the reader to CLN's documentation. Suffice to say
that the same behaviour applies to complex numbers as well as return
values of certain functions. Complex numbers are automatically
converted to real numbers if the imaginary part becomes zero. The
full set of tests that can be applied is listed in the following
table.
MethodReturns true if....is_zero()object is equal to zero.is_positive()object is not complex and greater than 0.is_integer()object is a (non-complex) integer.is_pos_integer()object is an integer and greater than 0.is_nonneg_integer()object is an integer and greater equal 0.is_even()object is an even integer.is_odd()object is an odd integer.is_prime()object is a prime integer (probabilistic primality test).is_rational()object is an exact rational number (integers are rational, too, as are complex extensions like 2/3+7/2*I).is_real()object is a real integer, rational or float (i.e. is not complex)ConstantsConstants behave pretty much like symbols except that that they return
some specific number when the method .evalf() is called.
The predefined known constants are:
NameCommon NameNumerical Value (35 digits)PiArchimedes' constant3.14159265358979323846264338327950288CatalanCatalan's constant0.91596559417721901505460351493238411EulerGammaEuler's (or Euler-Mascheroni) constant0.57721566490153286060651209008240243Fundamental operations: The <literal>power</literal>, <literal>add</literal> and <literal>mul</literal> classesSimple polynomial expressions are written down in GiNaC pretty
much like in other CAS. The necessary operators +,
-, * and /
have been overloaded to achieve this goal. When you run the following
program, the constructor for an object of type mul
is automatically called to hold the product of a
and b and then the constructor for an object of
type add is called to hold the sum of that
mul object and the number one:
Construction of <literal>add</literal> and <literal>mul</literal> objects
#include <GiNaC/ginac.h>
int main()
{
symbol a("a"), b("b");
ex MyTerm = 1+a*b;
// ...
}
For exponentiation, you have already seen the somewhat clumsy
(though C-ish) statement pow(x,2); to represent
x squared. This direct construction is necessary
since we cannot safely overload the constructor ^
in C++ to construct a power
object. If we did, it would have several counterintuitive effects:
Due to C's operator precedence,
2*x^2 would be parsed as (2*x)^2.
Due to the binding of the operator ^,
x^a^b would result in (x^a)^b.
This would be confusing since most (though not all) other CAS
interpret this as x^(a^b).
Also, expressions involving integer exponents are very
frequently used, which makes it even more dangerous to overload
^ since it is then hard to distinguish between the
semantics as exponentiation and the one for exclusive or. (It would
be embarassing to return 1 where one has requested
2^3.)
All effects are contrary to mathematical notation and differ from the
way most other CAS handle exponentiation, therefore overloading
^ is ruled out for GiNaC's C++ part. The situation
is different in ginsh, there the
exponentiation-^ exists. (Also note, that the
other frequently used exponentiation operator **
does not exist at all in C++).To be somewhat more precise, objects of the three classes
described here, are all containers for other expressions. An object
of class power is best viewed as a container with
two slots, one for the basis, one for the exponent. All valid GiNaC
expressions can be inserted. However, basic transformations like
simplifying pow(pow(x,2),3) to
x^6 automatically are only performed when
this is mathematically possible. If we replace the outer exponent
three in the example by some symbols a, the
simplification is not safe and will not be performed, since
a might be 1/2 and
x negative.Objects of type add and
mul are containers with an arbitrary number of
slots for expressions to be inserted. Again, simple and safe
simplifications are carried out like transforming
3*x+4-x to 2*x+4.The general rule is that when you construct such objects, GiNaC
automatically creates them in canonical form, which might differ from
the form you typed in your program. This allows for rapid comparison
of expressions, since after all a-a is simply zero.
Note, that the canonical form is not necessarily lexicographical
ordering or in any way easily guessable. It is only guaranteed that
constructing the same expression twice, either implicitly or
explicitly, results in the same canonical form.Built-in FunctionsThis chapter is not here yetImportant AlgorithmsIn this chapter the most important algorithms provided by GiNaC
will be described. Some of them are implemented as functions on
expressions, others are implemented as methods provided by expression
objects. If they are methods, there exists a wrapper function around
it, so you can alternatively call it in a functional way as shown in
the simple example:
Methods vs. wrapper functions
#include <GiNaC/ginac.h>
int main()
{
ex x = numeric(1.0);
cout << "As method: " << sin(x).evalf() << endl;
cout << "As function: " << evalf(sin(x)) << endl;
// ...
}
The general rule is that wherever methods accept one or more
parameters (arg1, arg2, ...)
the order of arguments the function wrapper accepts is the same but
preceded by the object to act on (object,
arg1, arg2, ...). This
approach is the most natural one in an OO model but it may lead to
confusion for MapleV users because where they would type
A:=x+1; subs(x=2,A); GiNaC would require
A=x+1; subs(A,x==2); (after proper declaration of A
and x). On the other hand, since MapleV returns 3 on
A:=x^2+3; coeff(A,x,0); (GiNaC:
A=pow(x,2)+3; coeff(A,x,0);) it is clear that
MapleV is not trying to be consistent here. Also, users of MuPAD will
in most cases feel more comfortable with GiNaC's convention. All
function wrappers are always implemented as simple inline functions
which just call the corresponding method and are only provided for
users uncomfortable with OO who are dead set to avoid method
invocations. Generally, a chain of function wrappers is much harder
to read than a chain of methods and should therefore be avoided if
possible. On the other hand, not everything in GiNaC is a method on
class ex and sometimes calling a function cannot be
avoided.
Polynomial ExpansionA polynomial in one or more variables has many equivalent
representations. Some useful ones serve a specific purpose. Consider
for example the trivariate polynomial 4*x*y + x*z + 20*y^2 +
21*y*z + 4*z^2. It is equivalent to the factorized
polynomial (x + 5*y + 4*z)*(4*y + z). Other
representations are the recursive ones where one collects for
exponents in one of the three variable. Since the factors are
themselves polynomials in the remaining two variables the procedure
can be repeated. In our expample, two possibilies would be
(4*y + z)*x + 20*y^2 + 21*y*z + 4*z^2 and
20*y^2 + (21*z + 4*x)*y + 4*z^2 + x*z.
To bring an expression into expanded form, its method
.expand() may be called. In our example above,
this corresponds to 4*x*y + x*z + 20*y^2 + 21*y*z +
4*z^2. Again, since the canonical form in GiNaC is not
easily guessable you should be prepared to see different orderings of
terms in such sums!Collecting expressionsAnother useful representation of multivariate polynomials is as
a univariate polynomial in one of the variables with the coefficients
being polynomials in the remaining variables. The method
collect() accomplishes this task:
#include <GiNaC/ginac.h>ex ex::collectsymbol const & s
Note that the original polynomial needs to be in expanded form in
order to be able to find the coefficients properly. The range of
occuring coefficients can be checked using the two methods
#include <GiNaC/ginac.h>int ex::degreesymbol const & sint ex::ldegreesymbol const & s
where degree() returns the highest coefficient and
ldegree() the lowest one. These two methods work
also reliably on non-expanded input polynomials. This is illustrated
in the following example:
Collecting expressions in multivariate polynomials
#include <GiNaC/ginac.h>
int main()
{
symbol x("x"), y("y");
ex PolyInp = 4*pow(x,3)*y + 5*x*pow(y,2) + 3*y
- pow(x+y,2) + 2*pow(y+2,2) - 8;
ex Poly = PolyInp.expand();
for (int i=Poly.ldegree(x); i<=Poly.degree(x); ++i) {
cout << "The x^" << i << "-coefficient is "
<< Poly.coeff(x,i) << endl;
}
cout << "As polynomial in y: "
<< Poly.collect(y) << endl;
// ...
}
When run, it returns an output in the following fashion:
The x^0-coefficient is y^2+11*y
The x^1-coefficient is 5*y^2-2*y
The x^2-coefficient is -1
The x^3-coefficient is 4*y
As polynomial in y: -x^2+(5*x+1)*y^2+(-2*x+4*x^3+11)*y
As always, the exact output may vary between different versions of
GiNaC or even from run to run since the internal canonical ordering is
not within the user's sphere of influence.Polynomial ArithmeticGCD and LCMThe functions for polynomial greatest common divisor and least common
multiple have the synopsis:
#include <GiNaC/normal.h>ex gcdconst ex *a, const ex *bex lcmconst ex *a, const ex *bThe functions gcd() and lcm() accepts two expressions
a and b as arguments and return
a new expression, their greatest common divisor or least common
multiple, respectively. If the polynomials a and
b are coprime gcd(a,b) returns 1
and lcm(a,b) returns the product of
a and b.
Polynomal GCD/LCM
#include <GiNaC/ginac.h>
int main()
{
symbol x("x"), y("y"), z("z");
ex P_a = 4*x*y + x*z + 20*pow(y, 2) + 21*y*z + 4*pow(z, 2);
ex P_b = x*y + 3*x*z + 5*pow(y, 2) + 19*y*z + 12*pow(z, 2);
ex P_gcd = gcd(P_a, P_b);
// x + 5*y + 4*z
ex P_lcm = lcm(P_a, P_b);
// 4*x*y^2 + 13*y*x*z + 20*y^3 + 81*y^2*z + 67*y*z^2 + 3*x*z^2 + 12*z^3
// ...
}
The <function>normal</function> methodWhile in common symbolic code gcd() and
lcm() are not too heavily used, some basic
simplification occurs frequently. Therefore
.normal(), which provides some basic form of
simplification, has become a method of class ex,
just like .expand().Symbolic DifferentiationSimple polynomial differentiation
#include <GiNaC/ginac.h>
int main()
{
symbol x("x"), y("y"), z("z");
ex P = pow(x, 5) + pow(x, 2) + y;
cout << P.diff(x,2) << endl; // 20*x^3 + 2
cout << P.diff(y) << endl; // 1
cout << P.diff(z) << endl; // 0
// ...
}
Differentiation with nontrivial functions
#include <GiNaC/ginac.h>
int main()
{
// To Be Done...
}
Series ExpansionExpressions know how to expand themselves as a Taylor series or
(more generally) a Laurent series. As in most conventional Computer
Algebra Systems no distinction is made between those two. There is a
class of its own for storing such series as well as a class for
storing the order of the series. A sample program could read:
Series expansion
#include <GiNaC/ginac.h>
int main()
{
symbol x("x");
numeric point(0);
ex MyExpr1 = sin(x);
ex MyExpr2 = 1/(x - pow(x, 2) - pow(x, 3));
ex MyTailor, MySeries;
MyTailor = MyExpr1.series(x, numZERO(), 5);
cout << MyExpr1 << " == " << MyTailor
<< " for small " << x << endl;
MySeries = MyExpr2.series(x, numZERO(), 7);
cout << MyExpr2 << " == " << MySeries
<< " for small " << x << endl;
\\ ...
}
Extending GiNaCLongish chapter follows here.A Comparison with other CASThis chapter will give you some information on how GiNaC
compares to other, traditional Computer Algebra Systems, like
Maple, Mathematica or
Reduce, where it has advantages and disadvantages
over these systems.AdvantagesGiNaC has several advantages over traditional Computer
Algebra Systems, like
familiar language: all common CAS implement their own
proprietary grammar which you have to learn first (and maybe learn
again when your vendor chooses to "enhance" it). With GiNaC you
can write your program in common C++, which is
standardized.structured data types: you can build up structured data
types using structs or classes
together with STL features instead of using unnamed lists of lists
of lists.strongly typed: in CAS, you usually have only one kind of
variables which can hold contents of an arbitrary type. This
4GL like feature is nice for novice programmers, but dangerous.
development tools: powerful development tools exist for
C++, like fancy editors (e.g. with automatic
indentation and syntax highlighting), debuggers, visualization
tools, documentation tools...modularization: C++ programs can
easily be split into modules by separating interface and
implementation.price: GiNaC is distributed under the GNU Public License
which means that it is free and available with source code. And
there are excellent C++-compilers for free, too.
extendable: you can add your own classes to GiNaC, thus
extending it on a very low level. Compare this to a traditional
CAS that you can usually only extend on a high level by writing in
the language defined by the parser. In particular, it turns out
to be almost impossible to fix bugs in a traditional system.
seemless integration: it is somewhere between difficult
and impossible to call CAS functions from within a program
written in C++ or any other programming
language and vice versa. With GiNaC, your symbolic routines
are part of your program. You can easily call third party
libraries, e.g. for numerical evaluation or graphical
interaction. All other approaches are much more cumbersome: they
range from simply ignoring the problem
(i.e. Maple) to providing a
method for "embedding" the system
(i.e. Yacas).efficiency: often large parts of a program do not need
symbolic calculations at all. Why use large integers for loop
variables or arbitrary precision arithmetics where double
accuracy is sufficient? For pure symbolic applications,
GiNaC is comparable in speed with other CAS.
DisadvantagesOf course it also has some disadvantages
not interactive: GiNaC programs have to be written in
an editor, compiled and executed. You cannot play with
expressions interactively. However, such an extension is not
inherently forbidden by design. In fact, two interactive
interfaces are possible: First, a simple shell that exposes GiNaC's
types to a command line can readily be written (and has been
written) and second, as a more consistent approach we plan
an integration with the CINTC++ interpreter.advanced features: GiNaC cannot compete with a program
like Reduce which exists for more than
30 years now or Maple which grows since
1981 by the work of dozens of programmers, with respect to
mathematical features. Integration, factorization, non-trivial
simplifications, limits etc. are missing in GiNaC (and are not
planned for the near future).portability: While the GiNaC library itself is designed
to avoid any platform dependent features (it should compile
on any ANSI compliant C++ compiler), the
currently used version of the CLN library (fast large integer and
arbitrary precision arithmetics) can be compiled only on systems
with a recently new C++ compiler from the
GNU Compiler Collection (GCC). GiNaC uses
recent language features like explicit constructors, mutable
members, RTTI, dynamic_casts and STL, so ANSI compliance is meant
literally. Recent GCC versions starting at
2.95, although itself not yet ANSI compliant, support all needed
features.
Why <literal>C++</literal>?Why did we choose to implement GiNaC in C++
instead of Java or any other language?
C++ is not perfect: type checking is not strict
(casting is possible), separation between interface and implementation
is not complete, object oriented design is not enforced. The main
reason is the often scolded feature of operator overloading in
C++. While it may be true that operating on classes
with a + operator is rarely meaningful, it is
perfectly suited for algebraic expressions. Writing 3x+5y as
3*x+5*y instead of
x.times(3).plus(y.times(5)) looks much more
natural. Furthermore, the main developers are more familiar with
C++ than with any other programming
language.ISO/IEC 14882:1998Programming Languages: C++CLN: A Class Library for NumbersBrunoHaiblehaible@ilog.frThe C++ Programming LanguageBjarneStroustrup30-201-88954-4Addison WesleyAlgorithms for Computer AlgebraKeithO.GeddesStephenR.CzaporGeorgeLabahn0-7923-9259-01992Kluwer Academic PublishersNorwell, MassachusettsComputer AlgebraSystems and Algorithms for Algebraic ComputationJ.H.DavenportY.SiretE.Tournier0-12-204230-11988Academic PressLondonIndexCLNobtaininggcdlcmgcd