OOF2: The Manual

8.4. Indices and Iterators

Before proceeding, it's necessary to take a diversion on the topic of indices and iterators.

It is often necessary to refer to the components of Fields, Fluxes, Equations, and OutputVals. OOF2's fieldindex module provides a generic mechanism for doing this, so that it's possible to loop over all of the components of an object without even having to know how many indices are required to specify a component. It takes two indices to specify a tensor component, one for a vector, and zero for a scalar, but all three cases can be handled identically.

The basic machinery contains:

  • The FieldIndex base class, which designates a component of a Field or other object. There are different subclasses for different kinds of objects (scalars, vectors, tensors, etc).

  • A Components base class, which is a container that can be iterated over to obtain the FieldIndexes. There are different subclasses for different kinds of objects, and for different ways of iterating over them (in-plane, out-of-plane, etc). Each indexable class (Field, Flux, etc) has a virtual function that returns the appropriate type of Components.

  • A ComponentIterator base class that is used for iterating over the Components. Different subclasses of ComponentIterators iterate over different types of Components.

  • Generic wrapper classes, IndexP and ComponentIteratorP, that wrap pointers to FieldIndex and ComponentIterator.

The base class for indices is FieldIndex. There are subclasses of FieldIndex for different kinds of Fields, Fluxes, Equations, and OutputVals. (From now on we'll talk about Fields, but everything also applies to Fluxes, Equations and OutputVals, unless explicitly stated otherwise.) To handle the polymorphism, generic C++ code doesn't use the FieldIndex classes directly. Instead it uses a wrapper class, IndexP, which is a light-weight object that contains a pointer (hence the P in the name) to a FieldIndex. The wrapper frees the user from having to know the actual class being used. It provides access to its FieldIndex and its virtual functions, and deallocates it when it goes out of scope.

Note that IndexP does not do reference counting. When an IndexP is destructed, its FieldIndex is destructed as well. When an IndexP is copied, the original FieldIndex is copied as well.

For looping over the indices of a Field, there are Components and ComponentIterator class hierarchies. Each type of Field has a components() method that returns a Components pointer. The Components classes are conceptually containers for FieldIndexes. The instances of the Components subclasses are static immutable objects.

In C++, Components objects have begin() and end() methods that return ComponentIterator objects, allowing them to work like STL iterators for looping over indexes. For example, you can print the indices of the components of the Temperature and Displacement Fields like this:

Field *temperature = Field::getField("Temperature");
Components* comps = temperature->components();
for(ComponentIteratorP ip = comps->begin(); ip!=comps->end(); ++ip)
  std::cerr << *ip << std::endl;

Field *displacement = Field::getField("Displacement");
Components* comps = displacement->components();
for(ComponentIteratorP ip = comps->begin(); ip!=comps->end(); ++ip)
  std::cerr << *ip << std::endl; 

or, more compactly, using range based for loops like this:

for(IndexP i : *Field::getField("Temperature")->components())
   std::cerr << i << std::endl;

for(IndexP i : *Field::getField("Displacement")->components())
   std::cerr << i << std::endl; 

both of which print

IndexP(ScalarFieldIndex())    [The single component of Temperature]
IndexP(VectorFieldIndex(0))   [The first component of Displacement]
IndexP(VectorFieldIndex(1))   [The second component of Displacement]

In Python, the components() methods return generator functions that serve the same purpose. For example, in Python you can print the indices of the components of the temperature and displacement Fields like this:

for i in Temperature.components():
   print(i)
for i in Displacement.components():
   print(i) 

which prints

ScalarFieldIndex()     [The single component of Temperature]
VectorFieldIndex(0)    [The first component of Displacement]
VectorFieldIndex(1)    [The second component of Displacement]

In some cases it's necessary to restrict iteration to either the in-plane or out-of-plane components of a Field or Flux (but not an Equation). In those cases, the components() method takes an argument of the Planarity class. If no argument is provided, as in the examples above, the default planarity is ALL_INDICES. See Field::components, Field::outOfPlaneComponents, Flux::components, and Flux::outOfPlaneComponents for examples and some important details.