Sometimes it's useful to check whether a given expression is a plain number, a sum, a polynomial with integer coefficients, or of some other specific type. GiNaC provides a couple of functions for this:
bool is_a<T>(const ex & e);
bool is_exactly_a<T>(const ex & e);
bool ex::info(unsigned flag);
unsigned ex::return_type() const;
return_type_t ex::return_type_tinfo() const;
When the test made by is_a<T>() returns true, it is safe to call
one of the functions ex_to<T>(), where T is one of the
class names (See The class hierarchy, for a list of all classes). For
example, assuming e is an ex:
{
...
if (is_a<numeric>(e))
numeric n = ex_to<numeric>(e);
...
}
is_a<T>(e) allows you to check whether the top-level object of
an expression ‘e’ is an instance of the GiNaC class ‘T’
(See The class hierarchy, for a list of all classes). This is most useful,
e.g., for checking whether an expression is a number, a sum, or a product:
{
symbol x("x");
ex e1 = 42;
ex e2 = 4*x - 3;
is_a<numeric>(e1); // true
is_a<numeric>(e2); // false
is_a<add>(e1); // false
is_a<add>(e2); // true
is_a<mul>(e1); // false
is_a<mul>(e2); // false
}
In contrast, is_exactly_a<T>(e) allows you to check whether the
top-level object of an expression ‘e’ is an instance of the GiNaC
class ‘T’, not including parent classes.
The info() method is used for checking certain attributes of
expressions. The possible values for the flag argument are defined
in ginac/flags.h, the most important being explained in the following
table:
|
To determine whether an expression is commutative or non-commutative and if
so, with which other expressions it would commutate, you use the methods
return_type() and return_type_tinfo(). See Non-commutative objects,
for an explanation of these.
Many GiNaC classes, like add, mul, lst, and
function, act as containers for subexpressions. For example, the
subexpressions of a sum (an add object) are the individual terms,
and the subexpressions of a function are the function's arguments.
GiNaC provides several ways of accessing subexpressions. The first way is to use the two methods
size_t ex::nops();
ex ex::op(size_t i);
nops() determines the number of subexpressions (operands) contained
in the expression, while op(i) returns the i-th
(0..nops()-1) subexpression. In the case of a power object,
op(0) will return the basis and op(1) the exponent. For
indexed objects, op(0) is the base expression and op(i),
i>0 are the indices.
The second way to access subexpressions is via the STL-style random-access
iterator class const_iterator and the methods
const_iterator ex::begin();
const_iterator ex::end();
begin() returns an iterator referring to the first subexpression;
end() returns an iterator which is one-past the last subexpression.
If the expression has no subexpressions, then begin() == end(). These
iterators can also be used in conjunction with non-modifying STL algorithms.
Here is an example that (non-recursively) prints the subexpressions of a given expression in three different ways:
{
ex e = ...
// with nops()/op()
for (size_t i = 0; i != e.nops(); ++i)
cout << e.op(i) << endl;
// with iterators
for (const_iterator i = e.begin(); i != e.end(); ++i)
cout << *i << endl;
// with iterators and STL copy()
std::copy(e.begin(), e.end(), std::ostream_iterator<ex>(cout, "\n"));
}
op()/nops() and const_iterator only access an
expression's immediate children. GiNaC provides two additional iterator
classes, const_preorder_iterator and const_postorder_iterator,
that iterate over all objects in an expression tree, in preorder or postorder,
respectively. They are STL-style forward iterators, and are created with the
methods
const_preorder_iterator ex::preorder_begin();
const_preorder_iterator ex::preorder_end();
const_postorder_iterator ex::postorder_begin();
const_postorder_iterator ex::postorder_end();
The following example illustrates the differences between
const_iterator, const_preorder_iterator, and
const_postorder_iterator:
{
symbol A("A"), B("B"), C("C");
ex e = lst(lst(A, B), C);
std::copy(e.begin(), e.end(),
std::ostream_iterator<ex>(cout, "\n"));
// {A,B}
// C
std::copy(e.preorder_begin(), e.preorder_end(),
std::ostream_iterator<ex>(cout, "\n"));
// {{A,B},C}
// {A,B}
// A
// B
// C
std::copy(e.postorder_begin(), e.postorder_end(),
std::ostream_iterator<ex>(cout, "\n"));
// A
// B
// {A,B}
// C
// {{A,B},C}
}
Finally, the left-hand side and right-hand side expressions of objects of
class relational (and only of these) can also be accessed with the
methods
ex ex::lhs();
ex ex::rhs();
Expressions can be compared with the usual C++ relational operators like
==, >, and < but if the expressions contain symbols,
the result is usually not determinable and the result will be false,
except in the case of the != operator. You should also be aware that
GiNaC will only do the most trivial test for equality (subtracting both
expressions), so something like (pow(x,2)+x)/x==x+1 will return
false.
Actually, if you construct an expression like a == b, this will be
represented by an object of the relational class (see Relations)
which is not evaluated until (explicitly or implicitly) cast to a bool.
There are also two methods
bool ex::is_equal(const ex & other);
bool ex::is_zero();
for checking whether one expression is equal to another, or equal to zero,
respectively. See also the method ex::is_zero_matrix(),
see Matrices.
Sometimes it is necessary to establish a mathematically well-defined ordering
on a set of arbitrary expressions, for example to use expressions as keys
in a std::map<> container, or to bring a vector of expressions into
a canonical order (which is done internally by GiNaC for sums and products).
The operators <, > etc. described in the last section cannot
be used for this, as they don't implement an ordering relation in the
mathematical sense. In particular, they are not guaranteed to be
antisymmetric: if ‘a’ and ‘b’ are different expressions, and
a < b yields false, then b < a doesn't necessarily
yield true.
By default, STL classes and algorithms use the < and ==
operators to compare objects, which are unsuitable for expressions, but GiNaC
provides two functors that can be supplied as proper binary comparison
predicates to the STL:
class ex_is_less : public std::binary_function<ex, ex, bool> {
public:
bool operator()(const ex &lh, const ex &rh) const;
};
class ex_is_equal : public std::binary_function<ex, ex, bool> {
public:
bool operator()(const ex &lh, const ex &rh) const;
};
For example, to define a map that maps expressions to strings you
have to use
std::map<ex, std::string, ex_is_less> myMap;
Omitting the ex_is_less template parameter will introduce spurious
bugs because the map operates improperly.
Other examples for the use of the functors:
std::vector<ex> v;
// fill vector
...
// sort vector
std::sort(v.begin(), v.end(), ex_is_less());
// count the number of expressions equal to '1'
unsigned num_ones = std::count_if(v.begin(), v.end(),
std::bind2nd(ex_is_equal(), 1));
The implementation of ex_is_less uses the member function
int ex::compare(const ex & other) const;
which returns 0 if *this and other are equal, -1
if *this sorts before other, and 1 if *this sorts
after other.