[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
4.5 Symbols
Symbolic indeterminates, or symbols for short, are for symbolic manipulation what atoms are for chemistry.
A typical symbol definition looks like this:
symbol x("x"); |
This definition actually contains three very different things:
- a C++ variable named
x
- a
symbol
object stored in this C++ variable; this object represents the symbol in a GiNaC expression - the string
"x"
which is the name of the symbol, used (almost) exclusively for printing expressions holding the symbol
Symbols have an explicit name, supplied as a string during construction, because in C++, variable names can't be used as values, and the C++ compiler throws them away during compilation.
It is possible to omit the symbol name in the definition:
symbol x; |
In this case, GiNaC will assign the symbol an internal, unique name of the
form symbolNNN
. This won't affect the usability of the symbol but
the output of your calculations will become more readable if you give your
symbols sensible names (for intermediate expressions that are only used
internally such anonymous symbols can be quite useful, however).
Now, here is one important property of GiNaC that differentiates it from
other computer algebra programs you may have used: GiNaC does not use
the names of symbols to tell them apart, but a (hidden) serial number that
is unique for each newly created symbol
object. If you want to use
one and the same symbol in different places in your program, you must only
create one symbol
object and pass that around. If you create another
symbol, even if it has the same name, GiNaC will treat it as a different
indeterminate.
Observe:
ex f(int n) { symbol x("x"); return pow(x, n); } int main() { symbol x("x"); ex e = f(6); cout << e << endl; // prints "x^6" which looks right, but... cout << e.degree(x) << endl; // ...this doesn't work. The symbol "x" here is different from the one // in f() and in the expression returned by f(). Consequently, it // prints "0". } |
One possibility to ensure that f()
and main()
use the same
symbol is to pass the symbol as an argument to f()
:
ex f(int n, const ex & x) { return pow(x, n); } int main() { symbol x("x"); // Now, f() uses the same symbol. ex e = f(6, x); cout << e.degree(x) << endl; // prints "6", as expected } |
Another possibility would be to define a global symbol x
that is used
by both f()
and main()
. If you are using global symbols and
multiple compilation units you must take special care, however. Suppose
that you have a header file ‘globals.h’ in your program that defines
a symbol x("x");
. In this case, every unit that includes
‘globals.h’ would also get its own definition of x
(because
header files are just inlined into the source code by the C++ preprocessor),
and hence you would again end up with multiple equally-named, but different,
symbols. Instead, the ‘globals.h’ header should only contain a
declaration like extern symbol x;
, with the definition of
x
moved into a C++ source file such as ‘globals.cpp’.
A different approach to ensuring that symbols used in different parts of your program are identical is to create them with a factory function like this one:
const symbol & get_symbol(const string & s) { static map<string, symbol> directory; map<string, symbol>::iterator i = directory.find(s); if (i != directory.end()) return i->second; else return directory.insert(make_pair(s, symbol(s))).first->second; } |
This function returns one newly constructed symbol for each name that is passed in, and it returns the same symbol when called multiple times with the same name. Using this symbol factory, we can rewrite our example like this:
ex f(int n) { return pow(get_symbol("x"), n); } int main() { ex e = f(6); // Both calls of get_symbol("x") yield the same symbol. cout << e.degree(get_symbol("x")) << endl; // prints "6" } |
Instead of creating symbols from strings we could also have
get_symbol()
take, for example, an integer number as its argument.
In this case, we would probably want to give the generated symbols names
that include this number, which can be accomplished with the help of an
ostringstream
.
In general, if you're getting weird results from GiNaC such as an expression ‘x-x’ that is not simplified to zero, you should check your symbol definitions.
As we said, the names of symbols primarily serve for purposes of expression output. But there are actually two instances where GiNaC uses the names for identifying symbols: When constructing an expression from a string, and when recreating an expression from an archive (see section Input and output of expressions).
In addition to its name, a symbol may contain a special string that is used in LaTeX output:
symbol x("x", "\\Box"); |
This creates a symbol that is printed as "x
" in normal output, but
as "\Box
" in LaTeX code (See section Input and output of expressions, for more
information about the different output formats of expressions in GiNaC).
GiNaC automatically creates proper LaTeX code for symbols having names of
greek letters (‘alpha’, ‘mu’, etc.).
Symbols in GiNaC can't be assigned values. If you need to store results of
calculations and give them a name, use C++ variables of type ex
.
If you want to replace a symbol in an expression with something else, you
can invoke the expression's .subs()
method
(see section Substituting expressions).
By default, symbols are expected to stand in for complex values, i.e. they live
in the complex domain. As a consequence, operations like complex conjugation,
for example (see section Complex expressions), do not evaluate if applied
to such symbols. Likewise log(exp(x))
does not evaluate to x
,
because of the unknown imaginary part of x
.
On the other hand, if you are sure that your symbols will hold only real
values, you would like to have such functions evaluated. Therefore GiNaC
allows you to specify
the domain of the symbol. Instead of symbol x("x");
you can write
realsymbol x("x");
to tell GiNaC that x
stands in for real values.
Furthermore, it is also possible to declare a symbol as positive. This will,
for instance, enable the automatic simplification of abs(x)
into
x
. This is done by declaring the symbol as possymbol x("x");
.
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |