Commit 4ade42e0 authored by Mark Olesen's avatar Mark Olesen
Browse files

New function object for using ParaView Catalyst with OpenFOAM

This integration represents a collective work
  - CINECA (Simone.Bna@cineca.it)
  - OpenCFD
  - with additional input from KitWare (Andy.Bauer@kitware.com)

NB: requires ParaView or ParaView Catalyst with MPI and Python
parent b7f5a665
## General Description
Library for [OpenFOAM] that provides a runtime-selectable
function object for embedding [ParaView Catalyst][Catalyst]
in-situ visualization into arbitrary OpenFOAM simulations.
Supports in-situ conversion of the following types:
- finite volume meshes and fields. Single or multi-region.
- finite area meshes and fields. Single region.
- lagrangian (clouds). Single or multiple clouds.
### Overset
For simulations with overset meshes, internal blanking is used to hide
the *holes* so that user visualization pipelines do not require any
thresholding workarounds. Note, however, that visualization artifices
may be present in multi-overlapping regions.
## Requirements
1. [OpenFOAM-v1806] or a recent [development version][OpenFOAM-git]
from [OpenFOAM.com][OpenFOAM].
2. [ParaView] or ParaView [Catalyst] 5.5 or newer,
compiled with python support (optionally with MPI support).
### Patching
It is **highly recommended** to patch the ParaView 5.5 sources to
include changes ([MR2433], [MR2436]) that will be part of the
ParaView 5.6 release.
These patches are necessary to ensure that the in-situ results are
placed in the correct output directory.
Without these patches, the results will always land in the current
working directory: not in the case-local `insitu` subdirectory
and ignoring the OpenFOAM `-case` parameter.
These [patches] will be automatically applied when the `makeParaView`
script from OpenFOAM ThirdParty is used.
## Building
Ensure that the OpenFOAM environment is active and that ParaView or Catalyst
can be found (Eg, the `ParaView_DIR` environment is properly set).
### To install in the normal OpenFOAM directories (using `wmake`)
Simply use the supplied `Allwmake` script:
````
./Allwmake
````
This will install the library under
`$WM_PROJECT_DIR/platforms/$WM_OPTIONS/lib`,
which corresponds to the `$FOAM_LIBBIN` location.
### To install in arbitrary locations (using `cmake`)
Without parameters, it installs to `/usr/local` as the default location.
This can be changed as required with the cmake `CMAKE_INSTALL_PREFIX`
parameter.
````
mkdir build; cd build
cmake -DCMAKE_INSTALL_PREFIX=/install/path ../src/catalyst
````
The installation using the `./Allwmake` script can be replicated with
either of these commands:
````
mkdir build; cd build
cmake -DCMAKE_INSTALL_PREFIX=$WM_PROJECT_DIR/platforms/$WM_OPTIONS ../src/catalyst
make install
````
or
````
mkdir build; cd build
cmake -DCMAKE_INSTALL_PREFIX=${FOAM_LIBBIN%/*} ../src/catalyst
make install
````
### Where to start
Look at the tutorials cases to get an idea of how different things
might be done. Know what types of inputs your particular Catalyst
function object is producing. The `writeAll.py` script included as
part of the normal OpenFOAM distribution provides a good starting point
for examing your output, but also for generating initial data sets for
your pipelines. Use the script generation capabilities within ParaView
to generate your pipelines. Save a backup of your pipelines as `pvsm`
state files so that you can go back and tweak your pipelines without
manually editing the python scripts and introducing possible errors.
Be aware that there may be some combinations of pipelines or
annotations that misbehave. If you encounter such a problem, try
backing out various pieces to isolate the cause. If it does appear to be
a reproducible error, test it on a tutorial example too.
## Authors
- Mark Olesen | <mark.olesen@esi-group.com> | (ESI-OpenCFD)
- Simone Bna | <simone.bna@cineca.it> | (CINECA)
## License
Licensed under GNU General Public License <http://www.gnu.org/licenses/>
with the same terms as OpenFOAM itself.
----
[OpenFOAM]: https://www.openfoam.com
[OpenFOAM-v1806]: https://www.openfoam.com/releases/openfoam-v1806/
[OpenFOAM-git]: https://develop.openfoam.com/Development/OpenFOAM-plus
[patches]: https://develop.openfoam.com/Development/ThirdParty-plus/raw/develop/etc/patches/paraview-5.5.2
[ParaView]: https://www.paraview.org/
[Catalyst]: https://www.paraview.org/in-situ/
[MR2433]: https://gitlab.kitware.com/paraview/paraview/merge_requests/2433
[MR2436]: https://gitlab.kitware.com/paraview/paraview/merge_requests/2436
Collection of scripts and setups.
- Some bits may need to migrate to the main OpenFOAM directories
from paraview.simple import *
from paraview import coprocessing
# The frequency to output everything
outputfrequency = 5
# ----------------------- CoProcessor definition -----------------------
def CreateCoProcessor():
def _CreatePipeline(coprocessor, datadescription):
class Pipeline:
for i in range(datadescription.GetNumberOfInputDescriptions()):
name = datadescription.GetInputDescriptionName(i)
adaptorinput = coprocessor.CreateProducer(datadescription, name)
grid = adaptorinput.GetClientSideObject().GetOutputDataObject(0)
extension = None
if grid.IsA('vtkImageData') or grid.IsA('vtkUniformGrid'):
writer = servermanager.writers.XMLPImageDataWriter(Input=adaptorinput)
extension = '.pvti'
elif grid.IsA('vtkRectilinearGrid'):
writer = servermanager.writers.XMLPRectilinearGridWriter(Input=adaptorinput)
extension = '.pvtr'
elif grid.IsA('vtkStructuredGrid'):
writer = servermanager.writers.XMLPStructuredGridWriter(Input=adaptorinput)
extension = '.pvts'
elif grid.IsA('vtkPolyData'):
writer = servermanager.writers.XMLPPolyDataWriter(Input=adaptorinput)
extension = '.pvtp'
elif grid.IsA('vtkUnstructuredGrid'):
writer = servermanager.writers.XMLPUnstructuredGridWriter(Input=adaptorinput)
extension = '.pvtu'
elif grid.IsA('vtkUniformGridAMR'):
writer = servermanager.writers.XMLHierarchicalBoxDataWriter(Input=adaptorinput)
extension = '.vthb'
elif grid.IsA('vtkMultiBlockDataSet'):
writer = servermanager.writers.XMLMultiBlockDataWriter(Input=adaptorinput)
extension = '.vtm'
else:
print("Don't know how to create a writer for a ", grid.GetClassName())
if extension:
coprocessor.RegisterWriter(writer, filename=name+'_%t'+extension, freq=outputfrequency)
return Pipeline()
class CoProcessor(coprocessing.CoProcessor):
def CreatePipeline(self, datadescription):
self.Pipeline = _CreatePipeline(self, datadescription)
return CoProcessor()
#--------------------------------------------------------------
# Global variables that will hold the pipeline for each timestep
# Creating the CoProcessor object, doesn't actually create the ParaView pipeline.
# It will be automatically setup when coprocessor.UpdateProducers() is called the
# first time.
coprocessor = CreateCoProcessor()
#--------------------------------------------------------------
# Enable Live-Visualizaton with ParaView
coprocessor.EnableLiveVisualization(False)
# ---------------------- Data Selection method ----------------------
def RequestDataDescription(datadescription):
"Callback to populate the request for current timestep"
global coprocessor
if datadescription.GetForceOutput() == True or datadescription.GetTimeStep() % outputfrequency == 0:
# We are just going to request all fields and meshes from the simulation
# code/adaptor.
for i in range(datadescription.GetNumberOfInputDescriptions()):
datadescription.GetInputDescription(i).AllFieldsOn()
datadescription.GetInputDescription(i).GenerateMeshOn()
return
# setup requests for all inputs based on the requirements of the
# pipeline.
coprocessor.LoadRequestedData(datadescription)
# ------------------------ Processing method ------------------------
def DoCoProcessing(datadescription):
"Callback to do co-processing for current timestep"
global coprocessor
# Update the coprocessor by providing it the newly generated simulation data.
# If the pipeline hasn't been setup yet, this will setup the pipeline.
coprocessor.UpdateProducers(datadescription)
# Write output data, if appropriate.
coprocessor.WriteData(datadescription);
# Write image capture (Last arg: rescale lookup table), if appropriate.
coprocessor.WriteImages(datadescription, rescale_lookuptable=False)
# Live Visualization, if enabled.
coprocessor.DoLiveVisualization(datadescription, "localhost", 22222)
#!/bin/sh
cd "${0%/*}" || exit # Run from this directory
. ${WM_PROJECT_DIR:?}/wmake/scripts/wmakeFunctions # The wmake functions
# -----------------------------------------------------------------------------
rm -f $FOAM_LIBBIN/libcatalystFoam* # Cleanup library
rm -f $FOAM_SITE_LIBBIN/libcatalystFoam* # ... extra safety
rm -f $FOAM_USER_LIBBIN/libcatalystFoam* # ... extra safety
# Cleanup generated files - remove entire top-level
removeObjectDir "$PWD"
#------------------------------------------------------------------------------
#!/bin/sh
cd "${0%/*}" || exit # Run from this directory
. ${WM_PROJECT_DIR:?}/wmake/scripts/cmakeFunctions # The cmake functions
#------------------------------------------------------------------------------
# CMake into objectsDir with external dependency
# - This function override can be removed with OpenFOAM-1806
cmakeVersioned()
{
local depend="$1"
local sourceDir="$2"
local objectsDir
# Where generated files are stored
objectsDir=$(findObjectDir "$sourceDir") || exit 1 # Fatal
# Version changed
sentinel=$(sameDependency "$depend" "$sourceDir") || \
rm -rf "$objectsDir" > /dev/null 2>&1
mkdir -p $objectsDir \
&& (cd $objectsDir && _cmake $sourceDir && make) \
&& echo "$depend" >| "${sentinel:-/dev/null}"
}
#------------------------------------------------------------------------------
echo "======================================================================"
echo "${PWD##*/} : $PWD"
echo
unset depend
if [ -d "$ParaView_DIR" ]
then
depend="ParaView_DIR=$ParaView_DIR"
fi
if [ "$targetType" = objects ]
then
depend=ignore
elif [ -n "$depend" ]
then
if command -v cmake >/dev/null
then
cmakeVersioned "$depend" "$PWD" || {
echo
echo " WARNING: incomplete build of ParaView Catalyst"
echo
}
else
echo "==> skip catalyst (needs cmake)"
fi
else
echo "WARNING: skip ParaView Catalyst (missing or incorrrect ParaView)"
fi
echo "======================================================================"
#------------------------------------------------------------------------------
include(${PARAVIEW_USE_FILE})
include_directories(
$ENV{WM_PROJECT_DIR}/src/OpenFOAM/lnInclude
$ENV{WM_PROJECT_DIR}/src/OSspecific/$ENV{WM_OSTYPE}/lnInclude
$ENV{WM_PROJECT_DIR}/src/finiteArea/lnInclude
$ENV{WM_PROJECT_DIR}/src/finiteVolume/lnInclude
$ENV{WM_PROJECT_DIR}/src/fileFormats/lnInclude
$ENV{WM_PROJECT_DIR}/src/conversion/lnInclude
$ENV{WM_PROJECT_DIR}/src/meshTools/lnInclude
$ENV{WM_PROJECT_DIR}/src/lagrangian/basic/lnInclude
${CMAKE_CURRENT_SOURCE_DIR}
${CMAKE_CURRENT_BINARY_DIR}
)
link_directories(
$ENV{FOAM_LIBBIN}
$ENV{FOAM_LIBBIN}/dummy
)
add_definitions(
-DWM_$ENV{WM_PRECISION_OPTION} -DNoRepository
-DWM_LABEL_SIZE=$ENV{WM_LABEL_SIZE}
)
set(CMAKE_BUILD_TYPE Release)
set(CMAKE_CXX_FLAGS_DEBUG
"-g -O0 -std=c++11 -Wall -Wextra -Wno-unused-parameter -Wnon-virtual-dtor -Wno-overloaded-virtual"
)
set(CMAKE_C_FLAGS_DEBUG "-g -O0 -std=c++11")
set(CMAKE_CXX_FLAGS_RELEASE
"-O3 -std=c++11 -Wall -Wextra -Wno-unused-parameter -Wnon-virtual-dtor -Wno-overloaded-virtual")
set(CMAKE_C_FLAGS_RELEASE "-O3 -std=c++11")
# Set output library destination to OpenFOAM library directory
set(LIBRARY_OUTPUT_PATH $ENV{FOAM_LIBBIN}
CACHE INTERNAL
""
)
file(GLOB SOURCE_FILES
catalystCoprocess.C
cloud/catalystCloud.C
cloud/foamVtkCloudAdaptor.C
areaMesh/catalystFaMesh.C
areaMesh/foamVtkFaMeshAdaptor.C
areaMesh/foamVtkFaMeshAdaptorGeom.C
areaMesh/foamVtkFaMeshAdaptorFields.C
volMesh/catalystFvMesh.C
volMesh/foamVtkFvMeshAdaptor.C
volMesh/foamVtkFvMeshAdaptorGeom.C
volMesh/foamVtkFvMeshAdaptorGeomVtu.C
volMesh/foamVtkFvMeshAdaptorFields.C
)
set(OPENFOAM_LIBRARIES
OpenFOAM
finiteArea
finiteVolume
fileFormats
conversion
Pstream
meshTools
lagrangian
)
add_library(
catalystFoam
SHARED
${SOURCE_FILES}
)
target_link_libraries(
catalystFoam
vtkPVPythonCatalyst vtkParallelMPI
${OPENFOAM_LIBRARIES}
)
#-----------------------------------------------------------------------------
cmake_minimum_required(VERSION 2.8)
project(catalyst)
# Set policy for CMP0002 needed for cmake > 3
cmake_policy(SET CMP0002 OLD)
if (EXISTS "$ENV{ParaView_DIR}")
message("Building with Paraview from $ENV{ParaView_DIR}")
find_package(ParaView REQUIRED COMPONENTS vtkPVPythonCatalyst)
if(NOT PARAVIEW_USE_MPI)
message(SEND_ERROR "ParaView must be built with MPI enabled")
endif()
else ()
message (FATAL_ERROR "ParaView_DIR not set")
endif ()
include(CMakeLists-Common.txt)
#-----------------------------------------------------------------------------
/*---------------------------------------------------------------------------*\
========= |
\\ / 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 "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>
// * * * * * * * * * * * * * * Static Data Members * * * * * * * * * * * * * //
namespace Foam
{
namespace functionObjects
{
defineTypeNameAndDebug(catalystFaMesh, 0);
addToRunTimeSelectionTable(functionObject, catalystFaMesh, dictionary);
}
}
// * * * * * * * * * * * * Protected Member Functions * * * * * * * * * * * //
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);
}
}
}
// * * * * * * * * * * * * * * * * Constructors * * * * * * * * * * * * * * //
Foam::functionObjects::catalystFaMesh::catalystFaMesh
(
const word& name,
const Time& runTime,
const dictionary& dict
)
:
fvMeshFunctionObject(name, runTime, dict),
selectAreas_(),
selectFields_(),
scripts_(),
meshes_(),
backends_(),
adaptor_()
{
read(dict);
}
// * * * * * * * * * * * * * * * * Destructor * * * * * * * * * * * * * * * //
Foam::functionObjects::catalystFaMesh::~catalystFaMesh()
{}
// * * * * * * * * * * * * * * * Member Functions * * * * * * * * * * * * * //
bool Foam::functionObjects::catalystFaMesh::read(const dictionary& dict)
{
fvMeshFunctionObject::read(dict);
int debugLevel = 0;
if (dict.readIfPresent("debug", debugLevel))
{
catalystCoprocess::debug = debugLevel;
}
// All possible meshes
meshes_ = mesh_.lookupClass<faMesh>();
selectAreas_.clear();
dict.readIfPresent("areas", selectAreas_);
if (selectAreas_.empty())
{
word areaName;
if (!dict.readIfPresent("area", areaName))
{
wordList available = mesh_.sortedNames<faMesh>();
if (available.size())
{
areaName = available.first();
}
}
if (!areaName.empty())
{
selectAreas_.resize(1);
selectAreas_.first() = areaName;
}
}
// Restrict to specified meshes
meshes_.filterKeys(wordRes(selectAreas_));
dict.lookup("fields") >> selectFields_;
dict.lookup("scripts") >> scripts_; // Python scripts
catalystCoprocess::expand(scripts_, dict); // Expand and check availability
Info<< type() << " " << name() << ":" << nl
<<" areas " << flatOutput(selectAreas_) << nl
<<" meshes " << flatOutput(meshes_.sortedToc()) << nl
<<" fields " << flatOutput(selectFields_) << nl
<<" scripts " << scripts_ << nl;
// Run-time modification of pipeline
if (adaptor_.valid())
{
adaptor_().reset(scripts_);
}
// Ensure consistency - only retain backends with corresponding mesh region
backends_.retain(meshes_);
return true;
}
bool Foam::functionObjects::catalystFaMesh::execute()
{
const wordList regionNames(meshes_.sortedToc());
if (regionNames.empty())
{
return false;
}
// Enforce sanity for backends and adaptor
{
bool updateAdaptor = false;
forAllConstIters(meshes_, iter)
{
if (!backends_.found(iter.key()))
{
backends_.insert
(
iter.key(),
new Foam::vtk::faMeshAdaptor(*(iter.object()))
);
updateAdaptor = true;
}
}
if (updateAdaptor && !adaptor_.valid())
{
adaptor_.reset(new catalystCoprocess());
adaptor_().reset(scripts_);
}
}
// Gather all fields that we know how to convert
wordHashSet allFields;
forAllConstIters(backends_, iter)
{
allFields += iter.object()->knownFields(selectFields_);
}
// Data description for co-processing
vtkNew<vtkCPDataDescription> descrip;