Raw File
PythonBoundaryCondition.cpp
/**
 * \file
 * \copyright
 * Copyright (c) 2012-2021, 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 "PythonBoundaryCondition.h"

#include <pybind11/pybind11.h>

#include <iostream>

#include "BaseLib/ConfigTree.h"
#include "FlushStdoutGuard.h"
#include "MeshLib/MeshSearch/NodeSearch.h"
#include "ProcessLib/BoundaryConditionAndSourceTerm/Utils/CreateLocalAssemblers.h"
#include "PythonBoundaryConditionLocalAssembler.h"

namespace ProcessLib
{
PythonBoundaryCondition::PythonBoundaryCondition(
    PythonBoundaryConditionData&& bc_data,
    unsigned const integration_order,
    unsigned const shapefunction_order,
    unsigned const global_dim,
    bool const flush_stdout)
    : _bc_data(std::move(bc_data)), _flush_stdout(flush_stdout)
{
    std::vector<MeshLib::Node*> const& bc_nodes =
        _bc_data.boundary_mesh.getNodes();
    MeshLib::MeshSubset bc_mesh_subset(_bc_data.boundary_mesh, bc_nodes);

    // Create local DOF table from the bc mesh subset for the given variable and
    // component id.
    _dof_table_boundary = _bc_data.dof_table_bulk.deriveBoundaryConstrainedMap(
        std::move(bc_mesh_subset));

    BoundaryConditionAndSourceTerm::createLocalAssemblers<
        PythonBoundaryConditionLocalAssembler>(
        global_dim, _bc_data.boundary_mesh.getElements(), *_dof_table_boundary,
        shapefunction_order, _local_assemblers,
        _bc_data.boundary_mesh.isAxiallySymmetric(), integration_order,
        _bc_data);
}

void PythonBoundaryCondition::getEssentialBCValues(
    const double t, GlobalVector const& x,
    NumLib::IndexValueVector<GlobalIndexType>& bc_values) const
{
    FlushStdoutGuard guard(_flush_stdout);
    (void)guard;

    auto const nodes = _bc_data.boundary_mesh.getNodes();

    auto const& bulk_node_ids_map =
        *_bc_data.boundary_mesh.getProperties().getPropertyVector<std::size_t>(
            "bulk_node_ids", MeshLib::MeshItemType::Node, 1);

    bc_values.ids.clear();
    bc_values.values.clear();

    bc_values.ids.reserve(_bc_data.boundary_mesh.getNumberOfNodes());
    bc_values.values.reserve(_bc_data.boundary_mesh.getNumberOfNodes());

    std::vector<double> primary_variables;

    for (auto const* node : _bc_data.boundary_mesh.getNodes())
    {
        auto const boundary_node_id = node->getID();
        auto const bulk_node_id = bulk_node_ids_map[boundary_node_id];

        // gather primary variable values
        primary_variables.clear();
        auto const num_var = _dof_table_boundary->getNumberOfVariables();
        for (int var = 0; var < num_var; ++var)
        {
            auto const num_comp =
                _dof_table_boundary->getNumberOfVariableComponents(var);
            for (int comp = 0; comp < num_comp; ++comp)
            {
                MeshLib::Location loc{_bc_data.bulk_mesh_id,
                                      MeshLib::MeshItemType::Node,
                                      bulk_node_id};
                auto const dof_idx =
                    _bc_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.push_back(x[dof_idx]);
            }
        }

        auto* xs = nodes[boundary_node_id]->getCoords();  // TODO DDC problems?
        auto pair_flag_value = _bc_data.bc_object->getDirichletBCValue(
            t, {xs[0], xs[1], xs[2]}, boundary_node_id, primary_variables);
        if (!_bc_data.bc_object->isOverriddenEssential())
        {
            DBUG(
                "Method `getDirichletBCValue' not overridden in Python "
                "script.");
            return;
        }

        if (!pair_flag_value.first)
        {
            continue;
        }

        MeshLib::Location l(_bc_data.bulk_mesh_id, MeshLib::MeshItemType::Node,
                            bulk_node_id);
        const auto dof_idx = _bc_data.dof_table_bulk.getGlobalIndex(
            l, _bc_data.global_component_id);
        if (dof_idx == NumLib::MeshComponentMap::nop)
        {
            OGS_FATAL(
                "Logic error. This error should already have occurred while "
                "gathering primary variables. Something nasty is going on!");
        }

        // For the DDC approach (e.g. with PETSc option), the negative
        // index of g_idx means that the entry by that index is a ghost
        // one, which should be dropped. Especially for PETSc routines
        // MatZeroRows and MatZeroRowsColumns, which are called to apply
        // the Dirichlet BC, the negative index is not accepted like
        // other matrix or vector PETSc routines. Therefore, the
        // following if-condition is applied.
        if (dof_idx >= 0)
        {
            bc_values.ids.emplace_back(dof_idx);
            bc_values.values.emplace_back(pair_flag_value.second);
        }
    }
}

void PythonBoundaryCondition::applyNaturalBC(
    const double t, std::vector<GlobalVector*> const& x, int const process_id,
    GlobalMatrix& K, GlobalVector& b, GlobalMatrix* Jac)
{
    FlushStdoutGuard guard(_flush_stdout);

    try
    {
        GlobalExecutor::executeMemberOnDereferenced(
            &GenericNaturalBoundaryConditionLocalAssemblerInterface::assemble,
            _local_assemblers, *_dof_table_boundary, t, x, process_id, K, b,
            Jac);
    }
    catch (MethodNotOverriddenInDerivedClassException const& /*e*/)
    {
        DBUG("Method `getFlux' not overridden in Python script.");
    }
}

std::unique_ptr<PythonBoundaryCondition> createPythonBoundaryCondition(
    BaseLib::ConfigTree const& config, MeshLib::Mesh const& boundary_mesh,
    NumLib::LocalToGlobalIndexMap const& dof_table, std::size_t bulk_mesh_id,
    int const variable_id, int const component_id,
    unsigned const integration_order, unsigned const shapefunction_order,
    unsigned const global_dim)
{
    //! \ogs_file_param{prj__process_variables__process_variable__boundary_conditions__boundary_condition__type}
    config.checkConfigParameter("type", "Python");

    //! \ogs_file_param{prj__process_variables__process_variable__boundary_conditions__boundary_condition__Python__bc_object}
    auto const bc_object = config.getConfigParameter<std::string>("bc_object");
    //! \ogs_file_param{prj__process_variables__process_variable__boundary_conditions__boundary_condition__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(bc_object))
    {
        OGS_FATAL(
            "Function `{:s}' is not defined in the python script file, or "
            "there was no python script file specified.",
            bc_object);
    }

    auto* bc = scope[bc_object.c_str()]
                   .cast<PythonBoundaryConditionPythonSideInterface*>();

    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 boundary could be empty, i.e. there is no
    // boundary condition.
#ifdef USE_PETSC
    // This can be extracted to createBoundaryCondition() 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 createBoundaryCondition().
    if (boundary_mesh.getDimension() == 0 &&
        boundary_mesh.getNumberOfNodes() == 0 &&
        boundary_mesh.getNumberOfElements() == 0)
    {
        return nullptr;
    }
#endif  // USE_PETSC

    return std::make_unique<PythonBoundaryCondition>(
        PythonBoundaryConditionData{
            bc, dof_table, bulk_mesh_id,
            dof_table.getGlobalComponent(variable_id, component_id),
            boundary_mesh},
        integration_order, shapefunction_order, global_dim, flush_stdout);
}

}  // namespace ProcessLib
back to top