OpenFOAM bits: the #includeIfPresent dictionary directive
As mentioned in the ReleaseNotes-1.6,
the new
#includeIfPresent
directive is similar to the#include
directive, but does not generate an error if the file does not exist.
Initially, this may not sound particularly useful. Perhaps you haven’t
really found very many uses for the normal #include
directive anyway, so
why would you ever want it to fail silently if the file doesn’t exist? This
functionality turns out, however, to actually be quite useful.
Before examining this, we should first be aware of the OpenFOAM string
expansion that is implicit in the #include
and in the #includeIfPresent
directives.
OpenFOAM String Expansion
In many places where a string or filename is expected within an OpenFOAM dictionary, the string is first subjected to an expansion. The two most essential aspects:
-
Elements within the string that resemble shell variables (eg,
$VAR
,${VAR}
, ..) are expanded using environment variables. -
A leading
~OpenFOAM
is expanded to a user/site/shipped OpenFOAM configuration directory. The commandfoamEtcFile -list
can be used to report the corresponding directories for your installation. While a leading~/
and a leading~user
are also expanded with their usual Unix shell meanings, the~OpenFOAM
pseudo-user is a very much more flexible solution.
In addition to the original environment, the following environment variables are also defined internally (since almost all of the OpenFOAM solvers and utilities use the argList class):
- FOAM_CASE
- set to the path of the global case (same for serial and parallel jobs). This corresponds roughly to the value provided by the -case option or the current working directory if the -case option was not specified.
- FOAM_CASENAME
- set to the name of the global case. This is the equivalent of the basename of FOAM_CASE (ie, stripped of any leading directory components).
Whereas the FOAM_CASENAME variable is new in the OpenFOAM-1.6 version, the FOAM_CASE variable has existed for sometime in OpenFOAM. With FOAM_CASE, we have a convenient means of addressing locations elsewhere within our calculation case. This is often useful when specifying the location of tabulated boundary conditions that don’t really fit anywhere nicely in OpenFOAM’s constant/system/timeValue directory scheme.
If you happen to encounter a dictionary that uses $FOAM_ROOT/$FOAM_CASE, this is from the days before OpenFOAM had the -case option. In current versions (OpenFOAM-1.5 and newer), $FOAM_ROOT/ is no longer defined or needed and you can simply remove it.
Example 1
Sharing constant/ and system/ directories between related calculations
is an example of where #includeIfPresent
comes in handy.
Suppose we have an existing mesh, boundary conditions and solver settings in
the directory oldCalc and we would like to reuse as much as possible for a
similar calculation newCalc. The only chage might be, for example, a
change in the inlet velocity boudary condition. The brute-force method would
be to use cp
or rsync
to copy everything and then make our changes to
the boundary conditions before running the newCalc. This, however, not
only wastes disk space, but is really annoying later when you try to figure
out if oldCalc and newCalc did actually use exactly the same mesh.
A much more elegant solution is to simply use file links. For example,
mkdir newCalc
cd newCalc
ln -s ../oldCalc/0 .
ln -s ../oldCalc/constant .
ln -s ../oldCalc/system .
Of course since we have linked in the entire constant/ and system/ directories, it is going to be rather difficult to modify anything in newCalc/system/ without inadvertently changing the values for oldCalc/system/. The solution is to have slightly modified versions of the important system/ dictionaries (ie, system/controlDict, system/decomposeParDict, system/fvSchemes, system/fvSolution) that have a very minor modification near the end of them:
and
etc.
This makes it possible to share most of the settings while retaining the ability to make local adjstments for a particular case without affecting the other. This type of statement can be added to all of your system/ dictionaries without any problems. The files will only be included if they are present.
Since these included files are only included by another dictionary and are never read directly by other OpenFOAM operations, we can forgo the niceties of having a FoamFile header if we wish. For example, to alter a few relaxation factors:
echo "relaxationFactors { p 0.15; U 0.6; }" > fvSolution
touch system/fvSolution
Which also lends itself quite nicely to scripting. Since we didn’t actually
edit any values in system/fvSolution itself, but instead used the
#includeIfPresent
to merge in the new values, the extra touch
is
required to change the file modification time of system/fvSolution and get
OpenFOAM to notice that something has changed.
Later, when our calculation is now over the rough bits, we may wish to revert to the normal relaxation factors. We have a variety of ways to achieve it. We’ll take the simplest:
rm fvSolution
touch system/fvSolution
Example 2
Now that we have the general idea, we can address what was glossed over in the previous example: if the 0/ directory is shared, how can we specify different boundary conditions?
The solution builds on the same concept that I posted on the OpenFOAM forum some time ago. The idea being to specially craft the 0/ fields to avoid directly mentioning any specific values.
As a concrete example, we could have a 0/T file that looks like this:
The first point of interest is that we place all of our boundary conditions in a single file and place this file directly in the $FOAM_CASE directory. This not only lets us share the 0/ files between the calculations, it also places all of our essential boundary conditions in a single file where they are easy to find and simple to edit. The $FOAM_CASE/boundaryConditions file would contain something like this:
The second point of interest is that the 0/U, 0/T, … files define an inletCondition that can be used in a generic way within the 0/boundaryField file. This file can (should) include any specific specializations near the end:
The contents (if any) of the “$FOAM_CASE/boundaryField” and “$FOAM_CASE/boundaryField-$FOAM_CASENAME” files can now be used to specify which particular patches are currently to have an inletCondition and which should have a wallCondition etc.
Closure
Clever use of dictionary structuring and the dictionary directives allows us
to reuse most, if not all, of the dictionaries and initial condition
definitions between related calculations. The #include
and
#includeIfPresent
directives let us do so with a very large degree of
flexiblity.
Caution
Although the #include
looks like a pre-processor directive, it is in fact
a function written without any space in between:
#include "..." <- OK
# include "..." <- WRONG