From 910a16ef4489ef54ec12a69e0f0c16f1d0d7c978 Mon Sep 17 00:00:00 2001 From: Timo Koch <timo.koch@iws.uni-stuttgart.de> Date: Wed, 11 Jul 2018 08:47:07 +0200 Subject: [PATCH] [io][vtk][xml] Implement vtu reader to separate xml interface from loadsolution algorithm --- dumux/io/loadsolution.hh | 197 +++++++++--------------- dumux/io/vtk/vtureader.hh | 114 ++++++++++++++ dumux/io/{tinyxml2 => xml}/tinyxml2.cpp | 0 dumux/io/{tinyxml2 => xml}/tinyxml2.h | 0 4 files changed, 190 insertions(+), 121 deletions(-) create mode 100644 dumux/io/vtk/vtureader.hh rename dumux/io/{tinyxml2 => xml}/tinyxml2.cpp (100%) rename dumux/io/{tinyxml2 => xml}/tinyxml2.h (100%) diff --git a/dumux/io/loadsolution.hh b/dumux/io/loadsolution.hh index 25132ba57d..ac74e63f4f 100644 --- a/dumux/io/loadsolution.hh +++ b/dumux/io/loadsolution.hh @@ -21,17 +21,19 @@ * \ingroup InputOutput * \brief read from a file into a solution vector */ -#ifndef DUMUX_LOADSOLUTION_HH -#define DUMUX_LOADSOLUTION_HH +#ifndef DUMUX_IO_LOADSOLUTION_HH +#define DUMUX_IO_LOADSOLUTION_HH #include <string> #include <iostream> +#include <vector> +#include <unordered_set> #include <dune/common/exceptions.hh> #include <dumux/common/parameters.hh> #include <dumux/common/typetraits/isvalid.hh> -#include <dumux/io/tinyxml2/tinyxml2.h> +#include <dumux/io/vtk/vtureader.hh> namespace Dumux { @@ -46,139 +48,78 @@ struct hasState /*! * \ingroup InputOutput - * \brief helper class to read from a file into a solution vector + * \brief helper function to read from a file into a solution vector */ -class LoadSolutionHelper { -public: - template <class FVGridGeometry, class SolutionVector, class PvNamesFunc> - static void loadFromVtkFile(const std::string fileName, - const FVGridGeometry& fvGridGeometry, - PvNamesFunc pvNamesFunc, - SolutionVector& sol) - { - using namespace tinyxml2; - - XMLDocument xmlDoc; - auto eResult = xmlDoc.LoadFile(fileName.c_str()); - if (eResult != XML_SUCCESS) - DUNE_THROW(Dune::IOError, "Couldn't open XML file " << fileName << "."); - - XMLElement *pieceNode = xmlDoc.FirstChildElement("VTKFile")->FirstChildElement("UnstructuredGrid")->FirstChildElement("Piece"); - if (pieceNode == nullptr) - DUNE_THROW(Dune::IOError, "Couldn't get Piece node in " << fileName << "."); - - XMLElement *dataNode = nullptr; - if (FVGridGeometry::discMethod == DiscretizationMethod::box) - dataNode = pieceNode->FirstChildElement("PointData"); - else - dataNode = pieceNode->FirstChildElement("CellData"); +template <class SolutionVector, class PvNamesFunc> +auto loadFromVtuFile(const std::string fileName, + const VTUReader::DataType& dataType, + PvNamesFunc&& pvNamesFunc, + SolutionVector& sol) +-> typename std::enable_if_t<!decltype(isValid(hasState())(sol[0]))::value, void> +{ + VTUReader vtu(fileName); + using PrimaryVariables = typename SolutionVector::block_type; + using Scalar = typename PrimaryVariables::field_type; - if (dataNode == nullptr) - DUNE_THROW(Dune::IOError, "Couldn't get data node in " << fileName << "."); + for (size_t pvIdx = 0; pvIdx < PrimaryVariables::dimension; ++pvIdx) + { + const auto pvName = pvNamesFunc(pvIdx); + const auto vec = vtu.readData<std::vector<Scalar>>(pvName, dataType); + if (vec.size() != sol.size()) + DUNE_THROW(Dune::IOError, "Size mismatch between solution vector and read data (" << sol.size() << " != " << vec.size() << ")"); - setPrimaryVariables_(dataNode, fvGridGeometry, pvNamesFunc, sol); + for (std::size_t i = 0; i < sol.size(); ++i) + sol[i][pvIdx] = vec[i]; } +} -private: - - template <class Scalar, class FVGridGeometry> - static std::vector<Scalar> extractDataToVector_(tinyxml2::XMLElement *xmlNode, - const std::string& name, - const FVGridGeometry& fvGridGeometry) +/*! + * \ingroup InputOutput + * \brief helper function to read from a file into a solution vector + */ +template <class SolutionVector, class PvNamesFunc> +auto loadFromVtuFile(const std::string fileName, + const VTUReader::DataType& dataType, + PvNamesFunc&& pvNamesFunc, + SolutionVector& sol) +-> typename std::enable_if_t<decltype(isValid(hasState())(sol[0]))::value, void> +{ + VTUReader vtu(fileName); + const auto vec = vtu.readData<std::vector<int>>("phase presence", dataType); + std::unordered_set<int> states; + for (size_t i = 0; i < sol.size(); ++i) { - // loop over XML node siblings to find the correct data array - tinyxml2::XMLElement *dataArray = xmlNode->FirstChildElement("DataArray"); - for (; dataArray != nullptr; dataArray = dataArray->NextSiblingElement("DataArray")) - { - const char *attributeText = dataArray->Attribute("Name"); - - if (attributeText == nullptr) - DUNE_THROW(Dune::IOError, "Couldn't get Name attribute."); - - if (std::string(attributeText) == name) - break; - } - if (dataArray == nullptr) - DUNE_THROW(Dune::IOError, "Couldn't find the data array " << name << "."); - - std::stringstream dataStream(dataArray->GetText()); - std::vector<Scalar> vec(fvGridGeometry.numDofs()); - for (auto& val : vec) - { - dataStream >> val; - } - - return vec; + const int state = vec[i]; + sol[i].setState(state); + states.insert(state); } - template<class SolutionVector, class FVGridGeometry, class PvNamesFunc> - static auto setPrimaryVariables_(tinyxml2::XMLElement *node, - const FVGridGeometry& fvGridGeometry, - PvNamesFunc pvNamesFunc, - SolutionVector& sol) - -> typename std::enable_if_t<!decltype(isValid(hasState())(sol[0]))::value, void> + using PrimaryVariables = typename SolutionVector::block_type; + using Scalar = typename PrimaryVariables::field_type; + for (size_t pvIdx = 0; pvIdx < PrimaryVariables::dimension; ++pvIdx) { - using PrimaryVariables = typename SolutionVector::block_type; - using Scalar = typename PrimaryVariables::field_type; - - for (size_t pvIdx = 0; pvIdx < PrimaryVariables::dimension; ++pvIdx) + if (pvNamesFunc(pvIdx, 1) == pvNamesFunc(pvIdx, 2)) { - auto pvName = pvNamesFunc(pvIdx); - - auto vec = extractDataToVector_<Scalar>(node, pvName, fvGridGeometry); - + const auto vec = vtu.readData<std::vector<Scalar>>(pvNamesFunc(pvIdx, 1), dataType); for (size_t i = 0; i < sol.size(); ++i) sol[i][pvIdx] = vec[i]; } - } - - template<class SolutionVector, class FVGridGeometry, class PvNamesFunc> - static auto setPrimaryVariables_(tinyxml2::XMLElement *node, - const FVGridGeometry& fvGridGeometry, - PvNamesFunc pvNamesFunc, - SolutionVector& sol) - -> typename std::enable_if_t<decltype(isValid(hasState())(sol[0]))::value, void> - { - auto vec = extractDataToVector_<int>(node, "phase presence", fvGridGeometry); - - int minState = std::numeric_limits<int>::max(); - int maxState = std::numeric_limits<int>::min(); - for (size_t i = 0; i < sol.size(); ++i) + else { - int state = vec[i]; - sol[i].setState(state); - using std::min; - minState = min(minState, state); - using std::max; - maxState = max(maxState, state); - } + std::unordered_map<int, std::vector<Scalar>> switchedPvsSol; + for (const auto& state : states) + switchedPvsSol[state] = vtu.readData<std::vector<Scalar>>(pvNamesFunc(pvIdx, state), dataType); - using PrimaryVariables = typename SolutionVector::block_type; - using Scalar = typename PrimaryVariables::field_type; - for (size_t pvIdx = 0; pvIdx < PrimaryVariables::dimension; ++pvIdx) - { - if (pvNamesFunc(pvIdx, 1) == pvNamesFunc(pvIdx, 2)) - { - auto vec = extractDataToVector_<Scalar>(node, pvNamesFunc(pvIdx, 1), fvGridGeometry); - - for (size_t i = 0; i < sol.size(); ++i) - sol[i][pvIdx] = vec[i]; - } - else - { - std::vector<std::vector<Scalar>> switchedPvsSol; - for (int state = minState; state <= maxState; ++state) - switchedPvsSol.push_back(extractDataToVector_<Scalar>(node, - pvNamesFunc(pvIdx, state), - fvGridGeometry)); - - for (size_t i = 0; i < sol.size(); ++i) - sol[i][pvIdx] = switchedPvsSol[sol[i].state() - minState][i]; - } + for (size_t i = 0; i < sol.size(); ++i) + sol[i][pvIdx] = switchedPvsSol[sol[i].state()][i]; } } -}; +} +/*! + * \ingroup InputOutput + * \brief helper function to determine the primray variable names of a model with privar state + */ template<class ModelTraits, class FluidSystem> std::string primaryVariableName(int pvIdx, int state) { @@ -188,6 +129,10 @@ std::string primaryVariableName(int pvIdx, int state) return ModelTraits::template primaryVariableName<FluidSystem>(pvIdx, state); } +/*! + * \ingroup InputOutput + * \brief helper function to determine the primray variable names of a model + */ template<class ModelTraits> std::string primaryVariableName(int pvIdx) { @@ -197,16 +142,26 @@ std::string primaryVariableName(int pvIdx) return ModelTraits::primaryVariableName(pvIdx); } + +/*! + * \ingroup InputOutput + * \brief load a solution vector from file + * \note Supports the following file extensions: *.vtu + */ template <class FVGridGeometry, class SolutionVector, class PvNamesFunc> void loadSolution(const std::string& fileName, const FVGridGeometry& fvGridGeometry, - PvNamesFunc pvNamesFunc, + PvNamesFunc&& pvNamesFunc, SolutionVector& sol) { - auto extension = fileName.substr(fileName.find_last_of(".") + 1); + const auto extension = fileName.substr(fileName.find_last_of(".") + 1); if (extension == "vtu") - LoadSolutionHelper::loadFromVtkFile(fileName, fvGridGeometry, pvNamesFunc, sol); + { + const auto dataType = FVGridGeometry::discMethod == DiscretizationMethod::box + ? VTUReader::DataType::pointData : VTUReader::DataType::cellData; + loadFromVtuFile(fileName, dataType, pvNamesFunc, sol); + } else DUNE_THROW(Dune::NotImplemented, "loadSolution for extension " << extension); } diff --git a/dumux/io/vtk/vtureader.hh b/dumux/io/vtk/vtureader.hh new file mode 100644 index 0000000000..5ce9f7e6a6 --- /dev/null +++ b/dumux/io/vtk/vtureader.hh @@ -0,0 +1,114 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/***************************************************************************** + * See the file COPYING for full copying permissions. * + * * + * This program 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 2 of the License, or * + * (at your option) any later version. * + * * + * This program 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 this program. If not, see <http://www.gnu.org/licenses/>. * + *****************************************************************************/ +/*! + * \file + * \ingroup InputOutput + * \brief A vtu reader using tinyxml2 as xml backend + */ +#ifndef DUMUX_IO_VTK_VTUREADER_HH +#define DUMUX_IO_VTK_VTUREADER_HH + +#include <iostream> +#include <iterator> +#include <algorithm> + +#include <dune/common/exceptions.hh> +#include <dumux/io/xml/tinyxml2.h> + +namespace Dumux { + +/*! + * \ingroup InputOutput + * \brief A vtu reader using tinyxml2 as xml backend + */ +class VTUReader +{ +public: + /*! + * \brief The data array types + */ + enum class DataType { cellData, pointData }; + + /*! + * \brief The contructor creates a tinyxml2::XMLDocument from file + */ + VTUReader(const std::string& fileName) + : fileName_(fileName) + { + using namespace tinyxml2; + const auto eResult = doc_.LoadFile(fileName.c_str()); + if (eResult != XML_SUCCESS) + DUNE_THROW(Dune::IOError, "Couldn't open XML file " << fileName << "."); + } + + /*! + * \brief The contructor creates a tinyxml2::XMLDocument from file + * \tparam Container a container type that has begin(), end(), push_back(), e.g. std::vector<> + * \param name the name attribute of the data array to read + * \param type the data array type + */ + template<class Container> + Container readData(const std::string& name, const DataType& type) + { + using namespace tinyxml2; + XMLElement *pieceNode = doc_.FirstChildElement("VTKFile")->FirstChildElement("UnstructuredGrid")->FirstChildElement("Piece"); + if (pieceNode == nullptr) + DUNE_THROW(Dune::IOError, "Couldn't get Piece node in " << fileName_ << "."); + + XMLElement *dataNode = nullptr; + if (type == DataType::pointData) + dataNode = pieceNode->FirstChildElement("PointData"); + else if (type == DataType::cellData) + dataNode = pieceNode->FirstChildElement("CellData"); + else + DUNE_THROW(Dune::IOError, "Only cell and point data are supported."); + + if (dataNode == nullptr) + DUNE_THROW(Dune::IOError, "Couldn't get data node in " << fileName_ << "."); + + // loop over XML node siblings to find the correct data array + XMLElement *dataArray = dataNode->FirstChildElement("DataArray"); + for (; dataArray != nullptr; dataArray = dataArray->NextSiblingElement("DataArray")) + { + const char *attributeText = dataArray->Attribute("Name"); + + if (attributeText == nullptr) + DUNE_THROW(Dune::IOError, "Couldn't get Name attribute of a data array."); + + if (attributeText == name) + break; + } + if (dataArray == nullptr) + DUNE_THROW(Dune::IOError, "Couldn't find the data array " << name << "."); + + Container data; + std::stringstream dataStream(dataArray->GetText()); + std::istream_iterator<typename Container::value_type> it(dataStream); + std::copy(it, std::istream_iterator<typename Container::value_type>(), std::back_inserter(data)); + return data; + } + +private: + const std::string fileName_; //!< the vtu file name + tinyxml2::XMLDocument doc_; //!< the xml document created from file with name fileName_ +}; + +} // end namespace Dumux + +#endif diff --git a/dumux/io/tinyxml2/tinyxml2.cpp b/dumux/io/xml/tinyxml2.cpp similarity index 100% rename from dumux/io/tinyxml2/tinyxml2.cpp rename to dumux/io/xml/tinyxml2.cpp diff --git a/dumux/io/tinyxml2/tinyxml2.h b/dumux/io/xml/tinyxml2.h similarity index 100% rename from dumux/io/tinyxml2/tinyxml2.h rename to dumux/io/xml/tinyxml2.h -- GitLab