The structure<T> template provides an way to extend GiNaC with custom
algebraic classes that is easy to use but has its limitations, the most
severe of which being that you can't add any new member functions to
structures. To be able to do this, you need to write a new class definition
from scratch.
This section will explain how to implement new algebraic classes in GiNaC by giving the example of a simple 'string' class. After reading this section you will know how to properly declare a GiNaC class and what the minimum required member functions are that you have to implement. We only cover the implementation of a 'leaf' class here (i.e. one that doesn't contain subexpressions). Creating a container class like, for example, a class representing tensor products is more involved but this section should give you enough information so you can consult the source to GiNaC's predefined classes if you want to implement something more complicated.
All algebraic classes (that is, all classes that can appear in expressions)
in GiNaC are direct or indirect subclasses of the class basic. So a
basic * represents a generic pointer to an algebraic class. Working
with such pointers directly is cumbersome (think of memory management), hence
GiNaC wraps them into ex (see Expressions are reference counted).
To make such wrapping possible every algebraic class has to implement several
methods. Visitors (see Visitors and tree traversal), printing, and
(un)archiving (see Input/output) require helper methods too. But don't
worry, most of the work is simplified by the following macros (defined
in registrar.h):
GINAC_DECLARE_REGISTERED_CLASS
GINAC_IMPLEMENT_REGISTERED_CLASS
GINAC_IMPLEMENT_REGISTERED_CLASS_OPT
The GINAC_DECLARE_REGISTERED_CLASS macro inserts declarations
required for memory management, visitors, printing, and (un)archiving.
It takes the name of the class and its direct superclass as arguments.
The GINAC_DECLARE_REGISTERED_CLASS should be the first line after
the opening brace of the class definition.
GINAC_IMPLEMENT_REGISTERED_CLASS takes the same arguments as
GINAC_DECLARE_REGISTERED_CLASS. It initializes certain static
members of a class so that printing and (un)archiving works. The
GINAC_IMPLEMENT_REGISTERED_CLASS may appear anywhere else in
the source (at global scope, of course, not inside a function).
GINAC_IMPLEMENT_REGISTERED_CLASS_OPT is a variant of
GINAC_IMPLEMENT_REGISTERED_CLASS. It allows specifying additional
options, such as custom printing functions.
Now we will start implementing a new class mystring that allows
placing character strings in algebraic expressions (this is not very useful,
but it's just an example). This class will be a direct subclass of
basic. You can use this sample implementation as a starting point
for your own classes 1.
The code snippets given here assume that you have included some header files as follows:
#include <iostream>
#include <string>
#include <stdexcept>
using namespace std;
#include <ginac/ginac.h>
using namespace GiNaC;
Now we can write down the class declaration. The class stores a C++
string and the user shall be able to construct a mystring
object from a string:
class mystring : public basic
{
GINAC_DECLARE_REGISTERED_CLASS(mystring, basic)
public:
mystring(const string & s);
private:
string str;
};
GINAC_IMPLEMENT_REGISTERED_CLASS(mystring, basic)
The GINAC_DECLARE_REGISTERED_CLASS macro insert declarations required
for memory management, visitors, printing, and (un)archiving.
GINAC_IMPLEMENT_REGISTERED_CLASS initializes certain static members
of a class so that printing and (un)archiving works.
Now there are three member functions we have to implement to get a working class:
mystring(), the default constructor.
int compare_same_type(const basic & other), which is used internally
by GiNaC to establish a canonical sort order for terms. It returns 0, +1 or
-1, depending on the relative order of this object and the other
object. If it returns 0, the objects are considered equal.
Please notice: This has nothing to do with the (numeric) ordering
relationship expressed by <, >= etc (which cannot be defined
for non-numeric classes). For example, numeric(1).compare_same_type(numeric(2))
may return +1 even though 1 is clearly smaller than 2. Every GiNaC class
must provide a compare_same_type() function, even those representing
objects for which no reasonable algebraic ordering relationship can be
defined.
mystring(const string& s) which is the constructor
we declared.
Let's proceed step-by-step. The default constructor looks like this:
mystring::mystring() { }
In the default constructor you should set all other member variables to
reasonable default values (we don't need that here since our str
member gets set to an empty string automatically).
Our compare_same_type() function uses a provided function to compare
the string members:
int mystring::compare_same_type(const basic & other) const
{
const mystring &o = static_cast<const mystring &>(other);
int cmpval = str.compare(o.str);
if (cmpval == 0)
return 0;
else if (cmpval < 0)
return -1;
else
return 1;
}
Although this function takes a basic &, it will always be a reference
to an object of exactly the same class (objects of different classes are not
comparable), so the cast is safe. If this function returns 0, the two objects
are considered equal (in the sense that A-B=0), so you should compare
all relevant member variables.
Now the only thing missing is our constructor:
mystring::mystring(const string& s) : str(s) { }
No surprises here. We set the str member from the argument.
That's it! We now have a minimal working GiNaC class that can store strings in algebraic expressions. Let's confirm that the RTTI works:
ex e = mystring("Hello, world!");
cout << is_a<mystring>(e) << endl;
// -> 1 (true)
cout << ex_to<basic>(e).class_name() << endl;
// -> mystring
Obviously it does. Let's see what the expression e looks like:
cout << e << endl;
// -> [mystring object]
Hm, not exactly what we expect, but of course the mystring class
doesn't yet know how to print itself. This can be done either by implementing
the print() member function, or, preferably, by specifying a
print_func<>() class option. Let's say that we want to print the string
surrounded by double quotes:
class mystring : public basic
{
...
protected:
void do_print(const print_context & c, unsigned level = 0) const;
...
};
void mystring::do_print(const print_context & c, unsigned level) const
{
// print_context::s is a reference to an ostream
c.s << '\"' << str << '\"';
}
The level argument is only required for container classes to
correctly parenthesize the output.
Now we need to tell GiNaC that mystring objects should use the
do_print() member function for printing themselves. For this, we
replace the line
GINAC_IMPLEMENT_REGISTERED_CLASS(mystring, basic)
with
GINAC_IMPLEMENT_REGISTERED_CLASS_OPT(mystring, basic,
print_func<print_context>(&mystring::do_print))
Let's try again to print the expression:
cout << e << endl;
// -> "Hello, world!"
Much better. If we wanted to have mystring objects displayed in a
different way depending on the output format (default, LaTeX, etc.), we
would have supplied multiple print_func<>() options with different
template parameters (print_dflt, print_latex, etc.),
separated by dots. This is similar to the way options are specified for
symbolic functions. See Printing, for a more in-depth description of the
way expression output is implemented in GiNaC.
The mystring class can be used in arbitrary expressions:
e += mystring("GiNaC rulez");
cout << e << endl;
// -> "GiNaC rulez"+"Hello, world!"
(GiNaC's automatic term reordering is in effect here), or even
e = pow(mystring("One string"), 2*sin(Pi-mystring("Another string")));
cout << e << endl;
// -> "One string"^(2*sin(-"Another string"+Pi))
Whether this makes sense is debatable but remember that this is only an example. At least it allows you to implement your own symbolic algorithms for your objects.
Note that GiNaC's algebraic rules remain unchanged:
e = mystring("Wow") * mystring("Wow");
cout << e << endl;
// -> "Wow"^2
e = pow(mystring("First")-mystring("Second"), 2);
cout << e.expand() << endl;
// -> -2*"First"*"Second"+"First"^2+"Second"^2
There's no way to, for example, make GiNaC's add class perform string
concatenation. You would have to implement this yourself.
When dealing with objects that are just a little more complicated than the
simple string objects we have implemented, chances are that you will want to
have some automatic simplifications or canonicalizations performed on them.
This is done in the evaluation member function eval(). Let's say that
we wanted all strings automatically converted to lowercase with
non-alphabetic characters stripped, and empty strings removed:
class mystring : public basic
{
...
public:
ex eval(int level = 0) const;
...
};
ex mystring::eval(int level) const
{
string new_str;
for (size_t i=0; i<str.length(); i++) {
char c = str[i];
if (c >= 'A' && c <= 'Z')
new_str += tolower(c);
else if (c >= 'a' && c <= 'z')
new_str += c;
}
if (new_str.length() == 0)
return 0;
else
return mystring(new_str).hold();
}
The level argument is used to limit the recursion depth of the
evaluation. We don't have any subexpressions in the mystring
class so we are not concerned with this. If we had, we would call the
eval() functions of the subexpressions with level - 1 as
the argument if level != 1. The hold() member function
sets a flag in the object that prevents further evaluation. Otherwise
we might end up in an endless loop. When you want to return the object
unmodified, use return this->hold();.
Let's confirm that it works:
ex e = mystring("Hello, world!") + mystring("!?#");
cout << e << endl;
// -> "helloworld"
e = mystring("Wow!") + mystring("WOW") + mystring(" W ** o ** W");
cout << e << endl;
// -> 3*"wow"
We have implemented only a small set of member functions to make the class work in the GiNaC framework. There are two functions that are not strictly required but will make operations with objects of the class more efficient:
unsigned calchash() const;
bool is_equal_same_type(const basic & other) const;
The calchash() method returns an unsigned hash value for the
object which will allow GiNaC to compare and canonicalize expressions much
more efficiently. You should consult the implementation of some of the built-in
GiNaC classes for examples of hash functions. The default implementation of
calchash() calculates a hash value out of the tinfo_key of the
class and all subexpressions that are accessible via op().
is_equal_same_type() works like compare_same_type() but only
tests for equality without establishing an ordering relation, which is often
faster. The default implementation of is_equal_same_type() just calls
compare_same_type() and tests its result for zero.
For a real algebraic class, there are probably some more functions that you might want to provide:
bool info(unsigned inf) const;
ex evalf(int level = 0) const;
ex series(const relational & r, int order, unsigned options = 0) const;
ex derivative(const symbol & s) const;
If your class stores sub-expressions (see the scalar product example in the previous section) you will probably want to override
size_t nops() cont;
ex op(size_t i) const;
ex & let_op(size_t i);
ex subs(const lst & ls, const lst & lr, unsigned options = 0) const;
ex map(map_function & f) const;
let_op() is a variant of op() that allows write access. The
default implementations of subs() and map() use it, so you have
to implement either let_op(), or subs() and map().
You can, of course, also add your own new member functions. Remember
that the RTTI may be used to get information about what kinds of objects
you are dealing with (the position in the class hierarchy) and that you
can always extract the bare object from an ex by stripping the
ex off using the ex_to<mystring>(e) function when that
should become a need.
That's it. May the source be with you!
GiNaC used to use a custom run time type information system (RTTI). It was
removed from GiNaC. Thus, one needs to rewrite constructors which set
tinfo_key (which does not exist any more). For example,
myclass::myclass() : inherited(&myclass::tinfo_static) {}
needs to be rewritten as
myclass::myclass() {}
[1] The self-contained source for this example is included in GiNaC, see the doc/examples/mystring.cpp file.