ENH: additional ensightCloud function object (#3095)

This commit is contained in:
Mark Olesen
2024-01-26 14:13:35 +01:00
parent fe1d7e01d6
commit 52f5a6d039
4 changed files with 798 additions and 0 deletions

View File

@ -5,5 +5,6 @@ icoUncoupledKinematicCloud/icoUncoupledKinematicCloud.C
dsmcFields/dsmcFields.C
vtkCloud/vtkCloud.C
ensightCloud/ensightCloudWriteObject.cxx
LIB = $(FOAM_LIBBIN)/liblagrangianFunctionObjects

View File

@ -0,0 +1,266 @@
/*---------------------------------------------------------------------------*\
========= |
\\ / F ield | OpenFOAM: The Open Source CFD Toolbox
\\ / O peration |
\\ / A nd | www.openfoam.com
\\/ M anipulation |
-------------------------------------------------------------------------------
Copyright (C) 2024 OpenCFD Ltd.
-------------------------------------------------------------------------------
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::functionObjects::ensightCloudWriteObject
Group
grpLagrangianFunctionObjects
Description
This functionObject writes cloud(s) in ensight format
Example of function object specification:
\verbatim
cloudWrite1
{
type ensightCloud;
libs (lagrangianFunctionObjects);
writeControl writeTime;
writeInterval 1;
format ascii;
timeFormat scientific;
timePrecision 5;
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
\heading Basic Usage
\table
Property | Description | Required | Default
type | Type name: ensightCloud | yes |
clouds | List of clouds (name or regex) | no |
cloud | Cloud name | no |
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
width | Mask width for \c data/XXXX | no | 8
directory | The output directory name | no | postProcessing/NAME
overwrite | Remove existing directory | no | false
consecutive | Consecutive output numbering | no | false
width | Padding width for file name | no | 8
prune | Suppress writing of empty clouds | no | false
timeFormat | Time format (ensight case) | no | scientific
timePrecision | Time precision (ensight case) | no | 5
writeControl | Output control | recommended | timeStep
\endtable
The output filename and fields are added to the functionObjectProperties
information. For the previous example specification:
\verbatim
cloudWrite1
{
myCloud
{
file "<case>/simulation.case";
fields (T U rho);
}
}
\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::vtkCloud
Foam::functionObjects::ensightWrite
Foam::functionObjects::fvMeshFunctionObject
Foam::functionObjects::timeControl
SourceFiles
ensightCloudWriteObject.cxx
ensightCloudWriteObjectImpl.cxx
\*---------------------------------------------------------------------------*/
#ifndef functionObjects_ensightCloudWriteObject_H
#define functionObjects_ensightCloudWriteObject_H
#include "fvMeshFunctionObject.H"
#include "ensightCase.H"
#include "globalIndex.H"
#include "parcelSelectionDetail.H"
#include "wordRes.H"
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
namespace Foam
{
namespace functionObjects
{
/*---------------------------------------------------------------------------*\
Class ensightCloudWriteObject Declaration
\*---------------------------------------------------------------------------*/
class ensightCloudWriteObject
:
public fvMeshFunctionObject,
public Foam::Detail::parcelSelection
{
// Private Data
//- Ensight output options
ensightCase::options caseOpts_;
//- Output directory
fileName outputDir_;
//- Consecutive output numbering
bool consecutive_;
//- Suppress writing of empty clouds
bool pruneEmpty_;
//- Apply output filter (for the current cloud)
bool applyFilter_;
//- Sizing of selected parcels (including any filtering)
globalIndex procAddr_;
//- Requested names of clouds to process
wordRes selectClouds_;
//- Subset of cloud fields to process
wordRes selectFields_;
//- Ensight case handler
autoPtr<ensightCase> ensCase_;
// Private Member Functions
//- Ensight case handler
ensightCase& ensCase() { return *ensCase_; }
//- Write a cloud to disk (creates parent directory),
//- and record on the cloud OutputProperties.
// \param file is the output file name, with extension.
bool writeCloud(const word& cloudName);
//- Write fields of IOField<Type>
template<class Type>
wordList writeFields
(
const word& cloudName,
const objectRegistry& obrTmp
);
//- No copy construct
ensightCloudWriteObject(const ensightCloudWriteObject&) = delete;
//- No copy assignment
void operator=(const ensightCloudWriteObject&) = delete;
public:
//- Runtime type information
TypeName("ensightCloud");
// Constructors
//- Construct from Time and dictionary
ensightCloudWriteObject
(
const word& name,
const Time& runTime,
const dictionary& dict
);
//- Destructor
virtual ~ensightCloudWriteObject() = default;
// Member Functions
//- Read the ensightCloud specification
virtual bool read(const dictionary& dict);
//- Execute, currently does nothing
virtual bool execute();
//- Write fields
virtual bool write();
};
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
} // End namespace functionObjects
} // End namespace Foam
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
#endif
// ************************************************************************* //

View File

@ -0,0 +1,425 @@
/*---------------------------------------------------------------------------*\
========= |
\\ / F ield | OpenFOAM: The Open Source CFD Toolbox
\\ / O peration |
\\ / A nd | www.openfoam.com
\\/ M anipulation |
-------------------------------------------------------------------------------
Copyright (C) 2024 OpenCFD Ltd.
-------------------------------------------------------------------------------
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 "ensightCloudWriteObject.H"
#include "ensightCells.H"
#include "Cloud.H"
#include "dictionary.H"
#include "fvMesh.H"
#include "ensightOutputCloud.H"
#include "addToRunTimeSelectionTable.H"
#include "pointList.H"
#include "stringOps.H"
// * * * * * * * * * * * * * * Static Data Members * * * * * * * * * * * * * //
namespace Foam
{
namespace functionObjects
{
defineTypeNameAndDebug(ensightCloudWriteObject, 0);
addToRunTimeSelectionTable
(
functionObject,
ensightCloudWriteObject,
dictionary
);
}
}
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
// Implementation
#include "ensightCloudWriteObjectImpl.cxx"
// * * * * * * * * * * * * * Private Member Functions * * * * * * * * * * * //
bool Foam::functionObjects::ensightCloudWriteObject::writeCloud
(
const word& cloudName
)
{
applyFilter_ = false;
procAddr_.clear();
const auto* cloudPtr = mesh_.cfindObject<cloud>(cloudName);
if (!cloudPtr)
{
return false;
}
const auto& currCloud = *cloudPtr;
objectRegistry obrTmp
(
IOobject
(
"ensight::ensightCloud::" + cloudName,
mesh_.time().constant(),
mesh_,
IOobject::NO_READ,
IOobject::NO_WRITE,
IOobject::NO_REGISTER
)
);
currCloud.writeObjects(obrTmp);
const auto* pointsPtr = cloud::findIOPosition(obrTmp);
if (!pointsPtr)
{
// This should be impossible
return false;
}
applyFilter_ = calculateFilter(obrTmp, log);
Pstream::reduceOr(applyFilter_);
// Number of parcels (locally)
const label nParcels =
(
applyFilter_ ? parcelAddr_.count() : pointsPtr->size()
);
// Gather sizes (offsets irrelevant)
procAddr_.reset(globalIndex::gatherOnly{}, nParcels);
bool noCloud(!procAddr_.totalSize());
Pstream::broadcast(noCloud);
if (applyFilter_)
{
// Report filtered/unfiltered count
Log << "After filtering using "
<< procAddr_.totalSize() << '/'
<< (returnReduce(pointsPtr->size(), sumOp<label>()))
<< " parcels" << nl;
}
if (pruneEmpty_ && noCloud)
{
return false;
}
// Copy positions (for simplicity and for filtering).
// Store as floatVector, since that is what Ensight will write anyhow
DynamicList<floatVector> positions;
positions.reserve(UPstream::master() ? procAddr_.maxSize() : nParcels);
{
const auto& points = *pointsPtr;
positions.resize_nocopy(nParcels);
auto iter = positions.begin();
if (applyFilter_)
{
if (std::is_same<float, vector::cmptType>::value)
{
for (const label idx : parcelAddr_)
{
*iter = points[idx];
++iter;
}
}
else
{
for (const label idx : parcelAddr_)
{
const auto& pos = points[idx];
(*iter).x() = narrowFloat(pos.x());
(*iter).y() = narrowFloat(pos.y());
(*iter).z() = narrowFloat(pos.z());
++iter;
}
}
}
else
{
if (std::is_same<float, vector::cmptType>::value)
{
for (const auto& pos : points)
{
*iter = pos;
++iter;
}
}
else
{
for (const auto& pos : points)
{
(*iter).x() = narrowFloat(pos.x());
(*iter).y() = narrowFloat(pos.y());
(*iter).z() = narrowFloat(pos.z());
++iter;
}
}
}
}
// Write positions
{
autoPtr<ensightFile> os = ensCase().newCloud(cloudName);
ensightOutput::writeCloudPositions
(
os.ref(),
positions,
procAddr_
);
}
// Prevent any possible conversion of positions as a field
obrTmp.filterKeys
(
[](const word& k)
{
return k.starts_with("position") || k.starts_with("coordinate");
},
true // prune
);
// Write fields
DynamicList<word> written(obrTmp.size() + currCloud.objectRegistry::size());
written.push_back
(
writeFields<label>(cloudName, obrTmp)
);
written.push_back
(
writeFields<scalar>(cloudName, obrTmp)
);
written.push_back
(
writeFields<vector>(cloudName, obrTmp)
);
// Any cloudFunctions results
written.push_back
(
writeFields<scalar>(cloudName, currCloud)
);
// Record information into the state (all processors)
//
// foName
// {
// cloudName
// {
// file "<case>/postProcessing/name/casename.case";
// fields (U T rho);
// }
// }
const fileName& file = ensCase().path();
// Case-local file name with "<case>" to make relocatable
dictionary propsDict;
propsDict.add
(
"file",
time_.relativePath(file, true)
);
propsDict.add("fields", written);
setObjectProperty(name(), cloudName, propsDict);
return true;
}
// * * * * * * * * * * * * * * * * Constructors * * * * * * * * * * * * * * //
Foam::functionObjects::ensightCloudWriteObject::ensightCloudWriteObject
(
const word& name,
const Time& runTime,
const dictionary& dict
)
:
fvMeshFunctionObject(name, runTime, dict),
caseOpts_("format", dict, IOstreamOption::BINARY),
outputDir_(),
consecutive_(false),
pruneEmpty_(false),
applyFilter_(false),
procAddr_()
{
// May still want this?
// if (postProcess)
// {
// // Disable for post-process mode.
// // Emit as FatalError for the try/catch in the caller.
// FatalError
// << type() << " disabled in post-process mode"
// << exit(FatalError);
// }
read(dict);
}
// * * * * * * * * * * * * * * * Member Functions * * * * * * * * * * * * * //
bool Foam::functionObjects::ensightCloudWriteObject::read
(
const dictionary& dict
)
{
fvMeshFunctionObject::read(dict);
// Case/writer options
consecutive_ = dict.getOrDefault("consecutive", false);
caseOpts_.width(dict.getOrDefault<label>("width", 8));
caseOpts_.overwrite(dict.getOrDefault("overwrite", false));
caseOpts_.timeFormat("timeFormat", dict);
caseOpts_.timePrecision("timePrecision", dict);
pruneEmpty_ = dict.getOrDefault("prune", false);
selectClouds_.clear();
dict.readIfPresent("clouds", selectClouds_);
selectClouds_.uniq();
if (selectClouds_.empty())
{
word cloudName;
if (dict.readIfPresent("cloud", cloudName))
{
selectClouds_.push_back(std::move(cloudName));
}
}
selectFields_.clear();
dict.readIfPresent("fields", selectFields_);
selectFields_.uniq();
// Actions to define selection
parcelSelect_ = dict.subOrEmptyDict("selection");
// Output directory
outputDir_.clear();
dict.readIfPresent("directory", outputDir_);
if (outputDir_.size())
{
// User-defined output directory
outputDir_.expand();
if (!outputDir_.isAbsolute())
{
outputDir_ = time_.globalPath()/outputDir_;
}
}
else
{
// Standard postProcessing/ naming
outputDir_ = time_.globalPath()/functionObject::outputPrefix/name();
}
outputDir_.clean(); // Remove unneeded ".."
return true;
}
bool Foam::functionObjects::ensightCloudWriteObject::execute()
{
return true;
}
bool Foam::functionObjects::ensightCloudWriteObject::write()
{
const wordList cloudNames
(
selectClouds_.empty()
? mesh_.sortedNames<cloud>()
: mesh_.sortedNames<cloud>(selectClouds_)
);
if (cloudNames.empty())
{
return true; // skip - nothing available
}
if (!ensCase_)
{
ensCase_.reset
(
new ensightCase(outputDir_, time_.globalCaseName(), caseOpts_)
);
// Generate a (non-moving) dummy geometry
// - ParaView ensight-reader needs this, and usually ensight does too
autoPtr<ensightGeoFile> os = ensCase().newGeometry(false);
ensightCells::writeBox(os.ref(), mesh_.bounds());
}
if (consecutive_)
{
ensCase().nextTime(time_.value());
}
else
{
ensCase().setTime(time_.value(), time_.timeIndex());
}
Log << type() << ' ' << name() << " write" << nl;
// Each cloud separately
for (const word& cloudName : cloudNames)
{
// writeCloud() includes mkDir (on master)
if (writeCloud(cloudName))
{
Log << " cloud : " << endl;
}
}
ensCase().write(); // Flush case information
return true;
}
// ************************************************************************* //

View File

@ -0,0 +1,106 @@
/*---------------------------------------------------------------------------*\
========= |
\\ / F ield | OpenFOAM: The Open Source CFD Toolbox
\\ / O peration |
\\ / A nd | www.openfoam.com
\\/ M anipulation |
-------------------------------------------------------------------------------
Copyright (C) 2024 OpenCFD Ltd.
-------------------------------------------------------------------------------
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 "IOField.H"
#include "ensightOutputCloud.H"
// * * * * * * * * * * * * * Private Member Functions * * * * * * * * * * * //
template<class Type>
Foam::wordList Foam::functionObjects::ensightCloudWriteObject::writeFields
(
const word& cloudName,
const objectRegistry& obrTmp
)
{
static_assert
(
(
std::is_same<label, typename pTraits<Type>::cmptType>::value
|| std::is_floating_point<typename pTraits<Type>::cmptType>::value
),
"Label and Floating-point vector space only"
);
// Other integral types (eg, bool etc) would need cast/convert to label.
// Similarly for labelVector etc.
// Fields are not always on all processors (eg, multi-component parcels).
// Thus need to resolve names between all processors.
wordList fieldNames =
(
selectFields_.size()
? obrTmp.names<IOField<Type>>(selectFields_)
: obrTmp.names<IOField<Type>>()
);
Pstream::combineReduce(fieldNames, ListOps::uniqueEqOp<word>());
Foam::sort(fieldNames); // Consistent order
DynamicList<Type> scratch;
for (const word& fieldName : fieldNames)
{
const List<Type>* fldPtr = obrTmp.findObject<IOField<Type>>(fieldName);
const List<Type>& values = (fldPtr ? *fldPtr : List<Type>::null());
autoPtr<ensightFile> os =
ensCase().newCloudData<Type>(cloudName, fieldName);
if (applyFilter_)
{
scratch.resize_nocopy(parcelAddr_.count());
auto iter = scratch.begin();
for (const label idx : parcelAddr_)
{
*iter = values[idx];
++iter;
}
// TBD:
// recalculate globalIndex instead of relying on procAddr_ ?
ensightOutput::writeCloudField(os.ref(), scratch, procAddr_);
}
else
{
// TBD:
// recalculate globalIndex instead of relying on procAddr_ ?
ensightOutput::writeCloudField(os.ref(), values, procAddr_);
}
}
return fieldNames;
}
// ************************************************************************* //