diff --git a/src/solvers/contact_solver.cpp b/src/solvers/contact_solver.cpp index 05c27ae..dd8892c 100644 --- a/src/solvers/contact_solver.cpp +++ b/src/solvers/contact_solver.cpp @@ -1,64 +1,67 @@ /* * SPDX-License-Indentifier: AGPL-3.0-or-later * * Copyright (©) 2016-2022 EPFL (École Polytechnique Fédérale de Lausanne), * Laboratory (LSMS - Laboratoire de Simulation en Mécanique des Solides) * * 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 "contact_solver.hh" #include "logger.hh" #include #include /* -------------------------------------------------------------------------- */ namespace tamaas { ContactSolver::ContactSolver(Model& model, const GridBase& surface, Real tolerance) : model(model), tolerance(tolerance), surface_stddev(std::sqrt(surface.var())) { auto discretization = model.getBoundaryDiscretization(); auto n_points = model.getTraction().getNbPoints(); if (n_points != surface.dataSize()) TAMAAS_EXCEPTION("Model size and surface size do not match!"); this->surface.wrap(surface); _gap = allocateGrid(model.getType(), discretization); model.registerField("gap", _gap); } /* -------------------------------------------------------------------------- */ - -void ContactSolver::printState(UInt iter, Real cost_f, Real error) { - // UInt precision = std::cout.precision(); +void ContactSolver::printState(UInt iter, Real cost_f, Real error) const { if (iter % dump_frequency) return; + logIteration(iter, cost_f, error); +} + +/* -------------------------------------------------------------------------- */ +void ContactSolver::logIteration(UInt iter, Real cost_f, Real error) const { Logger().get(LogLevel::info) << std::setw(5) << iter << " " << std::setw(15) << std::scientific << cost_f << " " << std::setw(15) << error << std::endl << std::fixed; } /* -------------------------------------------------------------------------- */ void ContactSolver::addFunctionalTerm( std::shared_ptr func) { functional.addFunctionalTerm(std::move(func)); } } // namespace tamaas /* -------------------------------------------------------------------------- */ diff --git a/src/solvers/contact_solver.hh b/src/solvers/contact_solver.hh index 3cb7f63..db90235 100644 --- a/src/solvers/contact_solver.hh +++ b/src/solvers/contact_solver.hh @@ -1,85 +1,87 @@ /* * SPDX-License-Indentifier: AGPL-3.0-or-later * * Copyright (©) 2016-2022 EPFL (École Polytechnique Fédérale de Lausanne), * Laboratory (LSMS - Laboratoire de Simulation en Mécanique des Solides) * * 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 CONTACT_SOLVER_HH #define CONTACT_SOLVER_HH /* -------------------------------------------------------------------------- */ #include "meta_functional.hh" #include "model.hh" #include "tamaas.hh" /* -------------------------------------------------------------------------- */ namespace tamaas { class ContactSolver { public: /// Constructor ContactSolver(Model& model, const GridBase& surface, Real tolerance); /// Destructor virtual ~ContactSolver() = default; public: /// Print state of solve - virtual void printState(UInt iter, Real cost_f, Real error); + virtual void printState(UInt iter, Real cost_f, Real error) const; + /// Log iteration info + virtual void logIteration(UInt iter, Real cost_f, Real error) const; /// Get maximum number of iterations auto getMaxIterations() const { return max_iterations; } /// Set maximum number of iterations void setMaxIterations(UInt n) { max_iterations = n; } /// Get dump_frequency auto getDumpFrequency() const { return dump_frequency; } /// Set dump_frequency void setDumpFrequency(UInt n) { dump_frequency = n; } /// Add term to functional void addFunctionalTerm(std::shared_ptr func); /// Returns functional object const functional::Functional& getFunctional() const { return functional; } // Virtual functions public: /// Solve for a mean traction vector virtual Real solve(std::vector /*load*/) { TAMAAS_EXCEPTION("solve() not implemented"); } /// Solve for normal pressure virtual Real solve(Real load) { return solve(std::vector{load}); } /// Accessor for tolerance TAMAAS_ACCESSOR(tolerance, Real, Tolerance); public: GridBase& getSurface() { return surface; } Model& getModel() { return model; } protected: Model& model; GridBase surface; std::shared_ptr> _gap = nullptr; functional::MetaFunctional functional; Real tolerance; UInt max_iterations = 1000; Real surface_stddev; UInt dump_frequency = 100; }; } // namespace tamaas /* -------------------------------------------------------------------------- */ #endif // CONTACT_SOLVER_HH diff --git a/src/solvers/polonsky_keer_rey.cpp b/src/solvers/polonsky_keer_rey.cpp index 8a70d82..ec215dc 100644 --- a/src/solvers/polonsky_keer_rey.cpp +++ b/src/solvers/polonsky_keer_rey.cpp @@ -1,299 +1,302 @@ /* * SPDX-License-Indentifier: AGPL-3.0-or-later * * Copyright (©) 2016-2022 EPFL (École Polytechnique Fédérale de Lausanne), * Laboratory (LSMS - Laboratoire de Simulation en Mécanique des Solides) * * 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 "polonsky_keer_rey.hh" #include "elastic_functional.hh" #include "logger.hh" #include "loop.hh" #include "model_type.hh" #include #include /* -------------------------------------------------------------------------- */ namespace tamaas { PolonskyKeerRey::PolonskyKeerRey(Model& model, const GridBase& surface, Real tolerance, type variable_type, type constraint_type) : ContactSolver(model, surface, tolerance), variable_type(variable_type), constraint_type(constraint_type) { #define SET_VIEWS(_, __, type) \ case type: { \ setViews(); \ break; \ } switch (model.getType()) { BOOST_PP_SEQ_FOR_EACH(SET_VIEWS, ~, TAMAAS_MODEL_TYPES); } #undef SET_VIEWS search_direction = allocateGrid( operation_type, model.getBoundaryDiscretization()); projected_search_direction = allocateGrid( operation_type, model.getBoundaryDiscretization()); switch (variable_type) { case gap: { model.getBEEngine().registerDirichlet(); primal = gap_view.get(); dual = pressure_view.get(); this->functional.addFunctionalTerm( std::make_shared(*integral_op, this->surface)); break; } case pressure: { model.getBEEngine().registerNeumann(); primal = pressure_view.get(); dual = gap_view.get(); this->functional.addFunctionalTerm( std::make_shared(*integral_op, this->surface)); break; } } } /* -------------------------------------------------------------------------- */ Real PolonskyKeerRey::solve(std::vector target_v) { // Update in case of a surface change between solves this->surface_stddev = std::sqrt(this->surface.var()); // Printing column headers Logger().get(LogLevel::info) << std::setw(5) << "Iter" << " " << std::setw(15) << "Cost_f" << " " << std::setw(15) << "Error" << '\n' << std::fixed; const Real target = target_v.back(); // Setting uniform value if constraint if (constraint_type == variable_type && std::abs(primal->sum()) <= 0) *primal = target; else if (constraint_type == variable_type) *primal *= target / primal->mean(); else if (constraint_type != variable_type) *primal = this->surface_stddev; // Algorithm variables Real Gold = 1, error = 0; UInt n = 0; bool delta = false; *search_direction = 0; do { // Computing functional gradient functional.computeGradF(*primal, *dual); const Real dbar = meanOnUnsaturated(*dual); // Enforcing dual constraint via gradient if (constraint_type != variable_type) { *dual += 2 * target + dbar; } else { // Centering dual on primal > 0 *dual -= dbar; } // Computing gradient norm const Real G = computeSquaredNorm(*dual); // Updating search direction (conjugate gradient) updateSearchDirection(delta * G / Gold); Gold = G; // Computing critical step const Real tau = computeCriticalStep(target); // Update primal variable delta = updatePrimal(tau); // We should scale to constraint if (constraint_type == variable_type) enforceMeanValue(target); error = computeError(); const Real cost_f = functional.computeF(*primal, *dual); printState(n, cost_f, error); } while (error > this->tolerance and n++ < this->max_iterations); // Final update of dual variable functional.computeGradF(*primal, *dual); + // Log final iteration + logIteration(n, functional.computeF(*primal, *dual), error); + enforceAdmissibleState(); return error; } /* -------------------------------------------------------------------------- */ void PolonskyKeerRey::enforceAdmissibleState() { /// Make dual admissible const Real dual_min = dual->min(); *dual -= dual_min; // Primal is pressure: need to make sure gap is positive if (variable_type == pressure) { *displacement_view = *dual; *displacement_view += this->surface; } // Primal is gap: need to make sure dual is positive (pressure + adhesion) else { *displacement_view = *primal; *displacement_view += this->surface; integral_op->apply(*displacement_view, *pressure_view); *pressure_view -= dual_min; } } /* -------------------------------------------------------------------------- */ /** * Computes \f$ \frac{1}{\mathrm{card}(\{p > 0\})} \sum_{\{p > 0\}}{f_i} \f$ */ Real PolonskyKeerRey::meanOnUnsaturated(const GridBase& field) const { // Sum on unsaturated const Real sum = Loop::reduce( [] CUDA_LAMBDA(Real & p, const Real& f) { return (p > 0) ? f : 0; }, *primal, field); // Number of unsaturated points const UInt n_unsat = Loop::reduce( [] CUDA_LAMBDA(Real & p) -> UInt { return (p > 0); }, *primal); return sum / n_unsat; } /* -------------------------------------------------------------------------- */ /** * Computes \f$ \sum_{\{p > 0\}}{f_i^2} \f$ */ Real PolonskyKeerRey::computeSquaredNorm(const GridBase& field) const { return Loop::reduce( [] CUDA_LAMBDA(Real & p, const Real& f) { return (p > 0) ? f * f : 0; }, *primal, field); } /* -------------------------------------------------------------------------- */ /** * Computes \f$ \tau = \frac{ \sum_{\{p > 0\}}{q_i't_i} }{ \sum_{\{p > 0\}}{r_i' * t_i} } \f$ */ Real PolonskyKeerRey::computeCriticalStep(Real target) { integral_op->apply(*search_direction, *projected_search_direction); const Real rbar = meanOnUnsaturated(*projected_search_direction); if (constraint_type == variable_type) *projected_search_direction -= rbar; else { *projected_search_direction += 2 * target + rbar; } const Real num = Loop::reduce( [] CUDA_LAMBDA(const Real& p, const Real& q, const Real& t) { return (p > 0) ? q * t : 0; }, *primal, *dual, *search_direction); const Real denum = Loop::reduce( [] CUDA_LAMBDA(const Real& p, const Real& r, const Real& t) { return (p > 0) ? r * t : 0; }, *primal, *projected_search_direction, *search_direction); return num / denum; } /* -------------------------------------------------------------------------- */ /** * Update steps: * 1. \f$\mathbf{p} = \mathbf{p} - \tau \mathbf{t} \f$ * 2. Truncate all \f$p\f$ negative * 3. For all points in \f$I_\mathrm{na} = \{p = 0 \land q < 0 \}\f$ do \f$p_i = * p_i - \tau q_i\f$ */ bool PolonskyKeerRey::updatePrimal(Real step) { const UInt na_num = Loop::reduce( [step] CUDA_LAMBDA(Real & p, const Real& q, const Real& t) -> UInt { p -= step * t; // Updating primal if (p < 0) p = 0; // Truncating neg values if (p == 0 && q < 0) { // If non-admissible state p -= step * q; return 1; } return 0; }, *primal, *dual, *search_direction); return na_num == 0; } /* -------------------------------------------------------------------------- */ /** * Error is based on \f$ \sum{p_i q_i} \f$ */ Real PolonskyKeerRey::computeError() { /// Making sure dual respects constraint *dual -= dual->min(); /// Complementarity error const Real error = primal->dot(*dual); if (std::isnan(error)) TAMAAS_EXCEPTION("Encountered NaN in complementarity error: this may be " "caused by a contact area of a single node."); const Real norm = [this]() { if (variable_type == pressure) return std::abs(primal->sum() * this->surface_stddev); return std::abs(dual->sum() * this->surface_stddev); }(); return std::abs(error) / (norm * primal->getGlobalNbPoints()); } /* -------------------------------------------------------------------------- */ /** * Do \f$\mathbf{t} = \mathbf{q}' + \delta \frac{R}{R_\mathrm{old}}\mathbf{t} * \f$ */ void PolonskyKeerRey::updateSearchDirection(Real factor) { Loop::loop( [factor] CUDA_LAMBDA(Real & p, Real & q, Real & t) { t = (p > 0) ? q + factor * t : 0; }, *primal, *dual, *search_direction); } /* -------------------------------------------------------------------------- */ void PolonskyKeerRey::enforceMeanValue(Real mean) { *primal *= mean / primal->mean(); } } // namespace tamaas /* -------------------------------------------------------------------------- */