From 24cdce4b0a2b9219aa6a72b2503dbb63ec31106d Mon Sep 17 00:00:00 2001 From: Christoph Lehmann <christoph.lehmann@ufz.de> Date: Wed, 11 May 2016 17:03:08 -0400 Subject: [PATCH] [PL] added class for output and secondary vars --- ProcessLib/ProcessOutput.h | 258 +++++++++++++++++++++++++++++++++ ProcessLib/SecondaryVariable.h | 237 ++++++++++++++++++++++++++++++ 2 files changed, 495 insertions(+) create mode 100644 ProcessLib/ProcessOutput.h create mode 100644 ProcessLib/SecondaryVariable.h diff --git a/ProcessLib/ProcessOutput.h b/ProcessLib/ProcessOutput.h new file mode 100644 index 00000000000..0e29556db7b --- /dev/null +++ b/ProcessLib/ProcessOutput.h @@ -0,0 +1,258 @@ +/** + * \copyright + * Copyright (c) 2012-2016, OpenGeoSys Community (http://www.opengeosys.org) + * Distributed under a Modified BSD License. + * See accompanying file LICENSE.txt or + * http://www.opengeosys.org/project/license + * + */ + +#ifndef PROCESSLIB_PROCESSOUTPUT_H +#define PROCESSLIB_PROCESSOUTPUT_H + +#include "MeshLib/IO/VtkIO/VtuInterface.h" +#include "ProcessVariable.h" +#include "SecondaryVariable.h" + +namespace ProcessLib +{ + +//! Holds information about which variables to write to output files. +template <typename GlobalVector> +struct ProcessOutput final +{ + //! Constructs a new instance. + ProcessOutput(BaseLib::ConfigTree const& output_config, + std::vector<std::reference_wrapper<ProcessVariable>> const& + process_variables, + SecondaryVariableCollection<GlobalVector> const& secondary_variables) + { + auto const out_vars = output_config.getConfSubtree("variables"); + + for (auto out_var : out_vars.getConfParamList<std::string>("variable")) + { + if (output_variables.find(out_var) != output_variables.cend()) + { + ERR("output variable `%s' specified more than once.", out_var.c_str()); + std::abort(); + } + + auto pred = [&out_var](ProcessVariable const& pv) { + return pv.getName() == out_var; + }; + + // check if out_var is a process variable + auto const& pcs_var = std::find_if( + process_variables.cbegin(), process_variables.cend(), pred); + + if (pcs_var == process_variables.cend() + && !secondary_variables.variableExists(out_var)) + { + ERR("Output variable `%s' is neither a process variable nor a" + " secondary variable", out_var.c_str()); + std::abort(); + } + + DBUG("adding output variable `%s'", out_var.c_str()); + output_variables.insert(out_var); + } + + if (auto out_resid = output_config.getConfParamOptional<bool>( + "output_extrapolation_residuals")) { + output_residuals = *out_resid; + } + + // debug output + if (auto const param = + output_config.getConfParamOptional<bool>("output_iteration_results")) + { + DBUG("output_iteration_results: %s", (*param) ? "true" : "false"); + + output_iteration_results = *param; + } + } + + //! All variables that shall be output. + std::set<std::string> output_variables; + + //! Tells if also to output extrapolation residuals. + bool output_residuals = false; + + bool output_iteration_results = false; +}; + + +//! Writes output to the given \c file_name using the VTU file format. +template <typename GlobalVector> +void doProcessOutput( + std::string const& file_name, + GlobalVector const& x, + MeshLib::Mesh& mesh, + AssemblerLib::LocalToGlobalIndexMap const& dof_table, + std::vector<std::reference_wrapper<ProcessVariable>> const& + process_variables, + SecondaryVariableCollection<GlobalVector> secondary_variables, + ProcessOutput<GlobalVector> const& process_output) +{ + DBUG("Process output."); + + // Copy result +#ifdef USE_PETSC + // TODO It is also possible directly to copy the data for single process + // variable to a mesh property. It needs a vector of global indices and + // some PETSc magic to do so. + std::vector<double> x_copy(x.getLocalSize() + x.getGhostSize()); +#else + std::vector<double> x_copy(x.size()); +#endif + x.copyValues(x_copy); + + auto const& output_variables = process_output.output_variables; + + std::size_t const n_nodes = mesh.getNNodes(); + int global_component_offset = 0; + int global_component_offset_next = 0; + + // primary variables + for (ProcessVariable& pv : process_variables) + { + int const n_components = pv.getNumberOfComponents(); + global_component_offset = global_component_offset_next; + global_component_offset_next += n_components; + + if (output_variables.find(pv.getName()) == output_variables.cend()) + continue; + + DBUG(" process variable %s", pv.getName().c_str()); + + auto& output_data = pv.getOrCreateMeshProperty(); + + for (std::size_t node_id = 0; node_id < n_nodes; ++node_id) + { + MeshLib::Location const l(mesh.getID(), + MeshLib::MeshItemType::Node, node_id); + // TODO extend component ids to multiple process variables. + for (int component_id = 0; component_id < n_components; + ++component_id) + { + auto const global_component_id = global_component_offset + component_id; + auto const index = + dof_table.getLocalIndex( + l, global_component_id, x.getRangeBegin(), + x.getRangeEnd()); + + output_data[node_id * n_components + component_id] = + x_copy[index]; + } + } + } + +#ifndef USE_PETSC + // the following section is for the output of secondary variables + + auto count_mesh_items = []( + MeshLib::Mesh const& mesh, MeshLib::MeshItemType type) -> std::size_t + { + switch (type) { + case MeshLib::MeshItemType::Cell: return mesh.getNElements(); + case MeshLib::MeshItemType::Node: return mesh.getNNodes(); + default: break; // avoid compiler warning + } + return 0; + }; + + auto get_or_create_mesh_property = [&mesh, &count_mesh_items]( + std::string const& property_name, MeshLib::MeshItemType type) + { + // Get or create a property vector for results. + boost::optional<MeshLib::PropertyVector<double>&> result; + + auto const N = count_mesh_items(mesh, type); + + if (mesh.getProperties().hasPropertyVector(property_name)) + { + result = mesh.getProperties().template + getPropertyVector<double>(property_name); + } + else + { + result = mesh.getProperties().template + createNewPropertyVector<double>(property_name, type); + result->resize(N); + } + assert(result && result->size() == N); + + return result; + }; + + auto add_secondary_var = [&](SecondaryVariable<GlobalVector> const& var, + std::string const& output_name) + { + assert(var.n_components == 1); // TODO implement other cases + + { + DBUG(" secondary variable %s", output_name.c_str()); + + auto result = get_or_create_mesh_property( + output_name, MeshLib::MeshItemType::Node); + assert(result->size() == mesh.getNNodes()); + + std::unique_ptr<GlobalVector> result_cache; + auto const& nodal_values = + var.fcts.eval_field(x, dof_table, result_cache); + + // Copy result + for (std::size_t i = 0; i < mesh.getNNodes(); ++i) + { + assert(!std::isnan(nodal_values[i])); + (*result)[i] = nodal_values[i]; + } + } + + if (process_output.output_residuals && var.fcts.eval_residuals) + { + DBUG(" secondary variable %s residual", output_name.c_str()); + auto const& property_name_res = output_name + "_residual"; + + auto result = get_or_create_mesh_property( + property_name_res, MeshLib::MeshItemType::Cell); + assert(result->size() == mesh.getNElements()); + + std::unique_ptr<GlobalVector> result_cache; + auto const& residuals = + var.fcts.eval_residuals(x, dof_table, result_cache); + + // Copy result + for (std::size_t i = 0; i < mesh.getNElements(); ++i) + { + assert(!std::isnan(residuals[i])); + (*result)[i] = residuals[i]; + } + } + }; + + for (auto const& var : secondary_variables) + { + auto const var_name = secondary_variables.getMappedName(var.name); + if (output_variables.find(var_name) + != output_variables.cend()) + { + add_secondary_var(var, var_name); + } + } + + // secondary variables output end +#else + (void) secondary_variables; +#endif // USE_PETSC + + // Write output file + DBUG("Writing output to \'%s\'.", file_name.c_str()); + MeshLib::IO::VtuInterface vtu_interface(&mesh, vtkXMLWriter::Binary, true); + vtu_interface.writeToFile(file_name); +} + +} // ProcessLib + + +#endif // PROCESSLIB_PROCESSOUTPUT_H diff --git a/ProcessLib/SecondaryVariable.h b/ProcessLib/SecondaryVariable.h new file mode 100644 index 00000000000..7e3cbfbef0c --- /dev/null +++ b/ProcessLib/SecondaryVariable.h @@ -0,0 +1,237 @@ +/** + * \copyright + * Copyright (c) 2012-2016, OpenGeoSys Community (http://www.opengeosys.org) + * Distributed under a Modified BSD License. + * See accompanying file LICENSE.txt or + * http://www.opengeosys.org/project/license + * + */ + +#ifndef PROCESSLIB_SECONDARY_VARIABLE_H +#define PROCESSLIB_SECONDARY_VARIABLE_H + +#include "BaseLib/ConfigTree.h" +#include "BaseLib/uniqueInsert.h" +#include "NumLib/Extrapolation/Extrapolator.h" + +namespace AssemblerLib { class LocalToGlobalIndexMap; } + +namespace ProcessLib +{ + +//! Holder for function objects that compute secondary variables, +//! and (optionally) also the residuals (e.g., in case of extrapolation) +template<typename GlobalVector> +struct SecondaryVariableFunctions final +{ + /*! Type of functions used. + * + * \note The argument \c dof_table is the d.o.f. table of the process, i.e. + * it possibly contains information about several process variables. + * + * \remark The \c result_cache can be used to store the \c GlobalVector if it + * is computed on-the-fly. Then a reference to the result cache can be returned. + * Otherwise the \c Function must return a reference to a \c GlobalVector that + * is stored somewhere else. + */ + using Function = std::function<GlobalVector const&( + GlobalVector const& x, + AssemblerLib::LocalToGlobalIndexMap const& dof_table, + std::unique_ptr<GlobalVector>& result_cache)>; + + SecondaryVariableFunctions() = default; + + template<typename F1, typename F2> + SecondaryVariableFunctions(F1&& eval_field_, F2&& eval_residuals_) + : eval_field(std::forward<F1>(eval_field_)) + , eval_residuals(std::forward<F2>(eval_residuals_)) + { + // Used to detect nasty implicit conversions. + static_assert(std::is_same<GlobalVector const&, + typename std::result_of<F1( + GlobalVector const&, AssemblerLib::LocalToGlobalIndexMap const&, + std::unique_ptr<GlobalVector>& + )>::type>::value, + "The function eval_field_ does not return a const reference" + " to a GlobalVector"); + + static_assert(std::is_same<GlobalVector const&, + typename std::result_of<F2( + GlobalVector const&, AssemblerLib::LocalToGlobalIndexMap const&, + std::unique_ptr<GlobalVector>& + )>::type>::value, + "The function eval_residuals_ does not return a const reference" + " to a GlobalVector"); + } + + template<typename F1> + SecondaryVariableFunctions(F1&& eval_field_, std::nullptr_t) + : eval_field(std::forward<F1>(eval_field_)) + { + // Used to detect nasty implicit conversions. + static_assert(std::is_same<GlobalVector const&, + typename std::result_of<F1( + GlobalVector const&, AssemblerLib::LocalToGlobalIndexMap const&, + std::unique_ptr<GlobalVector>& + )>::type>::value, + "The function eval_field_ does not return a const reference" + " to a GlobalVector"); + } + + Function eval_field; + Function eval_residuals; +}; + +//! Stores information about a specific secondary variable +template<typename GlobalVector> +struct SecondaryVariable final +{ + std::string const name; //!< Name of the variable; used, e.g., for output. + const unsigned n_components; //!< Number of components of the variable. + + //! Functions used for computing the secondary variable. + SecondaryVariableFunctions<GlobalVector> fcts; +}; + +//! Handles configuration of several secondary variables from the project file. +template<typename GlobalVector> +class SecondaryVariableCollection final +{ +public: + /*! Constructs new instance. + * + * \param config Configuration settings. + * \param tag_names Possible tag names that contain information about specific + * secondary variables. + * + * In this method only the mapping from tag names to variables is set up. + * Any further information has to be passed via addSecondaryVariable(). + */ + SecondaryVariableCollection( + boost::optional<BaseLib::ConfigTree> const& config, + std::initializer_list<std::string> tag_names) + { + if (!config) return; + + // read which variables are defined in the config + for (auto const& tag_name : tag_names) { + if (auto var_name = config->getConfParamOptional<std::string>(tag_name)) + { + // TODO check primary vars, too + BaseLib::insertIfKeyValueUniqueElseError( + _map_tagname_to_varname, tag_name, *var_name, + "Secondary variable names must be unique."); + } + } + } + + /*! Tells if a secondary variable with the specified name has been set up. + * + * \note \c variable_name is not the tag name in the project file! + */ + bool variableExists(std::string const& variable_name) const + { + auto pred = [&variable_name](std::pair<std::string, std::string> const& p) { + return p.second == variable_name; + }; + + // check if out_var is a secondary variable + auto const& var = std::find_if( + _map_tagname_to_varname.cbegin(), _map_tagname_to_varname.cend(), pred); + + return var != _map_tagname_to_varname.cend(); + } + + /*! Set up a secondary variable. + * + * \param tag_name the tag in the project file associated with this secondary variable. + * \param num_components the variable's number of components. + * \param fcts functions that compute the variable. + * + * \note + * Only variables requested by the user in the project file will be configured. + * All other variables are silently ignored. + */ + void addSecondaryVariable( + std::string const& tag_name, const unsigned num_components, + SecondaryVariableFunctions<GlobalVector>&& fcts) + { + auto it = _map_tagname_to_varname.find(tag_name); + + // get user-supplied var_name for the given tag_name + if (it != _map_tagname_to_varname.end()) { + auto const& var_name = it->first; + // TODO make sure the same variable is not pushed twice + _secondary_variables.push_back( + {var_name, num_components, std::move(fcts)}); + } + } + + std::string const& getMappedName(std::string const& tag_name) + { + return _map_tagname_to_varname[tag_name]; + } + + //! Returns an iterator to the first secondary variable. + typename std::vector<SecondaryVariable<GlobalVector>>::const_iterator + begin() const + { + return _secondary_variables.begin(); + } + + //! Returns an iterator past the last secondary variable. + typename std::vector<SecondaryVariable<GlobalVector>>::const_iterator + end() const + { + return _secondary_variables.end(); + } + +private: + //! Maps project file tag names to secondary variable names. + std::map<std::string, std::string> _map_tagname_to_varname; + + //! Collection of all configured secondary variables. + std::vector<SecondaryVariable<GlobalVector>> _secondary_variables; +}; + + +//! Creates an object that computes a secondary variable via extrapolation +//! of integration point values. +template<typename GlobalVector, typename PropertyEnum, typename LocalAssembler> +SecondaryVariableFunctions<GlobalVector> +makeExtrapolator(PropertyEnum const property, + NumLib::Extrapolator<GlobalVector, PropertyEnum, LocalAssembler>& + extrapolator, + typename NumLib::Extrapolator<GlobalVector, PropertyEnum, + LocalAssembler>::LocalAssemblers const& local_assemblers) +{ + static_assert(std::is_base_of< + NumLib::Extrapolatable<GlobalVector, PropertyEnum>, LocalAssembler>::value, + "The passed local assembler type (i.e. the local assembler interface) must" + " derive from NumLib::Extrapolatable<>."); + + auto const eval_field = [property, &extrapolator, &local_assemblers]( + GlobalVector const& /*x*/, + AssemblerLib::LocalToGlobalIndexMap const& /*dof_table*/, + std::unique_ptr<GlobalVector>& /*result_cache*/ + ) -> GlobalVector const& + { + extrapolator.extrapolate(local_assemblers, property); + return extrapolator.getNodalValues(); + }; + + auto const eval_residuals = [property, &extrapolator, &local_assemblers]( + GlobalVector const& /*x*/, + AssemblerLib::LocalToGlobalIndexMap const& /*dof_table*/, + std::unique_ptr<GlobalVector>& /*result_cache*/ + ) -> GlobalVector const& + { + extrapolator.calculateResiduals(local_assemblers, property); + return extrapolator.getElementResiduals(); + }; + return { eval_field, eval_residuals }; +} + +} // namespace ProcessLib + +#endif // PROCESSLIB_SECONDARY_VARIABLE_H -- GitLab