GiNaC Tutorial An open framework for symbolic computation within the C++ programming language The GiNaC Group ChristianBauer
Christian.Bauer@Uni-Mainz.DE
AlexanderFrink
Alexander.Frink@Uni-Mainz.DE
RichardB.Kreckel
Richard.Kreckel@Uni-Mainz.DE
Others
whoever@ThEP.Physik.Uni-Mainz.DE
Introduction The 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. License The 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 GiNaC This 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 you After 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) Installation GiNaC's installation follows the spirit of most GNU software. It is easily installed on your system by three steps: configuration, build, installation. Prerequistes In 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. Configuration To 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-script Simple 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 GiNaC After 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. Installation To 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 Concepts This 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. Expressions The 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 counted An 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 Hierarchy GiNaC'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.
The GiNaC class hierarchy
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 sums Although 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:
Naive internal representation of <emphasis>d(a+2*b-c)</emphasis>
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.
Better internal representation of <emphasis>d(a+2*b-c)</emphasis>
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.
Symbols Symbols 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. Numbers For 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 numbers Once 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. Method Returns 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) Constants Constants behave pretty much like symbols except that that they return some specific number when the method .evalf() is called. The predefined known constants are: Name Common Name Numerical Value (35 digits) Pi Archimedes' constant 3.14159265358979323846264338327950288 Catalan Catalan's constant 0.91596559417721901505460351493238411 EulerGamma Euler's (or Euler-Mascheroni) constant 0.57721566490153286060651209008240243 Fundamental operations: The <literal>power</literal>, <literal>add</literal> and <literal>mul</literal> classes Simple 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 Functions This chapter is not here yet
Important Algorithms In 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 Expansion A 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 expressions Another 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::collect symbol 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::degree symbol const & s int ex::ldegree symbol 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 Arithmetic GCD and LCM The functions for polynomial greatest common divisor and least common multiple have the synopsis: #include <GiNaC/normal.h> ex gcd const ex *a, const ex *b ex lcm const ex *a, const ex *b The 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> method While 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 Differentiation Simple 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 Expansion Expressions 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 GiNaC Longish chapter follows here. A Comparison with other CAS This 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. Advantages GiNaC 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. Disadvantages Of 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 CINT C++ 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:1998 Programming Languages: C++ CLN: A Class Library for Numbers BrunoHaible
haible@ilog.fr
The C++ Programming Language BjarneStroustrup 3 0-201-88954-4 Addison Wesley Algorithms for Computer Algebra KeithO.Geddes StephenR.Czapor GeorgeLabahn 0-7923-9259-0 1992 Kluwer Academic Publishers
Norwell, Massachusetts
Computer Algebra Systems and Algorithms for Algebraic Computation J.H.Davenport Y.Siret E.Tournier 0-12-204230-1 1988 Academic Press
London
Index CLN obtaining gcd lcm gcd