ENH: output filtering of vtkCloud by user selection (#1056)

- can filter by stride or field information.
  For example,

      selection
      {
          stride
          {
              // every 10th parcelId
              action  add;
              source  stride;
              stride  10;
          }
          Umin
          {
              // Remove slow parcels
              action  subtract;
              source  field;
              field   U;
              accept  (less 1e-3);
          }
          diam
          {
              // Only particular diameter ranges
              action  subset;
              source  field;
              field   d;
              accept  (greater 1e-3) and (less 1e-3);
          }
      }
This commit is contained in:
Mark Olesen
2018-11-13 22:57:49 +01:00
parent 0d29257a6d
commit 0f48b89185
9 changed files with 832 additions and 24 deletions

View File

@ -4,5 +4,6 @@ icoUncoupledKinematicCloud/icoUncoupledKinematicCloud.C
dsmcFields/dsmcFields.C
vtkCloud/vtkCloud.C
vtkCloud/parcelSelectionDetail.C
LIB = $(FOAM_LIBBIN)/liblagrangianFunctionObjects

View File

@ -0,0 +1,416 @@
/*---------------------------------------------------------------------------*\
========= |
\\ / 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::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:
{
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;
}
// ************************************************************************* //

View File

@ -0,0 +1,221 @@
/*---------------------------------------------------------------------------*\
========= |
\\ / 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/>.
Class
Foam::Detail::parcelSelection
Description
Selection of parcels based on their objectRegistry entries.
Normally accessed via a dictionary entry.
Example sub-dictionary entry
\verbatim
selection
{
stride
{
// every 10th parcelId
action add;
source stride;
stride 10;
}
injector
{
// Only output from injectorID == 1
action subset;
source field;
field typeId;
accept (equal 1);
}
Umin
{
// Remove slow parcels
action subtract;
source field;
field U;
accept (less 1e-3);
}
diam
{
// Only particular diameter ranges
action subset;
source field;
field d;
accept (greater 1e-3) and (less 1e-3);
}
}
\endverbatim
\heading Entry type
\table
Property | Description | Required | Default
action | all/clear/invert add/subtract/subset/ignore | yes |
source | field/stride | mostly |
\endtable
\heading Stride source
\table
Property | Description | Required | Default
stride | The stride for the parcel id | yes |
\endtable
\heading Field source
\table
Property | Description | Required | Default
field | The label/scalar/vector field name | yes |
accept | Acceptance or test criterion | yes |
\endtable
The \c accept criterion has two forms:
-# single expression
- (expr)
-# composite expression
- (expr) or (expr)
- (expr) and (expr)
The expressions are a (op scalar) pair that form a unary scalar
predicate. The \a op is one of the following:
- lt, less
- le, lessEq
- gt, greater
- ge, greaterEq
- eq, equal
- neq, notEqual
For example,
\verbatim
accept (less 10);
accept (less 10) or (greater 100);
accept (ge 10) and (le 20);
\endverbatim
See also
Foam::predicates::scalars
Foam::functionObjects::vtkCloud
SourceFiles
parcelSelectionDetail.C
\*---------------------------------------------------------------------------*/
#ifndef parcelSelectionDetail_H
#define parcelSelectionDetail_H
#include "bitSet.H"
#include "Enum.H"
#include "objectRegistry.H"
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
namespace Foam
{
namespace Detail
{
/*---------------------------------------------------------------------------*\
Class Detail::parcelSelection Declaration
\*---------------------------------------------------------------------------*/
class parcelSelection
{
public:
//- Enumeration defining the valid selection actions
enum actionType
{
ALL, //!< "all" - select all parcels
CLEAR, //!< "clear" - clear the selection
INVERT, //!< "invert" - invert the selection
ADD, //!< "add" - parcel selection
SUBTRACT, //!< "subtract" - remove parcel selection
SUBSET, //!< "subset" - subset parcel selection
IGNORE, //!< "ignore" - dummy no-op
};
//- Names for the actionType
static const Enum<actionType> actionNames;
//- Enumeration defining the valid sources
enum sourceType
{
FIELD, //!< "field" - select based on field value
STRIDE //!< "stride" - select based on stride (parcel id)
};
//- Names for the sourceType
static const Enum<sourceType> sourceNames;
//- Enumeration defining and/or logic
enum logicType { AND, OR };
//- Names for the logicType
static const Enum<logicType> logicNames;
protected:
// Protected data
//- The filtered parcel addressing. Eg, for the current cloud.
dictionary parcelSelect_;
//- The filtered parcel addressing. Eg, for the current cloud.
bitSet parcelAddr_;
// Protected Member Functions
//- Calculate parcel selection filter.
// \return True if the filter is applicable
bool calculateFilter
(
const objectRegistry& obrTmp,
const bool log = true
);
public:
// Constructors
//- Construct null
parcelSelection();
//- Destructor
virtual ~parcelSelection() = default;
};
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
} // End namespace Detail
} // End namespace Foam
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
#endif
// ************************************************************************* //

View File

@ -132,9 +132,23 @@ bool Foam::functionObjects::vtkCloud::writeCloud
return false;
}
applyFilter_ = calculateFilter(obrTmp, log);
reduce(applyFilter_, orOp<bool>());
// Number of parcels (locally)
label nParcels = (applyFilter_ ? parcelAddr_.count() : pointsPtr->size());
// Total number of parcels on all processes
const label nTotParcels =
returnReduce(pointsPtr->size(), sumOp<label>());
const label nTotParcels = returnReduce(nParcels, sumOp<label>());
if (applyFilter_ && log)
{
// Report filtered/unfiltered count
Log << "After filtering using " << nTotParcels << '/'
<< (returnReduce(pointsPtr->size(), sumOp<label>()))
<< " parcels" << nl;
}
if (pruneEmpty_ && !nTotParcels)
{
@ -216,7 +230,14 @@ bool Foam::functionObjects::vtkCloud::writeCloud
}
if (applyFilter_)
{
vtk::writeListParallel(format.ref(), *pointsPtr, parcelAddr_);
}
else
{
vtk::writeListParallel(format.ref(), *pointsPtr);
}
if (Pstream::master())
@ -333,6 +354,7 @@ Foam::functionObjects::vtkCloud::vtkCloud
printf_(),
useVerts_(false),
pruneEmpty_(false),
applyFilter_(false),
selectClouds_(),
selectFields_(),
directory_(),
@ -404,7 +426,6 @@ bool Foam::functionObjects::vtkCloud::read(const dictionary& dict)
useVerts_ = dict.lookupOrDefault<bool>("cellData", false);
pruneEmpty_ = dict.lookupOrDefault<bool>("prune", false);
selectClouds_.clear();
dict.readIfPresent("clouds", selectClouds_);
@ -417,7 +438,10 @@ bool Foam::functionObjects::vtkCloud::read(const dictionary& dict)
selectFields_.clear();
dict.readIfPresent("fields", selectFields_);
selectFields_.uniq();
// Actions to define selection
parcelSelect_ = dict.subOrEmptyDict("selection");
// Output directory

View File

@ -44,23 +44,56 @@ Description
cloud myCloud;
fields (T U rho);
width 4; // file-padding
selection
{
stride
{
// every 10th parcelId
action add;
source stride;
stride 10;
}
Umin
{
// Remove slow parcels
action subtract;
source field;
field U;
accept (less 1e-3);
}
diam
{
// Only particular diameter ranges
action subset;
source field;
field d;
accept (greater 1e-3) and (less 1e-3);
}
}
}
\endverbatim
Usage
\heading Basic Usage
\table
Property | Description | Required | Default
type | Type name: vtkCloud | yes |
writeControl | Output control | recommended | timeStep
cloud | | no | defaultCloud
clouds | wordRe list of clouds | no |
fields | wordRe list of fields | no |
prune | Suppress writing of empty clouds | no | false
cellData | Emit cellData instead of pointData | no | false
directory | The output directory name | no | postProcessing/NAME
width | Padding width for file name | no | 8
clouds | List of clouds (name or regex) | no |
cloud | Cloud name | no | defaultCloud
fields | List of fields (name or regex) | no |
selection | Parcel selection control | no | empty-dict
\endtable
\heading Output Options
\table
Property | Description | Required | Default
format | Format as ascii or binary | no | binary
precision | Write precision in ascii | no | same as IOstream
directory | The output directory name | no | postProcessing/NAME
width | Padding width for file name | no | 8
cellData | Emit cellData instead of pointData | no | false
prune | Suppress writing of empty clouds | no | false
writeControl | Output control | recommended | timeStep
\endtable
The output filename and fields are added to the functionObjectProperties
@ -77,7 +110,17 @@ Usage
}
\endverbatim
Note
The selection dictionary can be used for finer control of the parcel
output. It contains a set of (add,subtract,subset,clear,invert)
selection actions and sources.
Omitting the selection dictionary is the same as specifying the
conversion of all parcels (in the selected clouds).
More syntax details are to be found in the corresponding
Foam::Detail::parcelSelection class.
See also
Foam::Detail::parcelSelection
Foam::functionObjects::ensightWrite
Foam::functionObjects::vtkWrite
Foam::functionObjects::fvMeshFunctionObject
@ -93,6 +136,7 @@ SourceFiles
#define functionObjects_vtkCloud_H
#include "fvMeshFunctionObject.H"
#include "parcelSelectionDetail.H"
#include "foamVtkOutputOptions.H"
#include "foamVtkSeriesWriter.H"
#include "wordRes.H"
@ -111,7 +155,8 @@ namespace functionObjects
class vtkCloud
:
public fvMeshFunctionObject
public fvMeshFunctionObject,
public Foam::Detail::parcelSelection
{
// Private data
@ -127,6 +172,9 @@ class vtkCloud
//- Suppress writing of empty clouds
bool pruneEmpty_;
//- Apply output filter (for the current cloud)
bool applyFilter_;
//- Requested names of clouds to process
wordRes selectClouds_;

View File

@ -88,7 +88,14 @@ Foam::wordList Foam::functionObjects::vtkCloud::writeFields
}
}
if (applyFilter_)
{
vtk::writeListParallel(format.ref(), values, parcelAddr_);
}
else
{
vtk::writeListParallel(format.ref(), values);
}
if (Pstream::master())
{

View File

@ -54,6 +54,7 @@ maxDeltaT 1;
functions
{
#include "vtkCloud"
#include "vtkWrite"
}
// ************************************************************************* //

View File

@ -6,6 +6,9 @@ cloudWrite
libs ("liblagrangianFunctionObjects.so");
log true;
// Nothing happens before this anyhow
timeStart 0.5;
writeControl writeTime;
// cloud reactingCloud1;
@ -15,16 +18,68 @@ cloudWrite
fields ( U T d "Y.*" );
//- Output format (ascii | binary) - default = binary
// format binary;
// format ascii;
// writePrecision 12;
//- Suppress writing of empty clouds - default = false
// prune true;
// precision 12;
//- Output directory name - default = "VTK"
// Suppress writing of empty clouds - default = false
prune true;
//- Output directory name - Default postProcessing
// directory "VTK";
selection
{
all
{
action all;
}
none
{
action clear;
}
// Reduced number of output parcels
stride
{
action add;
source stride;
stride 4;
}
T
{
action subset;
source field;
field T;
accept (greater 280) and (less 300);
}
YH2O
{
action subset;
source field;
field YH2O(l);
accept (greater 0.5);
}
diameter
{
action subset;
source field;
field d;
accept (greater 1e-10);
}
Umin
{
action subtract;
source field;
field U;
accept (less 0.1);
}
}
}

View File

@ -0,0 +1,35 @@
// -*- C++ -*-
// Minimal example of using the vtkWrite function object.
vtkWrite
{
type vtkWrite;
libs ("libutilityFunctionObjects.so");
log true;
// Nothing happens before this anyhow
timeStart 0.4;
writeControl writeTime;
boundary false;
interpolate true;
fields (U);
// format ascii;
// Region of interest
selection
{
inletRegion
{
action add;
source zone;
zones (leftFluid);
}
}
// Write cell ids as field - Default=true
writeIds false;
}
// ************************************************************************* //