Files
openfoam/src/functionObjects/lagrangian/common/parcelSelectionDetail.C
Mark Olesen ff18ab58c2 ENH: add 'use' action for parcel selection
- shortcut for "clear" + "add".

  At the end of the operation, only parcels matching that particular
  selection source will be used.
2018-11-24 13:34:02 +01:00

425 lines
12 KiB
C

/*---------------------------------------------------------------------------*\
========= |
\\ / F ield | OpenFOAM: The Open Source CFD Toolbox
\\ / O peration |
\\ / A nd | Copyright (C) 2018 OpenCFD Ltd.
\\/ M anipulation |
-------------------------------------------------------------------------------
License
This file is part of OpenFOAM.
OpenFOAM is free software: you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
OpenFOAM is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
for more details.
You should have received a copy of the GNU General Public License
along with OpenFOAM. If not, see <http://www.gnu.org/licenses/>.
\*---------------------------------------------------------------------------*/
#include "parcelSelectionDetail.H"
#include "scalarPredicates.H"
#include "labelField.H"
#include "scalarField.H"
#include "pointField.H"
#include "ListListOps.H"
// * * * * * * * * * * * * * * Static Data Members * * * * * * * * * * * * * //
const Foam::Enum
<
Foam::Detail::parcelSelection::actionType
>
Foam::Detail::parcelSelection::actionNames
({
{ actionType::ALL, "all" },
{ actionType::CLEAR, "clear" },
{ actionType::INVERT, "invert" },
{ actionType::USE, "use" },
{ actionType::ADD, "add" },
{ actionType::SUBTRACT, "subtract" },
{ actionType::SUBSET, "subset" },
{ actionType::IGNORE, "ignore" },
});
const Foam::Enum
<
Foam::Detail::parcelSelection::sourceType
>
Foam::Detail::parcelSelection::sourceNames
({
{ sourceType::FIELD, "field" },
{ sourceType::STRIDE, "stride" },
});
const Foam::Enum
<
Foam::Detail::parcelSelection::logicType
> Foam::Detail::parcelSelection::logicNames
({
{ logicType::AND, "and" },
{ logicType::OR, "or" },
});
// * * * * * * * * * * * * * * * Local Functions * * * * * * * * * * * * * * //
namespace Foam
{
template<class Type, class Predicate, class AccessOp>
static void apply
(
bitSet& selection,
const Detail::parcelSelection::actionType action,
const Predicate& accept,
const UList<Type>& list,
const AccessOp& aop
)
{
using actionType = Detail::parcelSelection::actionType;
const label len = selection.size();
switch (action)
{
case actionType::ADD:
case actionType::USE:
{
if (actionType::USE == action)
{
// USE = CLEAR + ADD (ie, only use this selection)
selection = false;
}
for (label parceli = 0; parceli < len; ++parceli)
{
if (accept(aop(list[parceli])))
{
selection.set(parceli);
}
}
}
break;
case actionType::SUBTRACT:
{
for (label parceli = 0; parceli < len; ++parceli)
{
if (accept(aop(list[parceli])))
{
selection.unset(parceli);
}
}
}
break;
case actionType::SUBSET:
{
for (const label parceli : selection)
{
if (!accept(aop(list[parceli])))
{
selection.unset(parceli);
}
}
}
break;
default:
break;
}
}
} // End namespace Foam
// * * * * * * * * * * * * * * * * Constructors * * * * * * * * * * * * * * //
Foam::Detail::parcelSelection::parcelSelection()
:
parcelSelect_(),
parcelAddr_()
{}
// * * * * * * * * * * * * * Private Member Functions * * * * * * * * * * * //
bool Foam::Detail::parcelSelection::calculateFilter
(
const objectRegistry& obrTmp,
const bool log
)
{
if (parcelSelect_.empty())
{
parcelAddr_.clear();
return false;
}
// Start with all parcels unselected
// Number of parcels (locally)
const auto* pointsPtr = obrTmp.findObject<vectorField>("position");
label nParcels = pointsPtr->size();
parcelAddr_.reset();
parcelAddr_.resize(nParcels);
reduce(nParcels, sumOp<label>());
Log << "Applying parcel filtering to " << nParcels << " parcels" << nl;
if (!nParcels)
{
parcelAddr_.clear();
return false;
}
// The unary function type(s) for testing a scalar.
// Allocate 3 slots.
// 0 is the test
// 1,2 are for storage of composite tests (eg, and/or logic)
predicates::scalars tests(3);
for (const entry& dEntry : parcelSelect_)
{
if (!dEntry.isDict())
{
WarningInFunction
<< "Ignoring non-dictionary entry "
<< dEntry << endl;
continue;
}
const dictionary& dict = dEntry.dict();
// A very limited number of sources (stride, field)
// and actions (all add subtract subset) so handle manually
auto action = actionNames.get("action", dict);
// These ones we do directly
switch (action)
{
case actionType::ALL:
Log << "- select all" << nl;
parcelAddr_ = true;
continue;
break;
case actionType::CLEAR:
Log << "- clear" << nl;
parcelAddr_ = false;
continue;
break;
case actionType::INVERT:
Log << "- invert" << nl;
parcelAddr_.flip();
continue;
break;
case actionType::IGNORE:
continue;
break;
default:
break;
}
// The others need a source
// Need a source
const auto source = sourceNames.get("source", dict);
switch (source)
{
case sourceType::STRIDE:
{
const label stride = dict.get<label>("stride");
const labelField& ids =
obrTmp.lookupObject<labelField>("origId");
Log << "- " << actionNames[action]
<< " stride " << stride << nl;
if (stride <= 0)
{
WarningInFunction
<< nl
<< "Ignoring bad value for stride=" << stride << nl
<< endl;
}
else if (stride == 1)
{
// More efficient handling of stride 1, but should
// not really be using stride == 1.
switch (action)
{
case actionType::ADD:
parcelAddr_ = true;
break;
case actionType::SUBTRACT:
parcelAddr_ = false;
break;
default:
break;
}
}
else
{
// Using stride > 1
apply
(
parcelAddr_,
action,
[=](const label id) -> bool { return !(id % stride); },
ids,
accessOp<label>() // pass-through
);
}
}
break;
case sourceType::FIELD:
{
const word fieldName(dict.get<word>("field"));
const auto* labelFld =
obrTmp.findObject<labelField>(fieldName);
const auto* scalarFld =
obrTmp.findObject<scalarField>(fieldName);
const auto* vectorFld =
obrTmp.findObject<vectorField>(fieldName);
Log << "- " << actionNames[action] << " field " << fieldName;
if (!labelFld && !scalarFld && !vectorFld)
{
WarningInFunction
<< nl
<< "No scalar/vector parcel field: " << fieldName
<< " ignoring selection" << nl
<< endl;
continue;
}
const entry& e = dict.lookupEntry("accept", keyType::LITERAL);
ITstream& is = e.stream();
if (4 == is.size())
{
// 4 tokens:
// -> (op val)
Tuple2<word,scalar> expr1(is);
e.checkITstream(is);
tests.first() = predicates::scalars::operation(expr1);
Log << " : " << expr1;
}
else if (9 == is.size())
{
// 9 tokens:
// -> (op val) and (op val)
// -> (op val) or (op val)
Tuple2<word,scalar> expr1(is);
word logicName(is);
Tuple2<word,scalar> expr2(is);
e.checkITstream(is);
logicType logic = logicNames[logicName];
tests[1] = predicates::scalars::operation(expr1);
tests[2] = predicates::scalars::operation(expr2);
switch (logic)
{
case logicType::AND:
tests.first() =
predicates::scalars::andOp(tests[1], tests[2]);
break;
case logicType::OR:
tests.first() =
predicates::scalars::orOp(tests[1], tests[2]);
break;
}
Log << " : " << expr1 << ' ' << logicName << ' ' << expr2;
}
else
{
action = actionType::IGNORE;
// Use the following to always provoke an error.
e.checkITstream(is);
}
if (labelFld)
{
apply
(
parcelAddr_,
action,
tests.first(),
*labelFld,
accessOp<label>() // pass-through
);
}
else if (scalarFld)
{
apply
(
parcelAddr_,
action,
tests.first(),
*scalarFld,
accessOp<scalar>() // pass-through
);
}
else if (vectorFld)
{
apply
(
parcelAddr_,
action,
tests.first(),
*vectorFld,
[](const vector& val) -> scalar
{
return Foam::mag(val);
}
);
}
Log << endl;
}
break;
default:
break;
}
}
return true;
}
// ************************************************************************* //