Reference documentation for deal.II version Git f982bbb689 2019-09-17 15:49:08 -0400
The step-19 tutorial program

1. Introduction
2. The commented program

# Introduction

In step-18, we saw a need to write output files in an intermediate format: in a parallel program, it doesn't scale well if all processors participate in computing a result, and then only a single processor generates the graphical output. Rather, each of them should generate output for its share of the domain, and later on merge all these output files into a single one.

Thus was the beginning of step-19: it is the program that reads a number of files written in intermediate format, and merges and converts them into the final format that one would like to use for visualization. It can also be used for the following purpose: if you are unsure at the time of a computation what graphics program you would like to use, write your results in intermediate format; it can later be converted, using the present program, to any other format you may want.

While this in itself was not interesting enough to make a tutorial program, we have used the opportunity to introduce one class that has proven to be extremely help- and useful in real application programs, but had not been covered by any of the previous tutorial programs: the ParameterHandler class. This class is used in applications that want to have some of their behavior determined at run time, using input files. For example, one may want to specify the geometry, or specifics of the equation to be solved, at run time. Other typical parameters are the number of nonlinear iterations, the name of output files, or the names of input files specifying material properties or boundary conditions.

Working with such parameter files is not rocket science. However, it is rather tedious to write the parsers for such files, in particular if they should be extensible, be able to group parameters into subsections, perform some error checks such as that parameters can have only certain kinds of values (for example, it should only be allowed to have integer values in an input file for parameters that denote a number of iteration), and similar requirements. The ParameterHandler class allows for all this: an application program will declare the parameters it expects (or call a function in the library that declares a number of parameters for you), the ParameterHandler class then reads an input file with all these parameters, and the application program can then get their values back from this class.

In order to perform these three steps, the ParameterHandler offers three sets of functions: first, the ParameterHandler::declare_entry function is used to declare the existence of a named parameter in the present section of the input file (one can enter and leave subsections in a parameter file just like you would navigate through a directory tree in a file system, with the functions ParameterHandler::enter_subsection and ParameterHandler::leave_subsection taking on the roles of the commands cd dir and cd ..; the only difference being that if you enter a subsection that has never been visited before, it is created: it isn't necessary to "create" subsections explicitly). When declaring a parameter, one has to specify its name and default value, in case the parameter isn't later listed explicitly in the parameter file. In addition to that, there are optional arguments indicating a pattern that a parameter has to satisfy, such as being an integer (see the discussion above), and a help text that might later give an explanation of what the parameter stands for.

Once all parameters have been declared, parameters can be read, using the ParameterHandler::parse_input family of functions. There are versions of this function that can read from a file stream, that take a file name, or that simply take a string and parse it. When reading parameters, the class makes sure that only parameters are listed in the input that have been declared before, and that the values of parameters satisfy the pattern that has been given to describe the kind of values a parameter can have. Input that uses undeclared parameters or values for parameters that do not conform to the pattern are rejected by raising an exception.

A typical input file will look like this:

set Output format = dx
set Output file = my_output_file.dx
set Maximal number of iterations = 13
subsection Application
set Color of output = blue
set Generate output = false
end

Note that subsections can be nested.

Finally, the application program can get the values of declared parameters back by traversing the subsections of the parameter tree and using the ParameterHandler::get and related functions. The ParameterHandler::get simply returns the value of a parameter as a string, whereas ParameterHandler::get_integer, ParameterHandler::get_double, and ParameterHandler::get_bool already convert them to the indicated type.

Using the ParameterHandler class therefore provides for a pretty flexible mechanism to handle all sorts of moderately complex input files without much effort on the side of the application programmer. We will use this to provide all sorts of options to the step-19 program in order to convert from intermediate file format to any other graphical file format.

The rest of the story is probably best told by looking at the source of step-19 itself. Let us, however, end this introduction by pointing the reader at the extensive class documentation of the ParameterHandler class for more information on specific details of that class.

# The commented program

#### Preliminaries

As usual, we start with include files. This program is content with really few of these – we only need two files from the library (one for input and output of graphical data, one for parameter handling), and a few C++ standard headers:

#include <deal.II/base/data_out_base.h>
#include <deal.II/base/parameter_handler.h>
#include <list>
#include <iostream>
#include <fstream>

As mentioned in the first few tutorial programs, all names in deal.II are declared in a namespace dealii. To make using these function and class names simpler, we import the entire content of that namespace into the global scope. As done for all previous programs already, we'll also place everything we do here into a namespace of its own:

namespace Step19
{
using namespace dealii;

Before we start with the actual program, let us declare a few global variables that will be used to hold the parameters this program is going to use. Usually, global variables are frowned upon for a good reason, but since we have such a short program here that does only a single thing, we may stray from our usual line and make these variables global, rather than passing them around to all functions or encapsulating them into a class.

The variables we have are: first, an object that will hold parameters of operation, such as output format (unless given on the command line); second, the names of input and output files; and third, the format in which the output is to be written:

std::vector<std::string> input_file_names;
std::string output_file;
std::string output_format;

All the stuff this program does can be done from here on. As described in the introduction, what we have to do is declare what values the parameter file can have, parse the command line, read the input files, then write the output. We will do this in this order of operation, but before that let us declare a function that prints a message about how this program is to be used; the function first prints a general message, and then goes on to list the parameters that are allowed in the parameter file (the ParameterHandler class has a function to do exactly this; see the results section for what it prints):

void print_usage_message()
{
static const char *message =
"\n"
"Converter from deal.II intermediate format to other graphics formats.\n"
"\n"
"Usage:\n"
" ./step-19 [-p parameter_file] list_of_input_files \n"
" [-x output_format] [-o output_file]\n"
"\n"
"Parameter sequences in brackets can be omitted if a parameter file is\n"
"specified on the command line and if it provides values for these\n"
"missing parameters.\n"
"\n"
"The parameter file has the following format and allows the following\n"
"values (you can cut and paste this and use it for your own parameter\n"
"file):\n"
"\n";
std::cout << message;
}

#### Declaring parameters for the input file

The second function is used to declare the parameters this program accepts from the input file. While we don't actually take many parameters from the input file except for, possibly, the output file name and format, we nevertheless want to show how to work with parameter files.

In short, the ParameterHandler class works as follows: one declares the entries of parameters that can be given in input files together, and later on one can read an input file in which these parameters are set to their values. If a parameter is not listed in the input file, the default value specified in the declaration of that parameter is used. After that, the program can query the values assigned to certain parameters from the ParameterHandler object.

Declaring parameters can be done using the ParameterHandler::declare_entry function. It's arguments are the name of a parameter, a default value (given as a string, even if the parameter is numeric in nature, and thirdly an object that describes constraints on values that may be passed to this parameter. In the example below, we use an object of type Patterns::Anything to denote that there are no constraints on file names (this is, of course, not true – the operating system does have constraints, but from an application standpoint, almost all names are valid). In other cases, one may, for example, use Patterns::Integer to make sure that only parameters are accepted that can be interpreted as integer values (it is also possible to specify bounds for integer values, and all values outside this range are rejected), Patterns::Double for floating point values, classes that make sure that the given parameter value is a comma separated list of things, etc. Take a look at the Patterns namespace to see what is possible.

The fourth argument to declare_entry is a help string that can be printed to document what this parameter is meant to be used for and other information you may consider important when declaring this parameter. The default value of this fourth argument is the empty string.

I always wanted to have an example program describing the ParameterHandler class, because it is so particularly useful. It would have been useful in a number of previous example programs (for example, in order to let the tolerance for linear solvers, or the number of refinement steps be determined by a run-time parameter, rather than hard-coding them into the program), but it turned out that trying to explain this class there would have overloaded them with things that would have distracted from the main purpose. However, while writing this program, I realized that there aren't all that many parameters this program can usefully ask for, or better, it turned out: declaring and querying these parameters was already done centralized in one place of the library, namely the DataOutInterface class that handles exactly this – managing parameters for input and output.

So the second function call in this function is to let the DataOutInterface declare a good number of parameters that control everything from the output format to what kind of output should be generated if output is written in a specific graphical format. For example, when writing data in encapsulated postscript (EPS) format, the result is just a 2d projection, not data that can be viewed and rotated with a viewer. Therefore, one has to choose the viewing angle and a number of other options up front, when output is generated, rather than playing around with them later on. The call to DataOutInterface::declare_parameters declares entries that allow to specify them in the parameter input file during run-time. If the parameter file does not contain entries for them, defaults are taken.

As a final note: DataOutInterface is a template, because it is usually used to write output for a specific space dimension. However, this program is supposed to be used for all dimensions at the same time, so we don't know at compile time what the right dimension is when specifying the template parameter. Fortunately, declaring parameters is something that is space dimension independent, so we can just pick one arbitrarily. We pick 1, but it could have been any other number as well.

void declare_parameters()
{
prm.declare_entry("Output file",
"",
"The name of the output file to be generated");

Since everything that this program can usefully request in terms of input parameters is already handled by now, let us nevertheless show how to use input parameters in other circumstances. First, parameters are like files in a directory tree: they can be in the top-level directory, but you can also group them into subdirectories to make it easier to find them or to be able to use the same parameter name in different contexts.

Let us first declare a dummy parameter in the top-level section; we assume that it will denote the number of iterations, and that useful numbers of iterations that a user should be able to specify are in the range 1...1000, with a default value of 42:

prm.declare_entry("Dummy iterations",
"42",
"A dummy parameter asking for an integer");

Next, let us declare a sub-section (the equivalent to a subdirectory). When entered, all following parameter declarations will be within this subsection. To also visually group these declarations with the subsection name, I like to use curly braces to force my editor to indent everything that goes into this sub-section by one level of indentation. In this sub-section, we shall have two entries, one that takes a Boolean parameter and one that takes a selection list of values, separated by the '|' character:

prm.enter_subsection("Dummy subsection");
{
prm.declare_entry("Dummy generate output",
"true",
"A dummy parameter that can be fed with either "
"'true' or 'false'");
prm.declare_entry("Dummy color of output",
"red",
Patterns::Selection("red|black|blue"),
"A dummy parameter that shows how one can define a "
"parameter that can be assigned values from a finite "
"set of values");
}

After this, we have left the subsection again. You should have gotten the idea by now how one can nest subsections to separate parameters. There are a number of other possible patterns describing possible values of parameters; in all cases, if you try to pass a parameter to the program that does not match the expectations of the pattern, it will reject the parameter file and ask you to fix it. After all, it does not make much sense if you had an entry that contained the entry "red" for the parameter "Generate output".

}

#### Parsing the command line

Our next task is to see what information has been provided on the command line. First, we need to be sure that there is at least one parameter: an input file. The format and the output file can be specified in the parameter file, but the list of input files can't, so at least one parameter needs to be there. Together with the name of the program (the zeroth parameter), argc must therefore be at least 2. If this is not the case, we print an error message and exit:

void parse_command_line(const int argc, char *const *argv)
{
if (argc < 2)
{
print_usage_message();
exit(1);
}

Next, collect all parameters in a list that will be somewhat simpler to handle than the argc/argv mechanism. We omit the name of the executable at the zeroth index:

std::list<std::string> args;
for (int i = 1; i < argc; ++i)
args.emplace_back(argv[i]);

Then process all these parameters. If the parameter is -p, then there must be a parameter file following (which we should then read), in case of -x it is the name of an output format. Finally, for -o it is the name of the output file. In all cases, once we've treated a parameter, we remove it from the list of parameters:

while (args.size())
{
if (args.front() == std::string("-p"))
{
if (args.size() == 1)
{
std::cerr << "Error: flag '-p' must be followed by the "
<< "name of a parameter file." << std::endl;
print_usage_message();
exit(1);
}
args.pop_front();
const std::string parameter_file = args.front();
args.pop_front();

Now read the input file :

prm.parse_input(parameter_file);

Both the output file name as well as the format can be specified on the command line. We have therefore given them global variables that hold their values, but they can also be set in the parameter file. We therefore need to extract them from the parameter file here, because they may be overridden by later command line parameters:

if (output_file == "")
output_file = prm.get("Output file");
if (output_format == "")
output_format = prm.get("Output format");

Finally, let us note that if we were interested in the values of the parameters declared above in the dummy subsection, we would write something like this to extract the value of the Boolean flag (the prm.get function returns the value of a parameter as a string, whereas the prm.get_X functions return a value already converted to a different type):

prm.enter_subsection("Dummy subsection");
{
prm.get_bool("Dummy generate output");
}

We would assign the result to a variable, of course, but don't here in order not to generate an unused variable that the compiler might warn about.

Alas, let's move on to handling of output formats:

}
else if (args.front() == std::string("-x"))
{
if (args.size() == 1)
{
std::cerr << "Error: flag '-x' must be followed by the "
<< "name of an output format." << std::endl;
print_usage_message();
exit(1);
}
args.pop_front();
output_format = args.front();
args.pop_front();
}
else if (args.front() == std::string("-o"))
{
if (args.size() == 1)
{
std::cerr << "Error: flag '-o' must be followed by the "
<< "name of an output file." << std::endl;
print_usage_message();
exit(1);
}
args.pop_front();
output_file = args.front();
args.pop_front();
}

Otherwise, this is not a parameter that starts with a known minus sequence, and we should consider it to be the name of an input file. Let us therefore add this file to the list of input files:

else
{
input_file_names.push_back(args.front());
args.pop_front();
}
}

Next check a few things and create errors if the checks fail. Firstly, there must be at least one input file

if (input_file_names.size() == 0)
{
std::cerr << "Error: No input file specified." << std::endl;
print_usage_message();
exit(1);
}
}

#### Generating output

Now that we have all the information, we need to read all the input files, merge them, and generate a single output file. This, after all, was the motivation, borne from the necessity encountered in the step-18 tutorial program, to write this program in the first place.

So what we do first is to declare an object into which we will merge the data from all the input file, and read in the first file through a stream. Note that every time we open a file, we use the AssertThrow macro to check whether the file is really readable – if it isn't then this will trigger an exception and corresponding output will be generated from the exception handler in main():

template <int dim, int spacedim>
void do_convert()
{
{
std::ifstream input(input_file_names[0]);
AssertThrow(input, ExcIO());
}

For all the other input files, we read their data into an intermediate object, and then merge that into the first object declared above:

for (unsigned int i = 1; i < input_file_names.size(); ++i)
{
std::ifstream input(input_file_names[i]);
AssertThrow(input, ExcIO());
}

Once we have this, let us open an output stream, and parse what we got as the name of the output format into an identifier. Fortunately, the DataOutBase class has a function that does this parsing for us, i.e. it knows about all the presently supported output formats and makes sure that they can be specified in the parameter file or on the command line. Note that this ensures that if the library acquires the ability to output in other output formats, this program will be able to make use of this ability without having to be changed!

std::ofstream output_stream(output_file);
AssertThrow(output_stream, ExcIO());

Finally, write the merged data to the output:

merged_data.write(output_stream, format);
}

#### Dispatching output generation

The function above takes template parameters relating to the space dimension of the output, and the dimension of the objects to be output. (For example, when outputting whole cells, these two dimensions are the same, but the intermediate files may contain only data pertaining to the faces of cells, in which case the first parameter will be one less than the space dimension.)

The problem is: at compile time, we of course don't know the dimensions used in the input files. We have to plan for all cases, therefore. This is a little clumsy, since we need to specify the dimensions statically at compile time, even though we will only know about them at run time.

So here is what we do: from the first input file, we determine (using a function in DataOutBase that exists for this purpose) these dimensions. We then have a series of switches that dispatch, statically, to the do_convert functions with different template arguments. Not pretty, but works. Apart from this, the function does nothing – except making sure that it covered the dimensions for which it was called, using the AssertThrow macro at places in the code that shouldn't be reached:

void convert()
{
AssertThrow(input_file_names.size() > 0,
ExcMessage("No input files specified."));
std::ifstream input(input_file_names[0]);
AssertThrow(input, ExcIO());
const std::pair<unsigned int, unsigned int> dimensions =
switch (dimensions.first)
{
case 1:
switch (dimensions.second)
{
case 1:
do_convert<1, 1>();
return;
case 2:
do_convert<1, 2>();
return;
}
break;
case 2:
switch (dimensions.second)
{
case 2:
do_convert<2, 2>();
return;
case 3:
do_convert<2, 3>();
return;
}
break;
case 3:
switch (dimensions.second)
{
case 3:
do_convert<3, 3>();
return;
}
}
}
} // namespace Step19

#### main()

Finally, the main program. There is not much more to do than to make sure parameters are declared, the command line is parsed (which includes reading parameter files), and finally making sure the input files are read and output is generated. Everything else just has to do with handling exceptions and making sure that appropriate output is generated if one is thrown.

int main(int argc, char **argv)
{
try
{
using namespace Step19;
declare_parameters();
parse_command_line(argc, argv);
convert();
}
catch (std::exception &exc)
{
std::cerr << std::endl
<< std::endl
<< "----------------------------------------------------"
<< std::endl;
std::cerr << "Exception on processing: " << std::endl
<< exc.what() << std::endl
<< "Aborting!" << std::endl
<< "----------------------------------------------------"
<< std::endl;
return 1;
}
catch (...)
{
std::cerr << std::endl
<< std::endl
<< "----------------------------------------------------"
<< std::endl;
std::cerr << "Unknown exception!" << std::endl
<< "Aborting!" << std::endl
<< "----------------------------------------------------"
<< std::endl;
return 1;
};
return 0;
}

# Results

With all that above, here is first what we get if we just run the program without any parameters at all:

examples/step-19> ./step-19
Converter from deal.II intermediate format to other graphics formats.
Usage:
./step-19 [-p parameter_file] list_of_input_files
[-x output_format] [-o output_file]
Parameter sequences in brackets can be omitted if a parameter file is
specified on the command line and if it provides values for these
missing parameters.
The parameter file has the following format and allows the following
values (you can cut and paste this and use it for your own parameter
file):
# Listing of Parameters
# ---------------------
# A dummy parameter asking for an integer
set Dummy iterations = 42
# The name of the output file to be generated
set Output file =
# A name for the output format to be used
set Output format = gnuplot
subsection DX output parameters
# A Boolean field indicating whether neighborship information between cells
# is to be written to the OpenDX output file
set Write neighbors = true
end
subsection Dummy subsection
# A dummy parameter that shows how one can define a parameter that can be
# assigned values from a finite set of values
set Dummy color of output = red
# A dummy parameter that can be fed with either 'true' or 'false'
set Dummy generate output = true
end
subsection Eps output parameters
# Angle of the viewing position against the vertical axis
set Azimut angle = 60
# Name of a color function used to colorize mesh lines and/or cell
# interiors
set Color function = default
# Whether the interior of cells shall be shaded
set Color shading of interior of cells = true
# Whether the mesh lines, or only the surface should be drawn
set Draw mesh lines = true
# Whether only the mesh lines, or also the interior of cells should be
# plotted. If this flag is false, then one can see through the mesh
set Fill interior of cells = true
# Number of the input vector that is to be used to generate color
# information
set Index of vector for color = 0
# Number of the input vector that is to be used to generate height
# information
set Index of vector for height = 0
# The width in which the postscript renderer is to plot lines
set Line widths in eps units = 0.5
# Whether width or height should be scaled to match the given size
set Scale to width or height = width
# Scaling for the z-direction relative to the scaling used in x- and
# y-directions
set Scaling for z-axis = 1
# The size (width or height) to which the eps output file is to be scaled
set Size (width or height) in eps units = 300
# Angle of the viewing direction against the y-axis
set Turn angle = 30
end
subsection Povray output parameters
# Whether camera and lighting information should be put into an external
# file "data.inc" or into the POVRAY input file
set Include external file = true
# Whether POVRAY should use bicubic patches
set Use bicubic patches = false
# A flag indicating whether POVRAY should use smoothed triangles instead of
# the usual ones
set Use smooth triangles = false
end
subsection UCD output parameters
# A flag indicating whether a comment should be written to the beginning of
# the output file indicating date and time of creation as well as the
# creating program
set Write preamble = true
end

That's a lot of output for such a little program, but then that's also a lot of output formats that deal.II supports. You will realize that the output consists of first entries in the top-level section (sorted alphabetically), then a sorted list of subsections. Most of the parameters have been declared by the DataOutBase class, but there are also the dummy entries and sections we have added in the declare_parameters() function, along with their default values and documentations.

Let us try to run this program on a set of input files generated by a modified step-18 run on 32 nodes of a cluster. The computation was rather big, with more than 350,000 cells and some 1.2M unknowns. That makes for 32 rather big intermediate files that we will try to merge using the present program. Here is the list of files, totaling some 245MB of data:

examples/step-19> ls -l *d2
-rw-r--r-- 1 bangerth wheeler 7982085 Aug 12 10:11 solution-0005.0000-000.d2
-rw-r--r-- 1 bangerth wheeler 7888316 Aug 12 10:13 solution-0005.0000-001.d2
-rw-r--r-- 1 bangerth wheeler 7715984 Aug 12 10:09 solution-0005.0000-002.d2
-rw-r--r-- 1 bangerth wheeler 7887648 Aug 12 10:06 solution-0005.0000-003.d2
-rw-r--r-- 1 bangerth wheeler 7833291 Aug 12 10:09 solution-0005.0000-004.d2
-rw-r--r-- 1 bangerth wheeler 7536394 Aug 12 10:07 solution-0005.0000-005.d2
-rw-r--r-- 1 bangerth wheeler 7817551 Aug 12 10:06 solution-0005.0000-006.d2
-rw-r--r-- 1 bangerth wheeler 7996660 Aug 12 10:07 solution-0005.0000-007.d2
-rw-r--r-- 1 bangerth wheeler 7761545 Aug 12 10:06 solution-0005.0000-008.d2
-rw-r--r-- 1 bangerth wheeler 7754027 Aug 12 10:07 solution-0005.0000-009.d2
-rw-r--r-- 1 bangerth wheeler 7607545 Aug 12 10:11 solution-0005.0000-010.d2
-rw-r--r-- 1 bangerth wheeler 7728039 Aug 12 10:07 solution-0005.0000-011.d2
-rw-r--r-- 1 bangerth wheeler 7577293 Aug 12 10:14 solution-0005.0000-012.d2
-rw-r--r-- 1 bangerth wheeler 7735626 Aug 12 10:10 solution-0005.0000-013.d2
-rw-r--r-- 1 bangerth wheeler 7629075 Aug 12 10:10 solution-0005.0000-014.d2
-rw-r--r-- 1 bangerth wheeler 7314459 Aug 12 10:09 solution-0005.0000-015.d2
-rw-r--r-- 1 bangerth wheeler 7414738 Aug 12 10:10 solution-0005.0000-016.d2
-rw-r--r-- 1 bangerth wheeler 7330518 Aug 12 10:05 solution-0005.0000-017.d2
-rw-r--r-- 1 bangerth wheeler 7418213 Aug 12 10:11 solution-0005.0000-018.d2
-rw-r--r-- 1 bangerth wheeler 7508715 Aug 12 10:08 solution-0005.0000-019.d2
-rw-r--r-- 1 bangerth wheeler 7747143 Aug 12 10:06 solution-0005.0000-020.d2
-rw-r--r-- 1 bangerth wheeler 7563548 Aug 12 10:05 solution-0005.0000-021.d2
-rw-r--r-- 1 bangerth wheeler 7846767 Aug 12 10:12 solution-0005.0000-022.d2
-rw-r--r-- 1 bangerth wheeler 7479576 Aug 12 10:12 solution-0005.0000-023.d2
-rw-r--r-- 1 bangerth wheeler 7925060 Aug 12 10:12 solution-0005.0000-024.d2
-rw-r--r-- 1 bangerth wheeler 7842034 Aug 12 10:13 solution-0005.0000-025.d2
-rw-r--r-- 1 bangerth wheeler 7585448 Aug 12 10:13 solution-0005.0000-026.d2
-rw-r--r-- 1 bangerth wheeler 7609698 Aug 12 10:10 solution-0005.0000-027.d2
-rw-r--r-- 1 bangerth wheeler 7576053 Aug 12 10:08 solution-0005.0000-028.d2
-rw-r--r-- 1 bangerth wheeler 7682418 Aug 12 10:08 solution-0005.0000-029.d2
-rw-r--r-- 1 bangerth wheeler 7544141 Aug 12 10:05 solution-0005.0000-030.d2
-rw-r--r-- 1 bangerth wheeler 7348899 Aug 12 10:04 solution-0005.0000-031.d2

So let's see what happens if we attempt to merge all these files into a single one:

examples/step-19> time ./step-19 solution-0005.0000-*.d2 -x gmv -o solution-0005.gmv
real 2m08.35s
user 1m26.61s
system 0m05.74s
examples/step-19> ls -l solution-0005.gmv
-rw-r--r-- 1 bangerth wheeler 240680494 Sep 9 11:53 solution-0005.gmv

So in roughly two minutes we have merged 240MB of data. Counting reading and writing, that averages a throughput of 3.8MB per second, not so bad.

If visualized, the output looks very much like that shown for step-18. But that's not quite as important for the moment, rather we are interested in showing how to use the parameter file. To this end, remember that if no parameter file is given, or if it is empty, all the default values listed above are used. However, whatever we specify in the parameter file is used, unless overridden again by parameters found later on the command line.

For example, let us use a simple parameter file named solution-0005.prm that contains only one line:

set Output format = gnuplot

If we run step-19 with it again, we obtain this (for simplicity, and because we don't want to visualize 240MB of data anyway, we only convert the one, the twelfth, intermediate file to gnuplot format):

examples/step-19> ./step-19 solution-0005.0000-012.d2 -p solution-0005.prm -o solution-0005.gnuplot
examples/step-19> ls -l solution-0005.gnuplot
-rw-r--r-- 1 bangerth wheeler 20281669 Sep 9 12:15 solution-0005.gnuplot

We can then visualize this one file with gnuplot, obtaining something like this:

That's not particularly exciting, but the file we're looking at has only one 32nd of the entire domain anyway, so we can't expect much.

In more complicated situations, we would use parameter files that set more of the values to non-default values. A file for which this is the case could look like this, generating output for the OpenDX visualization program:

set Output format = dx
set Output file = my_output_file.dx
set Dummy iterations = -13
subsection Dummy subsection
set Dummy color of output = blue
set Dummy generate output = false
end

If one wanted to, one could write comments into the file using the same format as used above in the help text, i.e. everything on a line following a hashmark (#) is considered a comment.

If one runs step-19 with this input file, this is what is going to happen:

examples/step-19> ./step-19 solution-0005.0000-012.d2 -p solution-0005.prm
Line 4:
The entry value
-13
for the entry named
Dummy iterations
does not match the given pattern
[Integer range 1...1000 (inclusive)]

Ah, right: valid values for the iteration parameter needed to be within the range [1...1000]. We would fix that, then go back to run the program with correct parameters.

This program should have given some insight into the input parameter file handling that deal.II provides. The ParameterHandler class has a few more goodies beyond what has been shown in this program, for those who want to use this class, it would be useful to read the documentation of that class to get the full picture.

# The plain program

/* ---------------------------------------------------------------------
*
* Copyright (C) 2005 - 2018 by the deal.II authors
*
* This file is part of the deal.II library.
*
* The deal.II library is free software; you can use it, redistribute
* it, and/or modify it under the terms of the GNU Lesser General
* version 2.1 of the License, or (at your option) any later version.
* The full text of the license can be found in the file LICENSE.md at
* the top level directory of deal.II.
*
* ---------------------------------------------------------------------
*
* Author: Luca Heltai, Wolfgang Bangerth, 2005
*/
#include <deal.II/base/data_out_base.h>
#include <deal.II/base/parameter_handler.h>
#include <list>
#include <iostream>
#include <fstream>
namespace Step19
{
using namespace dealii;
std::vector<std::string> input_file_names;
std::string output_file;
std::string output_format;
void print_usage_message()
{
static const char *message =
"\n"
"Converter from deal.II intermediate format to other graphics formats.\n"
"\n"
"Usage:\n"
" ./step-19 [-p parameter_file] list_of_input_files \n"
" [-x output_format] [-o output_file]\n"
"\n"
"Parameter sequences in brackets can be omitted if a parameter file is\n"
"specified on the command line and if it provides values for these\n"
"missing parameters.\n"
"\n"
"The parameter file has the following format and allows the following\n"
"values (you can cut and paste this and use it for your own parameter\n"
"file):\n"
"\n";
std::cout << message;
}
void declare_parameters()
{
prm.declare_entry("Output file",
"",
"The name of the output file to be generated");
prm.declare_entry("Dummy iterations",
"42",
"A dummy parameter asking for an integer");
prm.enter_subsection("Dummy subsection");
{
prm.declare_entry("Dummy generate output",
"true",
"A dummy parameter that can be fed with either "
"'true' or 'false'");
prm.declare_entry("Dummy color of output",
"red",
Patterns::Selection("red|black|blue"),
"A dummy parameter that shows how one can define a "
"parameter that can be assigned values from a finite "
"set of values");
}
}
void parse_command_line(const int argc, char *const *argv)
{
if (argc < 2)
{
print_usage_message();
exit(1);
}
std::list<std::string> args;
for (int i = 1; i < argc; ++i)
args.emplace_back(argv[i]);
while (args.size())
{
if (args.front() == std::string("-p"))
{
if (args.size() == 1)
{
std::cerr << "Error: flag '-p' must be followed by the "
<< "name of a parameter file." << std::endl;
print_usage_message();
exit(1);
}
args.pop_front();
const std::string parameter_file = args.front();
args.pop_front();
prm.parse_input(parameter_file);
if (output_file == "")
output_file = prm.get("Output file");
if (output_format == "")
output_format = prm.get("Output format");
prm.enter_subsection("Dummy subsection");
{
prm.get_bool("Dummy generate output");
}
}
else if (args.front() == std::string("-x"))
{
if (args.size() == 1)
{
std::cerr << "Error: flag '-x' must be followed by the "
<< "name of an output format." << std::endl;
print_usage_message();
exit(1);
}
args.pop_front();
output_format = args.front();
args.pop_front();
}
else if (args.front() == std::string("-o"))
{
if (args.size() == 1)
{
std::cerr << "Error: flag '-o' must be followed by the "
<< "name of an output file." << std::endl;
print_usage_message();
exit(1);
}
args.pop_front();
output_file = args.front();
args.pop_front();
}
else
{
input_file_names.push_back(args.front());
args.pop_front();
}
}
if (input_file_names.size() == 0)
{
std::cerr << "Error: No input file specified." << std::endl;
print_usage_message();
exit(1);
}
}
template <int dim, int spacedim>
void do_convert()
{
{
std::ifstream input(input_file_names[0]);
AssertThrow(input, ExcIO());
}
for (unsigned int i = 1; i < input_file_names.size(); ++i)
{
std::ifstream input(input_file_names[i]);
AssertThrow(input, ExcIO());
}
std::ofstream output_stream(output_file);
AssertThrow(output_stream, ExcIO());
merged_data.write(output_stream, format);
}
void convert()
{
AssertThrow(input_file_names.size() > 0,
ExcMessage("No input files specified."));
std::ifstream input(input_file_names[0]);
AssertThrow(input, ExcIO());
const std::pair<unsigned int, unsigned int> dimensions =
switch (dimensions.first)
{
case 1:
switch (dimensions.second)
{
case 1:
do_convert<1, 1>();
return;
case 2:
do_convert<1, 2>();
return;
}
break;
case 2:
switch (dimensions.second)
{
case 2:
do_convert<2, 2>();
return;
case 3:
do_convert<2, 3>();
return;
}
break;
case 3:
switch (dimensions.second)
{
case 3:
do_convert<3, 3>();
return;
}
}
}
} // namespace Step19
int main(int argc, char **argv)
{
try
{
using namespace Step19;
declare_parameters();
parse_command_line(argc, argv);
convert();
}
catch (std::exception &exc)
{
std::cerr << std::endl
<< std::endl
<< "----------------------------------------------------"
<< std::endl;
std::cerr << "Exception on processing: " << std::endl
<< exc.what() << std::endl
<< "Aborting!" << std::endl
<< "----------------------------------------------------"
<< std::endl;
return 1;
}
catch (...)
{
std::cerr << std::endl
<< std::endl
<< "----------------------------------------------------"
<< std::endl;
std::cerr << "Unknown exception!" << std::endl
<< "Aborting!" << std::endl
<< "----------------------------------------------------"
<< std::endl;
return 1;
};
return 0;
}