diff --git a/Applications/CLI/CMakeLists.txt b/Applications/CLI/CMakeLists.txt index f4e4a94213a15deeb51a0f63eb7f6b1a620a471d..74c05b7a45e9a407333d00bd035eefbc70145034 100644 --- a/Applications/CLI/CMakeLists.txt +++ b/Applications/CLI/CMakeLists.txt @@ -39,7 +39,7 @@ if(OGS_USE_PYTHON) target_link_libraries(ogs_embedded_python PUBLIC pybind11::embed) target_compile_definitions(ogs_embedded_python PUBLIC OGS_USE_PYTHON) target_link_libraries(ogs_embedded_python PRIVATE - ProcessLibBoundaryConditionPythonModule) + ProcessLibBoundaryConditionPythonModule ProcessLibSourceTermPythonModule) target_link_libraries(ogs PRIVATE ogs_embedded_python) diff --git a/Applications/CLI/ogs_embedded_python.cpp b/Applications/CLI/ogs_embedded_python.cpp index 39b6e3e28182fe1fabe928e579a73dedd69f306b..b925f02e65e996c7e18a6c07e9d768473e9ae8b0 100644 --- a/Applications/CLI/ogs_embedded_python.cpp +++ b/Applications/CLI/ogs_embedded_python.cpp @@ -13,12 +13,14 @@ #include "ogs_embedded_python.h" #include "ProcessLib/BoundaryCondition/Python/PythonBoundaryConditionModule.h" +#include "ProcessLib/SourceTerms/Python/PythonSourceTermModule.h" PYBIND11_EMBEDDED_MODULE(OpenGeoSys, m) { DBUG("Binding Python module OpenGeoSys."); ProcessLib::pythonBindBoundaryCondition(m); + ProcessLib::SourceTerms::Python::pythonBindSourceTerm(m); } #ifndef OGS_BUILD_SHARED_LIBS diff --git a/Documentation/ProjectFile/prj/process_variables/process_variable/source_terms/source_term/Python/c_Python.md b/Documentation/ProjectFile/prj/process_variables/process_variable/source_terms/source_term/Python/c_Python.md new file mode 100644 index 0000000000000000000000000000000000000000..dfd8a940617d636680e3ce482ecb203ca50d9c41 --- /dev/null +++ b/Documentation/ProjectFile/prj/process_variables/process_variable/source_terms/source_term/Python/c_Python.md @@ -0,0 +1,4 @@ +Source term using a Python script to compute its values. + +See ProcessLib::SourceTerms::Python::PythonSourceTermPythonSideInterface for +the Python-side source term interface. diff --git a/Documentation/ProjectFile/prj/process_variables/process_variable/source_terms/source_term/Python/t_flush_stdout.md b/Documentation/ProjectFile/prj/process_variables/process_variable/source_terms/source_term/Python/t_flush_stdout.md new file mode 100644 index 0000000000000000000000000000000000000000..9ea47703dd005980f7a336584429e48635465a9d --- /dev/null +++ b/Documentation/ProjectFile/prj/process_variables/process_variable/source_terms/source_term/Python/t_flush_stdout.md @@ -0,0 +1,3 @@ +Determines if stdout should be flushed each time before and after passing +control to the Python script. This might slow down operation, but ensures the +right order of messages printed to stdout from Python and C++. diff --git a/Documentation/ProjectFile/prj/process_variables/process_variable/source_terms/source_term/Python/t_source_term_object.md b/Documentation/ProjectFile/prj/process_variables/process_variable/source_terms/source_term/Python/t_source_term_object.md new file mode 100644 index 0000000000000000000000000000000000000000..f36c8cd36ac7097d0fe9961ca75350142b0405dc --- /dev/null +++ b/Documentation/ProjectFile/prj/process_variables/process_variable/source_terms/source_term/Python/t_source_term_object.md @@ -0,0 +1,2 @@ +The object (variable name) in the provided Python script that will be used for +this source term. diff --git a/ProcessLib/CMakeLists.txt b/ProcessLib/CMakeLists.txt index 61f2ee6746a5e216943913aa368515cdf45e6308..162ba4aacc5185ee79e8b4ad4f3c706984316932 100644 --- a/ProcessLib/CMakeLists.txt +++ b/ProcessLib/CMakeLists.txt @@ -32,6 +32,10 @@ if(OGS_USE_PYTHON) add_subdirectory(BoundaryCondition/Python) target_link_libraries(ProcessLib PUBLIC ProcessLibBoundaryConditionPython) + + add_subdirectory(SourceTerms/Python) + target_link_libraries(ProcessLib + PUBLIC ProcessLibSourceTermPython) endif() if(OGS_INSITU) diff --git a/ProcessLib/GroundwaterFlow/Tests.cmake b/ProcessLib/GroundwaterFlow/Tests.cmake index 13a983ac84126a5fa6a29df75ad98c5cb3189f15..da650d5a542909bec1c1dfe7ab5ab2f0ba8828e2 100644 --- a/ProcessLib/GroundwaterFlow/Tests.cmake +++ b/ProcessLib/GroundwaterFlow/Tests.cmake @@ -732,3 +732,26 @@ AddTest( python_laplace_eq_ref.vtu square_1e3_neumann_pcs_0_ts_1_t_1.000000.vtu pressure_expected pressure 4e-4 1e-16 ) +AddTest( + NAME PythonSourceTermPoissonSinAXSinBYDirichlet_square_1e3 + PATH Elliptic/square_1x1_GroundWaterFlow_Python + EXECUTABLE ogs + EXECUTABLE_ARGS square_1e3_poisson_sin_x_sin_y.prj + WRAPPER time + TESTER vtkdiff + REQUIREMENTS OGS_USE_PYTHON AND NOT (OGS_USE_LIS OR OGS_USE_MPI) + DIFF_DATA + square_1x1_quad_1e3.vtu square_1e3_volumetricsourceterm_pcs_0_ts_1_t_1.000000.vtu analytical_solution pressure 0.7e-2 1e-16 +) + +AddTest( + NAME PythonSourceTermPoissonSinAXSinBYDirichlet_square_1e5 + PATH Elliptic/square_1x1_GroundWaterFlow_Python + EXECUTABLE ogs + EXECUTABLE_ARGS square_1e5_poisson_sin_x_sin_y.prj + WRAPPER time + TESTER vtkdiff + REQUIREMENTS OGS_USE_PYTHON AND NOT (OGS_USE_LIS OR OGS_USE_MPI) + DIFF_DATA + square_1x1_quad_1e5.vtu square_1e5_volumetricsourceterm_pcs_0_ts_1_t_1.000000.vtu analytical_solution pressure 0.75e-4 1e-16 +) diff --git a/ProcessLib/Process.cpp b/ProcessLib/Process.cpp index 152f2a3fba15e1f7014e318eca822955f75f29eb..a5e92bb5888ff4fad14b9f445e8ff73ab6b54fef 100644 --- a/ProcessLib/Process.cpp +++ b/ProcessLib/Process.cpp @@ -187,9 +187,11 @@ void Process::assemble(const double t, GlobalVector const& x, GlobalMatrix& M, const auto pcs_id = (_coupled_solutions) != nullptr ? _coupled_solutions->process_id : 0; + // the last argument is for the jacobian, nullptr is for a unused jacobian _boundary_conditions[pcs_id].applyNaturalBC(t, x, K, b, nullptr); - _source_term_collections[pcs_id].integrate(t, b); + // the last argument is for the jacobian, nullptr is for a unused jacobian + _source_term_collections[pcs_id].integrate(t, x, b, nullptr); } void Process::assembleWithJacobian(const double t, GlobalVector const& x, diff --git a/ProcessLib/SourceTerms/CreateSourceTerm.cpp b/ProcessLib/SourceTerms/CreateSourceTerm.cpp index 4e50e55f62343ea6eed0a69cb418af8ae9893279..b33cd640db6d86103e7bd2772411260ac2aca2d4 100644 --- a/ProcessLib/SourceTerms/CreateSourceTerm.cpp +++ b/ProcessLib/SourceTerms/CreateSourceTerm.cpp @@ -11,6 +11,9 @@ #include "CreateNodalSourceTerm.h" #include "CreateVolumetricSourceTerm.h" +#ifdef OGS_USE_PYTHON +#include "Python/CreatePythonSourceTerm.h" +#endif #include "SourceTerm.h" #include "SourceTermConfig.h" @@ -41,6 +44,19 @@ std::unique_ptr<SourceTerm> createSourceTerm( *config.component_id); } + if (type == "Python") + { +#ifdef OGS_USE_PYTHON + return ProcessLib::createPythonSourceTerm( + config.config, config.mesh, dof_table, mesh.getID(), variable_id, + *config.component_id, integration_order, shapefunction_order, + mesh.getDimension()); +#else + OGS_FATAL("OpenGeoSys has not been built with Python support."); +#endif + } + + OGS_FATAL("Unknown source term type: `%s'.", type.c_str()); } } // namespace ProcessLib diff --git a/ProcessLib/SourceTerms/NodalSourceTerm.cpp b/ProcessLib/SourceTerms/NodalSourceTerm.cpp index 04d8619f2ed413923c7fb21239bf2160de672cad..723b342672cfd1c04021b3591759a802c5d5608e 100644 --- a/ProcessLib/SourceTerms/NodalSourceTerm.cpp +++ b/ProcessLib/SourceTerms/NodalSourceTerm.cpp @@ -37,7 +37,8 @@ NodalSourceTerm::NodalSourceTerm( } } -void NodalSourceTerm::integrate(const double t, GlobalVector& b) const +void NodalSourceTerm::integrate(const double t, GlobalVector const& /*x*/, + GlobalVector& b, GlobalMatrix* /*jac*/) const { DBUG("Assemble NodalSourceTerm."); diff --git a/ProcessLib/SourceTerms/NodalSourceTerm.h b/ProcessLib/SourceTerms/NodalSourceTerm.h index cc22ee10a8110f9f3db01bc2d60ddc538a965c90..4df7f58b07fdb9da6cdbb82bbcec93bdd973aa92 100644 --- a/ProcessLib/SourceTerms/NodalSourceTerm.h +++ b/ProcessLib/SourceTerms/NodalSourceTerm.h @@ -22,7 +22,8 @@ public: const int variable_id, const int component_id, Parameter<double> const& parameter); - void integrate(const double t, GlobalVector& b) const override; + void integrate(const double t, GlobalVector const& x, GlobalVector& b, + GlobalMatrix* jac) const override; private: std::size_t const _bulk_mesh_id; diff --git a/ProcessLib/SourceTerms/Python/CMakeLists.txt b/ProcessLib/SourceTerms/Python/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..8ecddaea2e59a5715ef26188d8e27f4795415512 --- /dev/null +++ b/ProcessLib/SourceTerms/Python/CMakeLists.txt @@ -0,0 +1,32 @@ +add_library(ProcessLibSourceTermPython + CreatePythonSourceTerm.cpp + CreatePythonSourceTerm.h + PythonSourceTerm.cpp + PythonSourceTerm.h + PythonSourceTermLocalAssembler.h + PythonSourceTermPythonSideInterface.h) +if(BUILD_SHARED_LIBS) + install(TARGETS ProcessLibSourceTermPython + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}) +endif() + +target_compile_definitions(ProcessLibSourceTermPython + PUBLIC OGS_USE_PYTHON) + +target_link_libraries(ProcessLibSourceTermPython + PUBLIC BaseLib MathLib MeshLib NumLib logog + PRIVATE pybind11::pybind11) + +# For the embedded Python module +add_library(ProcessLibSourceTermPythonModule + PythonSourceTermModule.cpp + PythonSourceTermModule.h) +if(BUILD_SHARED_LIBS) + install(TARGETS ProcessLibSourceTermPythonModule + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}) +endif() + +target_link_libraries(ProcessLibSourceTermPythonModule + PUBLIC + ProcessLibSourceTermPython + pybind11::pybind11) diff --git a/ProcessLib/SourceTerms/Python/CreatePythonSourceTerm.cpp b/ProcessLib/SourceTerms/Python/CreatePythonSourceTerm.cpp new file mode 100644 index 0000000000000000000000000000000000000000..3707a3801e30d63d68c7193e29df0cbfbbd77cb4 --- /dev/null +++ b/ProcessLib/SourceTerms/Python/CreatePythonSourceTerm.cpp @@ -0,0 +1,87 @@ +/** + * \copyright + * Copyright (c) 2012-2018, OpenGeoSys Community (http://www.opengeosys.org) + * Distributed under a Modified BSD License. + * See accompanying file LICENSE.txt or + * http://www.opengeosys.org/project/license + * + */ + +#include "CreatePythonSourceTerm.h" + +#include <pybind11/pybind11.h> + +#include "BaseLib/ConfigTree.h" +#include "MeshLib/Mesh.h" +#include "NumLib/DOF/LocalToGlobalIndexMap.h" +#include "ProcessLib/SourceTerms/SourceTerm.h" +#include "PythonSourceTerm.h" + +namespace ProcessLib +{ +std::unique_ptr<SourceTerm> createPythonSourceTerm( + BaseLib::ConfigTree const& config, MeshLib::Mesh const& source_term_mesh, + NumLib::LocalToGlobalIndexMap const& dof_table, + std::size_t const bulk_mesh_id, int const variable_id, + int const component_id, unsigned const integration_order, + unsigned const shapefunction_order, unsigned const global_dim) +{ + DBUG("Constructing PythonSourceTerm from config."); + //! \ogs_file_param{prj__process_variables__process_variable__source_terms__source_term__type} + config.checkConfigParameter("type", "Python"); + + auto const source_term_object = + //! \ogs_file_param{prj__process_variables__process_variable__source_terms__source_term__Python__source_term_object} + config.getConfigParameter<std::string>("source_term_object"); + //! \ogs_file_param{prj__process_variables__process_variable__source_terms__source_term__Python__flush_stdout} + auto const flush_stdout = config.getConfigParameter("flush_stdout", false); + + // Evaluate Python code in scope of main module + pybind11::object scope = + pybind11::module::import("__main__").attr("__dict__"); + + if (!scope.contains(source_term_object)) + OGS_FATAL( + "Function `%s' is not defined in the python script file, or there " + "was no python script file specified.", + source_term_object.c_str()); + + auto* source_term = scope[source_term_object.c_str()] + .cast<ProcessLib::SourceTerms::Python:: + PythonSourceTermPythonSideInterface*>(); + + if (variable_id >= static_cast<int>(dof_table.getNumberOfVariables()) || + component_id >= dof_table.getNumberOfVariableComponents(variable_id)) + { + OGS_FATAL( + "Variable id or component id too high. Actual values: (%d, %d), " + "maximum values: (%d, %d).", + variable_id, component_id, dof_table.getNumberOfVariables(), + dof_table.getNumberOfVariableComponents(variable_id)); + } + + // In case of partitioned mesh the source_term could be empty, i.e. there is + // no source_term condition. +#ifdef USE_PETSC + // This can be extracted to createSourceTerm() but then the config + // parameters are not read and will cause an error. + // TODO (naumov): Add a function to ConfigTree for skipping the tags of the + // subtree and move the code up in createSourceTerm(). + if (source_term_mesh.getDimension() == 0 && + source_term_mesh.getNumberOfNodes() == 0 && + source_term_mesh.getNumberOfElements() == 0) + { + return nullptr; + } +#endif // USE_PETSC + + return std::make_unique<ProcessLib::SourceTerms::Python::PythonSourceTerm>( + dof_table, + ProcessLib::SourceTerms::Python::PythonSourceTermData{ + source_term, dof_table, bulk_mesh_id, + dof_table.getGlobalComponent(variable_id, component_id), + source_term_mesh}, + integration_order, shapefunction_order, global_dim, flush_stdout); +} + +} // namespace ProcessLib diff --git a/ProcessLib/SourceTerms/Python/CreatePythonSourceTerm.h b/ProcessLib/SourceTerms/Python/CreatePythonSourceTerm.h new file mode 100644 index 0000000000000000000000000000000000000000..b7b2d6a4b7b328ba235a38ef7f6e12714b428f1a --- /dev/null +++ b/ProcessLib/SourceTerms/Python/CreatePythonSourceTerm.h @@ -0,0 +1,38 @@ +/** + * \copyright + * Copyright (c) 2012-2018, OpenGeoSys Community (http://www.opengeosys.org) + * Distributed under a Modified BSD License. + * See accompanying file LICENSE.txt or + * http://www.opengeosys.org/project/license + * + */ + +#include <memory> + +namespace BaseLib +{ +class ConfigTree; +} + +namespace MeshLib +{ +class Mesh; +} + +namespace NumLib +{ +class LocalToGlobalIndexMap; +} + +namespace ProcessLib +{ +class SourceTerm; + +std::unique_ptr<SourceTerm> createPythonSourceTerm( + BaseLib::ConfigTree const& config, MeshLib::Mesh const& source_term_mesh, + NumLib::LocalToGlobalIndexMap const& dof_table, + std::size_t const bulk_mesh_id, int const variable_id, + int const component_id, unsigned const integration_order, + unsigned const shapefunction_order, unsigned const global_dim); + +} // namespace ProcessLib diff --git a/ProcessLib/SourceTerms/Python/PythonSourceTerm.cpp b/ProcessLib/SourceTerms/Python/PythonSourceTerm.cpp new file mode 100644 index 0000000000000000000000000000000000000000..733dd183140c9761507a12640b2220274651e9e0 --- /dev/null +++ b/ProcessLib/SourceTerms/Python/PythonSourceTerm.cpp @@ -0,0 +1,99 @@ +/** + * \copyright + * Copyright (c) 2012-2018, OpenGeoSys Community (http://www.opengeosys.org) + * Distributed under a Modified BSD License. + * See accompanying file LICENSE.txt or + * http://www.opengeosys.org/project/license + * + */ + +#include "PythonSourceTerm.h" + +#include <pybind11/pybind11.h> +#include <iostream> + +#include "MeshLib/MeshSearch/NodeSearch.h" +#include "NumLib/DOF/LocalToGlobalIndexMap.h" +#include "ProcessLib/Utils/CreateLocalAssemblers.h" +#include "ProcessLib/Utils/ProcessUtils.h" +#include "PythonSourceTermLocalAssembler.h" + +namespace +{ +//! Optionally flushes the standard output upon creation and destruction. +//! Can be used to improve the debug output readability when printing debug +//! messages both from OGS and from Python. +class FlushStdoutGuard final +{ +public: + //! Optionally flushes C++ stdout before running Python code. + explicit FlushStdoutGuard(bool const flush) : _flush(flush) + { + if (!flush) + return; + + LOGOG_COUT << std::flush; + } + + //! Optionally flushes Python's stdout after running Python code. + ~FlushStdoutGuard() + { + if (!_flush) + return; + + using namespace pybind11::literals; + pybind11::print("end"_a = "", "flush"_a = true); + } + +private: + //! To flush or not to flush. + const bool _flush; +}; +} // anonymous namespace + +namespace ProcessLib +{ +namespace SourceTerms +{ +namespace Python +{ +PythonSourceTerm::PythonSourceTerm( + NumLib::LocalToGlobalIndexMap const& source_term_dof_table, + PythonSourceTermData&& source_term_data, unsigned const integration_order, + unsigned const shapefunction_order, unsigned const global_dim, + bool const flush_stdout) + : SourceTerm(source_term_dof_table), + _source_term_data(std::move(source_term_data)), + _flush_stdout(flush_stdout) +{ + std::vector<MeshLib::Node*> const& source_term_nodes = + _source_term_data.source_term_mesh.getNodes(); + MeshLib::MeshSubset source_term_mesh_subset( + _source_term_data.source_term_mesh, source_term_nodes); + + // Create local DOF table from the source term mesh subset for the given + // variable and component id. + _dof_table_source_term = + _source_term_data.dof_table_bulk.deriveBoundaryConstrainedMap( + std::move(source_term_mesh_subset)); + + createLocalAssemblers<PythonSourceTermLocalAssembler>( + global_dim, _source_term_data.source_term_mesh.getElements(), + *_dof_table_source_term, shapefunction_order, _local_assemblers, + _source_term_data.source_term_mesh.isAxiallySymmetric(), + integration_order, _source_term_data); +} + +void PythonSourceTerm::integrate(const double t, const GlobalVector& x, + GlobalVector& b, GlobalMatrix* Jac) const +{ + FlushStdoutGuard guard(_flush_stdout); + + GlobalExecutor::executeMemberOnDereferenced( + &PythonSourceTermLocalAssemblerInterface::assemble, _local_assemblers, + *_dof_table_source_term, t, x, b, Jac); +} + +} // namespace Python +} // namespace SourceTerms +} // namespace ProcessLib diff --git a/ProcessLib/SourceTerms/Python/PythonSourceTerm.h b/ProcessLib/SourceTerms/Python/PythonSourceTerm.h new file mode 100644 index 0000000000000000000000000000000000000000..9ec77f3952f533ed64171d7a508f735d6256654c --- /dev/null +++ b/ProcessLib/SourceTerms/Python/PythonSourceTerm.h @@ -0,0 +1,79 @@ +/** + * \copyright + * Copyright (c) 2012-2018, OpenGeoSys Community (http://www.opengeosys.org) + * Distributed under a Modified BSD License. + * See accompanying file LICENSE.txt or + * http://www.opengeosys.org/project/license + * + */ + +#pragma once + +#include "ProcessLib/SourceTerms/SourceTerm.h" +#include "PythonSourceTermLocalAssemblerInterface.h" +#include "PythonSourceTermPythonSideInterface.h" + +namespace ProcessLib +{ +class LocalToGlobalIndexMap; +} + +namespace ProcessLib +{ +namespace SourceTerms +{ +namespace Python +{ +//! Groups data used by source terms, in particular by the local assemblers. +struct PythonSourceTermData final +{ + //! Python object computing source term values. + PythonSourceTermPythonSideInterface* source_term_object; + + //! DOF table of the entire domain. + NumLib::LocalToGlobalIndexMap const& dof_table_bulk; + + //! Mesh ID of the entire domain. + std::size_t const bulk_mesh_id; + + //! Global component ID of the (variable, component) to which this source + //! term is applied. + int const global_component_id; + + //! The source term mesh, i.e., the (sub-) domain of this source term. + const MeshLib::Mesh& source_term_mesh; +}; + +//! A source term whose values are computed by a Python script. +class PythonSourceTerm final : public ProcessLib::SourceTerm +{ +public: + explicit PythonSourceTerm( + NumLib::LocalToGlobalIndexMap const& source_term_dof_table, + PythonSourceTermData&& source_term_data, + unsigned const integration_order, unsigned const shapefunction_order, + unsigned const global_dim, bool const flush_stdout); + + void integrate(const double t, GlobalVector const& x, GlobalVector& b, + GlobalMatrix* jac) const override; + +private: + //! Auxiliary data. + PythonSourceTermData _source_term_data; + + //! Local dof table for the boundary mesh. + std::unique_ptr<NumLib::LocalToGlobalIndexMap> _dof_table_source_term; + + //! Local assemblers for all elements of the source term mesh. + std::vector<std::unique_ptr<PythonSourceTermLocalAssemblerInterface>> + _local_assemblers; + + //! Whether or not to flush standard output before and after each call to + //! Python code. Ensures right order of output messages and therefore + //! simplifies debugging. + bool const _flush_stdout; +}; + +} // namespace Python +} // namespace SourceTerms +} // namespace ProcessLib diff --git a/ProcessLib/SourceTerms/Python/PythonSourceTermLocalAssembler.h b/ProcessLib/SourceTerms/Python/PythonSourceTermLocalAssembler.h new file mode 100644 index 0000000000000000000000000000000000000000..9a8039ad6ce99ca501a78ba53069e4eb54b05da3 --- /dev/null +++ b/ProcessLib/SourceTerms/Python/PythonSourceTermLocalAssembler.h @@ -0,0 +1,234 @@ +/** + * \copyright + * Copyright (c) 2012-2018, OpenGeoSys Community (http://www.opengeosys.org) + * Distributed under a Modified BSD License. + * See accompanying file LICENSE.txt or + * http://www.opengeosys.org/project/license + * + */ + +#pragma once + +#include "PythonSourceTerm.h" + +#include "MathLib/LinAlg/Eigen/EigenMapTools.h" +#include "NumLib/DOF/DOFTableUtil.h" +#include "NumLib/Fem/FiniteElement/TemplateIsoparametric.h" +#include "ProcessLib/LocalAssemblerTraits.h" +#include "ProcessLib/Utils/InitShapeMatrices.h" +#include "PythonSourceTermLocalAssemblerInterface.h" +#include "PythonSourceTermPythonSideInterface.h" + +namespace ProcessLib +{ +namespace SourceTerms +{ +namespace Python +{ +//! Can be thrown to indicate that a member function is not overridden in a +//! derived class (in particular, if a Python class inherits from a C++ class). +struct MethodNotOverriddenInDerivedClassException +{ +}; + +template <typename NodalRowVectorType> +struct IntegrationPointData final +{ + IntegrationPointData(NodalRowVectorType const& N_, + double const& integration_weight_) + : N(N_), integration_weight(integration_weight_) + { + } + + NodalRowVectorType const N; + double const integration_weight; + + EIGEN_MAKE_ALIGNED_OPERATOR_NEW; +}; + +template <typename ShapeFunction, typename IntegrationMethod, + unsigned GlobalDim> +class PythonSourceTermLocalAssembler final + : public PythonSourceTermLocalAssemblerInterface +{ +public: + using ShapeMatricesType = ShapeMatrixPolicyType<ShapeFunction, GlobalDim>; + using NodalRowVectorType = typename ShapeMatricesType::NodalRowVectorType; + using NodalMatrixType = typename ShapeMatricesType::NodalMatrixType; + + explicit PythonSourceTermLocalAssembler( + MeshLib::Element const& e, + std::size_t const /*local_matrix_size*/, + bool is_axially_symmetric, + unsigned const integration_order, + PythonSourceTermData const& data) + : _data(data), + _element(e), + _is_axially_symmetric(is_axially_symmetric), + _integration_method(integration_order) + { + unsigned const n_integration_points = + _integration_method.getNumberOfPoints(); + + auto const shape_matrices = + initShapeMatrices<ShapeFunction, ShapeMatricesType, + IntegrationMethod, GlobalDim>( + _element, _is_axially_symmetric, _integration_method); + + for (unsigned ip = 0; ip < n_integration_points; ip++) + { + _ip_data.emplace_back( + shape_matrices[ip].N, + _integration_method.getWeightedPoint(ip).getWeight() * + shape_matrices[ip].integralMeasure * + shape_matrices[ip].detJ); + } + } + + void assemble(std::size_t const source_term_element_id, + NumLib::LocalToGlobalIndexMap const& dof_table_source_term, + double const t, const GlobalVector& x, GlobalVector& b, + GlobalMatrix* Jac) override + { + using ShapeMatricesType = + ShapeMatrixPolicyType<ShapeFunction, GlobalDim>; + using FemType = + NumLib::TemplateIsoparametric<ShapeFunction, ShapeMatricesType>; + FemType fe(*static_cast<const typename ShapeFunction::MeshElement*>( + &_element)); + + unsigned const num_integration_points = + _integration_method.getNumberOfPoints(); + auto const num_var = _data.dof_table_bulk.getNumberOfVariables(); + auto const num_nodes = ShapeFunction::NPOINTS; + auto const num_comp_total = + _data.dof_table_bulk.getNumberOfComponents(); + + auto const& bulk_node_ids_map = + *_data.source_term_mesh.getProperties() + .template getPropertyVector<std::size_t>("bulk_node_ids"); + + // gather primary variables + typename ShapeMatricesType::template MatrixType<ShapeFunction::NPOINTS, + Eigen::Dynamic> + primary_variables_mat(num_nodes, num_comp_total); + for (int var = 0; var < num_var; ++var) + { + auto const num_comp = + _data.dof_table_bulk.getNumberOfVariableComponents(var); + for (int comp = 0; comp < num_comp; ++comp) + { + auto const global_component = + dof_table_source_term.getGlobalComponent(var, comp); + + for (unsigned element_node_id = 0; element_node_id < num_nodes; + ++element_node_id) + { + auto const* const node = _element.getNode(element_node_id); + auto const boundary_node_id = node->getID(); + auto const bulk_node_id = + bulk_node_ids_map[boundary_node_id]; + MeshLib::Location loc{_data.bulk_mesh_id, + MeshLib::MeshItemType::Node, + bulk_node_id}; + auto const dof_idx = + _data.dof_table_bulk.getGlobalIndex(loc, var, comp); + if (dof_idx == NumLib::MeshComponentMap::nop) + { + // TODO extend Python BC to mixed FEM ansatz functions + OGS_FATAL( + "No d.o.f. found for (node=%d, var=%d, comp=%d). " + "That might be due to the use of mixed FEM ansatz " + "functions, which is currently not supported by " + "the implementation of Python BCs. That excludes, " + "e.g., the HM process.", + bulk_node_id, var, comp); + } + primary_variables_mat(element_node_id, global_component) = + x[dof_idx]; + } + } + } + + NodalRowVectorType local_rhs = Eigen::VectorXd::Zero(num_nodes); + NodalMatrixType local_Jac = + Eigen::MatrixXd::Zero(num_nodes, num_nodes * num_comp_total); + std::vector<double> prim_vars_data(num_comp_total); + auto prim_vars = MathLib::toVector(prim_vars_data); + + for (unsigned ip = 0; ip < num_integration_points; ip++) + { + auto const& ip_data = _ip_data[ip]; + auto const& N = ip_data.N; + auto const& coords = fe.interpolateCoordinates(N); + // Assumption: all primary variables have same shape functions. + prim_vars = N * primary_variables_mat; + + auto const& w = ip_data.integration_weight; + auto const flux_dflux = + _data.source_term_object->getFlux(t, coords, prim_vars_data); + auto const& flux = flux_dflux.first; + auto const& dflux = flux_dflux.second; + local_rhs.noalias() += N * (flux * w); + + if (static_cast<int>(dflux.size()) != num_comp_total) + { + // This strict check is technically mandatory only if a + // Jacobian is assembled. However, it is done as a + // consistency check also for cases without Jacobian + // assembly. + OGS_FATAL( + "The Python source term must return the derivative of " + "the flux w.r.t. each primary variable. %d components " + "expected. %d components returned from Python.", + num_comp_total, dflux.size()); + } + + if (Jac) + { + for (int comp = 0; comp < num_comp_total; ++comp) + { + auto const top = 0; + auto const left = comp * num_nodes; + auto const width = num_nodes; + auto const height = num_nodes; + // The assignement -= takes into account the sign convention + // of 1st-order in time ODE systems in OpenGeoSys. + local_Jac.block(top, left, width, height).noalias() -= + ip_data.N.transpose() * (dflux[comp] * w) * N; + } + } + } + + auto const& indices_specific_component = + dof_table_source_term(source_term_element_id, + _data.global_component_id) + .rows; + b.add(indices_specific_component, local_rhs); + + if (Jac) + { + // only assemble a block of the Jacobian, not the whole local matrix + auto const indices_all_components = NumLib::getIndices( + source_term_element_id, dof_table_source_term); + MathLib::RowColumnIndices<GlobalIndexType> rci{ + indices_specific_component, indices_all_components}; + Jac->add(rci, local_Jac); + } + } + +private: + PythonSourceTermData const& _data; + MeshLib::Element const& _element; + bool _is_axially_symmetric; + + IntegrationMethod const _integration_method; + std::vector< + IntegrationPointData<NodalRowVectorType>, + Eigen::aligned_allocator<IntegrationPointData<NodalRowVectorType>>> + _ip_data; +}; + +} // namespace Python +} // namespace SourceTerms +} // namespace ProcessLib diff --git a/ProcessLib/SourceTerms/Python/PythonSourceTermLocalAssemblerInterface.h b/ProcessLib/SourceTerms/Python/PythonSourceTermLocalAssemblerInterface.h new file mode 100644 index 0000000000000000000000000000000000000000..f970bb39e4c799e06f3809705fa28fd7184d44ab --- /dev/null +++ b/ProcessLib/SourceTerms/Python/PythonSourceTermLocalAssemblerInterface.h @@ -0,0 +1,34 @@ + +/** + * \copyright + * Copyright (c) 2012-2018, OpenGeoSys Community (http://www.opengeosys.org) + * Distributed under a Modified BSD License. + * See accompanying file LICENSE.txt or + * http://www.opengeosys.org/project/license + * + */ + +#pragma once + +namespace ProcessLib +{ +namespace SourceTerms +{ +namespace Python +{ + +class PythonSourceTermLocalAssemblerInterface +{ +public: + virtual void assemble( + std::size_t const source_term_element_id, + NumLib::LocalToGlobalIndexMap const& source_term_dof_table, + double const t, const GlobalVector& x, GlobalVector& b, + GlobalMatrix* Jac) = 0; + + virtual ~PythonSourceTermLocalAssemblerInterface() = default; +}; + +} // namespace Python +} // namespace SourceTerms +} // namespace ProcessLib diff --git a/ProcessLib/SourceTerms/Python/PythonSourceTermModule.cpp b/ProcessLib/SourceTerms/Python/PythonSourceTermModule.cpp new file mode 100644 index 0000000000000000000000000000000000000000..a556961c2d4a2c6882ae5a9d3a52d1e1f0c08093 --- /dev/null +++ b/ProcessLib/SourceTerms/Python/PythonSourceTermModule.cpp @@ -0,0 +1,57 @@ +/** + * \copyright + * Copyright (c) 2012-2018, OpenGeoSys Community (http://www.opengeosys.org) + * Distributed under a Modified BSD License. + * See accompanying file LICENSE.txt or + * http://www.opengeosys.org/project/license + * + */ + +#include "PythonSourceTermModule.h" + +#include <pybind11/pybind11.h> +#include <pybind11/stl.h> +#include "PythonSourceTermPythonSideInterface.h" + +namespace ProcessLib +{ +namespace SourceTerms +{ +namespace Python +{ +//! Trampoline class allowing methods of class +//! PythonSourceTermPythonSideInterface to be overridden on the Python +//! side. Cf. https://pybind11.readthedocs.io/en/stable/advanced/classes.html +class PythonSourceTermPythonSideInterfaceTrampoline + : public PythonSourceTermPythonSideInterface +{ +public: + using PythonSourceTermPythonSideInterface:: + PythonSourceTermPythonSideInterface; + + std::pair<double, std::vector<double>> getFlux( + double t, std::array<double, 3> const& x, + std::vector<double> const& primary_variables) const override + { + using Ret = std::pair<double, std::vector<double>>; + PYBIND11_OVERLOAD_PURE(Ret, PythonSourceTermPythonSideInterface, + getFlux, t, x, primary_variables); + } +}; + +void pythonBindSourceTerm(pybind11::module& m) +{ + namespace py = pybind11; + + py::class_<PythonSourceTermPythonSideInterface, + PythonSourceTermPythonSideInterfaceTrampoline> + pybc(m, "SourceTerm"); + + pybc.def(py::init()); + + pybc.def("getFlux", &PythonSourceTermPythonSideInterface::getFlux); +} + +} // namespace Python +} // namespace SourceTerms +} // namespace ProcessLib diff --git a/ProcessLib/SourceTerms/Python/PythonSourceTermModule.h b/ProcessLib/SourceTerms/Python/PythonSourceTermModule.h new file mode 100644 index 0000000000000000000000000000000000000000..47b279fc0a34f28c3a400d44c488a6372d36b932 --- /dev/null +++ b/ProcessLib/SourceTerms/Python/PythonSourceTermModule.h @@ -0,0 +1,25 @@ +/** + * \copyright + * Copyright (c) 2012-2018, OpenGeoSys Community (http://www.opengeosys.org) + * Distributed under a Modified BSD License. + * See accompanying file LICENSE.txt or + * http://www.opengeosys.org/project/license + * + */ + +#pragma once + +#include <pybind11/pybind11.h> +#include "BaseLib/ExportSymbol.h" + +namespace ProcessLib +{ +namespace SourceTerms +{ +namespace Python +{ +//! Creates Python bindings for the Python source term class. +OGS_EXPORT_SYMBOL void pythonBindSourceTerm(pybind11::module& m); +} // namespace Python +} // namespace SourceTerms +} // namespace ProcessLib diff --git a/ProcessLib/SourceTerms/Python/PythonSourceTermPythonSideInterface.h b/ProcessLib/SourceTerms/Python/PythonSourceTermPythonSideInterface.h new file mode 100644 index 0000000000000000000000000000000000000000..3314fb37d0a2aec4f5c9474205e691a6084af236 --- /dev/null +++ b/ProcessLib/SourceTerms/Python/PythonSourceTermPythonSideInterface.h @@ -0,0 +1,39 @@ +/** + * \copyright + * Copyright (c) 2012-2018, OpenGeoSys Community (http://www.opengeosys.org) + * Distributed under a Modified BSD License. + * See accompanying file LICENSE.txt or + * http://www.opengeosys.org/project/license + * + */ + +#pragma once + +namespace ProcessLib +{ +namespace SourceTerms +{ +namespace Python +{ +//! Base class for source terms. +//! This class will get Python bindings and is intended to be to be derived in +//! Python. +class PythonSourceTermPythonSideInterface +{ +public: + /*! + * Computes the flux for the provided arguments (time, position of the node, + * primary variables at the node). + * + * \return flux Flux of the source term at that node and derivative of the + * flux w.r.t. all primary variables. + */ + virtual std::pair<double, std::vector<double>> getFlux( + double /*t*/, std::array<double, 3> const& /*x*/, + std::vector<double> const& /*primary_variables*/) const = 0; + + virtual ~PythonSourceTermPythonSideInterface() = default; +}; +} // namespace Python +} // namespace SourceTerms +} // namespace ProcessLib diff --git a/ProcessLib/SourceTerms/SourceTerm.h b/ProcessLib/SourceTerms/SourceTerm.h index bbbd878a3e9a4e509a8954fc7909393293f20f30..de87c32c8431ef95ef70a44a6345fe5f86c1e9da 100644 --- a/ProcessLib/SourceTerms/SourceTerm.h +++ b/ProcessLib/SourceTerms/SourceTerm.h @@ -23,7 +23,8 @@ public: { } - virtual void integrate(const double t, GlobalVector& b) const = 0; + virtual void integrate(const double t, GlobalVector const& x, + GlobalVector& b, GlobalMatrix* jac) const = 0; virtual ~SourceTerm() = default; diff --git a/ProcessLib/SourceTerms/SourceTermCollection.cpp b/ProcessLib/SourceTerms/SourceTermCollection.cpp index 9636eae275a4460474d486082e03485bd099e18b..82bac27aeb295609eeee6ea49f52f8c365bec9eb 100644 --- a/ProcessLib/SourceTerms/SourceTermCollection.cpp +++ b/ProcessLib/SourceTerms/SourceTermCollection.cpp @@ -29,10 +29,11 @@ void SourceTermCollection::addSourceTermsForProcessVariables( } } -void SourceTermCollection::integrate(const double t, GlobalVector& b) const +void SourceTermCollection::integrate(const double t, GlobalVector const& x, + GlobalVector& b, GlobalMatrix* jac) const { for (auto const& st : _source_terms) - st->integrate(t, b); + st->integrate(t, x, b, jac); } } diff --git a/ProcessLib/SourceTerms/SourceTermCollection.h b/ProcessLib/SourceTerms/SourceTermCollection.h index 77f8646c23abb1150f08956522373d991cd17e08..c1c26160aef2317699d751e40ee8abe778d628d4 100644 --- a/ProcessLib/SourceTerms/SourceTermCollection.h +++ b/ProcessLib/SourceTerms/SourceTermCollection.h @@ -23,7 +23,8 @@ public: { } - void integrate(const double t, GlobalVector& b) const; + void integrate(const double t, GlobalVector const& x, GlobalVector& b, + GlobalMatrix* jac) const; void addSourceTermsForProcessVariables( std::vector<std::reference_wrapper<ProcessVariable>> const& diff --git a/ProcessLib/SourceTerms/VolumetricSourceTerm.cpp b/ProcessLib/SourceTerms/VolumetricSourceTerm.cpp index 166405fa84ce45a1c0ab192e58406847675f7359..c05f41bdd3d342efbc027c66729ccd0d776ecda9 100644 --- a/ProcessLib/SourceTerms/VolumetricSourceTerm.cpp +++ b/ProcessLib/SourceTerms/VolumetricSourceTerm.cpp @@ -47,8 +47,9 @@ VolumetricSourceTerm::VolumetricSourceTerm( _volumetric_source_term); } -void VolumetricSourceTerm::integrate(const double t, - GlobalVector& b) const +void VolumetricSourceTerm::integrate(const double t, GlobalVector const& /*x*/, + GlobalVector& b, + GlobalMatrix* /*jac*/) const { DBUG("Assemble VolumetricSourceTerm."); diff --git a/ProcessLib/SourceTerms/VolumetricSourceTerm.h b/ProcessLib/SourceTerms/VolumetricSourceTerm.h index 9f26f93d227613cccd89fa7f23632c8199050dcc..f3d785f310199d87d2cc086e72acaa07790089a7 100644 --- a/ProcessLib/SourceTerms/VolumetricSourceTerm.h +++ b/ProcessLib/SourceTerms/VolumetricSourceTerm.h @@ -27,7 +27,8 @@ public: int const variable_id, int const component_id, Parameter<double> const& volumetric_source_term); - void integrate(const double t, GlobalVector& b) const; + void integrate(const double t, GlobalVector const& x, GlobalVector& b, + GlobalMatrix* jac) const; private: Parameter<double> const& _volumetric_source_term; diff --git a/Tests/Data/Elliptic/square_1x1_GroundWaterFlow_Python/sin_x_sin_y_source_term.py b/Tests/Data/Elliptic/square_1x1_GroundWaterFlow_Python/sin_x_sin_y_source_term.py new file mode 100644 index 0000000000000000000000000000000000000000..60766042860fbb28f926e329e32f2743c299fb99 --- /dev/null +++ b/Tests/Data/Elliptic/square_1x1_GroundWaterFlow_Python/sin_x_sin_y_source_term.py @@ -0,0 +1,24 @@ + +import OpenGeoSys +from math import pi, sin + +a = 2.0*pi +b = 2.0*pi + +def solution(x, y): + return sin(a*x-pi/2.0) * sin(b*y-pi/2.0) + +# - laplace(solution) = source term +def laplace_solution(x, y): + return a*a * sin(a*x-pi/2.0) * sin(b*y-pi/2.0) + b*b * sin(a*x-pi/2.0) * sin(b*y-pi/2.0) + +# source term for the benchmark +class SinXSinYSourceTerm(OpenGeoSys.SourceTerm): + def getFlux(self, t, coords, primary_vars): + x, y, z = coords + value = laplace_solution(x,y) + Jac = [ 0.0 ] + return (value, Jac) + +# instantiate source term object referenced in OpenGeoSys' prj file +sinx_siny_source_term = SinXSinYSourceTerm() diff --git a/Tests/Data/Elliptic/square_1x1_GroundWaterFlow_Python/square_1e3_poisson_sin_x_sin_y.prj b/Tests/Data/Elliptic/square_1x1_GroundWaterFlow_Python/square_1e3_poisson_sin_x_sin_y.prj new file mode 100644 index 0000000000000000000000000000000000000000..cf71bd3ad3a209e9949071b12fddd2dc42e4513e --- /dev/null +++ b/Tests/Data/Elliptic/square_1x1_GroundWaterFlow_Python/square_1e3_poisson_sin_x_sin_y.prj @@ -0,0 +1,132 @@ +<?xml version="1.0" encoding="ISO-8859-1"?> +<OpenGeoSysProject> + <meshes> + <mesh>square_1x1_quad_1e3.vtu</mesh> + <mesh>square_1x1_quad_1e3_geometry_ll.vtu</mesh> + <mesh>square_1x1_quad_1e3_geometry_lr.vtu</mesh> + <mesh>square_1x1_quad_1e3_geometry_ul.vtu</mesh> + <mesh>square_1x1_quad_1e3_geometry_ur.vtu</mesh> + <mesh>square_1x1_quad_1e3_entire_domain.vtu</mesh> + </meshes> + <python_script>sin_x_sin_y_source_term.py</python_script> + <processes> + <process> + <name>GW23</name> + <type>GROUNDWATER_FLOW</type> + <integration_order>2</integration_order> + <hydraulic_conductivity>K</hydraulic_conductivity> + <process_variables> + <process_variable>pressure</process_variable> + </process_variables> + <secondary_variables> + <secondary_variable type="static" internal_name="darcy_velocity" output_name="v"/> + </secondary_variables> + </process> + </processes> + <time_loop> + <processes> + <process ref="GW23"> + <nonlinear_solver>basic_picard</nonlinear_solver> + <convergence_criterion> + <type>DeltaX</type> + <norm_type>NORM2</norm_type> + <abstol>1.e-6</abstol> + </convergence_criterion> + <time_discretization> + <type>BackwardEuler</type> + </time_discretization> + <output> + <variables> + <variable> pressure </variable> + <variable> v </variable> + </variables> + </output> + <time_stepping> + <type>SingleStep</type> + </time_stepping> + </process> + </processes> + <output> + <type>VTK</type> + <prefix>square_1e3_volumetricsourceterm</prefix> + </output> + </time_loop> + <nonlinear_solvers> + <nonlinear_solver> + <name>basic_picard</name> + <type>Picard</type> + <max_iter>10</max_iter> + <linear_solver>general_linear_solver</linear_solver> + </nonlinear_solver> + </nonlinear_solvers> + <linear_solvers> + <linear_solver> + <name>general_linear_solver</name> + <lis>-i cg -p jacobi -tol 1e-16 -maxiter 10000</lis> + <eigen> + <solver_type>CG</solver_type> + <precon_type>DIAGONAL</precon_type> + <max_iteration_step>10000</max_iteration_step> + <error_tolerance>1e-16</error_tolerance> + </eigen> + <petsc> + <prefix>gw</prefix> + <parameters>-gw_ksp_type cg -gw_pc_type bjacobi -gw_ksp_rtol 1e-16 -gw_ksp_max_it 10000</parameters> + </petsc> + </linear_solver> + </linear_solvers> + <parameters> + <parameter> + <name>K</name> + <type>Constant</type> + <value>1</value> + </parameter> + <parameter> + <name>p0</name> + <type>Constant</type> + <value>0</value> + </parameter> + <parameter> + <name>pressure_edge_points</name> + <type>Constant</type> + <value>1</value> + </parameter> + </parameters> + <process_variables> + <process_variable> + <name>pressure</name> + <components>1</components> + <order>1</order> + <initial_condition>p0</initial_condition> + <boundary_conditions> + <boundary_condition> + <mesh>square_1x1_quad_1e3_geometry_ll</mesh> + <type>Dirichlet</type> + <parameter>pressure_edge_points</parameter> + </boundary_condition> + <boundary_condition> + <mesh>square_1x1_quad_1e3_geometry_lr</mesh> + <type>Dirichlet</type> + <parameter>pressure_edge_points</parameter> + </boundary_condition> + <boundary_condition> + <mesh>square_1x1_quad_1e3_geometry_ul</mesh> + <type>Dirichlet</type> + <parameter>pressure_edge_points</parameter> + </boundary_condition> + <boundary_condition> + <mesh>square_1x1_quad_1e3_geometry_ur</mesh> + <type>Dirichlet</type> + <parameter>pressure_edge_points</parameter> + </boundary_condition> + </boundary_conditions> + <source_terms> + <source_term> + <mesh>square_1x1_quad_1e3_entire_domain</mesh> + <type>Python</type> + <source_term_object>sinx_siny_source_term</source_term_object> + </source_term> + </source_terms> + </process_variable> + </process_variables> +</OpenGeoSysProject> diff --git a/Tests/Data/Elliptic/square_1x1_GroundWaterFlow_Python/square_1e5_poisson_sin_x_sin_y.prj b/Tests/Data/Elliptic/square_1x1_GroundWaterFlow_Python/square_1e5_poisson_sin_x_sin_y.prj new file mode 100644 index 0000000000000000000000000000000000000000..093c642323e109768c756eb2c65764150b268cb0 --- /dev/null +++ b/Tests/Data/Elliptic/square_1x1_GroundWaterFlow_Python/square_1e5_poisson_sin_x_sin_y.prj @@ -0,0 +1,132 @@ +<?xml version="1.0" encoding="ISO-8859-1"?> +<OpenGeoSysProject> + <meshes> + <mesh>square_1x1_quad_1e5.vtu</mesh> + <mesh>square_1x1_quad_1e5_geometry_ll.vtu</mesh> + <mesh>square_1x1_quad_1e5_geometry_lr.vtu</mesh> + <mesh>square_1x1_quad_1e5_geometry_ul.vtu</mesh> + <mesh>square_1x1_quad_1e5_geometry_ur.vtu</mesh> + <mesh>square_1x1_quad_1e5_entire_domain.vtu</mesh> + </meshes> + <python_script>sin_x_sin_y_source_term.py</python_script> + <processes> + <process> + <name>GW23</name> + <type>GROUNDWATER_FLOW</type> + <integration_order>2</integration_order> + <hydraulic_conductivity>K</hydraulic_conductivity> + <process_variables> + <process_variable>pressure</process_variable> + </process_variables> + <secondary_variables> + <secondary_variable type="static" internal_name="darcy_velocity" output_name="v"/> + </secondary_variables> + </process> + </processes> + <time_loop> + <processes> + <process ref="GW23"> + <nonlinear_solver>basic_picard</nonlinear_solver> + <convergence_criterion> + <type>DeltaX</type> + <norm_type>NORM2</norm_type> + <abstol>1.e-6</abstol> + </convergence_criterion> + <time_discretization> + <type>BackwardEuler</type> + </time_discretization> + <output> + <variables> + <variable> pressure </variable> + <variable> v </variable> + </variables> + </output> + <time_stepping> + <type>SingleStep</type> + </time_stepping> + </process> + </processes> + <output> + <type>VTK</type> + <prefix>square_1e5_volumetricsourceterm</prefix> + </output> + </time_loop> + <nonlinear_solvers> + <nonlinear_solver> + <name>basic_picard</name> + <type>Picard</type> + <max_iter>10</max_iter> + <linear_solver>general_linear_solver</linear_solver> + </nonlinear_solver> + </nonlinear_solvers> + <linear_solvers> + <linear_solver> + <name>general_linear_solver</name> + <lis>-i cg -p jacobi -tol 1e-16 -maxiter 10000</lis> + <eigen> + <solver_type>CG</solver_type> + <precon_type>DIAGONAL</precon_type> + <max_iteration_step>10000</max_iteration_step> + <error_tolerance>1e-16</error_tolerance> + </eigen> + <petsc> + <prefix>gw</prefix> + <parameters>-gw_ksp_type cg -gw_pc_type bjacobi -gw_ksp_rtol 1e-16 -gw_ksp_max_it 10000</parameters> + </petsc> + </linear_solver> + </linear_solvers> + <parameters> + <parameter> + <name>K</name> + <type>Constant</type> + <value>1</value> + </parameter> + <parameter> + <name>p0</name> + <type>Constant</type> + <value>0</value> + </parameter> + <parameter> + <name>pressure_edge_points</name> + <type>Constant</type> + <value>1</value> + </parameter> + </parameters> + <process_variables> + <process_variable> + <name>pressure</name> + <components>1</components> + <order>1</order> + <initial_condition>p0</initial_condition> + <boundary_conditions> + <boundary_condition> + <mesh>square_1x1_quad_1e5_geometry_ll</mesh> + <type>Dirichlet</type> + <parameter>pressure_edge_points</parameter> + </boundary_condition> + <boundary_condition> + <mesh>square_1x1_quad_1e5_geometry_lr</mesh> + <type>Dirichlet</type> + <parameter>pressure_edge_points</parameter> + </boundary_condition> + <boundary_condition> + <mesh>square_1x1_quad_1e5_geometry_ul</mesh> + <type>Dirichlet</type> + <parameter>pressure_edge_points</parameter> + </boundary_condition> + <boundary_condition> + <mesh>square_1x1_quad_1e5_geometry_ur</mesh> + <type>Dirichlet</type> + <parameter>pressure_edge_points</parameter> + </boundary_condition> + </boundary_conditions> + <source_terms> + <source_term> + <mesh>square_1x1_quad_1e5_entire_domain</mesh> + <type>Python</type> + <source_term_object>sinx_siny_source_term</source_term_object> + </source_term> + </source_terms> + </process_variable> + </process_variables> +</OpenGeoSysProject> diff --git a/Tests/Data/Elliptic/square_1x1_GroundWaterFlow_Python/square_1x1.gml b/Tests/Data/Elliptic/square_1x1_GroundWaterFlow_Python/square_1x1.gml index 3e5bb2a80a8f6bbdc4b7776b696d7a6af0d43b51..c1cc186de8a9d70ba75dd3061a819ca6ca3dfe76 100644 --- a/Tests/Data/Elliptic/square_1x1_GroundWaterFlow_Python/square_1x1.gml +++ b/Tests/Data/Elliptic/square_1x1_GroundWaterFlow_Python/square_1x1.gml @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a3f52cd5a8058d967f8ead873e1afa323af39266e538bcd2c35af08683a81dad -size 1083 +oid sha256:2f1dac68820a46835f6e89762fede4d9b6799bd125d18c624859639b95830808 +size 1123 diff --git a/Tests/Data/Elliptic/square_1x1_GroundWaterFlow_Python/square_1x1_quad_1e3.vtu b/Tests/Data/Elliptic/square_1x1_GroundWaterFlow_Python/square_1x1_quad_1e3.vtu index b56982101f6be9ed1cb17945723023e95d9b9f98..663c9c55ab81712cadb341c8c0ed292e06061245 100644 --- a/Tests/Data/Elliptic/square_1x1_GroundWaterFlow_Python/square_1x1_quad_1e3.vtu +++ b/Tests/Data/Elliptic/square_1x1_GroundWaterFlow_Python/square_1x1_quad_1e3.vtu @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3653db864dd917e8d5be84d68ee2de13ba3485b1ef23de4e9894fb6749a1aa11 -size 25692 +oid sha256:d3ef47420d7071bfe1a8194048ab0de7b1ab53018814e08235a5c063c95d7609 +size 43824 diff --git a/Tests/Data/Elliptic/square_1x1_GroundWaterFlow_Python/square_1x1_quad_1e3_entire_domain.vtu b/Tests/Data/Elliptic/square_1x1_GroundWaterFlow_Python/square_1x1_quad_1e3_entire_domain.vtu new file mode 100644 index 0000000000000000000000000000000000000000..982a4503f4a652f06d74371c1041b5deced18647 --- /dev/null +++ b/Tests/Data/Elliptic/square_1x1_GroundWaterFlow_Python/square_1x1_quad_1e3_entire_domain.vtu @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5aa7d5132ac307d2bfb1bbe8ce737c6de309a3f494ad231f5eb512d7d4083d42 +size 155464 diff --git a/Tests/Data/Elliptic/square_1x1_GroundWaterFlow_Python/square_1x1_quad_1e3_geometry_ll.vtu b/Tests/Data/Elliptic/square_1x1_GroundWaterFlow_Python/square_1x1_quad_1e3_geometry_ll.vtu new file mode 100644 index 0000000000000000000000000000000000000000..2acfaf9fb2956770259901a765efd8d9d4938718 --- /dev/null +++ b/Tests/Data/Elliptic/square_1x1_GroundWaterFlow_Python/square_1x1_quad_1e3_geometry_ll.vtu @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:612e6cc7d6f49da22c69cf3b4987541f2c40c77e3abe14db8af4b20f8e966742 +size 1253 diff --git a/Tests/Data/Elliptic/square_1x1_GroundWaterFlow_Python/square_1x1_quad_1e3_geometry_lr.vtu b/Tests/Data/Elliptic/square_1x1_GroundWaterFlow_Python/square_1x1_quad_1e3_geometry_lr.vtu new file mode 100644 index 0000000000000000000000000000000000000000..20373ccf7cbb79c1ceb4ff9ba5040dcdd0aea503 --- /dev/null +++ b/Tests/Data/Elliptic/square_1x1_GroundWaterFlow_Python/square_1x1_quad_1e3_geometry_lr.vtu @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:83af233d08ebfbe5a8f987650f181161ae524fd7279f296d8bcd7aed131d9dd4 +size 1265 diff --git a/Tests/Data/Elliptic/square_1x1_GroundWaterFlow_Python/square_1x1_quad_1e3_geometry_ul.vtu b/Tests/Data/Elliptic/square_1x1_GroundWaterFlow_Python/square_1x1_quad_1e3_geometry_ul.vtu new file mode 100644 index 0000000000000000000000000000000000000000..e45a14869950ad47325665d931c0f273fb083add --- /dev/null +++ b/Tests/Data/Elliptic/square_1x1_GroundWaterFlow_Python/square_1x1_quad_1e3_geometry_ul.vtu @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9f09b957a59823c990360b50ed328f27390f8876a950987b75b31746ae748132 +size 1257 diff --git a/Tests/Data/Elliptic/square_1x1_GroundWaterFlow_Python/square_1x1_quad_1e3_geometry_ur.vtu b/Tests/Data/Elliptic/square_1x1_GroundWaterFlow_Python/square_1x1_quad_1e3_geometry_ur.vtu new file mode 100644 index 0000000000000000000000000000000000000000..b79c7a7a1cffbf6cf565718ddcca94f52e11893c --- /dev/null +++ b/Tests/Data/Elliptic/square_1x1_GroundWaterFlow_Python/square_1x1_quad_1e3_geometry_ur.vtu @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:23183aa05576bac8c4e2a6255991c6ac69b4fc1fa32a7038ac60ff0dc7e2fb46 +size 1287 diff --git a/Tests/Data/Elliptic/square_1x1_GroundWaterFlow_Python/square_1x1_quad_1e5.vtu b/Tests/Data/Elliptic/square_1x1_GroundWaterFlow_Python/square_1x1_quad_1e5.vtu new file mode 100644 index 0000000000000000000000000000000000000000..a64c678a9b46fb204c2e9602e4df16d627dec94b --- /dev/null +++ b/Tests/Data/Elliptic/square_1x1_GroundWaterFlow_Python/square_1x1_quad_1e5.vtu @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ff1b3c1c89ed84a64f06112726f5653d0f5c74c2b646f58c8424bc5ba7d0ab99 +size 5678625 diff --git a/Tests/Data/Elliptic/square_1x1_GroundWaterFlow_Python/square_1x1_quad_1e5_entire_domain.vtu b/Tests/Data/Elliptic/square_1x1_GroundWaterFlow_Python/square_1x1_quad_1e5_entire_domain.vtu new file mode 100644 index 0000000000000000000000000000000000000000..ca0d8d8fb8c30aef64adb7da27280589615c13d4 --- /dev/null +++ b/Tests/Data/Elliptic/square_1x1_GroundWaterFlow_Python/square_1x1_quad_1e5_entire_domain.vtu @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9c56618c5c8e4172801fead89bc46fdaa008be871992ac4427b213a24ef53fa3 +size 13489433 diff --git a/Tests/Data/Elliptic/square_1x1_GroundWaterFlow_Python/square_1x1_quad_1e5_geometry_ll.vtu b/Tests/Data/Elliptic/square_1x1_GroundWaterFlow_Python/square_1x1_quad_1e5_geometry_ll.vtu new file mode 100644 index 0000000000000000000000000000000000000000..2acfaf9fb2956770259901a765efd8d9d4938718 --- /dev/null +++ b/Tests/Data/Elliptic/square_1x1_GroundWaterFlow_Python/square_1x1_quad_1e5_geometry_ll.vtu @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:612e6cc7d6f49da22c69cf3b4987541f2c40c77e3abe14db8af4b20f8e966742 +size 1253 diff --git a/Tests/Data/Elliptic/square_1x1_GroundWaterFlow_Python/square_1x1_quad_1e5_geometry_lr.vtu b/Tests/Data/Elliptic/square_1x1_GroundWaterFlow_Python/square_1x1_quad_1e5_geometry_lr.vtu new file mode 100644 index 0000000000000000000000000000000000000000..3088e65f6a947ebc803304244610f234603f71f8 --- /dev/null +++ b/Tests/Data/Elliptic/square_1x1_GroundWaterFlow_Python/square_1x1_quad_1e5_geometry_lr.vtu @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a1646c78d38f7c6cbbed4b9a477baba2bcf254db55db52ae0d63188f77c5bd39 +size 1273 diff --git a/Tests/Data/Elliptic/square_1x1_GroundWaterFlow_Python/square_1x1_quad_1e5_geometry_ul.vtu b/Tests/Data/Elliptic/square_1x1_GroundWaterFlow_Python/square_1x1_quad_1e5_geometry_ul.vtu new file mode 100644 index 0000000000000000000000000000000000000000..598e4dcf0c1a8698950d8d68e6bf1582405a6412 --- /dev/null +++ b/Tests/Data/Elliptic/square_1x1_GroundWaterFlow_Python/square_1x1_quad_1e5_geometry_ul.vtu @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c1a75a0a8fe914c6baf5e2772825feee0924b414e319177c12b71c018c15abf2 +size 1261 diff --git a/Tests/Data/Elliptic/square_1x1_GroundWaterFlow_Python/square_1x1_quad_1e5_geometry_ur.vtu b/Tests/Data/Elliptic/square_1x1_GroundWaterFlow_Python/square_1x1_quad_1e5_geometry_ur.vtu new file mode 100644 index 0000000000000000000000000000000000000000..df1cb1f6e84f916edc34872402e411c2373e8627 --- /dev/null +++ b/Tests/Data/Elliptic/square_1x1_GroundWaterFlow_Python/square_1x1_quad_1e5_geometry_ur.vtu @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:79d7684fd39a33fbe7a06f63060fb9dc67e6445938f3a6de5d6d5c70f685b307 +size 1295 diff --git a/web/content/docs/benchmarks/elliptic/poisson_equation.pandoc b/web/content/docs/benchmarks/elliptic/poisson_equation.pandoc new file mode 100644 index 0000000000000000000000000000000000000000..0d920439850b933cc420168283f897931b071bbb --- /dev/null +++ b/web/content/docs/benchmarks/elliptic/poisson_equation.pandoc @@ -0,0 +1,194 @@ ++++ +date = "2018-10-10T09:17:39+01:00" +title = "Poisson equation using Python for source term specification" +project = "Elliptic/square_1x1_GroundWaterFlow_Python/square_1e3_poisson_sin_x_sin_y.prj" +author = "Tom Fischer" +weight = 102 + +aliases = [ "/docs/benchmarks/" ] # First benchmark page + +[menu] + [menu.benchmarks] + parent = "elliptic" + ++++ + +{{< data-link >}} + +## Poisson Equation + +The Poisson equation is: +$$ +\begin{equation} +- k\; \Delta p = Q \quad \text{in }\Omega +\end{equation}$$ +w.r.t boundary conditions +$$ +\eqalign{ +p(x) = g_D(x) &\quad \text{on }\Gamma_D,\cr +k\;{\partial p(x) \over \partial n} = g_N(x) &\quad \text{on }\Gamma_N, +}$$ + +where $p$ could be the pressure, the subscripts $D$ and $N$ denote the +Dirichlet- and Neumann-type boundary conditions, $n$ is the normal vector +pointing outside of $\Omega$, and $\Gamma = \Gamma_D \cup \Gamma_N$ and +$\Gamma_D \cap \Gamma_N = \emptyset$. + +## Problem specification and analytical solution + +We solve the Poisson equation on a square domain $\Omega = [0\times 1]^2$ +with $k = 1$ w.r.t. the specific boundary conditions: +$$ +\eqalign{ +p(x,y) = 1 &\quad \text{on } (x=0,y=0) \subset \Gamma_D,\cr +p(x,y) = 1 &\quad \text{on } (x=0,y=1) \subset \Gamma_D,\cr +p(x,y) = 1 &\quad \text{on } (x=1,y=0) \subset \Gamma_D,\cr +p(x,y) = 1 &\quad \text{on } (x=1,y=1) \subset \Gamma_D,\cr +}$$ +and the source term is +$$ +Q(x,y) = a^2 \sin\left(ax - \frac{\pi}{2}\right) \sin\left(by - \frac{\pi}{2}\right) ++b^2 \sin\left(ax - \frac{\pi}{2}\right) \sin\left(by - \frac{\pi}{2}\right) +$$ + +The analytical solution of (1) is +$$ +p(x,y) = \sin\left(ax - \frac{\pi}{2}\right) + \sin\left(by - \frac{\pi}{2}\right). +$$ +Since +$$ +\frac{\partial^2 p}{\partial x} (x,y) + = - a^2 \sin\left(ax - \frac{\pi}{2}\right) + \sin\left(by - \frac{\pi}{2}\right) +$$ +and +$$ +\frac{\partial^2 p}{\partial y} (x,y) + = - b^2 \sin\left(ax - \frac{\pi}{2}\right) + \sin\left(by - \frac{\pi}{2}\right) +$$ +it yields +$$ +\Delta p(x,y) + = - a^2 \sin\left(ax - \frac{\pi}{2}\right) \sin\left(by - \frac{\pi}{2}\right) + - b^2 \sin\left(ax - \frac{\pi}{2}\right) \sin\left(by - \frac{\pi}{2}\right) +$$ +and +$$ +Q = - \Delta p(x,y) + = a^2 \sin\left(ax - \frac{\pi}{2}\right) \sin\left(by - \frac{\pi}{2}\right) + + b^2 \sin\left(ax - \frac{\pi}{2}\right) \sin\left(by - \frac{\pi}{2}\right). +$$ + +## Input files + +The project file is +[`square_1e3_poisson_sin_x_sin_y.prj`](https://github.com/ufz/ogs/tree/master/Tests/Data/Elliptic/square_1x1_GroundWaterFlow_Python/square_1e3_poisson_sin_x_sin_y.prj). It describes the +process to be solved and the related process variable together with their +initial, boundary conditions and source terms. The definition of the source term +$Q$ is in a [Python +script](https://github.com/ufz/ogs/tree/master/Tests/Data/Elliptic/square_1x1_GroundWaterFlow_Python/sinx_siny_source_term.py). +The script for setting the source terms is referenced in the project file as +follows: +``` +<python_script>sin_x_sin_y_source_term.py</python_script> +``` +In the source term descripition +``` +<source_term> + <mesh>square_1x1_quad_1e3_entire_domain</mesh> + <type>Python</type> + <source_term_object>sinx_siny_source_term</source_term_object> +</source_term> +``` +the domain is specified by the mesh-tag. The function $Q$ is defined by the +Python object `sinx_sinx_source_term` that is created in the last line of the +Python script: +```python +import OpenGeoSys +from math import pi, sin + +a = 2.0*pi +b = 2.0*pi + +def solution(x, y): + return - sin(a*x-pi/2.0) * sin(b*y-pi/2.0) + +# - laplace(solution) = source term +def laplace_solution(x, y): + return a*a * sin(a*x-pi/2.0) * sin(b*y-pi/2.0) + b*b * sin(a*x-pi/2.0) * sin(b*y-pi/2.0) + +# source term for the benchmark +class SinXSinYSourceTerm(OpenGeoSys.SourceTerm): + def getFlux(self, t, coords, primary_vars): + x, y, z = coords + value = laplace_solution(x,y) + Jac = [ 0.0 ] + return (value, Jac) + +# instantiate source term object referenced in OpenGeoSys' prj file +sinx_siny_source_term = SinXSinYSourceTerm() +``` + +## Running simulation + +To start the simulation (after successful compilation) run: +```bash +$ ogs square_1e3_poisson_sin_x_sin_y.prj +``` + +It will produce some output and write the computed result into the +`square_1e3_volumetricsourceterm_pcs_0_ts_1_t_1.000000.vtu`, which can be +directly visualized and analysed in paraview for example. + +The output on the console will be similar to the following on: +``` +info: This is OpenGeoSys-6 version 6.1.0-1132-g00a6062a2. +info: OGS started on 2018-10-10 09:22:17+0200. + +info: ConstantParameter: K +info: ConstantParameter: p0 +info: ConstantParameter: pressure_edge_points +info: Initialize processes. +createPythonSourceTerm +info: Solve processes. +info: [time] Output of timestep 0 took 0.000695229 s. +info: === Time stepping at step #1 and time 1 with step size 1 +info: [time] Assembly took 0.0100119 s. +info: [time] Applying Dirichlet BCs took 0.000133991 s. +info: ------------------------------------------------------------------ +info: *** Eigen solver computation +info: -> solve with CG (precon DIAGONAL) +info: iteration: 81/10000 +info: residual: 5.974447e-17 + +info: ------------------------------------------------------------------ +info: [time] Linear solver took 0.00145817 s. +info: [time] Iteration #1 took 0.0116439 s. +info: [time] Solving process #0 took 0.011662 s in time step #1 +info: [time] Time step #1 took 0.0116858 s. +info: [time] Output of timestep 1 took 0.000671864 s. +info: The whole computation of the time stepping took 1 steps, in which + the accepted steps are 1, and the rejected steps are 0. + +info: [time] Execution took 0.0370049 s. +info: OGS terminated on 2018-10-10 09:22:17+020 +``` + +## Results and evaluation + +### Comparison of the numerical and analytical solutions + +{{< img src="../square_1e3_poisson_sin_x_sin_y_sourceterm_Pressure_PythonSourceTerm.png" >}} +The above picture shows the numerical result. The solution conforms in the edges +to the prescribed boundary conditions. +{{< img src="../square_1e3_poisson_sin_x_sin_y_sourceterm_Diff_Pressure_AnalyticalSolution_PythonSourceTerm.png" >}} +Since a coarse mesh ($32 \times 32$ elements) is used for the simulation the +difference between the numerical and the analytical solution is relatively large. + +#### Comparison for higher resolution mesh ($316 \times 316$ elements) +{{< img src="../square_1e5_poisson_sin_x_sin_y_sourceterm_Diff_Pressure_AnalyticalSolution_PythonSourceTerm.png" >}} +The difference between the numerical and the analytical solution is much smaller +than in the coarse mesh case. + diff --git a/web/content/docs/benchmarks/elliptic/square_1e3_poisson_sin_x_sin_y_sourceterm_Diff_Pressure_AnalyticalSolution_PythonSourceTerm.png b/web/content/docs/benchmarks/elliptic/square_1e3_poisson_sin_x_sin_y_sourceterm_Diff_Pressure_AnalyticalSolution_PythonSourceTerm.png new file mode 100644 index 0000000000000000000000000000000000000000..9c4e18ab60faec0c22dea3b93e51412ec116858c --- /dev/null +++ b/web/content/docs/benchmarks/elliptic/square_1e3_poisson_sin_x_sin_y_sourceterm_Diff_Pressure_AnalyticalSolution_PythonSourceTerm.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:776901f0e2b1eac0114c682265bb95578a37fcc66616f040e3be97528c6682ac +size 164143 diff --git a/web/content/docs/benchmarks/elliptic/square_1e3_poisson_sin_x_sin_y_sourceterm_Pressure_PythonSourceTerm.png b/web/content/docs/benchmarks/elliptic/square_1e3_poisson_sin_x_sin_y_sourceterm_Pressure_PythonSourceTerm.png new file mode 100644 index 0000000000000000000000000000000000000000..fc2efbc0e2aed28239e5b16cab83ccfe7a1a02c3 --- /dev/null +++ b/web/content/docs/benchmarks/elliptic/square_1e3_poisson_sin_x_sin_y_sourceterm_Pressure_PythonSourceTerm.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8f0535683ab8662903d1d8dcf38c3bf290d0925591a0ce5b2b99dfe3a0048567 +size 183103 diff --git a/web/content/docs/benchmarks/elliptic/square_1e5_poisson_sin_x_sin_y_sourceterm_Diff_Pressure_AnalyticalSolution_PythonSourceTerm.png b/web/content/docs/benchmarks/elliptic/square_1e5_poisson_sin_x_sin_y_sourceterm_Diff_Pressure_AnalyticalSolution_PythonSourceTerm.png new file mode 100644 index 0000000000000000000000000000000000000000..dcfc2d318d1be110dc27a0a5780944685eb11c9f --- /dev/null +++ b/web/content/docs/benchmarks/elliptic/square_1e5_poisson_sin_x_sin_y_sourceterm_Diff_Pressure_AnalyticalSolution_PythonSourceTerm.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5bede41f6571cb9d91e6fd619c2eb08757c06f772842ad1426e4c9cc6345de8c +size 163087