Commit 04daa405 authored by Mark Olesen's avatar Mark Olesen
Browse files

ENH: unified catalyst function object (closes #3)

- previously had separate function objects for each category of source
  (finite volume, finite area, lagrangian). This lead to isolation of
  the individual input sources, which highly restricted the flexibility.

- now have a single catalyst function object with an arbitrary number
  of inputs (sources). The catalyst channel name is that of the
  corresponds dictionary name. For volume mesh sources, it is possible
  to address "mesh" and "patches" sub-channels.

  Eg,
      mesh = coprocessor.CreateProducer(datadescription, 'myregion/mesh')

  This restructuring also aims at integration of other OpenFOAM content
  (eg, surfMesh, sampled probes, sampled surfaces) as a future
  development.

- the output directory (by default "<case>/insitu") is passed onto
  catalyst as its working directory, using the upstream patches that
  now close issue #4
parent 524024a2
......@@ -63,6 +63,9 @@ set(LIBRARY_OUTPUT_PATH $ENV{FOAM_LIBBIN}
file(GLOB SOURCE_FILES
catalystCoprocess.C
catalystTools.C
catalystInput.C
catalystFunctionObject.C
cloud/catalystCloud.C
cloud/foamVtkCloudAdaptor.C
......
......@@ -24,12 +24,10 @@ License
\*---------------------------------------------------------------------------*/
#include "catalystFaMesh.H"
#include "catalystCoprocess.H"
#include "addToRunTimeSelectionTable.H"
#include "faMesh.H"
#include "fvMesh.H"
#include <vtkNew.h>
#include <vtkCPDataDescription.h>
#include <vtkMultiBlockDataSet.h>
#include <vtkInformation.h>
......@@ -38,76 +36,39 @@ License
namespace Foam
{
namespace functionObjects
namespace catalyst
{
defineTypeNameAndDebug(catalystFaMesh, 0);
addToRunTimeSelectionTable(functionObject, catalystFaMesh, dictionary);
defineTypeNameAndDebug(faMeshInput, 0);
addNamedToRunTimeSelectionTable
(
catalystInput,
faMeshInput,
dictionary,
area
);
}
}
// * * * * * * * * * * * * Protected Member Functions * * * * * * * * * * * //
bool Foam::functionObjects::catalystFaMesh::readBasics(const dictionary& dict)
void Foam::catalyst::faMeshInput::update()
{
int debugLevel = 0;
if (dict.readIfPresent("debug", debugLevel))
{
catalystCoprocess::debug = debugLevel;
}
// Backend requires a corresponding mesh
backends_.filterKeys
(
[this](const word& k){ return meshes_.found(k); }
);
if (Pstream::master())
forAllConstIters(meshes_, iter)
{
fileName dir;
if (dict.readIfPresent("mkdir", dir))
if (!backends_.found(iter.key()))
{
dir.expand();
dir.clean();
}
Foam::mkDir(dir);
}
dict.readIfPresent("outputDir", outputDir_);
outputDir_.expand();
outputDir_.clean();
if (Pstream::master())
{
Foam::mkDir(outputDir_);
}
dict.lookup("scripts") >> scripts_; // Python scripts
catalystCoprocess::expand(scripts_, dict); // Expand and check availability
if (adaptor_.valid())
{
// Run-time modification of pipeline
adaptor_().reset(outputDir_, scripts_);
}
return true;
}
void Foam::functionObjects::catalystFaMesh::updateState
(
polyMesh::readUpdateState state
)
{
// Trigger change of state
// Be really paranoid and verify if the mesh actually exists
const wordList regionNames(backends_.toc());
for (const word& regionName : regionNames)
{
if (meshes_.found(regionName) && time_.found(regionName))
{
backends_[regionName]->updateState(state);
}
else
{
backends_.erase(regionName);
meshes_.erase(regionName);
backends_.set
(
iter.key(),
new Foam::vtk::faMeshAdaptor(*(iter.object()))
);
}
}
}
......@@ -115,61 +76,52 @@ void Foam::functionObjects::catalystFaMesh::updateState
// * * * * * * * * * * * * * * * * Constructors * * * * * * * * * * * * * * //
Foam::functionObjects::catalystFaMesh::catalystFaMesh
Foam::catalyst::faMeshInput::faMeshInput
(
const word& name,
const Time& runTime,
const dictionary& dict
)
:
fvMeshFunctionObject(name, runTime, dict),
outputDir_("<case>/insitu"),
scripts_(),
adaptor_(),
catalystInput(name),
time_(runTime),
regionName_(),
selectAreas_(),
selectFields_(),
meshes_(),
backends_()
{
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);
}
// * * * * * * * * * * * * * * * * Destructor * * * * * * * * * * * * * * * //
Foam::functionObjects::catalystFaMesh::~catalystFaMesh()
{}
// * * * * * * * * * * * * * * * Member Functions * * * * * * * * * * * * * //
bool Foam::functionObjects::catalystFaMesh::read(const dictionary& dict)
bool Foam::catalyst::faMeshInput::read(const dictionary& dict)
{
fvMeshFunctionObject::read(dict);
catalystInput::read(dict);
readBasics(dict);
selectAreas_.clear();
selectFields_.clear();
backends_.clear();
// All possible meshes
meshes_ = mesh_.lookupClass<faMesh>();
regionName_ =
dict.lookupOrDefault<word>("region", polyMesh::defaultRegion);
const objectRegistry& obr =
time_.lookupObject<objectRegistry>(regionName_);
// All possible meshes for the given region
meshes_ = obr.lookupClass<faMesh>();
selectAreas_.clear();
dict.readIfPresent("areas", selectAreas_);
if (selectAreas_.empty())
{
word areaName;
if (!dict.readIfPresent("area", areaName))
{
wordList available = mesh_.sortedNames<faMesh>();
wordList available = obr.sortedNames<faMesh>();
if (available.size())
{
......@@ -185,59 +137,49 @@ bool Foam::functionObjects::catalystFaMesh::read(const dictionary& dict)
}
// Restrict to specified meshes
meshes_.filterKeys(wordRes(selectAreas_));
meshes_.filterKeys(selectAreas_);
dict.lookup("fields") >> selectFields_;
Info<< type() << " " << name() << ":" << nl
<<" areas " << flatOutput(selectAreas_) << nl
<<" meshes " << flatOutput(meshes_.sortedToc()) << nl
<<" fields " << flatOutput(selectFields_) << nl
<<" scripts " << scripts_ << nl;
// Ensure consistency - only retain backends with corresponding mesh region
backends_.retain(meshes_);
return true;
}
bool Foam::functionObjects::catalystFaMesh::execute()
void Foam::catalyst::faMeshInput::update(polyMesh::readUpdateState state)
{
const wordList regionNames(meshes_.sortedToc());
// Trigger change of state
if (regionNames.empty())
{
return false;
}
const objectRegistry& obr =
time_.lookupObject<objectRegistry>(regionName_);
// Be really paranoid and verify if the mesh actually exists
const wordList regionNames(backends_.toc());
// Enforce sanity for backends and adaptor
for (const word& regionName : regionNames)
{
bool updateAdaptor = false;
forAllConstIters(meshes_, iter)
if (meshes_.found(regionName) && obr.found(regionName))
{
if (!backends_.found(iter.key()))
{
backends_.insert
(
iter.key(),
new Foam::vtk::faMeshAdaptor(*(iter.object()))
);
updateAdaptor = true;
}
backends_[regionName]->updateState(state);
}
if (updateAdaptor && !adaptor_.valid())
else
{
adaptor_.reset(new catalystCoprocess());
adaptor_().reset(outputDir_, scripts_);
backends_.erase(regionName);
meshes_.erase(regionName);
}
}
}
// Gather all fields that we know how to convert
Foam::label Foam::catalyst::faMeshInput::addChannels(dataQuery& dataq)
{
update(); // Enforce sanity for backends and adaptor
if (backends_.empty())
{
return 0;
}
// Gather all fields that we know how to convert
wordHashSet allFields;
forAllConstIters(backends_, iter)
{
......@@ -245,96 +187,89 @@ bool Foam::functionObjects::catalystFaMesh::execute()
}
// Data description for co-processing
vtkNew<vtkCPDataDescription> descrip;
dataq.set(name(), allFields);
// Form data query for catalyst
catalystCoprocess::dataQuery dataq
(
vtk::faMeshAdaptor::channelNames.names(),
time_, // timeQuery
descrip.Get()
);
return 1;
}
// Query catalyst
const HashTable<wordHashSet> expecting(adaptor_().query(dataq, allFields));
if (catalystCoprocess::debug)
bool Foam::catalyst::faMeshInput::convert
(
dataQuery& dataq,
outputChannels& outputs
)
{
const wordList regionNames(backends_.sortedToc());
if (regionNames.empty())
{
if (expecting.empty())
{
Info<< type() << ": expecting no data" << nl;
}
else
{
Info<< type() << ": expecting data " << expecting << nl;
}
return false; // skip - not available
}
if (expecting.empty())
// Single channel only
label nChannels = 0;
if (dataq.found(name()))
{
++nChannels;
}
if (!nChannels)
{
return true;
return false; // skip - not requested
}
auto output = vtkSmartPointer<vtkMultiBlockDataSet>::New();
// TODO: currently don't rely on the results from expecting much at all
// Each region in a separate block
unsigned int regionNo = 0;
unsigned int blockNo = 0;
for (const word& regionName : regionNames)
{
auto pieces = backends_[regionName]->output(selectFields_);
output->SetBlock(regionNo, pieces);
output->GetMetaData(regionNo)->Set
(
vtkCompositeDataSet::NAME(),
regionName
);
++regionNo;
}
auto dataset =
backends_[regionName]->output(selectFields_);
if (regionNo)
{
Log << type() << ": send data" << nl;
adaptor_().process(dataq, output);
}
{
const fileName channel = name();
return true;
}
if (dataq.found(channel))
{
// Get existing or new
vtkSmartPointer<vtkMultiBlockDataSet> block =
outputs.lookup
(
channel,
vtkSmartPointer<vtkMultiBlockDataSet>::New()
);
block->SetBlock(blockNo, dataset);
bool Foam::functionObjects::catalystFaMesh::write()
{
return true;
}
block->GetMetaData(blockNo)->Set
(
vtkCompositeDataSet::NAME(),
regionName
);
outputs.set(channel, block); // overwrites existing
}
}
bool Foam::functionObjects::catalystFaMesh::end()
{
// Only here for extra feedback
if (log && adaptor_.valid())
{
Info<< type() << ": Disconnecting ParaView Catalyst..." << nl;
++blockNo;
}
adaptor_.clear();
return true;
}
void Foam::functionObjects::catalystFaMesh::updateMesh(const mapPolyMesh&)
Foam::Ostream& Foam::catalyst::faMeshInput::print(Ostream& os) const
{
updateState(polyMesh::TOPO_CHANGE);
}
os << name() << nl
<<" areas " << flatOutput(selectAreas_) << nl
<<" meshes " << flatOutput(meshes_.sortedToc()) << nl
<<" fields " << flatOutput(selectFields_) << nl;
void Foam::functionObjects::catalystFaMesh::movePoints(const polyMesh&)
{
updateState(polyMesh::POINTS_MOVED);
return os;
}
......
......@@ -22,51 +22,35 @@ License
along with OpenFOAM. If not, see <http://www.gnu.org/licenses/>.
Class
Foam::functionObjects::catalystFaMesh
Group
grpUtilitiesFunctionObjects
Foam::catalyst::faMeshInput
Description
A Paraview Catalyst adaptor for OpenFOAM faMesh regions.
A Paraview Catalyst source for OpenFOAM faMesh regions.
The output comprises a single "input" channel, which is a multi-block
dataset (one block per area mesh).
The source comprises a single internal "input" channel,
which is a multi-block dataset (one block per area mesh).
On output, the "input" sub-channel receives the name of the source.
Example of function object specification:
Example specification:
\verbatim
catalyst
myArea
{
type catalyst::area;
libs ("libcatalystFoam.so");
type area;
fields (U p);
scripts ( ... );
executeControl timeStep;
executeInterval 1;
}
\endverbatim
Usage
\table
Property | Description | Required | Default
type | catalyst::area | yes |
log | report extra information | no | false
mkdir | initial directory to create | no |
type | area | yes |
region | | no | region0
regions | wordRe list of regions | no |
fields | wordRe list of fields | yes |
scripts | Python pipeline scripts | yes |
\endtable
Note
The execution frequency can be defined by the functionObject and
by the Catalyst pipeline.
See also
Foam::functionObjects::functionObject
Foam::functionObjects::fvMeshFunctionObject
Foam::functionObjects::timeControl
Foam::catalystCoprocess
Foam::catalyst::catalystInput
Foam::vtk::faMeshAdaptor
SourceFiles
......@@ -75,47 +59,38 @@ SourceFiles
\*---------------------------------------------------------------------------*/
#ifndef functionObjects_catalystFaMesh_H
#define functionObjects_catalystFaMesh_H
#ifndef catalyst_faMeshInput_H
#define catalyst_faMeshInput_H
#include "fvMeshFunctionObject.H"
#include "foamVtkFaMeshAdaptor.H"
#include "wordRes.H"
#include "HashPtrTable.H"
#include "catalystInput.H"
#include "foamVtkFaMeshAdaptor.H"
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
namespace Foam
{
// Forward declarations
class catalystCoprocess;
namespace functionObjects
namespace catalyst
{
/*---------------------------------------------------------------------------*\
Class catalystFaMesh Declaration
Class catalyst::faMeshInput Declaration
\*---------------------------------------------------------------------------*/
class catalystFaMesh
class faMeshInput
:
public fvMeshFunctionObject
public catalystInput
{
protected:
// Protected data
//- The output directory
fileName outputDir_;
//- Python scripts for the catalyst pipeline
stringList scripts_;
//- The catalyst coprocess
autoPtr<catalystCoprocess> adaptor_;
//- Reference to the time database
const Time& time_;
//- The region name for the area meshes
word regionName_;
//- Requested names of areas to process
wordRes selectAreas_;
......@@ -132,29 +107,25 @@ protected:
// Protected Member Functions
//- Common boilerplate settings
bool readBasics(const dictionary& dict);
//- On movement
void updateState(polyMesh::readUpdateState state);
//- Update/synchronize internals with catalyst backends
void update();
//- No copy construct
catalystFaMesh(const catalystFaMesh&) = delete;
faMeshInput(const faMeshInput&) = delete;
//- No copy assignment
void operator=(const catalystFaMesh&) = delete;
void operator=(const faMeshInput&) = delete;
public:
//- Runtime type information
TypeName("catalyst::area");
ClassName("catalyst::faMesh");
// Constructors
//- Construct from Time and dictionary
catalystFaMesh
faMeshInput
(
const word& name,
const Time& runTime,
......@@ -163,7 +134,7 @@ public:
//- Destructor
virtual ~catalystFaMesh();
virtual ~faMeshInput() = default;
// Member Functions
......@@ -171,27 +142,23 @@ public:
//- Read the specification
virtual bool read(const dictionary& dict);
//- Execute catalyst pipelines
virtual bool execute();
//- Write - does nothing
virtual bool write();
//- On end - provide feedback about disconnecting from catatyst.
virtual bool end();
//- Update for changes of mesh or mesh point-motion
virtual void update(polyMesh::readUpdateState state);