postProcess: Improve usability of packaged function objects

Packaged function objects can now be deployed equally effectively by
(a) using a locally edited copy of the configuration file, or by
(b) passing parameters as arguments to the global configuration file.

For example, to post-process the pressure field "p" at a single location
"(1 2 3)", the user could first copy the "probes" packaged function
object file to their system directory by calling "foamGet probes". They
could then edit the file to contain the following entries:

    points ((1 2 3));
    field  p;

The function object can then be executed by the postProcess application:

    postProcess -func probes

Or it can be called at run-time, by including from within the functions
section of the system/controlDict file:

    #includeFunc probes

Alternatively, the field and points parameters could be passed as
arguments either to the postProcess application by calling:

    postProcess -func "probes(points=((1 2 3)), p)"

Or by using the #includeFunc directive:

    #includeFunc probes(points=((1 2 3)), p)

In both cases, mandatory parameters that must be either edited or
provided as arguments are denoted in the configuration files with
angle-brackets, e.g.:

    points  (<points>);

Many of the packaged function objects have been split up to make them
more specific to a particular use-case. For example, the "surfaces"
function has been split up into separate functions for each surface
type; "cutPlaneSurface", "isoSurface", and "patchSurface". This
splitting means that the packaged functions now only contain one set of
relevant parameters so, unlike previously, they now work effectively
with their parameters passed as arguments. To ensure correct usage, all
case-dependent parameters are considered mandatory.

For example, the "streamlines" packaged function object has been split
into specific versions; "streamlinesSphere", "streamlinesLine",
"streamlinesPatch" and "streamlinesPoints". The name ending denotes the
seeding method. So, the following command creates ten streamlines with
starting points randomly seeded within a sphere with a specified centre
and radius:

    postProcess -func "streamlinesSphere(nPoints=10, centre=(0 0 0), radius=1)"

The equivalent #includeFunc approach would be:

    #includeFunc streamlinesSphere(nPoints=10, centre=(0 0 0), radius=1)

When passing parameters as arguments, error messages report accurately
which mandatory parameters are missing and provide instructions to
correct the format of the input. For example, if "postProcess -func
graphUniform" is called, then the code prints the following error message:

    --> FOAM FATAL IO ERROR:

    Essential value for keyword 'start' not set
    Essential value for keyword 'end' not set
    Essential value for keyword 'nPoints' not set
    Essential value for keyword 'fields' not set

    In function entry:
        graphUniform

    In command:
        postProcess -func graphUniform

    The function entry should be:
        graphUniform(start = <point>, end = <point>, nPoints = <number>, fields = (<fieldNames>))

    file: controlDict/functions/graphUniform from line 15 to line 25.

As always, a full list of all packaged function objects can be obtained
by running "postProcess -list", and a description of each function can
be obtained by calling "foamInfo <functionName>". An example case has
been added at "test/postProcessing/channel" which executes almost all
packaged function objects using both postProcess and #includeFunc. This
serves both as an example of syntax and as a unit test for maintenance.
This commit is contained in:
Will Bainbridge
2021-07-08 13:50:42 +01:00
parent 0b68176c60
commit 5d0d9a4fa3
128 changed files with 2472 additions and 488 deletions

View File

@ -171,47 +171,63 @@ Foam::fileName Foam::functionObjectList::findDict
}
void Foam::functionObjectList::checkUnsetEntries
(
const string& funcCall,
const dictionary& funcArgsDict,
const dictionary& funcDict,
const string& context
)
Foam::List<Foam::Tuple2<Foam::word, Foam::string>>
Foam::functionObjectList::unsetEntries(const dictionary& funcDict)
{
const wordRe unset("<.*>");
unset.compile();
const wordRe unsetPattern("<.*>");
unsetPattern.compile();
forAllConstIter(IDLList<entry>, funcArgsDict, iter)
List<Tuple2<word, string>> unsetArgs;
forAllConstIter(IDLList<entry>, funcDict, iter)
{
if (iter().isStream())
{
ITstream& tokens = iter().stream();
ITstream& its = iter().stream();
OStringStream oss;
bool isUnset = false;
forAll(tokens, i)
forAll(its, i)
{
if (tokens[i].isWord())
oss << its[i];
if (its[i].isWord() && unsetPattern.match(its[i].wordToken()))
{
if (unset.match(tokens[i].wordToken()))
{
FatalIOErrorInFunction(funcDict)
<< "Essential value for keyword '"
<< iter().keyword()
<< "' not set in function entry" << nl
<< " " << funcCall.c_str() << nl
<< " in " << context.c_str() << nl
<< " Placeholder value is "
<< tokens[i].wordToken()
<< exit(FatalIOError);
}
isUnset = true;
}
}
if (isUnset)
{
unsetArgs.append
(
Tuple2<word, string>
(
iter().keyword(),
oss.str()
)
);
}
}
else
{
checkUnsetEntries(funcCall, iter().dict(), funcDict, context);
List<Tuple2<word, string>> subUnsetArgs =
unsetEntries(iter().dict());
forAll(subUnsetArgs, i)
{
unsetArgs.append
(
Tuple2<word, string>
(
iter().keyword() + '/' + subUnsetArgs[i].first(),
subUnsetArgs[i].second()
)
);
}
}
}
return unsetArgs;
}
@ -219,7 +235,7 @@ bool Foam::functionObjectList::readFunctionObject
(
const string& funcArgs,
dictionary& functionsDict,
const string& context,
const Pair<string>& contextTypeAndValue,
HashSet<word>& requiredFields,
const word& region
)
@ -332,11 +348,81 @@ bool Foam::functionObjectList::readFunctionObject
funcDict.lookupOrDefault("funcName", string::validate<word>(funcArgs))
);
dictionary funcArgsDict;
funcArgsDict.add(funcName, funcDict);
// Check for anything in the configuration that has not been set
List<Tuple2<word, string>> unsetArgs = unsetEntries(funcDict);
bool hasUnsetError = false;
forAll(unsetArgs, i)
{
if
(
unsetArgs[i].first() != "fields"
&& unsetArgs[i].first() != "objects"
)
{
hasUnsetError = true;
}
}
if (!hasUnsetError)
{
forAll(unsetArgs, i)
{
funcDict.set(unsetArgs[i].first(), wordList());
}
}
else
{
FatalIOErrorInFunction(funcDict0)
<< nl;
forAll(unsetArgs, i)
{
FatalIOErrorInFunction(funcDict0)
<< "Essential value for keyword '" << unsetArgs[i].first()
<< "' not set" << nl;
}
FatalIOErrorInFunction(funcDict0)
<< nl << "In function entry:" << nl
<< " " << funcArgs.c_str() << nl
<< nl << "In " << contextTypeAndValue.first().c_str() << ":" << nl
<< " " << contextTypeAndValue.second().c_str() << nl;
word funcType;
wordReList args;
List<Tuple2<word, string>> namedArgs;
dictArgList(funcArgs, funcType, args, namedArgs);
string argList;
forAll(args, i)
{
args[i].strip(" \n");
argList += (argList.size() ? ", " : "") + args[i];
}
forAll(namedArgs, i)
{
namedArgs[i].second().strip(" \n");
argList +=
(argList.size() ? ", " : "")
+ namedArgs[i].first() + " = " + namedArgs[i].second();
}
forAll(unsetArgs, i)
{
unsetArgs[i].second().strip(" \n");
argList +=
(argList.size() ? ", " : "")
+ unsetArgs[i].first() + " = " + unsetArgs[i].second();
}
FatalIOErrorInFunction(funcDict0)
<< nl << "The function entry should be:" << nl
<< " " << funcType << '(' << argList.c_str() << ')'
<< exit(FatalIOError);
}
// Re-parse the funcDict to execute the functionEntries
// now that the function argument entries have been added
dictionary funcArgsDict;
funcArgsDict.add(funcName, funcDict);
{
OStringStream os;
funcArgsDict.write(os);
@ -348,9 +434,6 @@ bool Foam::functionObjectList::readFunctionObject
);
}
// Check for anything in the configuration that has not been set
checkUnsetEntries(funcArgs, funcArgsDict, funcDict0, context);
// Lookup the field, fields and objects entries from the now expanded
// funcDict and insert into the requiredFields
dictionary& expandedFuncDict = funcArgsDict.subDict(funcName);
@ -479,7 +562,7 @@ Foam::autoPtr<Foam::functionObjectList> Foam::functionObjectList::New
(
args["func"],
functionsDict,
"command line " + args.commandLine(),
{"command", args.commandLine()},
requiredFields,
region
);
@ -495,7 +578,7 @@ Foam::autoPtr<Foam::functionObjectList> Foam::functionObjectList::New
(
funcs[i],
functionsDict,
"command line " + args.commandLine(),
{"command", args.commandLine()},
requiredFields,
region
);