diff --git a/src/solvers/kato_saturated.cpp b/src/solvers/kato_saturated.cpp index 7d41b4d..c74a7b2 100644 --- a/src/solvers/kato_saturated.cpp +++ b/src/solvers/kato_saturated.cpp @@ -1,159 +1,173 @@ /** * @file * @section LICENSE * * Copyright (©) 2016-19 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 "kato_saturated.hh" #include "logger.hh" #include #include /* -------------------------------------------------------------------------- */ namespace tamaas { /* -------------------------------------------------------------------------- */ KatoSaturated::KatoSaturated(Model& model, const GridBase& surface, - Real tolerance, Real pmax) + Real tolerance, Real pmax) : PolonskyKeerRey(model, surface, tolerance, PolonskyKeerRey::pressure, PolonskyKeerRey::pressure), pmax(pmax) {} /* -------------------------------------------------------------------------- */ 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() { // We shift the gap by the minimum on unsaturated area Real shift = Loop::reduce( [&] CUDA_LAMBDA(const auto& p, const auto& d) { return (p < pmax) ? d : std::numeric_limits::max(); }, *primal, *dual); *dual -= shift; Real norm = 1; // Ignore points that are saturated Real error = Loop::reduce( [&] CUDA_LAMBDA(auto p, auto d) { return (p < pmax) ? p * d : 0; }, *primal, *dual); if (std::isnan(error)) TAMAAS_EXCEPTION("Encountered NaN in complementarity error: this may be " "caused by a contact area of a single node."); if (variable_type == pressure) norm = std::abs(primal->sum() * this->surface_stddev); else norm = std::abs(dual->sum() * this->surface_stddev); norm *= primal->getNbPoints(); return std::abs(error) / norm; } /* -------------------------------------------------------------------------- */ void KatoSaturated::enforceMeanValue(Real mean) { // We want to cancel the difference between saturated alpha + t and the // applied pressure *primal -= primal->mean(); auto f = [&](Real scale) { Real sum = Loop::reduce( [&] CUDA_LAMBDA(Real t) { t += scale; if (t > pmax) return pmax; else if (t < 0) return 0.; else return t; }, *this->primal); sum /= this->primal->getNbPoints(); return sum - mean; }; if (pmax < mean) TAMAAS_EXCEPTION("Cannot find equilibrium"); // Secant Newton on f // Initial points Real x_n_2 = 0., x_n_1 = pmax - primal->min(), x_n = 0.; Real f_n_2 = 0., f_n_1 = 0., f_n = 0.; // Secant loop do { f_n_2 = f(x_n_2); f_n_1 = f(x_n_1); 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( [&] CUDA_LAMBDA(auto& t) { t += x_n; if (t > pmax) t = pmax; else if (t < 0) t = 0.; }, *this->primal); } +/* -------------------------------------------------------------------------- */ +void KatoSaturated::enforceAdmissibleState() { + /// Make dual admissible + Real shift = Loop::reduce( + [&] CUDA_LAMBDA(const auto& p, const auto& 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/kato_saturated.hh b/src/solvers/kato_saturated.hh index 73be942..2ddf3bd 100644 --- a/src/solvers/kato_saturated.hh +++ b/src/solvers/kato_saturated.hh @@ -1,60 +1,62 @@ /** * @file * @section LICENSE * * Copyright (©) 2016-19 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 KATO_SATURATED_HH #define KATO_SATURATED_HH /* -------------------------------------------------------------------------- */ #include "polonsky_keer_rey.hh" /* -------------------------------------------------------------------------- */ namespace tamaas { /* -------------------------------------------------------------------------- */ /// Polonsky-Keer algorithm with saturation of the pressure class KatoSaturated : public PolonskyKeerRey { public: /// Constructor KatoSaturated(Model& model, const GridBase& surface, Real tolerance, Real pmax); ~KatoSaturated() override = default; public: /// Mean on unsaturated constraint zone Real meanOnUnsaturated(const GridBase& field) const override; /// Compute squared norm Real computeSquaredNorm(const GridBase& field) const override; /// Update search direction void updateSearchDirection(Real factor) override; /// Compute critical step Real computeCriticalStep(Real target = 0) override; /// Update primal and check non-admissible state bool updatePrimal(Real step) override; /// Compute error/stopping criterion Real computeError() override; /// Enfore mean value constraint void enforceMeanValue(Real mean) override; + /// Enforce contact constraints on final state + void enforceAdmissibleState() override; protected: Real pmax; ///< saturation pressure }; } // namespace tamaas #endif // KATO_SATURATED_HH diff --git a/src/solvers/polonsky_keer_rey.cpp b/src/solvers/polonsky_keer_rey.cpp index 0693a62..18b891a 100644 --- a/src/solvers/polonsky_keer_rey.cpp +++ b/src/solvers/polonsky_keer_rey.cpp @@ -1,297 +1,303 @@ /** * @file * @section LICENSE * * Copyright (©) 2016-19 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 "fft_plan_manager.hh" #include "logger.hh" #include "loop.hh" #include "model_type.hh" #include #include /* -------------------------------------------------------------------------- */ __BEGIN_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( new functional::ElasticFunctionalGap(*integral_op, this->surface), true); break; } case pressure: { model.getBEEngine().registerNeumann(); primal = pressure_view.get(); dual = gap_view.get(); this->functional.addFunctionalTerm( new functional::ElasticFunctionalPressure(*integral_op, this->surface), true); break; } } } /* -------------------------------------------------------------------------- */ Real PolonskyKeerRey::solve(std::vector target_v) { const Real target = target_v.back(); Real G = 0, Gold = 1, error = 0, error_norm = 1; UInt n = 0; bool delta = false; *search_direction = 0; //*dual = 0; // Printing column headers Logger().get(LogLevel::info) << std::setw(5) << "Iter" << " " << std::setw(15) << "Cost_f" << " " << std::setw(15) << "Error" << '\n' << std::fixed; // 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; do { // Computing functional gradient functional.computeGradF(*primal, *dual); 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 G = computeSquaredNorm(*dual); // Updating search direction (conjugate gradient) updateSearchDirection(delta * G / Gold); Gold = G; // Computing critical step Real tau = computeCriticalStep(target); // Update primal variable delta = updatePrimal(tau); // We should scale to constraint if (constraint_type == variable_type) enforceMeanValue(target); error = computeError() / error_norm; Real cost_f = functional.computeF(*primal, *dual); printState(n, cost_f, error); } while (error > this->tolerance && n++ < this->max_iterations); // Final update of dual variable functional.computeGradF(*primal, *dual); + enforceAdmissibleState(); + + return error; +} + +/* -------------------------------------------------------------------------- */ +void PolonskyKeerRey::enforceAdmissibleState() { + /// Make dual admissible 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; } - - return error; } /* -------------------------------------------------------------------------- */ /** * Computes \f$ \frac{1}{\mathrm{card}(\{p > 0\})} \sum_{\{p > 0\}}{f_i} \f$ */ Real PolonskyKeerRey::meanOnUnsaturated(const GridBase& field) const { // Sum on unsaturated Real sum = Loop::reduce( [] CUDA_LAMBDA(Real & p, const Real& f) { return (p > 0) ? f : 0; }, *primal, field); // Number of unsaturated points 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); Real rbar = meanOnUnsaturated(*projected_search_direction); if (constraint_type == variable_type) *projected_search_direction -= rbar; else { *projected_search_direction += 2 * target + rbar; } 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); 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) { 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; } else 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(); Real norm = 1; 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."); if (variable_type == pressure) norm = std::abs(primal->sum() * this->surface_stddev); else norm = std::abs(dual->sum() * this->surface_stddev); norm *= primal->getNbPoints(); 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 : 0; }, *primal, *dual, *search_direction); } /* -------------------------------------------------------------------------- */ void PolonskyKeerRey::enforceMeanValue(Real mean) { - *primal *= mean / primal->mean(); + *primal *= mean / primal->mean(); } /* -------------------------------------------------------------------------- */ void PolonskyKeerRey::addFunctionalTerm(functional::Functional* func) { functional.addFunctionalTerm(func, false); } __END_TAMAAS__ /* -------------------------------------------------------------------------- */ diff --git a/src/solvers/polonsky_keer_rey.hh b/src/solvers/polonsky_keer_rey.hh index 46af569..fa9538c 100644 --- a/src/solvers/polonsky_keer_rey.hh +++ b/src/solvers/polonsky_keer_rey.hh @@ -1,128 +1,130 @@ /** * @file * @section LICENSE * * Copyright (©) 2016-19 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 __POLONSKY_KEER_REY_HH__ #define __POLONSKY_KEER_REY_HH__ /* -------------------------------------------------------------------------- */ #include "contact_solver.hh" #include "grid_view.hh" #include "meta_functional.hh" #include "westergaard.hh" /* -------------------------------------------------------------------------- */ __BEGIN_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: using ContactSolver::solve; /// 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(); + /// Enforce contact constraints on final state + virtual void enforceAdmissibleState(); /// Add term to functional void addFunctionalTerm(functional::Functional* func); /// Enfore mean value constraint virtual void enforceMeanValue(Real mean); 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; }; /* -------------------------------------------------------------------------- */ 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) // I noz is fugly - TODO factory? if (bdim == 1) { operation_type = model_type::basic_1d; if (variable_type == gap) integral_op = WESTERGAARD(basic_1d, dirichlet, "westergaard_dirichlet"); else integral_op = WESTERGAARD(basic_1d, neumann, "westergaard_neumann"); } else if (bdim == 2) { operation_type = model_type::basic_2d; if (variable_type == gap) integral_op = WESTERGAARD(basic_2d, dirichlet, "westergaard_dirichlet"); else integral_op = WESTERGAARD(basic_2d, neumann, "westergaard_neumann"); } #undef WESTERGAARD } __END_TAMAAS__ #endif // __POLONSKY_KEER_REY_HH__ diff --git a/tests/test_saturated_pressure.py b/tests/test_saturated_pressure.py index 0fa9edc..d3a4acb 100644 --- a/tests/test_saturated_pressure.py +++ b/tests/test_saturated_pressure.py @@ -1,67 +1,66 @@ # -*- coding: utf-8 -*- # @file # @section LICENSE # # Copyright (©) 2016-19 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 . from __future__ import print_function, division import numpy as np import tamaas as tm def test_saturated_pressure(tamaas_fixture): grid_size = 256 load = 0.06 p_sat = 0.4 iso = tm.Isopowerlaw2D() iso.q0 = 4 iso.q1 = 4 iso.q2 = 16 iso.hurst = 0.8 sg = tm.SurfaceGeneratorFilter2D() sg.random_seed = 2 sg.setSizes([grid_size, grid_size]) sg.setSpectrum(iso) surface = sg.buildSurface() surface *= 0.01 / iso.rmsHeights() surface -= np.max(surface) model = tm.ModelFactory.createModel(tm.model_type.basic_2d, [1., 1.], [grid_size, grid_size]) model.E = 1. model.nu = 0 solver = tm.KatoSaturated(model, surface, 1e-12, p_sat) solver.setMaxIterations(6000) assert solver.solve(load) < 1e-12 tractions = model['traction'] mean_pressure_error = abs(np.mean(tractions)-load)/load max_pressure_error = np.max(tractions)-p_sat gaps = model['gap'] gaps[np.where(tractions == p_sat)] = 0. - # press = tractions - # orthogonality_error = np.max(press*gaps) / (press.max() * gaps.max()) + press = tractions + orthogonality_error = np.max(press*gaps) / (press.max() * gaps.max()) - # assert mean_pressure_error < 1e-12 and max_pressure_error < 1e-12 and \ - # orthogonality_error < 2e-10 - assert mean_pressure_error < 1e-12 and max_pressure_error < 1e-12 + assert mean_pressure_error < 1e-12 and max_pressure_error < 1e-12 and \ + orthogonality_error < 4e-8