GiNaC allows the output of expressions in a variety of different formats (see Input/output). This section will explain how expression output is implemented internally, and how to define your own output formats or change the output format of built-in algebraic objects. You will also want to read this section if you plan to write your own algebraic classes or functions.
All the different output formats are represented by a hierarchy of classes
rooted in the print_context class, defined in the print.h
header file:
print_dfltprint_latexprint_treeprint_csrcprint_csrc_floatfloat type
print_csrc_doubledouble type
print_csrc_cl_NThe print_context base class provides two public data members:
class print_context
{
...
public:
std::ostream & s;
unsigned options;
};
s is a reference to the stream to output to, while options
holds flags and modifiers. Currently, there is only one flag defined:
print_options::print_index_dimensions instructs the idx class
to print the index dimension which is normally hidden.
When you write something like std::cout << e, where e is
an object of class ex, GiNaC will construct an appropriate
print_context object (of a class depending on the selected output
format), fill in the s and options members, and call
void ex::print(const print_context & c, unsigned level = 0) const;
which in turn forwards the call to the print() method of the
top-level algebraic object contained in the expression.
Unlike other methods, GiNaC classes don't usually override their
print() method to implement expression output. Instead, the default
implementation basic::print(c, level) performs a run-time double
dispatch to a function selected by the dynamic type of the object and the
passed print_context. To this end, GiNaC maintains a separate method
table for each class, similar to the virtual function table used for ordinary
(single) virtual function dispatch.
The method table contains one slot for each possible print_context
type, indexed by the (internally assigned) serial number of the type. Slots
may be empty, in which case GiNaC will retry the method lookup with the
print_context object's parent class, possibly repeating the process
until it reaches the print_context base class. If there's still no
method defined, the method table of the algebraic object's parent class
is consulted, and so on, until a matching method is found (eventually it
will reach the combination basic/print_context, which prints the
object's class name enclosed in square brackets).
You can think of the print methods of all the different classes and output
formats as being arranged in a two-dimensional matrix with one axis listing
the algebraic classes and the other axis listing the print_context
classes.
Subclasses of basic can, of course, also overload basic::print()
to implement printing, but then they won't get any of the benefits of the
double dispatch mechanism (such as the ability for derived classes to
inherit only certain print methods from its parent, or the replacement of
methods at run-time).
The method table for a class is set up either in the definition of the class,
by passing the appropriate print_func<C>() option to
GINAC_IMPLEMENT_REGISTERED_CLASS_OPT() (See Adding classes, for
an example), or at run-time using set_print_func<T, C>(). The latter
can also be used to override existing methods dynamically.
The argument to print_func<C>() and set_print_func<T, C>() can
be a member function of the class (or one of its parent classes), a static
member function, or an ordinary (global) C++ function. The C template
parameter specifies the appropriate print_context type for which the
method should be invoked, while, in the case of set_print_func<>(), the
T parameter specifies the algebraic class (for print_func<>(),
the class is the one being implemented by
GINAC_IMPLEMENT_REGISTERED_CLASS_OPT).
For print methods that are member functions, their first argument must be of
a type convertible to a const C &, and the second argument must be an
unsigned.
For static members and global functions, the first argument must be of a type
convertible to a const T &, the second argument must be of a type
convertible to a const C &, and the third argument must be an
unsigned. A global function will, of course, not have access to
private and protected members of T.
The unsigned argument of the print methods (and of ex::print()
and basic::print()) is used for proper parenthesizing of the output
(and by print_tree for proper indentation). It can be used for similar
purposes if you write your own output formats.
The explanations given above may seem complicated, but in practice it's really simple, as shown in the following example. Suppose that we want to display exponents in LaTeX output not as superscripts but with little upwards-pointing arrows. This can be achieved in the following way:
void my_print_power_as_latex(const power & p,
const print_latex & c,
unsigned level)
{
// get the precedence of the 'power' class
unsigned power_prec = p.precedence();
// if the parent operator has the same or a higher precedence
// we need parentheses around the power
if (level >= power_prec)
c.s << '(';
// print the basis and exponent, each enclosed in braces, and
// separated by an uparrow
c.s << '{';
p.op(0).print(c, power_prec);
c.s << "}\\uparrow{";
p.op(1).print(c, power_prec);
c.s << '}';
// don't forget the closing parenthesis
if (level >= power_prec)
c.s << ')';
}
int main()
{
// a sample expression
symbol x("x"), y("y");
ex e = -3*pow(x, 3)*pow(y, -2) + pow(x+y, 2) - 1;
// switch to LaTeX mode
cout << latex;
// this prints "-1+{(y+x)}^{2}-3 \frac{x^{3}}{y^{2}}"
cout << e << endl;
// now we replace the method for the LaTeX output of powers with
// our own one
set_print_func<power, print_latex>(my_print_power_as_latex);
// this prints "-1+{{(y+x)}}\uparrow{2}-3 \frac{{x}\uparrow{3}}{{y}
// \uparrow{2}}"
cout << e << endl;
}
Some notes:
my_print_power_as_latex could also have been
a const basic &, the second one a const print_context &.
mul objects converting their operands to
power objects for the purpose of printing.
mul class.
power/print_latex method provided by GiNaC prints square roots
using \sqrt, but the above code doesn't.
It's not possible to restore a method table entry to its previous or default
value. Once you have called set_print_func(), you can only override
it with another call to set_print_func(), but you can't easily go back
to the default behavior again (you can, of course, dig around in the GiNaC
sources, find the method that is installed at startup
(power::do_print_latex in this case), and set_print_func that
one; that is, after you circumvent the C++ member access control...).
Symbolic functions employ a print method dispatch mechanism similar to the
one used for classes. The methods are specified with print_func<C>()
function options. If you don't specify any special print methods, the function
will be printed with its name (or LaTeX name, if supplied), followed by a
comma-separated list of arguments enclosed in parentheses.
For example, this is what GiNaC's ‘abs()’ function is defined like:
static ex abs_eval(const ex & arg) { ... }
static ex abs_evalf(const ex & arg) { ... }
static void abs_print_latex(const ex & arg, const print_context & c)
{
c.s << "{|"; arg.print(c); c.s << "|}";
}
static void abs_print_csrc_float(const ex & arg, const print_context & c)
{
c.s << "fabs("; arg.print(c); c.s << ")";
}
REGISTER_FUNCTION(abs, eval_func(abs_eval).
evalf_func(abs_evalf).
print_func<print_latex>(abs_print_latex).
print_func<print_csrc_float>(abs_print_csrc_float).
print_func<print_csrc_double>(abs_print_csrc_float));
This will display ‘abs(x)’ as ‘|x|’ in LaTeX mode and fabs(x)
in non-CLN C source output, but as abs(x) in all other formats.
There is currently no equivalent of set_print_func() for functions.
Creating a new output format involves subclassing print_context,
which is somewhat similar to adding a new algebraic class
(see Adding classes). There is a macro GINAC_DECLARE_PRINT_CONTEXT
that needs to go into the class definition, and a corresponding macro
GINAC_IMPLEMENT_PRINT_CONTEXT that has to appear at global scope.
Every print_context class needs to provide a default constructor
and a constructor from an std::ostream and an unsigned
options value.
Here is an example for a user-defined print_context class:
class print_myformat : public print_dflt
{
GINAC_DECLARE_PRINT_CONTEXT(print_myformat, print_dflt)
public:
print_myformat(std::ostream & os, unsigned opt = 0)
: print_dflt(os, opt) {}
};
print_myformat::print_myformat() : print_dflt(std::cout) {}
GINAC_IMPLEMENT_PRINT_CONTEXT(print_myformat, print_dflt)
That's all there is to it. None of the actual expression output logic is implemented in this class. It merely serves as a selector for choosing a particular format. The algorithms for printing expressions in the new format are implemented as print methods, as described above.
print_myformat is a subclass of print_dflt, so it behaves
exactly like GiNaC's default output format:
{
symbol x("x");
ex e = pow(x, 2) + 1;
// this prints "1+x^2"
cout << e << endl;
// this also prints "1+x^2"
e.print(print_myformat()); cout << endl;
...
}
To fill print_myformat with life, we need to supply appropriate
print methods with set_print_func(), like this:
// This prints powers with '**' instead of '^'. See the LaTeX output
// example above for explanations.
void print_power_as_myformat(const power & p,
const print_myformat & c,
unsigned level)
{
unsigned power_prec = p.precedence();
if (level >= power_prec)
c.s << '(';
p.op(0).print(c, power_prec);
c.s << "**";
p.op(1).print(c, power_prec);
if (level >= power_prec)
c.s << ')';
}
{
...
// install a new print method for power objects
set_print_func<power, print_myformat>(print_power_as_myformat);
// now this prints "1+x**2"
e.print(print_myformat()); cout << endl;
// but the default format is still "1+x^2"
cout << e << endl;
}