diff --git a/src/solvers/kato_saturated.cpp b/src/solvers/kato_saturated.cpp index 2503943..20b451b 100644 --- a/src/solvers/kato_saturated.cpp +++ b/src/solvers/kato_saturated.cpp @@ -1,240 +1,239 @@ /* * SPDX-License-Indentifier: AGPL-3.0-or-later * * Copyright (©) 2016-2024 EPFL (École Polytechnique Fédérale de Lausanne), * Laboratory (LSMS - Laboratoire de Simulation en Mécanique des Solides) * Copyright (©) 2020-2024 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 "kato_saturated.hh" #include "logger.hh" #include #include /* -------------------------------------------------------------------------- */ namespace tamaas { /* -------------------------------------------------------------------------- */ KatoSaturated::KatoSaturated(Model& model, const GridBase& surface, Real tolerance, Real pmax) : PolonskyKeerRey(model, surface, tolerance, PolonskyKeerRey::pressure, PolonskyKeerRey::pressure), pmax(pmax) { model.request("KatoSaturated::residual_displacement", model.getType(), model.getBoundaryDiscretization(), 1); } /* -------------------------------------------------------------------------- */ Real KatoSaturated::solve(std::vector load) { GridBase initial_surface = surface; GridBase& residual_disp = model.field("KatoSaturated::residual_displacement"); residual_disp = 0; const auto norm = surface.var(); auto negative = [this, norm](const GridBase& f) { const auto neg_norm = Loop::reduce( [](const Real& f) { return (f < 0) * f * f; }, f); return neg_norm / (norm * f.getGlobalNbPoints()) > this->tolerance; }; UInt n = 0; do { surface = initial_surface; surface -= residual_disp; PolonskyKeerRey::solve(load); // Update the rough surface Loop::loop([] CUDA_LAMBDA(Real & h_pl, Real g) { h_pl -= g * (g < 0); }, residual_disp, *this->dual); } while (negative(*this->dual) and n++ < this->max_iterations); surface = initial_surface; *this->displacement_view += residual_disp; return Real(n >= this->max_iterations); } /* -------------------------------------------------------------------------- */ Real KatoSaturated::meanOnUnsaturated(const GridBase& /*field*/) const { return 0; } Real KatoSaturated::computeSquaredNorm(const GridBase& /*field*/) const { return 1.; } void KatoSaturated::updateSearchDirection(Real /*factor*/) { *this->search_direction = *this->dual; } Real KatoSaturated::computeCriticalStep(Real /*target*/) { // integral_op->apply(*search_direction, *projected_search_direction); // Real num = search_direction->dot(*search_direction); // Real denum = projected_search_direction->dot(*search_direction); // return 0.1 * num / denum; return 1; } bool KatoSaturated::updatePrimal(Real step) { UInt na_num = Loop::reduce( [step] CUDA_LAMBDA(Real & p, const Real& /*q*/, const Real& t) -> UInt { p -= step * t; // Updating primal return 0; // if (p < 0) // p = 0; // Truncating neg values // if (p == 0 && q < 0) { // If non-admissible state // p -= step * q; // return 1; // } else // return 0; }, *primal, *dual, *search_direction); return na_num == 0; } /* -------------------------------------------------------------------------- */ Real KatoSaturated::computeError() const { // We shift the gap by the minimum on unsaturated area const auto pmax = this->pmax; const Real shift = Loop::reduce( [pmax] CUDA_LAMBDA(Real p, Real d) { return (p < pmax) ? d : std::numeric_limits::max(); }, *primal, *dual); // Ignore points that are saturated const Real error = Loop::reduce( [pmax, shift] CUDA_LAMBDA(Real p, Real d) { - return (p < pmax) ? p * (d - shift) : 0; + return (p < pmax) * p * (d - shift); }, *primal, *dual); if (std::isnan(error)) throw nan_error{ TAMAAS_MSG("Encountered NaN in complementarity error: this may be ", "caused by a contact area of a single node.")}; Real norm = 1; if (variable_type == pressure) norm = std::abs(primal->sum() * this->surface_stddev); else norm = std::abs(dual->sum() * this->surface_stddev); - norm *= primal->getGlobalNbPoints(); return std::abs(error) / norm; } /* -------------------------------------------------------------------------- */ void KatoSaturated::enforceMeanValue(Real mean) { // We want to cancel the difference between saturated alpha + t and the // applied pressure const auto pmax = this->pmax; *primal -= primal->mean(); auto f = [&](Real scale) { Real sum = Loop::reduce( [pmax, scale] CUDA_LAMBDA(Real t) -> Real { t += scale; if (t > pmax) return pmax; if (t < 0) return 0; return t; }, *this->primal); sum /= this->primal->getGlobalNbPoints(); return sum - mean; }; if (pmax < mean) throw std::runtime_error{TAMAAS_MSG("cannot find equilibrium")}; // Dichotomy + Secant Newton on f // Initial points Real x_n_2 = -primal->max(), x_n_1 = -primal->min(), x_n = 0.; Real f_n_2 = 0., f_n_1 = 0., f_n = 0.; // Find a not-too-large search interval Logger().get(LogLevel::debug) << TAMAAS_MSG("Searching equilibrium interval"); while (std::signbit(f(x_n_1)) == std::signbit(f(x_n_2))) { x_n_1 *= 10; x_n_2 *= 10; } Logger().get(LogLevel::debug) << TAMAAS_MSG( "Reducing interval [abs(x1/x2) = ", std::abs(x_n_1 / x_n_2), ']'); UInt n_dic = 10; f_n_1 = f(x_n_1); for (UInt i = 0; i < n_dic; ++i) { x_n = 0.5 * (x_n_1 + x_n_2); f_n = f(x_n); if (std::signbit(f_n) == std::signbit(f_n_1)) { x_n_1 = x_n; f_n_1 = f_n; } else x_n_2 = x_n; } Logger().get(LogLevel::debug) << TAMAAS_MSG( "Starting Newton secant [abs(x1/x2) = ", std::abs(x_n_1 / x_n_2), ']'); // Secant loop do { f_n_2 = f(x_n_2); f_n_1 = f(x_n_1); if (f_n_1 == f_n_2) break; // Avoid nans x_n = x_n_1 - f_n_1 * (x_n_1 - x_n_2) / (f_n_1 - f_n_2); f_n = f(x_n); x_n_2 = x_n_1; x_n_1 = x_n; } while (std::abs(f_n / mean) > 1e-14); // Pressure update Loop::loop( [pmax, x_n] CUDA_LAMBDA(Real & t) { t += x_n; if (t > pmax) t = pmax; else if (t < 0) t = 0.; }, *this->primal); } /* -------------------------------------------------------------------------- */ void KatoSaturated::enforceAdmissibleState() { /// Make dual admissible const auto pmax = this->pmax; Real shift = Loop::reduce( [pmax] CUDA_LAMBDA(Real p, Real d) { return (p < pmax) ? d : std::numeric_limits::max(); }, *primal, *dual); *dual -= shift; *displacement_view = *dual; *displacement_view += this->surface; } } // namespace tamaas diff --git a/src/solvers/polonsky_keer_rey.cpp b/src/solvers/polonsky_keer_rey.cpp index 85a4352..7e8e3ee 100644 --- a/src/solvers/polonsky_keer_rey.cpp +++ b/src/solvers/polonsky_keer_rey.cpp @@ -1,332 +1,332 @@ /* * SPDX-License-Indentifier: AGPL-3.0-or-later * * Copyright (©) 2016-2024 EPFL (École Polytechnique Fédérale de Lausanne), * Laboratory (LSMS - Laboratoire de Simulation en Mécanique des Solides) * Copyright (©) 2020-2024 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 "polonsky_keer_rey.hh" #include "elastic_functional.hh" #include "logger.hh" #include "loop.hh" #include "model_type.hh" #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) { model_type_dispatch( [this](auto&& _) { constexpr auto type = std::decay_t::value; setViews(); }, model.getType()); search_direction = allocateGrid( operation_type, model.getBoundaryDiscretization()); projected_search_direction = allocateGrid( operation_type, model.getBoundaryDiscretization()); setupFunctional(); } /* -------------------------------------------------------------------------- */ void PolonskyKeerRey::setIntegralOperator(std::string name) { auto op = model.getIntegralOperator(name); Logger().get(LogLevel::debug) << TAMAAS_MSG("setting operator ", name); if ((variable_type == gap and op->getKind() != IntegralOperator::dirichlet) or (variable_type == pressure and op->getKind() != IntegralOperator::neumann)) throw model_type_error{TAMAAS_MSG("operator kind ", op->getKind(), " is not compatible with variable type ", variable_type)}; if (op->getType() != model_type::basic_1d and op->getType() != model_type::basic_2d) throw model_type_error{TAMAAS_MSG("operator type ", op->getType(), " should be a 'basic' model type")}; integral_op = std::move(op); setupFunctional(); } /* -------------------------------------------------------------------------- */ void PolonskyKeerRey::setupFunctional() { Logger().get(LogLevel::debug) << TAMAAS_MSG("functional setup"); this->functional.clear(); if (not integral_op) { model_type_dispatch( [this](auto&& _) { constexpr auto type = std::decay_t::value; defaultOperator(); }, model.getType()); } 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 auto red = Loop::reduce( [] CUDA_LAMBDA(const Real& p, const Real& f) { return Vector({(p > 0) * f, static_cast(p > 0)}); }, *primal, field); return red(0) / red(1); } /* -------------------------------------------------------------------------- */ /** * 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; }, *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); const Real shift = [rbar, target, this]() { if (constraint_type == variable_type) return rbar; else return -(2 * target + rbar); }(); const auto red = Loop::reduce( [shift] CUDA_LAMBDA(const Real& p, const Real& q, const Real& r, const Real& t) { return Vector({(p > 0) * q * t, (p > 0) * (r - shift) * t}); }, *primal, *dual, *projected_search_direction, *search_direction); return red(0) / red(1); } /* -------------------------------------------------------------------------- */ /** * 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 p *= p >= 0; // Truncating neg values const auto na = p == 0 and q < 0; p -= na * step * q; // Adding point to active set return na; }, *primal, *dual, *search_direction); return na_num == 0; } /* -------------------------------------------------------------------------- */ /** * Error is based on \f$ \sum{p_i q_i} \f$ */ Real PolonskyKeerRey::computeError() const { /// Complementarity error const Real primal_sum = primal->sum(); const Real error = primal->dot(*dual) - primal_sum * dual->min(); if (std::isnan(error)) throw nan_error{ TAMAAS_MSG("Encountered NaN in complementarity error: this may be " "caused by a contact area of a single node.")}; const Real norm = [this, primal_sum]() { 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()); + return std::abs(error) / norm; } /* -------------------------------------------------------------------------- */ /** * 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); }, *primal, *dual, *search_direction); } /* -------------------------------------------------------------------------- */ void PolonskyKeerRey::enforceMeanValue(Real mean) { *primal *= mean / primal->mean(); } } // namespace tamaas /* -------------------------------------------------------------------------- */