POOMA banner

POOMA Tutorial 11
Text Input and Outupt

Contents:
    Introduction
    The Inform Class
    Formatted ASCII Output
        The PrintArray Class
        dbprint() and Related Function

Introduction

Standard C++ I/O mechanisms, of course, remain available to POOMA codes. Many POOMA classes have operator<<() defined to write an ASCII representation of an instance to a stream. For example,

Range<3> r(Range<1>(2,10,2),Range<1>(1,3,1),Range<1>(3));
std::cout << "r = " << r << std::endl;

will produce the following output to stdout:

r = [2:10:2,1:3:1,0:2:1]
Classes providing operator<<() include
 
Array DynamicArray Field  
Loc Interval Range IndirectionList
Vector Tensor TinyMatrix  
UniformGridLayout GridLayout    
UniformRectilinearMesh RectilinearMesh    
ParticleBCItem UniformLayout SpatialLayout  

Standard C++ input from stdin or files will read values into variables of intrinsic C++ types. The argc and argv variables work as usual for command-line arguments, except that you should first pass them through Pooma::initialize() as described in the tutorial on compiling and running POOMA programs to intercept global POOMA command-line options.

POOMA provides additional enhancements for stream output (the Inform class) and for readable, formatted output of large array-like containers (and views of them). The PrintArray class manages formatting, and the global dbprint() and other db*() functions provide convenient shortcuts and a means for printing POOMA container data values interactively from debuggers.

For more serious output and input of data, such as restart files and Field or Array data from large program runs, POOMA provides extensible classes and mechanisms for binary file I/O.

The Inform Class

POOMA includes an I/O utility class called Inform. This class is basically a smarter ostream: as well as printing the values supplied by the programs that use it, it can also format the output to include an optional prefix string, and print out the identifier of the parallel context in which it is used. In addition, it can be used to print messages to multiple output destinations, such as a log file plus standard out.

In normal usage, programs send values to Informs using the overloaded operator<<, just as if they were ostreams. Each message is assigned the Inform's current level of interest; lower level numbers indicate more important or more interesting messages. Each Inform also stores a threshold level internally, and only prints messages whose level numbers are less than or equal to that threshold. The threshold value for an Inform object can be obtained with the outputLevel() method; the current level for the next message can be obtained with the messageLevel() method. Both methods have associated set methods taking integer arguments to modify these values. A quick way to turn off output from an Inform object is to set the output level to a special "off" setting, by calling setOutputLevel(Inform::off).

When running with multiple threads in a context, only one thread does the output, either for standard C++ stream output, or for Inform output. This is the "control" thread, which manages task assignment to the others. It is important to note that any output to an Inform which reads data from a multi-patch container is independent of whether other threads might be currently modifying those values. To avoid this, insert a call to Pooma::blockAndEvaluate() before the output statement:

Array<2,double,MultiPatch<GridTag,Brick> > a(...);
Inform pout;
Pooma::blockAndEvaluate();
pout << "a(23,42) = " << a(23,42) << std::endl;

By default, a newly-created Inform will only print out messages sent to it on context 0, rather than on all contexts. Programs may change this behavior by calling the method printContext(), with the ID of the context on which output is to appear as its argument. If the argument to this method is the constant Inform::allContexts, then subsequent messages will be printed on all contexts being used by the program, rather than just one. (Note: currently, POOMA is limited to one context, so this does not yet actually do anything.)

Informs can be constructed in three different ways. The first, and simplest, prints messages to cout. By default, output is displayed on context 0, and has no prefix. The other two constructors allow the calling program to specify the file to which output is to be sent, and the mode with which that file is to be opened, or the C++ ostream to which output is to be appended:

    Inform(const char *prefix = 0,
           Context_t context = 0);

    Inform(const char *prefix,
           const char *fname,
           int writemode,
           Context_t context = 0);

    Inform(const char *prefix,
           std::ostream &outstream,
           Context_t context = 0);

Other methods are provided to get and set the prefix to be displayed in front of messages, the Inform's context, the current level of interest of messages, and the threshold for displaying messages. An overloaded set of open() methods are also provided to open more output streams within the Inform. These methods return an ID which can be used to select particular streams when setting such things as the level of interest. Finally, most of the standard ostream manipulators and operator<<()s are provided.

Formatted ASCII Output

The POOMA PrintArray class has templated print() methods that print readable formatted output of large containers of values. It provides methods for controlling formatting parameters like the number of values per line, numeric format, and precision.

The global dbprint() template functions are a procedural interface around PrintArray, used with a set of global functions for setting common formatting parameters shared by all subsequent dbprint() invocations. These are useful shorthand for ASCII output from source code, but more importantly they provide a means to set up nontemplate output functions callable interactively from within a debugger. This is helpful for debugging POOMA programs by examining values from Arrays and other containers.

The PrintArray Class

The typical way to use PrintArray is to construct a PrintArray object, then use its print() methods for sending formatted ASCII output of POOMA container data to a stream such as cout or an Inform object. The constructor accepts values for six formatting parameters, which are maintained as member data in the object.

PrintArray(int domainWidth = 3, int dataWidth =10, 
           int dataPrecision = 4, int carReturn = -1, 
           bool scientific = false, int spacing = 1);
It has methods to (re)set and get current values for these formatting parameters. The following lists the methods and describes the parameters:
setDomainWidth(), domainWidth() :
The output format includes (base:bound:stride,base:bound:stride) prefixes at the beginning of each row of values. This controls the number of columns (digits) to allow for each base, bound, or stride value.
setDataWidth(), dataWidth() :
Number of columns per numeric data item. For POOMA multicomponent types such as Vector and Tensor, this is columns per component.
setDataPrecision(), dataPrecision() :
If scientific is true, the number of digits past the decimal point; otherwise, the total number of significant digits.
setCarReturn(), carReturn() :
If less than 0, print all values in a row (first array index) on one line of output. If greater than 0, specifies the number of values to print before breaking the output with a carriage return.
setScientific(), scientific() :
Whether or not to use scientific notation in output formatting.
setsSpacing(), spacing() :
Number of spaces between each data item. For POOMA multicomponent types such as Vector and Tensor, this is spaces between each whole object.

The print() methods of PrintArray are member templates:

template<class S, class A>
void print(S &s, const A &a) const;

template<class S, class A, class DomainType>
void print(S &s, const A &a, const DomainType &d) const;

These take an output stream, a container object, and an optional domain object for explicitly subsetting the container. They work with POOMA Field, Array, and DynamicArray container objects (including attributes from Particles), but are not restricted to these. The only restrictions are that the container must export an enum value dimensions, such as Array::dimensions, and must have an array-indexing capability such that operator()(int i0, int i1, ..., int iN) returns a contained data value. (Here, N=dimensions-1.)

If you pass in a view of an Array, for example, to the first prototype, the output will show zero-based, unit-stride indexing rather than the original-Array indexes specified by the view. To avoid this, use the second prototype and pass in the whole Array and a view-subsetting domain object, such as a Range, separately. These code snips illustrate the difference, and show what the output is like for a 3D Array:

Range<3> r(Range<1>(2,10,2),Range<1>(1,3,1),Range<1>(3));
Array<3> a(20,20,20); // ... assign values to a ...

Inform pout;   // An output stream
PrintArray pa; // Use defaults for formatting parameters

pa.print(pout, a(r));
prints
~~~~~~~~~~~~~~ (0:4:1,0:2:1,0:2:1) ~~~~~~~~~~~~~~


(0:4:1,0:2:1,0):
----------------------------------------------------
(000:004:001,000,000) =        2.5        4.5        6.5        8.5       10.5
(000:004:001,001,000) =        2.5        4.5        6.5        8.5       10.5
(000:004:001,002,000) =        2.5        4.5        6.5        8.5       10.5


(0:4:1,0:2:1,1):
----------------------------------------------------
(000:004:001,000,001) =        2.5        4.5        6.5        8.5       10.5
(000:004:001,001,001) =        2.5        4.5        6.5        8.5       10.5
(000:004:001,002,001) =        2.5        4.5        6.5        8.5       10.5


(0:4:1,0:2:1,2):
----------------------------------------------------
(000:004:001,000,002) =        2.5        4.5        6.5        8.5       10.5
(000:004:001,001,002) =        2.5        4.5        6.5        8.5       10.5
(000:004:001,002,002) =        2.5        4.5        6.5        8.5       10.5
while
pa.print(pout, a, r);
prints
~~~~~~~~~~~~~~ (2:10:2,1:3:1,0:2:1) ~~~~~~~~~~~~~~


(2:10:2,1:3:1,0):
----------------------------------------------------
(002:010:002,001,000) =        2.5        4.5        6.5        8.5       10.5
(002:010:002,002,000) =        2.5        4.5        6.5        8.5       10.5
(002:010:002,003,000) =        2.5        4.5        6.5        8.5       10.5


(2:10:2,1:3:1,1):
----------------------------------------------------
(002:010:002,001,001) =        2.5        4.5        6.5        8.5       10.5
(002:010:002,002,001) =        2.5        4.5        6.5        8.5       10.5
(002:010:002,003,001) =        2.5        4.5        6.5        8.5       10.5


(2:10:2,1:3:1,2):
----------------------------------------------------
(002:010:002,001,002) =        2.5        4.5        6.5        8.5       10.5
(002:010:002,002,002) =        2.5        4.5        6.5        8.5       10.5
(002:010:002,003,002) =        2.5        4.5        6.5        8.5       10.5

dbprint() and Related Functions

Many debuggers have a command prompt or expression-evaluation window and allow interactive calling of functions with simple arguments. Few, if any, of these debuggers have a convenient means to invoke template functions even when the templates have been instantiated in the executable code, and none allow interactive construction of objects or invocation of objects' member functions, whether the associated class and/or member functions are templated or not.

Recognizing this, we provide the dbprint() function templates, which are a procedural interface to the PrintArray::print() member templates:

template<class Container>
void dbprint(const Container &c);

template<class Container, class DomainType>
void dbprint(const Container &c, const DomainType &domain);

template<class Container>
void dbprint(const Container &c, const int &i0);

template<class Container>
void dbprint(const Container &c, const int &i0, const int &i1);
// ...

template<class Container>
void dbprint(const Container &c, const int &i0, ..., const int &i20);

The first two prototypes map directly to the PrintArray::print() functions described in the previous section. The remaining prototypes are for printing single container elements with scalar indexing, and for printing views using sets of integers for base, bound, and stride values in the various dimensions. Prototypes for 1 through 21 integer arguments, skipping {11,13,17,19,20}, allow for "sensible" interpretation of lists of integers as single-element or multi-element views of containers having dimensionality 1 through 7:

dimensions
single element
(base,bound)
for each dimension;
all stride 1
(base,bound,stride)
for each dimension
0:i0-1:1
in each dimension
1
1
2
3
1
2
2
4
6
1
3
3
6
9
1
4
4
8
12
1
5
5
10
15
1
6
6
12
18
1
7
7
14
21
1
Interpretation of various numbers of int& arguments for different dimensionilities.

You may call any of these functions from your source code, of course, and the compiler will instantiate the appropriate template instances and underlying PrintArray::print() instances. For calling interactively from a debugger, you must make the extra step of adding non-template wrappers for your specific container types, so that the underlying template instances are compiled into your executable. The following code snip illustrates this:

// Global typedefs; useful in making user-defined functions below:
const unsigned D = 2;
typedef UniformRectilinearMesh<d> Mesh_t;
typedef Field<DiscreteGeometry<Cell, Mesh_t>, double> ScalarField_t;
typedef Field<DiscreteGeometry<Cell, Mesh_t>, Vector<D> >VectorField_t;
typedef Array<D, double, CompressibleBrick> ScalarArray_t;
typedef Array<D, Vector<D>, CompressibleBrick> VectorArray_t;

class Atoms : public Particles<SharedBrickUniform> {
public:
  // Particle attributes:
  DynamicArray<Vector<D>, SharedBrick> r;
  DynamicArray<Vector<D>, SharedBrick> v;
  // ... rest of class definition ....
  // Constructor: set up layouts, register attributes
  Atoms(const UniformLayout &pl) : Particles<SharedBrickUniform>(pl)
  {
    addAttribute(r);
    addAttribute(v);
  }
};
typedef DynamicArray<Vector<D>, SharedBrick> VAttribute_t;

// User-defined nontemplate dbprint()-type functions:
void sfdbprint(const ScalarField_t &f) { dbprint(f); }
void vfdbprint(const VectorField_t &f) { dbprint(f); }
void sadbprint(const ScalarArray_t &a) { dbprint(a); }
void vadbprint(const VectorArray_t &a) { dbprint(a); }
void pdbprint(const VAttribute_t &pa) { dbprint(pa); }

// Subsetting functions:
// N.B.: these have to have separate names; some debuggers aren't smart enough
// to understand multiple prototypes of function with same name.
void esfdbprint(const ScalarField_t &f, int i) { dbprint(f,i); }
void rsfdbprint(const ScalarField_t &f, int base0, int bound0,
                int stride0, int base1, int bound1, int stride1)
{ dbprint(f, base0,bound0,stride0, base1,bound1,stride1); }
void epdbprint(const VAttribute_t &pa, int i) { dbprint(pa, i); }
void rpdbprint(const VAttribute_t &pa, int base, int bound, int stride)
{ dbprint(pa, base,bound,stride); }

int main(int argc, char* argv[])
{
  // Make and Arrays, and some Fields with GuardLayers<D>(2)
  ScalarField_t s(...);
  VectorField_t v(...);
  ScalarArray_t sa(...);
  VectorArray_t va(...);

  // Make Atoms object:
  Atoms atoms(...);
  atoms.globalCreate(20);

  //... Assign values to all these...

  // ...Stop the debugger somewhere down here...

Note that you must define a separately-named non-template function for each different fully-specified data type you want to examine interactively from the debugger (specified values for all template parameters of Array or Field, for example). This is a bit cumbersome, but can be worth the trouble.

Following is a screen-shot from an example session running the complete program sketched above, illustrating how to call the user-defined dbprint() wrapper functions. Calling syntax may vary from one debugger to the next. In this case, the debugger is dbx running on SGI IRIX 6.5. It is stopped at a breakpoint in main():

(dbx) ccall dbSetCarReturn(5)
(dbx) ccall sfdbprint(&s)
( -2:004:001, -2) =       -1.5       -0.5        0.5        1.5        2.5
                           3.5        4.5
( -2:004:001, -1) =       -1.5       -0.5        0.5        1.5        2.5
                           3.5        4.5
( -2:004:001,000) =       -1.5       -0.5        0.5        1.5        2.5
                           3.5        4.5
( -2:004:001,001) =       -1.5       -0.5        0.5        1.5        2.5
                           3.5        4.5
( -2:004:001,002) =       -1.5       -0.5        0.5        1.5        2.5
                           3.5        4.5
( -2:004:001,003) =       -1.5       -0.5        0.5        1.5        2.5
                           3.5        4.5
( -2:004:001,004) =       -1.5       -0.5        0.5        1.5        2.5
                           3.5        4.5
(dbx) ccall esfdbprint(&s, 1,1,1)
(000,000) =        0.5
(dbx) ccall rsfdbprint(&s, 1,3,2, 1,2,1)
(001:003:002,001) =        1.5        3.5
(001:003:002,002) =        1.5        3.5
(dbx) ccall dbSetCarReturn(2)
(dbx) ccall pdbprint(&atoms.r)
(000:019:001) = (     1.965,     2.024) (     1.549,     1.807)
                (    0.7699,     2.476) (    0.8934,     2.369)
                (     2.401,     1.617) (    0.7499,     2.148)
                (     0.987,     2.125) (    0.5183,     2.347)
                (     1.792,     1.669) (     1.192,     1.937)
                (    0.9148,     2.503) (     2.114,     1.823)
                (     2.099,      1.19) (     1.855,     1.124)
                (      1.68,    0.4495) (    0.2164,    0.5908)
                (     1.647,     1.128) (      1.36,     1.131)
                (     2.836,     1.302) (   0.06326,    0.4787)
(dbx) ccall epdbprint(&atoms.r, 2)
(002) = (    0.7699,     2.476)
(dbx) ccall rpdbprint(&atoms.r, 2, 6, 2)
(002:006:002) = (    0.7699,     2.476) (     2.401,     1.617)
                (     0.987,     2.125)
(dbx)

Note that the first function call, to print the entire Fields, includes the global guard layers. Calling dbprint() (or sfdbprint()) from your source code, you could pass in s(), or s(s.physicalDomain()), to exclude the global guard layers; this is not possible interactively.

The dbSetCarReturn() invocations illustrate more of the POOMA db*() function family. These invoke the corresponding PrintArray functions on a global PrintArray object maintained internally by POOMA. This sets a format state that persists from one interactive function call to the next. Here is the set of these functions. Refer to the previous section on PrintArray for their meanings:

int dbDomainWidth();
void dbSetDomainWidth(int val);
int dbDataWidth();
void dbSetDataWidth(int val);
int dbDataPrecision();
void dbSetDataPrecision(int val);
int dbCarReturn();
void dbSetCarReturn(int val);
bool dbScientific();
void dbSetScientific(bool val);
int dbSpacing();
void dbSetSpacing(int val);

Two additional functions allow toggling between the default Inform object used by dbprint() and one or more user-defined Inform objects:

void dbSetInform(Inform &inform) :
Replace the default Inform object with the input object.
void dbSwapInform() :
After a preceding dbSetInform(), toggles between the input Inform object and the default. That is, repeated calls to dbSwapInform() switch back and forth between the two Informs.
[Prev] [Home] [Next]
Copyright © Los Alamos National Laboratory 1998-2000