diff --git a/python/wrap/model.cpp b/python/wrap/model.cpp index 0484633..9238cc7 100644 --- a/python/wrap/model.cpp +++ b/python/wrap/model.cpp @@ -1,554 +1,556 @@ /* * SPDX-License-Indentifier: AGPL-3.0-or-later * * Copyright (©) 2016-2023 EPFL (École Polytechnique Fédérale de Lausanne), * Laboratory (LSMS - Laboratoire de Simulation en Mécanique des Solides) * Copyright (©) 2020-2023 Lucas Frérot * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published * by the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . * */ /* -------------------------------------------------------------------------- */ #include "model.hh" #include "adhesion_functional.hh" #include "elastic_functional.hh" #include "field_container.hh" #include "functional.hh" #include "hooke.hh" #include "integral_operator.hh" #include "model_dumper.hh" #include "model_extensions.hh" #include "model_factory.hh" #include "model_type.hh" #include "numpy.hh" #include "residual.hh" #include "wrap.hh" #include #include #include #include /* -------------------------------------------------------------------------- */ namespace tamaas { namespace wrap { using namespace py::literals; struct model_operator_accessor { Model& m; decltype(auto) get(const std::string& name) { return m.getIntegralOperator(name); } }; template std::unique_ptr> instanciateFromNumpy(numpy& num) { std::unique_ptr> result = nullptr; switch (num.ndim()) { case 2: result = std::make_unique>>(num); return result; case 3: result = std::make_unique>>(num); return result; case 4: result = std::make_unique>>(num); return result; default: TAMAAS_EXCEPTION("instanciateFromNumpy expects the last dimension of numpy " "array to be the number of components"); } } void wrapFieldContainer(py::module& mod) { py::class_(mod, "FieldContainer") .def(py::init<>()) .def("__getitem__", &FieldContainer::at, py::return_value_policy::reference_internal) .def( "__setitem__", [](FieldContainer& f, const std::string& key, numpy grid) { f[key] = instanciateFromNumpy(grid); }, py::keep_alive<1, 3>()) .def( "__setitem__", [](FieldContainer& f, const std::string& key, numpy grid) { f[key] = instanciateFromNumpy(grid); }, py::keep_alive<1, 3>()) .def( "__setitem__", [](FieldContainer& f, const std::string& key, numpy grid) { f[key] = instanciateFromNumpy(grid); }, py::keep_alive<1, 3>()) .def( "__contains__", [](const FieldContainer& m, const std::string& key) { const auto fields = m.fields(); return std::find(fields.begin(), fields.end(), key) != fields.end(); }, "Test field existence") .def( "__iter__", [](const FieldContainer& m) { const auto& fields = m.fields_map(); return py::make_key_iterator(fields.cbegin(), fields.cend()); }, py::keep_alive<0, 1>(), "Iterator on fields") /// Deprecated interface .def( "registerField", [](Model& m, const std::string& name, numpy field) { TAMAAS_DEPRECATE("registerField()", "the [] operator"); m[name] = instanciateFromNumpy(field); }, "field_name"_a, "field"_a, py::keep_alive<1, 3>()) .def( "getField", [](const Model& m, const std::string& name) -> decltype(m.at(name)) { TAMAAS_DEPRECATE("getField()", "the [] operator"); return m.at(name); }, "field_name"_a, py::return_value_policy::reference_internal) .def( "getFields", [](const Model& m) { TAMAAS_DEPRECATE("getFields()", "list(model)"); return m.fields(); }, "Return fields list"); } /// Wrap functional classes void wrapFunctionals(py::module& mod) { py::class_, functional::wrap::PyFunctional> func(mod, "Functional"); func.def(py::init<>()) .def("computeF", &functional::Functional::computeF, "Compute functional value") .def("computeGradF", &functional::Functional::computeGradF, "Compute functional gradient"); py::class_( mod, "ElasticFunctionalPressure", func) .def(py::init&>()); py::class_(mod, "ElasticFunctionalGap", func) .def(py::init&>()); py::class_ adh(mod, "AdhesionFunctional", func); adh.def_property("parameters", &functional::AdhesionFunctional::getParameters, &functional::AdhesionFunctional::setParameters, "Parameters dictionary") .def("setParameters", [](functional::AdhesionFunctional& f, const std::map& m) { TAMAAS_DEPRECATE("setParameters()", "the parameters property"); f.setParameters(m); }); py::class_( mod, "ExponentialAdhesionFunctional", adh, "Potential of the form F = -γ·exp(-g/ρ)") .def(py::init&>(), "surface"_a); py::class_( mod, "MaugisAdhesionFunctional", adh, "Cohesive zone potential F = H(g - ρ)·γ/ρ") .def(py::init&>(), "surface"_a); py::class_( mod, "SquaredExponentialAdhesionFunctional", adh, "Potential of the form F = -γ·exp(-0.5·(g/ρ)²)") .def(py::init&>(), "surface"_a); } /// Wrap IntegralOperator void wrapIntegralOperator(py::module& mod) { - py::class_(mod, "IntegralOperator") + py::class_>(mod, "IntegralOperator") + .def(py::init()) .def("apply", [](IntegralOperator& op, numpy input, numpy output) { TAMAAS_DEPRECATE("apply()", "the () operator"); auto in = instanciateFromNumpy(input); auto out = instanciateFromNumpy(output); op.apply(*in, *out); }) .def(TAMAAS_DEPRECATE_ACCESSOR(getModel, IntegralOperator, "model"), py::return_value_policy::reference) .def(TAMAAS_DEPRECATE_ACCESSOR(getKind, IntegralOperator, "kind")) .def(TAMAAS_DEPRECATE_ACCESSOR(getType, IntegralOperator, "type")) .def( "__call__", [](IntegralOperator& op, numpy input, numpy output) { auto in = instanciateFromNumpy(input); auto out = instanciateFromNumpy(output); op.apply(*in, *out); }, "Apply the integral operator") .def("updateFromModel", &IntegralOperator::updateFromModel, "Resets internal persistent variables from the model") .def_property_readonly("kind", &IntegralOperator::getKind) .def_property_readonly("model", &IntegralOperator::getModel) .def_property_readonly("type", &IntegralOperator::getType) .def_property_readonly("shape", &IntegralOperator::matvecShape) .def( "matvec", [](const IntegralOperator& op, numpy X) -> GridBase { GridBaseNumpy x(X); auto y = op.matvec(x); return y; }, py::return_value_policy::move); py::enum_(mod, "integration_method", "Integration method used for the computation " "of volumetric Fourier operators") .value("linear", integration_method::linear, "No approximation error, O(N₁·N₂·N₃) time complexity, may cause " "float overflow/underflow") .value("cutoff", integration_method::cutoff, "Approximation, O(sqrt(N₁²+N₂²)·N₃²) time complexity, no " "overflow/underflow risk"); } /// Wrap BEEngine classes void wrapBEEngine(py::module& mod) { py::class_(mod, "BEEngine") .def("solveNeumann", &BEEngine::solveNeumann) .def("solveDirichlet", &BEEngine::solveDirichlet) .def("registerNeumann", &BEEngine::registerNeumann) .def("registerDirichlet", &BEEngine::registerDirichlet) .def(TAMAAS_DEPRECATE_ACCESSOR(getModel, BEEngine, "model"), py::return_value_policy::reference) .def_property_readonly("model", &BEEngine::getModel); } template void wrapModelTypeTrait(py::module& mod) { using trait = model_type_traits; py::class_(mod, trait::repr) .def_property_readonly_static( "dimension", [](const py::object&) { return trait::dimension; }, "Dimension of computational domain") .def_property_readonly_static( "components", [](const py::object&) { return trait::components; }, "Number of components of vector fields") .def_property_readonly_static( "boundary_dimension", [](const py::object&) { return trait::boundary_dimension; }, "Dimension of boundary of computational domain") .def_property_readonly_static( "voigt", [](const py::object&) { return trait::voigt; }, "Number of components of symmetrical tensor fields") .def_property_readonly_static( "indices", [](const py::object&) { return trait::indices; }); } /// Wrap Models void wrapModelClass(py::module& mod) { py::enum_(mod, "model_type") .value("basic_1d", model_type::basic_1d, "Normal contact with 1D interface") .value("basic_2d", model_type::basic_2d, "Normal contact with 2D interface") .value("surface_1d", model_type::surface_1d, "Normal & tangential contact with 1D interface") .value("surface_2d", model_type::surface_2d, "Normal & tangential contact with 2D interface") .value("volume_1d", model_type::volume_1d, "Contact with volumetric representation and 1D interface") .value("volume_2d", model_type::volume_2d, "Contact with volumetric representation and 2D interface"); auto trait_mod = mod.def_submodule("_type_traits"); trait_mod.doc() = "Defines type trait objects."; wrapModelTypeTrait(trait_mod); wrapModelTypeTrait(trait_mod); wrapModelTypeTrait(trait_mod); wrapModelTypeTrait(trait_mod); wrapModelTypeTrait(trait_mod); wrapModelTypeTrait(trait_mod); py::class_(mod, "_model_operator_acessor") .def(py::init()) .def( "__getitem__", [](model_operator_accessor& acc, std::string name) { try { return acc.get(name); } catch (std::out_of_range&) { throw py::key_error(name); } }, py::return_value_policy::reference_internal) .def("__contains__", [](model_operator_accessor& acc, std::string key) { const auto ops = acc.m.getIntegralOperators(); return std::find(ops.begin(), ops.end(), key) != ops.end(); }) .def( "__iter__", [](const model_operator_accessor& acc) { const auto& ops = acc.m.getIntegralOperatorsMap(); return py::make_key_iterator(ops.cbegin(), ops.cend()); }, py::keep_alive<0, 1>()); py::class_(mod, "Model") .def(py::init(py::overload_cast&, const std::vector&>( &ModelFactory::createModel)), R"-(Create a new model of a given type, physical size and *global* discretization. :param model_type: the type of desired model :param system_size: the physical size of the domain in each direction :param global_discretization: number of points in each direction)-") .def_property_readonly("type", &Model::getType) .def_property("E", &Model::getYoungModulus, &Model::setYoungModulus, "Young's modulus") .def_property("nu", &Model::getPoissonRatio, &Model::setPoissonRatio, "Poisson's ratio") .def_property_readonly("mu", &Model::getShearModulus, "Shear modulus") .def_property_readonly("E_star", &Model::getHertzModulus, "Contact (Hertz) modulus") .def_property_readonly("be_engine", &Model::getBEEngine, "Boundary element engine") .def( "setElasticity", [](Model& m, Real E, Real nu) { TAMAAS_DEPRECATE("setElasticity()", "the E and nu properties"); m.setElasticity(E, nu); }, "E"_a, "nu"_a) .def(TAMAAS_DEPRECATE_ACCESSOR(getHertzModulus, Model, "E_star")) .def(TAMAAS_DEPRECATE_ACCESSOR(getYoungModulus, Model, "E")) .def(TAMAAS_DEPRECATE_ACCESSOR(getShearModulus, Model, "mu")) .def(TAMAAS_DEPRECATE_ACCESSOR(getPoissonRatio, Model, "nu")) .def(TAMAAS_DEPRECATE_ACCESSOR(getTraction, Model, "traction"), py::return_value_policy::reference_internal) .def(TAMAAS_DEPRECATE_ACCESSOR(getDisplacement, Model, "displacement"), py::return_value_policy::reference_internal) .def(TAMAAS_DEPRECATE_ACCESSOR(getSystemSize, Model, "system_size")) .def(TAMAAS_DEPRECATE_ACCESSOR(getDiscretization, Model, "shape")) .def(TAMAAS_DEPRECATE_ACCESSOR(getBoundarySystemSize, Model, "boundary_system_size")) .def(TAMAAS_DEPRECATE_ACCESSOR(getBoundaryDiscretization, Model, "boundary_shape")) .def("setIntegrationMethod", &Model::setIntegrationMethod) .def("solveNeumann", &Model::solveNeumann, "Solve surface tractions -> displacements") .def("solveDirichlet", &Model::solveDirichlet, "Solve surface displacemnts -> tractions") .def("dump", &Model::dump, "Write model data to registered dumpers") .def("addDumper", &Model::addDumper, "dumper"_a, py::keep_alive<1, 2>(), "Register a dumper") .def( "getBEEngine", [](Model& m) -> decltype(m.getBEEngine()) { TAMAAS_DEPRECATE("getBEEngine()", "the be_engine property"); return m.getBEEngine(); }, py::return_value_policy::reference_internal) .def( "getIntegralOperator", [](const Model& m, std::string name) { TAMAAS_DEPRECATE("getIntegralOperator()", "the operators property"); return m.getIntegralOperator(name); }, "operator_name"_a, py::return_value_policy::reference_internal) .def( "applyElasticity", [](Model& model, numpy stress, numpy strain) { auto out = instanciateFromNumpy(stress); auto in = instanciateFromNumpy(strain); model.applyElasticity(*out, *in); }, "Apply Hooke's law") // Python magic functions .def("__repr__", [](const Model& m) { std::stringstream ss; ss << m; return ss.str(); }) .def( "__copy__", [](const Model&) { throw std::runtime_error("__copy__ not implemented"); }, "Shallow copy of model. Not implemented.") .def( "__deepcopy__", [](const Model& m, py::dict) { return ModelFactory::createModel(m); }, "Deep copy of model.") .def_property_readonly("boundary_fields", &Model::getBoundaryFields) .def_property_readonly( "operators", [](Model& m) { return model_operator_accessor{m}; }, "Returns a dict-like object allowing access to the model's " "integral operators") // More python-like access to model properties .def_property_readonly("shape", &Model::getDiscretization, "Discretization (local in MPI environment)") .def_property_readonly("global_shape", &Model::getGlobalDiscretization, "Global discretization (in MPI environement)") .def_property_readonly("boundary_shape", &Model::getBoundaryDiscretization, "Number of points on boundary") .def_property_readonly("system_size", &Model::getSystemSize, "Size of physical domain") .def_property_readonly("boundary_system_size", &Model::getBoundarySystemSize, "Physical size of surface") .def_property_readonly("traction", (const GridBase& (Model::*)() const) & Model::getTraction, "Surface traction field") .def_property_readonly("displacement", (const GridBase& (Model::*)() const) & Model::getDisplacement, "Displacement field"); py::class_>( mod, "ModelDumper") .def(py::init<>()) .def("dump", &ModelDumper::dump, "model"_a, "Dump model") .def( "__lshift__", [](ModelDumper& dumper, Model& model) { dumper << model; }, "Dump model"); } /// Wrap factory for models void wrapModelFactory(py::module& mod) { py::class_(mod, "ModelFactory") .def_static( "createModel", py::overload_cast&, const std::vector&>( &ModelFactory::createModel), "model_type"_a, "system_size"_a, "global_discretization"_a, R"-(Create a new model of a given type, physical size and *global* discretization. :param model_type: the type of desired model :param system_size: the physical size of the domain in each direction :param global_discretization: number of points in each direction)-") .def_static("createModel", py::overload_cast(&ModelFactory::createModel), "model"_a, "Create a deep copy of a model.") .def_static( "createResidual", [](Model& model, Real sigma_y, Real hardening) { TAMAAS_DEPRECATE("createResidual", "Residual constructor"); return ModelFactory::createResidual(model, sigma_y, hardening); }, "model"_a, "sigma_y"_a, "hardening"_a = 0., R"-(Create an isotropic linear hardening residual. :param model: the model on which to define the residual :param sigma_y: the (von Mises) yield stress :param hardening: the hardening modulus)-") .def_static("registerVolumeOperators", &ModelFactory::registerVolumeOperators, "model"_a, "Register Boussinesq and Mindlin operators to model.") .def_static("registerNonPeriodic", &ModelFactory::registerNonPeriodic, "model"_a, "name"_a, "Register non-periodic Boussinesq operator") .def_static("setIntegrationMethod", &ModelFactory::setIntegrationMethod, "operator"_a, "method"_a, "cutoff"_a, "Set the integration method (linear or cutoff) for a volume " "integral operator") .def_static( "registerHookeField", [](Model& m, const std::string& name) { model_type_dispatch( [&name, &m](auto&& type) { constexpr auto mtype = std::decay_t::value; return m.registerIntegralOperator>(name); }, m.getType()); }, "model"_a, "name"_a, "Register a HookeField operator"); } /// Wrap residual class void wrapResidual(py::module& mod) { py::class_(mod, "Residual") .def(py::init>(), py::keep_alive<1, 2>(), "model"_a, "material"_a, R"-(Create a residual object with a material. Defines the following residual equation: ɛ - ∇N[σ(ɛ)] - ∇M[t] = 0 Where σ(ɛ) is the *eigenstress* associated with the constitutive law. :param model: the model on which to define the residual :param material: material object which defines a constitutive law )-") .def("computeResidual", &Residual::computeResidual) .def("updateState", &Residual::updateState) .def("computeResidualDisplacement", &Residual::computeResidualDisplacement) .def(TAMAAS_DEPRECATE_ACCESSOR(getVector, Residual, "vector"), py::return_value_policy::reference_internal) .def( "getStress", [](const Residual& res) { TAMAAS_DEPRECATE("getStress", "model[\"stress\"]"); const auto& stress = res.getModel().field("stress"); Grid view(res.getModel().getDiscretization(), stress.getNbComponents(), stress.view()); return view; }, py::return_value_policy::reference_internal) .def("setIntegrationMethod", [](Residual& residual, integration_method method, Real cutoff) { TAMAAS_DEPRECATE("setIntegrationMethod", "model.setIntegrationMethod()"); residual.getModel().setIntegrationMethod(method, cutoff); }) .def_property_readonly("vector", &Residual::getVector) .def_property_readonly( "model", static_cast(&Residual::getModel)) .def_property_readonly("material", &Residual::getMaterial); } void wrapModel(py::module& mod) { wrapFieldContainer(mod); wrapBEEngine(mod); wrapModelClass(mod); wrapModelFactory(mod); wrapFunctionals(mod); wrapResidual(mod); wrapIntegralOperator(mod); } } // namespace wrap } // namespace tamaas diff --git a/python/wrap/model_extensions.hh b/python/wrap/model_extensions.hh index d82b65a..50ac281 100644 --- a/python/wrap/model_extensions.hh +++ b/python/wrap/model_extensions.hh @@ -1,95 +1,104 @@ /* * SPDX-License-Indentifier: AGPL-3.0-or-later * * Copyright (©) 2016-2023 EPFL (École Polytechnique Fédérale de Lausanne), * Laboratory (LSMS - Laboratoire de Simulation en Mécanique des Solides) * Copyright (©) 2020-2023 Lucas Frérot * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published * by the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . * */ /* -------------------------------------------------------------------------- */ #include "functional.hh" #include "model.hh" #include "model_dumper.hh" #include "residual.hh" #include "wrap.hh" #include /* -------------------------------------------------------------------------- */ namespace tamaas { /* -------------------------------------------------------------------------- */ namespace functional { namespace wrap { /// Class for extension of Functional in python class PyFunctional : public Functional { public: using Functional::Functional; // Overriding pure virtual functions Real computeF(GridBase& variable, GridBase& dual) const override { // NOLINTNEXTLINE(readability-else-after-return) PYBIND11_OVERLOAD_PURE(Real, Functional, computeF, variable, dual); } void computeGradF(GridBase& variable, GridBase& gradient) const override { // NOLINTNEXTLINE(readability-else-after-return) PYBIND11_OVERLOAD_PURE(void, Functional, computeGradF, variable, gradient); } }; } // namespace wrap } // namespace functional /* -------------------------------------------------------------------------- */ namespace wrap { /* -------------------------------------------------------------------------- */ class PyModelDumper : public ModelDumper { public: using ModelDumper::ModelDumper; void dump(const Model& model) override { // NOLINTNEXTLINE(readability-else-after-return) PYBIND11_OVERLOAD_PURE(void, ModelDumper, dump, model); } }; /* -------------------------------------------------------------------------- */ class PyResidual : public Residual { public: using Residual::Residual; void computeResidual(GridBase& strain_increment) override { // NOLINTNEXTLINE(readability-else-after-return) PYBIND11_OVERLOAD(void, Residual, computeResidual, strain_increment); } void updateState(GridBase& converged_strain_increment) override { // NOLINTNEXTLINE(readability-else-after-return) PYBIND11_OVERLOAD(void, Residual, updateState, converged_strain_increment); } void computeResidualDisplacement(GridBase& strain_increment) override { // NOLINTNEXTLINE(readability-else-after-return) PYBIND11_OVERLOAD(void, Residual, computeResidualDisplacement, strain_increment); } }; +class PyIntegralOperator : public IntegralOperator { +public: + using IntegralOperator::IntegralOperator; + + void apply(GridBase& input, GridBase& output) const override { + PYBIND11_OVERLOAD_PURE(void, IntegralOperator, apply, input, output); + } +}; + /* -------------------------------------------------------------------------- */ } // namespace wrap /* -------------------------------------------------------------------------- */ } // namespace tamaas diff --git a/src/model/be_engine.cpp b/src/model/be_engine.cpp index 81f5921..5e5054a 100644 --- a/src/model/be_engine.cpp +++ b/src/model/be_engine.cpp @@ -1,89 +1,91 @@ /* * SPDX-License-Indentifier: AGPL-3.0-or-later * * Copyright (©) 2016-2023 EPFL (École Polytechnique Fédérale de Lausanne), * Laboratory (LSMS - Laboratoire de Simulation en Mécanique des Solides) * Copyright (©) 2020-2023 Lucas Frérot * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published * by the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . * */ /* -------------------------------------------------------------------------- */ #include "be_engine.hh" #include "logger.hh" #include "model.hh" /* -------------------------------------------------------------------------- */ namespace tamaas { /* -------------------------------------------------------------------------- */ void solve(IntegralOperator::kind kind, - const std::map& operators, + const std::map>& operators, GridBase& input, GridBase& output) try { operators.at(kind)->apply(input, output); } catch (std::out_of_range& e) { Logger().get(LogLevel::warning) << "Operator (" << "Westergaard::" << kind << ") not registered\n"; throw e; } template void registerWestergaardOperator( - std::map& operators, + std::map>& + operators, Model& model) { std::stringstream sstr; sstr << "Westergaard::" << otype; if (operators.find(otype) == operators.end()) operators[otype] = model.template registerIntegralOperator>( sstr.str()); } template void BEEngineTmpl::solveNeumann(GridBase& neumann, GridBase& dirichlet) const { solve(IntegralOperator::neumann, this->operators, neumann, dirichlet); } template void BEEngineTmpl::solveDirichlet(GridBase& dirichlet, GridBase& neumann) const { solve(IntegralOperator::dirichlet, this->operators, dirichlet, neumann); } template void BEEngineTmpl::registerNeumann() { Logger().get(LogLevel::debug) << TAMAAS_DEBUG_MSG("Registering Neumann Westergaard"); registerWestergaardOperator(this->operators, *this->model); } /// Register dirichlet operator template void BEEngineTmpl::registerDirichlet() { Logger().get(LogLevel::debug) << TAMAAS_DEBUG_MSG("Registering Dirichlet Westergaard"); registerWestergaardOperator( this->operators, *this->model); } template class BEEngineTmpl; template class BEEngineTmpl; template class BEEngineTmpl; template class BEEngineTmpl; template class BEEngineTmpl; template class BEEngineTmpl; /* -------------------------------------------------------------------------- */ } // namespace tamaas diff --git a/src/model/be_engine.hh b/src/model/be_engine.hh index f13be4a..4c94907 100644 --- a/src/model/be_engine.hh +++ b/src/model/be_engine.hh @@ -1,87 +1,86 @@ /* * SPDX-License-Indentifier: AGPL-3.0-or-later * * Copyright (©) 2016-2023 EPFL (École Polytechnique Fédérale de Lausanne), * Laboratory (LSMS - Laboratoire de Simulation en Mécanique des Solides) * Copyright (©) 2020-2023 Lucas Frérot * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published * by the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . * */ /* -------------------------------------------------------------------------- */ #ifndef BE_ENGINE_HH #define BE_ENGINE_HH /* -------------------------------------------------------------------------- */ -#include "model_type.hh" #include "integral_operator.hh" +#include "model_type.hh" #include "westergaard.hh" /* -------------------------------------------------------------------------- */ namespace tamaas { /** * @brief Boundary equation engine class. Solves the Neumann/Dirichlet problem * This class should be dimension and model-type agnostic. */ class BEEngine { public: BEEngine(Model* model) : model(model) {} /// Destructor virtual ~BEEngine() = default; public: /// Solve Neumann problem (expects boundary data) virtual void solveNeumann(GridBase& neumann, GridBase& dirichlet) const = 0; /// Solve Dirichlet problem (expects boundary data) virtual void solveDirichlet(GridBase& dirichlet, GridBase& neumann) const = 0; /// Register neumann operator virtual void registerNeumann() = 0; /// Register dirichlet operator virtual void registerDirichlet() = 0; /// Get model const Model& getModel() const { return *model; } /// Compute L_2 norm of influence functions Real getNeumannNorm() { return operators[IntegralOperator::neumann]->getOperatorNorm(); } protected: Model* model; - std::map operators; + std::map> operators; }; template class BEEngineTmpl : public BEEngine { public: - BEEngineTmpl(Model* model): BEEngine(model) {} + BEEngineTmpl(Model* model) : BEEngine(model) {} void solveNeumann(GridBase& neumann, - GridBase& dirichlet) const override; + GridBase& dirichlet) const override; void solveDirichlet(GridBase& dirichlet, GridBase& neumann) const override; /// Register neumann operator void registerNeumann() override; /// Register dirichlet operator void registerDirichlet() override; - }; } // namespace tamaas #endif // BE_ENGINE_HH diff --git a/src/model/model.cpp b/src/model/model.cpp index 1941f94..a9a87d1 100644 --- a/src/model/model.cpp +++ b/src/model/model.cpp @@ -1,168 +1,175 @@ /* * SPDX-License-Indentifier: AGPL-3.0-or-later * * Copyright (©) 2016-2023 EPFL (École Polytechnique Fédérale de Lausanne), * Laboratory (LSMS - Laboratoire de Simulation en Mécanique des Solides) * Copyright (©) 2020-2023 Lucas Frérot * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published * by the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . * */ /* -------------------------------------------------------------------------- */ #include "model.hh" #include "be_engine.hh" #include "logger.hh" /* -------------------------------------------------------------------------- */ namespace tamaas { /* -------------------------------------------------------------------------- */ void Model::setElasticity(Real E_, Real nu_) { setYoungModulus(E_); setPoissonRatio(nu_); } /* -------------------------------------------------------------------------- */ void Model::applyElasticity(GridBase& stress, const GridBase& strain) const { operators.at("hooke")->apply(const_cast&>(strain), stress); } /* -------------------------------------------------------------------------- */ GridBase& Model::getTraction() { return this->field("traction"); } const GridBase& Model::getTraction() const { return this->field("traction"); } /* -------------------------------------------------------------------------- */ GridBase& Model::getDisplacement() { return this->field("displacement"); } const GridBase& Model::getDisplacement() const { return this->field("displacement"); } /* -------------------------------------------------------------------------- */ const std::vector& Model::getSystemSize() const { return system_size; } /* -------------------------------------------------------------------------- */ const std::vector& Model::getDiscretization() const { return discretization; } /* -------------------------------------------------------------------------- */ void Model::solveNeumann() { engine->registerNeumann(); engine->solveNeumann(getTraction(), getDisplacement()); } void Model::solveDirichlet() { engine->registerDirichlet(); engine->solveDirichlet(getDisplacement(), getTraction()); } /* -------------------------------------------------------------------------- */ -IntegralOperator* Model::getIntegralOperator(const std::string& name) const { - return operators.at(name).get(); +void Model::registerIntegralOperator(const std::string& name, + std::shared_ptr op) { + operators[name] = std::move(op); +} + +/* -------------------------------------------------------------------------- */ +std::shared_ptr +Model::getIntegralOperator(const std::string& name) const { + return operators.at(name); } /* -------------------------------------------------------------------------- */ std::vector Model::getIntegralOperators() const { std::vector keys; keys.reserve(operators.size()); std::transform(operators.begin(), operators.end(), std::back_inserter(keys), [](auto&& pair) { return pair.first; }); return keys; } /* -------------------------------------------------------------------------- */ void Model::updateOperators() { for (auto& op : operators) op.second->updateFromModel(); } /* -------------------------------------------------------------------------- */ void Model::addDumper(std::shared_ptr dumper) { this->dumpers.push_back(std::move(dumper)); } void Model::dump() const { if (dumpers.empty()) Logger().get(LogLevel::warning) << "dumper list is empty\n"; for (const auto& dumper : dumpers) if (dumper) dumper->dump(*this); } /* -------------------------------------------------------------------------- */ std::vector Model::getBoundaryFields() const { std::vector boundary_fields; for (auto&& pair : this->fields_map()) { const auto boundary = boost::apply_visitor( [&](auto&& grid_ptr) { return this->isBoundaryField(*grid_ptr); }, pair.second); if (boundary) boundary_fields.push_back(pair.first); } return boundary_fields; } /* -------------------------------------------------------------------------- */ std::ostream& operator<<(std::ostream& o, const Model& _this) { o << "Model<" << _this.getType() << "> (E = " << _this.getYoungModulus() << ", nu = " << _this.getPoissonRatio() << ")\n"; auto out_collec = [&o](auto&& collec) { std::for_each(collec.begin(), collec.end() - 1, [&o](const auto& x) { o << x << ", "; }); o << collec.back(); }; // Printing domain size o << " - domain = ["; out_collec(_this.getSystemSize()); o << "]\n"; // Printing discretization o << " - discretization = ["; out_collec(_this.getDiscretization()); o << "]\n"; if (mpi::size() > 1) { o << " - global discretization = ["; out_collec(_this.getGlobalDiscretization()); o << "]\n"; } // Print fields o << " - registered fields = ["; out_collec(_this.fields()); o << "]\n"; o << " - registered operators = ["; out_collec(_this.getIntegralOperators()); o << "]"; if (not _this.dumpers.empty()) o << "\n - " << _this.dumpers.size() << " registered dumpers"; return o; } /* -------------------------------------------------------------------------- */ } // namespace tamaas diff --git a/src/model/model.hh b/src/model/model.hh index 6da6a7e..168129c 100644 --- a/src/model/model.hh +++ b/src/model/model.hh @@ -1,202 +1,208 @@ /* * SPDX-License-Indentifier: AGPL-3.0-or-later * * Copyright (©) 2016-2023 EPFL (École Polytechnique Fédérale de Lausanne), * Laboratory (LSMS - Laboratoire de Simulation en Mécanique des Solides) * Copyright (©) 2020-2023 Lucas Frérot * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published * by the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . * */ /* -------------------------------------------------------------------------- */ #ifndef MODEL_HH #define MODEL_HH /* -------------------------------------------------------------------------- */ #include "be_engine.hh" #include "field_container.hh" #include "grid_base.hh" #include "integral_operator.hh" #include "model_dumper.hh" #include "model_type.hh" #include "tamaas.hh" #include #include #include /* -------------------------------------------------------------------------- */ namespace tamaas { /* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */ /// Forward declaration enum class integration_method; /** * @brief Model containing pressure and displacement * This class is a container for the model fields. It is supposed to be * dimension agnostic, hence the GridBase members. */ class Model : public FieldContainer { protected: /// Constructor Model(std::vector system_size, std::vector discretization) : FieldContainer(), system_size(std::move(system_size)), discretization(std::move(discretization)) {} public: /// Set elasticity parameters void setElasticity(Real E, Real nu); /// Get Hertz contact modulus Real getHertzModulus() const { return E / (1 - nu * nu); } /// Get Young's modulus Real getYoungModulus() const { return E; } /// Get Poisson's ratio Real getPoissonRatio() const { return nu; } /// Get shear modulus Real getShearModulus() const { return E / (2 * (1 + nu)); } /// Set Young's modulus void setYoungModulus(Real E_) { if (E_ < 0) TAMAAS_EXCEPTION("Elastic modulus should be positive"); this->E = E_; updateOperators(); } /// Set Poisson's ratio void setPoissonRatio(Real nu_) { if (nu_ > 0.5 or nu_ <= -1) TAMAAS_EXCEPTION("Poisson ratio should be in ]-1, 0.5]"); this->nu = nu_; updateOperators(); } // Model info accessors public: /// Get model type virtual model_type getType() const = 0; /// Get system physical size const std::vector& getSystemSize() const; /// Get boundary system physical size virtual std::vector getBoundarySystemSize() const = 0; /// Get discretization const std::vector& getDiscretization() const; /// Get discretization of global MPI system virtual std::vector getGlobalDiscretization() const = 0; /// Get boundary discretization virtual std::vector getBoundaryDiscretization() const = 0; /// Get boundary element engine BEEngine& getBEEngine() { TAMAAS_ASSERT(engine, "BEEngine was not initialized"); return *engine; } // Exposing some common operators public: /// Apply Hooke's law void applyElasticity(GridBase& stress, const GridBase& strain) const; /// Solve Neumann problem using default neumann operator void solveNeumann(); /// Solve Dirichlet problem using default dirichlet operator void solveDirichlet(); public: /// Register a new integral operator template - IntegralOperator* registerIntegralOperator(const std::string& name); + std::shared_ptr + registerIntegralOperator(const std::string& name); + /// Register external operator + void registerIntegralOperator(const std::string& name, + std::shared_ptr op); /// Get a registerd integral operator - IntegralOperator* getIntegralOperator(const std::string& name) const; + std::shared_ptr + getIntegralOperator(const std::string& name) const; /// Get list of integral operators std::vector getIntegralOperators() const; /// Get operators mapcar const auto& getIntegralOperatorsMap() const { return operators; } /// Tell operators to update their cache void updateOperators(); /// Set integration method for registered volume operators virtual void setIntegrationMethod(integration_method method, Real cutoff) = 0; public: /// Get pressure GridBase& getTraction(); /// Get pressure const GridBase& getTraction() const; /// Get displacement GridBase& getDisplacement(); /// Get displacement const GridBase& getDisplacement() const; /// Determine if a field is defined on boundary template bool isBoundaryField(const GridBase& field) const { return field.getDimension() == getBoundaryDiscretization().size(); } /// Return list of fields defined on boundary std::vector getBoundaryFields() const; public: /// Set the dumper object void addDumper(std::shared_ptr dumper); /// Dump the model void dump() const; friend std::ostream& operator<<(std::ostream& o, const Model& _this); protected: Real E = 1, nu = 0; std::vector system_size; std::vector discretization; std::unique_ptr engine = nullptr; std::unordered_map> operators; std::vector> dumpers; }; /* -------------------------------------------------------------------------- */ /* Template functions */ /* -------------------------------------------------------------------------- */ template -IntegralOperator* Model::registerIntegralOperator(const std::string& name) { +std::shared_ptr +Model::registerIntegralOperator(const std::string& name) { Logger().get(LogLevel::debug) << TAMAAS_DEBUG_MSG("registering operator " + name); operators[name] = std::make_unique(this); - return operators[name].get(); + return operators[name]; } /* -------------------------------------------------------------------------- */ /* Output model to stream */ /* -------------------------------------------------------------------------- */ std::ostream& operator<<(std::ostream& o, const Model& _this); /* -------------------------------------------------------------------------- */ /* Simpler grid allocation */ /* -------------------------------------------------------------------------- */ template std::unique_ptr> allocateGrid(Model& model) { return allocateGrid( model.getType(), (boundary) ? model.getBoundaryDiscretization() : model.getDiscretization()); } template std::unique_ptr> allocateGrid(Model& model, UInt nc) { return allocateGrid(model.getType(), (boundary) ? model.getBoundaryDiscretization() : model.getDiscretization(), nc); } } // namespace tamaas #endif // MODEL_HH diff --git a/src/solvers/polonsky_keer_rey.hh b/src/solvers/polonsky_keer_rey.hh index e712df3..892ff7b 100644 --- a/src/solvers/polonsky_keer_rey.hh +++ b/src/solvers/polonsky_keer_rey.hh @@ -1,137 +1,137 @@ /* * SPDX-License-Indentifier: AGPL-3.0-or-later * * Copyright (©) 2016-2023 EPFL (École Polytechnique Fédérale de Lausanne), * Laboratory (LSMS - Laboratoire de Simulation en Mécanique des Solides) * Copyright (©) 2020-2023 Lucas Frérot * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published * by the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . * */ /* -------------------------------------------------------------------------- */ #ifndef POLONSKY_KEER_REY_HH #define POLONSKY_KEER_REY_HH /* -------------------------------------------------------------------------- */ #include "contact_solver.hh" #include "grid_view.hh" #include "meta_functional.hh" #include "westergaard.hh" /* -------------------------------------------------------------------------- */ namespace tamaas { class PolonskyKeerRey : public ContactSolver { public: /// Types of algorithm (primal/dual) or constraint enum type { gap, pressure }; public: /// Constructor PolonskyKeerRey(Model& model, const GridBase& surface, Real tolerance, type variable_type, type constraint_type); ~PolonskyKeerRey() override = default; public: /// \cond DO_NOT_DOCUMENT using ContactSolver::solve; ///\endcond /// Solve Real solve(std::vector target) override; /// Mean on unsaturated constraint zone virtual Real meanOnUnsaturated(const GridBase& field) const; /// Compute squared norm virtual Real computeSquaredNorm(const GridBase& field) const; /// Update search direction virtual void updateSearchDirection(Real factor); /// Compute critical step virtual Real computeCriticalStep(Real target = 0); /// Update primal and check non-admissible state virtual bool updatePrimal(Real step); /// Compute error/stopping criterion virtual Real computeError() const; /// Enforce contact constraints on final state virtual void enforceAdmissibleState(); /// Enfore mean value constraint virtual void enforceMeanValue(Real mean); /// Set integral operator for gradient computation void setIntegralOperator(std::string name); private: /// Set correct views on normal traction and gap template void setViews(); protected: type variable_type, constraint_type; model_type operation_type; GridBase* primal = nullptr; ///< non-owning pointer for primal varaible GridBase* dual = nullptr; ///< non-owning pointer for dual variable /// CG search direction std::unique_ptr> search_direction = nullptr; /// Projected CG search direction std::unique_ptr> projected_search_direction = nullptr; /// View on normal pressure, gap, displacement std::unique_ptr> pressure_view, gap_view, displacement_view = nullptr; /// Integral operator for gradient computation - IntegralOperator* integral_op = nullptr; + std::shared_ptr integral_op = nullptr; }; /* -------------------------------------------------------------------------- */ template void PolonskyKeerRey::setViews() { constexpr UInt dim = model_type_traits::dimension; constexpr UInt bdim = model_type_traits::boundary_dimension; constexpr UInt comp = model_type_traits::components; pressure_view = std::unique_ptr>{ new GridView(model.getTraction(), {}, comp - 1)}; gap_view = std::unique_ptr>{ new GridView(*this->_gap, {}, comp - 1)}; displacement_view = std::unique_ptr>{new GridView( model.getDisplacement(), model_type_traits::indices, comp - 1)}; #define WESTERGAARD(type, kind, desc) \ this->model.registerIntegralOperator< \ Westergaard>(desc) - IntegralOperator *dirichlet, *neumann; + std::shared_ptr dirichlet, neumann; // I noz is fugly - TODO factory? if (bdim == 1) { operation_type = model_type::basic_1d; dirichlet = WESTERGAARD(basic_1d, dirichlet, "westergaard_dirichlet"); neumann = WESTERGAARD(basic_1d, neumann, "westergaard_neumann"); } else if (bdim == 2) { operation_type = model_type::basic_2d; dirichlet = WESTERGAARD(basic_2d, dirichlet, "westergaard_dirichlet"); neumann = WESTERGAARD(basic_2d, neumann, "westergaard_neumann"); } if (variable_type == gap) - integral_op = dirichlet; + integral_op = std::move(dirichlet); else - integral_op = neumann; + integral_op = std::move(neumann); #undef WESTERGAARD } } // namespace tamaas #endif // POLONSKY_KEER_REY_HH