OGS
PythonBoundaryCondition.cpp
Go to the documentation of this file.
1// SPDX-FileCopyrightText: Copyright (c) OpenGeoSys Community (opengeosys.org)
2// SPDX-License-Identifier: BSD-3-Clause
3
5
6#include <algorithm>
7#include <pybind11/pybind11.h>
8
9#include <iostream>
10
11#include "BaseLib/ConfigTree.h"
12#include "FlushStdoutGuard.h"
17
18namespace
19{
21 std::size_t const nnodes)
22{
23 bc_values.ids.clear();
24 bc_values.values.clear();
25
26 bc_values.ids.reserve(nnodes);
27 bc_values.values.reserve(nnodes);
28}
29
31 NumLib::LocalToGlobalIndexMap const& dof_table,
32 std::vector<std::reference_wrapper<ProcessLib::ProcessVariable>> const& pvs)
33{
34 auto const num_vars_dt = dof_table.getNumberOfVariables();
35 auto const num_vars_pv = pvs.size();
36
37 if (static_cast<std::size_t>(num_vars_dt) != num_vars_pv)
38 {
40 "The number of variables in the d.o.f. table does not match the "
41 "number of process variables: {} != {}.",
42 num_vars_dt, num_vars_pv);
43 }
44
45 for (std::size_t var = 0; var < num_vars_pv; ++var)
46 {
47 auto const num_comp_dt = dof_table.getNumberOfVariableComponents(var);
48 auto const num_comp_pv = pvs[var].get().getNumberOfGlobalComponents();
49
50 if (num_comp_dt != num_comp_pv)
51 {
53 "The number of components of variable #{} in the d.o.f. table "
54 "does not match the number of components of process variable "
55 "#{} ({}): {} != {}.",
56 var, var, pvs[var].get().getName(), num_vars_dt, num_vars_pv);
57 }
58 }
59}
60} // anonymous namespace
61
62namespace ProcessLib
63{
65 PythonBcData&& bc_data, unsigned const integration_order,
66 bool const flush_stdout, unsigned const bulk_mesh_dimension,
67 NumLib::LocalToGlobalIndexMap const& dof_table_bulk)
68 : _bc_data(std::move(bc_data)), _flush_stdout(flush_stdout)
69{
70 checkConsistency(dof_table_bulk,
71 _bc_data.all_process_variables_for_this_process);
72
73 std::vector<MeshLib::Node*> const& bc_nodes =
74 _bc_data.bc_or_st_mesh.getNodes();
75 MeshLib::MeshSubset bc_mesh_subset(_bc_data.bc_or_st_mesh, bc_nodes);
76
78 dof_table_bulk.deriveBoundaryConstrainedMap(std::move(bc_mesh_subset));
79
82 bulk_mesh_dimension, _bc_data.bc_or_st_mesh.getElements(),
84 NumLib::IntegrationOrder{integration_order},
85 _bc_data.bc_or_st_mesh.isAxiallySymmetric(), _bc_data);
86}
87
89 const double t, GlobalVector const& x,
91{
92 FlushStdoutGuard guard(_flush_stdout);
93 (void)guard;
94
95 auto const& boundary_mesh = _bc_data.bc_or_st_mesh;
96 auto const& boundary_nodes = boundary_mesh.getNodes();
97 auto const* bc_object = _bc_data.bc_or_st_object;
98
99 initBCValues(bc_values, boundary_nodes.size());
100
101 std::vector<double> primary_variables;
102
103 try
104 {
105 for (auto const* boundary_node : boundary_nodes)
106 {
107 auto const boundary_node_id = boundary_node->getID();
108 auto const dof_idx = getDofIdx(boundary_node_id);
109
110 if (dof_idx == NumLib::MeshComponentMap::nop)
111 {
112 // This d.o.f. has not been found. This can be the case, e.g.,
113 // for Taylor-Hood elements, where the lower order field has no
114 // d.o.f. on higher order nodes.
115 continue;
116 }
117
118 if (dof_idx < 0)
119 {
120 // For the DDC approach (e.g. with PETSc option) a negative
121 // index means that this entry is a ghost entry and should be
122 // dropped.
123 continue;
124 }
125
126 collectPrimaryVariables(primary_variables, *boundary_node, x);
127
128 auto const [apply_bc, bc_value] = bc_object->getDirichletBCValue(
129 t,
130 {(*boundary_node)[0], (*boundary_node)[1], (*boundary_node)[2]},
131 boundary_node_id, primary_variables);
132
133 if (!bc_object->isOverriddenEssential())
134 {
135 DBUG(
136 "Method `getDirichletBCValue' not overridden in Python "
137 "script.");
138 return;
139 }
140
141 if (!apply_bc)
142 {
143 continue;
144 }
145
146 bc_values.ids.emplace_back(dof_idx);
147 bc_values.values.emplace_back(bc_value);
148 }
149 }
150 catch (pybind11::error_already_set const& e)
151 {
152 OGS_FATAL("Error evaluating Dirichlet BC in Python: {}", e.what());
153 }
154}
155
157 std::size_t const boundary_node_id) const
158{
159 MeshLib::Location const loc{_bc_data.bc_or_st_mesh.getID(),
160 MeshLib::MeshItemType::Node, boundary_node_id};
161 return _dof_table_boundary->getGlobalIndex(loc,
162 _bc_data.global_component_id);
163}
164
166 std::size_t const boundary_node_id, int const var, int const comp) const
167{
168 MeshLib::Location const loc{_bc_data.bc_or_st_mesh.getID(),
169 MeshLib::MeshItemType::Node, boundary_node_id};
170 return _dof_table_boundary->getGlobalIndex(loc, var, comp);
171}
172
174 std::vector<double>& primary_variables, MeshLib::Node const& boundary_node,
175 GlobalVector const& x) const
176{
177 primary_variables.clear();
178 auto const num_var = _dof_table_boundary->getNumberOfVariables();
179 auto const boundary_node_id = boundary_node.getID();
180
181 for (int var = 0; var < num_var; ++var)
182 {
183 auto const num_comp =
184 _dof_table_boundary->getNumberOfVariableComponents(var);
185 for (int comp = 0; comp < num_comp; ++comp)
186 {
187 auto const dof_idx = getDofIdx(boundary_node_id, var, comp);
188
189 double const pv_value =
191 ? x[dof_idx]
192 : interpolateToHigherOrderNode(x, var, comp, boundary_node);
193
194 primary_variables.push_back(pv_value);
195 }
196 }
197}
198
200 GlobalVector const& x, int const var, int const comp,
201 MeshLib::Node const& boundary_node) const
202{
203 auto const& boundary_elements =
204 _bc_data.bc_or_st_mesh.getElementsConnectedToNode(
205 boundary_node.getID());
206
207 if (boundary_elements.size() != 1)
208 {
209 DBUG(
210 "Boundary node {} is associated with {} elements in the boundary "
211 "mesh.",
212 boundary_node.getID(),
213 boundary_elements.size());
214 }
215
216 // Interpolations on all associated elements should return the same. Just
217 // pick any of them.
218 auto const& boundary_element = *boundary_elements.front();
219
220 assert(boundary_element.getNumberOfBaseNodes() <
221 boundary_element.getNumberOfNodes() &&
222 "We expect that the boundary element is a higher order element. "
223 "Otherwise no interpolation should take place.");
224
225 // Search local node id
226 auto const local_node_id_within_boundary_element =
227 getNodeIDinElement(boundary_element, &boundary_node);
228
229 auto const boundary_element_id = boundary_element.getID();
230
231 // Assumption: all boundary elements have a local assembler (in the same
232 // order)
233 auto const& loc_asm = *_local_assemblers[boundary_element_id];
234
235 return loc_asm.interpolate(local_node_id_within_boundary_element,
236 *_dof_table_boundary, x, var, comp);
237}
238
240 const double t, std::vector<GlobalVector*> const& x, int const process_id,
242{
243 FlushStdoutGuard guard(_flush_stdout);
244
245 try
246 {
249 _local_assemblers, *_dof_table_boundary, t, x, process_id, K, b,
250 Jac);
251 }
253 {
254 DBUG("Method `getFlux' not overridden in Python script.");
255 }
256 catch (pybind11::error_already_set const& e)
257 {
258 OGS_FATAL("Error evaluating natural BC in Python: {}", e.what());
259 }
260}
261
263 BaseLib::ConfigTree const& config)
264{
266 config.checkConfigParameter("type", "Python");
267
269 auto const bc_object = config.getConfigParameter<std::string>("bc_object");
271 auto const flush_stdout = config.getConfigParameter("flush_stdout", false);
272
273 return {bc_object, flush_stdout};
274}
275
276std::unique_ptr<PythonBoundaryCondition> createPythonBoundaryCondition(
277 PythonBoundaryConditionConfig const& config,
278 MeshLib::Mesh const& boundary_mesh,
279 NumLib::LocalToGlobalIndexMap const& dof_table_bulk,
280 MeshLib::Mesh const& bulk_mesh, int const variable_id,
281 int const component_id, unsigned const integration_order,
282 unsigned const shapefunction_order,
283 std::vector<std::reference_wrapper<ProcessVariable>> const&
284 all_process_variables_for_this_process)
285{
286 // Evaluate Python code in scope of main module
287 pybind11::object scope =
288 pybind11::module::import("__main__").attr("__dict__");
289
290 if (!scope.contains(config.bc_object))
291 {
292 OGS_FATAL(
293 "Function `{:s}' is not defined in the python script file, or "
294 "there was no python script file specified.",
295 config.bc_object);
296 }
297
298 auto* bc = scope[config.bc_object.c_str()]
300
301 if (variable_id >=
302 static_cast<int>(dof_table_bulk.getNumberOfVariables()) ||
303 component_id >=
304 dof_table_bulk.getNumberOfVariableComponents(variable_id))
305 {
306 OGS_FATAL(
307 "Variable id or component id too high. Actual values: ({:d}, "
308 "{:d}), maximum values: ({:d}, {:d}).",
309 variable_id, component_id, dof_table_bulk.getNumberOfVariables(),
310 dof_table_bulk.getNumberOfVariableComponents(variable_id));
311 }
312
313 // In case of partitioned mesh the boundary could be empty, i.e. there is no
314 // boundary condition.
315#ifdef USE_PETSC
316 // This can be extracted to createBoundaryCondition() but then the config
317 // parameters are not read and will cause an error.
318 // TODO (naumov): Add a function to ConfigTree for skipping the tags of the
319 // subtree and move the code up in createBoundaryCondition().
320 if (boundary_mesh.getDimension() == 0 &&
321 boundary_mesh.getNumberOfNodes() == 0 &&
322 boundary_mesh.getNumberOfElements() == 0)
323 {
324 return nullptr;
325 }
326#endif // USE_PETSC
327
328 return std::make_unique<PythonBoundaryCondition>(
330 {bc, dof_table_bulk.getGlobalComponent(variable_id, component_id),
331 boundary_mesh, all_process_variables_for_this_process,
332 shapefunction_order}},
333 integration_order, config.flush_stdout, bulk_mesh.getDimension(),
334 dof_table_bulk);
335}
336
337} // namespace ProcessLib
#define OGS_FATAL(...)
Definition Error.h:19
MathLib::EigenMatrix GlobalMatrix
MathLib::EigenVector GlobalVector
GlobalMatrix::IndexType GlobalIndexType
void DBUG(fmt::format_string< Args... > fmt, Args &&... args)
Definition Logging.h:22
std::string getName(std::string const &line)
Returns the name/title from the "Zone"-description.
T getConfigParameter(std::string const &param) const
void checkConfigParameter(std::string const &param, std::string_view const value) const
std::size_t getID() const
A subset of nodes on a single mesh.
Definition MeshSubset.h:17
unsigned getDimension() const
Returns the dimension of the mesh (determined by the maximum dimension over all elements).
Definition Mesh.h:79
std::size_t getNumberOfNodes() const
Get the number of nodes.
Definition Mesh.h:91
std::size_t getNumberOfElements() const
Get the number of elements.
Definition Mesh.h:88
int getGlobalComponent(int const variable_id, int const component_id) const
int getNumberOfVariableComponents(int variable_id) const
std::unique_ptr< LocalToGlobalIndexMap > deriveBoundaryConstrainedMap(int const variable_id, std::vector< int > const &component_ids, MeshLib::MeshSubset &&new_mesh_subset) const
static constexpr NUMLIB_EXPORT GlobalIndexType const nop
virtual void assemble(std::size_t const id, NumLib::LocalToGlobalIndexMap const &dof_table_boundary, double const t, std::vector< GlobalVector * > const &x, int const process_id, GlobalMatrix *K, GlobalVector &b, GlobalMatrix *Jac)=0
std::unique_ptr< NumLib::LocalToGlobalIndexMap > _dof_table_boundary
Local dof table for the boundary mesh.
double interpolateToHigherOrderNode(GlobalVector const &x, int const var, int const comp, MeshLib::Node const &boundary_node) const
PythonBcData _bc_data
Auxiliary data used by the local assemblers.
void applyNaturalBC(const double t, std::vector< GlobalVector * > const &x, int const process_id, GlobalMatrix *K, GlobalVector &b, GlobalMatrix *Jac) override
std::vector< std::unique_ptr< PythonBoundaryConditionLocalAssemblerInterface > > _local_assemblers
Local assemblers for all elements of the boundary mesh.
void getEssentialBCValues(const double t, const GlobalVector &x, NumLib::IndexValueVector< GlobalIndexType > &bc_values) const override
Writes the values of essential BCs to bc_values.
GlobalIndexType getDofIdx(std::size_t const boundary_node_id) const
void collectPrimaryVariables(std::vector< double > &primary_variables, MeshLib::Node const &boundary_node, GlobalVector const &x) const
PythonBoundaryCondition(PythonBcData &&bc_data, unsigned const integration_order, bool const flush_stdout, unsigned const bulk_mesh_dimension, NumLib::LocalToGlobalIndexMap const &dof_table_bulk)
void createLocalAssemblersPython(const unsigned dimension, std::vector< MeshLib::Element * > const &mesh_elements, NumLib::LocalToGlobalIndexMap const &dof_table, std::vector< std::unique_ptr< LocalAssemblerInterface > > &local_assemblers, NumLib::IntegrationOrder const integration_order, ExtraCtorArgs &&... extra_ctor_args)
std::unique_ptr< PythonBoundaryCondition > createPythonBoundaryCondition(PythonBoundaryConditionConfig const &config, MeshLib::Mesh const &boundary_mesh, NumLib::LocalToGlobalIndexMap const &dof_table_bulk, MeshLib::Mesh const &bulk_mesh, int const variable_id, int const component_id, unsigned const integration_order, unsigned const shapefunction_order, std::vector< std::reference_wrapper< ProcessVariable > > const &all_process_variables_for_this_process)
Creates a new PythonBoundaryCondition object.
PythonBoundaryConditionConfig parsePythonBoundaryCondition(BaseLib::ConfigTree const &config)
void checkConsistency(NumLib::LocalToGlobalIndexMap const &dof_table, std::vector< std::reference_wrapper< ProcessLib::ProcessVariable > > const &pvs)
void initBCValues(NumLib::IndexValueVector< GlobalIndexType > &bc_values, std::size_t const nnodes)
std::vector< IndexType > ids
std::vector< double > values
static void executeMemberOnDereferenced(Method method, Container const &container, Args &&... args)