FACETS components design

Generic design principles

  • The basic idea is to come up with interfaces that are parallel to Babel, but not confined by the datatypes used within Babel itself.
  • std::vectors are to be the choice of containers for C++ interfaces, since they offer memory-safe containment of arrays.
  • Method overloading will not be used in order to more closely match Babel's capabilities. This should increase the ease of mapping to other languages.
  • Data is to be passed by reference to prevent large copy-overhead.

Design principles

* Components need to have an abstract class as its base type for the polymorphic manipulation and type checking.

  • For example, all components derive from FcComponent class and can be manipulated via FcComponent* and FcComponent& and all core component derive from FcCoreIfc and support this interface.

* Base type class should not be too "heavy”: have a minimal set of methods that define the abstraction (FcComponent, for example) or a mix-in interface (!Fc0dIfc, for example). * Base classes typically guarantee the most basic interface and are abstract. Since derived classes are typically manipulated via a base class pointer, it is up to the user of the derived class to check if the methods are implemented, i.e. check the exact run-time type of the object and provide mechanisms for proper downcast. Checking of object types should occur only once: if an object’s type has been checked by a container, the object’s type is safe to use in any other objects contained in the container.

  • For example, objects that use concrete classes (like FcCoreEdgeComponent that) should check if the contained FcComponent’s are actually of the run-time type FcCoreIfc* and FcEdgeIfc* and downcast them to these types. Any other objects in FcCoreEdgeContainer, for example its !updaters, can use the contained components as core and edge.

* Generic coupling !updaters do not ensure the existence of more specific (physics-dependent) interfaces. It is up to the containers that use these !updaters to do the type checking of the coupled components and provide coupling !updaters with the properly cast handles. * Dimensional interfaces should be separated out that can be reused and only minimal amount of set and get methods needs to be implemented by concrete components.

  • For example, core is typically 0d and should not be asked to support higher-dimensional data exchange.
  • Source can be 2d but should then support 0d and 1d interfaces or throw exception if they are not available.

* Get and set functions should be capable of registering themselves in the base-class or global registries and called by name. • Coupling interfaces should be compile-time safe, guaranteeing the existence of necessary string handles.

  • For example, FcCoreComponent implements functions that are being used in get/set by name methods with the names registered in FcCoreEdgeIfc.

Figure 1. FACETS Components Hierarchy

Physics-independent components

There is set of component classes (see Figure 1) that can be used by many applications (not just FACETS). Such components do not use any physics specific names. For example, all classes that do not contain strings “Core”, “Edge”, “Source” and “Wall” are physics independent. They are briefly described below. Component (FcComponent) represents a unit of simulation and obeys the interface described on the wiki ( https://www.FACETSproject.org/wiki/InterfacesAndNamingScheme). FACETS components have state and advance in time.

External components that use external legacy codes are components, i.e. use FcComponent class. Typically it contains a pointer to a !librarified external code wrapped in C++ to obey the methods of FcComponent. If the code is not written in C or C++ it might use Babel (for example !UEDGE) or !Fortran opaque pointers for language interoperability.

A container component (FcContainer) contains components (FcComponent) and applies all the operations to the contained component sequentially and recursively. FcContainer derives from FcComponent.

An updater component (FcUpdaterComponent) uses FACETS infrastructure extensively (arrays, grids, !updaters etc). It updates itself in time using algorithms and couplers called updaters (FcUpdater). !Updaters do not have a state that corresponds to the simulation time. It might keep some state that is not visible to the outside. There is set of get/set interfaces that represent data exchange of particular type: 0d, 1d and Nd type of data (FcOdIfc, Fc1dIfc and FcNdIfc). These !dimensionalities do not correspond to the dimensions of the space where components using them operate. They just represent the nature of data that is can be set in the component or gotten from it.

Physics-specific components

Physic specific component hierarchy depends on the physics modules that are used in FACETS. At the moment we have Core. Edge, Wall and Source types of modules.

Data exchange between components depends on what kind of components are being coupled. The data exchange is specified by the variable names and types that are being exchanged. The FACETS hierarchy implies symmetric exchange at the moment: the coupled components get and set the same variables. Data exchange is supported by coupling interfaces: FcCoreEdgeIfc, FcEdgeWallIfc, and FcCoreSourceIfc. Coupling interfaces have lists of variables that can participate in the data exchange in coupling and register these strings to particular get and set methods, so that when the get/set by name function gets called through the base class (FcComponent) the methods gets delegated to a specific method that is supposed to be implemented by a concrete component. For example, FcCoreEdgeIfc does this in its constructor:

child->addGetDoubleHandler( "ptclFlux_CE_electron", this, &FcCoreEdgeIfc::get_ptclFlux_CE_electron    );
and declares a pure virtual function:
virtual int get_ptclFlux_CE_electron(double& value) const = 0;

This means that any concrete class deriving from it should implement this function. When a component of the derived class has this method called

FcComponent* eu = new FcUEDGEComponent();
eu->get0DDouble("ptclFlux_CE_electron");

FACETS looks through the registry and delegates the call to the get_ptclFlux_CE_electron function implemented in !FcUEDGEComponent class.

Physics-specific interfaces (FcCoreIfc, FcEdgeIfc, FcWallIfc and FcSourceIfc) derive from coupling interfaces and from the dimensional interfaces. They do not introduce new functionality or implement any of the pure virtual methods. They just introduce a physic type so the framework knows it and can impose type checking and correct composition rules. Core and edge exchange scalar data on the boundary on a particular magnetic surfaces, and edge exchange data with a wall at one wall segment so the data is also scalar. That is why core and edge components derive from Fc0dIfc, while the wall component will probably need 1d data and should derive from Fc1dIfc.

Concrete physics components derive from the physics specific interfaces and from FcComponent (for external components) or from FcUpdaterComponent (for internal components). For example, FcUEDGEComponent derives from FcEdgeIfc and FcComponent, while FcCoreComponent, developed by the FACETS team, derives from FcCoreIfc and FcUpdaterComponent.

Coupling between components is mediated by a component that contains them. In order to make coupling correct, such coupling containers are made specific for each type of coupling. In this case, the coupling container can contain only correct modules. For example, FcCoreEdgeContainer expects to contain pointers to components of type FcCoreIfc and FcEdgeIfc.

The particular instances of these types are coupled using an updater contained in FcCoreEdgeContainer (see section about coupling !updaters). Such updater is generic: does not know what kind of components are coupled except it depends the dimensionality of the data. For example, in the case of coupling of core and edge, the data is saclar, hence the updater is Fc0d0dUpdater. In the case when the coupled components intrinsically use different dimensions, the updater should mix the dimensions and perform what the data transformations as needed.

Coupling updaters (not implemented yet)

Implementation of coupling is strongly dependent on the dimensionality of the exchanged data. That is why we will have hierarchies determined by the dimensionality of the components that are being coupled. For example, all coupling !updaters coupling Nd and Md components will derive from FcNdMdUpdater base class. They might support one-way or two-way coupling depending on how the update() method will be implemented in the derived class. At the moment we couple only 0d components.

Figure 2. Coupling updaters hierarchy.

Fc0d0dUpdater (see Figure 2) is a base class for all 0d0d !updaters performing 0d get/set from the first component and 0d get/set for the second component. It is a abstract class as update() is not implemented:

class Fc0d0dUpdater::public FcUpdater {
  public:
    Fc0d0dUpdater(){
      // If these next two lines fail, throw an exception
      // Need vector<FcComponent*> getComponents() method in FcContainer!
      FcComponent* first = this->getOwner->getComponents()[0];
      FcComponent* second = this->getOwner->getComponents()[1];
      comp1 = dynamic_cast<Fc0dIfc*> first;
      if (comp1 == 0) throw;
       comp2 = dynamic_cast<Fc0dIfc*> second;
      if (comp2 == 0) throw;
    }
    setAttrib() {
      // If there is no such info in input file, exception is thrown
      vars = getStrVector<"vars">;
      }
     virtual ~Fc0d0dUpdater() {}

  protected:
    std::vector<string> vars; // names of vars in the data exchange
    Fc0dIfc* comp1;            // provides 0d get and set methods
    Fc0dIfc* comp2;            // provides 2d set and set methods
};

Examples of several concrete class implementing update() method in a particular way are given below.

class Fc0d0dOneWayExplicitUpdater::public Fc0d0dUpdater {
  public:
     update() {
        // gets vars from comp1 and sets vars in com2
};
class Fc0d0dTwoWayUpdater::public Fc0d0dUpdater {
  public:
    update() {
      // check that vars comp1 and comp2 are not 0
      // exchange vars between comp1 and comp2
     }
};
Fc0d0dImplicitUpdater::public Fc0d0dUpdater{
  // extra data members to hold temp states
};
Fc0d0dPicardUpdater::public Fc0d0dPicardUpdater {
  public:
    update() {
      // checks if vars, comp1 and comp2 are not NULL and if not
      // implement Picard
    }
};

Things that can go wrong in input files (to address soon!)

1. An updater gets inserted into a component that is just FcComponent (like !uedge). Not sure what happens. Probably the updater will be ignored?

2. An updater with incorrect <in> and <out> !structs that do not exist in the component that is being updated. Not sure what happens.

3. A component of concrete double container type will get containees of incorrect type. For example, FcCoreEdgeComponent will have core and wall inside. Not sure what happens.

4. The same, if more than 2 components are put inside.

5. A coupling updater of incorrect dims is in the list of !updaters of the component that does not support this dimension (for example 1d1d updater in a container that contains 0d and 1d component. The cast to correct dim will fail and an exception will be thrown. Provided in a suggested solution described in the next section.

6. An updater that does not “belong” to the component that includes it. For example, FcDiffCoreFastUpdater, whose name suggests that it works only for core components, might end up in wall. Or, a coupling updater gets in a container that is not good for this type of coupling. For example, a container of type FcContainer? with wall and core in it and having 0d1d coupler. Sveta suggests that we deal with this problem at some point. One can approach it by adding a list of correct components names to each updater class, so that an updater could first get its container’s handle by using getOwner() and applying typeid().name() to get the container name and checking it against the list of legal owners. In the updaters description below, this problem is not addressed as it is not specific to the described types of !updaters.

Avoiding multiple containers for each type of coupling

At the moment, we impose correct coupling by performing it in specialized containers. For example, FcCoreEdgeComponent, which checks if it indeed contains one instance of core and one instance of edge in it. How one avoids introducing such containers (does not scale)? The coupling updater will get the references to concrete components (for example EdgeIfc type) and can rely on the type safety.

Here are two suggestions for future consideration in facets or other projects.

Ammar's suggestion

First, we template over the component types. For example, imagine a basic container class that is templated over a list of components it can hold. For example, one can imagine then doing (just thinking out loud and of course better suggestions can be made):

class FcCoreEdgeComponent : public FcGenericContainer<FcCoreIfc, FcEdgeIfc> {
};

There would be no need to have anything in the body of the class. Now, the generic container would provide methods to get the pointers to the proper type:

 FcCoreEdgeComponent *cec;
 Fc0DIfc& core = cec->getComponent<0>();
 Fc0DIfc& edge = cec->getComponent<1>();

This code, for example, could be use in a updater to fetch the references to the two contained components and the updates can then be applied by calling the appropriate methods. I am not sure how the getComponent() method would be implemented, but it would essentially return a reference to the type of component specified in the corresponding template list. Now, all type-safety of containers would be satisfied as we can check if FcCoreEdgeComponent has the types specified in the template list. Also, the issue of what updater can be in which container can also be checked in this manner. For example, a 0D coupler updater knows that it needs to live in a 0D-0D container and this could be checked when the simulation is being built.

Sveta's suggestion

Sveta did not like the fact that in Ammar’s solution, one can have any two components in the template list, so correct composition will not be ensured. Hence, her suggestion is to use a generic 2-container but have it templated over a coupling interface and add component types associated with the each coupling interface as follows.

template <class CouplingIfc> struct CouplingTraits {
};
//Specializations
struct CouplingTraits<FcCoreEdgeIfc> {
	typedef FcCoreIfc TypeOne;
	typedef FcEdgeIfc TypeTwo;
};
struct CouplingTraits<FcCoreSourceIfc> {
	typedef FcCoreIfc TypeOne;
	typedef FcSourceIfc TypeTwo;
};
template <class CouplingIfc> class Fc2Container : public FcContainer {
	typedef CouplingTraits<CouplingIfc>::TypeOne TypeOne;
	typedef CouplingTraits<CouplingIfc>::TypeTwo TypeTwo;
public:
	Fc2Container() {
		TypeOne* comp1 = dynamic_cast<TypeOne*>(getComponent<0>());
		TypeTwo* comp1 = dynamic_cast<TypeTwo*>(getComponent<1>());
		//Give these handles to the coupler or other updaters
	}

};

In this case, Fc2Container<FcCoreEdgeIfc> will have comp1 of type FcCoreIfc* and comp2 of type FcEdgeIfc* and will pass correct handles to the generic 0D updater. This updater will cast them to 0D interfaces and perform data exchange. What is nice is that any particular combination of coupled components appears in the hierarchy just once. For example, CoreEdge appear only in FcCoreEdgeIfc. By creating all allowed combinations in these coupling classes, we will ensure that 2-containers can contain only the correct couples.

Attachments