WARPXM v1.10.0
Loading...
Searching...
No Matches
Working with Input Files

The WARPXM input file (.inp file) is an XML-like formatted file that provides setup information for a WARPXM simulation. For the Python language input files using the WARPXM interface library, warpy, see the warpy documenation.

WARPXM Input File Format

The WARPXM input file (.inp file) is an XML-like file format. Blocks are opened using an opening tag <tag_name>, then closed with the matching </tag_name>. Blocks can be nested as deep as desired. The only requirement is that all the direct children blocks and attributes of a given block must be unique, and must be(?) valid C identifiers (i.e. [A-Za-z_][A-za-z0-9_]*). Like XML the input file is case-sensitive.

Note that you cannot add XML attributes to the opening tag; instead, all attributes of a given block are contained inside the body as key = value pairs. There is one attribute per line. Valid value types are strings, numbers (both integer and floating point), and lists. The input file is unable to express boolean values directly; instead, boolean values are expressed as integers where non-zero values are true and zero as false. List value types are expressed using a square brackets and a comma separated list of values. An example input file section:

<sim>
attr1 = 1
attr2 = String.Value
attr3 = [Left, Right]
<nested>
attr1 = 10
is_true = 1
Type = mesh
</nested>
<another>
attr1 = 5.3
is_false = 1
Type = variable
</another>
</sim>

The actual types of the values is not fixed; for example, the value "1" can be interpreted as a string, integer, boolean, or floating pointing point number. Note that this is different from WARPX, where a particular value had a specific type, so "1" was always an int, while "1.0" was always a double. WARPXM is able to lexically cast between types as desired, so both of these values could be interpreted as any one of a multitude of different types. However, there are limitations. For example, an attribute with the value "[a,b]" cannot be cast to a number type, but can be cast to a string or a list of strings.

As a general rule of thumb, each block represents some sort of WARPXM class. So the above input file section might be used to construct a class top something like this:

class mesh
{
std::string name;
std::string type;
int attr1;
bool is_true;
};
class variable
{
std::string name;
std::string type;
double attr1;
bool is_false;
};
class top
{
std::string name;
int attr1;
std::string attr2;
std::vector<std::string> attr3;
std::unique_ptr<mesh> m;
std::unique_ptr<variable> v;
};
name
Definition: advection2d_conservation.py:32
int v
Definition: gem_challenge_hall.py:42

The order of blocks/attributes is not important(?), i.e. the following blocks are equivalent:

<a>
attr1 = 1
attr2 = [2,3,4]
<b>
</b>
</a>
<a>
attr2 = [2,3,4]
<b>
</b>
attr1 = 1
</a>

Setup function and CryptSets

The general process by which objects are created and setup in WARPXM is:

  • Object is created from the WxCreator. Parameters are set to a "default" sensible value in the constructor.
  • At some later point, the void Setup(const WxCryptSet& wxc) member function of the class is called to initialize the class parameters with what's read from the input file.

The WxCryptSet class represents a single block of the input file, along with any nested attributes and sub-blocks. Note that you cannot access any input file parameters of your parent block, or any sybling blocks. These can be accessed indirectly by establishing a parent-child relation at creation time from WxCreator.

To read an attribute, there are a set of templated get functions defined in WxCryptSet which will attempt to retrieve the value of a given attribute name. If no expected return type is specified, you will get a WxAny object which represents a value of any type.

int val = wxc.get<int>("attr1");
std::vector<std::string> boundaries = wxc.get<std::vector<std::string>>("BoundaryNames");
const WxAny& val = wxc.get("attr2");
Class WxAny is based on the "any" class described in "Valued Conversion", Kevlin Henney,...
Definition: wxany.h:139

Allowed types (typedefs equivalent to any of these are allowed, too. These should be the raw types, i.e. int instead of const int&):

  • bool
  • char
  • unsigned char
  • short
  • unsigned short
  • int
  • unsigned int
  • long
  • unsigned long
  • long long
  • unsigned long long
  • float
  • double
  • std::string
  • WxAny
  • std::vector<T>, where T is any of the listed allowed types

You can get sub blocks by calling getSet with the name of the subblock. If you don't know the name of the subblock, it is possible to discover it using the getNamesOfTypes. In order to use this function, the block must have an attribute named Type (i.e. the base type). A de-facto standard in the code is that for polymorphic types, there is an attribute Kind which can be used to look up the appropriate subclass in WxCreator. However, if you know that there will only be one such type you can always manually call new without using WxCreator.

Example usage to iterate through all variables and create them:

std::vector<std::string> names = wxc.getNamesOfType("variable");
for(auto& name : names)
{
const WxCryptSet& var_cryptset = wxc.getSet(name);
std::string kind = var_cryptset.get<std::string>("Kind");
// the actual distributed variable setup is more complicated than this
// this just demonstrates the idea
variable->setup(var_cryptset);
// keep track of all variables in a member vector
variables_list.push_back(variable);
}
static B * getNew(const std::string &nm)
Get a new object whose creator has the given name and base class has a default constructor.
Definition: wxcreator.h:161
VALUETYPE get(const std::string &name) const
Retrieve the value associated with name.
Definition: wxcrypt.h:80
WxCryptSet extends WxCrypt by providing, in addition to name-value pairs, an set of named WxCryptSets...
Definition: wxcryptset.h:35
const WxCryptSet & getSet(const std::string &name) const
Get cryptset with given name.
Definition: variable.h:22

WxCreator

In order to easily and extendably handle different class types, WARPXM uses an abstract factory pattern. This allows the arbitrary construction of polymorphic types (ex.: WmPatchProcess is the base class of patch-based operations such as spatial solvers, variable adjusters, etc.). There is no requirement that all objects created from WxCreator must inherit from a single base class. However, a common base class type must be passed to the WxCreator in order to look up an appropriate subclass to construct.

In order for WxCreator to know what classes are available for construction, classes must be registered with WxCreator. To do this, at the bottom of the associated .cc/.cpp file add:

#include "lib/wxcreator.h"
REGISTER_CREATOR("input_file_class_kind", my_class, base_class);
#define REGISTER_CREATOR(name, specialization, base)
Definition: wxcreator.h:317

To get WxCreator to construct a subclass,

// subclass_kind's value would typically be read in from the input file, not hard coded
std::string subclass_kind = "input_file_class_kind";
// if the child class doesn't need to know anything about the parent
base_class* v = WxCreatorMap<base_class>::getNew(subclass_kind);
// if the child class does need to know who the parent class is
base_class* v2 = WxCreatorMap<base_class>::getNew(subclass_kind, parent_object);
// the created class has not been setup yet; do that now
v->setup(some_crypt_set);
v2->setup(some_other_crypt_set);
// You are responsable for deleting the created objects when done with them
delete v;
delete v2;

General layout of the input file

The general layout of a WARPXM input file is:

  1. A top level block which represents the WmSimulation class named warpxm. This essentially the entry point into WARPXM. It has an attribute sim which is the name of the subblock containing some sort of solver. At this time there can only be a single solver per simulation.
  2. The WmSolver block contains the actual contents for what is to be simulated. It defines the start/stop time, stores the mesh, distributed variables, contains any number of host actions, and defines appropriate sequences for how the host actions should be executed. Host actions are the top level wrappers for any executable task. This could be performing a variable sync operation, writing out data files, taking a timestep, etc. Note that there is a special host action for the time stepping controller which is not sequenced in the usual fashion as other host actions. It has a Kind of time_stepper.<stepper_kind>, where stepper_kind is the actual kind of time stepper to use (fixed timesteps, error-based metric, stability criterion, etc.).
  3. WmSequencedGroup is used to collect any number of host actions which are run in the order specified.
  4. WmSequencedGroups can be specified to execute at different phases of the simulation using the SolverSequence. Lists of sequence groups are run serially in the order specified. Note that any sequence group can appear in any of the specified time periods, and can even appear multiple times within a single specified time period. You may only have one solver sequence per solver. Periods at which a sequence group can be run:
    • StartOnly - only run once at the beginning of the simulation. Note that on restarts these sequence groups are not run. Generally this is used for setting up initial conditions.
    • PerStep - These sequence groups are run every timestep.
    • PreRedPerStep - These sequence groups are run when the timestepper deems that the previous timestep must be re-ran (usually to take a smaller timestep).
    • EndOnly - These sequence groups are run when the simulation reaches the end normally, i.e. no error termination conditions arise.
    • Restart - These sequence groups are run when a simulation is being started from the middle of a previously terminated simulation, whether the termination was erroneous or not. One example use case of "non-erroneous" termination would be to continue a previous simulation where the end time was specified too early and you would like to extend the end time without having to re-run from the start.