Level
.Factory
classes. Factory here, means, that input data is used and processed to produce output data. A factory can be understood as a data processing unit. It does not store any data itself.Declare location of include headers
In [1]:
.I /opt/install/do-conf-ep-serial/include/
Out[1]:
Load Trilinos libraries
In [2]:
.L libepetra
Out[2]:
In [3]:
.L libxpetra
Out[3]:
In [4]:
.L libmuelu
Out[4]:
In [5]:
.L libteuchoscore
Out[5]:
Include some standard header files to create a communicator and an Xpetra::Map
In [6]:
#include "Kokkos_DefaultNode.hpp"
#include "Epetra_SerialComm.h"
#include "Teuchos_RCP.hpp"
#include "Teuchos_DefaultSerialComm.hpp"
#include "Xpetra_MapFactory.hpp"
#include "Xpetra_Map.hpp"
Out[6]:
Again, we use standard template parameters:
In [7]:
typedef double SC;
typedef int LO;
typedef int GO;
typedef Kokkos::Compat::KokkosSerialWrapperNode EpetraNode;
typedef EpetraNode NO;
Out[7]:
Let's create a MueLu::Level
object:
In [8]:
#include "MueLu_Level.hpp"
Out[8]:
In [9]:
MueLu::Level l;
Out[9]:
A MueLu::Level
object is basically a data container. You can store (arbitrary) data in the level container given a (unique) name. To store data, you use the Set
routine:
In [10]:
l.Set("MyInt", 42);
l.Set("MyDouble", 37.88);
Out[10]:
This also works for more complicated objects. Usually, you store RCP
pointers to some linear algebra objects, such as matrices, vectors or just Xpetra::Map
objects as shown next:
In [11]:
Epetra_SerialComm Comm;
Teuchos::RCP<const Teuchos::Comm<int> > comm = Xpetra::toXpetra(Comm);
Teuchos::RCP<Xpetra::Map<LO,GO,NO>> map = Xpetra::MapFactory<LO,GO,NO>::Build(Xpetra::UseEpetra, 10, 0, comm);
Out[11]:
In [12]:
l.Set("MyMap", map);
Out[12]:
The Level
class has a print routine, that can be very useful for debugging. It prints a list with all data stored insided and some additional information. Please note, that you have to provide the MueLu::Extreme
verbosity flag to print the list.
In [13]:
l.print(std::cout, MueLu::Extreme);
Out[13]:
Routines like IsAvailable
allow you to check programmatically at runtime whether a variable is stored in the Level
container:
In [14]:
l.IsAvailable("MyMap");
Out[14]:
To access data, you can use the Get
data. It is templated on the data type.
In [15]:
Teuchos::RCP<Xpetra::Map<LO,GO,NO>> myMap = l.Get<Teuchos::RCP<Xpetra::Map<LO,GO,NO>>>("MyMap");
Out[15]:
In [16]:
std::cout << *myMap << std::endl;
Out[16]:
Data, which is stored on the level class using the Set
routine is not automatically freed after the Get
call, as one can easily verify:
In [17]:
l.print(std::cout, MueLu::Extreme);
Out[17]:
As you can see, the variable myMap
is still contained in the list of data.
You can explicitly remove data from the container by using the following Delete
call.
In [18]:
l.Delete("MyMap",MueLu::NoFactory::get());
Out[18]:
Printing out the content of l
proofs that MyMap
is not stored in the container any more.
In [19]:
l.print(std::cout, MueLu::Extreme);
Out[19]:
You may have noticed that we needed the MueLu::NoFactory::get()
parameter in the Delete
call. This leads us to the next step, discussing data dependencies and finally the Factory concept in MueLu.
MueLu::NoFactory
means that the associated data has not been created by a so-called Factory
. In order to automatically manage and resolve data dependencies MueLu uses the so-called Factories
. Factory
classes contain the algorithms: they require some algorithm-specific input data, process the input data and produce some output data. In contrast: the Level
class is a pure container class, storing and providing data that is used by the Factory
classes.
Please note: the naming Factory
class might be misleading. It is not a Factory
class in the sense of the Factory
pattern to create new objects as it is used, e.g., in Xpetra (see, e.g., Xpetra::MapFactory
).
Long in short: MueLu strictly separates data and algorithms. The data is stored in the Level
container classes whereas data is processed in the Factory
classes. The Factory
classes get data from the Level
classes and store newly generated data in the Level
classes.
To demonstrate the principle, let's define the most trivial Factory
class, one can think of:
In [20]:
#include "MueLu_SingleLevelFactoryBase.hpp"
using Teuchos::RCP;
using MueLu::Level;
class DummyFact : public MueLu::SingleLevelFactoryBase {
virtual void DeclareInput(Level ¤tLevel) const {};
virtual void Build(Level & currentLevel) const {};
};
Out[20]:
For now, the meaning of the empty routines DeclareInput
and Build
is not important.
Next, we create two instances, that is, two different factories.
In [21]:
DummyFact f1;
DummyFact f2;
Out[21]:
Important is, that each factory has a unique memory adress, e.g.
In [22]:
std::cout << "Memory adress of factory1: " << &f1 << std::endl;
std::cout << "Memory adress of factory2: " << &f2 << std::endl;
Out[22]:
The keypoint is to understand, that data in MueLu is uniquely described by the name and the generating factory. The generating factory itself is identified and referred to by its memory adress.
This allows to store different data using the same variable name but a different factory.
Let's give an example:
In [23]:
l.Set("MyInt", 46, &f1);
l.Set("MyInt", 47, &f2);
Out[23]:
In [25]:
l.print(std::cout,MueLu::Extreme);
Out[25]:
Obviously, storing data failed. In fact, data generated by a factory is only stored, if it has requested before.
So, let's request the data named MyInt generated by f1:
In [24]:
l.Request("MyInt", &f1);
l.print(std::cout,MueLu::Extreme);
Out[24]:
The printout shows a variable MyInt generated by NoFactory
from the beginning (with data $42$). There is a second MyInt generated by the adress associated with f1
. The variable is listed to be requested, but there is no data stored (data not available
). Now, let's try to set the data:
In [26]:
l.Set("MyInt", 46, &f1);
l.print(std::cout,MueLu::Extreme);
Out[26]:
That worked! Now we have two different variables MyInt. One generated by NoFactory
(which corresponds to the user) with the value $42$ and one generated by f1
with the value $46$.
Ok. Let's store another MyInt generated by f2
, just to test it:
In [27]:
l.Request("MyInt", &f2);
l.Set("MyInt", 47, &f2);
l.print(std::cout,MueLu::Extreme);
Out[27]:
In [28]:
int ret = l.Get<int>("MyInt", &f2);
int ret2 = l.Get<int>("MyInt", &f2);
int ret3 = l.Get<int>("MyInt", &f2);
l.print(std::cout, MueLu::Extreme);
Out[28]:
As you can see, we can access the data several times and it is not automatically freed. We can use Release
to decrement the request counter. If it goes to zero, the data is deleted from the Level
container. If it was a RCP pointer that runs out of scope, the underlying data is also automatically freed.
In [29]:
l.Release("MyInt", &f2);
l.print(std::cout, MueLu::Extreme);
Out[29]:
Long in short: the Request
call increases the request counter, whereas the Release
call decreases the request counter. If the request counter is zero the data is removed from the level container. Only data, which has been requested before (i.e., its request counter is larger than zero) can be stored.
The only exception is user data (generated by NoFactory
). It can always be stored. User data has to be deleted using the Delete
call.
To clear all data from the level class the following commands would be necessary:
In [30]:
l.Release("MyInt", &f1); // Release the data gen. by f1
l.Delete("MyInt", MueLu::NoFactory::get()); // Remove the 42 from l
l.Delete("MyDouble", MueLu::NoFactory::get());
l.print(std::cout, MueLu::Extreme);
Out[30]:
The level container now should be empty.
Let's implement a first non-trivial factory. That is, we have to provide a implementation for the DeclareInput
and the Build
routine. The following factory shall take a variable InputNumber
of type int
as input and store the square root of it in the level container using the variable name SquareRoot
.
In the DeclareInput
routine we have to declare all necessary input data. In our case this is the InputNumber
variable, which is supposed to be provided by the user. In the Build
routine we obtain the input data from the Level
class, build the square root and store the result on the Level
class.
A naive implementation could look like follows:
In [31]:
class SquareRootFact : public MueLu::SingleLevelFactoryBase {
public:
virtual void DeclareInput(MueLu::Level ¤tLevel) const {
currentLevel.DeclareInput("InputNumber", MueLu::NoFactory::get(), this);
};
virtual void Build(MueLu::Level & currentLevel) const {
// extract data from level
double userInput = currentLevel.Get<int>("InputNumber");
// do calculation, process data
double result = sqrt(userInput);
// store result in level container
currentLevel.Set("SquareRoot", result);
};
};
Out[31]:
Let's create an instance of the class:
In [32]:
SquareRootFact sqFact;
Out[32]:
The Level
class has a Request
routine, which recursively makes sure that all necessary input data for the given factory is properly requested in the Level
container:
In [33]:
l.Request(sqFact);
l.print(std::cout, MueLu::Extreme);
Out[33]:
In our case above call is not really necessary (but it does not really hurt either), since we only need user-provided data as input (NoFactory
).
Please note: we never call the DeclareInput
routine of the Factory class directly (unless you are an absolute expert), since it contains some sophisticated logic to recursively resolve data dependencies. You always should think from the level perspective and call the member functions provided by the Level
class.
Of course, we must not forget to set the input data:
In [34]:
l.Set("InputNumber", 25);
Out[34]:
Then, we can call the build routine and check the output
In [35]:
sqFact.Build(l);
l.print(std::cout,MueLu::Extreme);
Out[35]:
It seems to work, but let's find a more elegant solution.
In [36]:
l.ExpertClear();
Out[36]:
Usually, input data for a factory is provided by another factory (rather than the user). So, just for demonstration purposes, let's introduce a new factory DataFactory2
which only stores a number in the level container. The implementation would be like this:
In [37]:
class DataFactory2 : public MueLu::SingleLevelFactoryBase {
public:
virtual void DeclareInput(MueLu::Level ¤tLevel) const {};
virtual void Build(MueLu::Level & currentLevel) const {
currentLevel.Set("Number", 36.0, this);
};
};
Out[37]:
Please note the this
pointer as third argument in the Set
call. It means that the concrete instance of DataFactory2
generated the variable Number
, that is stored in the Level
container.
Next, we create a SquareRootFact2
factory, which expects the input variable Number
from a factory which actually provides the data variable Number
. In the DeclareInput
call as well as in the Get
call in the Build
routine, we use the GetFactory("Number")
routine, which basically returns the factory which is responsible to produce the variable Number
. In our case this will be an instance of DataFactory2
. We will see later how we declare the data dependency.
In [38]:
class SquareRootFact2 : public MueLu::SingleLevelFactoryBase {
public:
virtual void DeclareInput(MueLu::Level ¤tLevel) const {
currentLevel.DeclareInput("Number", GetFactory("Number").get(), this);
};
virtual void Build(MueLu::Level & currentLevel) const {
// extract data from level
double userInput = currentLevel.Get<double>("Number", GetFactory("Number").get());
//double userInput = Get<double>(currentLevel,"Number");
// do calculation, process data
double result = sqrt(userInput);
// store result in level container
currentLevel.Set("SquareRoot", result, this);
};
};
Out[38]:
We create an instance of both the DataFactory2
and SquareRootFact2
classes.
In [39]:
DataFactory2 datFact;
SquareRootFact2 sqFact2;
Out[39]:
Then, we declare the data dependencies between these two factories. In our case datFact
is responsible to provide variable Number
as input for factory sqFact2
. In other words: sqFact2
requires Number
generated by datFact
. That is, we set datFact
as the generating factory for Number
in sqFact2
. This is done by the SetFactory
routine:
In [40]:
sqFact2.SetFactory("Number", Teuchos::rcpFromRef(datFact));
Out[40]:
To calculate the square root of Number
we then request the result on the level class with the command
In [41]:
l.Request("SquareRoot", &sqFact2);
Out[41]:
In [42]:
l.print(std::cout,MueLu::Extreme);
Out[42]:
As one can see, the Request
call for SquareRoot
automatically triggered an Request
for Number
, since it is prerequisite to calculate the result.
After all requests have (automatically) been resolved in a recursive fashion we are ready for the result.
In [43]:
double sqres = l.Get<double>("SquareRoot", &sqFact2);
Out[43]:
Again, we do not call the Build
routines directly, but just ask for the result. The Build
routines are then automatically recursively called to produce the requested result.
Note, you have to call Request("SquareRoot",...)
before the Get("SquareRoot",...)
call. Data, which has not been requested first, won't be calculated and nothing would happen.
Next, we can look at the content of the level container. Obviously, it only contains the SquareRoot
variable with the result $6$. The input data Number
was only necessary to calculate the variable SquareRoot
. No other factory has requested Number
. Therefore, Number
has automatically been freed and removed from the container.
The result SquareRoot
is stored on the level class as we've requested it by our Request
call.
In [44]:
l.print(std::cout,MueLu::Extreme);
Out[44]:
Of course, the Build
routines are only called if we try to get data from the level container, which is not available, yet. Once it has been calculated, we can extract it as often we want (as long as we do not release the data) without the data being recalculated.
In [45]:
double sqres2 = l.Get<double>("SquareRoot", &sqFact2);
Out[45]:
The Get
and Set
calls can be written a little bit more elegant using the Get
and Set
functions of the Factory
class. In the following example, the commented lines are equivalent with the new commands below.
In [46]:
class DataFactory3 : public MueLu::SingleLevelFactoryBase {
public:
virtual void DeclareInput(MueLu::Level ¤tLevel) const {};
virtual void Build(MueLu::Level & currentLevel) const {
//currentLevel.Set("Number", 36.0, this);
Set<double>(currentLevel, "Number", 36.0);
};
};
class SquareRootFact3 : public MueLu::SingleLevelFactoryBase {
public:
virtual void DeclareInput(MueLu::Level ¤tLevel) const {
//currentLevel.DeclareInput("Number", GetFactory("Number").get(), this);
Input(currentLevel,"Number");
};
virtual void Build(MueLu::Level & currentLevel) const {
// extract data from level
//double userInput = currentLevel.Get<double>("Number", GetFactory("Number").get());
double userInput = Get<double>(currentLevel,"Number");
// do calculation, process data
double result = sqrt(userInput);
// store result in level container
//currentLevel.Set("SquareRoot", result, this);
Set<double>(currentLevel,"SquareRoot", result);
};
};
Out[46]:
The following list might help to understand the different syntax of the Level
and Factory
calls. Note, that Factory
internally always uses GetFactory
to determine the associated generating factory for the data variables.
Level class | Factory class |
---|---|
Level::DeclareInput("VariableName", FactoryPtr, GeneratingFactoryPtr) | Factory::Input(Level, "VariableName") |
Level::Get |
Factory::Get |
Level::Set("VariableName", FactoryPtr) | Factory::Set(Level, "VariableName") |
In [47]:
DataFactory3 datFact3;
SquareRootFact3 sqFact3;
sqFact3.SetFactory("Number", Teuchos::rcpFromRef(datFact3));
l.Request("SquareRoot", &sqFact3);
l.Get<double>("SquareRoot", &sqFact3);
Out[47]:
In [ ]: